文档章节

第七章 右左法则----复杂指针解析

北极心
 北极心
发布于 2016/08/11 10:29
字数 2120
阅读 46
收藏 3
点赞 0
评论 0
c++

 

首先看看如下一个声明:

int* ( *( *fun )( int* ) )[10];

这是一个会让初学者感到头晕目眩、感到恐惧的函数指针声明。在熟练掌握C/C++的声明语法之前,不学习一定的规则,想理解好这类复杂声明是比较困难的。

C/C++所有复杂的声明结构,都是由各种声明嵌套构成的。如何解读复杂指针声明?右左法则是一个很著名、很有效的方法。不过,右左法则其实并不是C/C++标准里面的内容,它是从C/C++标准的声明规定中归纳出来的方法。C/C++标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的,从嵌套的角度看,两者可以说是一个相反的过程。右左法则的英文原文是这样说的:

The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.

这段英文的翻译如下:

右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。

    笔者要对这个法则进行一个小小的修正,应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。

    现在通过一些例子来讨论右左法则的应用,先从最简单的开始,逐步加深:

int (*func)(int *p);

首先找到那个未定义的标识符,就是func,它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返回值类型是int。

int (*func)(int *p, int (*f)(int*));

func被一对括号包含,且左边有一个*号,说明func是一个指针,跳出括号,右边也有个括号,那么func是一个指向函数的指针,这类函数具有int *和int (*)(int*)这样的形参,返回值为int类型。再来看一看func的形参int (*f)(int*),类似前面的解释,f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int。

int (*func[5])(int *p);

func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素是指针,要注意这里的*不是修饰func的,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,因此*修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。

int (*(*func)[5])(int *p);

func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。

int (*(*func)(int *p))[5];

func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。

要注意有些复杂指针声明是非法的,例如:

int func(void) [5];

func是一个返回值为具有5个int元素的数组的函数。但C语言的函数返回值不能为数组,这是因为如果允许函数返回值为数组,那么接收这个数组的内容的东西,也必须是一个数组,但C/C++语言的数组名是一个不可修改的左值,它不能直接被另一个数组的内容修改,因此函数返回值不能为数组。

int func[5](void);

func是一个具有5个元素的数组,这个数组的元素都是函数。这也是非法的,因为数组的元素必须是对象,但函数不是对象,不能作为数组的元素。

实际编程当中,需要声明一个复杂指针时,如果把整个声明写成上面所示这些形式,将对可读性带来一定的损害,应该用typedef来对声明逐层分解,增强可读性。

typedef是一种声明,但它声明的不是变量,也没有创建新类型,而是某种类型的别名。typedef有很大的用途,对一个复杂声明进行分解以增强可读性是其作用之一。例如对于声明:

int (*(*func)(int *p))[5];

可以这样分解:

typedef  int (*PARA)[5];

typedef PARA (*func)(int *);

这样就容易看得多了。

typedef的另一个作用,是作为基于对象编程的高层抽象手段。在ADT中,它可以用来在C/C++和现实世界的物件间建立关联,将这些物件抽象成C/C++的类型系统。在设计ADT的时候,我们常常声明某个指针的别名,例如:

typedef struct node * list;

从ADT的角度看,这个声明是再自然不过的事情,可以用list来定义一个列表。但从C/C++语法的角度来看,它其实是不符合C/C++声明语法的逻辑的,它暴力地将指针声明符从指针声明器中分离出来,这会造成一些异于人们阅读习惯的现象,考虑下面代码:

const struct node *p1;

typedef struct node *list;

const list p2;

p1类型是const struct node*,那么p2呢?如果你以为就是把list简单“代入”p2,然后得出p2类型也是const struct node*的结果,就大错特错了。p2的类型其实是struct node * const p2,那个const限定的是p2,不是node。造成这一奇异现象的原因是指针声明器被分割,标准中规定:

6.7.5.1 Pointer declarators

Semantics

 If in the declaration ‘‘T D1’’, D1 has the form

* type-qualifier-listopt D

and the type specified for ident in the declaration ‘‘T D’’ is

‘‘derived-declarator-type-list T’’

then the type specified for ident is

‘‘derived-declarator-type-list type-qualifier-list pointer to T’’

For each type qualifier in the list, ident is a so-qualified pointer.

指针的声明器由指针声明符*、可选的类型限定词type-qualifier-listopt和标识符D组成,这三者在逻辑上是一个整体,构成一个完整的指针声明器。这也是多个变量同列定义时指针声明符必须紧跟标识符的原因,例如:

int *p, q, *k;

p和k都是指针,但q不是,这是因为*p、*k是一个整体指针声明器,以表示声明的是一个指针。编译器会把指针声明符左边的类型包括其限定词作为指针指向的实体的类型,右边的限定词限定被声明的标识符。但现在typedef struct node *list硬生生把*从整个指针声明器中分离出来,编译器找不到*,会认为const list p2中的const是限定p2的,正因如此,p2的类型是node * const而不是const node*。

虽然typedef struct node* list不符合声明语法的逻辑,但基于typedef在ADT中的重要作用以及信息隐藏的要求,我们应该让用户这样使用list A,而不是list *A,因此在ADT的设计中仍应使用上述typedef语法,但需要注意其带来的不利影响。

本文转载自:http://blog.csdn.net/code_crash/article/details/4854965

共有 人打赏支持
北极心
粉丝 34
博文 39
码字总数 16464
作品 0
深圳
后端工程师
理解UNIX系统的signal函数的定义

读《 unix 环境高级编程》信号机制一章,遇到 signal 函数: #include<signal.h> void ( signal (int signo, void (func) (int) )) (int ); 返回值:成功返回信号以前的处理配置,出错返回 ...

扶殊88
2011/11/29
0
0
【C++】typedef用法小结

不管实在C还是C++代码中,typedef这个词都不少见,当然出现频率较高的还是在C代码中。typedef与#define有些相似,但更多的是不同,特别是在一些复杂的用法上,就完全不同了。 用途一: 定义一...

xyq10612
2014/04/22
0
0
Javascript算法系列之快速排序(Quicksort)

快速排序(Quicksort)是对冒泡排序的一种改进,是一种分而治之算法归并排序的风格 核心的思想就是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有...

文艺小青年
2017/07/06
0
0
c++ primer 第五版学习笔记

第二章 函数体外定义的内置类型变量会初始化为0,函数体外的是未初始化的 用constexpr声明变量表示它是一个常量表达式(编译器可以确定的值),且只能应用于字面值 c++11中可以用 来定义一个...

David栗子
2017/12/11
0
0
第一章 数组与指针概念剖析

数组与指针生来就是双胞胎,多数人就是从数组的学习开始指针的旅程的。在学习的过程中,很自然就会经常听到或见到关于数组与指针的各种各样的看法,下面我节选一些在各种论坛和文章里经常见到...

北极心
2016/08/11
4
0
typedef的使用总结(转)

typedef的使用总结(转) 用途一: 定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如: char pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符...

小熊猫大暴走
2012/04/28
0
0
typedef的四个用途和两大陷阱

typedef的四个用途和两个陷阱 --------------------------------- 用途一: 定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象...

晨曦之光
2012/03/09
75
0
typedef的四个用途和两个陷阱

用途一: 定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如: char pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针, // 和一个字...

扶殊88
2011/11/29
0
0
C++ Primer 学习笔记(第四章:表达式)

C++ Primer 学习笔记(第四章:表达式) [TOC] 4.1 基础 左值和右值: 当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。...

ShawnLue
2015/08/20
0
0
二叉树,排序二叉树

说到二叉树,这可是数据结构里面的非常重要的一种数据结构,二叉树是树的一种,本身具有递归性质,所以基于二叉树的一些算法很容易用递归算法去实现。作为一种非线性结构,比起线性结构还是相...

长平狐
2013/12/25
129
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Python爬虫 爬取百合网的女人们和男人们

学Python也有段时间了,目前学到了Python的类。个人感觉Python的类不应称之为类,而应称之为数据类型,只是数据类型而已!只是数据类型而已!只是数据类型而已!重要的事情说三篇。 据书上说...

p柯西
14分钟前
0
0
在Java中,你真的会日期转换吗

1.什么是SimpleDateFormat 在java doc对SimpleDateFormat的解释如下: SimpleDateFormatis a concrete class for formatting and parsing dates in a locale-sensitive manner. It allows fo......

Java小铺
22分钟前
0
0
Linux系统梳理---系统搭建(二):tomcat的安装和使用

上一章讲到JDK的安装使用,这一章主要记录下服务器tomcat的安装以及部署一个项目. 1.下载tomcat,这里下载的是apache-tomcat-8.5.32.tar.gz 2.创建文件夹,便于管理,和JDK一样,在usr目录下创建t...

勤奋的蚂蚁
33分钟前
0
0
ES15-聚合

1.Terms Aggregation 分组聚合 2.Filter Aggregation 过滤聚合

贾峰uk
34分钟前
0
0
【2018.07.19学习笔记】【linux高级知识 20.27-20.30】

20.27 分发系统介绍 20.28 expect脚本远程登录 20.29 expect脚本远程执行命令 20.30 expect脚本传递参数

lgsxp
37分钟前
0
0
10.32/10.33 rsync通过服务同步~10.35 screen工具

通过服务的方式同步要编辑配置文件:[root@linux-xl ~]# vim /etc/rsyncd.confport=873log file=/var/log/rsync.logpid file=/var/run/rsyncd.pidaddress=192.168.43.21[tes...

洗香香
40分钟前
0
0
与女儿谈商业模式 (3):沃尔玛的成功模式

分类:与女儿谈商业模式 | 标签: 经济学 沃尔玛 陈志武 2007-05-10 09:09阅读(11279)评论(30) 与女儿谈商业模式 (3):沃尔玛的成功模式 陈志武 /文 沃尔玛(Wal-Mart)是另一个有意思的财...

祖冲之
46分钟前
0
0
网页加载速度优化方法总结

1、减少请求 最大的性能漏洞就是一个页面需要发起几十个网络请求来获取诸如样式表、脚本或者图片这样的资源,这个在相对低带宽和高延迟的移动设备连接上来说影响更严重。 2、整合资源 对开发...

Jack088
52分钟前
0
0
dubbo学习

https://blog.csdn.net/houshaolin/article/details/76408399

喵五郎
今天
0
0
mybatis-session.selectList源码分析

0.构建工厂:SqlSessionFactory 。 new SqlSessionFactoryBuilder.build(配置的xml文件) 获取sqlSession对象 //指定事务隔离级别 1. sqlMapper.openSession(TransactionIsolationLevel.SER......

writeademo
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部