关于指针的总结

1,和指针有关的声明

简单的就不说了,你能理解下面的例子吗?

  1. int **p1; // p1 is a pointer  to a pointer  to an int.  
  2. int *&p2; // p2 is a reference to a pointer  to an int.  
  3. int &*p3; // ERROR: Pointer  to a reference is illegal.  
  4. int &&p4; // ERROR: Reference to a reference is illegal.

此外,还有函数指针、指向数组的指针、或者指针的数组......是不是觉得头大?其实里面是有规律的:简单点,C语言遵循最简单的理解原则:就近。(一个复杂的教程会给你列举几十种情况,扔掉它!你难道认为发明C语言的专家是一个怪胎么?)更明确的说法:“右左原则”,从变量名开始,先向右看,然后向左看;当你碰到一个括号时,调转越读方向。括号内的所有内容都分析完毕,就跳出括号的范围;直到整个声明都分析完毕。

下面是个变态的例子:

 

  1. int * (* (*fp1) (int) ) [10];  
  2. 阅读步骤:  
  3. 1. 从变量名开始 -------------------------------------------- fp1  
  4. 2. 往右看,什么也没有,碰到了),因此往左看,碰到一个* ------ 一个指针  
  5. 3. 跳出括号,碰到了(int) ----------------------------------- 一个带一个int参数的函数  
  6. 4. 向左看,发现一个* --------------------------------------- (函数)返回一个指针  
  7. 5. 跳出括号,向右看,碰到[10] ------------------------------ 一个10元素的数组  
  8. 6. 向左看,发现一个* --------------------------------------- 指针  
  9. 7. 向左看,发现int ----------------------------------------- int类型  
  10. 总结:fp1被声明成为一个函数的指针,该函数返回指向指针数组的指针.  

明白了吗?尝试着做做下面的练习:

  1. int *( *( *arr[5])())();  
  2. 阅读步骤:  
  3. 1. 从变量名开始 -------------------------------------------- arr  
  4. 2. 往右看,发现是一个数组 ---------------------------------- 一个5元素的数组  
  5. 3. 向左看,发现一个* --------------------------------------- 指针  
  6. 4. 跳出括号,向右看,发现() -------------------------------- 不带参数的函数  
  7. 5. 向左看,碰到* ------------------------------------------- (函数)返回一个指针  
  8. 6. 跳出括号,向右发现() ------------------------------------ 不带参数的函数  
  9. 7. 向左,发现* --------------------------------------------- (函数)返回一个指针  
  10. 8. 继续向左,发现int --------------------------------------- int类型  
  11. 总结:arr被声明成为一个函数的数组指针,该函数返回指向函数指针的指针。
  12. float ( * ( *b()) [] )();       
  13.         // b is a function that returns a  
  14.                             // pointer to an array of pointers  
  15.                             // to functions returning floats.  
  16. void * ( *c) ( charint (*)());        
  17.         // c is a pointer to a function that takes  
  18.                                 // two parameters:  
  19.                                 //   a char and a pointer to a  
  20.                                 //   function that takes no  
  21.                                 //   parameters and returns  
  22.                                 //   an int  
  23.                                 // and returns a pointer to void.  
  24. void ** (*d) (int &,  char **(*)(char *, char **));   
  25.         // d is a pointer to a function that takes  
  26.                             // two parameters:  
  27.                             //   a reference to an int and a pointer  
  28.                             //   to a function that takes two parameters:  
  29.                             //    a pointer to a char and a pointer  
  30.                             //    to a pointer to a char  
  31.                             //   and returns a pointer to a pointer  
  32.                             //   to a char  
  33.                             // and returns a pointer to a pointer to void  
  34. float ( * ( * e[10]) (int &) ) [5];              
  35.         // e is an array of 10 pointers to  
  36.                             // functions that take a single  
  37.                             // reference to an int as an argument  
  38.                             // and return pointers to  
  39.                             // an array of 5 floats. 

另一个方法,比较直观:按照结合的方式来进行,比如下面的例子:

 

  1. int *p[3]; 
  2. //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组
  3. int (*p)[3]; 
  4. //首先从P 处开始,先与*结合,说明P 是一个指针
  5. //然后再与[]结合(与"()"这步可以忽略,只是为
  6. //了改变优先级),说明指针所指向的内容是一个
  7. //数组,然后再与int 结合,说明数组里的元素是
  8. //整型的.所以P 是一个指向由整型数据组成的数
  9. //组的指针
  10. int **p; 
  11. //首先从P 开始,先与*结合,说是P 是一个指针,然
  12. //后再与*结合,说明指针所指向的元素是指针,然
  13. //后再与int 结合,说明该指针所指向的元素是整
  14. //型数据.由于二级指针以及更高级的指针极少用
  15. //在复杂的类型中,所以后面更复杂的类型我们就
  16. //不考虑多级指针了,最多只考虑一级指针.
  17. int p(int);
  18.  //从P 处起,先与()结合,说明P 是一个函数,然后进入
  19. //()里分析,说明该函数有一个整型变量的参数
  20. //然后再与外面的int 结合,说明函数的返回值是
  21. //一个整型数据
  22. int (*p)(int);
  23.  //从P 处开始,先与指针结合,说明P 是一个指针,然后与
  24. //()结合,说明指针指向的是一个函数,然后再与()里的
  25. //int 结合,说明函数有一个int 型的参数,再与最外层的
  26. //int 结合,说明函数的返回类型是整型,所以P 是一个指
  27. //向有一个整型参数且返回类型为整型的函数的指针
  28. int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂
  29. //从P 开始,先与()结合,说明P 是一个函数,然后进
  30. //入()里面,与int 结合,说明函数有一个整型变量
  31. //参数,然后再与外面的*结合,说明函数返回的是
  32. //一个指针,,然后到最外面一层,先与[]结合,说明
  33. //返回的指针指向的是一个数组,然后再与*结合,说
  34. //明数组里的元素是指针,然后再与int 结合,说明指
  35. //针指向的内容是整型数据.所以P 是一个参数为一个
  36. //整数据且返回一个指向由整型指针变量组成的数组
  37. //的指针变量的函数.

顺便一提:这种“按照优先级结合”的辨别方式,让我联想到const标识符,也是遵循“就近原则”的——谁离它近、结合的优先级高,它就修饰谁。想到这个地方,是不是有种发誓要将所有的优先级全部背下来的冲动呢?(一些冷门的优先级:*和++、*和[]、()等等......)

 

下面尝试贴一些能够运行的代码:

  1. #include <string.h>
  2. int (*pfun)();
  3. int* fun()
  4. {
  5.     int *pi = new int;
  6.     return pi;
  7. }
  8. void main()
  9. {
  10.     int* (*arr[5])(); 
  11.     arr[0]=&fun;
  12. }

函数指针类型的声明:

  1. int (*pfun)();
  2. //typedef int boluo;
  3. typedef int (*PFUNC)();
  4. void main()
  5. {
  6.     int (*p)[3];
  7.     int a[3];
  8.     p = &a;
  9. }
  10. int fun()
  11. {
  12.     return 0;
  13. }
  14. PFUNC fun2()
  15. {
  16.     pfun = &fun;
  17.     return pfun;
  18. }

对于上面提到的例子,也可以写出验证程序,掌握了规律,一切原来并不复杂:

  1. int* fun()
  2. {
  3.     int i=99;
  4.     return &i;
  5. }
  6. typedef int* (*PB)();
  7. typedef PB (*PA)();
  8. PB func1()
  9. {
  10.     PB pobj = &fun;
  11.     return pobj;
  12. }
  13. void main()
  14. {
  15.     int * (* (*arr[5])())();//上面提过的例子,够复杂吧?我仍旧可以写出例子来!
  16.     arr[0] = &func1;
  17. }

再下面是一个更简单的例子,两个函数指针,小case了;在最开始的时候,我写上面这个例子有点困难,琢磨一下,从简单的开始,摸索到规律后就easy了:

  1. typedef int(*PB)();
  2. typedef PB(*PA)();
  3.   int fun()
  4.   {
  5.   return 99;
  6.   }
  7.   PB fun2()
  8.   {
  9.   PB pobj = &fun;
  10.   return pobj;
  11.   }
  12.   
  13.     
  14.       void main()
  15.       {
  16.       int(*(*a)())();
  17.       a=&fun2;  
  18. }  

 

写这种“验证程序”,有什么规律呢?你会发现,我使用了很多typedef——定义那些“中间类型”,也就是我们使用“右左原则”分析得到的类型;通过它们,我们一路赋值过来,就OK了。

比如:

  1. int(*(*a)())();
  2. // a是函数指针类型, 我记之为PA类型, 它所指向函数的返回值是一个指针类型, 暂记为PB类型
  3. // 所以有, typedef PB(*PA)(); 
  4. // 继续向外分析, PB类型又是一个函数指针, 而这个函数指针指向的函数, 返回值为int
  5. // 所以有, typedef int(*PB)();
  6. // 剩下的就简单了: 为了给a赋值, 我们必须写一个函数, 这个函数的返回值是PB类型
  7. // 于是, fun2就产生了
  8. // 在这个函数中, 我们必须构造一个PB类型的返回值
  9. // 为了得到这个返回值, 看看PB类型的定义, PB pobj = &fun; 一句话就搞定了. 

 

要点:从内向外分析,每碰到一个类型,就定义它(typedef),最后赋值的时候,想办法构造出对象就行了。

为了熟练,再写一个例子:

  1. int * (* (*arr[5])())();
  2. // arr是数组,数组中的元素是指针类型,记为PA;
  3. // 出了第一层括号,往右看,发现PA是函数指针,而且(该函数指针指向的函数)返回值又是指针类型,记为PB;
  4. // 故有:typedef PB (*PA)();
  5. // 再出一层括号,发现PB又是函数指针,且返回值是int*;
  6. // 故有:typedef int* (*PB)();
  7. // 我们在写例子的时候,arr[0] = &func1; func1是一个返回值为PB的函数;
  8. // 在func1函数实体中构造一个PB类型的返回值,pobj作为一个PB函数指针类型,它必须指向一个返回值为int* 的函数
  9. // 于是结果就写出来了!

为什么要理解这么复杂的例子呢?很简单:计算机都能够编译通过,能够理解的格式,我们人类没有道理不能理解!如果不愿意理解,那么,至少你得承认:你没有领悟到规则的真正含义、没有超越前人的智慧。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值