高质量程序设计指南c++/c语言(20)--符号常量
存在两种符号常量,用#define定义的宏常量和用const定义的常量。宏常量是预编译伪指令,在本质上是字面常量。在c语言中,const符号常量默认是外连接的,因此会给它分配存储空间,也就是说你不能在两个编译单元中同时定义一个同名的const符号常量(重复定义错误),或者把一个const符号常量定义在一个头文件中而在多个编译单元中同时包含该头文件。但是在标准c++中,const符号常量默认是内连接的,因此可以定义在头文件中。当在不同的编译单元中同时包含该头文件时,编译器认为它们是不同的符号常量,因此每个编译单元独立编译时会分别为它们分配存储空间,而在连接时进行常量合并。
在c++中,const定义的符号常量要区别对待:对于基本数据类型的常量,编译器会把它们放到符号表中而不分配存储空间,而const的类对象则需要分配存储空间。还有一些情况下是需要分配存储空间的,例如,强制声明为extern的符号常量或取符号常量的地址等操作,都将强迫编译器为这些常量分配存储空间,以满足用户的需求。
你可以取一个const符号常量的地址:对于基本数据类型的const常量,编译器会重新在内存中创建一个它的拷贝,你通过其地址访问到的就是这个拷贝而非原始的符号常量;而对于复合数据类型的const常量,实际上它是编译时不允许修改的变量,因此,如果你能绕过编译器的静态类型安全检查机制,就可以在运行时修改其内存单元。
#include<iostream> using namespace std; class Integer { public: Integer():m_int(10){} int m_int; }; int main(void) { const long lng=10; long *pl = (long *)&lng; //取常量的地址 *pl = 1000; //迂回修改 cout << *pl << endl; //1000, 修改的是拷贝内容 cout << lng << endl; //10, 符号表内的原始常量没有改变 const Integer a; Integer *p = (Integer *)&a; //除去常量属性 p->m_int = 1000; //迂回修改 cout << p->m_int << endl; //1000, 修改const对象 cout << a.m_int << endl; //1000, 迂回修改成功 return 0; }
这个例子说明,const修饰符只是编译时(源代码层面或语言层面)强类型安全检查机制的一种手段,以帮助程序员发现无意中要修改它们的代码并进行纠正,而在运行时(二进制层面)无法阻止恶意的修改。也就是说“防君子不防小人”。
1、const与#define的比较
前者比后者有更多的优点,const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行静态类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能产生意想不到的错误。
2、类中的常量
不能在类声明中初始化非静态const数据成员,如下面是错误的
class A { public: const int SIZE = 100; };
非静态数据成员的初始化只能在类的构造函数的初始化列表里面
class A { public: A(int n):SIZE(n){} const int SIZE; };
上面的常量是对于每一个类A的对象而言的,怎样才能建立在整个类中都恒定的常量呢?使用enum或者static const
class A { public: enum { SIZE1 = 100, SIZE2 = 200 }; int array[SIZE1]; };
枚举常量不会占用对象的存储空间,它们在编译时会被全部求值。枚举常量的缺点是不能表示浮点数和字符串。
class A { static const int SIZE; }; const int A::SIZE = 100;