跳转至

知识点 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+ii=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 = ba++&(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

易错:和"局部变量未初始化"搞混。局部变量(如 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

解析

aint a[M][N]

  • a[i] 的类型是 int[N],退化后是 int *,指向第 i 行首元素 → a[i]+j 就是 &a[i][j]
  • *(a+i)aint(*)[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]

矩阵乘法的下标规律是"左行右列、中间指标 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 还是 <= nn 还是 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-ij 会取到 5-ia[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-ii=0j 取到 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

循环用 ptrarray 起步逐个后移(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 错:xy 是两个互相独立的数组对象,各占各的一块存储空间,地址不可能相同(题目问的是"长度",D 偷换成了"存储区域")。

这道题真正考的是 sizeofstrlen

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——按哪种理解判卷,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 73,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