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. 重点章节优先

按重要性排序:

  1. 指针(最重要,最难)
  2. 函数
  3. 数组
  4. 结构体
  5. 文件操作

📚 祝你考试顺利!加油!💪

相关资源


© 2025 Rl. 使用 Stellar 创建
站点访问量 Loading... 站点访客数 Loading... 页面访问量 Loading...