改错题专项训练 · C 语言
背景:2026 年广东专升本计算机真题出现「改错题」信号,2027 届大概率延续。 题型:给出一段有错的 C 代码,要求找出并改正错误(通常 2–3 处)。 注意:不得增行删行、不得改动程序结构,只改指定行或错误处。
① 📊【历年真题考情】
改错题在广东专升本计算机统考中的位置:
| 项目 | 结论 |
|---|---|
| 题型地位 | 2026 改革新增题型("考试形式与内容优化"改革第一年) |
| 真题证据 | 2026 卷面含「改错题」(考生回忆 + 第三方冲刺卷佐证) |
| 未来趋势 | 2027 大概率延续 2026 新题型框架——必须专项准备 |
| 考纲说明 | 2026 考纲全解:综合复习第 10 周含"改错题"查漏补缺 |
| 关联考点 | 2026 新增"程序运行环境与调试"(编译/链接/运行错误)与改错题强相关 |
改错题常见出题点(2026 新题型预判):
==与=混淆、循环边界<vs<=、scanf缺&- 指针未初始化 / 空指针、
(*p)++vs*p++优先级 - 传值调用无法修改实参(需传指针)、字符串
\0遗漏 - malloc 忘 free / fopen 忘检查 NULL、数组越界、递归缺边界
🎯 学习目标:学完本章,你能在 2-3 分钟内定位一段错误代码中的全部错误——改错题是最能拉开差距的新题型,会"找错"等于会"写对"。
一、常见错误类型清单
1. 语法错误(编译不通过)
| 错误 | 示例 | 改正 |
|---|---|---|
| 缺少分号 | int a = 1 | int a = 1; |
| 括号不匹配 | if (a > 5 { | if (a > 5) { |
| 关键字拼写错 | whlie (i < 10) | while (i < 10) |
| 主函数写错 | void mian() | int main() 或 void main() |
| 头文件遗漏 | 用 printf 但没 #include <stdio.h> | 加上头文件 |
| 字符串用单引号 | char s[] = 'abc'; | char s[] = "abc"; |
2. 逻辑错误(编译通过但结果错)
| 错误 | 示例 | 改正 |
|---|---|---|
== 写成 = | if (a = 5) | if (a == 5) |
| 循环条件错 | for (i = 1; i <= n; i++) 求 1 到 n-1 | i < n |
| 数组越界 | int a[5]; a[5] = 10; | a[4] = 10; |
| 字符串无结束符 | char s[3] = "abc"; | char s[4] = "abc"; |
| 指针未初始化 | int *p; *p = 5; | int a; int *p = &a; *p = 5; |
| 除零错误 | avg = sum / n; n 可能为 0 | 先判断 n > 0 |
| 交换缺中间变量 | a = b; b = a; | t = a; a = b; b = t; |
3. 运算符与表达式错误
| 错误 | 示例 | 改正 |
|---|---|---|
| 自增自减位置错 | a = i++; 想用新值 | a = ++i; |
| 整数除法 | 5 / 2 = 2 想要 2.5 | 5.0 / 2 或 (float)5 / 2 |
| 赋值与比较混淆 | if (x = 0) 永真(x=0 为假) | if (x == 0) |
| 逻辑运算符错 | if (a > 0 & b > 0) | if (a > 0 && b > 0) |
| 位运算与逻辑运算混 | if (!a & b) | 看意图,可能是 !a && b |
4. 函数与参数错误
| 错误 | 示例 | 改正 |
|---|---|---|
| 函数未声明就调用 | 先调用后定义 | 加函数原型声明 |
| 参数类型不匹配 | void f(int a); f(3.5); | 改参数类型或强制转换 |
| 返回值类型错 | int f() { return 3.5; } | return (int)3.5; 或改返回类型 |
| 传值无法修改实参 | void swap(int a, int b) | 用指针 void swap(int *a, int *b) |
| 数组参数写成值 | void f(int a[10]) 内 sizeof(a) 错 | 数组传参退化为指针,需传长度 |
5. 指针与数组错误
| 错误 | 示例 | 改正 |
|---|---|---|
| 指针解引用未初始化 | int *p; scanf("%d", p); | int a; int *p = &a; scanf("%d", p); |
| 数组名当指针修改 | int a[10]; a++; | 数组名是常量指针,不能自增 |
| 字符指针修改字符串常量 | char *s = "abc"; s[0] = 'A'; | 用字符数组 char s[] = "abc"; |
| 指针越界 | int a[5], *p = a; p += 5; *p = 1; | p += 4 或检查边界 |
| 二级指针用错 | int **p; *p = &a; | int *q; p = &q; *p = &a; |
6. 字符串处理错误
| 错误 | 示例 | 改正 |
|---|---|---|
strcpy 目标空间不足 | char d[3]; strcpy(d, "hello"); | 目标数组足够大 |
用 == 比较字符串 | if (s1 == s2) | if (strcmp(s1, s2) == 0) |
strlen 算上 \0 | 误以为长度含结束符 | strlen 不计 \0 |
输入字符串用 & | scanf("%s", &s); | scanf("%s", s);(数组名已是地址) |
gets 不安全 | gets(s); | 用 fgets(s, sizeof(s), stdin); |
二、典型改错题 15 道(含解析)
题 1:求 1 到 n 的和
#include <stdio.h>
int sum(int n)
{
int i, s = 0;
for (i = 1; i < n; i++) /**** 错误 1 ****/
s = s + i;
return s;
}
int main()
{
int n = 10;
printf("%d\n", sum(n));
return 0;
}错误 1:
i < n→i <= n解析:求 1 到 n 的和,循环应包含 n。
i < n只加到 n-1,结果为 45 而非 55。
题 2:判断闰年
#include <stdio.h>
int isLeap(int year)
{
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) /**** 错误 1 ****/
return 1;
else
return 0;
}
int main()
{
int y;
scanf("%d", y); /**** 错误 2 ****/
printf("%d\n", isLeap(y));
return 0;
}错误 1:运算符优先级问题。
&&优先级高于||,实际逻辑正确,但建议加括号增强可读性:(year % 4 == 0 && year % 100 != 0) || year % 400 == 0。若题目要求改,通常此处逻辑本身正确,重点看错误 2。错误 2:
scanf("%d", y)→scanf("%d", &y)解析:
scanf需要变量地址,y是值传递,必须加&。
题 3:交换两个数
#include <stdio.h>
void swap(int a, int b) /**** 错误 1 ****/
{
int temp;
temp = a;
a = b;
b = temp;
}
int main()
{
int x = 3, y = 5;
swap(x, y);
printf("%d, %d\n", x, y);
return 0;
}错误 1:
void swap(int a, int b)→void swap(int *a, int *b)同时函数体内改为:
ctemp = *a; *a = *b; *b = temp;调用改为
swap(&x, &y);解析:C 语言传值调用,形参改变不影响实参。必须用指针传递地址才能真正交换。
题 4:求字符串长度
#include <stdio.h>
int myStrlen(char s[])
{
int n = 0;
while (s[n] != '\0')
n++;
return n + 1; /**** 错误 1 ****/
}
int main()
{
char str[] = "hello";
printf("%d\n", myStrlen(str));
return 0;
}错误 1:
return n + 1;→return n;解析:
strlen不计字符串结束符\0。循环结束时 n 已经是字符个数("hello" 为 5),不应再加 1。
题 5:数组求最大值
#include <stdio.h>
int findMax(int a[], int n)
{
int i, max = 0; /**** 错误 1 ****/
for (i = 1; i < n; i++)
if (a[i] > max)
max = a[i];
return max;
}
int main()
{
int arr[5] = {-3, -1, -5, -2, -4};
printf("%d\n", findMax(arr, 5));
return 0;
}错误 1:
max = 0→max = a[0],且循环从i = 1开始(或max = a[0]; for(i=0; i<n; i++))解析:当数组全为负数时,初始化为 0 会导致返回 0 而非真正的最大值(-1)。应初始化为数组第一个元素。
题 6:字符串复制
#include <stdio.h>
void myStrcpy(char *dest, char *src)
{
while (*src != '\0') {
*dest = *src;
dest++;
src++;
}
/**** 缺少错误 1 ****/
}
int main()
{
char d[10], s[] = "abc";
myStrcpy(d, s);
printf("%s\n", d);
return 0;
}错误 1(遗漏):循环结束后缺少
*dest = '\0';解析:字符串复制必须在末尾添加结束符
\0,否则printf("%s")会输出乱码或越界。
题 7:斐波那契数列
#include <stdio.h>
int fib(int n)
{
if (n == 1 || n == 2) /**** 错误 1 ****/
return 1;
return fib(n - 1) + fib(n - 2);
}
int main()
{
printf("%d\n", fib(0));
return 0;
}错误 1:缺少
n <= 0的边界处理。若题目要求 fib(0)=0,应加:cif (n <= 0) return 0; if (n == 1 || n == 2) return 1;解析:
fib(0)会无限递归(fib(-1) + fib(-2)...)导致栈溢出。必须处理边界。
题 8:指针求和
#include <stdio.h>
int main()
{
int *p, *q, a = 3, b = 5;
p = &a;
q = &b;
printf("%d\n", p + q); /**** 错误 1 ****/
return 0;
}错误 1:
p + q→*p + *q解析:两个指针不能直接相加(C 语言不支持指针+指针)。应解引用后相加值,结果为 8。
题 9:结构体输入
#include <stdio.h>
struct Student {
char name[20];
int age;
};
int main()
{
struct Student s;
scanf("%s %d", s.name, &s.age);
printf("%s, %d\n", s.name, s.age);
s.name = "Tom"; /**** 错误 1 ****/
return 0;
}错误 1:
s.name = "Tom";→strcpy(s.name, "Tom");(需#include <string.h>)解析:数组名是常量地址,不能直接赋值。字符串复制必须用
strcpy。
题 10:冒泡排序
#include <stdio.h>
void bubbleSort(int a[], int n)
{
int i, j, temp;
for (i = 0; i < n - 1; i++)
for (j = 0; j < n - 1; j++) /**** 错误 1 ****/
if (a[j] > a[j + 1]) {
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
int main()
{
int arr[5] = {5, 3, 1, 4, 2};
bubbleSort(arr, 5);
for (int i = 0; i < 5; i++)
printf("%d ", arr[i]);
return 0;
}错误 1:
j < n - 1→j < n - 1 - i解析:冒泡排序每轮将最大元素沉到末尾,下一轮无需再比较已排好的部分。
j < n - 1虽然也能排好序(结果正确),但效率低,标准写法是j < n - 1 - i。若题目要求优化,此处应改。
题 11:动态内存分配
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p = (int *)malloc(10 * sizeof(int));
int i;
for (i = 0; i < 10; i++)
p[i] = i;
for (i = 0; i < 10; i++)
printf("%d ", p[i]);
/**** 缺少错误 1 ****/
return 0;
}错误 1(遗漏):缺少
free(p);解析:
malloc分配的堆内存必须手动释放,否则内存泄漏。良好习惯是用完即free。
题 12:字符统计
#include <stdio.h>
void count(char *s, int *upper, int *lower)
{
*upper = 0;
*lower = 0;
while (*s != '\0') {
if (*s >= 'A' && *s <= 'Z')
(*upper)++; /**** 注意此处 ****/
else if (*s >= 'a' && *s <= 'z')
*lower++;
s++;
}
}
int main()
{
char str[] = "Hello World";
int u, l;
count(str, &u, &l);
printf("upper=%d, lower=%d\n", u, l);
return 0;
}注意:
(*upper)++是正确的(先解引用再自增)。如果写成*upper++则是错误的(指针后移而非值自增)。若题目中写的是
*upper++→ 改为(*upper)++解析:
*upper++等价于*(upper++),是指针移动,不是对指向的值加 1。必须加括号。
题 13:二维数组传参
#include <stdio.h>
void printMatrix(int **a, int row, int col) /**** 错误 1 ****/
{
int i, j;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++)
printf("%d ", a[i][j]);
printf("\n");
}
}
int main()
{
int mat[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
printMatrix(mat, 3, 4);
return 0;
}错误 1:
int **a→int a[][4]或int (*a)[4]解析:二维数组名传给函数时退化为「指向一维数组的指针」,不是二级指针。必须指定第二维大小。
题 14:递归求阶乘
#include <stdio.h>
int factorial(int n)
{
if (n == 0)
return 0; /**** 错误 1 ****/
return n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(5));
return 0;
}错误 1:
return 0;→return 1;解析:0! = 1(数学定义),不是 0。若返回 0,整个阶乘结果都为 0。
题 15:文件读写
#include <stdio.h>
int main()
{
FILE *fp;
char ch;
fp = fopen("test.txt", "r"); /**** 错误 1(潜在) ****/
while ((ch = fgetc(fp)) != EOF)
putchar(ch);
fclose(fp);
return 0;
}错误 1(潜在):缺少文件打开失败检查。应加:
cif (fp == NULL) { printf("文件打开失败\n"); return 1; }解析:若文件不存在,
fopen返回NULL,后续fgetc(NULL)会导致程序崩溃。必须检查返回值。
三、改错题解题步骤
- 先通读:理解程序整体功能(求什么/做什么)
- 编译检查: mentally 编译,找语法错误(分号、括号、关键字)
- 逻辑跟踪:用简单输入手动执行,看哪一步结果不对
- 边界检查:n=0、n=1、空字符串、全负数等边界情况
- 指针检查:是否初始化、是否越界、解引用是否正确
- 函数检查:参数类型、返回值、传值 vs 传址
- 确认修改:只改错误处,不增删行,不改程序结构
四、改错题高频考点排名
==与=混淆(出现率最高)- 循环边界
<=与< - 指针未初始化 / 空指针
- 字符串结束符
\0遗漏 - 传值调用无法修改实参(需传指针)
- 数组越界
scanf缺&- 字符串比较用
==而非strcmp - 递归缺少边界条件
- 二维数组传参类型错误
五、补充:编译/链接/运行错误综合改错题(2026 新考点联动)
2026 考纲新增"程序运行环境与调试"考点,改错题常结合"判断错误发生在哪个阶段"来考。
题 16:判断错误阶段(2026 单选8 风格)
下列说法错误的是( )
A. 缺少分号通常属于编译错误 B. 数组越界通常在运行阶段才被发现 C. 链接错误常见于符号找不到定义 D. 除以 0 属于编译错误
👆 点击展开答案与解析
答案:D
- 除以 0 是运行错误(运行时才崩溃),不是编译错误
- A 对:缺分号破坏语法 → 编译阶段报错
- B 对:数组越界编译期发现不了,运行时崩
- C 对:函数只有声明没有定义 → 链接时符号找不到
口诀:语法找编译,符号找链接,越界/除零找运行
题 17:综合改错(语法 + 逻辑 + 运行时错误)
下面的程序想统计 1 到 n 之间能被 3 整除的数之和,但有多处错误,请全部改正。
#include <stdio.h>
int main()
{
int n, sum, i;
scanf("%d", n); /**** 错误 1 ****/
sum = 0
for (i = 1; i < n; i++) /**** 错误 2 ****/
if (i % 3 = 0) /**** 错误 3 ****/
sum = sum + i;
printf("sum=%d\n", sum);
return 0;
}👆 点击展开答案与解析
错误 1:scanf("%d", n) → scanf("%d", &n)(scanf 需要变量地址)
错误 2:sum = 0 → sum = 0;(缺少分号 → 编译错误)
错误 3:i < n → i <= n(应包含 n,否则漏掉 n 本身 → 逻辑错误)
错误 4:if (i % 3 = 0) → if (i % 3 == 0)(= 赋值 vs == 比较 → 逻辑错误)
逐错误定位:
scanf("%d", n):缺&,n 是值 → 运行时可能崩溃/输入无效sum = 0缺分号:编译错误i < n:求 1~n 应<=,漏最后一项 → 逻辑错误i % 3 = 0:=把 i%3 赋值为 0(恒真/恒假错),应为==→ 逻辑错误
改正后:
#include <stdio.h>
int main()
{
int n, sum, i;
scanf("%d", &n);
sum = 0;
for (i = 1; i <= n; i++)
if (i % 3 == 0)
sum = sum + i;
printf("sum=%d\n", sum);
return 0;
}验证:输入 10 → 3+6+9 = 18
⚠️ 改错题高频扣分坑(新增模块)
| # | 陷阱 | 错误做法 | 正确做法 | 关联章节 |
|---|---|---|---|---|
| 1 | 只找一个错 | 改完一个就交卷 | 改错题通常 2-3 处,改完再复查一遍 | 全章 |
| 2 | 增行删行 | 加一行辅助代码 | 只改错误处,不得增删行、改结构 | 全章 |
| 3 | == 写成 = | if (a = 5) 以为比较 | if (a == 5) | 1.4 选择结构 |
| 4 | 循环边界 | i < n 求 1~n | i <= n(含 n) | 1.5 循环 |
| 5 | scanf 缺 & | scanf("%d", n) | scanf("%d", &n) | 1.3 顺序 |
| 6 | 指针忘初始化 | int *p; *p = 5; | int a; int *p = &a; *p = 5; | 1.8 指针 |
| 7 | (*p)++ 写错 | 写成 *p++ 想自增值 | (*p)++ 改值;*p++ 移指针 | 1.8 指针 |
| 8 | 字符串忘 \0 | 手动复制后不加结束符 | 循环后补 *dest = '\0'; | 1.6 数组 |
| 9 | 传值想改实参 | swap(int a,int b) 想交换 | 传指针 swap(int *a,int *b) | 1.7/1.8 |
| 10 | malloc/fopen 忘检查 | 直接使用返回值 | 先判 NULL | 1.8 指针 |
| 11 | 递归忘边界 | fib(0) 无限递归 | 加 if (n <= 0) return 0; | 1.7 函数 |
| 12 | main 写错 | void mian() | int main() | 1.1 概述 |