🛡️

身份验证

答对题目即可进入,验证你是谁

剩余尝试次数:3

新手避坑指南 — 10 个最常见的错误

C语言学习教程

新手避坑指南 — 10 个最常见的错误

🌟 课前故事:每个高手都曾是”踩坑达人”

有一个小秘密想告诉你:那些写了几十年代码的”大神”们,他们刚学编程时,和你犯过一模一样的错误。

少写了一个分号,编译器报了一屏幕的错误;把 = 当成 ==,找了一个小时的bug;scanf 忘了 &,程序直接崩溃……这些都是编程的”成人礼”——几乎每个C程序员都经历过。

错误不是失败,而是学习的捷径。 每次你踩到一个坑、搞懂一个报错信息、修复一个bug,你就比5分钟前的自己更懂C语言。

这节课整理了10个新手最高频的错误。不是让你”背诵”,而是让你”提前认识”——当这些坑出现在你面前时,你能认出来,然后绕过去。


📖 概念:两种类型的错误

在学习具体错误之前,先了解错误的两种基本类型:

┌───────────────────────────────────────────────┐
│               程序错误的分类                    │
│                                               │
│  📝 编译错误 (Compile Error)                   │
│  ├── 发生在编译阶段                              │
│  ├── 编译器会给出错误信息(行号+原因)              │
│  ├── 例子:忘记分号、拼写错误、类型不匹配            │
│  └── 特点:程序根本跑不起来,必须修好才能运行        │
│                                               │
│  ⚡ 运行错误 (Runtime Error)                     │
│  ├── 发生在程序运行阶段                            │
│  ├── 编译通过了,但运行时期溃或结果不对              │
│  ├── 例子:除以0、数组越界、scanf忘&               │
│  └── 特点:更难发现,需要仔细检查和测试              │
│                                               │
│  🐛 逻辑错误 (Logic Error)                       │
│  ├── 程序能跑,结果也"看起来"对,但实际上是错的       │
│  ├── 编译器不会报错,程序也不崩溃                   │
│  ├── 例子:=写成==、公式写错、循环条件多1或少1       │
│  └── 特点:最难发现!需要仔细验证结果               │
└───────────────────────────────────────────────┘

坑1:忘记分号 ;

错误描述

C语言中,每条语句必须以分号 ; 结尾。分号就像句号——告诉编译器”这句话说完了”。

错误示例

// ❌ 错误代码
#include <stdio.h>

int main() {
    int x = 5          // 缺少分号!
    printf("%d", x)    // 缺少分号!(通常编译器先报告这一行)
    return 0;
}

编译器报错

error: expected ';' before 'printf'

正确代码

// ✅ 正确代码
#include <stdio.h>

int main() {
    int x = 5;           // 分号!
    printf("%d", x);     // 分号!
    return 0;            // 分号!
}

为什么容易忘?

  1. 写完一行急着写下一行,手比脑快
  2. 从其他语言转过来(Python不需要分号)
  3. 大括号 } 前面忘记加分号

怎么养成习惯?

每次写语句时,先写分号再写内容,或者写内容时就把分号带上。把它当成”闭环”——有开头就有结尾。


坑2:=== 搞混

错误描述

这是C语言中最高频最隐蔽后果最严重的错误。

错误示例

// ❌ 错误代码
int score = 85;

if (score = 100) {           // 注意:这是一个等号!
    printf("满分!\n");
}
// score 被改成了 100!
// 条件永远为真!
这个错误可怕的地方:
1. 编译器通常不报错(语法上是合法的!)
2. 程序能正常运行
3. 但结果悄悄错了
4. 而且你的变量值被偷偷修改了

正确代码

// ✅ 正确代码
int score = 85;

if (score == 100) {          // 两个等号:比较
    printf("满分!\n");
}
// score 的值没有变
// 条件只在 score 真的等于100时才成立

防御技巧:常量放左边

// ❌ 容易被误写
if (x == 10)     // 如果写成 if (x = 10),编译器不报错

// ✅ 推荐写法:常量放左边
if (10 == x)     // 如果写成 if (10 = x),编译器报错!
                 // error: lvalue required as left operand of assignment

为什么? 因为你不能给常量 10 赋值,所以编译器会帮你拦住这个错误。


坑3:scanf 忘记 &

错误描述

scanf 需要知道数据存放的地址,而 &变量名 就是取这个地址。

错误示例

// ❌ 错误代码
int age;
scanf("%d", age);    // 忘记 &!
// 程序行为:崩溃(Windows)或者 age 的值不变(有时"看起来正常")

编译器报错

warning: format '%d' expects argument of type 'int *',
         but argument 2 has type 'int'

(注意:有些编译器只给警告,不报错!程序能编译通过但运行崩溃!)

正确代码

// ✅ 正确代码
int age;
scanf("%d", &age);   // & 不能少

// 例外:字符串数组不加 &
char name[50];
scanf("%s", name);   // name 本身就是地址,不需要 &name

记忆口诀

“scanf 要地址,变量前加 &;数组是例外,本身就是地址。“

为什么有时候”看起来正常”?

这其实是最危险的情况。如果你忘了 &scanf 会把 age(一个随机数)当成地址,向那个随机地址写数据。如果碰巧那个随机地址是可写的,程序就不崩溃,但数据没有存到你想要的地方。


坑4:整数除法截断

错误描述

两个整数相除,结果还是整数——小数部分被直接丢弃(不是四舍五入)。

错误示例

// ❌ 错误代码
float score = 85 / 100;     // score = 0.000000!
float half = 5 / 2;          // half = 2.000000!
double avg = (1 + 2 + 3) / 3; // avg = 2.000000!

// 你以为 score = 0.85,实际是 0.0
// 因为 85 / 100 两个整数 → 结果 0 → 赋值给 float → 0.0

正确代码

// ✅ 正确代码——至少让一个操作数是浮点数
float score = 85.0 / 100;       // 0.850000 ✅
float half = 5.0 / 2;            // 2.500000 ✅
double avg = (1 + 2 + 3) / 3.0;  // 2.000000 ✅

// 或者强制类型转换
float score2 = (float)85 / 100;  // 0.850000 ✅
float half2 = (float)5 / 2;      // 2.500000 ✅

图解:发生了什么

int a = 5, b = 2;
float result = a / b;

执行过程:
第1步:a / b = 5 / 2 = 2    ← 整数除法,得2
第2步:result = 2           ← 把2转成float(2.0f)赋给result

结果:result = 2.0          ← 小数部分在第1步就丢了!

这个错误最常见在哪里?

// 各种"看似应该有小数"的计算
float percentage = (correct / total) * 100;  // ❌ 如果correct和total都是int
float ratio = width / height;                // ❌ 宽高比永远是整数
float average = sum / count;                 // ❌ 平均分永远是整数

坑5:数组越界

错误描述

数组索引从 0 开始,arr[5] 的合法索引是 0~4。访问 arr[5] 或更大的索引叫”越界”。

错误示例

// ❌ 错误代码
int arr[5] = {1, 2, 3, 4, 5};  // 5个元素:索引 0,1,2,3,4

arr[5] = 6;    // 越界!最大索引是4
arr[10] = 10;  // 严重越界!写到了别人的内存
arr[-1] = 0;   // 负索引!也越界

// 循环中更容易犯
for (int i = 0; i <= 5; i++) {  // ❌ i=5 时越界!
    arr[i] = i * 2;              // arr[5] 不存在!
}

编译器报错?

通常不报错! C语言不检查数组越界——这是C语言的灵活性,也是它的危险性。越界访问可能导致:

  1. 读到别人的数据(逻辑错误)
  2. 覆盖别人的数据(数据损坏)
  3. 程序崩溃(运气好)
  4. 安全漏洞(缓冲溢出攻击)

正确代码

// ✅ 正确代码
int arr[5] = {1, 2, 3, 4, 5};

// 用 < 而不是 <=
for (int i = 0; i < 5; i++) {    // i=0,1,2,3,4 ✅
    printf("%d ", arr[i]);
}

// 使用宏定义避免"魔法数字"
#define ARR_SIZE 5
int arr[ARR_SIZE];
for (int i = 0; i < ARR_SIZE; i++) {
    arr[i] = i + 1;
}

图解:数组内存布局

int arr[5];  声明一个5元素的数组

内存地址:  [0]     [1]     [2]     [3]     [4]    ← 合法区域
           ┌──────┬──────┬──────┬──────┬──────┬──────┐
           │ arr  │ arr  │ arr  │ arr  │ arr  │ 其   │
           │ [0]  │ [1]  │ [2]  │ [3]  │ [4]  │ 他   │
           │      │      │      │      │      │ 变   │
           │      │      │      │      │      │ 量   │
           └──────┴──────┴──────┴──────┴──────┴──────┘

                                    如果访问 arr[5],
                                    就会踩到别人的地盘!

坑6:字符串比较不能用 ==

错误描述

== 比较的是地址(两个变量在内存中的位置),而不是字符串的内容

错误示例

// ❌ 错误代码
char name1[] = "Tom";
char name2[] = "Tom";

if (name1 == name2) {           // ❌ 比较的是地址,永远不相等!
    printf("名字相同\n");       // 不会执行!
}

// 即使和字面量比也不行
if (name1 == "Tom") {           // ❌ 也是比较地址
    printf("名字是Tom\n");      // 结果不确定!
}

正确代码

// ✅ 正确代码
#include <string.h>              // 必须引入这个头文件!

char name1[] = "Tom";
char name2[] = "Tom";

if (strcmp(name1, name2) == 0) {     // strcmp 返回0表示相等
    printf("名字相同\n");            // ✅ 正确!
}

if (strcmp(name1, "Tom") == 0) {     // 和字面量比较也可以
    printf("名字是Tom\n");           // ✅ 正确!
}

strcmp 返回值详解

返回值含义
0两个字符串相等
< 0第一个字符串”小于”第二个(字典序)
> 0第一个字符串”大于”第二个(字典序)
strcmp("abc", "abc")  → 0   // 相等
strcmp("abc", "abd")  → -1  // abc < abd(c在d前面)
strcmp("abc", "abb")  → 1   // abc > abb(c在b后面)
strcmp("abc", "ABC")  → 1   // 大写字母的ASCII码小于小写

坑7:忘记初始化变量

错误描述

声明变量但未赋值时,变量的值是随机的(内存中原来就有的值——“垃圾值”)。

错误示例

// ❌ 错误代码
int x;                // x 的值是随机的!
printf("%d\n", x);    // 可能输出 32767、-858993460 等任何值

int arr[10];          // 数组中每个元素都是随机值
int sum = 0;
for (int i = 0; i < 10; i++) {
    sum += arr[i];    // 累加了一堆垃圾值!
}
printf("%d\n", sum);  // 结果是什么?天知道!

正确代码

// ✅ 正确代码——始终初始化
int x = 0;                   // 初始化为0
printf("%d\n", x);           // 输出:0

int arr[10] = {0};           // 全部初始化为0
int sum = 0;
for (int i = 0; i < 10; i++) {
    sum += arr[i];           // 现在结果可预测了
}
printf("%d\n", sum);         // 输出:0

// 指定初始值
int scores[5] = {85, 90, 78, 92, 88};  // 全部指定
int nums[100] = {0};                     // 全部置零

为什么局部变量是”垃圾值”?

全局变量和静态变量:自动初始化为0
局部变量(函数内部的):
  → 分配在"栈"上
  → 栈内存是复用的——上次用完没清零
  → 所以你拿到的是"别人的旧数据"
  → 这就是为什么局部变量必须手动初始化!

坑8:循环条件写错导致死循环

错误描述

循环的终止条件写错,或者忘记更新循环变量,导致程序永远循环,CPU占用100%。

错误示例

// ❌ 错误1:忘记更新循环变量
int i = 0;
while (i < 10) {
    printf("%d ", i);
    // 忘记 i++; ← 死循环!i 永远是0,条件永远成立
}

// ❌ 错误2:分号让循环体为空
int j = 0;
while (j < 10); {    // ← 注意这个分号!循环体是空的!
    printf("%d ", j);
    j++;             // 这不在循环里面!
}
// 程序卡死在 while (j < 10); 这一行

// ❌ 错误3:条件永远为真
for (int k = 0; k >= 0; k++) {  // k永远>=0,死循环!
    printf("%d ", k);
}

正确代码

// ✅ 正确代码
int i = 0;
while (i < 10) {
    printf("%d ", i);
    i++;             // 别忘了更新循环变量!
}

for (int j = 0; j < 10; j++) {   // for 循环不容易忘(自增写在头部)
    printf("%d ", j);
}

// 注意:while 后面不要随便加分号
while (condition) {   // ✅ 没有分号
    // 循环体
}

如何避免?

  1. 能用 for 的地方尽量用 for(初始化、条件、更新都在一行)
  2. while 时,检查循环体内是否有改变条件的语句
  3. 如果程序”卡住了”不动,首先怀疑死循环

坑9:忘记 #include 头文件

错误描述

使用了某个库函数,但没有 include 对应的头文件,编译器不认识这个函数。

错误示例

// ❌ 错误代码
int main() {
    printf("Hello\n");     // 忘了 #include <stdio.h>!
    return 0;
}

// 或者:
#include <stdio.h>
int main() {
    double root = sqrt(16);  // 忘了 #include <math.h>!
    printf("%f\n", root);
    return 0;
}

编译器报错

warning: implicit declaration of function 'printf'
  或者
error: 'printf' undeclared (first use in this function)

正确代码

// ✅ 正确代码
#include <stdio.h>      // 使用 printf、scanf 需要

// 数学函数需要
#include <math.h>       // 使用 sqrt、pow、sin、cos 等需要
// 注意:编译时可能需要加 -lm(链接数学库)

// 字符串函数需要
#include <string.h>     // 使用 strlen、strcmp、strcpy 等需要

// 通用工具需要
#include <stdlib.h>     // 使用 malloc、rand、atoi 等需要

// 布尔类型需要
#include <stdbool.h>    // 使用 bool、true、false 需要

int main() {
    printf("Hello\n");
    double root = sqrt(16);
    printf("%f\n", root);
    return 0;
}

常用函数和对应头文件速查

函数头文件用途
printf, scanfstdio.h输入输出
sqrt, pow, sinmath.h数学计算
strlen, strcmp, strcpystring.h字符串操作
malloc, free, randstdlib.h内存管理、工具
bool, true, falsestdbool.h布尔类型
toupper, islowerctype.h字符判断和转换

坑10:花括号不配对

错误描述

{} 不是成对出现——多了或者少了。

错误示例

// ❌ 错误1:少了一个 }
int main() {
    if (x > 0) {
        printf("positive\n");
    // 忘记关闭 if 的花括号!
    return 0;
}  // 编译器报告 expected '}',但指向的位置可能很奇怪

// ❌ 错误2:多了或少了导致结构混乱
if (condition1)
    statement1;
    statement2;   // 缩进让人以为它在if里,实际不在!
    statement3;

// 实际等于:
if (condition1) {
    statement1;
}
statement2;       // 总是执行!
statement3;       // 总是执行!

编译器报错

error: expected '}' at end of input

error: expected '}' before 'return'

正确代码

// ✅ 正确代码
int main() {
    if (x > 0) {            // { ← 写的时候就顺手写 }
        printf("positive\n");
    }                       // } ← 配对
    return 0;
}                           // } ← 配对

// 技巧:每写一个 {,立刻在下一行写 },然后回到中间填代码
if (condition) {
    // 先写 { 和 }
    // 再在中间写代码
}

一个小技巧

// 写代码的"支架法":
int main()                         // 1. 写函数头
{                                  // 2. 写 {,立刻换行写 }
                                   // 3. 光标回到中间
    // 你的代码写在这里
}                                  // 4. } 已经在等你了

更隐蔽的情况——if/else 悬挂

// ❌ 错误:else 和哪个 if 配对?
if (a > 0)
    if (b > 0)
        printf("都大于0\n");
else                             // 这个 else 属于 if (b > 0)!不是 if (a > 0)!
    printf("a大于0但b不大于0?\n");

// ✅ 加上花括号后意思就明确了
if (a > 0) {
    if (b > 0) {
        printf("都大于0\n");
    }
} else {                         // 现在 else 明确属于 if (a > 0)
    printf("a不大于0\n");
}

原则: else 和最近的未配对的 if 配对。用花括号让意图明确,不要依赖缩进


🔍 排错流程:看到错误不要慌

当编译器报错时,按这个流程逐步排查:

第一步:读错误信息(最重要!)

错误信息的标准格式:
文件名:行号:列号: 错误级别: 错误描述

例如:
main.c:5:12: error: expected ';' before 'return'
  ↑      ↑  ↑     ↑        ↑
  文件   行  列    级别     描述

不要被一堆英文吓到! 抓住关键词:

第二步:看编译器指向的那一行(和它的上一行)

编译器报的行号有时不完全准确——错误可能在报告位置的前一两行。

第三步:按优先级检查

检查项看什么
1. 分号每条语句末尾有 ; 吗?
2. 括号配对{ }( )" "' ' 都成对吗?
3. 拼写变量名和函数名拼对了吗?大小写对吗?
4. include用到的函数都 include 对应头文件了吗?
5. 类型匹配printf 的占位符和后面的变量类型匹配吗?
6. 逻辑错误= vs ==、整数除法、数组越界

第四步:缩小范围

如果错误太多,先修第一个错误。后面的错误可能是第一个错误引起的连锁反应。

// 例如,忘了一个分号:
int x = 5          // ← 第1个错误:缺少分号
printf("%d", x);   // ← 第2个错误:expected ';' before 'printf'
return 0;          // ← 第3个错误:expected ';' before 'return'

// 修好第一个错误(加分号)后,后面的两个"错误"自动消失!

第五步:二分法调试(逻辑错误)

如果程序能跑但结果不对,用 printf 打印中间变量的值,逐步缩小问题范围:

int a = 5, b = 0;
int result;

printf("DEBUG: a=%d, b=%d\n", a, b);  // 检查输入

result = a / b;                        // ← 这里除以0了!

printf("DEBUG: result=%d\n", result);  // 这行不会执行(程序已崩溃)

📊 常见编译错误信息速查表

错误信息中文解释常见原因
expected ';' before ...在某符号前缺少分号忘记在语句末尾写 ;
'xxx' undeclaredxxx未声明(编译器不认识xxx)拼写错误或忘记 include
expected declaration or statement at end of input文件末尾缺少 }花括号不配对
expected '}' before ...在某符号前缺少 }花括号不配对
implicit declaration of function函数未显式声明忘记 #include 头文件
lvalue required as left operand of assignment赋值左边必须是变量常把 == 写成 =,或者 10=x
incompatible types when assigning赋值类型不兼容char = "A" 应该用单引号
division by zero除以0分母变量为0
segmentation fault (运行时)段错误——访问了不该访问的内存scanf忘&、数组越界、野指针
syntax error语法错误各种语法问题,看具体行
undefined reference to链接时找不到函数函数名拼错,或忘了链接库

💡 排错心智:三个黄金心态

心态1:错误是朋友,不是敌人

编译错误是编译器在帮你找问题。应该感谢编译器——它在你运行程序之前就拦住了bug。没有编译检查的语言(比如JavaScript、Python),错误要到运行时才能发现,更难调试。

心态2:一次只改一个东西

看到10个错误,不要慌,也不要全部一起改。从第一个错误开始改,改完重新编译。很可能修好第一个错误后,后面的9个都消失了。

心态3:不理解就问,但先自己查

遇到不认识的错误,先:

  1. 把错误信息复制到搜索引擎
  2. 看看别人遇到同样问题怎么解决的
  3. 如果还是不懂,再去问老师或同学

这个”先自己琢磨,再求助”的过程,是编程能力成长最快的方式。


💡 练习建议

初级练习

  1. “找茬”练习:故意写错代码(删分号、改拼写、忘include),观察编译器的反应,熟悉各种错误信息
  2. 修复练习:把下面每个错误代码改对
// 练习A:这段代码有几个错误?
include <stdio.h>
int main()
    printf("Hello World\n")
    return 0

// 练习B:这段代码为什么结果不对?
float half = 1 / 2;
printf("一半是:%f\n", half);  // 期望0.5,实际0.0

// 练习C:这段代码为什么崩溃?
int age;
scanf("%d", age);
printf("年龄:%d\n", age);

中级练习

  1. 排错日志:每次遇到新错误,记下来:什么错误、什么原因、怎么修的
  2. 代码审查:和同学交换代码,互相找bug

📝 要点速查清单


🔗 相关主题