今天在做一个趋势笔试题的时候,才让我有了系统把const关键字好好总结一下的冲动,因为这个关键词大大小小好多地方都出现过,出现频率非常高,而每次只是简短的把答案看了一下,没有真正将其整个用法弄透,马上要找工作了,为避免悲剧再次发生,不得不引起我的重视,这才有了这篇博文。
首先,一看到const关键字,我们的第一反应就是定义常量。的确,这没有错,const就是constant的缩写嘛,但是事实上他的用法远不止这些。接下来我们一一来讲解。
在C程序中,const的用法主要有定义常量、修饰函数参数和修饰函数返回值。而在C++程序中,它除了上述功能外,还可以修饰函数的定义体,定义类中某个成员函数为恒态函数,即不改变类中的数据成员。对于定义常量的用法,这里就不多说了,重点看一下修饰函数参数、修饰函数返回值以及修饰函数的定义体。
1)修饰函数参数
首先如果该参数用于输出,那么无论是采用指针传递还是引用传递,都不能加const修饰。所以const只能用于修饰输入参数。这里又分三种情况:输入参数采用值传递还是指针传递还是引用传递。
(1)如果采用值传递,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不需要加const 修饰。
例如,对于函数void Func1(int x),写成void Func1(const int x)一点意义也没有。同理,对于void
Func2(A a)也不需要写成void Func2(const A a),其中A为用户自定义的对象类型。
(2)如果采用指针传递,那么加const可以防止函数体内部对该参数进行改变,起到保护作用。
例如,假设StringCopy函数定义为:void StringCopy(char *strDest, const char *strSrc),那么,如果函数体试图改变strSrc的内容,编译器将报错。
(3)如果采用引用传递,
首先我们来说一下,为什么要引入引用传递这种方法。原因是:对于非内部数据类型的参数而言,象void
Func(A a) 这样声明的函数注定效率比较底。因为函数体内将产生A 类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以将函数声明改为void
Func(A& a)。这样一来,根据引用传递的定义,只是借用了参数的别名,不需要产生临时对象。
但是,这样一来,当函数体中改变了参数a的值后,相应的传递的原始值也会相应改变。所以如果不希望改变原始参数,只需要在前面加上const修饰,这样一来,函数最终定义为void
Func(const A& a)。同理,是否应将void Func(int x) 改写为void
Func(const int &x),以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
总结一下const作为函数输入参数的用法:
(1)对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void
Func(A a) 改为void Func(const A &a)。
(2)对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void
Func(int x) 不应该改为void Func(const int &x)。
2)修饰函数的返回值
根据上面的思路,这里也分三种情况,即值传递、指针传递、引用传递。
(1)如果函数返回值采用“值传递”方式,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。
例如,不要把函数int
GetInt(void) 写成const int GetInt(void)。同理不要把函数A
GetA(void) 写成const A GetA(void),其中A 为用户自定义的数据类型。
(2)如果函数返回值采用“指针传递”方式,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
例如,定义函数为:const char *GetString(void),那么char *str = GetString()将会出现编译错误。应该写成const
char *str = GetString()。
(3)如果函数返回值是采用“引用传递”方式,它的意义在于能提供啊效率,而这种方式使用场合并不多。这个时候,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。
例如,对于类的重载赋值函数A
& operate = (const A &other),如果不加cons修饰,则定义A a, b, c;(a = b) = c,程序合法,但是如果加上const修饰,即const A
& operate = (const A &other),则程序会报错。
3)修饰函数的定义体。
定义const函数,只需要将const关键字放在函数声明的尾部。任何不会修改类的数据成员的函数都应该声明为const 类型。如果在编写const 成员函数时,不慎修改了数据成员,或者调用了其它非const 成员函数,编译器将报错,这无疑会提高程序的健壮性。
例如,以下程序中,类stack 的成员函数GetCount 仅用于计数,从逻辑上讲GetCount 应当为const 函数。编译器将指出GetCount 函数中的错误。
class Stack
{
public:
void Push(int elem);
int Pop(void);
int GetCount(void) const; // const 成员函数
private:
int m_num;
int m_data[100];
};
int Stack::GetCount(void) const
{
++ m_num; // 编译错误,企图修改数据成员m_num
Pop(); // 编译错误,企图调用非const 函数
return m_num;
}
到这里,const关键字的讲解就结束了。以下是几点使用const的几点规则。
1)
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数。
2) const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的。
3) const成员函数不可以修改对象的数据,不管对象是否具有const性质.它在编译时,以是否修改成员数据为依据,进行检查。
4) 然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的。