const关键字
const即constan的缩写,即不变的,被const修饰之后,相当于程序员告诉编译器:这个值是不变的,你处理的时候注意着点儿。const关键字的核心是ReadOnly,即“只读”。
const可以修饰内置类型变量、自定义的类对象、类的成员函数、函数的返回值、函数的参数。
const修饰普通变量
|
|
编译器会把a认定为常量,其值不可被改变,所以对它赋值是错误的。
const修饰指针变量
const修饰指针变量有三种情况:
- const修饰指针指向的内容,即指针所指地址中的内容不可变。
- const修饰指针本身,即指针所指向的地址不可变。
- const同时修饰指针和指针指向的内容,即二者皆不可变。
|
|
由内向外看,*表示p是一个指针;const int表示p指针指向一个int型常量,综合起来就是p是一个常量指针。即p指针指向的这个地址存的是个8,这个值不能变,但是p可以指向其他的地址。
|
|
由内而外看,const表示p是一个常量,这已经说明它自身的值是不变的;int*表示p是一个指向int型变量的指针;所以,综合起来,p是一个指向int型变量的指针常量。即p指向的地址是不可变的,但这段地址存放的内容是可以变的。
|
|
依旧由内而外看,const表示p是一个常量;const int*表示p是一个指向int型常量的指针;综合起来,p是一个指向int型常量的指针常量。即p指向的地址以及它指向的地址中存放的内容均为不可变的。
总结:
*
左边的const表示内容不变;*
右边的const表示地址不变。常量指针:指向常量的指针,即其指向的地址中的内容不变。
指针常量:指针本身是常量,即其指向地址不变。
const修饰函数参数
const修饰函数参数也分三种情况:
-
对于值传递的函数,一般这种情况不需要 const 修饰,因为函数会自动产生临时变量复制实参值。
1 2 3 4 5 6 7 8 9
include <iostream> void ValueTransfer(const int a){ ++a; std::cout << a << std::endl; } int main(){ ValueTransfer(8); return 0; }
上边这段程序用g++编译时报错:
1 2 3
const_test.cpp: In function ‘void ValueTransfer(int)’: const_test.cpp:3:4: error: increment of read-only parameter ‘a’ ++a;
对只读的参数a进行自增操作是非法的,编译直接不通过。
-
当 const 参数为指针时,可以防止指针被意外篡改。
1 2 3 4 5 6 7 8 9 10 11
#include<iostream> void Test(int *const a){ std::cout << *a << std::endl; //a为8 *a = 9; } int main(void){ int a = 8; Test(&a); std::cout << a << std::endl; // a为9 return 0; }
-
自定义类型的参数传递,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们采取 const 外加引用传递的方法。
按值传递对象需要复制所有对象成员的副本,这可能会减慢程序的执行时间,如果对象有很多成员,则更是如此。另一方面,当按引用传递对象时,由于该函数可以访问原始对象,而不必进行任何复制,所以它比通过值传递更快,正因为如此,一般更愿意按引用传递对象。
但是,按引用传递对象有一个缺点,因为该函数可以访问原始对象,所以它可以调用其成员函数更改对象成员数据。这就是为什么当程序员想要保护对象的内容时,通常不会按引用传递变量。
幸运的是这个问题有解决办法。为了保护对象让它作为实参传递,而又不必复制副本,可以将它作为常量引用进行传递,这意味着原始对象作为引用被传递给了函数,但是它不能调用没有const修饰的成员函数或更改对象的成员数据,它只能调用自己被指定为const函数的成员函数。
const修饰函数返回值
const修饰函数返回值也分三种情况:
- const 修饰内置类型的返回值,修饰与不修饰返回值没什么区别。
- const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。
- const 修饰返回的指针或者引用,是否返回一个指向 const 的指针,取决于我们想让用户干什么。
const修饰类的成员函数
const 修饰类成员函数,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为 const 成员函数。
const 关键字不能与 static 关键字同时使用,因为 static 关键字修饰静态成员函数,静态成员函数不含有 this 指针,即不能实例化,const 成员函数必须具体到某一实例。
|
|
如果 get_cm() 去掉 const 修饰,g++编译时提示以下错误:
|
|
Cmf 传递的 const _tt 即使没有改变对象的值,编译器也认为函数会改变对象的值,进而编译不通过。所以我们尽量按照要求将所有的不需要改变对象内容的函数都作为 const 成员函数。
如果有个成员函数想修改对象中的某一个成员怎么办?这时我们可以使用 mutable 关键字修饰这个成员,mutable 的意思也是易变的,容易改变的意思,被 mutable 关键字修饰的成员可以处于不断变化中。
const修饰函数形参
值传递pass by value
值传递时,被调函数只是获得了来自主调函数的实参的一个副本,相当于把实参值赋给形参,这个形参就变成了被调函数里的一个局部变量,与主调函数之后再无联系,不管你被调函数里怎么折腾这个局部变量,都不会影响到主调函数内的实参。
这种情况,函数形参用不用const修饰其实区别不大,因为值传递本来就不会改变实参本身。
如果函数内部不会有改变形参值的操作,也可以加const,但这只是强调这个函数内部不会改变形参的值,同时也确保形参值不会被意外改变。
如果函数是需要改变形参值的,那自然是不能加const了,加了无法通过编译。
指针传递pass by pointer
|
|
指针传递本质仍旧是值传递,无非传递的值是地址而已。
就像上边这个例子,取a的地址作为实参传递给fun函数,其形参p也就指向了a的地址。无论你在fun内部对p进行什么操作,你都无法改变实参a的地址。但是fun函数可以通过获得的a的地址来改变a的内容,如上,a的值会变成20。
这种情况,如果在*右边加一个const,那p就是指针常量,在fun函数内就不能有改变p指向的操作,同时也可以防止指针被意外改变;如果在*左边加一个const,那p就是指向常量的指针,也就无法在fun内通过解引用p来改变a的值了。
引用传递pass by reference
|
|
引用传递其实是给实参起了一个别名,在被调函数内部可以通过操作这个别名来改变实参。
引用本身不是一个对象,引用在定义之后也没办法改变它绑定的对象,而const是修饰对象的,所以对于引用而言,const只能出现在&左边,而不能出现在右边。
const int &a;表示a是一个整型常量的别名。
这种内置类型的形参,加不加const区别不大,如果函数内不会有改变形参值的操作,可以加const,但加了也只是强调函数内部不会改变实参的值,同时也保证实参值不被意外改变。
但对于类类型的形参而言,按值传递对象需要复制所有对象成员的副本,这可能会减慢程序的执行时间,如果对象有很多成员,则更是如此。另一方面,当按引用传递对象时,由于该函数可以访问原始对象,而不必进行任何复制,所以它比通过值传递更快,正因为如此,一般更愿意按引用传递对象。
但是,按引用传递对象有一个缺点,因为该函数可以访问原始对象,所以它可以调用其成员函数更改对象成员数据。这就是为什么当程序员想要保护对象的内容时,通常不会按引用传递变量。
幸运的是这个问题有解决办法。为了保护对象让它作为实参传递,而又不必复制副本,可以将它作为常量引用进行传递,这意味着原始对象作为引用被传递给了函数,但是它不能调用没有const修饰的成员函数或更改对象的成员数据,它只能调用自己被指定为const函数的成员函数。