`
java-mans
  • 浏览: 11391834 次
文章分类
社区版块
存档分类
最新评论

深入探索 C/C++ 数组与指针的奥秘之七:右左法则----复杂指针解析

 
阅读更多

深入探索 C/C++ 数组与指针的奥秘之七:右左法则----复杂指针解析

首先看看如下一个声明:


这是一个会让初学者感到头晕目眩、感到恐惧的函数指针声明。在熟练掌握 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.
这段英文的翻译如下:
右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。
笔者要对这个法则进行一个小小的修正,应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。
现在通过一些例子来讨论右左法则的应用,先从最简单的开始,逐步加深:


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


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


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


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


func 是一个函数指针,这类函数具有 int* 类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有 5 个 int 元素的数组。
要注意有些复杂指针声明是非法的,例如:


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


func 是一个具有 5 个元素的数组,这个数组的元素都是函数。这也是非法的,因为数组的元素必须是对象,但函数不是对象,不能作为数组的元素。
实际编程当中,需要声明一个复杂指针时,如果把整个声明写成上面所示这些形式,将对可读性带来一定的损害,应该用 typedef 来对声明逐层分解,增强可读性。
typedef 是一种声明,但它声明的不是变量,也没有创建新类型,而是某种类型的别名。typedef 有很大的用途,对一个复杂声明进行分解以增强可读性是其作用之一。例如对于声明:


可以这样分解:


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


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


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 组成,这三者在逻辑上是一个整体,构成一个完整的指针声明器。这也是多个变量同列定义时指针声明符必须紧跟标识符的原因,例如:


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/supermegaboy/archive/2009/11/23/4854965.aspx

分享到:
评论

相关推荐

    数组与指针的艺术.doc

    数组与指针的艺术 数组与指针的艺术

    C/C++语言中指针的奥秘

    指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞 清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指 针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区...

    免费下载:C语言难点分析整理.doc

    38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针...

    c语言难点分析整理,C语言

    38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针...

    深入理解c语言指针的奥秘

    深入理解C/C++语言指针的奥秘。对你学习C有很大的帮助!

    深入理解C语言指针的奥秘 C和C++中函数指针的含义(上).pdf

    深入理解C语言指针的奥秘 C和C++中函数指针的含义(上).pdf

    c/c++ 指针详解教程

    让你不再害怕指针,深入理解C语言指针的奥秘。 C++程序设计中使用指针可以: 使程序简洁、紧凑、高效 有效地表示复杂的数据结构 动态分配内存 得到多于一个的函数返回值

    高级C语言 C 语言编程要点

    38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针...

    史上最强的C语言资料

    38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针...

    高级C语言详解

    38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针...

    C语言难点分析整理.doc

    38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组...

    C语言难点分析整理

    38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针...

    高级进阶c语言教程..doc

    38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针...

    C++指针经典教程(指针专讲)

    对于初学者来讲指针这个东西很烦人,理解起来并不难,但是在实际开发中该如何灵活的使用指针却是个难点。特分享《C/C++指针经验总结——经典教程》,这个教程让你更快更透彻的知晓C++指针的奥秘:)

    C++指针及句柄

    C++指针及句柄,深入理解 指针的奥秘 .

    深入理解C语言指针的奥秘

    特经典,简单易懂,分析的透彻.一语中的. 帮你揭开指针的奥密.

    关于C语言指针的最详尽精辟阐述

    关于C语言指针的最详尽精辟阐述,无论对初学者,还是中高级c/c++程序员都有不小的帮助,让你彻底了解指针奥秘,厚积薄发才能决胜千里!

    易学C++(简单易懂的讲解)

    7.3 向函数传递数组……69 7.4二维数组……71习题……74第八章内存里的快捷方式……78 8.1什么是指针……78 8.2指针变量的定义和使用……78 8.3指针的操作……80 8.4指针与保护……82 8.5指针与数组……83 8.6指针与...

    指针的详细说明

    你还在为指针发愁吗?请看本文.本文章详细说明拉C,C++中指针的概念,含义,以及函数指针等较为难懂的东西.

    侯捷- -深入浅出MFC

    深入浅出MFC(第二版) 目录 第0章 你一定要知道(导读) 这本书适合谁 你需要什么技术基础 你需要什么软硬件环境 让我们使用同一种语言 本书符号习惯 本书例程的取得 范例程序说明 与前版本之差异 如何联络作者 第...

Global site tag (gtag.js) - Google Analytics