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" 就装不下了——别省。
| 声明 | 本质 | sizeof | 能否被赋值 |
|---|---|---|---|
| int a[10] | 分配 40 字节的数组实体 | 40(整个数组) | 不能(a 是地址常量般的标识符) |
| int *p | 分配一个指针变量 | 8(指针本身) | 能(p++ 合法) |
两者在「表达式里」可互换(都退化为首元素地址),在「声明处」是天壤之别。函数参数处数组名一律退化为指针,所以函数内改 a[0] 影响原数组(第 7 章的 bump3)。
i < 10 而非 i <= 10,从第一天就养成。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 */
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](按最长串对齐、整块连续)更省空间、但串不可修改。选择标准:只读字符串表用指针数组;要修改内容用二维数组。