这一章在干嘛?

三板斧进阶:指向指针的指针与复杂声明解读法、函数指针(回调与转移表)、命令行参数,以及字符串常量的真实身份。

13.1 指向指针的指针与复杂声明

13.2 函数指针:回调与转移表

13.3 命令行参数与字符串常量

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。口诀「有括号是指针,没括号是数组」。