第 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 是数组,元素是函数指针 —— 转移表的原料 */
函数也有地址;函数指针 = 存函数地址的变量,把「行为」变成可以传递、替换、批量调度的数据:
/* 回调:把比较规则交给调用者 */
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);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'; 未定义!字面量通常在只读段 */