来由
来源:为了解决运算符重载的对象显式调用问题。
class_A = class_B * 2.75;
这里的 * 运算符重载我们可以通过成员函数实现,但是像这样的语句:
class_A = 2.75 * class_B;
就不能了。从概念上说,2.75 * class_B 应与 class_B * 2.75 相同,但第一个表达式不对应于成员函数,因为2.75不是Time类型的对象。
左侧操作数应是调用对象,但2.75不是对象。因此,
编译器不能使用成员函数调用来替换该表达式。
解决这个难题的一种方式是,告知每个人(包括程序员自己),只能按 class_B * 2.75 这种格式编写,不能写成 2.75 * class_B 。这是一种对服务器友好-客户警惕的(server-friendly, client-beware)解决方案,与OOP无关。
然而,还有另一种解决方式——非成员函数(记住,大多数运算符都可以通过成员或非成员函数来重载)。非成员函数不是由对象调用的,它使用的所有值(包括对象)都是显式参数。这样,
编译器能够将表达式 class_A = 2.75 * class_B; 与 class_A = operator*(2.75, class_B) 非成员函数调用匹配。
对于非成员函数重载运算符来说,运算符表达式左边的操作数对应于运算符函数的第一个参数(
这里的函数特征标有顺序!),运算符表达式右边的操作数对应于运算符函数的第二个参数。而原来的成员函数则按相反的顺序处理操作数,也就是说,double值乘以Time值。
使用非成员函数可以按所需的顺序获得操作数,但是这里有个问题,非成员函数不能直接访问类的私有数据,所以,就引出了我们的
友元函数。
套路
- 始终记住一点,友元函数是是非成员函数,所以不能使用域限定符 : : 对友元函数进行操作。但是同时,友元函数的访问权限又与成员函数相同。
- 实际上,我们经常采用在友元函数定义中调用成员函数的方式(这里有代码重用的思想,因为我们已经实现了Time值乘以double值,区别只是在于参数的顺序不同,所以可以直接调用该成员函数而不是重新实现一遍):
Time operator*(double m, const Time & t)
{
return t * m;
}
一个经验就是,如果要为类重载运算符,并将非类的项作为第一个操作数,则可以
用友元函数来反转操作数的顺序。
- 重载<<运算符,一般使用如下模式(因为返回值为ostream对象,可以在一个语句中多次调用):
ostream & operator<<(ostream & os, const c_name & obj)
{
os << ...;
return os;
}
- 重载一个运算符,如果它的功能是计算得到一个新的类对象,则应考虑是否可以使用类构造函数来完成这种工作。这样做不仅可以避免麻烦,而且可以确保新的对象是按照正确的方式创建的(方便,高效,安全)。例如:
Vector Vector::operator+(const Vector & v) const
{
return Vector(x + v.x, y + v.y);
}
- 将加法定义为友元函数可以让程序更容易适应自动类型转换,原因在于,两个操作数都成为函数参数,因此与函数原型匹配。