C 指针与数组
更新时间:2026-08-26。本文是
languages/c/主题入门层第 34 篇。数组与指针的关系讲了"数组名衰减成指针",这一篇把它用起来:怎么用指针遍历数组、指针数组长什么样、char *argv[]到底怎么读。
本文要回答的问题
- 怎么用指针遍历数组?和下标遍历比哪个好?
- 指针数组(
int *arr[5])和数组指针(int (*arr)[5])怎么区分? main的char *argv[]是什么结构?
一、用指针遍历数组
c
#include <stdio.h>
int main(void) {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // p 指向首元素
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // 等价 arr[i]
}
printf("\n");
// 更常见的推进式写法:
for (int *q = arr; q < arr + 5; q++) {
printf("%d ", *q); // q 每次前进一个元素
}
printf("\n");
return 0;
}两种遍历等价,推荐哪种?
| 写法 | 可读性 | 编译器生成代码 |
|---|---|---|
arr[i] | 好,意图明确 | 与指针等价 |
*(p + i) | 略绕 | 相同(现代编译器) |
for (q = arr; q < end; q++) | 推进式,适合"扫一遍" | 相同 |
现代编译器下两者生成的机器码一样(-O1 起),选可读性好的 arr[i]。别迷信"指针比下标快"——那是 30 年前的事了。
二、指针数组 vs 数组指针:声明怎么读
c
int *a[5]; // 指针数组:a 是数组,5 个元素都是 int*
int (*b)[5]; // 数组指针:b 是指针,指向"含 5 个 int 的数组"读声明的口诀:从变量名开始,向右找,被括号挡了就向左:
| 声明 | 怎么读 | 是什么 |
|---|---|---|
int *a[5] | a 是数组(5 个),元素是 int* | 指针数组:5 个指针 |
int (*b)[5] | b 是指针,指向数组(5 个 int) | 数组指针:指向数组的指针 |
日常用到最多的是指针数组——char *argv[] 就是"字符串指针的数组":
c
char *names[3] = {"alice", "bob", "carol"};
// names[0] 是 "alice" 字符串的首地址
for (int i = 0; i < 3; i++) {
printf("%s\n", names[i]);
}每个元素是 char*,指向各自的字符串(它们长度不同也 OK,因为不占固定空间——对比二维字符数组的固定行宽)。
三、char *argv[] 的结构
c
int main(int argc, char *argv[])argv 是"指针的数组":argv[0]、argv[1]……每个都是 char*,指向一个命令行参数字符串,最后 argv[argc] 是 NULL:
text
argv → [ argv[0] ] → "./prog"
[ argv[1] ] → "hello"
[ argv[2] ] → "42"
[ argv[3] ] → NULL(哨兵)所以遍历参数有两种写法:
c
for (int i = 0; i < argc; i++) printf("%s\n", argv[i]);
// 或用 NULL 哨兵:
for (char **p = argv; *p != NULL; p++) printf("%s\n", *p);第二种里 char **p 是"指向 char* 的指针"——指针的指针,遍历指针数组的标准姿势。
四、数组参数:本质是指针
c
void f(int arr[], int n) // 等价于 void f(int *arr, int n)编译器把数组参数一律当指针处理,所以:
sizeof(arr)是 8,不是数组大小;- 函数里改
arr[i]影响外部数组; - 必须传长度。
c
void fill(int arr[], int n) { // 编译器眼里 arr 就是 int*
for (int i = 0; i < n; i++) arr[i] = i;
}五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 指针数组/数组指针搞混 | 编译错误或类型错 | 从变量名向右读声明 |
| 数组参数里 sizeof | 拿到 8 | 另传长度 |
指针越界(p > arr + n) | 越界访问 | 边界用 < arr + n |
忘 *p 直接 p++ 改内容 | 改的是地址 | 理清推进 vs 解引用 |
| 迷信指针比下标快 | 无意义优化 | 写可读的 arr[i] |
六、与本站主线衔接
一句话总结
用指针遍历数组和下标等价(编译器生成相同代码,别迷信性能差);int *a[5] 是"5 个指针"的数组、int (*b)[5] 是"指向数组"的指针;char *argv[] 是字符串指针数组,NULL 哨兵收尾——声明从变量名向右读,指针数组就不难。