📄 effective c++ 2e item21.htm
字号:
width=770>
<TBODY>
<TR>
<TD align=left width=300><B>关键字:</B><BR>Effective C++ </TD>
<TD align=middle width=120><B>贴文时间</B><BR>2001-7-12 20:14:53 </TD>
<TD align=middle width=80><B>文章类型: </B><BR>翻译 </TD>
<TD align=middle width=100><B>给贴子投票 </B><BR><A
href="http://www.csdn.net/develop/addscore.asp?id=8815">投票</A> </TD></TR>
<TR>
<TD> lostmouse 翻译 </TD>
<TD colSpan=3 vAlign=top><B>出处: </B><A
href="http://www.csdn.net/develop/article/8/Effective%20C++%202e%20e-book">Effective
C++ 2e e-book </A></TD></TR>
<TR>
<TD bgColor=#cccc99 colSpan=5> </TD></TR></TD></TR></TBODY></TABLE>
<DIV align=center>
<DIV align=left class=fst>
<DIV class=fstdiv3 id=print2><BR><BR>
<P>条款21: 尽可能使用const</P>
<P>使用const的好处在于它允许指定一种语意上的约束——某种对象不能被修改——编译器具体来实施这种约束。通过const,你可以通知编译器和其他程序员某个值要保持不变。只要是这种情况,你就要明确地使用const
,因为这样做就可以借助编译器的帮助确保这种约束不被破坏。</P>
<P>const关键字实在是神通广大。在类的外面,它可以用于全局或名字空间常量(见条款1和47),以及静态对象(某一文件或程序块范围内的局部对象)。在类的内部,它可以用于静态和非静态成员(见条款12)。</P>
<P>对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const,还有,两者都不指定为const:</P>
<P>char
*p
= "Hello"; //
非const指针,<BR>
// 非const数据</P>
<P>const char *p =
"Hello"; //
非const指针,<BR>
// const数据</P>
<P>char * const p =
"Hello"; //
const指针,<BR>
// 非const数据</P>
<P>const char * const p =
"Hello"; //
const指针,<BR>
// const数据</P>
<P>语法并非看起来那么变化多端。一般来说,你可以在头脑里画一条垂直线穿过指针声明中的星号(*)位置,如果const出现在线的左边,指针指向的数据为常量;如果const出现在线的右边,指针本身为常量;如果const在线的两边都出现,二者都是常量。</P>
<P>在指针所指为常量的情况下,有些程序员喜欢把const放在类型名之前,有些程序员则喜欢把const放在类型名之后、星号之前。所以,下面的函数取的是同种参数类型:</P>
<P>class Widget { ... };</P>
<P>void f1(const Widget *pw); //
f1取的是指向<BR>
// Widget常量对象的指针</P>
<P>void f2(Widget const *pw); // 同f2</P>
<P>因为两种表示形式在实际代码中都存在,所以要使自己对这两种形式都习惯。</P>
<P>const的一些强大的功能基于它在函数声明中的应用。在一个函数声明中,const可以指的是函数的返回值,或某个参数;对于成员函数,还可以指的是整个函数。</P>
<P>让函数返回一个常量值经常可以在不降低安全性和效率的情况下减少用户出错的几率。实际上正如条款29所说明的,对返回值使用const有可能提高一个函数的安全性和效率,否则还会出问题。</P>
<P>例如,看这个在条款19中介绍的有理数的operator*函数的声明:</P>
<P>const Rational operator*(const Rational&
lhs,<BR>
const Rational& rhs);</P>
<P>很多程序员第一眼看到它会纳闷:为什么operator*的返回结果是一个const对象?因为如果不是这样,用户就可以做下面这样的坏事:</P>
<P>Rational a, b, c;</P>
<P>...</P>
<P>(a * b) = c; // 对a*b的结果赋值</P>
<P>我不知道为什么有些程序员会想到对两个数的运算结果直接赋值,但我却知道:如果a,b和c是固定类型,这样做显然是不合法的。一个好的用户自定义类型的特征是,它会避免那种没道理的与固定类型不兼容的行为。对我来说,对两个数的运算结果赋值是非常没道理的。声明operator*的返回值为const可以防止这种情况,所以这样做才是正确的。</P>
<P>关于const参数没什么特别之处要强调——它们的运作和局部const对象一样。(但,见条款M19,const参数会导致一个临时对象的产生)然而,如果成员函数为const,那就是另一回事了。</P>
<P>const成员函数的目的当然是为了指明哪个成员函数可以在const对象上被调用。但很多人忽视了这样一个事实:仅在const方面有不同的成员函数可以重载。这是C++的一个重要特性。再次看这个String类:</P>
<P>class String {<BR>public:</P>
<P> ...</P>
<P> // 用于非const对象的operator[]<BR> char& operator[](int
position)<BR> { return data[position]; }</P>
<P> // 用于const对象的operator[]<BR> const char& operator[](int
position) const<BR> { return data[position]; }</P>
<P>private:<BR> char *data;<BR>};</P>
<P>String s1 = "Hello";<BR>cout <<
s1[0];
//
调用非const<BR>
// String::operator[]<BR>const String s2 = "World";<BR>cout <<
s2[0];
//
调用const<BR>
// String::operator[]</P>
<P>通过重载operator[]并给不同版本不同的返回值,就可以对const和非const String进行不同的处理:</P>
<P>String s =
"Hello";
// 非const String对象</P>
<P>cout <<
s[0];
//
正确——读一个<BR>
// 非const String</P>
<P>s[0] =
'x';
//
正确——写一个<BR>
// 非const String</P>
<P>const String cs = "World"; // const
String 对象</P>
<P>cout <<
cs[0];
//
正确——读一个<BR>
// const String</P>
<P>cs[0] =
'x';
//
错误!——写一个<BR>
// const String</P>
<P>另外注意,这里的错误只和调用operator[]的返回值有关;operator[]调用本身没问题。 错误产生的原因在于企图对一个const
char&赋值,因为被赋值的对象是const版本的operator[]函数的返回值。</P>
<P>还要注意,非const
operator[]的返回类型必须是一个char的引用——char本身则不行。如果operator[]真的返回了一个简单的char,如下所示的语句就不会通过编译:</P>
<P>s[0] = 'x';</P>
<P>因为,修改一个“返回值为固定类型”的函数的返回值绝对是不合法的。即使合法,由于C++“通过值(而不是引用)来返回对象”(见条款22)的内部机制的原因,s.data[0]的一个拷贝会被修改,而不是s.data[0]自己,这就不是你所想要的结果了。</P>
<P>让我们停下来看一个基本原理。一个成员函数为const的确切含义是什么?有两种主要的看法:数据意义上的const(bitwise
constness)和概念意义上的const(conceptual constness)。</P>
<P>bitwise
constness的坚持者认为,当且仅当成员函数不修改对象的任何数据成员(静态数据成员除外)时,即不修改对象中任何一个比特(bit)时,这个成员函数才是const的。bitwise
constness最大的好处是可以很容易地检测到违反bitwise
constness规定的事件:编译器只用去寻找有无对数据成员的赋值就可以了。实际上,bitwise
constness正是C++对const问题的定义,const成员函数不被允许修改它所在对象的任何一个数据成员。</P>
<P>不幸的是,很多不遵守bitwise
constness定义的成员函数也可以通过bitwise测试。特别是,一个“修改了指针所指向的数据”的成员函数,其行为显然违反了bitwise
constness定义,但如果对象中仅包含这个指针,这个函数也是bitwise const的,编译时会通过。这就和我们的直觉有差异:</P>
<P>class String {<BR>public:<BR> // 构造函数,使data指向一个<BR> //
value所指向的数据的拷贝<BR> String(const char *value);</P>
<P> ...</P>
<P> operator char *() const { return data;}</P>
<P>private:<BR> char *data;<BR>};</P>
<P>const String s = "Hello"; // 声明常量对象</P>
<P>char *nasty =
s;
// 调用 operator char*() const</P>
<P>*nasty =
'M';
// 修改s.data[0]</P>
<P>cout <<
s;
// 输出"Mello"</P>
<P>显然,在用一个值创建一个常量对象并调用对象的const成员函数时一定有什么错误,对象的值竟然可以修改!(关于这个例子更详细的讨论参见条款29)</P>
<P>这就导致conceptual constness观点的引入。此观点的坚持者认为,一个const成员函数可以修改它所在对象的一些数据(bits)
,但只有在用户不会发觉的情况下。例如,假设String类想保存对象每次被请求时数据的长度:</P>
<P>class String {<BR>public:<BR> // 构造函数,使data指向一个<BR> //
value所指向的数据的拷贝<BR> String(const char *value): lengthIsValid(false) { ...
}</P>
<P> ...</P>
<P> size_t length() const;</P>
<P>private:<BR> char *data;</P>
<P> size_t
dataLength; //
最后计算出的<BR>
// string的长度</P>
<P> bool
lengthIsValid; //
长度当前<BR>
// 是否合法<BR>};</P>
<P>size_t String::length() const<BR>{<BR> if (!lengthIsValid)
{<BR> dataLength = strlen(data); // 错误!<BR>
lengthIsValid = true; // 错误!<BR> }</P>
<P> return dataLength;<BR>}</P>
<P>这个length的实现显然不符合“bitwise const”的定义——dataLength 和lengthIsValid都可以修改——但对const
String对象来说,似乎它一定要是合法的才行。但编译器也不同意, 它们坚持“bitwise constness”,怎么办?</P>
<P>解决方案很简单:利用C++标准组织针对这类情况专门提供的有关const问题的另一个可选方案。此方案使用了关键字mutable,当对非静态数据成员运用mutable时,这些成员的“bitwise
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -