1.成员函数与非成员函数主要区别是:成员函数可以是虚拟的,而非成员函数则不能。
2.operator>> and operator<< 不能是成员函数,主要是一种调用的习惯的约束
看下面的例子,将operator>> and operator<<设计成成员函数:
// a class that incorrectly declares operator>> and
// operator<< as member functions
class String {
public:
String(const char *value);
...
istream& operator>>(istream& input);
ostream& operator<<(ostream& output);
private:
char *data;
};
String s;
s >> cin; // legal, but contrary
// to convention
s << cout; // ditto
从上可以看出设计成成员函数也是可以的,但调用时将变成XXX >> cin;和XXX << cout;这种奇怪的形式。(c++已经习惯了cin >> XXX;cout << XXX;形式)
正确方法:定义成非成员函数
istream& operator>>(istream& input, String& string)
{
delete [] string.data;
read from input into some memory, and make string.data
point to it
return input;
}
ostream& operator<<(ostream& output,
const String& string)
{
return output << string.data;
}
3.只有非成员函数可以对其最左边的参数进行类型转换,成员函数不可以。
Only non-member functions get type conversions on their left-most argument
Consider a class for representing rational numbers:
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
int numerator() const;
int denominator() const;
private:
...
};
class Rational {
public:
...
const Rational operator*(const Rational& rhs) const;
};
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth; // fine
result = result * oneEighth; // fine
result = oneHalf * 2; // fine
result = 2 * oneHalf; // error!
上面使用的操作符形式语句与下面使用函数形式语句等价:
result = oneHalf.operator*(2); // fine
result = 2.operator*(oneHalf); // error!
为什么会这样呢?乘法操作应该是可以互换位置的,所以这种设计有一定的局限性:
它只适用于两个Rational类型的乘法运算。下面我们看一下出现这种情况的原因:
result = oneHalf * 2;通过调用const Rational operator*(const Rational& rhs) const;函数来完成操作,也就是
result = oneHalf.operator*(2); 因为2是int型,而函数原型中的参数为const Rational& rhs,它要的是一个Rational类型。所以必须将2转换成Rational类型。
result = oneHalf * 2;之所以能正确执行,主要是因为编译器帮我们做了隐蔽的类型转换implicit type conversion,它通过Rational的构造函数来完成转换,如下:
const Rational temp(2); // create a temporary
// Rational object from 2
result = oneHalf * temp; // same as
// oneHalf.operator*(temp);
而result = 2 * oneHalf;调用为result = 2.operator*(oneHalf); 可是2并不是Rational对象,它并不能调用成员函数operator*,编译器将试图寻找一个非成员函数operator*,并调用result = operator*(2, oneHalf); 来完成操作,但并没有定义这样的函数,因此将产生错误。
上面的类型转换只有在构造函数为非explicit时进行,如果构造函数为explicit时,编译器不会进行这种隐蔽类型转换
因为explicit构造函数不能用于implicit conversions,下面说明了这种情况:
class Rational {
public:
explicit Rational(int numerator = 0, // this ctor is
int denominator = 1); // now explicit
...
const Rational operator*(const Rational& rhs) const;
...
};
下面两个调用都将产生错误
result = oneHalf * 2; // error!
result = 2 * oneHalf; // error!
解决办法:将成员函数改为非成员函数
The solution:
class Rational {
... // contains no operator*
};
// declare this globally or within a namespace; see
// Item M20 for why it's written as it is
const Rational operator*(const Rational& lhs,
const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
Rational oneFourth(1, 4);
Rational result;
result = oneFourth * 2; // fine
result = 2 * oneFourth; // hooray, it works!
那么是不是应该将operator*作为类Rational的friend呢(友元函数),因为operator*并没有访问类Rational的私有成员,因此没有必要将它作为类Rational的friend。
Whenever you can avoid friend functions, you should, because, much as in real life, friends are often more trouble than they're worth.
当可以不使用友元时,就不要使用,因为它经常带来很多麻烦。
看另外一个使用了友元的例子:
See another example:
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
...
private:
int n, d; // numerator and denominator
friend
const Rational // see Item 21 for why
operator*(const Rational& lhs, // the return value is
const Rational& rhs) // const
};
inline const Rational operator*(const Rational& lhs,
const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
这里为什么将operator*声明成类Rational的friend呢?因为在operator*的实现中访问了类Rational的私有成员。
注意:类的友元函数在类中声明,在类外定义,它不是类的成员函数,不要看到它在类中声明就把它当成员函数用。
4.关于运算符
运算符设计的两种方式:
a.非成员函数形式
non-member function style: ( two parameters )
inline const Rational operator*(const Rational& lhs,
const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
istream& operator>>(istream& input, String& string)
调用方式:
Rational a,b;
a*b; // call operator*(a,b);
String str;
cin >> str; // call operator>>(cin,str);
b.成员函数形式
member function style:: ( one parameter )
const Rational operator*(const Rational& rhs) const;
istream& operator>>(istream& input);
调用方式:
Rational a,b;
a*b; // call a.operator*(b);
String str;
str >> cin // call str.operator>>(cin);