Item M5:谨慎定义类型转换函数
提供让编译器进行隐式类型转换方法:单参数构造函数(single-argument constructors)和隐式类型转换运算符
例:
class Rational { public: Rational(int numerator = 0, // 转换 int 到 Rational. 可以是只定义一个参数,或者定义多参但除了第一个都有缺省值
int denominator = 1);
operator double() const; // 转换 Rational 类到double };
隐式类型转换运算符所带来的问题:当你在不需要使用转换函数时,这些的函数缺却会被调用运行。
例如:
Rational r(1, 2);
cout << r; // 应该打印出"1/2"
当编译器调用 operator<<时,会发现没有这样的函数存在,但是它会试图找到一个合适的隐式类型转换顺序以使得函数调用正常运行。编译器会发现它们能调用Rational::operator double 函数来把 r 转换为 double 类型。所以上述代码打印的结果是一个浮点数,而不是一个有理数。一个隐藏很深的错误就这样产生了。
解决方法是用不使用语法关键字的等同的函数来替代转换运算符。
如上例的
operator double() const;可以换成函数double asDouble() const;这样可以解决隐式类型转换运算符的问题。
通过不声明运算符(operator)的方法,可以克服隐式类型转换运算符的缺点,但是单参数构造函数没有那么简单。毕竟,你确实想给调用者提供一个单参数构造函数。同时你也希望防止编译器不加鉴别地调用这个构造函数。两个方法:1、利用一个最新编译器的特性,explicit 关键字。2、参数使用用户自定义类型。
例如:
explicit Rational r(1, 2); //explicit关键字
或者构造函数的参数使用用户自定义类型
class Rational { public: class ArraySize { // 用户自定义类型 public: ArraySize(int numElements): theSize(numElements) {} int size() const { return theSize; } private: int theSize; }; public: Rational(ArraySize numerator , ArraySize denominator = 1); };
ITEM M6:自增(INCREMENT)、自减(DECREMENT)操作符前缀形式与后缀形式的区别
重载函数间的区别决定于它们的参数类型上的差异,但是不论是 increment 或 decrement 的前缀还是后缀都只有一个参数。为了解决这个语言问题,C++规定后缀形式有一个 int 类型参数,当函数被调用时,编译器传递一个 0 做为 int 参数的值给该函数.
例:
class UPInt { // "unlimited precision int" public: UPInt& operator++(); // ++ 前缀 const UPInt operator++(int); // ++ 后缀 UPInt& operator--(); // -- 前缀 const UPInt operator--(int); // -- 后缀 UPInt& operator+=(int); // += 操作符,UPInts // 与 ints 相运算 ... }; UPInt i; ++i; // 调用 i.operator++(); i++; // 调用 i.operator++(0); --i; // 调用 i.operator--(); i--; // 调用 i.operator--(0);
尤其要注意的是:这些操作符前缀与后缀形式返回值类型是不同的。前缀形式返回一个引用,后缀形式返回一个 const 类型
原因:连续后缀i++++;
等同于i.operator++(0).operator++(0);
1.第一是与内置类型行为不一致。int型不允许i++++模式
2。第二个原因是使用两次后缀 increment 所产生的结果与调用者期望的不一致。第二次调用 operator++改变的值是第一次调用返回对象的值,而不是原始对象的值。
所以后缀返回const防止++++调用。
increment 的前缀形式有时叫做“增加然后取回”,后缀形式叫做“取回然后增加”。这两句话非常重要,因为它们是 increment 前缀与后缀的形式上的规范。
// 前缀形式:增加然后取回值 UPInt& UPInt::operator++() { *this += 1; // 增加 return *this; // 取回值 } // postfix form: fetch and increment const UPInt UPInt::operator++(int) { UPInt oldValue = *this; // 取回值 ++(*this); // 增加 return oldValue; // 返回被取回的值 }
如果你很关心效率问题, 当你第一次看到后缀 increment 函数时,你可能觉得有些问题。这个函数必须建立一个临时对象以做为它的返回值,上述实现代码建立了一个显示的临时对象(oldValue),这个临时对象必须被构造并在最后被析构。前缀increment 函数没有这样的临时对象。由此得出一个令人惊讶的结论,如果仅为了提高代码效率,UPInt 的调用者应该尽量使用前缀 increment,少用后缀 increment。
Item M7:不要重载“&&”,“||”, 或“,”
C++使用布尔表达式短路求值法(short-circuit evaluation)
char *p; ... if ((p != 0) && (strlen(p) > 10)) ...
这里不用担心当 p 为空时 strlen 无法正确运行,因为如果 p 不等于 0 的测试失败,strlen不会被调用。
同样:
int rangeCheck(int index) { if ((index < lowerBound) || (index > upperBound)) ... ... }
如果 index 小于 lowerBound,它不会与 upperBound 进行比较。
重载函数 operator&& 和operator||,是函数调用。函数调用法与短路求值法是绝对不同的。首先,没有采用短路计算法。其次,没法知道两边的表达式哪一个先计算。所以不要重载函数 operator&& 和operator||。
逗号操作符
void reverse(char s[]) { for (int i = 0, j = strlen(s)-1; i < j; ++i, --j) // 啊! 逗号操作符! { int c = s[i]; s[i] = s[j]; s[j] = c; } }
一个包含逗号的表达式首先计算逗号左边的表达式,然后计算逗号右边的表达式;整个表达式的结果是逗号右边表达式的值。所以在上述循环的最后部分里,
编译器首先计算++i,然后是—j,逗号表达式的结果是--j。
无法模仿的特性:
如果你写一个非成员函数 operator,你不能保证左边的表达式先于右边的表达式计算,因为函数(operator)调用时两个表达式做为参数被传递出去。但是你不能控制函数参数的计算顺序。所以非成员函数的方法绝对不行。
剩下的只有写成员函数 operator 的可能性了。即使这里你也不能依靠于逗号左边表达式先被计算的行为特性,因为编译器不一定必须按此方法去计算。
所以不要重载逗号运算符。