跳转至

期中考试参考答案

本页最后更新于 ,内容可能过时

一、单选题(每题 2.5 分,共 20 分)

1 · 合法的关键字

下列选项,______ 是 C 语言中合法的关键字。

  • A. integer
  • B. sizeof
  • C. main
  • D. array
答案

B

解析

判据是 C 的关键字表:32 个关键字中包含 sizeof(C89 起即为关键字,C23 仍然保留),另外三个都不是关键字。

  • A:integer 只是普通标识符,C 里对应的类型名是 int。
  • B:sizeof 是关键字,用于求类型或对象的字节数 ✅
  • C:main 是函数名,不是关键字。名字本身合法,只是与 main 函数重名会有冲突。
  • D:array 是普通标识符,不是关键字。

真机实测(gcc 15.2.0,-std=gnu23):分别写 int integer;、int array;,都能通过语法检查;写 int sizeof; 报 expected identifier or '(' before 'sizeof',说明它不能被当作名字使用。

易错:

  1. 把 main 当成关键字(它是合法的普通标识符,只是程序入口函数习惯叫这个名字)
  2. 把 sizeof 当成函数(它是运算符,不是库函数)

2 · || 与 && 的短路求值

如下程序段的输出结果是 ______

int a, b, c;
a = b = c = 1 / 2;
a++ || ++b && c++;
printf("%d, %d, %d\n", a, b, c);
  • A. 1, 1, 1
  • B. 1, 1, 0
  • C. 1, 0, 0
  • D. 1, 0, 1
答案

A

解析

分两步:先确定初始化的值,再按短路规则确定哪些子表达式会被计算。

  • 1 / 2 是两个整数相除,结果是 0,所以 a、b、c 都从 0 开始。
  • a++ 先取值 0(假),再让 a 变成 1。
  • && 的优先级高于 ||,表达式按 a++ || (++b && c++) 结合。
  • || 左侧为假,右侧需要计算:++b 使 b 先变成 1,值为 1(真),继续计算 c++,取值为 0 后 c 变成 1。
  • 最终 a = 1、b = 1、c = 1。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):输出 1, 1, 1;编译时给出两条警告 suggest parentheses around '&&' within '||' 和 value computed is not used(整个 || 表达式的结果没有参与后续运算)。

易错:

  1. 把 1 / 2 当成 0.5(整型相除,结果截断为 0)
  2. 以为 a++ 为假后右侧不再计算(|| 左侧为假时右侧必须计算)
  3. 忽略 && 的优先级高于 ||,把式子读成 (a++ || ++b) && c++

3 · do-while 的条件

执行如下循环时,说法正确的是 ______

int x = -2;
do {
    x = x * x;
} while (x = 1);
  • A. 循环体将执行一次
  • B. 循环体将执行无限次
  • C. 循环体不执行
  • D. 系统将提示有语法错误
答案

B

解析

条件写的是 x = 1,是赋值表达式而不是比较:每轮都把 x 重新赋成 1,表达式的值也恒为 1,条件永远为真。

  • do-while 的循环体至少执行一次,所以 C 不成立。
  • 条件中少了第二个 =,本意应为 x == 1;按题意推导,循环不会自行结束,选 B。
  • 该写法在语法上完全合法,D 不成立。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):程序在 2 秒内没有退出,被 timeout 终止(退出码 124)。

x = x * x 的增长没有上限

x 每轮平方,若干轮后超出 int 的表示范围;有符号整数溢出在标准中属于未定义行为。因此这段循环的性质是"条件恒为真、不会正常结束",而不是"保证无限执行"。考试按题意答 B。

易错:

  1. 把 x = 1 读成 x == 1,进而认为循环执行一次就结束
  2. 以为 do-while 的条件为假时循环体一次也不执行(先执行后判断)

4 · strlen 与 sizeof

若有定义 char s[] = "abc\0def";,则 strlen(s) 和 sizeof(s) 的值分别是

  • A. 3 和 8
  • B. 7 和 8
  • C. 3 和 7
  • D. 7 和 7
答案

A

解析

两个函数/运算符的口径不同:strlen 数到第一个 '\0' 为止,sizeof 是整个数组占的字节数。

  • 字符串字面量 "abc\0def" 里有 a、b、c、\0、d、e、f 共 7 个字符,另外编译器会在末尾自动补一个 '\0'。
  • strlen(s) 遇到中间那个 '\0' 就停,结果是 3。
  • sizeof(s) 统计整个数组,结果是 8。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):输出 3 8。

易错:

  1. 以为 strlen 会跳过中间的 '\0' 继续往后数
  2. 忘记字面量末尾自动补的 '\0',把 sizeof 算成 7

5 · 一维数组的初始化

对一维数组 int a[5]; 进行初始化,希望数组元素为 1, 2, 0, 0, 0,分别使用了如下方法

  • (1) int a[5] = {1, 2};
  • (2) int a[5] = {1, 2, 0, 0, 0};
  • (3) int a[5] = {1, 2, 0, 0, 0, 0};
  • (4) int a[5] = {1, 2, , , };

在上述初始化方法中正确的有 ______ 种

  • A. 1
  • B. 2
  • C. 3
  • D. 0
答案

B

解析

判据:初值个数可以少于元素个数(不足的补 0),不能多于;花括号里也不能留空的初值。

  • (1) 合法 ✅:只给前两个元素,其余自动补 0,结果就是 1, 2, 0, 0, 0。
  • (2) 合法 ✅:五个元素逐个给出,正是 1, 2, 0, 0, 0。
  • (3) 不合法:六个初值给五个元素的数组,标准把初值过多视为约束违反。
  • (4) 不合法:, 之间没有表达式,编译不过。

共 2 种,选 B。

真机实测(gcc 15.2.0,-std=gnu23 -Wall):

  • (1)、(2) 编译通过。
  • (3) 给出警告 excess elements in array initializer,但 gcc 仍然生成代码,数组元素实测为 1 2 0 0 0。按标准它不合法,按 gcc 的宽松处理它"能编译",本题按标准判为不正确。
  • (4) 报错 expected expression before ',' token,编译不过。

警告与错误不是一回事

gcc 对「初值过多」只发警告,该写法不符合标准,但程序照常生成。判卷以语言规则为准,不以编译器是否报错为准。

易错:

  1. 把 (3) 当成正确(初值 6 个,超过数组长度 5)
  2. 把 (4) 当成"省略的初值补 0"(省略只能在末尾整体省略,不能留空位)

6 · 结构化程序设计的三种基本结构

C 语言中用于结构化程序设计的 3 种基本结构是 ______

  • A. 顺序结构, 选择结构, 循环结构
  • B. if, switch, break
  • C. for, while, do-while
  • D. if, for, continue
答案

A

解析

三种基本结构是顺序、选择、循环,与具体用哪条语句实现无关。

  • A:顺序、选择、循环 ✅
  • B:break 是跳转语句,不是基本结构。
  • C:这是循环语句的三种写法,只对应"循环结构"一种。
  • D:三种语句分属选择、循环、跳转,同样不是三种基本结构的名称。

易错:把"实现某一结构的语句"当成"结构本身"来选(B、C、D 都是这一类)。


7 · if 条件的等价写法

在 if(x) 条件中的 x 与下面条件表达式等价的是

  • A. x!=0
  • B. x==1
  • C. x!=1
  • D. x==0
答案

A

解析

C 的条件按"非零为真、零为假"判断,所以 if(x) 成立当且仅当 x != 0。

  • A:x != 0 ✅,与 if(x) 的判据一致。
  • B:x == 1 把 2、3、-1 这类非零值排除掉了。
  • C:x != 1 在 x 为 0 时也成立,与原条件相反。
  • D:x == 0 正好与原条件相反。

易错:以为条件必须等于 1 才成立。


8 · 二维数组的说明

以下对二维数组 a 的正确说明是( )

  • A. int a[3][];
  • B. float a[3, 4];
  • C. double a[1][4];
  • D. float a(3)(4);
答案

C

解析

判据:二维数组的第二维长度必须写出来,两维各用一对方括号。

  • A:第二维没有长度,元素类型成了不完整的 int[],报错 array type has incomplete element type 'int[]'。
  • B:维度之间用逗号,报错 expected ']' before ',' token。
  • C:double a[1][4] 合法 ✅
  • D:维度写成了圆括号,报错 expected declaration specifiers or '...' before numeric constant。

真机实测(gcc 15.2.0,-std=gnu23):四段声明逐个做语法检查,只有 C 通过。

易错:把 a[3, 4] 当成"三维度写法"(逗号在方括号里是逗号表达式,不是分隔两个维度)。


二、程序阅读题(每题 5 分,共 20 分)

1 · 程序功能

以下程序的功能是 ______

#include <stdio.h>

int main() {
    int i, s = 0;
    for (i = 1; i < 10; i += 2)
        s += i + 1;
    printf("%d\n", s);
    return 0;
}
答案

计算并输出 2+4+6+8+10 的和

解析

i 从 1 开始、每次加 2,取到 1、3、5、7、9;循环体累加的是 i + 1,即 2、4、6、8、10。

  • 累加项:2 + 4 + 6 + 8 + 10 = 30
  • 输出的是这个和,所以功能是"计算并输出 2+4+6+8+10 的和"。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):输出 30。

易错:把累加项看成 i 本身,误答"1+3+5+7+9 的和"(那是 25)。


2 · switch 穿透

以下程序运行所产生的输出是 ______

#include <stdio.h>

int main() {
    int i, a = 0, b = 0, c = 0;
    for (i = 0; i < 10; i++) {
        switch (i % 4) {
        case 0: a++;
        case 1: b++;
        case 2: c++; break;
        case 3: a++; break;
        }
    }
    printf("%d,%d,%d\n", a, b, c);
    return 0;
}
答案

5,6,8

解析

i 取 0~9,i % 4 依次是 0、1、2、3 循环;case 0 和 case 1 后面没有 break,会继续往下执行。

  • i % 4 == 0(i = 0, 4, 8,共 3 次):a++ 后穿透执行 case 1 的 b++、case 2 的 c++ 再 break → 每次加 a、b、c 各 1
  • i % 4 == 1(i = 1, 5, 9,共 3 次):b++、c++ → b、c 各加 3
  • i % 4 == 2(i = 2, 6,共 2 次):只 c++ → c 加 2
  • i % 4 == 3(i = 3, 7,共 2 次):a++ 后 break → a 加 2

汇总:a = 3 + 2 = 5,b = 3 + 3 = 6,c = 3 + 3 + 2 = 8。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):输出 5,6,8。

易错:

  1. 把每个 case 当成独立的 if,忽略穿透
  2. 次数统计错误:0~9 里模 4 余 0 的是 0、4、8 三个,余 1 的是 1、5、9 三个

3 · continue 与 break

写出下面程序执行后的屏幕显示结果 ______

#include <stdio.h>

int main() {
    int i, j = 0;
    for (i = 0; i < 20; i++) {
        if (i % 4 == 0) continue;
        if (i - 9) j++;
        else break;
    }
    printf("i=%d,j=%d\n", i, j);
    return 0;
}
答案

i=9,j=6

解析
  • i % 4 == 0 时 continue,跳过本轮:0、4、8、12、16 都不参与后面的判断。
  • if (i - 9) 以差值作条件:i 不等于 9 时差非零(真),j++;i 等于 9 时差为 0(假),执行 break。
  • i 从 0 递增到 9 的过程中,使 j 加 1 的是 1、2、3、5、6、7,共 6 次。
  • i = 9 时 break 退出循环,此时 i 保持为 9、j 为 6。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):输出 i=9,j=6。

易错:

  1. 把 if (i - 9) 当成"i 等于 9 时成立"(正好相反)
  2. 忽略 break 时 i 已递增到 9,把输出写成 i=8

4 · while 条件中的自增

写出下面程序执行后的输出结果 ______

#include <stdio.h>

int main() {
    int x = 0, y = 4, z = 2;
    while (z-- > 0 && ++x < 3)
        y = y - 1;
    printf("%d,%d,%d\n", x, y, z);
    return 0;
}
答案

2,2,-1

解析

z-- 是后缀自减,比较时用的是自减前的值;++x 是前缀自增,先加再用。逐轮看:

  • 第 1 轮:z 取 2 比较(2 > 0 真),z 变成 1;++x 使 x 变 1(1 < 3 真)→ y 减为 3
  • 第 2 轮:z 取 1 比较(真),z 变成 0;++x 使 x 变 2(真)→ y 减为 2
  • 第 3 轮:z 取 0 比较(0 > 0 假),z 变成 -1;&& 短路,右侧不再计算,x 保持 2

循环结束时 x = 2、y = 2、z = -1。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):输出 2,2,-1。

易错:

  1. 以为 z-- 比较时用的是减 1 之后的值(后缀先用后改)
  2. 在第三轮仍去算 ++x(&& 左侧为假时右侧短路,x 不再增加)
  3. 以为循环结束时 z 停在 0(比较那一次自减已经发生,z 是 -1)

三、改错题(10 分)

注意:不得增行或删行,也不得更改程序结构。

1 · 正整数转换成数字字符序列

以下程序将输入的正整数转换成数字字符序列,输出转换结果

/* 1 */  #include <stdio.h>
/* 2 */
/* 3 */  int main()
/* 4 */  {
/* 5 */      char s[10];
/* 6 */      int i = 0, n, t;
/* 7 */      scanf("%d", n);
/* 8 */      if (n = 0) s[i++] = '0';
/* 9 */      else {
/* 10 */         t = n;
/* 11 */         while (t > 0) { s[i++] = t % 10 + '0'; t /= 10; }
/* 12 */     }
/* 13 */     while (i > 0) putchar(s[--i]);
/* 14 */     printf("\n");
/* 15 */     return 0;
/* 16 */ }
答案

答案可能不唯一,还在讨论当中
如果你的答案正确,且和下面的不同,请及时反馈给助教

  • 第 7 行改为 scanf("%d", &n);
  • 第 8 行改为 if (n == 0) s[i++] = '0';
解析

第 7 行少取地址符:scanf 需要变量的地址才能写回,传 n 的值会被当成指针使用。

第 8 行把比较写成了赋值:n = 0 会把 n 清零,表达式的值恒为 0,if 分支永远不执行。

  • 两处都改完之后,输入 0 走 if 分支输出 0;输入正整数走 else 分支逐位取余存进 s,再逆序输出。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):

  • 原样编译只给出两条警告(format '%d' expects argument of type 'int *', but argument 2 has type 'int'、suggest parentheses around assignment used as truth value),程序仍能生成。运行时输入 12345 出现段错误(退出码 139),原因是 scanf 把 n 的未初始化值当成了地址。
  • 改完之后 scanf 的格式串与参数类型一致,警告消失。输入 0 输出 0,输入 7 输出 7,输入 12345 输出 12345。

第 8 行为什么不会让 if 恒为真

n = 0 的值是 0,按"非零为真"判断属于假,所以是 if 分支被跳过,不是恒为真。若写成 n = 1 才是恒为真。

易错:

  1. 只改第 7 行的 &,漏掉第 8 行的 ==
  2. 把第 8 行改成 if (n = 0) 之外的其他结构(题目不允许增删行、改结构)

2 · 累加数列

已知数列:S=1+1/(1×2)+1/(2×3)+1/(3×4)+...,以下程序输入一个阈值 eps,不断累加,直到当前项的值不大于 eps 时停止,输出累加和

/* 17 */ #include <stdio.h>
/* 18 */ int main()
/* 19 */ {
/* 20 */     int i;
/* 21 */     double t, s = 0, eps;
/* 22 */     scanf("%f", &eps);
/* 23 */     i = 1;
/* 24 */     t = 1.0;
/* 25 */     while (t > eps)
/* 26 */     {
/* 27 */         s = s + t;
/* 28 */         i++;
/* 29 */         t = 1 / (i * (i + 1));
/* 30 */     }
/* 31 */     printf("sum = %lf\n", s);
/* 32 */     return 0;
/* 33 */ }
答案

答案可能不唯一,还在讨论当中
如果你的答案正确,且和下面的不同,请及时反馈给助教

  • 第 22 行改为 scanf("%lf", &eps);
  • 第 23 行改为 i = 0;
  • 第 29 行改为 t = 1.0 / (i * (i + 1.0));
解析

三处错误分别在第 22、23、29 行:

  • 第 22 行:eps 是 double,scanf 读 double 要用 %lf。%f 按 float * 解释,只写 4 个字节,eps 的值是错的。
  • 第 23 行:序列首项是 1。循环体先把 t 累加、再让 i 自增、最后按 1/(i(i+1)) 生成下一项,所以进循环时 i 应为 0:第一次 i++ 后是 1,下一项正是 1/(1×2)。写成 i = 1 会让第 2 项变成 1/(2×3),漏掉 1/2。
  • 第 29 行:1 / (i * (i + 1)) 是整数除法,结果恒为 0,t 在第二轮变成 0,循环立即结束。

三处改完,t 依次为 1、1/2、1/6、1/12……,与题面数列一致。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):

  • 原样编译给出警告 format '%f' expects argument of type 'float *', but argument 2 has type 'double *'。输入 0.001 时输出 sum = 1.000000——整数除法让 t 立刻归零,只累加了首项。
  • 改完之后输入 0.001 输出 sum = 1.968750,输入 0.000001 输出 sum = 1.999000,随阈值减小向 2 收敛。

第 29 行写成 1.0 / (i * (i + 1.0)) 的理由

分子 1.0 是 double,i * (i + 1.0) 也因为 i + 1.0 变成 double,整个除法按浮点做。只把分子写成 1.0 也能得到正确结果,两种写法等价。

易错:

  1. 只改第 22 行就认为改动完成(第 23、29 行仍然错)
  2. 漏掉第 23 行:i = 1 使第二项成为 1/(2×3),与题面数列错位
  3. 改为 i = 0 后是否出现 1/(0×1) 除零:不会,i++ 在生成下一项之前执行

四、完全程序题(每空 3 分,共 21 分)

1 · 数组线性查找

以下是一个数组线性查找的程序:从数组的第一个位置开始,找到 key 的第二个出现时,记录位置

#include <stdio.h>

int main() {
    int a[10] = {3, 7, 2, 7, 9, 7, 1, 7, 5, 7};
    int n = 10, key = 7, i, count = 0, pos = ______(1)______;
    for (i = 0; i < n; i++) {
        if (a[i] == key) {
            ______(2)______;
            if (______(3)______) {
                pos = i;
                ______(4)______;
            }
        }
    }
    if (pos != -1)
        printf("第二次出现的位置: %d\n", pos);
    else
        printf("key 出现次数不足两次\n");
    return 0;
}
答案
  • (1) -1
  • (2) count++
  • (3) count == 2
  • (4) break
解析

后面的 if (pos != -1) 说明 pos 的初值是"没找到"的标记,取 -1。

  • (2) 每遇到一次 key 都要计数,填 count++。
  • (3) 题目要的是"第二次出现",所以计数值到 2 时命中,填 count == 2。
  • (4) 记录位置后不需要继续找,填 break 结束循环。

按题意推导:a 中 key = 7 出现在下标 1、3、5、7、9,第二次出现在下标 3。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):补全后输出 第二次出现的位置: 3。

易错:

  1. (1) 填 0:那样即便 key 只出现一次也会按"位置 0"输出,与 pos != -1 的判据冲突
  2. (3) 填 count > 2:要到第三次出现才命中
  3. (4) 漏填 break,pos 会被后续出现的位置覆盖

2 · 选择排序

使用选择排序算法将数组 a 中的数值按从小到大的顺序排序,然后输出排序后的数组

选择排序的基本思想:每一趟从待排序的元素中选出最小的元素,与待排序部分的第一个元素交换,直到全部排完

#include <stdio.h>

int main() {
    int a[] = {4, 45, 65, 43, 32, 3, 72, 27};
    int n, i, j, minIndex, t;
    n = ______(5)______;
    for (i = 0; i < n - 1; i++) {
        ______(6)______;
        for (j = i + 1; j < n; j++)
            if (a[j] < ______(7)______)
                minIndex = j;
        if (minIndex != i) {
            t = a[i];
            a[i] = a[minIndex];
            a[minIndex] = t;
        }
    }
    return 0;
}
答案
  • (5) sizeof(a) / sizeof(a[0])
  • (6) minIndex = i
  • (7) a[minIndex]
解析
  • (5) a 没有显式给出长度,元素总数 = 数组总字节数 / 单个元素字节数。sizeof(a) 是 32,sizeof(a[0]) 是 4,商为 8。
  • (6) 每一趟先假定待排序部分的第一个元素最小,填 minIndex = i。
  • (7) 内层循环逐个与当前记录的最小值比较,填 a[minIndex]。

按题意推导:每趟把未排序部分的最小值换到第 i 位,i 从 0 到 n - 2,结束后整体升序。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):补全后数组为 3 4 27 32 43 45 65 72(原程序只排序不输出,这里补了一句打印用于核对)。

sizeof(a) 只在数组名上可用

sizeof(a) / sizeof(a[0]) 成立的前提是 a 还是数组类型。若 a 作为参数传进函数,它会退化成指针,sizeof(a) 变成指针大小,这个除法就不再是元素个数。

易错:

  1. (6) 填 minIndex = 0:从第二趟起把已排好的元素也纳入比较范围
  2. (7) 填 a[j]:a[j] 与自身比较恒不成立,minIndex 不再更新

五、编程题(第 1 题 14 分,第 2 题 15 分,共 29 分)

1 · 矩阵上下翻转

输入一个 n 行 m 列(0<n,m<20)的矩阵 A,输出它上下翻转(第 1 行与最后一行交换,第 2 行与倒数第 2 行交换……)后的矩阵 B

输入:

3 2
1 2
3 4
5 6

输出:

5 6
3 4
1 2

参考代码:

#include <stdio.h>

int main() {
    int n, m, i, j;
    int a[20][20];

    scanf("%d %d", &n, &m);
    for (i = 0; i < n; i++)
        for (j = 0; j < m; j++)
            scanf("%d", &a[i][j]);

    for (i = n - 1; i >= 0; i--) {
        for (j = 0; j < m; j++) {
            if (j > 0)
                printf(" ");
            printf("%d", a[i][j]);
        }
        printf("\n");
    }
    return 0;
}
解析

思路一(参考代码采用):先按行读入,再从最后一行开始逐行输出。翻转后的第 1 行就是原来的第 n 行,第 2 行是原来的第 n-1 行,因此输出时行号从 n - 1 递减到 0 即可。

  • 数组按上界开成 a[20][20]:题目给定 0 < n, m < 20,开 20 足够。
  • 每行元素之间输出一个空格,用 if (j > 0) 控制不在行首补空格;每行结束输出换行。
  • 时间与空间都是 O(nm)。

思路二:先在数组里把第 i 行与第 n-1-i 行整行交换,再按正常顺序输出。两种做法结果相同,参考代码不执行交换。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):输入题面样例,输出与题目给出的输出逐行一致;另测 1 行 3 列(1 3 / 1 2 3),输出仍为 1 2 3,行号递减的循环对 n = 1 也成立。

不用真的构造矩阵 B

题目说"输出翻转后的矩阵 B",按行倒序输出即可达到同样效果,不必额外开一个数组存 B。判分依据输出结果,不检查中间是否交换过。

易错:

  1. 行循环写成 i = 0; i < n; i++,内层再按 n-1-i 取行,容易与列下标写混
  2. 每个数字后面都打印空格,行尾多一个空格
  3. 把 n 和 m 的顺序写反

2 · 删除字符串中的字符

编写程序,输入两个字符串 str1 和 str2(长度均小于 120),删除 str1 中所有在 str2 出现的字符(大写和小写都要删除),输出处理后的 str1。要求:不能使用其他数组

例:

输入:

I love C Language.
love

输出:

I  C anguag.

参考代码:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[120], str2[120];
    char c1, c2;
    int i, j, is_remove;

    fgets(str1, 120, stdin);
    fgets(str2, 120, stdin);
    str1[strcspn(str1, "\n")] = '\0';
    str2[strcspn(str2, "\n")] = '\0';

    for (i = 0; str1[i] != '\0'; i++) {
        is_remove = 0;
        for (j = 0; str2[j] != '\0'; j++) {
            c1 = str1[i];
            c2 = str2[j];
            if (c1 >= 'A' && c1 <= 'Z')
                c1 = c1 - 'A' + 'a';
            if (c2 >= 'A' && c2 <= 'Z')
                c2 = c2 - 'A' + 'a';
            if (c1 == c2) {
                is_remove = 1;
                break;
            }
        }
        if (!is_remove)
            putchar(str1[i]);
    }
    putchar('\n');

    return 0;
}

另一版(用 tolower 转换大小写):

#include <stdio.h>
#include <string.h>
#include <ctype.h>

int main() {
    char str1[120], str2[120];
    int i, j, is_remove;

    fgets(str1, 120, stdin);
    fgets(str2, 120, stdin);
    str1[strcspn(str1, "\n")] = '\0';
    str2[strcspn(str2, "\n")] = '\0';

    for (i = 0; str1[i] != '\0'; i++) {
        is_remove = 0;
        for (j = 0; str2[j] != '\0'; j++) {
            if (tolower(str1[i]) == tolower(str2[j])) {
                is_remove = 1;
                break;
            }
        }
        if (!is_remove)
            putchar(str1[i]);
    }
    putchar('\n');

    return 0;
}
解析

逐个检查 str1 的每个字符,看它在 str2 里有没有出现过(忽略大小写):出现过就不输出,没出现过就输出。

  • 大小写统一有两种写法:手写 if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';,或调用 tolower。两版其余部分相同。
  • 题目要求"不能使用其他数组",所以不另开数组存结果。参考代码直接 putchar 输出;也可以把保留下来的字符按 str1[k++] 写回 str1(k 不会超过 i,不会覆盖尚未检查的字符),最后补 '\0' 再 printf("%s\n", str1)。
  • 字符串长度小于 120,数组开 120 足够;fgets 最多读入 119 个字符,再补 '\0'。
  • fgets 会把行末换行也读进来,所以用 strcspn 找到换行并改成 '\0'。不处理的话,str1 末尾的换行会随字符一起输出;str2 末尾的换行还会参与比较,把 str1 的换行当成"要删除的字符"。
  • 时间 O(|str1| × |str2|),空间只用两个输入数组。

真机实测(gcc 15.2.0,-O2 -std=gnu23 -Wall):

  • 题面样例(I love C Language. / love)输出 I C anguag.,与题目一致。中间两个空格来自 I 后面的空格和被删空的 love 部分。
  • 第二行不以换行结尾(love 后直接 EOF)时输出不变,说明换行处理对两种输入都成立。
  • 另测 Hello World / lo,输出 He Wrd,覆盖了大写 H、W 的情况。

为什么用 strcspn 而不是 strlen(str1) - 1

strlen(str1) - 1 假定最后一定是换行,输入末尾没有换行时会删掉最后一个正常字符。strcspn(str1, "\n") 返回第一个换行或 '\0' 的位置,两种情况都安全。

易错:

  1. 忘了 fgets 的换行,输出多一个空行
  2. 用 scanf("%s", str1) 读入,遇到空格就停止,只读到第一个词
  3. 只按原样比较,未把 H 与 h 视为同一字符
  4. 另开一个数组存结果,违反"不能使用其他数组"的约束