📖 第 13 章:高级指针话题(Advanced Pointer Topics)

🔤 Kenneth Reek《Pointers on C》(C和指针)· Chapter 13
🗨️ 本章金句:「读不懂的声明拆开读;函数指针是「把行为当参数传」的钥匙。」
这一章在干嘛? 三板斧进阶:指向指针的指针与复杂声明解读法、函数指针(回调与转移表)、命令行参数,以及字符串常量的真实身份。
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 是数组,元素是函数指针 —— 转移表的原料 */
为什么 int (*p)[10] 存在: 函数参数要传「真二维数组」,形参必须知道每行宽度(第 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]。
🧠 小测验
1. int (*p)[10] 和 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 数组。
← 上一篇🏠 顶层目录下一篇 →