const 是constant的缩写,“恒定不变”的意思。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。所以很多C++程序设计书籍建议:“Use const whenever you need”。
const对象必须初始化,const对象一旦创建后其值就不能再改变;
const int k; //错误:K是一个未经初始化的常量
可以编译时初始化:
const int i = 42;
也可以运行时初始化:
const int j = get_size();//用来初始化const对象的无需在意是不是一个常量
当一个常量初始化方式属于编译时初始化,则编译器将在编译过程中把代码中所有用到该变量的地方都替换成其对应的值。如:
const int bufSize = 1024; //编译器会找到所有用到bufSize的地方,然后用1024替换。
指向常量对象的引用只能是“常量引用”,即:
const int i = 100;
int &j = j;//错误
const int &k = i;//正确
反过来,常量引用可以绑定到非常量对象上:
int i = 100;
const int &j = i;
i = 200;//此时j=200;所以通俗一点const实际上更像是一个“只读变量”,而非“不可改变的常量”,只是不能通过自身去修改。
同样的,const变量也可以指向非const变量:
int i = 100;
const int &j = i; //可以通过i修改变量的值,但是不能通过j修改。
指针常量:
指向常量的指针,同样被指向的可以是const变量也可以是非const变量,但是不能用于改变其所指对象的值:double pi = 3.14; const double pj = 8.88; const double *ptr = π //指向非const变量 pi = 2.12; //正确 *ptr = 1.11; //编译器报错 error: read-only variable is not assignable ptr = &pj; //正确 const限定的是整个(*ptr),即不能通过*ptr修改pi的值,但是ptr指向的指针变量(存放在指针中的地址)可以更改
const指针(常量指针):
常量指针(const point)也必须初始化,且一经初始化后它的值(也就是存放在指针中的那个地址)就不能再改变。
把*放在const关键字之前用以说明指针是一个常量,这样的书写形式隐含着一层意味,即不变的是指针本身而非指向的值。int errNumb = 1; int errNumb2 = 2; int *const curErr = &errNumb; //curErr将一直指向errNumb; curErr = &errNumb2; //错误 *curErr = 100; //正确,可以通过*curErr修改errNumb的值
this指针就是一个典型的const指针,可以改变this指向的值,但是不能改变this保存的地址。
如何区分指针常量和常量指针:
看const在*号的左边还是右边!
const在*号的左边,是常量指针;int i = 100; const int *j = &i; //const在*的左边,是指针常量 int const *k = &i; //这上面写法等价,const都在*的左边,都是指针常量 int *const m = &i; //const在*号的的右边,是常量指针。
简记为“左定值,右定向”
顶层const与底层const
顶层const:指针本身是一个常量
底层const:指针所值的对象是一个常量int i = 1; int *const p1 = &i; //不能改变p1的值,是顶层const const int *p2 = &i; //允许改变p2的值,不允许通过(*p2)改变所指对象的值,是底层const const int j = 1; const int *const p3 = &j; //不允许改变p3的值,也不允许改变(*p3) ,左边const是底层const,右边是顶层const
常量表达式
值不会改变,并且在编译器就能得到计算结果的表达式。const int i = 0; //i是常量表达式 const int j = i+1; //j是常量表达式 int k = 100; //k不是常量表达式 const int sz = get_size();//sz运行时初始化,不是常量表达式
constexpr变量
声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。
constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。
const int *p = nullptr; //底层const,p是一个指向整型变量的指针
constexpr int *p = nullptr; //顶层const,p是一个指向整型的常量指针
const放在函数前
const在函数前是修饰函数返回值。
若函数返回类型采用值传递方式,即不是指针也不是引用,此时返回值会复制到外部的临时对象中,加const修饰没有任何意义。如:
const int fun(int a){ return a+1; } //编译不会报错,但会出现警告:返回类型上的“const”类型限定符无效
所以const一般用来修饰指针或引用类型的返回值:
以指针传递的方式的函数返回值加const修饰,返回值(即指针)的内容不能被修改,且返回值只能返回给加const修饰的同类型指针,如:
const char* GetString(void);
char *str = GetString(); //错误
const char* str = GetString(); //正确
返回值为引用:(注意不能返回本地临时对象的引用,否则编译不会报错但是运行会出错。)
(1)首先明确一点,不能返回局部变量的引用,虽然编译没报错(有警告),但是运行会出错int& fun(int &a){ return a; } //正确 a不是局部变量 int& fun(int a){ return a; } //编译警告:返回对与参数“a”关联的堆栈内存的引用 运行时出错 int& fun(int &a){ return a++; }//编译警告:正在返回对本地临时对象的引用 运行时出错,a++返回的是一个右值 int& fun(int &a){ int i = a; return i; } //编译警告:返回对与局部变量“i”关联的堆栈内存的引用 运行时出错 int b = 100; //函数外定义的全局变量 int& fun(void){ return b;} //正确,a是全局变量,返回值不会产生临时变量
用const修饰引用类型的返回值:
int a = 100;//全局变量 const int& fun(int &num){ return num; } int main() { int var = 1; int i = fun(var); //正确。 i是返回值的拷贝,此后修改i的值与var无关 int &j = fun(var); //错误:将'const int'类型的值绑定到对'int'类型的引用时,会删除'const'限定符 const int &k = fun(var); //正确。此后k的值不可更改,符合返回值为const的意图 return 0; }
const成员函数:
const关键字放在类的成员函数末尾,函数内不得对任何成员变量做修改,也不得调用任何非const成员函数。
例外:mutable修饰的成员变量,可以在const成员函数里被修改
class A{
public:
int fun(int a);
};
调用fun时,函数定义只有一个参数,实际还有一个隐含的this指针作为参数传递进来:
int fun(A *const this, int a);
函数后面加上const之后:int fun(int a) const;
等于:int fun(const A *const this, int a);此时this指针指向的内容不能被修改
const成员函数的const修饰的实际上是*this,“const成员函数不能改变成员变量的值,否则编译器会报错”,其实质原因还是因为*this是const类型