const常量限定符
const的初始化及作用
对类型添加关键字const修饰后,其值不能被改变。
const int bufSize = 512; // 正确:const修饰的变量必须进行初始化
const int j = get_size(); // 正确:const修饰的变量初始值可以是任意复杂的表达式
bufSize = 1024; // 错误:修饰const后不能进行修改
const在多文件下的声明并使用
默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中定义了独立的变量。
如果想要只在一个文件中定义const,而在其他多个文件中声明并使用。解决办法是,对于const变量不管是声明还是定义都添加extern关键字(extern会将修饰变量设为全局变量,各个文件皆可使用)。
// study.h中声明a
extern const int a;
// study.cpp中定义a
#include <iostream>
#include "study_1.h"
extern const int a = 10;
int main()
{
std::cout << qwe() << std::endl;
std::cout << a << std::endl;
}
/*
输出结果为:
10
10
*/
// study_1.h中使用a
#include <iostream>
#include "study.h"
int qwe() {
return a;
}
注:在C++中extern关键字修饰之后,重复使用include引入其处于的头文件会造成重复定义。
const对象与引用
将引用绑定到const对象上,称之为对常量的引用,该引用也不能用作修改它所绑定的对象的值。
const int i = 20; // 声明一个常量
i = 21; // 错误:i不能修改
const int& j = i;
j = 21; // 错误:j为对常量的引用,不能修改
int m = 40;
const int& n = m; // 允许为一个常量引用绑定非常量对象
n = 41; // 错误:常量引用不能修改值
m = 41; // 正确:非常量可以进行修改,此时n的值也变为41
在const对象的引用中有一个特殊的情况,引用可能无法绑定到预想的对象。在普通引用中引用的类型和目标对象的类型必须一致,但在常量引用中,不同的类型之间进行绑定可能会导致绑定在一个临时量上。
double q = 3.14;
const int& p = q;
// 上两行代码在编译器中变成如下形式
// double q = 3.14;
// const int temp = q;
// const int& p = temp; // 因此p其实绑定的是一个临时量
std::cout << p << std::endl; // 值为3,因为进行类型转换,舍弃了小数点后的值
q = 5.5;
std::cout << p << std::endl; // 值依然为3,因为绑定的变量不是q
注:尽量避免临时量的形式,因为其脱离了设计引用的初衷。
const对象与指针
指向常量的指针(底层const)
指向常量的指针不能用于改变其所指对象的值,但是指针指向的对象可以换。此处为底层const,一般是与指针和引用等复合类型部分有关。
const double m1 = 3.14;
double *n1 = &m1; // 错误:n1是一个普通指针
const double *n2 = &m1; // 正确:n2是一个指向常量的指针
*n2 = 42; // 错误:指向常量的指针,其不能修改指向的对象的值
const double m2 = 6.28;
n2 = &m2; // 正确:指向常量的指针,可以修改其指向的对象。
注:指向常量的指针必须也加const进行修饰。(二者以下区别处也有说明)
常量指针(顶层const)
常量指针必须初始化,初始化后,其指向的对象就不能再改变。此处为顶层const,顶层const表示任意对象是常量,一般是与数据类型相关,如算术类型、类、指针等。
int n =0;
int *const m = &n; // 指针m将一直指向n。const为顶层const
const double n1 = 3.14; // const为顶层const
const double *const m1 = &n1; // 指针m1将一直指向n1,同时n1的值也不能改变。从左往右依次是底层const、顶层const
顶层const与底层const在拷贝操作时的区别
底层const相比于顶层const来说在拷入拷出时必须具有相同的底层const资格,或者两个对象的数据类型能够转换。
int m = 0;
int* const m1 = &m; // 顶层const,常量指针
const int* m2 = &m; // 底层const,指向常量的指针
int* n1 = m1; // 正确:顶层const指针不影响
int* n2 = m2; // 错误:底层const指针需要具有相同的底层const资格,或者两个对象数据类型能够转换
const int* n3 = m2; // 正确:n3与m2具有相同的底层const资格
const int n3 = m; // 正确:n3与m两个对象数据类型能够转换,也就是非常量能转换为常量
顶层const与底层const对于函数传参的影响
同上面顶层const与底层const在一般情况下一样遵循那些原则。
void fcn(const int i){ /* 函数体 */} // 实参初始化形参时会忽略掉顶层const,传入常量对象或者非常量对象都是可以的,但是在函数体内部,i是不能被修改的
int i = 0;
const int ci = i;
string::size_type ctr = 0;
reset(&i); // 调用形参类型是int*的reset函数
reset(&ci); // 错误:不能用指向const int对象的指针初始化int*
reset(i); // 调用形参类型是int&的reset函数
reset(ci); // 错误:不能把普通引用绑定到const对象ci上
reset(42); // 错误:不能把普通引用绑定到字面值上
reset(ctr); // 错误:类型不匹配
// 如果不需要修改引用的值,尽量设为常量引用,如下:如果不是常量引用的话,会容易出错
string q(const string& s) {
return s;
}
int main()
{
cout<<q("213");
}
constexpr和常量表达式
常量表达式是指值不会改变并且在编译过程中就能得到计算结果。在复杂系统中,很难分辨一个初始值是不是常量表达式,通过将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。
constexpr int mf =20; // 20是常量表达式
constexpr int limit = mf + 1; // mf + 1是常量表达式
constexpr int sz = size(); // 当size是一个constexpr函数时才正确
一般来说,如果认为一个变量是一个常量表达式,就声明为constexpr类型。
注:用constexpr声明的指针对象置为顶层const。
const int *p = nullptr; // p是一个指向常量的指针
constexpr int *q = nullptr; // q是一个常量指针
类型别名中的常量
const加在别名指针前还是对别进行修饰,如下代码,a是一个指向char的指针,在两个声明语句中const修饰的都是a,也就是修饰指针,所以是常量指针。
typedef char *a;
const a b =0; // b是指向char的常量指针
const a *c; // c是指针,指向的对象是一个指向char的常量指针
常量与auto
auto一般会忽略掉顶层const,底层const会保留下来。
const int i = 50;
auto j = i; // 忽略掉顶层const,自动推演出的类型为int
const auto m = i; // 此处得到的类型为const int
const int *n =&i;
auto a = n; // 此处得到的是const int
常量与decltype
decltype使用的表达式是一个变量,则返回该变量的类型(包括const和引用在内)。
const int i = 0, &j = i;
decltype(i) x = 0; // x的类型是const int
decltype(j) y = x; // y的类型是const int&