2.4 const限定符
const 修改的变量,其值无法更改
const 对象创建后其值无法更改,所以必须初始化
1 初始化和const
const 类型对象只能执行不改变其内容的操作。例,利用 const 对象去初始化另外一个对象(因为拷贝一个对象的值并不会改变它,拷贝完成的新对象和原来对象无关)
int i = 42;
const int ci = i; //正确:i的值被拷贝给了 ci
int j = ci; //正确:ci的值被拷贝给了 j
拷贝一个对象的值并不会改变它,一旦拷贝完成,新的对象就和原来的对象没什么关 系了。
2 const 对象默认仅在文件内有效
初始化方式定义一个 const 对象时,编译器将在编译过程中把用到该变量的地方都替换成对应值
为了多文件定义 const 变量,且避免对同一变量重复定义。const 对象默认仅在文件内有效。程序多个文件中同名的 const变量,等于多个文件分別定义了独立变量
多个文件之间共享 const 对象,须在变量定义前添加 extern
// file_l.cc 定义并初始化了一个常量,该常量能被其它文件访问
extern const int bufSize = fen();
// file_l.h 与file_l. cc中定义的 bufSize是同一个
extern const int bufSize;
file_l.cc 定义并初始化了 bufSize,因为其是常量,需用 extern 修饰使其被其他文件使用
file_l.h 中声明 也有extern 修饰(限定),作用是指明 bufSize 并非本文件所定义,它的定义在别处
2.4节练习
【练习2.26】
下面哪些句子是合法的?如果有不合法的句子,请说明为什么?
(a) const int buf; // 不合法,需初始化
(c) const int sz = ent; // 合法 初始化时与 const 无关
(b) int ent = 0; // 合法
(d) ++cnt; ++sz; // 不合法,const限定对象其值不可修改
2.4.1 const 的引用
把引用绑定到 const 对象上,称为对常量的引用,简称常量引用
与普通引用不同,常量引用不能修改它绑定的对象
const int ci = 1024;
const int &rl = ci; //正确:引用及其对应的对象都是常量
rl = 42; //错误:rl是对常量的引用
int &r2 = ci; //错误:让非常量引用指向常量对象
非常量引用无法指向常量对象,因初始化合法,便可通过其来改变它引用对象值,这显然不正确
1 初始化和对 const 的引用
除了两个例外,引用类型与引用对象类型一致
第一种情况:初始化常量引用时,允许用任意表达式作为初始值,只要该表达式结果能转换成引用类型即可
int i = 42;
//将int常量引用绑定到普通int对象上
const int &rl = i;
const int &r2 = 42; //正确:rl是一个常量引用
const int &r3 = rl * 2; //正确:r3是一个常量引用
int &r4 = rl * 2; //错误:r4是一个普通的非常量引用
【理解例外情况】
当一个常量引用被绑定到另外一种类型上时
double dval = 3.14;
const int &ri = dval;
为了确保让常量整型引用 ri 绑定一个整数,编译器会把上述代码替换成如下形式
//由双精度浮点数生成一个临时的整型
const int temp = dval;
//让ri绑定这个临时量
const int &ri = temp;
因此 ri 绑定了一个临时量对象。当 ri 不是常量时,允许对 ri 赋值,则会改变ri所引用对象的值,这种情况显然不合理
临时量对象:当编译器需要一个空间来暂存表达式的求值结果时,临时创建一个未命名对象,临时量对象简称为临时量
2 对 const 的引用可能引用一个非const 的对象
常量引用对引用操作做了限定,对引用对象是不是常量未限定
其对象可能是非常量,也允许通过其它途径改变其值
int i = 42;
int &rl = i; //引用ri绑定对象i
const int &r2 = i; //不允许通过r2修改i的值
rl = 0; // rl并非常量,i的值修改为0
r2 = 0; // 错误:r2是一个常量引用
2.4.2 指针和 const
与引用一样,指针可指向常量或非常量。和常量引用类似,指向常量的指针,不能改变所指对象的值。
要想存放常量对象的地址,只能使用指向常量的指针:
const double pi = 3.14; //pi是个常量,它的值不能改变
double *ptr = π //错误:ptr是一个普通指针
const double *cptr = π //正确:cptr可指向常量
*cptr = 42; //错误:不能给*cptr賦值
除了两个例外,指针类型必须与其所指对象类型一致
第一种例外:指向常量的指针指向一个非常量对象
指向常量的指针仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其它途径改变
double dval = 3.14; //dval是一个双精度浮点数,值可改变
cptr = &dval; //正确:但不能通过cptr改变dval的值
1 const 指针
指针是对象而引用不是,类似于其它对象类型,可以把指针本身定为常量,称为常量指针。常量指针必须初始化,且初始化后,其值(存放在指针中地址)不能再改变
星号放在 const 关键字前,说明指针是一个常量。即不变的是指针值,而非指向值
int errNumb = 0;
int *const curErr = errNumb; // curErr将一直指向errNumb
const double pi = 3.14159;
const double * const pip = π // pip是一个指向常量对象的常量指针
判断声明含义有效办法是从右向左读,此例中离 curErr 最近符号是 const , 说明其一个常量对 象,对象类型由声明符其余部分决定
声明符剩余符号是 *,说明其是一个常量指针。最后,声明语句的基本数据类型部分说明常量指针指向一个 int对象
常量指针,不能改变存储的地址值
指向常量的指针,不能改变所指对象的值
指向常量的常量指针,对象值和存储的地址值都不能改变
2.4.2节练习
【练习2.27】
下面的哪些初始化是合法的?请说明原因。
(a) int i = -1, &r = 0; //不合法,引用的类型是对象,0是整型字面值
(b) int * const p2 = &i2; //合法 常量指针,无法修改指针值
(c) const int i = -1, &r = 0; //合法,初始化常量引用,其能转换成引用类型即可
(d) const int * const p3 = &i2; //合法,指向const int 常量的常量指针
(e) const int *pl = &i2; //合法,指向const int常量的指针
(f) const int &const r2; //不合法,引用必须初始化
(g) const int i2 = i, &r = i; //合法
【练习2.28】
说明下面的这些定义是什么意思,挑出其中不合法的
(a) int i, * const cp; //不合法 int整型,常量指针必须初始化
(b) int *pl, *const p2; //不合法 指针指向int整型对象
(c) const int ic, &r = ic; //合法 常量整型,常量引用
(d) const int *const p3; //不合法 指向常量整型的常量指针必须初始化
(e) const int *p; //合法 指向常量的指针
【习2.29】
假设己有上一个练习中定义变量,则下面哪些语句合法?请说明原因
(a) i = ic; //合法 把常量整型赋值给整型
(b) pl = p3; //合法 指向常量整型的常量指针赋值给指针
(c) pl = ⁣ //不合法 必须是指向常量指针
(d) p3 = ⁣ //合法 指向常量整型的常量指针
(e) p2 = pl; //合法 用指针给常量指针赋值
(f) ic = *p3; //合法
2.4.3 顶层 const
指针本身是不是常量,和指针所指对象是不是常量是两个独立的问题。用名词顶层 const 表示指针是个常量,而用名词底层 const 表示所指对象是常量
顶层 const 可以表示任意对象(任何数据类型都适用)是常量, 如算术类型、类、指针等。底层 const 则与指针、引用等复合类型的基本类型部分有关,指针类型比较特殊,既可以是顶层 const 也可以是底层 const
int i = 0;
int * const pl = &i; // 不能改变pl的值,这是一个顶层
const const int ci = 42; // 不能改变ci的值,这是一个顶层
const const int *p2 = &ci; // 允许改变p2的值,这是一个底层const
const int *const p3 = p2; // 既是顶层又是底层 const
const int &r = ci; // 用于声明引用的const都是底层const
当执行对象的拷贝操作时,顶层和底层 const 区别
顶层const不受影响,因为拷贝操作并不会改变被拷贝对象值
底层 const :拷入拷出对象必须具有相同底层 const 资格,或两对象数据类型必须能转换
int *p = p3; // 错误:p3包含底层const定义,而p没有
p2 = p3; // 正确:p2和p3都是底层const
p2 = &i; // 正确:int*能转换成 const int*
int &r = ci; // 错误:普通的int&不能绑定到int常量上
const int &r2 = i; // 正确:const int&可以绑定到一个普通int上
总结:非常量可以转换成常量,反之不可
2.4.3节练习
【练习2.30】
对于下面的这些语句,请说明对象被声明成了顶层const还是底层const?
const int v2 = 0; // 底层
int vl = v2; // 非 const
int *pl = &vl, &rl = vl; // 非const
const int *p2 = &v2, *const p3 = &i, &r2 = v2; // 底层,顶底层,顶层
【练习2.31】
假设己有上一个练习中所做的那些声明,则下面的哪些语句是合法的?
请说明顶层const和底层const在每个例子中有何体现
rl = v2; // 不合法 赋值对象是底层const
pl = p2; // 不合法 常量不可转化为非常量
p2 = pl; // 合法 非常量可以转化为常量
pl = p3; // 不合法 常量不可转化为非常量
p2 = p3; // 合法
2.4.4 constexpr 和常量表达式
常量表达式是指值不会改变,且在编译过程就能得到计算结果的表达式,用常量表达式初始化的 const 对象也是常量表达式
一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定
const int max_files = 20; // max_files 是常量表达式
const int limit = max_files + 1; // limit 是常量表达式
int staff_size = 27; // staff_size 不是常量表达式
const int sz = get_size(); // sz 不是常量表达式
staff_size 的初始值是字面值常量,但它的数据类型是 int
sz 是常量,但其值运行时才能获取,也不是常量表达式
constexpr 变量
复杂程序中,分辨一个初始值是不是常量表达式是困难的。C++11 新标准规定,允许将变量声明为 constexpr 类型,以便编译器验证变量值是否是常量表达式
声明为 constexpr 的变量一定是常量,且必须用常量表达式初始化
constexpr int mf = 20; // 20 是常量表达式
constexpr int limit = mf + 1; // mf + 1 是常量表达式
constexpr int sz = size(); // 当 size 是一个 constexpr 函数时
//才是一条正确的声明语句
虽然不能使用普通函数作为 constexpr 变量初始值,但新标准允许定义一种 constexpr 函数。使编译时就可以计算结果,这样就能用函数去初始化 constexpr 变量了
建议:如果变量是常量表达式,那就把它声明成 constexpr 类型
字面值类型
常量表达式的值需要在编译时就得到计算,因此对声明 constexpr 时的类型有所限制
因为这些类型比较简单,值显而易见,就把它们称为 字面值类型 。算术类型、引用和指针都属于字面值类型,而自定义 类、I O 库、string 类型则不属于字面值类型
一个 constexpr 指针初始值必须是 nullptr 或 0 ,或是存储于某个固定地址中的对象
函数体内定义的变量一般不存放在固定地址中,而定义于函数体外的对象其地址不变
在 6.1.1 节还将提到,允许函数定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样也有固定地址
指针和 constexpr
在 constexpr 声明中如果定义了一个指针,限定符 constexpr 仅对指针有效,与指针所指的对象无关
const int *p = nullptr; // p 是一个指向整型常量的指针
constexpr int *q = nullptr; // q 是一个指向整数的常量指针
2.4.4节练习
【练习2.32】
下面的代码是否合法?如果非法,请设法将其修改正确
int null = 0, *p = null;
不合法
int x = 0; null是关键字
constexpr int *p = nullptr; 不能把整型赋值给指针
的变量一样也有固定地址
指针和 constexpr
在 constexpr 声明中如果定义了一个指针,限定符 constexpr 仅对指针有效,与指针所指的对象无关
const int *p = nullptr; // p 是一个指向整型常量的指针
constexpr int *q = nullptr; // q 是一个指向整数的常量指针
2.4.4节练习
【练习2.32】
下面的代码是否合法?如果非法,请设法将其修改正确
int null = 0, *p = null;
不合法
int x = 0; null是关键字
constexpr int *p = nullptr; 不能把整型赋值给指针