跳转到内容

exam-91 试卷整理与详解

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

/Users/wff/Downloads/91.pdf

这套卷子的重点依旧是 C 语言基础细节:数组、字符串、函数、指针、递归、宏、循环控制、文件指针、共用体、程序改错和简单程序设计。PDF 的 OCR 对代码里的逗号、分号、花括号、引号、[]== 等符号有一些识别误差,我整理时按原卷红色标注和 C 语言语法一起校对。

特别提醒:

  • 选择题第 8 题题干写的是“一维数组”,但选项实际都是二维数组定义,应该按“二维数组”来理解。
  • 选择题第 20 题按传统教材和 Turbo C 考试口径判断,int n = 5, a[n]; 是错误的。现代部分编译器可能支持变长数组,但做这类卷子要按教材标准答。
  • 程序设计第 1 题原卷红字代码里 double x, y = 0; 后直接写 while (x >= 1e-6)x 没初始化就参与判断,这在真实代码里有问题。我在解析里给出更稳的 do...while 写法。
题号答案
1A
2A
3D
4D
5D
6A
7A
8A
9A
10A
11D
12A
13C
14A
15B
16B
17B
18C
19D
20A
21B
22A
23A
24A
25B
题号答案
1x
21
31000 10
458
5642
66102
7p = funp = fun;
82
90
102
题号答案
1N
2N
3Y
4N
5N
6Y
7N
8N
9N
10N

题目整理:

int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, *p = a;

问:不能表示 a 数组元素的表达式是哪一个?

答案:A

错误表达式:

a[*p - a]

详细解析:

初始化后:

p 指向 a[0]
*p 的值是 1
a 表示数组首地址,也就是 &a[0]

表达式 *p - a 左边是整数 1,右边是地址 a。整数和地址不能这样相减,所以这个表达式不能正确表示数组元素。

其他选项:

a[9]

表示第 10 个元素,合法。

*p

表示 p 当前指向的元素,也就是 a[0]

*p++

等价于:

*(p++)

先取出当前 p 指向的元素,再让 p 指向下一个元素。

容易踩坑点:

  • *p 是元素值。
  • pa 才是地址。
  • *p++ 会改变指针 p 的指向。

给新手的建议:

  • 看到数组和指针混合题,先分清“值”和“地址”,不要急着算。

题目问:对于 void 类型函数,调用时不可作为哪一种?

答案:A

正确理解:

void 函数没有返回值,所以不能作为表达式使用。

例如:

void print_hello(void)
{
printf("hello\n");
}

可以这样调用:

print_hello();

但不能这样写:

int x = print_hello(); // 错误

详细解析:

void 函数调用可以作为一条语句出现在循环体、if 的分支语句、自定义函数体中,但它本身没有结果值,不能参与赋值、加减乘除、比较等表达式运算。

容易踩坑点:

  • 以为只要是函数调用就一定有返回值。

给新手的建议:

  • 函数名前面的返回类型是 void,就记住:调用它是为了“做事情”,不是为了“拿结果”。

题目整理:

int max(int x, int y)
{
int z;
if (x > y)
z = x;
else
z = y;
return z;
}
int a = 45, b = 27, c = 0;
c = max(a, b);
printf("%d\n", c);

答案:D

详细解析:

调用:

max(45, 27)

函数里:

x = 45
y = 27

因为:

45 > 27

所以:

z = 45

函数返回 45,所以输出:

45

容易踩坑点:

  • 看函数题时只盯着 return,没代入实参。

给新手的建议:

  • 函数调用题最好写出“形参对应实参”的表格。

题目整理:

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

答案:D

详细解析:

递归出口:

n <= 2 时,fib(n) = 2

计算 fib(3)

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

所以输出:

4

容易踩坑点:

  • 看到 fib 就套普通斐波那契数列,忘记这题的前两项都是 2

给新手的建议:

  • 递归题先看出口值,不要凭记忆套公式。

题目问:C 语言中字符串结束符是哪一个?

答案:D

正确写法:

'\0'

详细解析:

字符串 "abc" 在内存中实际是:

'a' 'b' 'c' '\0'

其中 '\0' 是空字符,ASCII 值为 0,用来表示字符串结束。

其他选项:

  • '0' 是字符零,ASCII 值不是 0。
  • "0" 是字符串,里面有字符 '0' 和结尾 '\0'
  • "\0" 是字符串形式,不是单个字符形式。

容易踩坑点:

  • 把字符 '0' 和空字符 '\0' 混淆。

给新手的建议:

  • 记忆方式:字符串结束符是“反斜杠零”,写成 '\0'

题目问:以下能正确定义数组并正确赋初值的是哪一个?

答案:A

正确写法:

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

详细解析:

d[3][2] 表示 3 行 2 列。初始化给了前两行:

1 2
3 4
0 0

没有显式给出的元素自动补 0

其他选项的问题:

  • int a[1][2] = {{1}, {3}}; 只有 1 行,却给了 2 行初值。
  • int c[2]0 = ... 语法错误。
  • int N = 5, b[N][N]; 按传统教材口径,数组长度不能使用普通变量。

容易踩坑点:

  • 二维数组少给初值可以补 0,但不能多给超出行数的初值。

给新手的建议:

  • 二维数组初始化题,先看“定义了几行几列”,再看初值有没有越界。

题目整理:

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

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

答案:A

错误说法:

有元素 a[0][0] 和 a[0][1] 可得到初值 0,其余元素均得不到初值 0。

详细解析:

二维数组第二维是 4,初值一共有 2 个:

0, 0

因此编译器推出第一维是 1,数组等价于:

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

数组实际内容是:

a[0][0] = 0
a[0][1] = 0
a[0][2] = 0
a[0][3] = 0

前两个显式给了 0,后两个也会自动补 0

容易踩坑点:

  • 以为只有写出来的元素才初始化为 0

给新手的建议:

  • 数组只要做了部分初始化,没写到的元素会自动补 0

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

答案:A

错误写法:

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

详细解析:

二维数组定义时,第二维不能省略。

可以省略第一维:

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

但不能省略第二维:

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

因为编译器需要知道每一行有几个元素,才能计算 a[i][j] 的位置。

容易踩坑点:

  • 以为有初始化列表就可以省略任意一维。

给新手的建议:

  • 二维数组口诀:第一维可以省,第二维必须写。

题目整理:

int a[3][4];

问:非法引用是哪一个?

答案:A

非法引用:

a[0][4]

详细解析:

a[3][4] 表示:

行下标:0、1、2
列下标:0、1、2、3

所以:

a[0][4]

列下标 4 越界。

其他选项:

a[1][3] // 合法
a[4 - 2][0] // 等价于 a[2][0],合法
a[0][2 * 1] // 等价于 a[0][2],合法

容易踩坑点:

  • 数组长度是 4,最后一个合法下标是 3

给新手的建议:

  • 每次看到 a[m][n],先写出合法下标范围:第一维 0 ~ m - 1,第二维 0 ~ n - 1

题目问:s1s2 指向两个字符串,若要求当 s1 所指字符串大于 s2 所指字符串时执行语句 S;,正确写法是哪一个?

答案:A

正确写法:

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

详细解析:

字符串不能用:

s1 > s2

这样比较,因为这比较的是两个地址大小,不是字符串内容大小。

字符串比较要用 strcmp

strcmp(s1, s2) > 0 表示 s1 大于 s2
strcmp(s1, s2) == 0 表示 s1 等于 s2
strcmp(s1, s2) < 0 表示 s1 小于 s2

容易踩坑点:

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

给新手的建议:

  • 字符串比较题,第一反应找 strcmp,再看返回值和 0 的关系。

题目整理:

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

问:将 a1 串连接到 a2 串后面的语句是哪一个?

答案:D

正确写法:

strcat(a2, a1);

详细解析:

strcat 的格式是:

strcat(目标字符串, 要追加的字符串);

所以:

strcat(a2, a1);

表示把 a1 接到 a2 后面。

执行后 a2 变成:

1234abc

容易踩坑点:

  • strcat 两个参数顺序写反。
  • 忘记目标数组必须有足够空间。

给新手的建议:

  • strcat(a2, a1) 可以读成:“把 a1 加到 a2 后面”。

题目整理:

static char str[10] = "China";

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

答案:A

详细解析:

数组长度已经写明:

str[10]

所以数组元素个数就是 10

字符串 "China" 的有效字符长度是 5,再加上结束符 '\0'6 个位置,但数组整体空间仍然是 10

容易踩坑点:

  • 把字符串长度 5 当成数组元素个数。
  • 把字符串占用空间 6 当成数组元素个数。

给新手的建议:

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

题目问:以下程序段中,不能正确赋字符串的是哪一个?

答案:C

错误写法:

char s[10];
s = "abcdefg";

详细解析:

数组名不能作为赋值号左边整体赋值。

定义时可以这样初始化:

char s[10] = "abcdefg";

定义后要复制字符串,应写:

strcpy(s, "abcdefg");

容易踩坑点:

  • 把字符数组名当成普通变量。

给新手的建议:

  • 字符数组定义时可以用 = 初始化。
  • 定义之后再放字符串,要用 strcpy

题目整理:

strcat(strcpy(str1, str2), str3)

问:该函数调用的功能是什么?

答案:A

详细解析:

先看内层:

strcpy(str1, str2)

作用是把 str2 复制到 str1 中。

strcpy 的返回值是目标字符串 str1 的地址,所以外层等价于:

strcat(str1, str3)

作用是把 str3 连接到 str1 后面。

整体功能:

先将 str2 复制到 str1 中,再将 str3 连接到 str1 后面。

容易踩坑点:

  • 不知道 strcpy 会返回目标字符串地址。
  • 字符串函数嵌套时从外往里看,导致顺序错乱。

给新手的建议:

  • 函数嵌套题从最里面开始分析。

题目整理:

int a[10];

问:给数组 a 的所有元素分别赋值为 1、2、3、... 的语句是哪一个?

答案:B

正确写法:

for (i = 1; i < 11; i++)
a[i - 1] = i;

详细解析:

i = 1 时:

a[i - 1] = a[0] = 1

i = 2 时:

a[i - 1] = a[1] = 2

一直到 i = 10

a[i - 1] = a[9] = 10

刚好给 a[0] ~ a[9] 赋值。

容易踩坑点:

  • 写成 a[i] = i,会跳过 a[0],并访问到 a[10] 越界。

给新手的建议:

  • 数组长度是 10,循环赋值时一定要覆盖 0 ~ 9

题目整理:

int a[10] = {6, 7, 8, 9, 10};

答案:B

详细解析:

数组初始化从下标 0 开始:

a[0] = 6
a[1] = 7
a[2] = 8
a[3] = 9
a[4] = 10

没有显式初始化的元素自动补 0

a[5] ~ a[9] = 0

容易踩坑点:

  • 以为第一个初值赋给 a[1]
  • 以为初值 6 会对应下标 6

给新手的建议:

  • 初始化列表永远从第一个元素 a[0] 开始依次放。

题目整理:

int i, k, a[10], p[3];
k = 5;
for (i = 0; i < 10; i++)
a[i] = i;
for (i = 0; i < 3; i++)
p[i] = a[i * (i + 1)];
for (i = 0; i < 3; i++)
k += p[i] * 2;
printf("%d\n", k);

答案:B

详细解析:

第一段循环后:

a[0]=0, a[1]=1, a[2]=2, a[3]=3, a[4]=4, ...

第二段循环:

ii * (i + 1)p[i]
00p[0] = a[0] = 0
12p[1] = a[2] = 2
26p[2] = a[6] = 6

第三段循环:

k = 5 + 0 * 2 + 2 * 2 + 6 * 2
= 5 + 0 + 4 + 12
= 21

容易踩坑点:

  • i * (i + 1) 不是 i + 1
  • 忘记 k 初始值是 5

给新手的建议:

  • 多个循环连续出现时,分段列变量表,不要脑算硬扛。

题目整理:

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]);

答案:C

详细解析:

二维数组内容是:

第0列 第1列 第2列
第0行 1 2 3
第1行 4 5 6
第2行 7 8 9

循环取值:

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

所以输出:

3,5,7,

选项中对应 3,5,7

容易踩坑点:

  • 二维数组按行存放,初值不是按列填。
  • 2 - i 每次都在变。

给新手的建议:

  • 遇到二维数组输出题,先画表格,再按下标找位置。

题目整理:

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

问:实参个数是多少?

答案:D

详细解析:

函数调用的最外层实参是:

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

所以实参个数是 3

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

容易踩坑点:

  • 一看到逗号就全数进去。

给新手的建议:

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

题目问:以下定义语句中错误的是哪一个?

答案:A

错误写法:

int n = 5, a[n];

详细解析:

按传统 C 语言教材和 Turbo C 考试口径,数组长度应该是常量表达式。

n 是变量,不是常量表达式,所以这题判错。

其他选项按原卷应理解为:

char *a[3];
char s[10] = "test";
int a[] = {1, 2};

这些写法是合法的。

补充提醒:

现代一些 C 编译器支持变长数组,例如 GCC 在某些模式下可能允许 int a[n];。但这类教材卷子通常不按这个标准来判。

容易踩坑点:

  • 把“编译器可能支持”和“考试标准答案”混在一起。

给新手的建议:

  • 做基础考试题时,数组长度优先按“常量表达式”理解。

题目整理:

int w = 3;
void main(void)
{
int w = 10;
printf("%d\n", fun(5) * w);
}
int fun(int k)
{
if (k == 0)
return w;
return fun(k - 1) * k;
}

答案:B

详细解析:

fun 函数中使用的 w 是全局变量:

int w = 3;

因为 main 里的:

int w = 10;

只在 main 函数内部有效,不能影响 fun 函数里的 w

递归计算:

fun(0) = 3
fun(1) = fun(0) * 1 = 3
fun(2) = fun(1) * 2 = 6
fun(3) = fun(2) * 3 = 18
fun(4) = fun(3) * 4 = 72
fun(5) = fun(4) * 5 = 360

最后在 main 中:

fun(5) * w = 360 * 10 = 3600

容易踩坑点:

  • 混淆全局变量 w = 3 和局部变量 w = 10
  • 递归题没有先找到出口。

给新手的建议:

  • 同名变量出现时,先看它在哪个函数、哪个作用域里生效。

题目整理:

int a[10];

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

答案:A

正确写法:

a[10 - 10]

详细解析:

先算下标:

10 - 10 = 0

所以:

a[10 - 10]

等价于:

a[0]

这是合法元素。

其他选项:

  • a[3,5]3,5 是逗号表达式,结果为 5,虽然某些编译器会把它当成 a[5],但教材题通常不作为规范数组引用写法。
  • a(5) 不是数组元素引用。
  • a[10] 越界,合法下标是 0 ~ 9

容易踩坑点:

  • 数组长度是 10,最后一个合法下标是 9。

给新手的建议:

  • 数组引用最稳写法就是 数组名[整数下标]

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

答案:A

按原卷选项应理解为:

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

详细解析:

二维数组初始化时,第一维可以省略,第二维必须写出来。

这里第二维是 3,初值一共有 5 个,可以按每行 3 个来放:

1 2 3
4 5 0

最后一个位置自动补 0

其他选项的问题:

  • int a[2][3] = {1, 2, 3, 4, 5, 6, 7}; 初值太多。
  • int a[][] = ... 第二维省略,错误。
  • int a[2][] = ... 第二维省略,错误。

容易踩坑点:

  • 第一维可以省略,第二维不能省略。
  • 初值少可以补 0,初值多会出错。

给新手的建议:

  • 二维数组初始化只记一句:让编译器必须知道“每行几个元素”。

题目整理:

char a[10];

问:以下语句中不能从键盘上给 a 数组的所有元素输入值的是哪一个?

答案:A

错误写法:

a = getchar();

详细解析:

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

而且:

getchar()

一次只能读一个字符,不可能直接把整个数组填满。

如果要逐个输入 10 个字符,可以写:

for (i = 0; i < 10; i++)
a[i] = getchar();

如果输入字符串,可以写:

scanf("%s", a);

容易踩坑点:

  • 把字符数组当成一个字符变量。
  • 忘记数组名不能整体赋值。

给新手的建议:

  • 给数组元素赋值,要么逐个 a[i] = ...,要么用字符串输入函数。

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

答案:B

错误写法:

char s[5] = {'a', 'b', 'c', 'd', 'e'};

详细解析:

这只是一个包含 5 个普通字符的字符数组:

'a' 'b' 'c' 'd' 'e'

没有字符串结束符:

'\0'

所以不能作为正常字符串使用。

其他选项:

char s[] = "abcde";

编译器会自动分配 6 个字符空间,保存:

'a' 'b' 'c' 'd' 'e' '\0'
char s[6] = "abcde";

刚好 6 个位置,合法。

char s[5] = "abc";

实际占 4 个位置,剩余位置补 0,合法。

容易踩坑点:

  • 把普通字符数组和字符串混为一谈。

给新手的建议:

  • 判断是不是字符串,要看最后有没有 '\0'

题目功能:求 xy 次方。

题目整理:

double fun(double x, int y)
{
int i;
double z;
for (i = 1, z = x; i < y; i++)
z = z * ____;
return z;
}

答案:x

完整语句:

z = z * x;

详细解析:

初始:

z = x

循环每执行一次,就再乘一个 x

如果 y = 3

开始:z = x
第 1 次:z = x * x
第 2 次:z = x * x * x

所以空里应填 x

容易踩坑点:

  • z 初始值已经是 x 这件事忘掉。

给新手的建议:

  • 幂运算循环题可以拿 x^3 手动走一遍。

题目整理:

int x, y, z;
x = y = z = 2;
x = y == z;

问:x 的值是多少?

答案:1

详细解析:

先执行:

x = y = z = 2;

三个变量都是 2

再执行:

x = y == z;

因为:

y == z
2 == 2

结果为真,C 语言中真用 1 表示。

所以:

x = 1

容易踩坑点:

  • == 看成赋值号。
  • 不知道关系表达式的结果是 10

给新手的建议:

  • = 是赋值,== 是判断相等。写题时可以故意读出来:“把 y == z 的判断结果赋给 x”。

题目整理:

#define N 10
#define s(x) x * x
#define f(x) (x * x)
int i1, i2;
i1 = 1000 / s(N);
i2 = 1000 / f(N);
printf("%d %d\n", i1, i2);

答案:1000 10

详细解析:

宏只是文本替换。

先看:

i1 = 1000 / s(N);

s(N) 展开为:

N * N

再替换 N = 10

i1 = 1000 / 10 * 10;

/* 同级,从左到右:

1000 / 10 * 10 = 100 * 10 = 1000

再看:

i2 = 1000 / f(N);

f(N) 展开为:

(N * N)

替换后:

i2 = 1000 / (10 * 10);

所以:

i2 = 1000 / 100 = 10

容易踩坑点:

  • 以为 s(N)f(N) 都等价于 10 * 10
  • 忘记宏展开没有自动加括号。

给新手的建议:

  • 写宏时尽量写成:
#define S(x) ((x) * (x))

题目整理:

int a[4][4] = {
{1, 2, -3, -4},
{0, -12, -13, 14},
{-21, 23, 0, -24},
{-31, 32, -33, 0}
};
int i, j, s = 0;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if (a[i][j] < 0)
continue;
if (a[i][j] == 0)
break;
s += a[i][j];
}
}
printf("%d\n", s);

答案:58

详细解析:

规则:

  • 遇到负数:continue,跳过当前这个数,继续看本行下一个数。
  • 遇到 0break,结束当前这一行的内层循环。
  • 遇到正数:加到 s 里。

逐行看:

数据累加情况
第0行1, 2, -3, -41 + 2,负数跳过
第1行0, -12, -13, 14第一个就是 0,本行直接结束
第2行-21, 23, 0, -24-21 跳过,加 23,遇 0 结束
第3行-31, 32, -33, 0-31 跳过,加 32-33 跳过,遇 0 结束

所以:

s = 1 + 2 + 23 + 32 = 58

容易踩坑点:

  • continue 只是跳过本次循环,不是结束整个内层循环。
  • break 才会结束当前内层循环。
  • 这里的 break 只跳出内层 for(j...),不会跳出外层 for(i...)

给新手的建议:

  • 遇到 continuebreak 混合题,最好按每一行单独模拟。

题目整理:

a = (b = 4) + (c = 2);

答案:a = 6b = 4c = 2

详细解析:

赋值表达式本身也有值。

(b = 4)

执行后 b4,这个表达式的值也是 4

(c = 2)

执行后 c2,这个表达式的值也是 2

所以:

a = 4 + 2 = 6

容易踩坑点:

  • 只看到 bc 被赋值,忘记整个表达式还要计算给 a

给新手的建议:

  • 赋值表达式的值就是被赋进去的那个值。

题目整理:

int n1, n2;
scanf("%d", &n2);
while (n2 != 0)
{
n1 = n2 % 10;
n2 = n2 / 10;
printf("%d", n1);
}

输入:

2016

答案:6102

详细解析:

每次循环:

  • n2 % 10 取个位。
  • n2 / 10 去掉个位。
  • 输出刚取到的个位。

过程:

循环n2 原值n1 = n2 % 10n2 = n2 / 10输出
1201662016
22011201
320020
42202

所以输出:

6102

容易踩坑点:

  • 201 / 10 是整数除法,结果是 20,不是 20.1
  • 中间的 0 也会输出。

给新手的建议:

  • “反向输出整数”题,核心就是 % 10 取个位、/ 10 去个位。

题目问:将函数 fun 的入口地址赋给指针变量 p 的语句是什么?

答案:p = fun;

详细解析:

函数名 fun 可以表示函数入口地址。

如果 p 是已经定义好的函数指针,那么:

p = fun;

就是把函数 fun 的入口地址赋给 p

注意不要写成:

p = fun();

fun() 表示调用函数,拿到函数返回值,不是函数入口地址。

容易踩坑点:

  • 函数名后面加括号就变成“调用函数”了。

给新手的建议:

  • 函数名不加 (),常常表示函数地址;加 (),就是调用函数。

题目整理:

int a = 3, b = 4, c = 5, d = 2;
if (a > b)
if (b > c)
printf("%d", d++ + 1);
else
printf("%d", ++d + 1);
printf("%d\n", d);

答案:2

详细解析:

先判断外层:

a > b
3 > 4

结果为假,所以整个内层 if (b > c) ... else ... 都不会执行。

因此 d 没有变化,仍然是:

d = 2

最后执行:

printf("%d\n", d);

输出:

2

容易踩坑点:

  • 以为 else 会和外层 if (a > b) 配对。
  • 忽略“外层条件为假,内层根本不会执行”。

给新手的建议:

  • 悬挂 else 题记住:else 总是和离它最近且尚未匹配的 if 配对。

题目整理:

a = 12;
(0 < a) && (a < 2)

答案:0

详细解析:

先看左边:

0 < 12 -> 真 -> 1

再看右边:

12 < 2 -> 假 -> 0

逻辑与 && 要求两边都为真才是真:

1 && 0 -> 0

容易踩坑点:

  • 把数学写法 0 < a < 2 直接搬到 C 语言里。

给新手的建议:

  • C 语言里判断范围,要写成 (0 < a) && (a < 2)

题目问:unsigned int 定义无符号基本整型变量,在 Turbo C 教材环境中分配几个字节?

答案:2

详细解析:

按传统 Turbo C 教材环境:

unsigned int 占 2 个字节

补充提醒:

现代很多编译器中,unsigned int 通常占 4 个字节。考试题如果没有特别说明,通常按教材环境答。

容易踩坑点:

  • 把现代电脑上的结果和教材考试环境混在一起。

给新手的建议:

  • 做题按教材环境;实际写代码时可以用 sizeof(unsigned int) 查看。

题目:C 语言所有函数都是外部函数。

答案:N

解析:

C 语言函数可以分为外部函数和内部函数。

例如,用 static 修饰的函数具有内部链接属性,只能在当前源文件中使用:

static void helper(void)
{
printf("helper\n");
}

所以“所有函数都是外部函数”是错误的。

题目整理:

file *fp;
fp = fopen("a.txt", "r");

判断:在 Turbo C 中是否合法?

答案:N

解析:

文件指针类型应写成大写:

FILE *fp;

file 不是标准类型名。

容易踩坑点:

  • C 语言区分大小写,FILEfile 不是同一个东西。

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

答案:Y

解析:

按传统 16 位有符号 int 环境,取值范围通常是:

-32768 ~ 32767

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

补充提醒:

不同编译器中 int 的字节数可能不同,但这道题按传统教材环境判断。

题目整理:

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

题目:

strlen("ASDFG\n")

判断:值是 7

答案:N

解析:

字符串 "ASDFG\n" 的有效字符是:

A S D F G \n

一共 6 个字符。

strlen 不统计字符串结束符 '\0'

所以结果是:

6

容易踩坑点:

  • '\0' 也算进 strlen
  • 忘记 \n 是一个字符,不是两个字符。

题目:共用体变量所占的内存长度等于最长成员的长度。

答案:Y

解析:

共用体所有成员共用同一段内存空间,所以它的大小至少要能放下最长的成员。

例如:

union Data
{
char c;
int i;
double d;
};

这个共用体的大小通常由最大的成员 double d 决定。

补充提醒:

实际编译器还可能因为内存对齐,让共用体大小略大于最长成员,但基础教材里通常按“等于最长成员长度”判断。

题目:

int a[10] = {1 * 10};

判断:这样可以使数组中全部元素的值为 1

答案:N

解析:

1 * 10 是一个表达式,结果是 10

所以数组初始化为:

a[0] = 10
a[1] ~ a[9] = 0

不是全部元素都为 1

容易踩坑点:

  • 以为 {1 * 10} 表示 10 个 1

题目整理:

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

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

答案:N

解析:

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

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

例如:

int t;
S(x, y);

这种前提下才有可能正常展开使用。

容易踩坑点:

  • 以为宏定义阶段会做完整语法检查。

给新手的建议:

  • 宏不是函数,它更像“复制粘贴替换”。

题目:

char *p = "girl";

判断:含义是定义字符型指针变量 pp 的值是字符串 "girl"

答案:N

解析:

这句话前半部分对,后半部分不严谨。

p 的值不是整个字符串,而是字符串常量首字符的地址。

可以理解为:

p 指向字符串 "girl" 的第一个字符 'g'

容易踩坑点:

  • 把指针变量的值理解成它指向的整段内容。

给新手的建议:

  • 指针变量里存的是地址,不是它指向的全部数据。

题目:循环结构中的 continue 语句是使整个循环终止执行。

答案:N

解析:

continue 的作用是结束本次循环,直接进入下一次循环判断。

真正终止整个循环的是:

break;

区别:

continue:跳过本次,继续下次
break:结束循环,直接出来

容易踩坑点:

  • continuebreak 混淆。

功能:一个已排好序的一维数组,输入一个数 number,要求按原来的排序规律将它插入数组中。

整理后的正确代码:

#include <stdio.h>
int main(void)
{
int a[11] = {1, 4, 6, 9, 13, 16, 19, 28, 40, 100};
int temp1, temp2, number, end, i, j;
for (i = 0; i < 10; i++)
printf("%5d", a[i]);
printf("\n");
scanf("%d", &number);
end = a[9];
if (number > end)
{
a[10] = number;
}
else
{
for (i = 0; i < 10; i++)
{
if (a[i] > number)
{
temp1 = a[i];
a[i] = number;
for (j = i + 1; j < 11; j++)
{
temp2 = a[j];
a[j] = temp1;
temp1 = temp2;
}
break;
}
}
}
for (i = 0; i < 11; i++)
printf("%6d", a[i]);
return 0;
}

主要错误说明:

第一处:

for (i = 0; i <= 10; i++)

应改为:

for (i = 0; i < 10; i++)

原因:原数组只有 10 个已有元素,打印原数组时只能打印 a[0] ~ a[9]a[10] 是预留给新插入元素的位置。

第二处:

end = a[10];

应改为:

end = a[9];

原因:原数组最后一个有效元素是 a[9],不是 a[10]

第三处:

a[11] = number;

应改为:

a[10] = number;

原因:数组 a[11] 的合法下标是 0 ~ 10a[11] 越界。

第四处:

if (a[i] < number)

应改为:

if (a[i] > number)

原因:数组是从小到大排序,应该找到第一个比 number 大的位置,把 number 插到它前面。

补充修正:

原卷代码里还有一些符号错误,上机时也要改:

scanf("%d" &number);

应写为:

scanf("%d", &number);

以及:

a[i] = number,

应写为:

a[i] = number;

详细解题过程:

假设原数组是:

1 4 6 9 13 16 19 28 40 100

如果输入 20

  1. 20 不大于最后一个元素 100
  2. 从前往后找第一个比 20 大的数。
  3. 找到 28,把 20 放到 28 的位置。
  4. 原来的 28, 40, 100 依次后移。

结果:

1 4 6 9 13 16 19 20 28 40 100

容易踩坑点:

  • 数组 a[11] 的最后一个合法下标是 10
  • 原数组已有 10 个元素,最后一个已有元素是 a[9]
  • 插入排序题要找第一个“大于插入值”的位置。

给新手的建议:

  • 这种题不要先看代码,先拿一个具体数字,比如 20,在纸上模拟插入过程。

功能:求 0 ~ 7 这 8 个数字所能组成的奇数个数。

整理后的正确代码:

#include <stdio.h>
int main(void)
{
long sum = 4, s = 4;
int j;
for (j = 2; j <= 8; j++)
{
printf("\n%ld", sum);
if (j <= 2)
s *= 7;
else
s *= 8;
sum += s;
}
printf("\nsum=%ld", sum);
return 0;
}

三处错误说明:

第一处:

long sum = 4; s = 4;

应改为:

long sum = 4, s = 4;

原因:s 也需要声明为 long 类型。用分号断开后,s = 4; 就变成使用一个还没声明的变量。

第二处:

printf("\n%d", sum);

应改为:

printf("\n%ld", sum);

原因:sumlong 类型,输出格式应使用 %ld

第三处:

if (j < 2)

应改为:

if (j < 3)

也可以写成:

if (j <= 2)

原因:当统计两位奇数时,最高位不能为 0,所以最高位只有 1 ~ 7 共 7 种选择;从三位数开始,中间位才可以有 8 种选择。

详细解题过程:

组成奇数,个位必须是奇数:

1、3、5、7

个位有 4 种选择,所以一位奇数有 4 个。

两位奇数:

十位不能为 0,有 7 种选择
个位必须奇数,有 4 种选择

所以新增两位奇数:

7 * 4 = 28

三位及以上:

  • 最高位不能为 0。
  • 中间位可以是 0 ~ 7 任意数字。
  • 个位必须是 1、3、5、7

这段程序用 s 表示当前位数新增的奇数个数,用 sum 累加总数。

容易踩坑点:

  • long 输出写成 %d
  • 多个变量声明时把逗号写成分号。
  • 最高位不能为 0,个位必须是奇数。

给新手的建议:

  • 计数题先把“个位、最高位、中间位”分开想,别一上来就看循环。

题目:按公式计算 y 的值,精度为 1e-6,即最后一项的绝对值小于 1e-6 时结束。

从卷面代码看,本题要累加的项是:

1 / (r * r + 1)

也就是:

y = 1/(1*1+1) + 1/(2*2+1) + 1/(3*3+1) + ...

参考答案:

#include <stdio.h>
#include <math.h>
int main(void)
{
int r = 1;
double x, y = 0.0;
do
{
x = 1.0 / (r * r + 1);
if (fabs(x) >= 1e-6)
{
printf("x=%f\n", x);
y += x;
}
r++;
} while (fabs(x) >= 1e-6);
printf("y=%f\n", y);
return 0;
}

详细解题过程:

第一步,用 r 表示当前是第几项:

int r = 1;

第二步,计算当前项:

x = 1.0 / (r * r + 1);

注意这里必须写 1.0,不要写 1。写 1.0 可以保证进行浮点运算。

第三步,如果当前项的绝对值还不小于 1e-6,就把它加到 y 中:

if (fabs(x) >= 1e-6)
y += x;

第四步,r++,准备计算下一项。

为什么这里推荐 do...while

原卷红字代码大致是:

double x, y = 0;
while (x >= 1e-6)
{
x = 1.0 / (r * r + 1);
y = y + x;
r++;
}

问题在于:x 没有初始化,就直接在 while (x >= 1e-6) 里使用了。未初始化变量的值是不确定的,程序可能直接不进入循环,也可能出现奇怪结果。

do...while 更适合这种“先算出一项,再判断这一项是否达到精度”的题。

容易踩坑点:

  • x 未初始化就参与 while 判断。
  • 写成 1 / (r * r + 1),容易产生整数除法问题。
  • 最后一项已经小于 1e-6 时,不应该再累加进去。
  • 题目说“绝对值”,更稳妥的写法是用 fabs(x)

给新手的建议:

  • 级数求和题先确定三件事:每一项怎么算、什么时候加、什么时候停。
  • 如果停止条件依赖“刚刚算出来的这一项”,优先考虑 do...while

题目:输入正整数 mn,设 100 <= m <= n <= 999,输出 mn 之间满足条件的三位数。

条件:

个位数的立方 + 十位数的平方 + 百位数 = 该数本身

例如:

135 = 1 + 3 * 3 + 5 * 5 * 5

参考答案:

#include <stdio.h>
int main(void)
{
int m, n;
int i, a, b, c;
scanf("%d%d", &m, &n);
for (i = m; i <= n; i++)
{
a = i % 10;
b = i / 10 % 10;
c = i / 100 % 10;
if (i == c + b * b + a * a * a)
printf("%d ", i);
}
printf("\n");
return 0;
}

详细解题过程:

第一步,遍历 m ~ n

for (i = m; i <= n; i++)

第二步,拆出三位数的各个位:

a = i % 10; // 个位
b = i / 10 % 10; // 十位
c = i / 100 % 10; // 百位

例如 i = 135

a = 135 % 10 = 5
b = 135 / 10 % 10 = 13 % 10 = 3
c = 135 / 100 % 10 = 1 % 10 = 1

第三步,判断是否满足条件:

if (i == c + b * b + a * a * a)

135

c + b * b + a * a * a
= 1 + 3 * 3 + 5 * 5 * 5
= 1 + 9 + 125
= 135

所以输出 135

如果输入:

135 600

输出:

135 175 518 598

容易踩坑点:

  • 忘记声明 mn
  • 个位、十位、百位拆错。
  • a * a * a 写成 a ^ 3。C 语言里 ^ 不是乘方,而是按位异或。
  • 循环边界要包含 n,所以写 i <= n

给新手的建议:

  • 拆三位数模板一定要背熟:
ge = x % 10;
shi = x / 10 % 10;
bai = x / 100;

这套 exam-91 很适合检查下面这些基础:

  • 数组下标从 0 开始,长度为 10 的数组合法下标是 0 ~ 9
  • 二维数组第一维可以省略,第二维不能省略。
  • 字符串结束符是 '\0',不是 '0'
  • 字符数组不等于字符串,字符串必须有结束符。
  • 数组名不能整体赋值。
  • 字符串比较、复制、连接分别用 strcmpstrcpystrcat
  • void 函数没有返回值,不能放进表达式里用。
  • 函数名不加括号可以表示入口地址。
  • continuebreak 的作用不同。
  • 宏定义只是文本替换,括号非常重要。
  • 把选择题第 6、7、8、9、15、16、18、22、23、24 题放在一起复习,它们都和数组有关。
  • 把选择题第 5、10、11、12、13、14、25 题放在一起复习,它们都和字符串有关。
  • 把选择题第 2、3、4、19、21 题放在一起复习,它们都和函数调用、递归、实参个数有关。
  • 填空题第 3 题一定要手动展开宏,别直接心算。
  • 填空题第 4 题建议画二维表格,分别标出 continuebreak 的效果。
  • 改错题第 1 题建议自己输入一个 number = 20,在纸上模拟数组后移过程。
  • 程序设计第 1 题重点记住:循环条件依赖当前项时,不要使用未初始化变量。
  • 程序设计第 2 题重点练 % 10/ 10 拆位。

第 91 套卷子看起来题目很多,但核心其实还是几条主线:

  • 数组:下标范围、初始化、二维数组列数不能省略。
  • 字符串:'\0'、字符数组空间、strcpystrcatstrcmp
  • 函数:返回值、void、递归、函数指针、实参个数。
  • 表达式:赋值表达式、关系表达式、逻辑表达式、宏展开。
  • 循环:breakcontinue、边界条件、未初始化变量。

复习时不要只背答案。更好的做法是每题都问自己一句:它到底想考哪个坑?能说出这个坑,下一次遇到变形题就不容易慌了。