const修饰的数据类型是常量类型,常量类型的对象和变量在定义初始化后是不能被更新的。其实只用记住这一个概念,就可以明白const操作对象的方法。
1)定义const常量
最简单的:
const int a = 1;
a = 2; (错误:a为const常量,在初始化后不能再进行改变)
而且由于这个性质,如果在定义const int a时未进行初始化,会产生编译错误。因为在程序过程中不能对const常量进行修改。而由于const限定符的只读属性,可以避免对程序数据的误操作。
2)const与define
结论:const常量能节省空间,从而避免不必要的内存分配。
阐述原因:
#define A 5
const int B = 5;
int a = A;(在编译期间进行宏替换,为A分配内存)
int b = B;(为B分配内存,初次分配后不会再分配内存)
int aa = A;(A再次分配内存)
int bb = B;(B没有内存分配)
这里define给出的是一个立即数,所以define定义的常量在内存中有若干拷贝。但const常量在程序运行过程中只有一份拷贝。
编译器通常不会为普通const常量分配存储空间,而是将他们保存在符号表中,这就是使得它成为编译期间的常量,没有了存储和读内存的操作,使得效率提高。
3)指向const对象的指针
这里可以这样理解:const后面跟上int,表示const修饰的是int数据类型,即指针指向的内容是被const限定的,不能被修改。
比如:
int a = 1;
int b = 2;
const int *ptr = &a;
这里ptr是一个指向int型的const对象指针,const限定的是ptr指向的对象(int型数据),而不是ptr本身。
所以:
*ptr = 10(错误,不能直接更新ptr指向的内容)
ptr = &b(正确,因为ptr本身并不受const的限定,ptr作为指针可以指向其他内存单元)
补充一点:
普通的指针变量是能够作为左值被赋值修改的。
比如:
int *p = &a;(*p在程序运行过程中能被修改,从而影响到a中数据的改变)
所以:
一个普通指针变量作为左值时,其右值不能是一个const常量。
比如:
const int c = 3;
int *p = &c(错误。这个其实也很好理解,*p可以随意改,那c的值也会被随意改,不满足const的性质)
实际程序中,指向const的指针常常用作函数的形参。将形参定义为指向const的指针,以此来确保传递给函数的实际对象在函数中不会因为形参而被修改。
4)const指针
这个可以和3)对应理解:const后面跟上的指针变量,指针表示地址,则const指针限定了指针本身不能被更新,但指针指向的对象可以被更新(上述3)是限定数据不能更新,指针本身可以更新,正好相反)
比如:
int a = 1;
int b = 2;
int *const ptr = &a;(首先定义const指针指向a)
如果在程序运行过程中进行如下操作:
ptr = &b(错误:因为ptr是一个const指针常量,其在运行过程中不能被更新,即不能再指向其他的内存单元)
*ptr = 3;(正确:因为*ptr是指针指向的对象,其不受const的限定,能够被更新。更新后,a的值为3)
5)指向const对象的const指针
这个就是上述3)和4)的结合。const既限定了指针,又限定了指针指向的对象。
比如:
const int a = 1;
const int *const ptr = &a;
ptr一经初始化完毕后,就不能再指向其他内存单元,也不能被修改其指向内存单元(a的地址)的数据。但a自身可以被定义为普通int型,a可以进行更新。
6)关于typedef和const指针的一个误区
typedef能够用作类型替换,类似于#define。
一个误区是:
typedef string *pstring;
const pstring ptr;
上述ptr是一个什么类型。很容易想到的是:替换pstring,变为const string *ptr。则ptr是一个指向string类型的const对象,即const修饰string数据类型。
实际不然:
看const pstring ptr。在这个里面const修饰的是pstring,而pstring是一个string类型的指针。所以在程序运行过程中,const依然应该修饰一个指针,而不是一个string对象。正确的扩展应为:
strng const *ptr;
出现这个问题的原因在于:对于const常量的表示,string const s1和 const string s2,s1和s2都是等价的类型,即const限定符即可以放在类型前也可以放在类型后。