声明与定义

    以前学C语言的时候,你有木有为下面的类似声明而迷惑呢:

const int *p;

int const *p;

int * const p;

    这里的const到底是修饰int,还是int*呢? 对于这几个声明的理解,是一些公司偏重的面试题。曾经我也很努力地去记住每一个声明的具体意义,但是过了没多久就又全忘了。最近看了《C专家编程》一书中的解释,才晃然间感觉到是真的有些懂了。

 

那么什么是声明,什么是定义呢?

    首先我们先想一下,什么是声明,什么是定义,为什么会有这两个东西在C语言中出现?

    我个人觉得,在现实世界中,我们可以把声明理解为一种告示。声明就是告诉别人说,这里有这么一个东西是属于谁的或者是什么样的一件事情等等。当然这个告示你可以贴的到处都是,以防有的人会看不到。因此,声明可以有多份。

    类似地,在C语言的世界里,声明一个变量,就是通告说在某个地方有这么一个变量存在,我们了解了地方有之后,就可以拿来使用了。

    定义说白了就是为某个东西取一个统一的意思,它也是一种特殊的声明。在C语言里,定义就是一种特殊的声明,定义一个变量的时候编译器会为其分配内存。

    因此,在C语言中同一变量可以被声明多次,但只能被定义一次(因为定义的时候分配内存,内存只分配一次就够了)。

 

怎么解析变量的声明?

    既然有了声明,那么想一下,声明在编译器中又是怎样被解析的呢?我们写的任何程序都是用来执行的,要执行程序,编译器肯定就要先理解程序。只要我们知道计算机是怎么来理解这些语言的,我们也就能很轻易的理解这些相关的知识了。

    像上面提到的三个声明:

const int *p;

int const *p;

int * const p;

    编译器会怎样理解呢?它会怎么定位这三个p的具体意义呢?当然,编译器是木有思想的,它只会一步步的去执行,所以编译器的开发者肯定会为它制定一个规则,告诉它说怎样怎样去一步步的理解程序中的声明。并且,这个规则必须是无二义性的,要不然编译器就会迷茫而不知道应该去选择那个意思去理解了。

    想一想,如没有一个指定的规则,而让编译器随便去解析就好的话,那么我们的同一段程序就有可能会有很多不同的结果。这样一来,计算机的世界其不是乱了套了。

    因此,只要我们抓着编译器的理解规则,就能轻而易举地现理解声明的具体意义了。

 

关于C的声明,编译器的解析规则是这样的:

[

编译器,从最左边开始读取,碰到第一个标识符就认定这是我们要定义的变量,接着它就按照如下的优先级依次读取并解析声明:

p1, 如果当前读到的标识符被括号括了起来,它就先解析括起来的那部分

p2, 解析完括号里的内容,它会向后看一眼,看看后面有什么东西不。

      如果后面碰到[],就解析为数组;如果后面碰到()就解析为函数

p3,看完后面,就向前看一眼,如果前面有*就说明是指针。

另外,编译器规定,const/volatile 一般修饰其后面紧跟的类型说明符,否则它作用于其左边的符号。

]

 

举几个例子,来模仿下编译器的解析过程:

例1, char * const * (* next) ();

1, char * const * (* next) (); 第一步,编译器读取到左边的第一个标识符next, 表示定义一变量next

2, char * const * (*        ) (); 第二步,解析标识符被括起来的部分:同样按照优先级规则,先看一下next后面,木有东西;然后看一下next的前面,有一个*。说明 next是个指针。

3,char * const *             (); 第三步,看完括号中的部分,看一下后面,有()说明,next指向的是一个函数。

4,char * const *               ; 第四步,看完后面,看一下前面*,说明函数的返回值是一个指针。

5,char * const                  ; 第五步,解析const, 按照规定,const修饰它后面紧跟的类型说明符,此处const后面没有类型说明符。所以它修饰其左边的 *, 说明是常指针

6, char *                            ; 第六步,char *,说明是指向字符的指针。

总结以上的解析过程可得,next是一个指针,它指向函数,函数的返回值是一指针,指向一个常指针,常指针指向的内容是字符。简言之,next是一个指向函数的指针,函数返回指向字符常指针的指针。

 

例2,文章刚开始处提到的三个声明

按照编译器规定,

const int * p; 因为const 后面紧挨着int, 所以它修饰int,说明是常整型。这里p是指向const int的指针;

int const * p; 因为const 后面紧跟的不是类型说明符,所以它作用于其左边的int,因此这里的解析为p是指向const int的指针;

int * const p; 因为const 后面紧跟的不是类型说明符,所以它作用于其左边的int *,因此这里的解析为p是const 指针,它指向int类型的变量。

 

总结

    知道了编译器的解析规则,以后碰到各种声明我们都可以自己按照编译器的思路去解析了,而不用再死记硬背每一个声明具体代表什么意思。因此,知其然并知其所以然才是王道。

    本人在这里自己写出来,主要是为了巩固自己的知识理解。如果有人对这块知识点感兴趣 建议大家还是去看看《C专家编程》的第三章分析C语言的声明或研究下C语言声明解析的源代码。相信在哪里你会得到更好的解释,通过别人转达过来的知识,终归会有些变味地。

 

参考文献:

1,《C专家编程》------第三章 分析C语言的声明

2,推荐一个关于C声明的解析网站:www.cdecl.org

从这个网站上我们还可以下载c语言声明的解析代码,有兴趣的可以研究下。

posted @ 2012-07-21 11:40 写代码的李纳 阅读(489) 评论(2) 推荐(0) 编辑
摘要: 4 反转单向链表(非递归实现)思路:图1 非递归反转链表 如图1所示,假设已经反转了前面的若干节点,且前一段链表的头节点指针为pre,则现在要做的事情是首先保存当前节点cur后面的链表,然后让当前节点cur的指针与后面的节点断开(step1),接下来再将当前节点的next指针指向前一段链表的头节点pre (step2)。处理完当前节点的连接反转后,所有的指针都向后移一位。开始处理下一个节点。 注意点: 1,反转后原来的头节点就变成了反转链表的尾节点,要注意将此结点next指针设为空,否则可能会产生死循环等问题 2,要记得处理链表中没有节点或只有一个的情况。代码实现://反转链表(非递归的方式) 阅读全文
posted @ 2011-05-08 17:45 写代码的李纳 阅读(3161) 评论(4) 推荐(1) 编辑
摘要: 本文主要介绍解决单向链表上的一些操作的思路和代码实现。主要的问题包括以下几点: 1 向单向链表中的插入一个节点 2 删除单向链表中的一个节点  3 查找单向链表中的一个节点   扩展问题1:查找单向链表中的倒数第k个节点。   扩展问题2:查找单向链表中的中间节点,当节点个数为偶数时返回中间两个元素中的前者(后者)  4反转单向链表(非递归实现)  5反转单向链表(递归实现)  6判断单向链表是否有环 7判断两个单向链表是否相交    扩展问题:返回两个链表的第一个交点。  8 用单链表实现栈,要求push和pop的时间复杂度为O(1)  9 用单链表实现队列,要求enQueue和deQueue的时间复杂度为O(1)  10 在一个链表中删除另一个链表中的元素(即求差集(A-B)) 阅读全文
posted @ 2011-05-06 18:44 写代码的李纳 阅读(7988) 评论(7) 推荐(2) 编辑
摘要: 众所周知,在计算机中要对给定的数据集进行若干处理,首要任务是把数据集的一部分(当数据量非常大时,可能只能一部分一部分地读取数据到内存中来处理)或全部存储到内存中,然后再对内存中的数据进行各种处理。 例如,对于数据集S{1,2,3,4,5,6},要求S中元素的和,首先要把数据存储到内存中,然后再将内存中的数据相加。 当内存空间中有足够大的连续空间时,可以把数据连续的存放在内存中,各种编程语言中的数组一般都是按这种方式存储的(也可能有例外),如图1(b);当内存中只有一些离散的可用空间时,想连续存储数据就非常困难了,这时能想到的一种解决方式是移动内存中的数据,把离散的空间聚集成连续的一块大空间,. 阅读全文
posted @ 2011-05-06 16:27 写代码的李纳 阅读(8215) 评论(5) 推荐(4) 编辑
摘要: 在本文中主要介绍了什么是MySql数据库,并进一步引出了它的一个重要特性, 即插入式的多存储引擎机制。然后,简单介绍了什么是存储引擎和MySql中几种主要的存储引擎。最后,介绍了如何查看数据库支持的所有存储引擎,如何查看某个数据库表的存储引擎类型及如何设置或修改表的存储引擎类型。刚刚入门学习MySql,文中有错误之处,还请大家多多指导! 阅读全文
posted @ 2011-04-29 16:14 写代码的李纳 阅读(54625) 评论(11) 推荐(21) 编辑
点击右上角即可分享
微信分享提示