const这个关键字在c++被“重载”了很多次,写在不同地方表示不同的意思。需要分别来说明:
const 修饰变量
第一点:const修饰的变量一定要初始化,const变量的值在编译时就要被确定,放在“代码段”的“.rodata”中,如果在程序中显示的修改const变量,会报编译时错误,如果一些技巧来欺骗编译器来修改const变量,其行为是未定义的。例如:
int num = 90; //定义非const变量
const int &r = num; //使用const引用来指向num,此后就不能通过r来修改num了
const_cast<int &>(r) = 100; //使用RTTI来去掉r的底层const,然后再来修改r,这样做是安全的因为num本来就是非const
cout<<num<<endl; //输出num为100
//但是
const int num = 90; //定义const变量
const int &r = num; //使用const引用来指向num,此后就不能通过r来修改num了
const_cast<int &>(r) = 100; //使用RTTI来去掉r的底层const,然后再来修改r,这样做行为是未定义的
cout<<num<<endl; //输出会发现num依然为90,c++承诺常量始终是常量
第二点:const变量的连接性是内部的。在头文件中定义一个非const变量(如 int num = 10;),由于头文件可能会被多个实现文件包含,这个“num”变量是一个全局变量,它是一个强符号,链接器会报重复定义,有趣的是被const修饰的变量却不会,原因是const变量的连接性是内部的,它的作用域和static一样会被限制在当前翻译单元中,这种连接性为内部的变量,在编译时就被初始化了,不需要等到运行时(const变量的初始化可以被推迟到运行时,后面介绍)。
底层const与顶层const##
const在修饰指针变量和引用时会有顶层底层之分,看代码
int num = 90;
int * const p = &num; //此时的p是带有顶层const的,它表示p是一个常量,因此必须初始化
int const * p = &num; //此时的p是带有底层const的,它表示不能通过p来修改num,而不管num本身是否为常量。
const int * p = &num; //这种写法等价于上面的
int num = 90;
int const & r = num; //此时的p是带有底层const的,它表示不能通过p来修改num,而不管num本身是否为常量。
const int & r = num; //这种写法等价于上面的
int & const r = num; //错误,没有这种写法
//关于为什么没有上述写法,我的理解是引用的本质是一个“自解引用的常量指针”,如
int &r = a; //等价于 int *const r = &a; 这也是为什么引用必须初始化的原因
cout<<a<<endl; //等价于 cout<<*a<<endl;
//当然上述对引用本质的理解是从编译器角度来说的,还可以从单纯的概念层面来说明问题,今后会写一篇我对引用理解的文章。
const与constexpr及常量表达式##
第一点:先说说什么是常量表达式:常量表达式是在编译时就能确定其值且在运行时不会改变的量,一个变量是不是常量表达式由其变量类型和初始值决定。
int num = 1;//不是
const int num = 1;//是
const int num = size();//不是,const修饰的变量可以在编译时初始化,也可以推迟到运行时
第二点:c++11引入了constexpr,被constexpr修饰的变量或函数必须在编译时计算出来,看代码
constexpr int num = size(); //size()必须为constexpr函数
//constexpr函数是一个隐式inline函数,还要求该函数的参数和返回值只能是字面值,不能是虚函数,不能是递归函数
第三点:constexpr与指针
cosntexpr int * p = # //constexpr是顶层的
int * const p = # //等价于上面的
constexpr const int * p = #
const int * const p = # //等价于上面的
//注意:由于constexpr的变量是一个在编译时确定的常量表达式,因此num必须有固定的地址,如全局变量和static变量。
const与函数重载
顶层const不能构成重载的依据,但底层const可以构成重载的依据,例如
void show(int num);
void show(const int num); //顶层const,这两个不是重载
void show(int * p);
void show(int *const p); //顶层const,这两个不是重载
void show(int & r);
void show(const int &r); //底层const,是重载
void show(int *p);
void show(const int *p); //底层const,是重载
//另外
void show();
void show()const; //可以重载
在类中使用const
当我们在类中声明const成员变量时,变量一定要初始化。我们需要在类的构造函数的初始化列表中初始化,不能在函数体中初始化(因为函数体是在对变量赋值,而不是赋值)。还需要注意的是:由于通过构造函数可以把该变量初始化成不同的值,使得不同对象之间的值不同(这可能不是我们所想的),所以可以采用匿名enum类型定义常量。