C++ Primer 阅读笔记(二)

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的值

C++ Primer 阅读笔记(二)

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; 不能把整型赋值给指针
上一篇:c++学习笔记(1)——c++ primer plus


下一篇:C++ Primer Plus 编程练习3