起因:
读到Effective C++里面,建议把函数参数尽量定义为const,在此记录下const修饰的意义以及自己的思考,从简到难逐步介绍const。
1、最简单的用法,const修饰一个数据类型
const int i = 1;
i = 2; // error, 此时i已经被定义为常量了,不允许修改
当申明变量i的时候,首先将i定义为int类型,紧接着对i初始化赋值为1,最后再将i用const修饰,表示i=1已经无法再修改了,所以下一行的i=2无法执行。
这就意味着,使用const修饰变量的时候,往往需要也伴随着初始化,以免出现未赋初始值,或者使用了未初始化的值
2、将函数的参数定义为const
int func(const int i);
在设计一个接口的时候,往往需要考虑哪些参数是这个接口必要的内容,并且还要考虑这些参数的使用场景,对于一些恒定的参数,其实并不希望接口的实现者去修改。
3、将函数的参数定义为const &
int func(const int &i);
这和上面多加了一个&符号,代表i是引用了其他变量,
相比于按值传递,按引用传递减少了拷贝,提高效率的同时也节省拷贝带来的内存消耗。同时为了避免函数内部又对参数进行修改,所以加上了const,最终组合成为了const &类型。
4、引用与指针常量
引用在primer书里面定义为变量的一个别名,例如
//case 1
int a = 1;
int b = &a;
//case 2
a= 2;
//则此时b=2
在case 1中,申明变量a,并初始化值为1,再变量b为变量a的引用,此时a和b共享同一块内存地址,该地址存储的值为1;如果b没有申明为a的引用,只是简单的赋值,则是a对应一块独立的内存地址,b也对立一块独立的内存地址。
在case 2中,将a修改为2,则此时再去读取b的值时候,则会发现b也随着修改了,这样的原理是否和指针有点类似呢,如果想用指针实现类似的效果,可以如下
//case 3
int *a = 1;
int *b = *a;
在case 3中,申明int类型的指针a,a指向的值为1,再身边int类型的指针b,将指针a的值去初始化指针b,此时a和b都指向同一块内存地址,无论使用是修改a的值,还是修改b的值,都会同时修改另一块。
当然引用和普通指针还是区别的,引用的一个特性是无法修改引用的对象
//case 4
int c = 3;
b = &c //执行失败,引用一旦申明则无法修改
在case 4中,尝试将b从引用的对象修改为c,而这一操作是不被允许的,因为引用一旦申明就无法修改,这里相当于具备了一层const属性,如果以指针实现类似的效果化,那就需要让指针指向的地址无法修改,而指向的值则可以修改,这样的效果被称为指针常量。
通常指针常量和常量指针读起来都比较拗口和难以记忆,主要是看const修饰的范围
const int *a;
当*离a近一些时,在编译时有限组合为*a,则先构成了指针,*a代表的是根据a的地址取出a的值,此时再去用const修饰,可以const还没有生效,地址a指向的值就已经取出来了,则const只能修饰的指针的值,这样的效果被称为常量指针。
int * const a;
与之对应的是const离a近一些,在编译时有限组合为const a,此时地址a被const修饰了,表示地址a不可以改变,比如原来指向0xFFFFFF,现在则无法指向到0xAAAAAA了。然后*再和const a组合,取出地址a指向的值,这样的效果被称为指针常量。
那拿引用和指针常量做对比,可以发现他们的使用意义其实是一样的。