进一步探讨指向指针的指针

int     i;
int     *pi;
int     **ppi;

printf("%d\n", ppi);    // 1
printf("%d\n", &ppi);   // 2
*ppi = 5;               // 3
  1. 如果 ppi 是个自动变量, 它就未被初始化, 这条语句将打印一个随机值. 如果它是个静态变量, 这条语句将打印 0.
  2. 这条语句将把存储 ppi 的地址作为10进制数打印出来. 这个值并不是很有用
  3. 这条与语句的结果是不可预测的. 对应 ppi 不应该指针间接访问操作, 因为它尚未被初始化.

如果执行了下面这条语句

ppi = π

以后我们就可以安全地对 ppi 进行间接操作了.

*ppi = &i;

下载下面各语句具有相同的效果

i = 'a';
*pi = 'a';
**ppi = 'a';

为了要使用更为复杂的涉及间接访问的方法? 这是因为简单复制并不总是可行, 列入 链表的插入. 在那些函数中, 我们无法使用简单的赋值, 因为变量名在函数作用域内部 是未知的. 函数所拥有的只是一个指向需要修改的内存未知的指针, 所以要对该指针 进行间接访问操作以访问需要修改的变量.

间接访问层次越多, 你需要用到它的次数就越少.

高级声明

函数指针

看看下面例子

int *f();

首先执行的是函数调用操作符 (), 因为它的额优先级高于间接访问操作符. 一次, f 是一个函数, 它的返回值类型是一个指向整数的指针.

考虑下面例子:

int (*f)();
这个声明有两对括号, 每对的含义各不相同. 第2对括号是函数调用操作符, 但第1对括号只起到聚组的作用. 它迫使间接访问在函数 调用之前进行, 使 f 称为一个函数指针, 它所指向的函数返回一个整型值.

程序中每个函数都位于内存的某个位置, 所以存在指向那个位置的指针.

下面这个就比较容易懂了

int *(*f)();
f 是一个函数指针, 它指向的函数返回值是一个整型指针.

考虑数组

int *f[];

两个操作符, 下标优先级更高, 所以 f 是一个数组, 它的元素类型是指向整型的指 针.

考虑下面, 下面例子隐藏了一个圈套.

int f()[];
这个声明是非法的, 函数只能返回标量, 不能返回数组

考虑下面例子

int f[]();
这个声明也是非法的, 因为 数组元素必须具有相同的长度, 但不同的函数显然可能不具有相同长度

但是下面这个声明是合法的

int (*f[])();
括号内的表达式 *f[] 首先进行求值, 所以 f 是一个元素为某种类型的指针的数组. 表达式末尾的 () 是函数调用操作符, 所以 f 可能是一个数组, 数组元素的类型是 函数指针, 它所指向的函数返回值是一个整型值.

搞明白上面的声明, 下面的就比较好理解

int *(f*[])();
这个声明创建了一个指针数组, 指针元素所指向的类型返回值为整型指针的函数.

函数指针

函数指针最常见的两个用途是 转换表(jump table)和作为参数传递给另一个函数.

简单声明一个函数并不能马上使用, 和其他指针一样, 对函数指针执行间接引用之前必须把它初始化为指向某个函数

int f(int);
int (*pf)(int) = &f;
初始化表达式中的 & 操作符是可选的, 因为 函数名被使用时总是由编译器把它转换为函数指针

命令行参数

int
main(int argc, char **argv)

argc 表示命令行的参数数目, argv 指向第一组参数值.

这个数据每个元素都是一个字符指针, 数组的末尾是一个 NULL 指针.

第一个参数就是程序的名称.

处理命令行参数

**argv == '-'

双重间接访问操作参数的第一个字符.

字符串常量

"xyz" + 1

字符串常量实际上是个指针, 它的意义是指向字符串中的第2个字符.

*"xyz"

这个表达式指向字符串的第一个字符.

"xyz"[2]

它指向的值是字符 z

*("xyz" + 4) 是错误的, 偏移量 4 超出了这个字符串的范围.