说起const,作为一个开发估计第一个想到的就是定义常量,但是,其实const第一次被写进C规范时,它的意思实际上是“一个不可以被改变的普通变量”,普通变量顾名思义就是说它实际上就是一个普通变量,只不过它比一般的普通变量更高级一点,因为它是不可改变的,既然是变量那自然也就符合普通变量的性质。在C编译器中const是不被看做一个编译时常量的,它是需要占用内存的。所以在C中const实际上的使用并不是非常广泛,它迫使程序员在预处理器中使用#define,但是#define毕竟是有很多不足的,所以C++中才会想到使用const来替代预处理器#define。很明显const在C++中发展的很好,我们用它修饰变量,修饰指针,修饰函数参数,返回类型等,下面我们就主要来讲下const在C++中的使用。
1、const与#define的简单比较
我们开始就说了,const在C++中最初的动机就是替代#define的,那么为什么我们要进行这种替代呢?它又能给我们带来什么好处呢?
首先,我们为什么要用const来替代#define呢?有人说他编程都不用const还不是照样没问题,程序依然正常。但是如果你编一个很大的程序,那么在程序中免不了要定义很多相同数值,如函数返回值等,这时,如果我们直接在代码中定义变量,或者直接拿着0,1这样数值使用,那么就可能让代码的其他使用者或者后期维护人员感到头痛,因为他们不知道你这些0,1到底干嘛的,他们就要猜。当然如果你说,这时我自己的代码,我干嘛让别人看啊,那当然没为题,反正后面麻烦的只会是你自己。又或者你说我加注释不就行了啊,但是如果你使用次数很多呢,难道要都加上注释吗,那样代码恐怕看着也会头疼吧。这时可能有人会想到#define了,当然#define确实就能解决这个问题了,我们只要定义一个宏就解决问题了,那我们为什么还要const呢,因为宏定义是有时是有问题的:宏定义有时是不安全的,它只是简单的替换,过程很简单,结果当然有时也很简单。例如:
#define M 5
#define N M+10
这时当我们计算M+N当然没问题,但是如果我们这时计算的是M*N呢,我们本想得到5*15,却得到了5*5+10,这就与我们的所需有所出入了,所以这时就要加括号,当然这也是定义宏的应该有的习惯,这就是所谓的边际效应,同时,我们定义宏时还有一个问题,那就是数据类型安全检查。所以这时候用const就能解决上述问题,因为const是有类型检查的,其实说了这么多就是一句话,const在程序开发中提供了更好的安全性与可控性。
2、const的特点
i、const的修饰的对象不可被修改
ii、const有数据类型,便于类型检查
iii、const节省空间,const常量是编译时常量,C++编译器一般是不给const分配空间的,它被保存在符号表中,当const被使用时,他在编译时进行常量折叠。
iv、C++默认的const是内部连接,C默认的const是外部连接,所以在C++中如果想完成C中const的使用,那么要使用extern把连接改成外部连接。
3、const的使用
i、const定义普通常量
const int iValue = 10;
int const iValue = 10;
上面两个定义意义相同,都是定义一个常量,我们不能随意修改iValue的值,当然如果我们硬要修改的话输出结果可能不是我们想的结果。如下例:
const int iValue = 3;
int *pValue = (int*)&iValue;
*pValue = 4;
//iValue = 5;//不能给常量赋值
cout << iValue << endl;
cout << *pValue << endl;
cout << &iValue << endl;
cout << pValue << endl
上例中,我们定义了一个常量iValue,我们自然不能直接修改iValue的值,但是我们定义了一个指针来强制指向了iValue的地址,这时,我们发现我们可以通过指针pValue来修改内存中的值,输出结果中iValue值依然是3,但是*pValue的值确实4,可能有人会问,你怎么知道它们两个是同块内存呢,这是因为我们看到上例中iValue的地址值与pValue的值相等,这就表明它们确实是同一块内存,这就奇怪了,同块内存为什么值不同呢?这就要说下const的一个特性了,因为const是一个编译时常量,当const被使用时,他在编译时进行常量折叠,什么是常量折叠呢,其实说白了,就是值替换,意思就是当编译时,编译器就会查找代码中的iValue的并把它的值替换为3,所以上例中不管我们怎么修改*pValue的值iValue的值都不会变,因为其实在真正使用它前,编译器并未给它分配内存空间,iValue只相当于一个可以替换的符号。
ii、指针中const的使用
const int *p;
int const *p;
int* const p;
const int* const p;
上面列了四个表达式,四个表达式都很简单,我们来讲下这四个表达式的不同,第一个表达式很明显,就是“一个指向const的int的普通指针”,意思就是我们不能通过p来修改它所指向的值,但是我们可以修改p的指向;第二个表达式我们看到和第一个的区别就是const放到了int的后面,这又怎么解释呢,其实我们不必被其外边所迷惑,它的意思与第一个表达式完全相同,但是很明显我们有时不小心可能会混淆,把它当成一个常量指针;也就是第三个表达式所表示的意思,即“指向一个int变量的const指针”,很明显,我们不能修改指针的指向,但是我们可以通过它修改指向的内存中的值;至于第四个,我们如果对前三个理解了,那么自然就明白了,它实际上就是指向const变量的const指针。但是在使用指针是有一点我们要注意下,虽然这种情况也许我们遇到的不多,如下例:
int Test = 3;
int Test2 = 4;
typedef int* T;
T const pCon1 = &Test;
*pCon1 = 4;
const T pCon2 = &Test2;
*pCon2 = 6;
cout << *pCon1 << " " << *pCon2 << endl;
上例中我们为int*定义了一个别名,这时下面的pCon1与pCon2指针我们看到的第一印象就是pCon1是一个const指针,而pCon2我们认为是一个指向const的普通指针,但是我们又发现我们竟然可以通过pCon2修改指向的值,这不符合我们上面说的理论啊,其实这也就是我要说的,在这里T并不是一个宏,并不是简单的替换而已,实际上我们看到的应该是const (int*) pCon2,这样我们就发现实际上pCon2依然是一个const指针,所以有时我们不能仅凭简单的猜测来决断一个问题,应该细致一点。
iii、用于函数参数及返回值
在函数参数中我们经常看到const的使用,并且经常伴随着引用使用,下面我们来讲下const在函数参数中的使用。
前面我们说过const就是定义常量,在函数参数中依然如此,const用在函数参数中就意味中在这个函数中我们不可以修改我们传进来的参数。
下面我们看几个例子来介绍下:
(1)、用于函数参数
void Fun(const int iVal);
传值为常量,在函数中不可修改,但是实际上本来就是传值,所以实际上这样用意义不大,因为即使我们修改它的值也不会影响到实参的值。
void Fun(const int *pVal);
指针所指的内容为常量,这时我们不可在函数通过pVal修改pVal所指向的内存的值。
void Fun(const int &rVal);
const修饰引用,这时等于直接传递的实参的地址,减少了程序的临时变量的建立,同时我们又不可以修改实参的值,同时对于临时对象的传递,我们也要使用const引用。
(2)、用于函数返回值
const int Fun();
单纯的返回内部类型常量,当然这个其实没用的,因为返回值本身返回一个临时变量,使用时只是单纯的赋值,所以返回一个内部数据类型的值时,应该去掉const从而避免使用者混淆,当然自定义对象的话返回常量确实很重要的,自定义对象有时我们会把返回值做左值,所以返回非const对象时我们可以做左值,而const却不可以。
const int *Fun();
返回一个指针,指向返回值的地址,但是这时我们要记得返回值不能是一个局部变量,因为如果是一个局部变量那么函数结束时局部变量就会被释放,结果不可知了。
int *const Fun();
返回的指针本身不可变,这个平时用的不多,这里就不多说。
iv、类中的使用
(1)、类中的常量
const我们普遍把它使用为定义常量,但是在类中const的定义并不是我们所要求的实际意义上的常量,const在每个类对象中分配存储并代表一个值,这个值一旦被定义就不可再改变,所以在类中定义一个const时,实际上意思是,在我们定义的这个对象寿命周期中,它是一个常量,但是同一个类不同的对象它所表示的值可能不同。同时我们知道在一个类中建立一个const时我们不能给初始值,只能在构造函数中的初始化列表中初始化。所以在类中我们如果要定义一个编译时常量,就不能使用const,我们能够使用enum来定义常量,淡然enum只能定义整型,所以如果我们要定义不是整型的常量,那么我们可以考虑使用static常量,它使用起来更加灵活,这个已经讲过。
(2)、const在成员函数中的使用
由于关于函数参数及返回值我们已经讨论过,所以现在我们就重点讲一下,const成员函数。如下举例:
class CTest
{
public:
void Fun() const;
private:
int m_iValue;
}
CTest::Fun() const
{
cout << "Test" << endl;
}
在上例中,我们定义了一个简单的类,并且我们在类中定义了一个const函数,在上例中我们应注意,当我们在类外部定义时,也必须带上const,不然编译器不会把他作为同一个函数,同时如果我们定义一个const的类对象,那么我们这个const对象只能条用const函数,因为如果一个函数不是const函数,那么编译器会默认的认为我们会在函数中修改对象,所以如果我们在函数中不修改对象,那么我们应该把它定义为const,因为这样const对象才可以调用。当然如果我们想在const函数中修改对象的值也不是不可以(虽然这样做有点欠),其实是有两种方法来做出修改的,第一种就是“强转”,所谓强转就是在const函数中,我们把对象的this指针强制转换为普通之指针,然后通过该指针来修改对象的值,但是这样做实际山破坏了其安全性,所以我们还有另一个方法,就是把我们要改变的数据成员声明为mutable,这样我们就可以修改其值了,如上例我们可以如下修改:
class CTest
{
public:
void Fun() const;
private:
mutable int m_iValue;
}
CTest::Fun() const
{
//((CTest*)this)->m_iValue++;//强转
m_iValue++;
cout << "Test" << endl;
}
这样如上例我们就可以修改m_iValue的值了。
关键字const能将对象、函数参数,返回值、成员函数等定义为常量,它可以消除预处理器中值代替而不对预处理器造成影响,并且它为程序设计提供优良的类型检查及安全性。const在C++中使用非常广泛,我们常使用它,而不是C中的#define。