const 是constant 的缩写,在C++关键字中const是一个限定符,被它修饰的变量不能被改变。但是const不仅仅可以修饰变量,还可以修饰函数,参数等等。
被const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。所以很多书都建议尽可能使用const。
目录
一、const修饰变量
一般有两种写法:
const TYPE value;
TYPE const value;
意思是:const修饰的类型为TYPE的变量value是不可变的。就是把变量value定义成了一个常量,任何试图为value赋值的行为都将引发错误。
const int i = 520; // 正确:const在修饰符前
int const j = 521; // 正确:const在修饰符后
i = 522; // 错误,i已经被const修饰,不可被修改
同时,因为const对象一旦创建后其值就不能再被改变,所以const对象必须初始化,而且,初始值可以使任何复杂的表达式:
const int i = fuc(); // 正确:运行时初始化,把fuc函数返回值赋给j
const int j; // 错误:j是一个未经初始化的常量
被const修饰后的变量除了不能被改变,其所能参与的操作与非const类型相比,大部分都能完成。例如,一样能参与算数运算,一样能转换成一个布尔值,等等。
当以编译器编译一个const对象时,它是把编译过程中用到的该变量的地方都替换成对应的值。另外,在全局作用域声明的const变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。如果要访问,在声明和定义前都加上extern。
// 定义并初始化了一个常量,该常量能被其他文件访问
extern const int i = fuc();
// 另外一个文件中
extern const int i; // 与上面那个定义的i是同一个
二、const的引用
可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用。与普通的引用不同的是,对常量的引用不能被用作修改它所绑定的对象。
const int i = 520;
const int &j = i; // 正确:引用及其对应的对象都是常量
j = 521; // 错误:j是对常量的引用,不可修改其值
int &k = i; // 错误:试图让一个非常量引用指向一个常量对象
// 假设该初始化正确,则可以通过k来改变它引用的对象的值,显然不正确
我们也可以对const的引用,引用一个并非const的对象。
int i = 520;
const int &j = i; // 正确:j绑定对象i,但是不允许通过j修改i的值
j = 521; // 错误:j是一个引用常量,不能通过它修改i的值
// 但是,这时i的值是允许通过其他途径修改的,包括直接赋值或绑定到其他引用来修改
i = 521;
// 这时,我们输出i和j就可以看到,i和j的值都是改变后了的521;
我们都知道引用类型必须与其所引用对象的类型一制,但是有例外,允许为一个常量引用绑定非常量的对象,字面值,甚至是一般表达式。
double i = 3.14;
const int &r1 = i; // 允许将const int&绑定到一个普通的double对象上
const int &r2 = 4.12; // 正确
const int &r3 = r1 * 2; // 正确
int &r4 = r1 * 2; // 错误:r4是一个普通的非常量引用
想知道这种例外情况的原因,最简单的办法是弄清楚当一个常量引用被绑定到另外一种类型上时到底发生了什么。
double i = 3.14;
const int &j = i;
// 这种情况下,编译器会创建一个未命名的对象,来和j绑定
const int temp = i; // 由双精度浮点数生成一个临时的整型变量
const int &j = temp; // 让j绑定这个临时量
// 这时如果我们修改i的值
i = 4.12;
// 我们输出i和j的值,得到的结果是i = 4.12, j = 3.
所以,常量引用可以不与其引用的对象类型一致是因为实质上,j绑定的还是一个和它相同类型的对象,只不过这个相同类型的对象是编译器临时创建的对象。这也就是为什么我们修改i的值后,输出的j值还是不变,因为j绑定的并不是i,而是编译器创建的临时量。
三、指针和const
与引用一样,也可以令指针指向常量或非常量。类似于常量引用,指向常量的指针不能用于改变其所指对象的值。想要存放常量对象的地址,只能用指向常量的指针。
const double i = 3.14;
double *j = &i; //错误:j是一个普通指针
const double *j = &i; //正确:j可以指向一个双精度的常量
*j = 4.12; //错误:不能用常量指针更改所指向的对象的值
同样的,和引用一样,指针的类型也必须与其所指对象的类型一致,但是也有例外,允许令一个指向常量的指针指向一个非常量对象。
double i = 3.14; // i是一个双精度浮点数,它的值可以改变
const double *j = &i; // 正确:但是不能通过j改变i的值
// 这时修改i的值
i = 4.12;
// 得到的结果i和j的值都是4.12
const指针
C++允许把指针本身定义为常量。常量指针和常量一样,都必须初始化,而且一旦完成,它的值(也就是存放在指针中的那个地址)就不能再改变了。常量指针的声明要把*放在const关键字之前。
int i = 0;
int *const j = &i; // j将一直指向i,这是一个顶层const
const double m = 3.14;
const double *const n = &m; // n是一个指向常量对象的常量指针,第一个是底层const,第二个是顶层const
向n这样的,我们把他称为指向常量对象的常量指针,为了区别,我们把表示指针本身是个常量的const称为顶层const,而把表示指针所指对象是一个常量的const称为底层const。在n中开头的const是底层const,在*号后面的是顶层const。
四、const修饰函数参数
const也可以用来修饰函数参数,它表示在函数体中不能修改参数的值(包括参数本身的值或者参数其中包含的值):
void function(const int Var); // 传递过来的参数在函数内不可以改变(无意义,该函数以传值的方式调用)
void function(const char* Var); // 参数指针所指内容为常量不可变(有意义,var是值传递,var本身的改变不会对传递的参数造成任何影响)
void function(char* const Var); // 参数指针本身为常量不可变(无意义,var本身也是通过传值的形式赋值的)
void function(const Class& Var); // 引用参数在函数内不可以改变(有意义)
参数const通常用于参数为指针或引用的情况,若输入参数采用“值传递”方式,由于函数将自动产生临时变量用于复制该参数,该参数本就不需要保护,所以不用const修饰。const修饰输入参数:如果输入参数采用“指针传递”,那么加const 修饰可以防止意外地改动该指针,起到保护作用。如果函数体内的语句试图改动const修饰的内容,编译器将报错。
但是,const只能修饰输入参数,如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const修饰,否则该参数将失去输出功能。
五、用const 修饰函数的返回值
如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
const char *function();
char *str = function(); // 错误:const char *类型不能转换成char *
const char *str = function(); // 正确:都是const char *类型可以赋值
如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。
如不要把函数int function() 写成const int function()。
函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
class A
{
A &operator = (const A &other);
} ;
A a, b, c; // a, b, c是A类型的
a = b = c; // 链式赋值
(a = b) = c; // 非链式赋值,但是合法
如果将赋值函数的返回值加const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。
六、const 成员函数(const放在函数后面)
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
任何不会修改数据成员(即函数中的变量)的函数都应该声明为const 类型。如果在编写const 成员函数时,不慎修改了数据成员,或者调用了其它非const 成员函数,编译器将报错,这无疑会提高程序的健壮性。const成员函数中,const放在函数声明的尾部。例如,我们定义一个类A。
class A {
public:
void function(int)const; //const成员函数
};
在这里,const是修饰this指向的对象的。成员函数function其实有两个参数,第一个是A*const this, 另一个才是int类型的参数。如果我们不想function函数改变参数的值,可以把函数原型改为function(const int),但如果我们不允许function改变this指向的对象呢?因为this是隐含参数,const没法直接修饰它,就加在函数的后面了,表示this的类型是const A *const this。const修饰*this是本质,至于说“表示该成员函数不会修改类的数据。否则会编译报错”之类的说法只是一个现象,根源就是因为*this是const类型的。
在以下程序中,类stack的成员函数GetCount 仅用于计数,从逻辑上讲GetCount 应当为const 函数。如果修改成员函数中的数据成员,编译器将报错。
class Stack
{
public:
void Push(int elem);
int Pop(void);
int GetCount(void) const; // const 成员函数
private:
int m_num;
int m_data[100];
} ;
int Stack::GetCount(void) const
{
++ m_num; // 错误:企图修改数据成员m_num
Pop(); // 错误:企图调用非const函数
return m_num;
}
七、const限定符和static的区别
- const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间。
- static表示的是静态的。类的静态成员函数、静态成员变量是和类相关的,而不是和类的具体对象相关的。即使没有具体对象,也能调用类的静态成员函数和成员变量。一般类的静态函数几乎就是一个全局函数,只不过它的作用域限于包含它的文件中。
- 在C++中,static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化,如:double Account::Rate=2.25; static关键字只能用于类定义体内部的声明中,定义时不能标示为static。
- 在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。
- const数据成员,只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。
- const数据成员的初始化只能在类的构造函数的初始化列表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static const。
- const成员函数主要目的是防止成员函数修改对象的内容。即const成员函数不能修改成员变量的值,但可以访问成员变量。当方法成员函数时,该函数只能是const成员函数。
- static成员函数主要目的是作为类作用域的全局函数。不能访问类的非静态数据成员。类的静态成员函数没有this指针,这导致:1、不能直接存取类的非静态成员变量,调用非静态成员函数。2、不能被声明为virtual。