C语言期末考试易错知识点总结
⭐
重点复习 考试必看 结合考试题型整理的易错知识点
内容导航
一、选择题/判断题易错点
1. 指针与数组的混淆
易错点1:数组名作为参数
1 2 3 4 5
| void func(int arr[]) { // 错误!这里sizeof(arr)返回指针大小(4/8字节) // 而不是数组总长度 int size = sizeof(arr); // 错误! }
|
**正确做法:**传递数组长度作为额外参数
1 2 3 4 5 6
| void func(int arr[], int n) { // n是数组元素个数 for (int i = 0; i < n; i++) { // ... } }
|
易错点2:指针运算
1 2 3
| int* p; p++; // 跳过 sizeof(int) 字节(通常是4字节) // 而不是1字节!
|
**关键理解:**指针的算术运算基于其指向的数据类型
1 2 3 4 5
| int arr[5] = {1, 2, 3, 4, 5}; int* p = arr;
p + 1; // 指向arr[1],地址增加 sizeof(int) = 4字节 *(p + 2); // 等价于 arr[2]
|
2. 结构体内存对齐
易错点:sizeof(结构体) ≠ 成员大小之和
1 2 3 4 5 6 7 8
| struct Student { char name; // 1字节 int age; // 4字节 float score; // 4字节 };
// sizeof(struct Student) 可能是 12 字节 // 而不是 1 + 4 + 4 = 9 字节!
|
**原因:**编译器会对结构体成员进行内存对齐(通常4字节对齐)
1 2 3
| // 实际内存布局(假设4字节对齐): // [name][空][空][空][age][age][age][age][score][score][score][score] // 1B + 3B填充 + 4B + 4B = 12B
|
位域的使用
1 2 3 4 5
| struct Flags { unsigned int a : 2; // a只占2位 unsigned int b : 3; // b只占3位 unsigned int c : 1; // c只占1位 }; // 总共可能只占1个int(4字节)
|
**注意:**位域可能因平台不同导致跨字节问题
3. 宏定义的副作用
易错点:宏参数未加括号
1 2 3 4 5 6 7
| // 错误的宏定义 #define MAX(a, b) a > b ? a : b
// 调用时 int result = MAX(x + y, z); // 展开为:x + y > z ? x + y : z // 由于运算符优先级,可能得到错误结果!
|
**正确写法:**参数和整体都加括号
1
| #define MAX(a, b) ((a) > (b) ? (a) : (b))
|
易错点:宏替换导致多次求值
1 2 3 4 5 6
| #define SQUARE(x) ((x) * (x))
int a = 5; int result = SQUARE(a++); // 危险! // 展开为:((a++) * (a++)) // a被自增了两次!
|
💡 建议
- 宏参数都要加括号
- 避免在宏参数中使用有副作用的表达式(如++、–)
- 考虑使用inline函数替代复杂宏
4. 全局变量与静态变量
| 变量类型 |
初始化默认值 |
生命周期 |
作用域 |
| 全局变量 |
0 |
整个程序运行期 |
整个文件(可用extern扩展) |
| 局部变量 |
随机值 |
函数执行期 |
函数内部 |
| static局部变量 |
0 |
整个程序运行期 |
函数内部 |
| static全局变量 |
0 |
整个程序运行期 |
当前文件 |
1 2 3 4 5 6
| void func() { int a; // 随机值! static int b; // 自动初始化为0
printf("%d %d\n", a, b); // a的值不确定 }
|
二、程序填空题易错点
1. 循环条件与边界处理
易错点1:循环条件写反
1 2 3 4 5 6
| // 遍历数组 for (int i = 0; i <= n; i++) // 错误!越界 for (int i = 0; i < n; i++) // 正确
for (int i = 1; i <= n; i++) // 从1开始,到n结束 for (int i = 0; i < n; i++) // 从0开始,到n-1结束
|
易错点2:二维数组行列顺序混淆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int matrix[3][4];
// 遍历:先行后列 for (int i = 0; i < 3; i++) { // 行 for (int j = 0; j < 4; j++) { // 列 printf("%d ", matrix[i][j]); } }
// 错误示例:行列颠倒 for (int i = 0; i < 4; i++) { // 错误!i应该到3 for (int j = 0; j < 3; j++) { // 错误!j应该到4 // ... } }
|
2. 指针与数组下标
易错点:指针算术类型不匹配
1 2 3 4 5 6 7 8 9 10
| int arr[5]; int* p = arr;
// 正确的指针访问 *(p + i) // 等价于 arr[i] *(arr + i) // 等价于 arr[i]
// 错误示例 int x = *(arr + i); // 正确 int y = arr + i; // 错误!arr+i是指针,不能直接赋给int
|
易错点:字符串输入处理
1 2 3 4 5 6 7 8 9 10
| char str[10];
// 方法1:scanf(遇到空格停止) scanf("%s", str);
// 方法2:scanf读取整行(包括空格) scanf("%[^\n]", str);
// 方法3:fgets(推荐) fgets(str, sizeof(str), stdin);
|
3. 函数参数传递
易错点:值传递vs地址传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| // 值传递:修改形参不影响实参 void swap1(int a, int b) { int temp = a; a = b; b = temp; } // ❌ 无法交换实际参数
// 地址传递:通过指针修改实参 void swap2(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } // ✅ 可以交换
int x = 10, y = 20; swap1(x, y); // x,y不变 swap2(&x, &y); // x,y被交换
|
4. 动态内存分配
易错点:忘记释放内存
1 2 3 4 5 6 7 8 9 10 11
| // 正确做法 int* p = (int*)malloc(n * sizeof(int)); if (p == NULL) { // 处理内存分配失败 }
// 使用内存...
// 释放内存 free(p); p = NULL; // 避免野指针
|
⚠️ 记忆口诀
- 谁malloc,谁free
- free后置NULL
- 配对使用,防止泄漏
三、编程题易错点
1. 数组越界与字符串处理
易错点:缓冲区溢出
1 2 3 4 5 6 7 8 9 10 11 12
| char src[] = "Hello, World!"; char dest[5]; // 只有5字节空间
// 危险!src有13字节,dest只有5字节 strcpy(dest, src); // ❌ 缓冲区溢出!
// 正确做法:先检查长度 if (strlen(src) < sizeof(dest)) { strcpy(dest, src); // ✅ 安全 } else { printf("目标空间不足\n"); }
|
安全函数替代方案
1 2 3 4 5 6 7 8 9
| // 使用strncpy替代strcpy strncpy(dest, src, sizeof(dest) - 1); dest[sizeof(dest) - 1] = '\0'; // 确保以'\0'结尾
// 使用strncat替代strcat strncat(dest, src, sizeof(dest) - strlen(dest) - 1);
// 使用snprintf替代sprintf snprintf(dest, sizeof(dest), "%s", src);
|
2. 递归函数
易错点:缺少递归终止条件
1 2 3 4 5 6 7 8 9 10 11 12
| // 错误示例:缺少终止条件 int factorial(int n) { return n * factorial(n - 1); // ❌ 无限递归! }
// 正确示例 int factorial(int n) { if (n <= 1) { // ✅ 递归终止条件 return 1; } return n * factorial(n - 1); }
|
递归过深导致栈溢出
1 2 3 4 5 6 7 8
| // 递归深度过大时,改用迭代 int factorial_iterative(int n) { int result = 1; for (int i = 2; i <= n; i++) { result *= i; } return result; }
|
3. 文件读写
易错点1:忘记检查文件是否成功打开
1 2 3 4 5 6 7 8 9 10 11 12 13
| FILE* fp = fopen("data.txt", "r");
// ❌ 直接使用,可能导致崩溃 fscanf(fp, "%d", &n);
// ✅ 正确做法:检查返回值 if (fp == NULL) { printf("无法打开文件\n"); return 1; }
// 使用完记得关闭 fclose(fp);
|
易错点2:处理文件结束符EOF
1 2 3 4 5 6 7 8 9 10
| // 错误示例 while (!feof(fp)) { // ❌ 可能多读一次 fscanf(fp, "%d", &n); printf("%d ", n); }
// 正确示例 while (fscanf(fp, "%d", &n) == 1) { // ✅ 检查返回值 printf("%d ", n); }
|
易错点3:二进制文件读写参数顺序
1 2 3 4 5 6 7
| int arr[10];
// fread参数:指针, 元素大小, 元素个数, 文件指针 fread(arr, sizeof(int), 10, fp); // ✅ 正确
// 错误示例 fread(arr, 10, sizeof(int), fp); // ❌ 参数顺序错误
|
四、高频考点清单
| 知识点 |
易错点 |
考试频率 |
| 指针与数组 |
数组名作为参数传递时的类型差异、指针算术运算 |
⭐⭐⭐⭐⭐ |
| 结构体与共用体 |
内存对齐、位域、共用体成员覆盖 |
⭐⭐⭐⭐⭐ |
| 宏定义与预处理 |
宏参数未加括号、宏替换的副作用 |
⭐⭐⭐⭐ |
| 动态内存 |
malloc/calloc的区别、free后置空指针 |
⭐⭐⭐⭐⭐ |
| 文件操作 |
模式字符串误用、文件指针位置(fseek/ftell) |
⭐⭐⭐⭐ |
| 字符串处理 |
缓冲区溢出、strcat/strcpy的安全使用 |
⭐⭐⭐⭐⭐ |
| 递归函数 |
递归终止条件、栈溢出 |
⭐⭐⭐ |
| 函数参数传递 |
值传递vs地址传递、数组退化为指针 |
⭐⭐⭐⭐⭐ |
五、复习建议
1. 针对性练习
多做历年真题中的易错题型,特别是:
- 指针相关题目
- 结构体和共用体
- 文件操作
- 动态内存分配
2. 代码调试
用printf或调试工具检查:
- 指针的值是否正确
- 数组下标是否越界
- 内存分配是否成功
- 文件是否正确打开
3. 记忆口诀
- 指针数组:”指针数组,数组指针,弄清顺序”
- 宏定义:”宏定义,参数括号,避免陷阱”
- 文件操作:”文件操作,模式选对,EOF处理”
- 动态内存:”谁malloc谁free,free后置NULL”
4. 重点章节优先
按重要性排序:
- 指针(最重要,最难)
- 函数
- 数组
- 结构体
- 文件操作
📚 祝你考试顺利!加油!💪
相关资源