跳转到内容

exam-87 试卷整理与详解

这份笔记是根据 87.pdf 整理出来的,原卷文件在本地路径:

/Users/wff/Downloads/87.pdf

这套卷子本身已经标出了一部分答案,我在整理时做了二次核对。个别位置有 OCR 识别误差,尤其是引号、分号、文件打开模式、逻辑运算符,我已经按 C 语言正确写法整理。

对新手来说,这套卷子非常适合复习下面这些内容:

  • 函数定义、函数调用、递归
  • 字符串比较、复制、连接
  • 一维数组、二维数组初始化
  • 数组下标和二维数组行优先存储
  • 指针和数组名作为函数参数
  • 文件读写基础
  • 宏、结构体、表达式优先级
  • 改错题和程序填空题的常见坑
题号答案
1C
2B
3A
4B
5C
6A
7C
8A
9B
10C
11B
12B
13A
14B
15D
16A
17C
18C
19B
20B
21B
22A
23A
24B
25B
题号答案
11
2"bi.dat", "w"fclose(fp);
3B 66
46
50
6114, 124, -1
71
811
9
1022
题号答案
1N
2T
3N
4T
5N
6N
7T
8T
9T
10N

题目问:以下正确的函数首部形式是哪一个?

答案:C

正确形式:

double fun(int x, int y)

详细解析:

函数首部用于函数定义时,后面紧跟函数体,所以末尾不写分号。

double fun(int x, int y)
{
return x + y;
}

选项 A

double fun(int x, int y);

这是函数声明,也叫函数原型,不是函数定义里的函数首部。

容易踩坑点:

  • 把“函数声明”和“函数定义首部”混为一谈。
  • 形参 int x, y 也是错的,C 语言里每个形参都要单独写类型。

给新手的建议:

  • 函数声明通常有分号。
  • 函数定义的函数首部后面跟 {},不写分号。

题目问:当 s1 所指字符串大于 s2 所指字符串时,执行语句 S,应该怎么写?

答案:B

正确写法:

if (strcmp(s1, s2) > 0)
S;

详细解析:

字符串不能直接用 > 比较内容。

s1 > s2

比较的是两个指针地址,不是字符串大小。

strcmp(s1, s2) 的结果规则是:

  • 返回值大于 0s1 大于 s2
  • 返回值等于 0:两个字符串相等
  • 返回值小于 0s1 小于 s2

容易踩坑点:

  • s1 > s2 比较字符串。
  • 只写 strcmp(s1, s2),这样只能判断“不相等”,不能判断谁大。

给新手的建议:

  • 字符串比较内容用 strcmp
  • 字符串复制用 strcpy
  • 字符串连接用 strcat

题目问:以下能对一维数组 a 正确初始化的是哪一个?

答案:A

正确写法:

int a[10] = {0};

详细解析:

{0} 表示把第一个元素初始化为 0,剩余元素自动补 0

所以这个数组实际效果是:

0 0 0 0 0 0 0 0 0 0

其他选项的问题:

  • int a[n] 在传统教材考试里,数组长度通常要求是常量表达式。
  • 初始化数组要用 {},不能用 ()
  • { 后面必须有完整的初始化列表,不能只写半截。

容易踩坑点:

  • 看到 {0} 以为只有第一个元素有值。

给新手的建议:

  • int a[10] = {0}; 是考试和实际写代码里都很常见的“全 0 初始化”写法。

题目问:以下数组定义中不正确的是哪一个?

答案:B

错误写法:

int d[3][] = {{1, 2}, {1, 2, 3}, {1, 2, 3, 4}};

详细解析:

二维数组定义时,第二维必须明确。

例如:

int d[][4] = {{1, 2}, {1, 2, 3}, {1, 2, 3, 4}};

这样才可以,因为编译器知道每一行有 4 列。

容易踩坑点:

  • 误以为二维数组只要写第一维就行。

给新手的建议:

  • 二维数组初始化时,可以省略第一维,不能省略第二维。

题目整理:

long fib(int n)
{
if (n > 2)
return fib(n - 1) + fib(n - 2);
else
return 2;
}
printf("%ld\n", fib(3));

答案:C

详细解析:

先看递归出口:

fib(1) = 2
fib(2) = 2

所以:

fib(3) = fib(2) + fib(1) = 2 + 2 = 4

输出结果是:

4

容易踩坑点:

  • 把这题当成普通斐波那契数列,直接套 1, 1, 2, 3
  • 忽略本题的递归出口返回的是 2,不是 1

给新手的建议:

  • 递归题永远先看“什么时候停”。
  • 不要背默认规律,要按题目代码一步一步代。

题目问:与实际参数为实型数组名相对应的形式参数不可以定义为什么?

答案:A

错误写法:

float a;

详细解析:

如果实参是数组名,比如:

float x[10];
fun(x);

那么传过去的是数组首元素地址,形参应该写成下面这种形式:

void fun(float a[])
void fun(float *a)

如果是二维实型数组,也可能写成:

void fun(float (*a)[3])

float a 只是一个普通浮点变量,不能接收数组名。

容易踩坑点:

  • 把数组参数当成普通变量。

给新手的建议:

  • 一维数组作函数参数时,优先记住两种等价写法:float a[]float *a

题目问:以下不能正确进行字符串赋初值的是哪一个?

答案:C

错误写法:

char str[5] = "good!";

详细解析:

字符串 "good!" 有 5 个可见字符:

g o o d !

但字符串末尾还需要一个结束标志:

'\0'

所以至少需要 6 个字符空间。

正确写法可以是:

char str[6] = "good!";
char str[] = "good!";

容易踩坑点:

  • 只数可见字符,忘记 '\0'

给新手的建议:

  • 字符串初始化字符数组时,数组长度至少要等于“可见字符数 + 1”。

题目问:以下对二维数组 a 的正确说明是哪一个?

答案:A

正确写法:

double a[1][4];

详细解析:

二维数组的基本格式是:

类型名 数组名[行数][列数];

其他选项的问题:

  • float a(3)(4) 不是数组定义。
  • int a[3][] 第二维省略,错误。
  • float a(3,4) 也不是数组定义。

容易踩坑点:

  • 把数学里的括号写法带到 C 语言数组里。

给新手的建议:

  • C 语言数组下标用 [],不是 ()

题目整理:

char s1[] = "abc", s2[20], *t = s2;
gets(t);

问:当 s1 大于 s2 时输出 s2,应该怎么写?

答案:B

正确写法:

if (strcmp(s1, t) > 0)
puts(s2);

详细解析:

这里:

t = s2;

所以 t 指向的就是 s2 的首地址。

因此:

strcmp(s1, t)

就等价于比较:

strcmp(s1, s2)

容易踩坑点:

  • 忘记 ts2 指向同一段字符数组。
  • 写成 strcmp(s2, t),这相当于自己和自己比较,结果是 0

给新手的建议:

  • 指针题先画箭头:t -> s2[0]

题目问:字符串的结束符是什么?

答案:C

正确答案:

'\0'

详细解析:

'\0' 是空字符,ASCII 值为 0,用于标记字符串结束。

要注意下面几个东西不一样:

'0' // 字符 0,ASCII 值是 48
'\0' // 空字符,ASCII 值是 0
"0" // 字符串,包含 '0' 和 '\0'
"\0" // 字符串,包含一个 '\0'

容易踩坑点:

  • '0''\0' 混淆。

给新手的建议:

  • 字符串结束符永远记 '\0',中间有反斜杠。

题目整理:

int a[][4] = {0, 0};

问:下面不正确的叙述是哪一个?

答案:B

详细解析:

二维数组第二维是 4,一共给了 2 个初值,所以第一维可以推断为 1

实际数组是:

0 0 0 0

因为数组初始化时,没写到的元素会自动补 0

选项 B 说“只有 a[0][0]a[0][1] 得到初值 0,其余元素得不到初值 0”,这是错的。

容易踩坑点:

  • 以为没显式写出来的元素就是随机值。

给新手的建议:

  • 数组初始化时,只要写了初始化列表,没写到的元素通常自动补 0

题目整理:

char a[10];

问:哪句不能从键盘给数组 a 输入值?

答案:B

错误写法:

a = getchar();

详细解析:

a 是数组名,不能放在赋值号左边整体赋值。

而且 getchar() 只能读取一个字符,不可能直接填满整个数组。

下面几种写法在考试语境里都可以理解为输入字符数组:

for (i = 0; i < 10; i++)
a[i] = getchar();
gets(a);
scanf("%s", a);

补充提醒:

  • gets 在现代 C 语言中已经不推荐使用,因为容易越界。
  • 考试里出现 gets,通常只是考字符串输入概念。

容易踩坑点:

  • 把字符数组 a 当成一个普通字符变量。

给新手的建议:

  • 数组名不是普通变量,不能写 a = ...

题目问:以下正确的说法是哪一个?

答案:A

正确说法:

如果函数返回值类型和 return 后表达式类型不一致,最终以函数定义的返回类型为准。

例如:

int fun(void)
{
return 3.14;
}

这里函数返回类型是 int,所以 3.14 会被转换成 3 返回。

容易踩坑点:

  • 以为 return 后面是什么类型,函数最终就返回什么类型。

给新手的建议:

  • 看函数返回值类型,先看函数名前面的类型。

题目整理:

int func(int a, int b)
{
return a + b;
}
int x = 2, y = 5, z = 8, r;
r = func(func(x, y), z);
printf("%d\n", r);

答案:B

详细解析:

先算里面:

func(x, y) = func(2, 5) = 7

再算外面:

func(7, z) = func(7, 8) = 15

所以输出:

15

容易踩坑点:

  • 函数嵌套调用时,不知道先算哪一层。

给新手的建议:

  • 嵌套函数调用先从最里面开始算。

题目整理:

int n[2] = {0}, i, j, k = 1;
for (i = 0; i <= k; i++)
for (j = 0; j <= k; j++)
n[j] = n[i] + 1;
printf("%d\n", n[k]);

答案:D

详细解析:

初始状态:

n[0] = 0
n[1] = 0
k = 1

循环过程:

ij执行语句结果
00n[0] = n[0] + 1n[0] = 1
01n[1] = n[0] + 1n[1] = 2
10n[0] = n[1] + 1n[0] = 3
11n[1] = n[1] + 1n[1] = 3

最后输出:

n[k] = n[1] = 3

容易踩坑点:

  • 没注意数组元素在循环中已经被更新。
  • 以为 n[i] 一直是初始值。

给新手的建议:

  • 数组循环赋值题,最好画一个表格记录每一步的变化。

题目整理:

int x[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (i = 0; i < 3; i++)
printf("%d, ", x[i][2 - i]);

答案:A

详细解析:

逐次代入:

i2 - i访问元素
02x[0][2]3
11x[1][1]5
20x[2][0]7

所以输出:

3, 5, 7,

容易踩坑点:

  • 二维数组下标从 0 开始。
  • x[i][2-i] 看成固定列。

给新手的建议:

  • 二维数组题先画成表格,再代下标。

题目整理:

exce((v1, v2), (v3, v4, v5), v6);

问:实参个数是多少?

答案:C

详细解析:

最外层参数有 3 个:

  1. (v1, v2)
  2. (v3, v4, v5)
  3. v6

括号里面的逗号是逗号表达式的一部分,不是函数参数分隔符。

容易踩坑点:

  • 看见逗号就直接数,结果把括号内的逗号也算进去了。

给新手的建议:

  • 数函数实参个数,只看最外层逗号。

题目整理:

char a1[] = "abc", a2[80] = "1234";

问:把 a1 串连接到 a2 串后面,应该怎么写?

答案:C

正确写法:

strcat(a2, a1);

详细解析:

strcat(目标串, 源串) 的意思是:

把源串接到目标串后面

所以:

strcat(a2, a1);

执行后 a2 变成:

1234abc

容易踩坑点:

  • strcat(a1, a2) 写反。

给新手的建议:

  • strcpystrcat 都是“第一个参数被改变”。

题目问:以下不能正确定义二维数组的是哪一个?

答案:B

错误写法:

int a[2][] = {{1, 2}, {3, 4}};

详细解析:

二维数组第二维不能省略。编译器必须知道每一行有几个元素,才知道如何计算 a[i][j] 的位置。

正确写法:

int a[][2] = {{1, 2}, {3, 4}};
int a[2][2] = {{1, 2}, {3, 4}};

容易踩坑点:

  • 以为已经写了初值,第二维就可以不写。

给新手的建议:

  • 二维数组的“列数”一定要明确。

题目整理:

int a[3][4] = {0};

答案:B

详细解析:

这个初始化会让数组中每个元素都是 0

可以理解成:

0 0 0 0
0 0 0 0
0 0 0 0

容易踩坑点:

  • 以为只有 a[0][0] 是 0。

给新手的建议:

  • = {0} 对数组来说就是非常常见的“全部初始化为 0”。

题目问:以下能对二维数组 a 进行正确初始化的是哪一个?

答案:B

正确写法:

int a[][3] = {1, 2, 3, 4, 5};

详细解析:

第二维是 3,所以每行放 3 个元素:

1 2 3
4 5 0

第一维可以根据初值个数自动推断出来。

其他选项的问题:

  • int a[][] 两维都省略,错误。
  • int a[2][] 第二维省略,错误。
  • int a[2][3] 只能放 6 个元素,却给了 7 个,错误。

容易踩坑点:

  • 不知道“第一维可省,第二维不可省”。

给新手的建议:

  • 二维数组初始化时,先看列数有没有写出来。

题目整理:

int a[3][4];

问:对 a 数组元素的正确引用是哪一个?

答案:A

正确写法:

a[1 + 1][0]

详细解析:

a[3][4] 的合法下标是:

第一维:0 1 2
第二维:0 1 2 3

a[1 + 1][0] 等价于:

a[2][0]

这是合法的。

容易踩坑点:

  • a[2][4] 第二维越界,因为最大只能到 3
  • a(2)(1) 不是 C 语言数组访问写法。
  • a[1,3] 不是二维数组元素访问,逗号表达式会让它变得很容易出错。

给新手的建议:

  • 二维数组元素访问必须写成 a[i][j]

题目整理:

char array[] = "China";

问:数组 array 占多少空间?

答案:A

详细解析:

"China" 有 5 个可见字符:

C h i n a

字符串末尾还会自动加一个 '\0'

所以数组实际有 6 个字符:

'C' 'h' 'i' 'n' 'a' '\0'

6 个字节。

容易踩坑点:

  • 忘记字符串结束符 '\0'

给新手的建议:

  • 字符串数组空间 = 可见字符数 + 1。

题目整理:

static char str[10] = "China";

问:数组元素个数是多少?

答案:B

详细解析:

数组定义中已经明确写了:

str[10]

所以数组元素个数就是 10

"China" 只决定前面几个元素的初值,不改变数组长度。

容易踩坑点:

  • 把字符串长度当成数组长度。

给新手的建议:

  • 题目问“数组元素个数”,优先看方括号里的长度。

题目整理:

int a[3][4];

问:非法引用是哪一个?

答案:B

非法写法:

a[0][4]

详细解析:

第二维长度是 4,合法下标只能是:

0 1 2 3

所以 a[0][4] 越界。

容易踩坑点:

  • 看到长度是 4,就以为下标也能写 4。

给新手的建议:

  • 数组最后一个下标永远是“长度 - 1”。

题目整理:

a = 3, b = 4, c = 4
a + b > c && b == c && a || b + c && b == c

答案:1

详细解析:

先按优先级拆开:

(a + b > c) && (b == c) && a || (b + c) && (b == c)

逐个计算:

a + b > c -> 3 + 4 > 4 -> 真
b == c -> 4 == 4 -> 真
a -> 3 -> 真
b + c -> 8 -> 真
b == c -> 真

所以整体为真,C 语言中真通常用 1 表示。

容易踩坑点:

  • 不熟悉 &&|| 的优先级。
  • 忘记非 0 就是真。

给新手的建议:

  • 逻辑表达式不会算时,先手动加括号。

题目要求:把从终端读入的文本复制到新文件 bi.dat 中,用 @ 作为结束标志。

答案:

fp = fopen("bi.dat", "w");
...
fclose(fp);

整理后的代码:

#include <stdio.h>
#include <stdlib.h>
FILE *fp;
int main(void)
{
char ch;
if ((fp = fopen("bi.dat", "w")) == NULL)
exit(0);
while ((ch = getchar()) != '@')
fputc(ch, fp);
fclose(fp);
return 0;
}

详细解析:

  • fopen("bi.dat", "w"):以写入方式打开文件,如果文件不存在则创建。
  • getchar():从键盘读一个字符。
  • fputc(ch, fp):把字符写入文件。
  • fclose(fp):关闭文件。

补充说明:

原卷标注里文件模式可能显示成大写 W,但标准 C 语言中文件打开模式一般写小写 "w"

容易踩坑点:

  • 文件模式字符串忘记写双引号。
  • 写完文件后忘记 fclose(fp);

给新手的建议:

  • 文件题先记住三步:打开文件、读写文件、关闭文件。

题目整理:

char ch = 'B';
printf("%c %d\n", ch, ch);

答案:B 66

详细解析:

已知字符 'A' 的 ASCII 码是 65,所以:

'B' = 66

%c 按字符输出,%d 按整数输出。

所以输出:

B 66

容易踩坑点:

  • 忘记格式串中 %c%d 中间有一个空格。

给新手的建议:

  • 同一个字符变量既可以按字符看,也可以按 ASCII 码值看。

题目整理:

int a[5] = {1, 3, 5, 7, 9}, *p;
p = &a[2];

问:++(*p) 的值是多少?

答案:6

详细解析:

p = &a[2],所以 p 指向 a[2]

数组中:

a[2] = 5

++(*p) 的意思是:

先把 *p 加 1,再取这个表达式的值

所以 5 变成 6,表达式的值也是 6

容易踩坑点:

  • 看不懂 *p++ 的结合。

给新手的建议:

  • 看到 p = &a[2],就在旁边写一句:*p 就是 a[2]

题目整理:

a = 13;
b = 6;
!a

答案:0

详细解析:

在 C 语言中:

  • 0 表示假
  • 0 表示真

因为 a = 13 是非 0,所以 a 为真。

!a 就是假,结果为 0

容易踩坑点:

  • 以为 !a 是把数字变成负数。

给新手的建议:

  • ! 是逻辑非,不是数学里的负号。

题目整理:

int x = 6, y, z;
x *= 18 + 1;
printf("%d, ", x--);
x += y = z = 11;
printf("%d, ", x);
x = y == z;
printf("%d\n", -x++);

答案:114, 124, -1

详细解析:

第一步:

x *= 18 + 1;

等价于:

x = x * 19;

所以:

x = 6 * 19 = 114

第二步:

printf("%d, ", x--);

先输出 114,再让 x 变成 113

第三步:

x += y = z = 11;

赋值从右向左:

z = 11
y = 11
x = 113 + 11 = 124

第四步:

x = y == z;

y == z 为真,所以 x = 1

第五步:

printf("%d\n", -x++);

x++ 先取旧值 1,再自增,所以输出的是:

-1

容易踩坑点:

  • x--x++ 都是先用旧值,再改变变量。
  • x += y = z = 11 要从右往左看。

给新手的建议:

  • 这类题一定拆成多行,不要在脑子里硬算。

题目整理:

int x = 2, y = 3, z = 4;
x + y && z

答案:1

详细解析:

先算:

x + y = 5

5 是非 0,表示真。

z = 4 也是非 0,表示真。

所以:

真 && 真 = 真

结果为 1

容易踩坑点:

  • 以为逻辑表达式会返回原来的数字 45

给新手的建议:

  • &&|| 的结果通常是 01

题目整理:

int x;
x = -3 + 4 * 5 - 6;

答案:11

详细解析:

先乘除,后加减:

4 * 5 = 20

所以:

x = -3 + 20 - 6 = 11

容易踩坑点:

  • 忘记乘法优先级高于加减法。

给新手的建议:

  • 表达式题先把优先级高的部分圈出来。

题目问:C 语言中,二维数组在内存中的存放方式是什么?

答案:

详细解析:

C 语言二维数组按行优先存放。

例如:

int a[2][3] = {
{1, 2, 3},
{4, 5, 6}
};

内存顺序是:

1 2 3 4 5 6

先放第 0 行,再放第 1 行。

容易踩坑点:

  • 把二维数组想成真正的二维表格,忘记内存本质上是一条连续空间。

给新手的建议:

  • 二维数组画表格理解,下标计算时记住“按行一行一行存”。

题目整理:

struct stud
{
char num[6];
int s[4];
double ave;
} a, *p;

问:变量 a 在内存中占多少字节?

答案:22

详细解析:

按传统教材环境:

  • char num[6]6 字节
  • int s[4]int2 字节,所以共 8 字节
  • double ave8 字节

所以:

6 + 8 + 8 = 22

补充提醒:

现代编译器可能因为 int 字节数和结构体对齐规则,算出来不是 22。这类考试题通常按教材环境和题目默认字节数来答。

容易踩坑点:

  • 忽略数组成员的元素个数。
  • 忽略不同环境下 int 字节数可能不同。

给新手的建议:

  • 做考试题按题目环境算;自己写代码时可以用 sizeof(a) 验证。

题目:

int i = 10, j = 2;
i *= j + 8;

判断:执行后 i 的值为 28

答案:N

解析:

i *= j + 8;

等价于:

i = i * (j + 8);

所以:

i = 10 * (2 + 8) = 100

不是 28

易错点:复合赋值右边会整体参与运算,不是简单写成 i = i * j + 8

题目:

int c;
while (c = getchar())
;

判断:这是正确的 C 语句。

答案:T

解析:

赋值表达式可以作为 while 的条件。每次读取一个字符,赋给 c,再根据 c 的值判断是否继续循环。

易错点:= 是赋值,== 是判断相等。这里用 = 语法上是可以的。

新手建议:虽然语法正确,但真实写代码时常常会写得更明确一些,避免误读。

题目:关系运算符 <=== 的优先级相同。

答案:N

解析:

<= 属于关系运算符,== 属于相等性运算符。

关系运算符优先级高于相等性运算符。

易错点:把所有比较符号都当成同一优先级。

题目:

int a[3][4] = {{1}, {5}, {9}};

判断:它的作用是将数组各行第一列的元素赋初值,其余元素为 0

答案:T

解析:

数组实际为:

1 0 0 0
5 0 0 0
9 0 0 0

易错点:不知道二维数组每一行可以用一组 {} 单独初始化。

题目:表达式 (j = 3, j++) 的值是 4

答案:N

解析:

逗号表达式的值看最后一个表达式。

最后一个表达式是:

j++

j++ 的表达式值是旧值 3,执行后 j 才变成 4

所以表达式的值是 3,不是 4

易错点:把变量最终值和表达式值混在一起。

题目:

#define S(a, b) t = a; a = b; b = t

判断:由于变量 t 没定义,所以此宏定义是错误的。

答案:N

解析:

宏定义本身只是文本替换规则,不会在定义时检查 t 是否已经定义。

但如果实际使用宏时,展开后的代码里 t 没有定义,那编译就会报错。

易错点:宏定义阶段和宏展开后的编译阶段不是一回事。

题目整理:

int a[3][3] = {{3, 5}, {8, 9}, {12, 35}}, i, sum = 0;
for (i = 0; i < 3; i++)
sum += a[i][2 - i];

判断:sum = 21

答案:T

解析:

数组补 0 后是:

3 5 0
8 9 0
12 35 0

求和元素:

a[0][2] = 0
a[1][1] = 9
a[2][0] = 12

所以:

sum = 0 + 9 + 12 = 21

易错点:忘记没写满的元素会自动补 0

题目:C 语言中只能逐个引用整型数组元素,而不能一次引用整个数组。

答案:T

解析:

例如:

int a[3] = {1, 2, 3};

可以写:

printf("%d", a[0]);

但不能直接用一条普通表达式把整个数组当一个值来引用或赋值。

易错点:数组名可以表示首地址,但它不是“整个数组的值”。

题目:整数 -32100 可以赋值给 int 型和 long int 型变量。

答案:T

解析:

按传统 16 位有符号 int 环境:

int 范围:-32768 ~ 32767

-32100 在这个范围内,所以可以赋给 int,也可以赋给范围更大的 long int

易错点:不同编译环境下整数范围可能不同,考试题按教材环境判断。

题目:在 C 程序中,函数既可以嵌套定义,也可以嵌套调用。

答案:N

解析:

C 语言可以嵌套调用函数:

r = func(func(x, y), z);

但标准 C 语言不允许在一个函数内部再定义另一个函数。

易错点:把“嵌套调用”和“嵌套定义”混为一谈。

功能:先把字符串 s 中的字符按正序存放到 t 中,然后把 s 中的字符按逆序连接到 t 后面。

例如:

s = "ABCDE"
t = "ABCDEEDCBA"

整理后的正确代码:

#include <stdio.h>
#include <string.h>
void fun(char *s, char *t)
{
int i, sl;
sl = strlen(s);
for (i = 0; i <= sl; i++)
t[i] = s[i];
for (i = 0; i < sl; i++)
t[sl + i] = s[sl - i - 1];
t[sl + i] = '\0';
}
int main(void)
{
char s[100], t[200];
printf("\nPlease enter string s: ");
scanf("%99s", s);
fun(s, t);
printf("The result is: %s\n", t);
return 0;
}

详细解析:

第一处错误:

t[sl + i] = s[sl - i];

应该改成:

t[sl + i] = s[sl - i - 1];

原因是字符串最后一个有效字符的下标是 sl - 1s[sl]'\0'

第二处错误:

t[sl] = '\0';

应该改成:

t[sl + i] = '\0';

原因是逆序拼接完成后,字符串结尾位置已经移动到了 sl + i

第三处错误:

fun(&s, &t);

应该改成:

fun(s, t);

原因是 st 本身作为数组名时,就可以表示首元素地址。

容易踩坑点:

  • 字符串最后一个有效字符下标是 strlen(s) - 1
  • '\0' 必须放在最终字符串末尾。
  • 数组传参时通常直接写数组名,不写 &数组名

给新手的建议:

  • 字符串拼接题最重要的是算清楚下标。
  • 可以拿 "ABCDE" 手动代入:sl = 5,逆序第一个字符应该取 s[4]

功能:用数字 1234 组成互不相同且无重复数字的三位数,并输出所有结果。

整理后的正确代码:

#include <stdio.h>
int main(void)
{
int i, j, k;
printf("\n");
for (i = 1; i <= 4; i++)
for (j = 1; j <= 4; j++)
for (k = 1; k <= 4; k++)
if (i != j && i != k && j != k)
printf("%d%d%d\n", i, j, k);
return 0;
}

详细解析:

第一处错误:

printf("\n")

应该补分号:

printf("\n");

第二处错误:

for (i = 1; i <= 5; i++)

数字只有 1234,所以应该写:

for (i = 1; i <= 4; i++)

第三处错误:

if (i != k || i != j || j != k)

应该改成:

if (i != j && i != k && j != k)

原因是题目要求三个数字互不相同,所以必须同时满足三组不相等。

补充说明:

原卷标注里只强调把其中一个逻辑或改成逻辑与,但从题目功能看,三个数字都不能重复,完整正确条件应使用三个 &&

容易踩坑点:

  • || 会导致有重复数字时也可能通过判断。
  • 循环边界写到 5,会把数字 5 也输出。

给新手的建议:

  • “同时满足多个条件”通常用 &&
  • “满足任意一个条件”才用 ||

题目:任意输入 10 个国家的名字,按由小到大排序。

参考答案:

#include <stdio.h>
#include <string.h>
#define N 10
int main(void)
{
char a[N][20], temp[20];
int i, j;
printf("请输入 10 个国家名称:\n");
for (i = 0; i < N; i++)
scanf("%19s", a[i]);
for (i = 0; i < N - 1; i++)
{
for (j = i + 1; j < N; j++)
{
if (strcmp(a[i], a[j]) > 0)
{
strcpy(temp, a[i]);
strcpy(a[i], a[j]);
strcpy(a[j], temp);
}
}
}
printf("排序后的结果:\n");
for (i = 0; i < N; i++)
printf("%s\n", a[i]);
return 0;
}

详细解题过程:

第一步,定义二维字符数组:

char a[10][20];

可以理解为:最多保存 10 个字符串,每个字符串最多占 20 个字符空间。

第二步,输入 10 个国家名字:

for (i = 0; i < N; i++)
scanf("%19s", a[i]);

第三步,用 strcmp 比较两个字符串大小:

if (strcmp(a[i], a[j]) > 0)

如果 a[i]a[j] 大,就交换它们,让小的排在前面。

第四步,字符串交换不能直接写:

a[i] = a[j]; // 错误

应该用 strcpy

strcpy(temp, a[i]);
strcpy(a[i], a[j]);
strcpy(a[j], temp);

容易踩坑点:

  • 字符串不能用 > 比较内容。
  • 字符数组不能整体赋值。
  • 交换字符串要用临时字符数组和 strcpy

给新手的建议:

  • 看到“字符串排序”,先想到 strcmpstrcpy
  • 这题本质是数组排序,只是比较和交换的对象从整数变成了字符串。

题目:编写函数 void fun(int tt[M][N], int pp[N]),求二维数组每一列中的最大元素,并依次放入一维数组 pp 中。

参考答案:

#include <stdio.h>
#define M 3
#define N 4
void fun(int tt[M][N], int pp[N])
{
int i, j;
for (j = 0; j < N; j++)
{
pp[j] = tt[0][j];
for (i = 1; i < M; i++)
{
if (tt[i][j] > pp[j])
pp[j] = tt[i][j];
}
}
}
int main(void)
{
int i;
int tt[M][N] = {
{1, 8, 3, 4},
{5, 2, 9, 6},
{7, 4, 0, 10}
};
int pp[N];
fun(tt, pp);
for (i = 0; i < N; i++)
printf("%d ", pp[i]);
printf("\n");
return 0;
}

这组测试数据中,每列最大值是:

第 0 列:max(1, 5, 7) = 7
第 1 列:max(8, 2, 4) = 8
第 2 列:max(3, 9, 0) = 9
第 3 列:max(4, 6, 10) = 10

所以输出:

7 8 9 10

详细解题过程:

外层循环控制列:

for (j = 0; j < N; j++)

每一列先假设第 0 行最大:

pp[j] = tt[0][j];

然后从第 1 行开始往下比较:

for (i = 1; i < M; i++)

如果发现更大的值,就更新:

if (tt[i][j] > pp[j])
pp[j] = tt[i][j];

容易踩坑点:

  • 题目要求“每列最大值”,外层应该遍历列 j
  • 每列都要先初始化一次 pp[j]
  • 不要把 MN 搞反,M 是行数,N 是列数。

给新手的建议:

  • 二维数组统计题先问自己:按行处理还是按列处理?
  • 如果题目说“每列”,通常外层循环写列,内层循环写行。

这套 exam-87 对新手来说,最容易暴露下面这些问题:

  • 函数声明、函数定义、函数调用分不清。
  • 字符串比较时想直接用 >
  • 字符数组长度忘记给 '\0' 留位置。
  • 二维数组第二维为什么不能省略还不够熟。
  • i++++ix-- 的表达式值和变量最终值分不清。
  • 逻辑运算中 &&|| 的使用场景容易混。
  • 结构体大小计算容易忽略数组成员。
  • 程序设计题看到字符串排序、二维数组列最大值时,不知道先拆步骤。
  • 把第 15 题、第 16 题、第 6 道填空题手动列变量变化表。
  • 把字符串相关题集中复习:第 2、7、9、10、18、23、24 题。
  • 把二维数组相关题集中复习:第 4、8、11、16、19、20、21、22、25 题。
  • 改错题 2 要重点记住:互不相同通常是三个条件同时成立,要用 &&
  • 程序设计 1 和程序设计 2 都建议自己重新手敲一遍,敲完以后改几个测试数据验证。

这套卷子和 exam-86 一样,重点不是考特别偏的知识,而是反复检查 C 语言基础是否扎实:

  • 数组和字符串是不是能稳定判断长度、下标、初始化。
  • 函数题是不是能分清形参、实参、返回值。
  • 表达式题是不是能按优先级一步一步算。
  • 综合程序是不是能拆成输入、处理、输出三个部分。

如果你能把这套卷子的字符串题、二维数组题和两个程序设计题自己写出来,你对 C 语言前半部分的基础就会稳很多。