跳转至

知识点 05 · 数组

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

1 · 数组名不可修改

对于定义 int a[3]={0}, b[3], i = 3, *j; 下列是合法的表达式的是( )。

  • A. a = b;
  • B. *j = *(a+i);
  • C. a++;
  • D. j=a+1;
答案

D

解析

逐项判断:

  • A 错:数组名不是左值,不能整体赋值(想拷贝数组必须用循环或 memcpy)。
  • B 错:j 声明后未初始化,*j = ... 是往野指针写数据(未定义行为);而且 a+i 中 i=3 已越界(a 只有 3 个元素,合法下标 0~2)。
  • C 错:数组名不能自增(它不是变量)。
  • D 对:a+1 是指向 a[1] 的指针,赋给 int *j 完全合法。✅

易错:B 有解引用,看着像合法表达式。这里野指针与越界两个问题同时存在。


2 · 数组名不可修改

如果定义了以下变量和数组,那么以下可以通过编译的合法表达式或者语句是( )。

int a[3], b[3], i = 3, j;
  • A. j = a == b;
  • B. b = a;
  • C. &(i + j);
  • D. a++;
答案

A

解析
  • A 对:a == b 比较两个地址(结果为 0),把 int 结果赋给 j 合法。虽然逻辑上永远为假(两个不同数组的地址不可能相等),但语法完全合法,本题问的就是"合法"。
  • B 错:数组名不可赋值。
  • C 错:i + j 是一个右值(临时结果),& 只能作用于左值(有确定地址的对象)。
  • D 错:数组名不能自增。

易错:A 看起来"没意义"就排除了。题目问的是"能否通过编译",不是"是否有意义"。
a = b、a++、&(i+j) 这三种写法都不合法。


3 · 数组名与指针

以下说法错误的是( )。

  • A. 字符串比较不能使用比较运算符
  • B. 指针可以指向任意类型的变量
  • C. sizeof 可以获得某个变量或类型占用内存大小
  • D. 数组名即为一个内存地址,但不能修改
答案

B

解析
  • A 对:if (s1 == s2) 比较的是地址而不是内容,字符串比较必须用 strcmp。
  • B 错:指针是有类型的,int * 和 double * 是不同的类型,不能无条件地互相赋值或指向任意对象(需要显式强制转换),否则解引用时类型解释就会出错。
  • C 对:sizeof 作用于变量或类型名都可以。
  • D 对:数组名就是一个地址常量。

易错:B 的说法"指针可以指向任意类型"看似正确,但 C 是强类型的,只有 void * 可以指向任意类型,而且把 void * 赋给别的指针类型仍需要转换。选项里说的"任意"对普通指针不成立。


4 · 数组初始化与自动补 0

以下代码的输出是什么?

int arr[5] = {0};
printf("%d\n", arr[3]);
  • A. 未定义行为
  • B. 0
  • C. 3
  • D. 随机值
答案

B

解析

{0} 只显式初始化了 arr[0],但其余元素全部自动补 0(这是 C 的规则,不是"随机值")。所以 arr[3] = 0。

易错:数组初始化的补 0 规则和局部变量未初始化不同。局部变量(如 int x;)不初始化才是垃圾值;数组只要给了初始化列表,未列出的元素一律补 0。


5 · 数组大小、sizeof 与 VLA

以下关于数组的描述错误的是?

  • A. 数组大小必须是一个常量表达式
  • B. 数组的索引从 0 开始
  • C. 可以用 sizeof 运算符直接获得数组元素个数
  • D. 数组名对应数组首元素地址,该地址不可被修改
答案

A

解析

本题答案是 A:

  • A 错:C99 标准之后允许变长数组(VLA),数组大小可以是运行时才确定的变量(如 int n; scanf("%d",&n); int a[n];)。所以说"必须是常量表达式"是过时/错误的表述。
  • C 对,但它的表述不够严谨:严格说 sizeof(a) 得到的是字节数而不是"元素个数",要得到元素个数得写 sizeof(a)/sizeof(a[0])。很多教材把 sizeof(a)/sizeof(a[0]) 这个惯用法简称为"用 sizeof 获得元素个数",所以本题把 C 算作正确表述、把 A 定为答案。

材料上的一条批注:

  • 选项 A:"C99 标准后,数组大小允许变长表达式"
  • 选项 B、C、D 都对 √

6 · 二维数组的地址表达式

数组元素 a[i][j] 的地址不能用下列哪个式子表达( )。

  • A. &a[i][j]
  • B. a[i]+j
  • C. *(a+i)+j
  • D. a+i+j
答案

D

解析

设 a 为 int a[M][N]。

  • a[i] 的类型是 int[N],退化后是 int *,指向第 i 行首元素 → a[i]+j 就是 &a[i][j] ✓
  • *(a+i) 中 a 是 int(*)[N](行指针),a+i 指向第 i 行,*(a+i) 得到 int[N] 再退化为 int * → *(a+i)+j = &a[i][j] ✓
  • a+i+j ✗:a 是行指针,+i 的步长是一整行(N*sizeof(int) 字节);再 +j 又是按行跳,得到的是 &a[i+j][0],不是 &a[i][j]。

等价关系:

a[i][j]  ≡  *(a[i] + j)  ≡  *(*(a + i) + j)
&a[i][j] ≡  a[i] + j      ≡  *(a + i) + j

易错:a+i+j 与 (a+i)+j 相同,都按行跳,容易误当作 a[i]+j。a 是行指针,a[i] 才是元素指针,步长不同。


7 · 矩阵操作

请写出下列程序的输出。

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

int main()
{
    int b[3][3];
    int c[3][3] = {0};
    int i, j, k;
    int a[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
    for (i = 0; i < 3; i++)
        for (j = 0; j < 3; j++)
        {
            b[j][i] = a[i][j];
        }
    for (i = 0; i < 3; i++)
        for (j = 0; j < 3; j++)
        {
            for ( k = 0; k < 3; k++)
            {
                c[i][j] += a[i][k] * b[k][j];
            }
        }
    for (i = 0; i < 3; i++)
        for (j = 0; j < 3; j++)
        {
            printf("%d ", c[i][j]);
        }
}
答案
14 32 50 32 77 122 50 122 194
解析

b[j][i] = a[i][j] 让 b = aᵀ,于是 c = a × b = a × aᵀ。

此时 c[i][j] = Σₖ a[i][k]·a[j][k],也就是第 i 行与第 j 行的内积:

j=0 j=1 j=2
i=0 1²+2²+3² = 14 1·4+2·5+3·6 = 32 1·7+2·8+3·9 = 50
i=1 32 4²+5²+6² = 77 4·7+5·8+6·9 = 122
i=2 50 122 7²+8²+9² = 194

三个要点:

  1. 转置:b[j][i] = a[i][j] 是行列互换,即 b = aᵀ,问题转化为"矩阵乘自己的转置"。
  2. 结果是对称矩阵(c[i][j] == c[j][i]),下三角可以由上三角得到,只需算 6 个数(对角线 3 个 + 上三角 3 个)。算出的结果若不对称,说明计算有误。
  3. printf("%d ", ...) 每个数后面都有空格且没有换行,所以最后输出是 ... 194(带尾随空格)。

易错:把 c[i][j] += a[i][k] * b[k][j] 里的 b[k][j] 误当 b[i][k]。

矩阵乘法 c[i][j] 由 a 的第 i 行与 b 的第 j 列相乘求和得到,两边共用的中间指标是 k。


8 · 数组元素访问与循环边界

以下程序段中有若干错误,请在不增删语句的情况下指明程序中的第几行有错误,并且写出正确的语句(每个错误 2 分)。

要求本程序的运行结果为 34512。

/* 第 1 行 */ int main() {
/* 第 2 行 */     int a[5] = { 1,2,3,4,5 };
/* 第 3 行 */     int i,index;
/* 第 4 行 */     for (i = 0;i <=5;i++) {
/* 第 5 行 */     index = (3 + i) % 5;
/* 第 6 行 */     printf("%d", (a+ index));
/* 第 7 行 */     }
/* 第 8 行 */     return 0;
/* 第 9 行 */ }
答案

3 处错误:

行 错误 改正 错因
4 for (i = 0;i <=5;i++) for (i = 0;i < 5;i++) 循环多跑一轮,会多输出一位数字(共 6 个字符)。注意:index = (3+i)%5 恒在 0~4,这一步并不越界,错在循环次数
5 index = (3 + i) % 5; index = (2 + i) % 5; 要从 a[2] 开始,才能得到 3 4 5 1 2
6 printf("%d", (a+ index)); printf("%d", *(a+index)); 或 printf("%d", a[index]); a+index 是地址,要打印元素必须解引用
解析
  • 第 4 行是差一错误(多循环一轮 → 多打一个字符 3,输出变成 345123)。看到 <= 就要警觉。

    "循环次数"与"下标越界"是两回事:本题的下标由 % 5 限制,index 恒在 0~4,不会越界,错在循环多跑了一轮。

  • 第 5 行需要从目标输出反推:要输出 3 4 5 1 2,就是依次取 a[2] a[3] a[4] a[0] a[1],所以 index = (2+i) % 5。

    这种"循环移位"用取模 % 实现是 C 里的常见技巧:(起始 + i) % 长度。

  • 第 6 行:a + index 是指针,*(a+index) 才是元素。这是 *(a+i) ≡ a[i] 的直接应用。

这类改错题的一般步骤:

  1. 从输出要求出发反推每一个式子(本题第 5 行只能这样定)。
  2. 有循环必有边界检查:< n 还是 <= n、n 还是 n-1,逐个数一遍。
  3. 分清"地址"和"元素":数组名、a+i、&a[i] 是地址;a[i]、*(a+i) 是元素。

9 · 冒泡排序的边界与升降序(改错)

下面的程序用冒泡排序把数组 a 排成降序,请找出其中的错误并改正。

/*  1  */#include <stdio.h>

/*  2  */int main() {
/*  3  */    int a[6] = {8, 2, 13, 4, 9, 15 };
/*  4  */    int i, j, temp;
/*  5  */    for (i = 0; i < 6 - 1; i++) {
/*  6  */        for (j = 0; j < 6 - i; j++) {
/*  7  */            if (a[j] >a[j + 1]) {
/*  8  */                temp = a[j];
/*  9  */                a[j] = a[j + 1];
/* 10  */                a[j + 1] = temp;
/* 11  */            }
/* 12  */        }
/* 13  */    }
/* 14  */    for (i = 0; i < 6; i++)
/* 15  */        printf("%d  ", a);
/* 16  */}
答案

3 处错误:

行 错误 改正 错因
6 for (j = 0; j < 6 - i; j++) for (j = 0; j < 6 - 1 - i; j++) 循环体里要访问 a[j+1],所以 j 最大只能到 5-1-i。写成 j < 6-i 时 j 会取到 5-i,a[j+1] 越界
7 if (a[j] > a[j + 1]) if (a[j] < a[j + 1]) 题目要求降序,应该是"前面的比后面的小就交换"
15 printf("%d ", a); printf("%d ", a[i]); 要输出的是元素值 a[i],a 是数组名(地址)
解析
  • 第 6 行的越界不容易发现:j < 6-i 在 i=0 时 j 取到 5,然后 a[6] 就越界了。冒泡排序的内层边界标准写法是 n-1-i。

    真机实测:保留 j < 6-i 的版本在 gcc 15.2.0 下会触发 *** stack smashing detected *** 直接 abort(另一次运行虽未触发保护,打印的却是 6 个地址值 414002160)——越界写 a[6] 属于未定义行为,不是"结果碰巧还对"。

  • 第 7 行的"升降序":> 是升序,< 是降序(因为"前者大于后者就交换"会把大的往后送,得到升序)。
  • 第 15 行是"数组名 vs 元素"的混淆。

改正后的输出(降序,每个数后面跟两个空格、末尾无换行):

15  13  9  8  4  2


10 · 数组名退化与行优先存储

执行下列语句后输出的结果是__。

int a[2][3] = { 1,2,3,4,5,6};
int* p;
p= a[1];
printf("%d", p[2]);
  • A. 4
  • B. 5
  • C. 6
  • D. 超出数组
答案

C(6)

解析

a[1] 是二维数组第二行的一维数组名,作为初值退化为 int *,指向 a[1][0],所以 p[2] 就是 a[1][2]。

二维数组按行优先存储、且初始化列表按行填满,故 a[1] 这一行是 {4,5,6},a[1][2] = 6。

考点:数组名退化(a[i] 类型是 int[3] → 变成 int *)与行优先,注意 p 只需要管列下标,不必再写行。


11 · 二维数组定义只能省略第一维

下面不能正确定义二维数组的选项是__。

  • A. int a[2][]={{1,2},{3,4}};
  • B. int a[][2]={1,2,3,4,5};
  • C. int a[2][2]={{1},{2}};
  • D. int a[2][2]={1,2,3};
答案

A

解析

C 语言只允许省略第一维,第二维必须给出——编译器要靠第二维算出一行的长度和总行数,所以 a[2][] 直接非法。

B 由 {1,2,3,4,5} 推出 int a[3][2](最后一行只给一个数,另一个补 0);

C 每行只给一个初值,a[0]={1,0}、a[1]={2,0};D 只给 3 个数,第 4 个补 0,B、C、D 都合法。

声明和初始化二维数组时,省略第一维可以,省略第二维不行。


12 · 字符(ASCII 码)作数组下标

若有以下语句,各选项中数值为 4 的表达式是__。

int a[12]={1,2,3,4,5,6,7,8,9,10,11,12};
char c = 'a', d, g;
  • A. a[g-c]
  • B. a[4]
  • C. a['d'-'c']
  • D. a['d'-c]
答案

D

解析

字符参与算术运算时按其 ASCII 码当整数用:'a'=97、'd'=100,所以 'd'-c = 100-97 = 3,a[3] 是第 4 个元素 = 4 ✅。

C 的 'd'-'c' = 1 → a[1] = 2;B 的 a[4] = 5;A 里的 g 声明后从未初始化,值不确定(用未初始化变量是未定义行为)。

考点:下标可以是任意整型表达式,字符常量/变量都算整型。


13 · 数组与结构体的整体操作

下面叙述中,错误的是__。

  • A. 对于实型数组,不可以在 scanf 与 printf 函数中直接用数组名对数组进行整体的输入或输出。
  • B. 对于字符型数组,可以在 scanf 与 printf 函数中用 %s 对数组的数组名进行整体的输入或输出。
  • C. 对于字符型数组,可以用来存放字符串。
  • D. 对于含有数组的结构体变量,可以在赋值语句中运用"="进行整体的赋值。
答案

四条在标准 C 下都成立,材料里这道题没有一个确定的答案 —— 可能是原卷就这么出的,也可能是扫描时某个选项被改了字。

解析

A 对:数组名只传了首地址、没有长度信息,整型/实型数组无法整体 I/O;

B、C 对:%s 配字符数组名可以整体读写字符串(scanf 会自动补 '\0'),char s[]="abc"; 正是"字符数组存字符串"。

D 也对:结构体变量可以整体赋值,= 会把其中的数组成员一并按字节拷贝——不能整体赋值的只有数组本身。

为什么这道题没有唯一答案

这四条在标准 C 下都成立,材料里这道题没有一个确定的答案 —— 可能是原卷就这么出的,也可能是扫描时某个选项被改了字。这四条规则本身没有争议。


14 · 行指针的元素访问

若有以下说明和语句,int c[4][5],(*p)[5]; p=c; 则正确访问 c 数组元素的表达式是__。

  • A. p+1
  • B. *(p+3)
  • C. *(p+1)+3
  • D. *(p[0]+2)
答案

D

解析

p 是行指针 int(*)[5],p[0] 等价于 *(p+0),取出第 0 行 c[0];

c[0] 退化为 int *,再加 2 解引用,*(p[0]+2) 就是元素 c[0][2] ✅。

A 的 p+1、C 的 *(p+1)+3 都还是地址(分别指向第 1 行、第 1 行第 3 列);B 的 *(p+3) 解引用行指针得到的是"第 3 行"这个一维数组名,本质仍是地址。

判断一个表达式是地址还是元素值:外层再解引用一次才是元素值。


15 · 元素地址的写法

表示数组 int a[2][3] 的第 i 行第 j 列元素地址的正确语句是__。

  • A. *(a[i]+j);
  • B. (a+i);
  • C. *(a+j);
  • D. a[i]+j;
答案

D

解析

由等价关系 &a[i][j] ≡ a[i]+j ≡ *(a+i)+j,D 正确。

A 的 *(a[i]+j) 已经解引用成了元素值(就是 a[i][j]);

B 的 (a+i) 是行指针,步长是一整行(3*sizeof(int) 字节),指向第 i 行而不是该元素;C 既把行下标写成了 j,解引用后得到的也不是地址。

问"地址"时不需解引用,问"元素"时才需要解引用。


16 · 数组初始化补 0 与指针遍历

请写出以下程序运行所产生的输出。

#include <stdio.h>

int main() {
    int i, *ptr;
    int array[5] = {5,3,1};
    for (ptr=array, i=0; i<5; i++, ptr++)
    {
        if (*ptr == 0)
            putchar('X');
        else
            putchar('A' + *ptr);
    }
    return 0;
}
答案
FDBXX
解析

array[5] = {5,3,1} 只给了 3 个初值,后面两个元素自动补 0,所以数组实际是 {5,3,1,0,0}——这是本题的第一个考点,漏掉补 0 就写不出后面的 XX。

循环让 ptr 从 array 开始逐个后移(i++ 与 ptr++ 同步),依次读 5 个元素。

非 0 时走 putchar('A' + *ptr),即以 'A'(65) 为基准做字符编码偏移:5 → 'A'+5 = 'F'(70)、3 → 'D'(68)、1 → 'B'(66)。

两个 0 走 if 分支各输出一个 'X',拼起来就是 FDBXX。


17 · 二维数组作函数参数 · 按列求最值

请写出以下程序运行所产生的输出。

#include <stdio.h>
#define  N  4

void fun( int a[][N],  int b[],  int flag )
{  int  i, j;
   for( i=0; i<N; i++ )
   {  b[i] = a[0][i];
      for( j=1; j<N; j++ )
         if ( flag ? (b[i] > a[j][i]) : (b[i] < a[j][i]) )
            b[i] = a[j][i];
   }
}

int main()
{  int  x[N][N]={1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16}, y[N], i;
   fun(x, y, 1);
   for (i=0;i<N; i++)   printf("%d,", y[i]);
   fun(x, y, 0);
   for (i=0;i<N; i++)   printf("%d,", y[i]);
   return 0;
}
答案
1,2,3,4,13,14,15,16,
解析

第一步:确定数据。 形参 int a[][N] 省略第一维、必须写出第二维;x 按行优先填满,所以 x[i][j] = i*4 + j + 1,即

x[i][j] j=0 j=1 j=2 j=3
i=0 1 2 3 4
i=1 5 6 7 8
i=2 9 10 11 12
i=3 13 14 15 16

第二步:fun 的功能。 外层 i 走的是列(注意内层比较用的都是 a[j][i],i 固定、j 变行)——所以 fun 是一列一列处理的。

先取 b[i] = a[0][i] 作为当前值,再让 j 从 1 到 3 依次比较:flag=1 时条件是 b[i] > a[j][i],比当前值小就更新,最后得到该列最小值;

flag=0 时条件反向,留下的是该列最大值。

第三步:逐列计算。 比较的是同一列的 4 个数(上下方向),不是同一行的数:

列 i 该列的 4 个数(a[0][i] → a[3][i]) fun(x,y,1):取最小 fun(x,y,0):取最大
0 1, 5, 9, 13 1 13
1 2, 6, 10, 14 2 14
2 3, 7, 11, 15 3 15
3 4, 8, 12, 16 4 16

第四步:输出。 第一次 fun(x,y,1) 后 y = {1,2,3,4},打印 1,2,3,4,;

第二次 fun(x,y,0) 把 y 整个覆盖成 {13,14,15,16},接着打印 13,14,15,16,。

printf("%d,") 是每个数后面跟一个逗号、没有换行,两次输出的数字首尾相接,所以最终是 1,2,3,4,13,14,15,16,(末尾也留着逗号)。

易错:把 a[j][i] 看成 a[i][j],于是按"行"去算——那会得到 1,1,1,1,16,16,16,16, 之类的错答案。只要下标写成 [j][i],就一定是按列处理。

易错:y 是同一块内存,第二次调用是覆盖而不是追加,两次输出不是两个独立数组的结果拼接。


18 · sizeof 与数组的"长度"

static char x[] = "12345";
char y[] = {'1','2','3','4','5','\0'};

那么( )。

  • A. x 数组的长度等于 y 数组的长度
  • B. x 数组的长度大于 y 数组的长度
  • C. x 数组的长度少于 y 数组的长度
  • D. x 数组与 y 数组的存储区域相同
答案

A

解析

这里的"长度"指数组占用的内存字节数,也就是 sizeof:

sizeof(x) == 6     /* '1'~'5' 五个字符 + 字面量隐含的那个 '\0' */
sizeof(y) == 6     /* 初值表里明写了 6 个字符,含自己写上的 '\0' */

两者都是 6 → A ✅。D 错:x、y 是两个互相独立的数组对象,各占各的一块存储空间,地址不可能相同(题目问的是"长度",D 偷换成了"存储区域")。

这道题涉及 sizeof 与 strlen 的区别:

sizeof(x) strlen(x) sizeof(y) strlen(y)
结果 6 5 6 5
数的是什么 数组占的字节数('\0' 算一格) 开头到第一个 '\0' 的字符个数('\0' 不算) 同左 同左
何时算 编译期运算符,看的是类型/对象有多大 运行时函数,看的是内容 同左 同左

要点:char x[] = "12345" 编译器会自动补一个 '\0',所以 x 是 6 字节而不是 5 字节;

而 y 初值表里的那个 '\0' 是人自己写上去的——两种写法完全等价。

若把 y 写成 {'1','2','3','4','5'}(不写 '\0'),y 就只有 5 字节,答案就变成 C 了。

填空时注意两点:字符串字面量自带 '\0',初值表不会自动补 '\0'。

补充:本题按 strlen 理解仍是 A(strlen(x) 与 strlen(y) 都是 5),两种理解下 A 都成立。

真机验证(gcc 15.2.0):sizeof(x)=6 sizeof(y)=6 strlen(x)=5 strlen(y)=5。


19 · 二维数组的反对角线访问

下面语句的输出结果是( )。

int i, 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. 147
  • B. 159
  • C. 357
  • D. 369
答案

C(即 357)

解析

初始化列表按行优先填满 3×3 数组:

x[i][j] j=0 j=1 j=2
i=0 1 2 3
i=1 4 5 6
i=2 7 8 9

循环取的是 x[i][2-i],两个下标一个递增、一个递减,走过的是:

i 2-i 元素 值
0 2 x[0][2] 3
1 1 x[1][1] 5
2 0 x[2][0] 7

即从右上到左下的反对角线(副对角线),结果 357 ✅。

printf 里没有空格也没有换行,三个数字连成一片,输出是 357,不是 3 5 7 或 3,5,7。

两条对角线一起记(n 阶方阵):

  • 主对角线:x[i][i] → 本题会得到 1 5 9 = 159,正是干扰项 B;
  • 副对角线:x[i][n-1-i] → 本题 n=3,就是 x[i][2-i] = 357。

易错:2-i 里的 2 是"最大下标"(n-1),不是长度 n——写成 x[i][3-i] 第一轮就 x[0][3] 越界。

真机验证(gcc 15.2.0):输出 357。


20 · 矩阵原地转置 · 逗号表达式

以下程序对一个 3×3 的矩阵进行行列互换(原地转置),请填空。

#include <stdio.h>

int main(int argc, char* argv[])
{
    int i, j, temp, a[3][3] = { 1,2,3,4,5,6,7,8,9 };
    for (i = 0; i < 3; i++)
        for (j = 0; j < i; j++)
            temp = a[i][j], (17) , (18) ;
    printf("\n the result array is:\n");
    for (i = 0; i < 3; i++)
    {   printf("\n");
        for (j = 0; j < 3; j++)
            printf("%5d", a[i][j]);
    }
    return 0;
}
答案
序号 答案
(17) a[i][j] = a[j][i]
(18) a[j][i] = temp
解析

先把逗号表达式还原成三条动作。

temp = a[i][j], (17), (18); 是一条语句,逗号运算符优先级最低、结合方向从左到右,所以三个子表达式依次求值——这里要的不是整个表达式的值,而是这三步的副作用:

temp    = a[i][j];   /* ① 把下三角的元素暂存起来 */
a[i][j] = a[j][i];   /* ② (17) 上三角的元素搬到下三角 */
a[j][i] = temp;      /* ③ (18) 暂存的原值回填到上三角 */

这就是一次交换,temp 用作临时变量。

为什么 (18) 必须是 temp? 走到第 ③ 步时,a[i][j] 已经被 ② 覆盖掉了,原值只留在 temp 里。

第 (18) 空若填成 a[i][j],这条语句没有任何副作用——下三角被换成了上三角的值,上三角不变,得到的矩阵不是转置结果:

真机实测:填成 a[i][j] 时结果是 123 / 256 / 369(不是转置);填 a[j][i] = temp 才是 147 / 258 / 369。

交换由三步组成:先暂存、再写入、后回填。

为什么内层必须是 j < i?

  • j < i 即"列下标小于行下标",j 只取 0 ~ i-1——扫的是主对角线以下的那一半(下三角)。
  • 转置要交换的永远是 (i,j) 与 (j,i) 这一对。外层 i 从小到大扫,内层只取 j<i,于是每一对恰好被处理一次 ✅。
  • 若把 j < i 改成 j < 3(整个矩阵都扫),(i,j) 和 (j,i) 这一对会被交换两次——等于没换。第一次把上三角换到下面,第二次又把它换回来,最终矩阵原封不动。这是填空题必须写 j < i 的原因。

    真机实测:把内层改成 j < 3、填入同样的 (17)(18),输出仍是 123456789(没转置);而 j < i 的版本得到正确的转置矩阵。

改完之后的运行结果:

 the result array is:

    1    4    7
    2    5    8
    3    6    9

printf("%5d", ...) 是每个数占 5 格、右对齐;printf("\n") 写在每行数字之前,所以第一行数字上方还有一个空行。

本题要点:上三角与下三角成对交换,只扫一半(j<i),中间用 temp 暂存。 交换类填空(逆序、排序、转置)中连续两空通常对应一次交换的两个动作。

真机验证(gcc 15.2.0):填 a[i][j] = a[j][i] 与 a[j][i] = temp 后输出 1 4 7 / 2 5 8 / 3 6 9。