C++编程思想07.pdf
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_05.gif)
《C++编程思想07.pdf》由会员分享,可在线阅读,更多相关《C++编程思想07.pdf(19页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、下载下载第7章常量常量概念的建立(由关键字c o n s t表示)允许程序员在变化和不变化之间划一条界线。在C+程序设计项目中提供了安全性和可控性。自从常量问世以来,它就有着很多不同的作用。与此同时,它在C语言中的意义又不一样。开始时,看起来容易混淆。在这一章里,我们将介绍什么时候、为什么和怎样使用关键字 c o n s t。最后,讨论v o l a t i l e,它是c o n s t的“兄弟”(因为它们都关系到是否变化,而且语法也一样)。c o n s t的最初动机是取代预处理器#d e f i n e s进行值替代。从此它曾被用于指针、函数变量、返回类型、类对象及其成员函数。所有这些用
2、法都稍有区别,但它们在概念上是一致的,我们将在以下各节中说明这些用法。7.1 值替代用C语言进行程序设计时,预处理器可以不受限制地建立宏并用它来替代值。因为预处理器只做文本替代,它既没有类型检查思想,也没有类型检查工具,所以预处理器的值替代会产生一些微小的问题,这些问题在C+中可通过使用c o n s t而避免。C语言中预处理器用值替代名字的典型用法是这样的:#define BUFSIZE 100B U F S I Z E是一个名字,它不占用存储空间且能放在一个头文件里,目的是为使用它的所有编译单元提供一个值。用值替代而不是用所谓的“不可思议的数”,这对于支持代码维护是非常重要的。如果代码中用
3、到不可思议的数,读者不仅不清楚这个数字来自哪里,而且也不知道它代表什么,进而,当决定改变一个值时,程序员必须执行手动编辑,而且还不能跟踪以保证没有漏掉其中的一个。多数情况,B U F S I Z E的工作方式与普通变量一样但也不都如此;而且这种方法还存在一个类型问题。这就会隐藏一些很难发现的错误。C+用c o n s t把值替代带进编译器领域来解决这些问题。可以这样写:const bufsize=100;或用更清楚的形式:const int bufsize=100;这样就可以在任何编译器需要知道这个值的地方使用 b u f s i z e,同时它还可以执行常量折叠,也就是说,编译器在编译时可以
4、通过必要的计算把一个复杂的常量表达式缩减成简单的。这一点在数组定义里显得尤其重要:char bufbufsize;我们可以为所有的内部数据类型(c h a r、i n t、f l o a t和d o u b l e型)以及由它们所定义的变量(也可以是类的对象,这将在以后章节里讲到)使用限定符 c o n s t。我们应该完全用 c o n s t取代#d e f i n e的值替代。7.1.1 头文件里的const与使用#d e f i n e一样,使用c o n s t必须把c o n s t定义放进头文件里。这样,通过包含头文件,可把c o n s t定义单独放在一个地方并把它分配给一个编
5、译单元。C+中的c o n s t默认为内部连接,也就是说,c o n s t仅在c o n s t被定义过的文件里才是可见的,而在连接时不能被其他编译单元看到。当定义一个常量(c o n s t)时,必须赋一个值给它,除非用e x t e r n作了清楚的说明:extern const bufsize;虽然上面的e x t e r n强制进行了存储空间分配(另外还有一些情况,如取一个 c o n s t的地址,也要进行存储空间分配),但是C+编译器通常并不为c o n s t分配存储空间,相反它把这个定义保存在它的符号表里。当c o n s t被使用时,它在编译时会进行常量折叠。当然,绝对不
6、为任何c o n s t分配存储是不可能的,尤其对于复杂的结构。这种情况下,编译器建立存储,这会阻止常量折叠。这就是 c o n s t为什么必须默认内部连接,即连接仅在特别编译单元内的原因;否则,由于众多的 c o n s t在多个c p p文件内分配存储,容易引起连接错误,连接程序在多个对象文件里看到同样的定义就会“抱怨”了。然而,因为c o n s t默认内部连接,所以连接程序不会跨过编译单元连接那些定义,因此不会有冲突。对于在大量场合使用的内部数据类型,包括常量表达式,编译器都能执行常量折叠。7.1.2 const的安全性c o n s t的作用不限于在常数表达式里代替#d e f i
7、 n e s。如果用运行期间产生的值初始化一个变量而且知道在那个变量寿命期内它是不变的,用 c o n s t限定该变量,程序设计中这是一个很好的做法。如果偶然改变了它,编译器会给出一个出错信息。下面是一个例子:我们会发现,i是一个编译期间的常量,但j是从i中计算出来的。然而,由于i是一个常量,j的计算值来自一个常数表达式,而它自身也是一个编译期间的常量。紧接下面的一行需要 j的地址,所以迫使编译器给j分配存储空间。即使分配了存储空间,把j值保存在程序的某个地方,由于编译器知道j是常量,而且知道j值是有效的,所以,这仍不会妨碍在决定数组 b u f的大小时使用j。在主函数m a i n()里,
8、对于标识符c中有另一种c o n s t,因为其值在编译期间是不知道的。这意味着需要存储空间,而编译器不想在符号表里保留任何东西(和 C的方式一样)。初始化必须发生在定义的地方,而且一旦初始化,其值不能改变。我们看到 c 2由c的值计算出来,也会看到这类常量的作用域与其他任何类型常量的作用域是一样的这是对#d e f i n e用法的另一种第7章 常量125下载改进。实际上,如果想一个值不变,就应该使之成为常量(c o n s t)。这不仅为防止意外的更改提供安全措施,也消除了存储和读内存操作,使编译器产生的代码更有效。7.1.3 集合c o n s t可以用于集合,但编译器不能把一个集合存放
9、在它的符号表里,所以必须分配内存。在这种情况下,c o n s t意味着“不能改变的一块存储”。然而,其值在编译时不能被使用,因为编译器在编译时不需要知道存储的内容。这样,我们不能写:在一个数组定义里,编译器必须能产生这样的移动存储数组的栈指针代码。在上面这两种非法定义里,编译器给出“提示”是因为它不能在数组定义里找到一个常数表达式。7.1.4 与C语言的区别常量引进是在早期的C+版本中,当时标准C规范正在制订。那时,常量被看作是一个好的思想而被包含在C中。但是,C中的c o n s t意思是“一个不能被改变的普通变量”,在C中,它总是占用存储而且它的名字是全局符。C编译器不能把c o n s
10、 t看成一个编译期间的常量。在C中,如果写:const bufsize=100;char bufbufsize;尽管看起来好像做了一件合理的事,但这将得到一个错误结果。因为 b u f s i z e占用存储的某个地方,所以C编译器不知道它在编译时的值。在C语言中可以选择这样书写:const bufsize;这样写在C+中是不对的,而C编译器则把它作为一个声明,这个声明指明在别的地方有存储分配。因为C默认c o n s t是外部连接的,C+默认c o n s t是内部连接的,这样,如果在C+中想完成与C中同样的事情,必须用e x t e r n把连接改成外部连接:extern const bu
11、fsize;/declaration only这种方法也可用在C语言中。在C语言中使用限定符c o n s t不是很有用,即使是在常数表达式里(必须在编译期间被求出)想使用一个已命名的值,使用c o n s t也不是很有用的。C迫使程序员在预处理器里使用#d e f i n e。126C+编程思想下载7.2 指针我们还可以使指针成为 c o n s t指针。当处理c o n s t指针时,编译器仍将努力阻止存储分配并进行常量折叠,但在这种情况下,这些特征似乎很少有用。更重要的是,如果程序员以后想在程序代码中改变这种指针的使用,编译器将给出通知。这大大增加了安全性。当使用带有指针的c o n s
12、 t时,有两种选择:或者c o n s t修饰指针正指向对象,或者 c o n s t修饰存储在指针本身的地址里。这些语法在开始时有点使人混淆,但练习之后就好了。7.2.1 指向const的指针使用指针定义的技巧,正如任何复杂的定义一样,是在标识符的开始处读它并从里向外读。c o n s t指定那个“最靠近”的。这样,如果要使正指向的元素不发生改变,我们得写一个像这样的定义:const int*x;从标识符开始,是这样读的:“x是一个指针,它指向一个const int。”这里不需要初始化,因为说x可以指向任何东西(那是说,它不是一个c o n s t),但它所指的东西是不能被改变的。这是一个容
13、易混淆的部分。有人可能认为:要想指针本身不变,即包含在指针 x里的地址不变,可简单地像这样把c o n s t从一边移向另一边:int const*x;并非所有的人都很肯定地认为:应该读成“x是一个指向i n t的c o n s t指针”。然而,实际上应读成“x是一个指向恰好是c o n s t的i n t普通指针”。即c o n s t又把它自己与i n t结合在一起,结果与前面定义一样。两个定义是一样的,这一点容易使人混淆。为使程序更具有可读性,我们应该坚持用第一种形式。7.2.2 const指针使指针本身成为一个c o n s t指针,必须把c o n s t标明的部分放在*的右边,如:
14、int d=1;int*const x=&d;现在它读成“x是一个指针,这个指针是指向i n t的c o n s t指针”。因为现在指针本身是c o n s t指针,编译器要求给它一个初始化值,这个值在指针寿命期间不变。然而要改变它所指向的值是可以的,可以写*x=2;也可以使用下面两种合法形式中的任何一种形式把一个c o n s t指针变为一个c o n s t对象:int d=1;const int*const x=&d;/(1)int const*const x2=&d;/(2)现在,指针和对象都不能改变。一些人认为第二种形式更好。因为c o n s t总是放在被修改者的右边。但对于特定的
15、代码类型来讲,程序员得自己决定哪一种形式更清楚。格式这本书主张,不管何时在一行里仅放一个指针定义,且在定义的地方初始化每个指针。正因为这一点,才可以把*“附于”数据类型上:int*u=&w;第7章 常量127下载i n t*本身好像是离散型的。这使代码更容易懂,可惜的是,事情并非如此。事实上,*与标识符结合,而不是与类型结合。它可以被放在类型名和标识符之间的任何地方。所以,可以这样做:int*u=&w,v=0;它建立一个int*u和一个非指针int v。由于读者时常混淆这一点,所以最好用本书里所用的表示形式(即一行里只定义一个指针)。7.2.3 赋值和类型检查C+关于类型检查有其特别之处,这一
16、点也扩展到指针赋值。我们可以把一个非 c o n s t对象的地址赋给一个c o n s t指针,因为也许有时不想改变某些可以改变的东西。然而,不能把一个c o n s t对象的地址赋给一个非 c o n s t指针,因为这样做可能通过被赋值指针改变这个 c o n s t指针。当然,总能用类型转换强制进行这样的赋值,但是,这不是一个好的程序设计习惯,因为这样就打破了对象的c o n s t属性以及由c o n s t提供的安全性。例如:虽然C+有助于防止错误发生,但如果程序员自己打破了这种安全机制,它也是无能为力的。串字面值限定词c o n s t是很严格的,c o n s t没被强调的地方
17、是有关串字面值。也许有人写:char*cp=howdy;编译器将接受它而不报告错误。从技术上讲,这是一个错误,因为串字面值(这里是“h o w d y”)是被编译器作为一个常量串建立的,所引用串的结果是它在内存里的首地址。所以串字面值实际上是常量串。然而,编译器把它们作为非常量看待,这是因为有许多现有的代码是这样做的。当然,改变串字面值的做法还未被定义,虽然可能在很多机器上是这样做的。7.3 函数参数和返回值用c o n s t限定函数参数及返回值是常量概念另一个容易被混淆的地方。如果以值传递对象时,对用户来讲,用c o n s t限定没有意义(它意味着传递的参数在函数里是不能被修改的)。如果
18、以常量返回用户定义类型的一个对象的值,这意味着返回值不能被修改。如果传递并返回地址,c o n s t将保证该地址内容不会被改变。7.3.1 传递const值如果函数是以值传递的,可用c o n s t限定函数参数,如:128C+编程思想下载这是什么意思呢?这是作了一个约定:变量初值不会被函数x()改变。然而,由于参数是以值传递的,因此要立即制作原变量的副本,这个约定对用户来说是隐藏的。在函数里,c o n s t有这样的意义:参数不能被改变。所以它其实是函数创建者的工具,而不是函数调用者的工具。为了不使调用者混淆,在函数内部用 c o n s t限定参数优于在参数表里用 c o n s t限
19、定参数。可以用一个指针这样做,但更好的语法形式是“引用”,这是第1 0章讨论的主题。简言之,引用像一个被自动逆向引用的常指针,它的作用是成为对象的别名。为建立一个引用,在定义里使用&。所以,不引起混淆的函数定义看来像这样的:这又会得到一个错误信息,但这时对象的常量性(c o n s t)不是函数特征标志的部分;它仅对函数实现有意义,所以它对用户来说是不可见的。7.3.2 返回const值对返回值来讲,存在一个类似的道理,即如果从一个函数中返回值,这个值作为一个常量:const int g();约定了函数框架里的原变量不会被修改。正如前面讲的,返回这个变量的值,因为这个变量被制成副本,所以初值不
20、会被修改。首先,这使c o n s t看起来没有什么意义。可以从这个例子中看到:返回常量值明显失去意义:对于内部数据类型来说,返回值是否是常量并没有关系,所以返回一个内部数据类型的值时,应该去掉c o n s t从而使用户程序员不混淆。处理用户定义的类型时,返回值为常量是很重要的。如果一个函数返回一个类对象的值,其值是常量,那么这个函数的返回值不能是一个左值(即它不能被赋值,也不能被修改)。例如:第7章 常量129下载f 5()返回一个非const X对象,然而f 6()返回一个const X对象。只有非c o n s t返回值能作为一个左值使用。换句话说,如果不让对象的返回值作为一个左值使用
21、,当返回一个对象的值时,应该使用c o n s t。返回一个内部数据类型的值时,c o n s t没有意义的原因是:编译器已经不让它成为一个左值(因为它总是一个值而不是一个变量)。仅当返回用户定义的类型对象的值时,才会出现上述问题。函数f 7()把它的参数作为一个非c o n s t引用(C+中另一种处理地址的办法,这是第 1 0章讨论的主题)。从效果上讲,这与取一个非c o n s t指针一样,只是语法不同。临时变量有时候,在求表达式值期间,编译器必须建立临时对象。像其他任何对象一样,它们需要存储空间而且必须被构造和删除。区别是我们从来看不到它们编译器负责决定它们的去留以及它们存在的细节。这
22、里有一个关于临时变量的情况:它们自动地成为常量。因为我们通常接触不到临时对象,不能使用与之相关的信息,所以告诉临时对象做一些改变几乎肯定会出错。当程序员犯那样的错误时,由于使所有的临时变量自动地成为常量,编译器会向他发出错误警告。类对象常量是怎样保存起来的,将在这一章的后面介绍。130C+编程思想下载7.3.3 传递和返回地址如果传递或返回一个指针(或一个引用),用户取指针并修改初值是可能的。如果使这个指针成为常(c o n s t)指针,就会阻止这类事的发生,这是非常重要的。事实上,无论什么时候传递一个地址给一个函数,我们都应该尽可能用 c o n s t修饰它,如果不这样做,就使得带有指向
23、c o n s t的指针函数不具备可用性。是否选择返回一个指向c o n s t的指针,取决于我们想让用户用它干什么。下面这个例子表明了如何使用c o n s t指针作为函数参数和返回值:函数t()把一个普通的非c o n s t指针作为一个参数,而函数 u()把一个c o n s t指针作为参数。在第7章 常量131下载函数u()里,我们会看到试图修改 c o n s t指针的内容是非法的。当然,我们可以把信息拷进一个非c o n s t变量。编译器也不允许使用存储在c o n s t指针里的地址来建立一个非c o n s t指针。函数v()和w()测试返回的语义值。函数v()返回一个从串字
24、面值中建立的 const char*。在编译器建立了它并把它存储在静态存储区之后,这个声明实际上产生串字面值的地址。像前面提到的一样,从技术上讲,这个串是一个常量,这个常量由函数 v()的返回值正确地表示。w()的返回值要求这个指针及这个指针所指向的对象均为常量。像函数 v()一样,仅仅因为它是静态的,所以在函数返回后由 w()返回的值是有效的。函数不能返回指向局部栈变量的指针,这是因为在函数返回后它们是无效的,而且栈也被清理了。可返回的另一个普通指针是在堆中分配的存储地址,在函数返回后它仍然有效。在函数m a i n()中,函数被各种参数测试。函数t()将接受一个非c o n s t指针参数
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 编程 思想 07
![提示](https://www.taowenge.com/images/bang_tan.gif)
限制150内