这一章在干嘛?
三板斧进阶:指向指针的指针与复杂声明解读法、函数指针(回调与转移表)、命令行参数,以及字符串常量的真实身份。
13.1 指向指针的指针与复杂声明
第 6 章见过 int **pp(链表章节它是主角)。这一节补一张「高级声明」速查表——读法都是右左法则:从标识符出发,右边优先([] 和 ()),再向左(*):
int *p; /* p 是指针 → int */
int *p[10]; /* p 先结合 [10]:数组,元素 int* —— 指针数组 */
int (*p)[10]; /* 括号先结合 *:指针 → int[10] —— 数组指针 */
int *f(int); /* f 是函数(int) → int* */
int (*f)(int); /* (*f) 是函数 → f 是指针 → 「int(int)」 —— 函数指针 */
int *(*f)(int); /* f 是函数指针 → 返回 int* */
int (*f[3])(int); /* f 是数组,元素是函数指针 —— 转移表的原料 */
存在:
函数参数要传「真二维数组」,形参必须知道每行宽度(第 8 章)。数组指针 p+1 跳一整行,正是它的用处。
13.2 函数指针:回调与转移表
函数也有地址;函数指针 = 存函数地址的变量,把「行为」变成可以传递、替换、批量调度的数据:
/* 回调:把比较规则交给调用者 */
void sort(void *base, size_t n, size_t size,
int (*cmp)(const void *, const void *));
static int by_int(const void *a, const void *b)
{
int x = *(const int *)a, y = *(const int *)b;
return (x > y) - (x < y); /* 返回负/零/正,安全不溢出 */
}
sort(arr, n, sizeof arr[0], by_int); /* 传函数名 = 传地址 */
转移表(switch 的表驱动版):同一签名的函数打包成数组,按下标调用:
double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double (*ops[])(double, double) = { add, sub, mul };
/* 转移表:op 为 0/1/2,免去 switch 三连 */
double r = ops[op](x, y);
两条安全线:
① 调用前判空(if (ops[op]) ops[op](x, y)),下标越界调用函数指针是灾难;② 原型必须严格一致——参数/返回类型不匹配的调用是未定义行为。
13.3 命令行参数与字符串常量
int main(int argc, char **argv) /* 也可写作 char *argv[] */
{
for (int i = 0; i < argc; i++)
printf("argv[%d] = %s\n", i, argv[i]);
return 0;
}
/* ./prog -v input.txt → argc=3, argv[0]="./prog",
argv[1]="-v", argv[2]="input.txt" */
argv 是「指针数组」:argc 个字符串指针,argv[argc] 恒为 NULL 作哨兵。解析时 strtol/atof 把字符串参数转数值,别对 argv[1] 想当然非空——先检查 argc。
字符串常量的真身:表达式中的 "hello" 是「指向静态区 char[6] 的指针」,所以下面的把戏合法但危险:
printf("%c\n", *"hello"); /* * 起作用:h(对字面量解引用) */
printf("%s\n", "hello" + 1); /* ello:指针偏移 */
char *s = "hi";
s = "bye"; /* 合法:改的是指针指向 */
/* s[0] = 'H'; 未定义!字面量通常在只读段 */
本章通关标准:
能按右左法则读出 int (*f[3])(int);知道回调就是把函数指针当参数传;写 main(argc, argv) 的程序会先查 argc 再用 argv[1]。
和 int *p[10] 怎么区分?
看括号:括号先结合的是 *,所以 int (*p)[10] 是指针,指向含 10 个 int 的数组(+1 跳一行);无括号的 int p[10] 里 [] 优先,p 先成数组,元素是 int。口诀「有括号是指针,没括号是数组」。
2. 回调函数是什么?为什么 sort 能排任意类型?
回调 = 调用方把函数指针传给库函数,库在合适时机「回过头来」调用它。sort 只掌握通用的交换/移动逻辑,元素如何比大小交给 cmp 函数指针——行为参数化,任意类型都能排。
3. 表达式 "hello" 的类型是什么?能对它写入吗?
类型是 char*(指向静态区 char[6],含结尾 \0),对它解引用/偏移合法(*“hello” 是 ‘h’)。但字面量通常位于只读存储段,写入是未定义行为;需要可修改的字符串应拷贝到 char 数组。