📖 第 8 章:数组(Arrays)

🔤 Kenneth Reek《Pointers on C》(C和指针)· Chapter 8
🗨️ 本章金句:「数组名和指针在多数场合可以互换——但它们是两种东西:一个是指针常量般的地址,一个是货真价实的指针变量。」
这一章在干嘛? 一维数组的本质(下标就是指针算术的语法糖)、数组作参数的退化、多维数组的存储与指针,以及指针数组。吃透本章,第 6 章的指针运算才算真正落地。
8.1 一维数组:下标即指针算术8.2 数组与指针:名同实异的边界8.3 多维数组:连续存储与数组指针8.4 指针数组:字符串表的标配

8.1 一维数组:下标即指针算术

int a[5] = {10, 20, 30, 40, 50};

a[2]        /* 语法糖,等价于 *(a + 2) */
*(a + 2)    /* 30:数组名当首元素地址用 */

数组名在表达式中退化为「指向首元素的指针常量」(不能 a++,因为它是地址不是变量),只有两处例外:sizeof(a) 得到整个数组的大小,&a 得到「指向整个数组」的指针(值同、类型不同)。

/* 下标与指针两种遍历:语义相同 */
for (i = 0; i < 5; i++)  sum += a[i];
for (p = a; p < a + 5; p++) sum += *p;   /* p++ 按元素步进 */

初始化规则:花括号列表;不给长度则自动按元素个数定;不完整初始化则剩余补 0;char msg[] = "hi" 自动含结尾 '\0'(3 字节),而 char msg[2] = "hi" 就装不下了——别省。

8.2 数组与指针:名同实异的边界

声明本质sizeof能否被赋值
int a[10]分配 40 字节的数组实体40(整个数组)不能(a 是地址常量般的标识符)
int *p分配一个指针变量8(指针本身)能(p++ 合法)

两者在「表达式里」可互换(都退化为首元素地址),在「声明处」是天壤之别。函数参数处数组名一律退化为指针,所以函数内改 a[0] 影响原数组(第 7 章的 bump3)。

边界检查是调用者的责任: C 不做 a[10](对 int a[10] 而言)越界检查,越界读写是未定义行为——可能踩坏相邻变量、改坏栈帧返回地址。循环边界 i < 10 而非 i <= 10,从第一天就养成。

8.3 多维数组:连续存储与数组指针

int m[3][4] 在内存里是一条线:按行主序铺开 12 个 int。m[1][2] 的地址 = 首地址 + (1×4 + 2)×sizeof(int):

int m[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};

int (*p)[4] = m;        /* 数组指针:指向「int[4]」,p+1 跳一整行 */
printf("%d\n", p[1][2]);        /* 7 */
printf("%d\n", (*(m + 1))[2]);  /* 同上:先下移一行,再取第 2 列 */

传参时行数可省、列数必须写明——编译器要靠列数算「跳一行」的步长:

void scan(int mat[][4], int rows);   /* 等价于 int (*mat)[4], int rows */
int *p[4] vs int (*p)[4]: 前者是「4 个 int* 的数组」(方括号优先结合),后者是「指向 int[4] 的指针」(括号强令 * 先结合)。读声明先看括号,再看左右。

8.4 指针数组:字符串表的标配

const char *names[] = { "STM32", "ESP32", "GD32" };

for (int i = 0; i < 3; i++)
    printf("%s\n", names[i]);   /* names[i] 是 char*,指向第 i 个串 */

names 是 3 个指针(各自指向字符串常量),字符串本身散落在静态区——总开销 = 3×指针 + 各串长度,比二维字符数组 char names[3][8](按最长串对齐、整块连续)更省空间、但串不可修改。选择标准:只读字符串表用指针数组;要修改内容用二维数组。

本章通关标准: 能秒答 a[i] 与 *(a+i) 的等价性;看到函数参数 int a[][4] 知道列数不能省;分得清指针数组和数组指针。
🧠 小测验
1. 数组名和指针变量的两点本质区别?
① sizeof:数组名给出整个数组大小,指针只给指针自身大小;② 数组名不是变量、不能被赋值(a++ 非法),指针可以随意改指。表达式用法上数组名退化为首元素地址,与指针互换。
2. 二维数组传参为什么列数必须写明?
数组退化为「指向一行的指针」,p+1 的步长 = 列数×元素大小。编译器需要列数计算跳行步长和 a[i][j] 的地址,行数则可由额外参数传入。
3. 指针数组 char *tab[] 和二维数组 char tab[][8] 各适合什么场景?
指针数组:各串独立存放、按实际长度占内存,适合只读字符串表;二维数组:整块连续、每行定长,可修改内容,但按最长串对齐可能浪费空间。
← 上一篇🏠 顶层目录下一篇 →