const限定符
1.const对象一定要初始化(手工给予初值),初始值可以是任意复杂的表达式;
const int i = get_max(); //运行时初始化
const int j = 4; //编译时初始化
2.const对象初始化第二种方法,利用一个对象去初始化,则他们是不是const都无关要紧;
int i = 2;
const int ti = i; //i的值被拷贝给了ci
int k = ti; //ci的值被拷贝给了k
3.默认状态下,const对象仅在文件内有效。当多个文件中出现了同名的const变量时,其实就等同于在不同文件中分别定义了独立的变量。
但是某些时候const变量的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下我们想让这类const对象像其他(非常量)对象一样工作,即在一个文件中定义const,而在其他多个文件中声明并使用它。解决方法是,对于const变量不管是声明还是定义都添加extern关键字(普通变量只是声明前加extern),这样就不用在每个文中分别定义独立的const变量了。
//file_0.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
//file_0.h头文件
extern const int bufSize;//与file_0.cc中定义的bufSize是同一个
file_0.h头文件中的声明也由extern限定,其作用时是指明bufSize并非本文件所独有,它的定义将在其他地方出现。
note:若想在多个文件之间共享const对象,必须在变量的定义和声明之前添加extern关键字。
const的引用
1.把引用绑定在const对象上,称为对常量的引用(reference to const)。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象,只有常量引用才能指向常量对象:
const int ti = 512; //如果去掉const,当然语法也对,引用bi就是常量了,但这不是我们这里讨论的重点
const int &bi = ti; //引用及其对应的对象都是常量
下面是错误的例子:
const int ti = 512;
const int &b1 = ti;
b1 = 4; //错误:b1是对常量的引用
int &b2 = ti; //错误:试图让一个非常量引用指向一个常量对象
note:常量引用是对const的引用,这只是个简称,严格来讲不存在常量引用。因为引用不是一个对象,我们无法让引用本身恒定不变。实际上,由于C++语言不允许改变引用所绑定的对象,所以从这层意义上理解所有的引用又都算是常量。而引用的对象是常量还是非常量可以决定其所能参与的操作,但无论如何都不会影响到引用和对象的绑定关系本身。
2.引用的类型必须与其所引用对象的类型一致,但有两个例外。
(1)在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可(关于类型转换后续会补充)。也允许为一个常量引用绑定非常量的对象、字面值、表达式。下面我们通过一个例子来理解这个例外:
double dval = 3.1415;
const int &ival = dval;
为了确保让ival绑定一个整数,编译器把上述代码变成了如下形式:
const int temp = dval; //由双精度浮点数生成一个临时的整型变量
const int &ival = temp;//让ival绑定这个临时变量,去掉const则非法
这种情况下,ival绑定了一个临时量(temporary)对象。所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时创建的一个未命名的对象。
(2)对const的引用可能引用一个非const的对象。我们必须要认识到,常量引用仅对引用可参与的操作做出了限定,对于引用本身是不是常量未作限定。
const指针
1.指针本身是一个对象,它又可以指向另一个对象,因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。用名词顶层const(top-level const)表示指针本身是个常量,而用名词底层const(low-level const)表示指针所指的对象是一个常量。
也就是说,顶层const可以表示任意的对象是常量,其对任意数据类型都适用,如算数类型、类、指针等。底层const则与指针和引用等复合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层const也可以是底层const,这一点和其他类型相比区别明显:
int i = 0;
int *const ptr1 = &i; //不能改变ptr1的值,这是一个顶层const
const int ival = 4; //不能改变ival的值,这是一个顶层const
const int *ptr2 = &ival; //允许改变ptr2的值,但不能改变*ptr2的值,这是一个底层const
const int *const ptr3 = ptr2;//靠右的const是顶层const,靠左的是底层const
const int &r = ival; //用于声明引用的const都是底层const
当执行对象的拷贝时,常量是顶层const还是底层const有明显的区别。其中,顶层const不受影响:
i = ival; //正确:拷贝ival的值,ival是一个顶层const,对此操作无影响
ptr2 = ptr3;//正确:两者指向的对象类型相同,ptr3顶层const不影响,ptr2若有顶层const则语法错误
执行拷贝操作并不改变被拷贝对象的值,因此,指针ptr3是否是常量没有影响。
而底层const的限制却不能忽视。当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能转换(一般来说,非常量可以转换成常量,即可以将非常量赋值给常量,反之不行)
int *ptr = ptr3; //错误:ptr3包含底层const的定义,而p没有
ptr2 = ptr3; //正确:两者都是底层const
ptr2 = &i; //正确:int*能转换成const int*
int &r = ival //错误:普通的int&不能绑定到int常量上
const int &r2 = i;//正确:const int&可以绑定到一个普通int上
note:关于顶层const和底层const的翻译易被误解为只有两层,知乎上vczh做了很形象的解释。点击打开链接
假设有这样的代码:
template<typename T> using Const = const T;
template<typename T> using Ptr = T*;
再定义const指针:
const int ***const shit = nullptr;
用Const和Ptr来表达:
Const<Ptr<Ptr<Ptr<Const<int>>>>> shit = nullptr;
其实low-level const翻译成低层更恰当,比如这种情况下:
int *const*const*const*const p = nullptr;
constexpr和常量表达式
1.常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
const int min = 4; //是
const int limit = min + 1;//是
const int sz = get_size();//sz不是常量表达式,虽然sz是常量,但它的具体值直到运行时才能获取到。
2.constexpr变量。C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明constexpr的变量一定是一个常量,而且必须用常量表达式初始化:
constexpr int min = 4; //是常量表达式
constexpr int limit = min + 1;//是常量表达式
constexpr int sz = get_size();//只有当get_size是一个constexpr函数时才是一条正确的声明语句
3.算数类型、引用和指针都术语字面值类型(literal type),自定义类Sales_item、IO库、string类型则不属于字面值类型,也就不能被定义成constexpr,即非字面值类型不能被定义成constexpr。一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
note:函数体中定义的变量一般不存放在固定地址中,故constexpr不能指向这种变量。定义于函数体外的对象其地址不变。还有一种,允许函数定义一类有效范围超过函数本身的变量,这类变量也有固定地址。
4.在constexpr声明中定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:
const int *p = nullptr; //p是一个指向整型常量的指针
constexpr int *q = nullptr;//q是一个指向整数的常量指针
p是一个指向常量的指针,q是一个常量指针,其中关键在于constexpr把q所定义的对象置为了顶层const。
//i和j都必须定义在函数体外
constexpr const int *p = &i;//p是常量指针,指向整型常量i
constexpr int *q = &j; //q是常量指针,指向整数j