首先,一看到 const 关键字,我们的第一反应就是定义常量。的确,这没有错,const 就是 constant 的缩写嘛,但是事实上他的用法远不止这些。接下来我们一一来讲解。

在 C 程序中,const 的用法主要有定义常量、修饰函数参数和修饰函数返回值。而在 C++ 程序中,它除了上述功能外,还可以修饰函数的定义体,定义类中某个成员函数为恒态函数,即不改变类中的数据成员。对于定义常量的用法,这里就不多说了,重点看一下修饰函数参数、修饰函数返回值以及修饰函数的定义体。

0)修饰变量或指针

int ii=0; 
const int i=0;           //i 是常量,i 的值不会被修改 
const int *p1i=&i;        // 指针 p1i 所指内容是常量,可以不初始化 
int const* p11i = &i; // 同上,const 在*号左边,作用是一样的。
int  * const p2i=ⅈ    // 指针 p2i 是常量,所指内容可修改 
const int * const p3i=&i; // 指针 p3i 是常量,所指内容也是常量 
p1i=ⅈ                 // 合法
*p2i=100;               // 合法

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 成员函数是可以修改它的。