实用经验 19 尽可能多的使用const

const可以说是C++中最为神奇的关键字。它的神奇在于:你可以通过编译器指定语义上的约束,而不需要花费任何的代价。例如,你可以通过const关键字告诉编译器和其他程序员,你程序中的某个数值需保持恒定不变,不论何时只要你这么做了,编译器就会协助你保证此约束不被破坏。

const关键字的用途是多种多样的。例如:在类的外部,你可以定义全局作用域的常量,也可以通过添加static来定义文件、函数或程序块作用域的常量。对于指针,通过const你可以定义指针是const、其所指向的数据是const或两者都是const。例如:

char szGreeting[]  = “Hello!  My God!;
char *pszGreeting = szGreeting;       		// 非 const 指针,非 const 数据
const char * pszGreeting = szGreeting;  	// 非 const 指针, const 数据
char *const pszGreeting = szGreeting;    	// const 指针, 非 const 数据
const char *const pszGreeting = szGreeting;	// const 指针, const 数据

上述语法也许让你眩晕,不知如何是好?其实并不然,如果你仔细研究,你会发现这里面的规律。规律总结如下:

  1. 如果const在*的左边,说明指针所指向对象是常值;如果所指向对象为常值,const在类型的前面和后面其实都一样。
  2. 如果const在*的右边说明指针是恒值;
  3. 最复杂的是const同时出现在*的左右两侧,此时说明指针是恒值,指针所指向的对象也是恒值。

const关键字功能不仅表现在变量定义,在函数声明、函数返回值及类成员函数方面,也有着让我们惊讶的表现。

函数声明使用const

有些人喜欢把const放到类型的前面,有些人喜欢把const放到类型的后面,但有一点是肯定的,如果有*号必须在*的前面。按照上面的总结,这两种声明其实并没什么本质的区别。如果你从事MFC开发,下面看两个函数的声明也许你不会陌生。

void UpdateUI_1(const CWnd *pWnd);// 向UpdateUI_1传入指向CWnd对象常量的指针
void UpdateUI_2(CWnd const *pWnd);// UpdateUI_2声明和UpdateUI_1一样。

上述两声明,无论哪种声明都约束pWnd所指向的对象在整个函数执行过程中禁止修改。

函数返回值声明为const

让函数的返回一个常量值,经常可以在不降低安全性和效率的前提下减少用户出错的概率。为了说明这个问题,我们先看下面的代码片段:

CRational {}      		// 有理数类,支持有理数的加减乘除四则运算。
CRational operator*(const rational& lhs, const rational& rhs);// 有理数乘法运算。
CRational a,  b,  c;
......
(a * b) = c; 			// 对a*b的结果赋值,不符合逻辑。但可以通过编译。

我不知道为何有些程序员会想到对两个数的运算结果直接赋值,但我们却知道:如果a、b和c是固定类型,这种做法显然是不合法的。但不幸的是:上述代码片段虽然不合法,但C++编译器却没有检测出来。明白了其中的问题,我们在看下面的代码片段。

CRational {}   // 有理数类,支持有理数的加减乘除四则运算。
const CRational operator*(const rational& lhs, const rational& rhs);  // 有理数乘法运算。
CRational a,  b,  c;
......
(a * b)  =  c;   // 对a*b的结果赋值,编译器报出编译错误。

可以看出,声明operate *操作符重载函数的返回const可以避免对两个数运算结果赋值问题。对我们来说,对两个数的运算结果赋值是非常没道理的。声明operator *的返回值为const可以防止这种情况,所以这样做才是正确的。

小心陷阱

  • 一个好的用户自定义类型的特征是:它会避免那种没道理的与固定类型不兼容的行为。
  • 声明函数返回const类型可避免那些没道理的与固定类型不兼容的行为。

const 成员函数

函数具有const属性,这是C++所特有的特征。将成员函数声明为const就是指明这个函数可以被const对象调用。

const成员函数优点:

  • const成员函数可使得类的接口更加易于理解。
  • const成员函数可以与 const 对象协同工作。这是高效编码十分重要的一个方面。

如果成员函数之间的区别仅仅为“是否是 const 的”,那么它们可被重载。很多人都忽略了这一点,但是这是 C++ 重要特征之一。

说了这么多,现在我们讨论把一个成员函数声明为const到底有什么玄机?这里面有两个说法:按位恒定和逻辑恒定。

按位恒定坚持:当且仅当一个成员函数对所有的数据成员都不作出改动时,才需要将此函数声明为const。也就是说如果一个成员函数声明const的条件是:成员函数不对对象内部做任何的修改。按位恒定的一个好处就是:它使得错误检查变得更轻松。但不是一个成员函数声明了const,它就不会修改类对象的数据成员。下面这个例子就是这样,虽然成员函数声明了const属性,但它依然可以修改类对象的数据成员。这显然是有问题的。

class  CHString    // 自定义CHstring类,类似STL中的string类
{
public:
	char& operator[](std::size_t position) const  // 定义CHstring类,[]中括号运算符。
	{
	    return pText[position];
	} 
private: 
    char *pText; 
};

const CHString cctb("Hello");	// 声明对象常量 
char *pc = &cctb[0];	       	// 调用 const 的 operator[] 

// 从而得到一个指向 cctb 中数据的指针 
*pc = 'J';	                  // cctb 现在的值为 "Jello" 

于是逻辑恒定论便应运而生了。逻辑恒定坚持:一个 const 的成员函数可能对其调用的对象内部做出改动,但是仅仅以客户端无法察觉的方式进行。利用可变的( mutable )数据成员可实现这一目标。 (mutable 可以使非静态数据成员不受按位恒定规则的约束):

// 字符串输出次数统计实现类,统计字符串被使用了多少次
class ClxTest     
{ 
public:
    ClxTest();
    ~ClxTest();
    // 输出字符串
    void Output() const;       
    // 获得字符串输出次数
    int  GetOutputTimes() const; 
    private:
    // 字符串输出次数
    mutable int m_iTimes;       
};
// 构造函数
ClxTest::ClxTest()
{
    m_iTimes = 0;
}
// 析构造函数
ClxTest::~ClxTest()
{
}
// 输出字符串。
void ClxTest::Output() const
{
    cout << "Output for test!" << endl;
    m_iTimes++;
}
// 字符串输出次数。
int ClxTest::GetOutputTimes() const
{
    return m_iTimes;
}
// 字符串输出测试。
void OutputTest(const ClxTest& lx)
{
    cout << lx.GetOutputTimes() << endl;
    lx.Output();
    cout << lx.GetOutputTimes() << endl;
}

尽量用const常量替换#define常量定义

C语言中定义一个int型常量,我们必须这么定义:

#define    MAX_LENGTH     100

而C++则大可不必,因为这种实现存在很多陷阱。如#define只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。

我们可以通过const实现常量定义。如上述定义可以用const定义为

const  int  MAX_LENGTH  =  100;

除了上述陷阱以外,从汇编的角度来看const定义常量,只是给出了对应的内存地址,而不是像#define一样给出的是立即数。const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。所以使用const常量可以节省内存。

请谨记

  • 将某些东西声明为const可帮助编译器侦测出错误用法。const可用于任何作用域的对象,函数形参,函数返回值,成员函数本体等。
  • 尽量用const替换#define,因为这种替换好处多多。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值