c++常量详解
概念
常量是存放固定且不可变值的,一旦确定初始值则在程序其它地方不可改变,所以const对象必须初始化。常量一般使用const关键字来修饰。
const对象可以大致分为三类:
1.constinta
constinta=10;
intconstb=10;
这两种格式是完全相同的。也就是说const与int哪个写前都不影响语义。有了这个概念后,我们来看这两个家伙:constint*pi与intconst*pi,它们的语义有不同吗?
你只要记住一点,int与const哪个放前哪个放后都是一样的,就好比constintn;与intconstn;一样。也就是说,它们是相同的。
2.constint*p
前面已经说了constint*p与intconst*p是完全一样的。
我们根据下面的例子看看它们的含义。
inta=30; intb=40; constint*p=&a; p=&b;//注意这里,p可以在任意时候重新赋值一个新内存地址 b=80;//想想看:这里能用*pi=80;来代替吗?当然不能 printf(“%d”,*p);//输出是80
语义分析:
p的值是可以被修改的。即它可以重新指向另一个地址的,但是,不能通过*p来修改b的值。
首先const 修饰的是整个*p(注意,是*p而不是p)。所以*p(p指向的对象)是常量,是不能被赋值的(虽然p所指的b是变量,不是常量)。
其次,p前并没有用const修饰,所以p是指针变量,能被赋值重新指向另一内存地址的。
你可能会疑问:那又如何用const来修饰pi呢?其实,你注意到int*constpi中const的位置就大概可以明白了。请记住,通过格式看语义。我们看看下面的定义。
3.int*constp
这里的const修饰的p,而不是上面的*p,我们根据下面的例子来分析:
inta=30; intb=40; int*constp=&a; //p=&b; 注意这里,p不能再这样重新赋值了,即不能再指向另一个新地址。 b=80; //这里能用*p=80;来代替吗?可以,这里可以通过*p修改a的值。 //请自行与前面一个例子比较。 printf(“%d”,*p);//输出是80 *p=100; printf(“%d”,a);//输出是100
语义分析:
p值是不能重新赋值修改了。它只能永远指向初始化时的内存地址了。并且可以通过*p来修改a的值了。与前一个例子对照一下吧!看以下的两点分析
1).p因为有了const的修饰,所以只是一个指针常量:也就是说p值是不可修改的(即p不可以重新指向b这个变量了)。
2).整个*p的前面没有const的修饰。也就是说,*p是变量而不是常量,所以我们可以通过*p来修改它所指内存a的值。
总之一句话,这次的p是一个指向int变量类型数据的指针常量。
最后总结两句:
1).如果const修饰在*p前则不能改的是*p而不是指p
2).如果const是直接写在p前则p不能改。
4.补充三种情况。
这里,我再补充以下三种情况。其实只要上面的语义搞清楚了,这三种情况也就已经被包含了。不过作为三种具体的形式,我还是简单提一下吧!
情况一:int*pi指针指向constintn常量的情况
constintn1=40; int*pi; pi=&n1;//这样可以吗?不行,VC下是编译错。constint类型的n1的地址是不能赋值给指向int类型地址的指针pi的。否则pi岂不是能修改n1的值了吗! pi=(int*)&n1;//这样可以吗?强制类型转换可是C所支持的。VC下编译通过,但是仍不能通过*pi=80来修改n1的值。去试试吧!看看具体的怎样。
情况二:constint*pi指针指向constintn1的情况
constintn1=40; constint*pi; pi=&n1;//两个类型相同,可以这样赋值。n1的值无论是通过pi还是n1都不能修改的。
情况三:用constint*constpi申明的指针
intn; constint*constpi=&n; //你能想象pi能够作什么操作吗?pi值不能改,也不能通过pi修改n的值。因为不管是 //*pi还是pi都是const的。
5.常量引用
inta=10; int&p1=a;//正确 int&p2=2;//错误,需要引用左值 constint&p3=a;//正确 constint&p4=4;//正确
关于引用的初始化有两点值得注意:
(1)当初始化值是一个左值(可以取得地址)时,没有任何问题,可以用常量引用也可以用非常量引用,如p1,p3;
(2)当初始化值不是一个左值时,则只能对一个constT&(常量引用)赋值。如p2,p3。而且这个赋值是有一个过程的:
首先将值隐式转换到类型T,然后将这个转换结果存放在一个临时对象里,最后用这个临时对象来初始化这个引用变量。
如果是对一个常量进行引用,则编译器首先建立一个临时变量,然后将该常量的值置入临时变量中,对该引用的操作就是对该临时变量的操作。对常量的引用可以用其它任何引用来初始化;但不能改变。
6.常量函数、常量引用参数、常量引用返回值
例1:boolverifyObjectCorrectness(constmyObj&obj);//constreferenceparameter
例2:voidAdd(constint&arg)const;//constfunction
例3:IStackconst&GetStack()const{return_stack;}//returnconstreference
6.1常量函数
1.一个函数通过在其后面加关键字const,它将被声明为常量函数
2.在C++,只有将成员函数声明为常量函数才有意义。带有const作后缀的常量成员函数又被称为视察者(inspector),没
有const作后缀的非常量成员函数被称为变异者(mutator)
3.与const有关的错误总是在编译时发现
4.[摘]Ifthefunctionisnotdeclaredconst,incannotbeappliedtoaconstobject,andthecompilerwill
giveanerrormessage.Aconstfunctioncanbeappliedtoanon-constobject
5.在C++中,一个对象的所有方法都接收一个指向对象本身的隐含的this指针;常量方法则获取了一个隐含的常量this指针
voidfunc_name()const;
以上说明函数func_name()不会改变*this。当你把this指针看成函数func_name()的一个不可见参数就理解了
voidfunc_name(T*this)(noconst)
voidfunc_name(constT*this)(const)
6.常量函数可以被任何对象调用,而非常量函数则只能被非常量对象调用,不能被常量对象调用,如:
classFred{ public: voidinspect()const;//ThismemberpromisesNOTtochange*this voidmutate();//Thismemberfunctionmightchange*this }; voiduserCode(Fred&changeable,constFred&unchangeable) { changeable.inspect();//OK:doesn'tchangeachangeableobject changeable.mutate();//OK:changesachangeableobject unchangeable.inspect();//OK:doesn'tchangeanunchangeableobject unchangeable.mutate();//ERROR:attempttochangeunchangeableobject }
7.在类中允许存在同名的常量函数和非常量函数,编译器根据调用该函数的对象选择合适的函数
当非常量对象调用该函数时,先调用非常量函数;
当常量对象调用该函数时,只能调用常量函数;
如果在类中只有常量函数而没有与其同名的非常量函数,则非常量与常量对象都可调用该常量函数;如:
#includeusingnamespacestd; structA { voidf()const{cout<<"constfunctionfiscalled"< ref_a.f();//callsvoidf()const,输出:constfunctionfiscalled a.g();//ok,normalcall
ref_a.g();//error,constobjectcannotcallnon-constfunction }
8.const关键字不能用在构造函数与析构函数中。因为构造函数的目的是初始化域值,因此它必须更改对象,析构函数同理
6.2常量引用参数
本例中,一个myObj类型的对象obj通过引用传入函数verifyObjectCorrectness。为安全起见,使用了const关键字来确保函数verifyObjectCorrectness不会改变对象obj所引用的对象的状态。此外,通过声明参数常量,函数的使用者可以确保他们的对象不会被改变,也不必担心在调用函数时带来副作用。以下代码试图对声明为常量引用的形参进行修改,从而不会通过编译!
#includeusingnamespacestd; classTest { public: voidf(constint&arg); private: intvalue; }; voidTest::f(constint&arg){ arg=10;//试图修改arg的值,此行将引起编译器错误//errorC2166:l-valuespecifiesconstobject cout<<"arg="< 6.3常量引用返回值
如果你想从常量方法(函数)中通过引用返回this对象的一个成员,你应该使用常量引用来返回它,即constX&
也就是说你想通过引用返回的东西如果从逻辑上来讲是this对象的一部分(与它是否在物理上嵌入在this对象中无关),那么常量方法需要通过常量引用或者通过值来返回,而不能通过非常量引用返回classPerson{ public: conststring&name_good()const;//Right:thecallercan'tchangethename string&name_evil()const;//Wrong:thecallercanchangethename . }; voidmyCode(constPerson&p)//You'repromisingnottochangethePersonobject { p.name_evil()="Igor";//butyouchangeditanyway!! }