const 是 constant 的缩写,本意是不变的,不易改变的意思。在 C++ 中是用来修饰内置类型变量,自定义对象,成员函数,返回值,函数参数。
C++ const 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。如果在编程中确实有某个值保持不变,就应该明确使用const,这样可以获得编译器的帮助。
const修饰普通类型的变量
const int a = 7;
int b = a; // 正确
a = 8; // 错误,不能改变
被定义为一个常量,并且可以将 a 赋值给 b,但是不能给 a 再次赋值。对一个常量赋值是违法的事情,因为 a 被编译器认为是一个常量,其值不允许修改。
默认状态下,const对象仅在文件内有效
当以编译时初始化的方式定义一个const对象时,就如对nBufferSize的定义:
const int nBufferSize = 512;
编译器将在编译过程中把用到该变量的地方都替换成对应的值。也就是说,编译器会找到代码中所有用到nBufferSize的地方,然后用512替换。
为了执行上述替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用了const对象的文件都必须得能访问到它的初始值才行。要做到这一点,就必须在每个用到变量的文件中都有对它的定义。为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同的文件中分别定义了独立的变量。
某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。相反,我们想让这类const对象像其他对象一样工作,也就是说,只在一个文件中定义const,而在其他多个文件中声明并使用它。解决的办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了:
file_1.cc 定义并初始化了一个常量,该常量能被其他文件访问
extern const int nBufferSize = fcn();
file_1.h 头文件
extern const int nBufferSize;
const的引用
对常量的引用
可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用。与普通引用不同的是,对常的引用不能被用作修改它所绑定的对象:
const int ci = 1024;
const int &r1 = ci; //正确,引用及其对应的对象都是常量
r1 = 42; //错误:r1是对常量的引用
int &r2 = ci; //错误:试图让一个非常量引用指向一个常量对象
初始化和对const的引用
引用的类型必须与其所引用对象的类型一致,但是有两个例外。第一种例外情况就是在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值、甚至是一个一般表达式。
int i = 42;
const int &r1 = i; //允许将const int&绑定到一个普通int对象上
const int &r2 = 42; //正确,r1是一个常量引用
const int &r3 = r1 * 2; //正确,r3是一个常量引用
int &r4 = r1 * 2; //错误:r4是一个普通的非常量引用
如何理解这种例外情况呢,最简单的办法是弄清楚当一个常量引用被绑定到另外一种类型上时到底发生了什么:
double dVal = 3.14;
const int &r1 = dVal;
此处ri引用了一个int型的数。对ri的操作应该是整数运算,但是dVal却是一个双精度浮点数而非整数。因此为了确保让ri绑定一个整数,编译器把上述代码变成了如下形式:
const int temp = dVal;
const int &ri = temp;
在这种情况下,ri绑定了一个临时量对象,所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建一个未命名的对象。
const 修饰指针变量
const 修饰指针指向的内容,则内容为不可变量
const int *p = 8;
*p = 9; //错误:不能给*p赋值
int a = 9;
p = &a; //正确:但是不能通过p改变a的值
则指针指向的内容 8 不可改变。简称左定值,因为 const 位于 * 号的左边
const 修饰指针,则指针为不可变量
int a = 8;
int* const p = &a;
*p = 9; // 正确
int b = 7;
p = &b; // 错误
对于 const 指针 p 其指向的内存地址不能够被改变,但其内容可以改变。简称,右定向。因为 const 位于 * 号的右边。
const 修饰指针和指针指向的内容,则指针和指针指向的内容都为不可变量
int a = 8;
const int * const p = &a;
这时,const p 的指向的内容和指向的内存地址都已固定,不可改变。
constexpr和常量表达式
常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化const对象也是常量表达式。
一个对象(或者表达式)是不是常量表达式由它的数据类型和初始值共同决定,例如:
const int max_files = 20; //是常量表达式
const int limit = max_files + 1; //是常量表达式
int staff_size = 27; //staff_size不是常量表达式
const int sz = get_size(); //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
必须明确一点,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象武关。
const int *p = nullptr; //p是一个指向整形常量的指针
constexpr int *q = nullptr; //q是一个指向整数的常量指针
const修饰成员函数
(1)const修饰的成员函数不能修改任何的成员变量(mutable修饰的变量除外)
(2)const成员函数不能调用非onst成员函数,因为非const成员函数可以会修改成员变量
#include <iostream>
using namespace std;
class Point{
public :
Point(int _x):x(_x){}
void testConstFunction(int _x) const{
///错误,在const成员函数中,不能修改任何类成员变量
x=_x;
///错误,const成员函数不能调用非onst成员函数,因为非const成员函数可以会修改成员变量
modify_x(_x);
}
void modify_x(int _x){
x=_x;
}
int x;
};
const修饰函数返回值
(1)指针传递
如果返回const data,non-const pointer,返回值也必须赋给const data,non-const pointer。因为指针指向的数据是常量不能修改。
const int * mallocA(){ ///const data,non-const pointer
int *a=new int(2);
return a;
}
int main()
{
const int *a = mallocA();
///int *b = mallocA(); ///编译错误
return 0;
}
(2)值传递
如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。所以,对于值传递来说,加const没有太多意义。
所以:
不要把函数int GetInt(void) 写成const int GetInt(void)。
不要把函数A GetA(void) 写成const A GetA(void),其中A 为用户自定义的数据类型。