知识点 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 · 数组名不可修改¶
如果定义了以下变量和数组,那么以下可以通过编译的合法表达式或者语句是( )。
- 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¶
以下代码的输出是什么?
- 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
解析
设 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是行指针,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]);
}
}
解析
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 |
三个要点:
- 认出转置:
b[j][i] = a[i][j]就是"行列互换",一眼看出b = aᵀ,题目立刻变成"矩阵乘自己的转置"。 - 结果是对称矩阵(
c[i][j] == c[j][i])。这可以作为自查手段——如果算出来不对称,一定是哪里错了,而且只需算 6 个数(对角线 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]的直接应用。
改错题通用套路(本题是范例):
- 先看输出要求,拿它当"标准答案"反推每一个式子(本题第 5 行只能这样定)。
- 有循环必有边界检查:
< n还是<= n、n还是n-1,逐个数一遍。- 分清"地址"和"元素":数组名、
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 元素"的经典混淆。
改正后的输出(降序,每个数后面跟两个空格、末尾无换行):
10 · 数组名退化与行优先存储¶
执行下列语句后输出的结果是__。
- 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 的表达式是__。
- 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;
}
解析
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;
}
解析
第一步:认清数据。 形参 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 与数组的"长度"¶
那么( )。
- A.
x数组的长度等于y数组的长度 - B.
x数组的长度大于y数组的长度 - C.
x数组的长度少于y数组的长度 - D.
x数组与y数组的存储区域相同
答案
A
解析
这里的"长度"指数组占用的内存字节数,也就是 sizeof:
两者都是 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——按哪种理解判卷,A 都安全。真机验证(gcc 15.2.0):
sizeof(x)=6 sizeof(y)=6 strlen(x)=5 strlen(y)=5。
19 · 二维数组的反对角线访问¶
下面语句的输出结果是( )。
- 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的版本得到正确的转置矩阵。
改完之后的运行结果:
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。