1 const
const
表示常量,一旦创建之后就无法改变其值,所以const
对象必须进行初始化。
const
对象只是限定一旦创建之后就无法改变其值,但允许使用普通变量/任意表达式初始化const
变量,或用const
变量初始化普通变量,如:
int a = 3;
const int d = a;
int e = d;
const int f = e * d;
默认情况下,const
变量仅在其定义的文件内有效。如果想要在不同的文件内共享同一个const
变量,就需要在const
变量的定义和使用的文件内都添加extern
:
//file1.h
extern const int f = 5;
//file1.cpp
//表明使用在别的文件中定义的const变量f
extern const int f;
2 顶层const
和底层const
int nVal = 5;
const int *p = &nVal;
int *const r = &nVal;
const int *const q = p;
复合类型判定时,自右向左进行判断。
p
是一个指针,所指向的对象是const int
类型,所以p
是底层const
,p
本身指向的位置可以变,但是当前p
指向的位置中存储的变量不能被改变;
r
是一个常量,其类型为指针,指向int
类型的对象,所以r
是顶层const
,r
是常量指针,表示r
指向的位置不能变,但指向的位置中存储的内容可以变;
q
是一个常量,其类型为指针,指向const int
类型的对象,所以q
既是顶层const
,也是底层const
,顶层const
表示q
指向的位置不能变,底层const
表示q
指向的位置存储的内容也不能变。
当执行对象拷贝时,顶层const
不影响,但底层const
必须匹配,如:
int i = 5;
int* const p1 = &i; //(1)
const int ci = 42;
const int* p2 = &ci;
const int* const p3 = p2; //(2)
const int& r = ci;
i = ci; //(3)
p2 = p3; //(4)
int* p = p3; //(5)
p2 = p3; //(6)
p2 = &i; //(7)
int& k = ci; //(8)
const int& r2 = i; //(9)
(1)
正确,p1
是顶层const
,指向int
变量,可以使用普通的int
变量给其赋值;
(2)
正确,p3
既是顶层const
,也是底层const
,可以使用底层const
给其赋值;
(3)
正确,ci
作为常量,可以给普通int
变量赋值,等价于使用字面值给普通int
变量赋值;
(4)
正确,p2
是底层const
,p3
底层和顶层都是const
,可以忽略顶层const
给p2
赋值;
(5)
会编译失败,因为如果允许这样写,那么就可以通过p
修改其所指向的内容,但是p3
中要求所指向的内容不能被修改,两者相互矛盾,所以不允许这种写法;
(6)
正确,因为p2
和p3
底层类型一致,顶层const
不影响拷贝;
(7)
正确,允许使用int *
初始化const int *
,但这有个风险就是如果p2
所指向的内容不能通过p2
修改,但可以通过其他途径修改,就是p2
自认为自己指向的是const
,但其实际未必是const
;
(8)
会编译失败,这是因为不允许将const
值绑定到非const
引用上,如果允许这样做,就表示允许通过k
修改常量的值,这是错误的;
(9)
正确,允许使用普通变量给const
变量赋值,只是和(7)
中一样,r2
自认为是常量,但其实可以通过其他途径进行修改。
3 constexpr
c++引入了constexpr
,表示常量表达式,是指值不会改变且在编译时已经知道其值的表达式。
将变量声明为constexpr
以便编译器来验证变量的值是否为常量表达式。但要注意如果对一个指针使用了constexpr
,也只是表示指针本身为常量表达式,和指针所指向的对象无关:
const int *p = nullptr; //p是一个指向const int的指针
constexpr int* p = nullptr;//p是一个常量指针,指针本身为常量,是顶层const
如果一个指针声明为constexpr
,其值必须为nullptr
或0,或是某个存储于固定地址中的对象,否则这个指针的值是无法在编译时确定的,也就不能称之为constexpr
。
4 const形参与实参
函数定义为具有const
形参时,其顶层const
可忽略。
可以给具有const
形参的函数传入非const
实参,如:
int func(const int n);
可以通过下面的方式进行调用:
int m = 5;
func(m);
只是在func
函数内部不能改变n
的值。
基于上述原因,不能仅仅区别是否使用const
形参进行函数重载,下面两个函数:
int func(const int n);
int func(int n);
在调用时是相同的,不能认为是函数的两种不同重载方式。
允许用常量对象初始化非常量对象,反之则不行。函数调用时规则一致,允许用常量实参赋予非常量形参,反之则不行。
尽量把不需要改变的形参定义为常量引用,这样明确告诉编译器不需要对实参进行复制且告知接口调用者不能在函数内部改变实参的值。如果定义成普通引用,则告知接口调用者可以在函数内部改变实参的值,且也无法用常量实参、字面值或需要类型转换的对象传递给普通的引用形参。