1,和指针有关的声明
简单的就不说了,你能理解下面的例子吗?
- int **p1; // p1 is a pointer to a pointer to an int.
- int *&p2; // p2 is a reference to a pointer to an int.
- int &*p3; // ERROR: Pointer to a reference is illegal.
- int &&p4; // ERROR: Reference to a reference is illegal.
此外,还有函数指针、指向数组的指针、或者指针的数组......是不是觉得头大?其实里面是有规律的:简单点,C语言遵循最简单的理解原则:就近。(一个复杂的教程会给你列举几十种情况,扔掉它!你难道认为发明C语言的专家是一个怪胎么?)更明确的说法:“右左原则”,从变量名开始,先向右看,然后向左看;当你碰到一个括号时,调转越读方向。括号内的所有内容都分析完毕,就跳出括号的范围;直到整个声明都分析完毕。
下面是个变态的例子:
- int * (* (*fp1) (int) ) [10];
- 阅读步骤:
- 1. 从变量名开始 -------------------------------------------- fp1
- 2. 往右看,什么也没有,碰到了),因此往左看,碰到一个* ------ 一个指针
- 3. 跳出括号,碰到了(int) ----------------------------------- 一个带一个int参数的函数
- 4. 向左看,发现一个* --------------------------------------- (函数)返回一个指针
- 5. 跳出括号,向右看,碰到[10] ------------------------------ 一个10元素的数组
- 6. 向左看,发现一个* --------------------------------------- 指针
- 7. 向左看,发现int ----------------------------------------- int类型
- 总结:fp1被声明成为一个函数的指针,该函数返回指向指针数组的指针.
明白了吗?尝试着做做下面的练习:
- int *( *( *arr[5])())();
- 阅读步骤:
- 1. 从变量名开始 -------------------------------------------- arr
- 2. 往右看,发现是一个数组 ---------------------------------- 一个5元素的数组
- 3. 向左看,发现一个* --------------------------------------- 指针
- 4. 跳出括号,向右看,发现() -------------------------------- 不带参数的函数
- 5. 向左看,碰到* ------------------------------------------- (函数)返回一个指针
- 6. 跳出括号,向右发现() ------------------------------------ 不带参数的函数
- 7. 向左,发现* --------------------------------------------- (函数)返回一个指针
- 8. 继续向左,发现int --------------------------------------- int类型
- 总结:arr被声明成为一个函数的数组指针,该函数返回指向函数指针的指针。
- float ( * ( *b()) [] )();
- // b is a function that returns a
- // pointer to an array of pointers
- // to functions returning floats.
- void * ( *c) ( char, int (*)());
- // c is a pointer to a function that takes
- // two parameters:
- // a char and a pointer to a
- // function that takes no
- // parameters and returns
- // an int
- // and returns a pointer to void.
- void ** (*d) (int &, char **(*)(char *, char **));
- // d is a pointer to a function that takes
- // two parameters:
- // a reference to an int and a pointer
- // to a function that takes two parameters:
- // a pointer to a char and a pointer
- // to a pointer to a char
- // and returns a pointer to a pointer
- // to a char
- // and returns a pointer to a pointer to void
- float ( * ( * e[10]) (int &) ) [5];
- // e is an array of 10 pointers to
- // functions that take a single
- // reference to an int as an argument
- // and return pointers to
- // an array of 5 floats.
另一个方法,比较直观:按照结合的方式来进行,比如下面的例子:
- int *p[3];
- //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组
- int (*p)[3];
- //首先从P 处开始,先与*结合,说明P 是一个指针
- //然后再与[]结合(与"()"这步可以忽略,只是为
- //了改变优先级),说明指针所指向的内容是一个
- //数组,然后再与int 结合,说明数组里的元素是
- //整型的.所以P 是一个指向由整型数据组成的数
- //组的指针
- int **p;
- //首先从P 开始,先与*结合,说是P 是一个指针,然
- //后再与*结合,说明指针所指向的元素是指针,然
- //后再与int 结合,说明该指针所指向的元素是整
- //型数据.由于二级指针以及更高级的指针极少用
- //在复杂的类型中,所以后面更复杂的类型我们就
- //不考虑多级指针了,最多只考虑一级指针.
- int p(int);
- //从P 处起,先与()结合,说明P 是一个函数,然后进入
- //()里分析,说明该函数有一个整型变量的参数
- //然后再与外面的int 结合,说明函数的返回值是
- //一个整型数据
- int (*p)(int);
- //从P 处开始,先与指针结合,说明P 是一个指针,然后与
- //()结合,说明指针指向的是一个函数,然后再与()里的
- //int 结合,说明函数有一个int 型的参数,再与最外层的
- //int 结合,说明函数的返回类型是整型,所以P 是一个指
- //向有一个整型参数且返回类型为整型的函数的指针
- int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂
- //从P 开始,先与()结合,说明P 是一个函数,然后进
- //入()里面,与int 结合,说明函数有一个整型变量
- //参数,然后再与外面的*结合,说明函数返回的是
- //一个指针,,然后到最外面一层,先与[]结合,说明
- //返回的指针指向的是一个数组,然后再与*结合,说
- //明数组里的元素是指针,然后再与int 结合,说明指
- //针指向的内容是整型数据.所以P 是一个参数为一个
- //整数据且返回一个指向由整型指针变量组成的数组
- //的指针变量的函数.
顺便一提:这种“按照优先级结合”的辨别方式,让我联想到const标识符,也是遵循“就近原则”的——谁离它近、结合的优先级高,它就修饰谁。想到这个地方,是不是有种发誓要将所有的优先级全部背下来的冲动呢?(一些冷门的优先级:*和++、*和[]、()等等......)
下面尝试贴一些能够运行的代码:
- #include <string.h>
- int (*pfun)();
- int* fun()
- {
- int *pi = new int;
- return pi;
- }
- void main()
- {
- int* (*arr[5])();
- arr[0]=&fun;
- }
函数指针类型的声明:
- int (*pfun)();
- //typedef int boluo;
- typedef int (*PFUNC)();
- void main()
- {
- int (*p)[3];
- int a[3];
- p = &a;
- }
- int fun()
- {
- return 0;
- }
- PFUNC fun2()
- {
- pfun = &fun;
- return pfun;
- }
对于上面提到的例子,也可以写出验证程序,掌握了规律,一切原来并不复杂:
- int* fun()
- {
- int i=99;
- return &i;
- }
- typedef int* (*PB)();
- typedef PB (*PA)();
- PB func1()
- {
- PB pobj = &fun;
- return pobj;
- }
- void main()
- {
- int * (* (*arr[5])())();//上面提过的例子,够复杂吧?我仍旧可以写出例子来!
- arr[0] = &func1;
- }
再下面是一个更简单的例子,两个函数指针,小case了;在最开始的时候,我写上面这个例子有点困难,琢磨一下,从简单的开始,摸索到规律后就easy了:
- typedef int(*PB)();
- typedef PB(*PA)();
- int fun()
- {
- return 99;
- }
- PB fun2()
- {
- PB pobj = &fun;
- return pobj;
- }
- void main()
- {
- int(*(*a)())();
- a=&fun2;
- }
写这种“验证程序”,有什么规律呢?你会发现,我使用了很多typedef——定义那些“中间类型”,也就是我们使用“右左原则”分析得到的类型;通过它们,我们一路赋值过来,就OK了。
比如:
- int(*(*a)())();
- // a是函数指针类型, 我记之为PA类型, 它所指向函数的返回值是一个指针类型, 暂记为PB类型
- // 所以有, typedef PB(*PA)();
- // 继续向外分析, PB类型又是一个函数指针, 而这个函数指针指向的函数, 返回值为int
- // 所以有, typedef int(*PB)();
- // 剩下的就简单了: 为了给a赋值, 我们必须写一个函数, 这个函数的返回值是PB类型
- // 于是, fun2就产生了
- // 在这个函数中, 我们必须构造一个PB类型的返回值
- // 为了得到这个返回值, 看看PB类型的定义, PB pobj = &fun; 一句话就搞定了.
要点:从内向外分析,每碰到一个类型,就定义它(typedef),最后赋值的时候,想办法构造出对象就行了。
为了熟练,再写一个例子:
- int * (* (*arr[5])())();
- // arr是数组,数组中的元素是指针类型,记为PA;
- // 出了第一层括号,往右看,发现PA是函数指针,而且(该函数指针指向的函数)返回值又是指针类型,记为PB;
- // 故有:typedef PB (*PA)();
- // 再出一层括号,发现PB又是函数指针,且返回值是int*;
- // 故有:typedef int* (*PB)();
- // 我们在写例子的时候,arr[0] = &func1; func1是一个返回值为PB的函数;
- // 在func1函数实体中构造一个PB类型的返回值,pobj作为一个PB函数指针类型,它必须指向一个返回值为int* 的函数
- // 于是结果就写出来了!
为什么要理解这么复杂的例子呢?很简单:计算机都能够编译通过,能够理解的格式,我们人类没有道理不能理解!如果不愿意理解,那么,至少你得承认:你没有领悟到规则的真正含义、没有超越前人的智慧。