C++ Primer 笔记五 const限定符

const限定符


     const是一种类型修饰符,用于说明永不改变的对象。const对象一旦定义,就无法再赋新值,所以必须被初始化。

const int ival = get_size();	// 正确:运行时初始化
const int ival1 = 42;			// 正确:编译时初始化

// error: uninitialized const 'ival2'
const int ival2;				// ival2应该被初始化

    const对象能参与一切不改变其内容的操作:

const int cival = ival;		// 正确:ival是否为const对象都无关紧要
ival1 = cival;				// 正确:拷贝cival的值,但值本身并没有被改变

    默认情况下,const对象仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同的文件中分别定义了独立的变量。如果想只在一个文件中定义的const对象能在其他多个文件中声明并使用,则对const对象的声明和定义都添加extern关键字。

// ×××××××××××××××××××  file.h ×××××××××××××××××××  
extern const int bufSize;	// 声明const对象
extern int i;				// 声明非const对象

// ×××××××××××××××××××  file1.cc ×××××××××××××××××××  
// 定义的时候也必须加上extern加以限定使其被其他文件使用
// 否则会报错:"在main.cc:对'bufSize'未定义的引用"
extern const int bufSize = fcn();

// 非const对象则不需要在定义的时候添加extern限定
int i = 20;

// ×××××××××××××××××××  main.cc ×××××××××××××××××××  
#include <iostream>
#include "file.h"

int main() {
	i = 33;
	std::cout << bufSize << std::endl;
}

const和引用
    可以把引用绑定到const对象上,即对常量的引用,对常量的引用不能被用作修改它所绑定的对象,也不能用非const引用指向一个const引用。

const cival = 1024;
const int &ciRef = cival;	// 正确:引用及其对象都是常量
int &r2 = cival; 			// 错误:不能用一个非const引用指向一个cosnt引用
ciRef = 32;					// 错误:ciRef是对常量的引用

    引用的类型必须与其所引用对象的类型一致。但是有两个例外。第一个例外就是初始化常量引用时允许用任意表达式作为初始值,只要该表达式可以转换成引用的类型。允许为一个常量引用绑定非常量的对象、字面值,甚至是一个一般表达式。(非常量引用只能绑定在对象上,不能与字面值或是某个表达式的计算结果绑定在一起)

int ival = 42;
const int &ciRef = ival;		// 允许const int&绑定到非const的int对象上
const int &ciRef1 = 42;			// OK
const int &ciRef1 = ciRef * 2;	// OK
int &iRef = ciRef1 * 2;			// 错误,iRef是一个非const引用

// 下面的操作是被允许的
double dval = 3.14;
const int &ciRef2 = dval;		// double类型的值可以隐式转换为int,可能会丢失精度
								// 等价于 :
								// const int temp = dval;
								// const int &ciRef2 = temp;

    编译器会临时创建一个未命名的对象来暂存表达式的求值结果,这个未命名的对象也称为临时量对象。所以,上面ciRef2实际上绑定的是一个临时量而不是dval
    特别的,const引用不要求引用对象本身也是一个常量。(但是对常量的引用必须是一个const引用)

int ival = 42;
int &iRef = ival;
const int &ciRef = ival;	// 合法,只是不能通过ciRef对ival的值进行修改
							// 但是可以直接为ival赋值
							// 也可以通过iRef修改ival的值

const和指针
    可以定义指向常量的指针,该指针不能用于改变其所值对象的值。存放常量对象的地址,只能使用指向常量的指针;而常量指针可以指向常量或者非常量对象。

const double pi = 3.14;
const double *cptr = &pi;		// 正确:指针及其对象都是常量
double *ptr = &pi;				// 错误:不能用非常量指针指向常量对象

double dval = 2.7;
const double *cptr1 = &dval;	// 允许常量指针指向非常量
*cptr1 = 0.618;					// 错误:不能通过常量指针修改所指对象的值,尽管对象本身是非常量

    不同于引用,指针本身就是一个对象,因此允许指针本身是一个常量,常量指针必须初始化。

int errNum = 0;
int *const curErr = &errNum;	// 一旦初始化化,curErr会一直指向errNum

// 虽然指针本身是个常量,但是由于errNum不是常量,因此可以修改errNum的值
errNum = -1;					
curErr = 1;						// 正确:改变了所指对象的值,而不是改变指向的对象

const int ival = 33;
const int * const iptr = &ival;	// 指针本身是一个常量,其所指的对象也是一个常量

顶层const和底层const
    指针本身是不是常量,及指针所指的对象是不是常量,是两个独立的问题。
    一般地,用顶层const表示对象本身是一个常量,适用于任何数据类型如算术类型、类、指针;不属于顶层的const称为底层const,与指针和引用等复合类型的基本类型部分有关。
    指针类型既可以是顶层const也可以是底层const。

int i = 0;
int *const p1 = &i;			// 顶层const,不能改变pi本身的值
const int ci = 42;			// 顶层const,不能改变ci的值
const int *p2 = &ci;		// 底层const,可以改变p2指向的对象
const int *const p3 = p2;	// 第一个是底层const,第二个是顶层const
const int &r = ci;			// 用于声明引用的const都是底层const

    执行对象的拷贝操作时,顶层const不受什么影响,因为执行拷贝操作并不会改变拷贝对象的值。但是对于底层const,拷贝和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。非常量可以转换成常量,反之则不行。(看着很别扭,其实想想上面const和引用指针的栗子就好了)

int i = 0;
const int ci = 42;
i = ci;							// 正确:ci是顶层const,对拷贝操作无影响,拷贝ci的值

const int *ptr = &ci;	
const int *cosnt ptr1 = ptr;	// 正确:都是底层const,ptr1的顶层const不影响
int *ptr2 = ptr1;				// 错误:ptr1包含了底层const,而ptr2没有
ptr = &i;						// 正确:int可以转换为const int

const int &r = ci;				// 错误:非const不能绑定到int常量上
const int &r1 = i;				// 正确:const int&可以绑定到一个非const int上

constexpr
    常量表达式是指值不会改变,且能在编译时计算并获取结果的表达式。
    字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。

const int max_files = 20;			// max_files是常量表达式
const int limit = max_files + 1;	// limit 也是常量表达式

int staff_size = 27;				// staff_size不是常量表达式,
									// 虽然是用常量表达式初始化,但本身不是一个const对象

const int sz = get_size();			// sz不是常量表达式
									// 虽然sz本身是一个const对象,但它的具体值在运行时才能获取

    C++11新标准允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,且必须用常量表达式来初始化:

constexpr int mf = 20;			// 20是常量表达式
constexpr int limit = mf + 1;	// mf+1是常量表达式 
constexpr int sz = size();		// 只有当size()是一个constexpr函数时该声明合法

    算术类型、引用和指针都属于字面值类型,自定义类、IO库等则不属于字面值类型,也不能被定义为constexpr。
    一个constexpr指针的初始值必须是nullprt或者0或者是存储于某个固定地址中的对象。对于constexpr定义的一个指针,限定符constexpr只对指针有效(顶层const),与指针所指的对象无关。

const int *p = nullptr;		//	p是一个非const指针,指向一个const int
constexpr int *q = nullptr;	// q是一个const指针,指向一个 int

int i = 0;
const int j = 1;
constexpr int *iptr = &i;	// 可以指向一个非常量
constexpr int *jprt = &j;	// 也可以指向一个常量

========================= END
#@¥%^&*……拖延是病~o(╯□╰)o~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值