77范文网 - 专业文章范例文档资料分享平台

《再再论指针》修订版(4)

来源:网络收集 时间:2018-12-17 下载这篇文档 手机版
说明:文章内容仅供预览,部分内容可能不全,需要完整文档或者需要复制内容,请下载word后使用。下载word有问题请添加微信号:或QQ: 处理(尽可能给您提供完整文档),感谢您的支持与谅解。点击这里给我发消息

q = p; /* A */

A 合法,这种情况并不属于赋值运算符的规则之内,它遵循的是另一个条款:左值转换。 一个被限定修饰的左值,在进行左值转换之后,右值具有左值的非限定修饰类型: 6.3.2 Other operands

6.3.2.1 Lvalues, arrays, and function designators

Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue). If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue; otherwise, the value has the type of the lvalue.

p 的值具有 p 的非限定修饰类型 int*,与 q 类型相容,因此赋值合法。对于 C++,基本 上与 C 相同,但有一个例外,就是右值类对象,由于右值类对象仍然是一个对象,C++规定 右值类对象具有与左值相同的限定修饰词。

指针与 const 的结合能够产生一些比较复杂的声明,例如: const int * const *** const ** const p;

这是一个较为复杂的指针声明符与 const 限定修饰词的组合,声明符部分嵌套了六次, 中间还带有两个 const,如何辨认哪一级是 const,哪一级不是呢?一旦明白了其中的原理, 其实是非常简单的。第一和最后一个 const 大家都已经很熟悉的了。对于藏在一堆*号中的 const,有一个非常简单的原则:const 与左边最后一个声明说明符之间有多少个*号,那么 就是多少级指针是 const 的。例如从右数起第二个 const,它与 int 之间有 4 个*号,那么 p 的四级部分就是 const 的,下面的赋值表达式是非法的: **p = (int *const***)10;

但下面的赋值是允许的: ***p=(int*const**)10;

从左边数起第二个 const,它与 int 之间有 1 个*,那么 p 的一级部分是 const 的,也就 是*****p = (int*const***const*)10;是非法的。

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

首先看看如下一个声明:

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 语法,但需要注意其带来的不利影响。

第八章 柔性数组成员

在讲述柔性数组成员之前,首先要介绍一下不完整类型(incomplete type)。不完整类 型是这样一种类型,它缺乏足够的信息例如长度去描述一个完整的对象。 6.2.5 Types

incomplete types (types that describe objects but lack information needed to determine their sizes).

C 与 C++关于不完整类型的语义是一样的。 基本上没有什么书介绍过不完整类型,很多人初次遇到这个概念时脑袋会一片空白。事 实上我们在实际的工程设计中经常使用不完整类型,只不过不知道有这么个概念而已。前向 声明就是一种常用的不完整类型: class base; struct test;

base 和 test 只给出了声明,没有给出定义。不完整类型必须通过某种方式补充完整, 才能使用它们进行实例化,否则只能用于定义指针或引用,因为此时实例化的是指针或引用 本身,不是 base 或 test 对象。

一个未知长度的数组也属于不完整类型: extern int a[];

extern 不能去掉,因为数组的长度未知,不能作为定义出现。不完整类型的数组可以通 过几种方式补充完整才能使用,大括号形式的初始化就是其中一种方式: int a[] = { 10, 20 };

柔性数组成员(flexible array member)也叫伸缩性数组成员,它的出现反映了 C 程序员 对精炼代码的极致追求。这种代码结构产生于对动态结构体的需求。在日常的编程中,有时 候需要在结构体中存放一个长度动态的字符串,一般的做法,是在结构体中定义一个指针成 员,这个指针成员指向该字符串所在的动态内存空间,例如: struct test {

int a; double b; char *p;

};

p 指向字符串。这种方法造成字符串与结构体是分离的,不利于操作,如果把字符串跟 结构体直接连在一起,不是更好吗?于是,可以把代码修改为这样: char a*+ = “hello world”;

struct test *PntTest = ( struct test* )malloc( sizeof( struct test ) + strlen( a ) + 1 ); strcpy( PntTest + 1, a );

这样一来,( char* )( PntTest + 1 )就是字符串“hello world”的地址了。这时候 p 成了多 余的东西,可以去掉。但是,又产生了另外一个问题:老是使用( char* )( PntTest + 1 )不方便。 如果能够找出一种方法,既能直接引用该字符串,又不占用结构体的空间,就完美了,符合 这种条件的代码结构应该是一个非对象的符号地址,在结构体的尾部放置一个 0 长度的数组 是一个绝妙的解决方案。不过,C/C++标准规定不能定义长度为 0 的数组,因此,有些编译 器就把 0 长度的数组成员作为自己的非标准扩展,例如: struct test {

百度搜索“77cn”或“免费范文网”即可找到本站免费阅读全部范文。收藏本站方便下次阅读,免费范文网,提供经典小说综合文库《再再论指针》修订版(4)在线全文阅读。

《再再论指针》修订版(4).doc 将本文的Word文档下载到电脑,方便复制、编辑、收藏和打印 下载失败或者文档不完整,请联系客服人员解决!
本文链接:https://www.77cn.com.cn/wenku/zonghe/373683.html(转载请注明文章来源)
Copyright © 2008-2022 免费范文网 版权所有
声明 :本网站尊重并保护知识产权,根据《信息网络传播权保护条例》,如果我们转载的作品侵犯了您的权利,请在一个月内通知我们,我们会及时删除。
客服QQ: 邮箱:tiandhx2@hotmail.com
苏ICP备16052595号-18
× 注册会员免费下载(下载后可以自由复制和排版)
注册会员下载
全站内容免费自由复制
注册会员下载
全站内容免费自由复制
注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: