const
是C++提供的一个强大的关键字,const
的用法很是多,但总的来讲,const
的做用只有一个:保证被修饰的内容不会被程序所修改。安全
对一个类型的对象使用const
修饰即限定这个对象是只读的,不能进行修改操做,因为没法进行修改操做,这也就要求咱们在声明const
对象时必须同时赋值或初始化。const对象的初始化通常是以下形式:函数
const TypeName Var = Expression;
示例:this
const int a = 0; int const a = 0; // 等价写法 a = 1; // 编译报错
能够注意到的是,const对象的初始化是用表达式初始化的,只是在咱们的示例中使用的是常量表达式。事实上,const初始化能够是以下形式:指针
int getA() { return 0; } const int a = 0; // 字面量0是常量表达式,在编译期就能肯定,a在编译期完成初始化 const int b = getA(); // getA()在编译期编译,b在运行时初始化
一个特殊状况是,指望这个const
对象存在于全局做用域,那么能够在声明时加上extern
修饰,那么就能够在声明时不赋初值,但必须确保程序至少有一处声明赋初值。code
// A.cpp extern const int a; // B.cpp extern const int a = 0;
const引用是对const对象的引用,也就是说,它只是确保不去修改引用的内容,与引用的对象是不是const
对象无关。对象
int a = 0; const int b = 0; const int &c = a; // 正确,能够直接修改a的值,但不能经过c修改a const int &d = b; // 正确,b、d均不可修改
须要注意的是,把const引用绑定到临时对象上是非法的。作用域
int a = 0; const int &b = a + 1; // 这里a+1生成了一个临时对象 //等价于 const int temp1 = a + 1; const int &b = temp1; const double &c = a; // 这里经过隐式类型转换生成了临时对象 // 等价于 const double temp = a; const double &c = temp;
虽然能够经过编译,但这是无心义的引用绑定。get
const也能够修饰指针。因为多级指针的存在,const的结合也就变得复杂起来。io
int a = 0; const int b = 0; const int *c = &a; // 合法,c是一个指向常量的指针,尽管a自己不是常量 // 等价于 int const *c = &a; const int *d = &b; // 合法,b是常量,c是指向常量的指针 int *e = &b; // 非法,b是常量,普通指针没法指向常量地址 const int *f = &b; // 合法,b是常量,f是指向常量的指针
最多见的是常量指针和指向常量的指针,前者表示指针是一个常量,即指向的地址不能修改,后者表示指向的地址所存储的内容是常量。常量指针和指向常量的指针是用const
修饰的一级指针的两种状况,在《C++ Primer》一书中,二者分别称为顶层const和底层const。编译
const int a = 0; const int *b = &a; // b是指向常量的指针,底层const int c = 0; int * const d = &c; // d是一个常量指针,顶层const const int * const e = &a; // e是一个指向常量的常量指针
在涉及到多级指针时,能够从右往左阅读声明表达式,确认const
修饰的是哪一级。
int a = 0; int *b = &a; // b是一个一级指针 int **c = &b; // c是一个二级指针 int **const *d = &c; // d是一个三级指针 /* 从右往左阅读表达式: 1.首先声明了一个变量d 2.下一个是*,说明d是一个指针,它指向了一个对象 3.接着是const,说明它指向的这个对象不能修改 4.接着又是一个*,说明指向的对象也是一个指针 5.而后是最后的*,说明指向的指针指向的对象还是一个指针 6.最后是int,说明最后一级指针指向的是一个int类型的地址 在理解这个声明以后很容易就能够对下面的赋值作判断 */ d = &c; // 正确,d是一个普通指针 *d = &b; // 错误,解引用d获得的是一个常量对象 **d = &a; // 正确,二次解引用d获得的是一个普通指针
前面提到,能够用常量表达式或者很是量表达式初始化const对象。所谓的常量表达式是指在编译期就能够获得结果的表达式,由常量表达式初始化的const对象也能够参与组成常量表达式。在某些时候,咱们但愿一个表达式能在编译期就获得肯定,但在复杂的项目中确认一个对象是否是常量表达式很是困难,由此C++引入了constexpr
关键字,用于显式说明某个对象是常量表达式。
constexpr int a = 0; // 正确,用字面量0初始化常量表达式 constexpr int b = getB(); // 正确与否取决于getB()是不是常量表达式
因为须要在编译期就肯定constexpr对象的值,这也就对指针和引用的constexpr初始化提出了更严格的要求:通常状况下,定义在函数内的对象地址没法在编译期肯定,所以没法做为初始化常量表达式的值,相反,全局对象能够。
const
能够修饰函数的形参。
int LiF(const int lif); // 正确,在函数内部不能修改lif // 固然,形参自己只是一个拷贝,在函数调用过程当中发生的修改并不会反馈到实参 int LiF(const int *lif); // 正确,保护原数据不被修改 int LiF(const int &lif); // 正确,这是最经常使用的写法,兼具效率与安全性
const
还能够修饰函数的返回值。const
确保函数的返回值不会被修改,即没法用做左值。
const int& LiF(int &lif) { return lif; }
const
能够修饰类的成员,因为调用构造函数时就已经确认了对象的内容,也就是说,const成员须要在构造函数以前初始化,那么,被修饰的类成员只能经过初始化列表初始化。
class LiF { public: LiF(int _lif): lif(_lif) {} private: const int lif; };
const能够修饰类的成员函数,被修饰的成员函数称为常成员函数,常成员函数能够被全部对象调用,但常对象只能调用常成员函数。这是由于,成员函数的参数列表里隐式传递了一个this
指针,用const
修饰成员函数,其实是修饰this
,而const *
是没办法转换成普通指针类型的,故不能调用普通成员函数。又因为函数重载不会忽略掉底层const,故根据成员函数的const
也能够构成重载。很是对象会经过精确匹配找到普通成员函数,而常对象则会匹配到对应的常成员函数。
class LiF { public: int get() { return lif; } int get() const { return lif; } // 常成员函数重载 private: int lif; }; LiF l1; const LiF l2; l1.get(); // 调用的是int get(); l2.get(); // 调用的是int get() const;
有时咱们但愿类的成员能记录某些信息,即使是在const对象内。这时就须要一个永远可变的成员,对应地,C++提供了mutable
关键字。
class LiF { public: void count() const { lif++; } private: mutable int lif; }; LiF l1; const LiF l2 = l1; l2.count();
const
还能够修饰成员函数的返回值,与普通函数的const返回值相似,以禁止链式调用,或者说禁止返回值成为左值。
class LiF { public: const LiF& operator= (const LiF &l) { lif = l.lif; return *this; } const LiF& set(int _lif) { lif = _lif; return *this; } private: int lif; }; LiF l1, l2, l3; l1 = l2 = l3; // 合法 (l1 = l2) = l3; // 非法,重载后的赋值运算符返回值是常量,不能再次赋值 LiF l4; l4.set(1); // 合法 l4.set(1).set(2); // 非法,set(1)以后返回的是常量this