第7章 常量
一、值替代:用C 语言进行程序设计时,预处理器可以不受限制地建立宏并用它来替代值。因为预处理器只做文本替代,它既没有类型检查思想,也没有类型检查工具,所以预处理器的值替代会产生一些微小的问题,这些问题在C + +中可通过使用const而避免。C + +中的const 默认为内部连接,也就是说,const 仅在const 被定义过的文件里才是可见的,而在连接时不能被其他编译单元看到。当定义一个常量const时,必须赋一个值给它,除非用extern作了清楚的说明:
extern const bufsize ;C + +编译器通常并不为const分配存储空间,相反它把这个定义保存在它的符号表里。当const 被使用时,它在编译时会进行常量折叠.
对常量进行分配内存的两个情况:1、上面的extern 强制进行了存储空间分配 (内部连接的常量不要分配地址,而外部连接的常量需要分配地址) 2、另外还有一些情况,如取一个const的地址,也要进行存储空间分配 。对于在大量场合使用的内部数据类型,包括常量表达式,编译器都能执行常量折叠。当然,绝对不为任何const分配存储是不可能的,尤其对于复杂的结构。这种情况下,编译器建立存储,这会阻止常量折叠。这(有时会给常量分配内存)就是const为什么必须默认内部连接,即连接仅在特别编译单元内的原因;否则,由于众多的const在多个cpp文件内分配存储,容易引起连接错误,连接程序在多个对象文件里看到同样的定义就会“抱怨”了(发现同名的厂量)。然而,因为const默认内部连接,所以连接程序不会跨过编译单元连接那些定义,因此不会有冲突。(C默认const 是外部连接的)
当处理const指针时,编译器仍将努力阻止存储分配并进行常量折叠。我们可以把一个非const 对象的地址赋给一个const指针,因为也许有时不想改变某些可以改变的东西。然而,不能把一个const对象的地址赋给一个非const指针,因为这样做可能通过被赋值指针改变这个const指针。当然,总能用类型转换强制进行这样的赋值(int *a=(int*)&e强行取得地址),但是,这不是一个好的程序设计习惯,因为这样就打破了对象的const属性以及由const提供的安全性。
函数参数和返回值:用const限定函数参数及返回值是常量概念另一个容易被混淆的地方。如果以值传递对象时,对用户(函数的调用者)来讲,用const限定没有意义(它意味着传递的参数在函数里是不能被修改的),但是对函数的设计者来说,却有着一定的作用,保证某个参数不会改变。所以它其实是函数创建者的工具,而不是函数调用者的工具。为了不使调用者混淆,在函数内部用const限定参数优于在参数表里用const限定参数。如果以常量返回用户定义类型的一个对象的值,这意味着返回值不能被修改。如果传递并返回地址,const将保证该地址内容不会被改变。对于内部数据类型int,float,char等基础数据类型,不包括指针和结构、数组)来说,返回值是否是常量并没有关系,所以返回一个内部数据类型的值时,应该去掉const从而使用户程序员不混淆。处理用户定义的类型时,返回值为常量是很重要的。如果一个函数返回一个类对象的值,其值是常量,那么这个函数的返回值不能是一个左值(即它不能被赋值,也不能被修改)。返回一个内部数据类型的值时,const没有意义的原因是:编译器已经不让它成为一个左值(因为它总是一个值而不是一个变量)。仅当返回用户定义的类型对象的值时,才会出现上述问题。如果传递或返回一个指针(或一个引用),用户取指针并修改初值是可能的。如果使这个指针成为常量指针,就会阻止这类事的发生,这是非常重要的。事实上,无论什么时候传递一个地址给一个函数,我们都应该尽可能用const修饰它,如果不这样做,就使得带有指向const的指针函数不具备可用性。是否选择返回一个指向const的指针,取决于我们想让用户用它干什么.
由于引用的语法(看起来像值传递)的原因,传递一个临时对象给带有一个引用的函数是可能的,但不能传递一个临时对象给带有一个指针的函数----因为它必须清楚地带有地址。所以,通过引用传递会产生一个在C 中不会出现的新情形:一个总是常量的临时变量,它的地址可以被传递给一个函数。这就是为什么临时变量通过引用被传递给一个函数时,这个函数的参数一定是常量(const)引用。在函数参数中使用常量引用特别重要。这是因为我们的函数也许会接受临时的对象,这个临时对象是由另一个函数的返回值创立或由函数使用者显式地创立的。临时对象总是不变的,因此如果不使用常量引用,参数将不会被编译器接受。 f(1)
二、集合:const可以用于集合(数组) const int a={1,2,3,4},但编译器不能把一个集合存放在它的符号表里,所以必须分配内存。在这种情况下,const 意味着“不能改变的一块存储”。然而,其值在编译时不能被使用,因为编译器在编译时不需要知道存储的内容,只有在运行的时候才知道(虽然早已确定它的值,但是不知道它的存放位置阿)!
三、类里的const 和enum
起初读者可能认为合乎逻辑的选择是把一个const放在类里。但这不会产生预期的结果。在一个类里,const恢复它在C 中的一部分意思。它在每个类对象里分配存储并代表一个值,这个值一旦被初始化以后就不能改变。在一个类里使用const的意思是“在这个对象寿命期内,这是一个常量”。然而,对这个常量来讲,每个不同的对象可以含一个不同的值。在一个类里建立一个const 时,不能给它初值。这个初始化工作必须发生在构造函数里,并且,要在构造函数的某个特别的地方。因为const必须在建立它的地方被初始化,所以在构造函数的主体里,const必须已初始化了,否则,就只有等待,直到在构造函数主体以后的某个地方给它初始化,这意味着过一会儿才给const初始化。
四、const 对象和成员函数
编译器强调对象为const的,因此它必须保证对象的数据成员在对象寿命期内不被改变。可以很容易地保证公有数据不被改变,但是怎么知道哪个成员函数会改变数据?又怎么知道哪个成员函数对于const对象来说是“安全”的呢?如果声明一个成员函数为const函数,则等于告诉编译器可以为一个const对象调用这个函数。一个没有被特别声明为const的成员函数被看成是将要修改对象中数据成员的函数,而且编译器不允许为一个const对象调用这个函数。。仅仅声明一个函数在类定义里是const的,不能保证成员函数也如此定义,所以编译器迫使程序员在定义函数时要重申const说明。(const已成为函数识别符的一部分,所以编译器和连接程序都要检查const)。
五、 volatile
volatile的语法与const是一样的,但是volatile的意思是“在编译器认识的范围外,这个数据可以被改变”。不知何故,环境正在改变数据(可能通过多任务处理),所以,volatile告诉编译器不要擅自做出有关数据的任何假定—在优化期间这是特别重要的。如果编译器说:“我已经把数据读进寄存器,而且再没有与寄存器接触”。一般情况下,它不需要再读这个数据。但是,如果数据是volatile修饰的,编译器不能作出这样的假定,因为可能被其他进程改变了,它必须重读这个数据而不是优化这个代码。
第8章 内联函数
一、内联函数和编译器
为了理解内联何时有效,应该先理解编译器遇到一个内联函数时将做什么。对于任何函数,编译器在它的符号表里放入函数类型(即包括名字和参数类型的函数原型及函数的返回类型)。另外,编译器看到内联函数和内联函数的分析没有错误时,函数的代码也被放入符号表。代码是以源程序形式存放还是以编译过的汇编指令形式存放取决于编译器。调用一个内联函数时,编译器首先确保调用正确,即所有的参数类型必须是正确类型或编译器必须能够将类型转换为正确类型,并且返回值在目标表达式里应该是正确类型或可改变为正确类型。当然,编译器对任何类型函数都是这样做的,这与预处理器显著不同,因为预处理器不能检查类型和进行转换。假如所有的函数类型信息符合调用的上下文的话,内联函数代码就会直接替换函数调用,消除了调用的开销。假如内联函数也是成员函数,对象的地址(this)就会被放入合适的地方,这当然也是预处理器不能执行的。
二、标志粘贴
标志粘贴在写代码时是非常有用的。它让我们设两个标识符并把它们粘贴在一起自动产生一个新的标识符。
#define FIELD(A) char* A##_string
main()
{
FIELD(a);
a_string ="aaaa";
cout<<a_string<<endl;
}
本文地址:http://com.8s8s.com/it/it29624.htm