【C语言】复杂类型声明

原文地址:

http://blog.csdn.net/wangweixaut061/article/details/6549768 原文不让转载,但实在是有用,就拷贝了一小部分过来。全文请点开链接。

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语言的数组名是一个右值,它不能作为左值来接收另一个数组,因此函数返回值不能为数组。

int func[5](void);

func是一个具有5个元素的数组,这个数组的元素都是函数。这也是非法的,因为数组的元素除了类型必须一样外,每个元素所占用的内存空间也必须相同,显然函数是无法达到这个要求的,即使函数的类型一样,但函数所占用的空间通常是不相同的。

作为练习,下面列几个复杂指针声明给读者自己来解析,答案放在第十章里。

int (*(*func)[5][6])[7][8];

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

int (*(*func[7][8][9])(int*))[5];

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

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

可以这样分解:

typedef  int (*PARA)[5];
typedef PARA (*func)(int *);

这样就容易看得多了。

c言语的复杂类型声明

在c言语的复杂类型声明中,我们用到3个修饰符:*、()、[]。含义如下: 
* 暗示声明一个指针 
() 暗示声明一个函数 
[] 暗示声明一个数组 
c语言允许我们一次使用多个上面所说明的修饰符来申明一个变量,这样我们可以构造出多种多样的类别来。 
大家先看下面的例子: 
int board[8][8]; //二维数组(一个8个元素的数组,元素内容还是数组) 
int **p; //指向指针的指针(一个指针,内容还是一个指针) 
int *array[10]; //10个元素的数组,元素内容是指针。 
int (*p)[10]; //一个指针,指向含有10个元素的数组。 
int *oof[3][4]; //3个元素的数组,存放的内容是指针,指针分别指向4个元素的数组 
int (*oof)[3][4]; //一个指针指向3*4 的数组。

看到这里,可能有人会想,你在说些什么哦,怎么看不明白,没有关系,看完下面的3条法则,你在来看上面的内容就很清晰了。 
1:离名字越近的修饰符优先级越高 
2:[],()优先级比*高 
3:用()括起来的表达式的优先级最高。

http://jingyan.baidu.com/article/22a299b50dd8e59e18376a77.html里面摘抄点东西过来。关于const 挺有用的。

我先总结一下:

  • const int * p  : p是一个指针,指向一个int,该int是不可写的。
  • int * const p :p是不可写的,p是一个指针,指向int
  • int const * p : p是一个指针,指向的内容是不可写的,其内容是int型的。

下面是原文:

继续补充:加上const。以上对于复杂声明的理解,明眼人一看就知道仅仅是从右值的角度入手。要理解const,就必须不可不提到左值了。左值右值是C++中的基本概念,三言两语也说不清楚。最粗浅的看法,左值指可以被写入,也就是说能出现于赋值语句的左边;右值指可读,只能在赋值表达式的右边。当然,一般来说,左值往往可以当做右值来使用,可写往往意味着可读。而const的作用,就是将原本可写的东西,给整成只读的了。具体到表达式来说,就是某些表达式的值具备左右值,而const就是去掉了它的左值功能。举例说吧,还是从最简单说起。

  int a; 表达式a的结果是int型,既是左值又是右值;

  const int 
a;,a返回的结果是int类型,但是此结果已不再可写了,也即是a不能放在赋值语句的左边了。

  int const a; 可这样理解,先不理int 
const,a是一个变量,既可读又可写,const将a整成只读。int表示const a的结果类型。虽然,理解上这样,但对编译器来说,const int 
a;和int const a;都一样,都只是表达了同样的意思,a是一个整型常量。

  const int 
*p;,*p结果为int型,加上const后,*p只能读了,所以,p是整形指针,其所指的内容只能读不能写,但p本身却可写。 int const 
*p;,则先强调*p的只读性,然后再说明*p为int型。其实,这两种写法的意思都一样。

  int *const p;,const 紧挨着p,说明p本身只读。至于 int 
*,则表示*p类型为int,可读写。因此,p是整形指针,p本身不可写,但其所指的内容却可读又可写。说实在,不明白这样的指针变量有什么鬼用,实际的代码应该用的很少才是。

  为了表达指针的只读纯洁性的概念,不仅指针本身不能写,连其指向的内容也不可修改,C++终于整出了下面这样伟大的代码。int 
const *const p;或者const int * const 
p;。C++的这种做法,俗称致力于解决臆想中的问题,因为const与指针的组合,实际上只有指针所指向的内容只能读很有意义,其他的,意义微乎其微。

时间: 2024-11-09 16:02:19

【C语言】复杂类型声明的相关文章

生成跨语言的类型声明和接口绑定的工具(Djinni )

Djinni 是一个用来生成跨语言的类型声明和接口绑定的工具,主要用于 C++ 和 Java 以及 Objective-C 间的互通. 示例接口定义文件: 1 # Multi-line comments can be added here. This comment will be propagated 2 # to each generated definition. 3 my_enum = enum { 4 option1; 5 option2; 6 option3; 7 } 8 9 my_

[转]如何理解c和c ++的复杂类型声明

本文作者girlrong是网易广州社区的C语言版版主,这篇文章被选在精华区.很是不错,不敢独享!据说她乐于助人,虚心诚恳,颇受网友欢迎.只可惜现在已退隐江湖了.在最近学习C语言过程中,了解些前辈大牛的经验看法 曾经碰到过让你迷惑不解.类似于int * (* (*fp1) (int) ) [10];这样的变量声明吗? 本文将由易到难,一步一步教会你如何理解这种复杂的C/C++声明.我们将从每天都能碰到的较简单的声明入手,然后逐步加入const修饰符和typedef,还有函数指针,最后介绍一个能够让

学习Golang语言(6):类型--切片

学习Golang语言(1): Hello World 学习Golang语言(2): 变量 学习Golang语言(3):类型--布尔型和数值类型 学习Golang语言(4):类型--字符串 学习Golang语言(5):类型--数组 学习Golang语言(6):类型--切片 在很多应用场景中,数组不能够满足我们的需求.在初始定义数组时,我们并不知道数组所需的长度.因此,我们需要一个大小可以动态变化的数组(动态数组) 在Go语言中,这种"动态数组"成为slice(切片). 但是实际上slic

Swift 语言附注 类型

本页包含内容: 类型注解(Type Annotation) 类型标识符(Type Identifier) 元组类型(Tuple Type) 函数类型(Function Type) 数组类型(Array Type) 可选类型(Optional Type) 隐式解析可选类型(Implicitly Unwrapped Optional Type) 协议合成类型(Protocol Composition Type) 元类型(Metatype Type) 类型继承子句(Type Inheritance C

从C语言的变量声明到Objective-C中的Block语法转载]

原文:From C Declarators to Objective-C Blocks Syntax 作者:Nils Hayat 译者:CocoaChina--sunshine 在这篇文章中,从简单的C语言中各种声明开始,以及复杂的声明组合,到最后Objective-C中的代码块bokck的语法. 花一些时间去了解代码块(block)衍生和组织形式,一旦明白了这些,就可以很方便的声明和使用它,而不用每次需要的时候才去Google一下. 如果你想把能想到的东西用block声明表现出来,请继续阅读!

DOCTYPE 文档类型声明

DOCTYPE声明文档类型,以便验证文档是否符合文档类型定义(DTD),同时指定了浏览器关于页面使用哪个HTML版本进行编写的指令.开发人员在编写完html代码后,要进行有效性验证.w3c提供了一个验证文档有效性的在线服务:http://validator.w3.org/. DOCTYPE声明不是html标签,必须放在html文档的第一行,而且绝不能在它之前添加xml声明语句,否则在IE6会触发怪异模式.在HTML 4.0中,DOCTYPE引用了DTD,因为HTML 4.0基于SGML.DTD规

C语言中标识符声明的几个关键字总结

C语言中声明一个名称就是把一个标识符与某个C语言对象相关联,如变量.函数或类型,C语言中可以声明的名称包括:变量.函数.类型.类型标志.结构成员与联合成员.枚举常量.语句标号和预处理器宏.除了语句标号和预处理器宏之外,所有标识符都在C语言声明中声明,变量.函数.类型放在声明的声明符中,类型标志.结构成员与联合成员和枚举常量在声明的某种类型说明符中声明,语句标号在C语言函数中出现时声明,而预处理器宏用#define预处理器命令声明. 如下列声明: extern const volatile uns

C语言存储类型

看c专家编程,有说存储类型一直不太清楚.看到一篇文章讲解c的存储类型,讲解了c语言中的各种变量的存储类型,而且是从进程.内存的角度讲解的,以前从没有这样理解过,觉得挺有用的,在这里转载过来. 首先要来理解一下可执行文件加载进内存后形成的进程在内存中的结构,如下图: 代码区:存放CPU执行的机器指令,代码区是可共享,并且是只读的. 数据区:存放已初始化的全局变量.静态变量(全局和局部).常量数据. BBS区:存放的是未初始化的全局变量和静态变量. 栈区:由编译器自动分配释放,存放函数的参数值.返回

动态语言 静态语言 强类型语言 弱类型语言

一 .静态语言的优势到底在哪? 引用 是像Java或者C#这样强类型的准静态语言在实现复杂的业务逻辑.开发大型商业系统.以及那些生命周期很长的应用中也有着非常强的优势 这是一个存在于大家心里常识了.我承认我自己在潜意识里面也觉得静态强类型语言适合开发复杂,大型系统.而弱类型脚本语言不适合开发太复杂,太大型的项目.但是在参与这个讨论过程中,我突然开始置疑这个观点,事实究竟是不是这样的呢? 先定义一下标准: 静态类型语言是指在编译时变量的数据类型即可确定的语言,多数静态类型语言要求在使用变量之前必须