第十一章-使用类(上)11.1~11.5

11.1 运算符重载

运算符重载是一种形式的C++多态。
很多C++运算符已经被重载,比如将*运算符用于地址中的值;但将它用于两个数字时,得到的将是乘积。C++根据操作数的数目和类型来决定使用哪种操作。
C++允许将运算符重载扩展到用户定义的类型,比如使用+将两个对象相加。
重载运算符可以让代码更加自然,比如原本将两个数组相加是一种常见的运算,通常需要一个for循环来实现:

for (int i = 0; i < 20; i++)
	evening[i] = sam[i] + janet[i];

但在C++中可以定一个表示数组的类,并重载+运算符,于是可以这样:

evening = sam + janet;

这种简单的加法表示法隐藏了内部机理,并强调了实质,是OOP的另一个目标。
要冲在运算符,就要使用称为运算符函数的特殊函数形式,格式如下:

operator op(argument-list)

所以operator +()重载+运算符,operator *()重载 *运算符。其中op必须是有效的C++运算符,不能虚构一个新的符号。operator 函数将重载[]运算符,因为[]是数组索引运算符。
比如有个Salesperson类,并为他定义了一个operator +()成员函数重载+运算符,以便将Salesperson对象的销售额相加,则如果district2、sid和sara都是Salesperson类对象,便可以编写这样的等式:district2 = sid + sara;。编译器发现,操作数都是Salesperson类对象,因此使用相应的运算符函数替换上述运算符:district2 = sid.operator+(sara);。该函数将隐式地使用sid(因为它调用了方法),而显式地使用sara对象(因为它被作为参数传递),来计算总和,并返回这个值。

11.2 计算时间:一个运算符重载实例

#include <iostream>
#include <cstdlib>
using namespace std;
class Time
{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h, int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0, int m = 0);
	Time Sum(const Time &t) const;
	void Show() const;
};
Time::Time() { hours = minutes = 0; }
Time::Time(int h, int m)
{ 
	hours = h;
	minutes = m;
}
void Time::AddMin(int m)
{
	minutes += m;
	hours += minutes / 60;
	minutes %= 60;
}
void Time::AddHr(int h)
{
	hours += h;
}
void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}
Time Time::Sum(const Time &t) const 
//参数是引用,但返回类型不是,参数声明为引用速递将更快,使用内存更少
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}
void Time::Show() const
{
	cout << hours << " hours, " << minutes << " minutes";
}

int main()
{
	Time planning;
	Time coding(2, 40);
	Time fixing(5, 55);
	Time total;

	cout << "planning time = ";
	planning.Show();
	cout << endl;

	cout << "coding time = ";
	coding.Show();
	cout << endl;

	cout << "fixing time = ";
	fixing.Show();
	cout << endl;

	total = coding.Sum(fixing);
	cout << "coding.Sum(fixing) = ";
	total.Show();
	cout << endl;

	system("pause");
	return 0;
}
输出:
planning time = 0 hours, 0 minutes
coding time = 2 hours, 40 minutes
fixing time = 5 hours, 55 minutes
coding.Sum(fixing) = 8 hours, 35 minutes

Sum()函数的返回值不能是引用,因为函数将创建一个新的Time()对象,来表示另外两个Time对象的和,返回对象将创建对象的副本,而调用函数可以使用它。然而如果返回类型为Time &,则引用的将是sum对象。但由于sum对象是局部变量,在函数结束时将被删除,因此引用将指向一个不存在的对象。使用返回类型Time意味着程序将删除sum之前构造它的拷贝,调用函数将得到该拷贝。
注意: 不要返回指向局部变量或临时对象的引用。函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据。

11.2.1 添加加法运算符

将Time类转换为重载的加法运算符很容易,只要将Sum()名称改为operator +()即可

#include <iostream>
#include <cstdlib>
using namespace std;
class Time
{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h, int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0, int m = 0);
	Time operator +(const Time &t) const;
	void Show() const;
};
Time::Time() { hours = minutes = 0; }
Time::Time(int h, int m)
{ 
	hours = h;
	minutes = m;
}
void Time::AddMin(int m)
{
	minutes += m;
	hours += minutes / 60;
	minutes %= 60;
}
void Time::AddHr(int h)
{
	hours += h;
}
void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}
Time Time::operator+(const Time &t) const
//operator +()也是由Time对象调用的,它将第二个Time对象作为参数,并返回一个Time对象
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}
void Time::Show() const
{
	cout << hours << " hours, " << minutes << " minutes";
}
int main()
{
	Time planning;
	Time coding(2, 40);
	Time fixing(5, 55);
	Time total;

	cout << "planning time = ";
	planning.Show();
	cout << endl;

	cout << "coding time = ";
	coding.Show();
	cout << endl;

	cout << "fixing time = ";
	fixing.Show();
	cout << endl;

	total = coding + fixing; //total = coding.operator+(fixing);也可以
	cout << "coding + fixing = ";
	total.Show();
	cout << endl;

	Time morefixing(3, 28);
	cout << "more fixing time = ";
	morefixing.Show();
	cout << endl;
	total = morefixing.operator+(total);
	cout << "morefixing.operator+(total) = ";
	total.Show();
	cout << endl;
	system("pause");
	return 0;
}
输出:
planning time = 0 hours, 0 minutes
coding time = 2 hours, 40 minutes
fixing time = 5 hours, 55 minutes
coding + fixing = 8 hours, 35 minutes
more fixing time = 3 hours, 28 minutes
morefixing.operator+(total) = 12 hours, 3 minutes

operator +()函数的名称使得可以使用函数表示法或运算符表示法来调用它。编译器将根据操作数的类型来确定如何做:

int a, b, c;
Time A, B, C;
c = a + b;
C = A + B;

可以将两个以上的对象相加吗?比如假如t1,t2,t3,t4都是Time对象,能这样吗?

t4 = t1 + t2 + t3;

先想想上述语句将被如何转换为函数调用,由于+是从左向右结合的运算符,因此上述语句首先被转换成这样:t4 = t1.operator+(t2 + t3);然后,函数参数本身被转换成一个函数调用,结果如下:

t4 = t1.operator+(t2.operator+(t3));

上述语句是合法的。

11.2.2 重载限制

  • 重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符,因此不能将减法运算符重载为计算两个double值的和,而不是他们的差。这种限制可以确保程序正常运行。
  • 使用运算符时不能违反运算符句法的规则,例如,不能将求模运算符重载成使用一个操作数:
int x;
Time shiva;
% x;    //无效!
% shiva;//无效!

同样,不能修改运算符的优先级。因此,如果将加号运算符重载成将两个类相加,则新的运算符与原来的加号具有相同的优先级

扫描二维码关注公众号,回复: 10633953 查看本文章
  • 不能创建新运算符。例如,不能定义operator **()函数来表示求幂。
  • 不能重载以下运算符:
  • size:sizeof运算符。
  • . :成员运算符
  • . *:成员指针运算符
  • :::作用域解析运算符
  • ?::条件运算符
  • typeid:一个RTTI运算符
  • const_cast:强制类型转换运算符
  • dynamic_cast:强制类型转换运算符
  • reinterpret_cast:强制类型转换运算符
  • static_cast:强制类型转换运算符

下面的运算符只能通过成员函数进行重载:

  • =:赋值运算符
  • ():函数调用运算符
  • []:下标运算符
  • ->:通过指针访问类成员的运算符

11.2.3 其他重载运算符

还有将两个时间相减或将时间乘以一个因子,这需要重载减法和乘法。也就是将:

Time operator-(const Time &t) const;
Time operator*(double n) const;

的原型添加到声明中。

#include <iostream>
using namespace std;
class Time
{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h, int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0, int m = 0);
	Time operator+(const Time &t) const;
	Time operator-(const Time &t) const;
	Time operator*(double n) const;
	void Show() const;
};
Time::Time() { hours = minutes = 0; }
Time::Time(int h, int m)
{ 
	hours = h;
	minutes = m;
}
void Time::AddMin(int m)
{
	minutes += m;
	hours += minutes / 60;
	minutes %= 60;
}
void Time::AddHr(int h)
{
	hours += h;
}
void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}
Time Time::operator+(const Time &t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}
Time Time::operator-(const Time &t) const
{
	Time diff;
	int tot1, tot2;
	tot1 = t.minutes + 60 * t.hours;
	tot2 = minutes + 60 * hours;
	diff.minutes = (tot2 - tot1) % 60;
	diff.hours = (tot2 - tot1) / 60;
	return diff;
}
Time Time::operator*(double mult) const
{
	Time result;
	long totalminutes = hours * mult * 60 + minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}
void Time::Show() const
{
	cout << hours << " hours, " << minutes << " minutes";
}
int main()
{
	Time weeding(4, 35);
	Time waxing(2, 47);
	Time total;
	Time diff;
	Time adjusted;

	cout << "weeding time = ";
	weeding.Show();
	cout << endl;

	cout << "waxing time = ";
	waxing.Show();
	cout << endl;

	cout << "total work time = ";
	total = weeding + waxing;
	total.Show();
	cout << endl;

	diff = weeding - waxing;
	cout << "weeding time - waxing time = ";
	diff.Show();
	cout << endl;

	adjusted = total * 1.5;
	cout << "adjusted work time = ";
	adjusted.Show();
	cout << endl;
	return 0;
}
weeding time = 4 hours, 35 minutes
waxing time = 2 hours, 47 minutes
total work time = 7 hours, 22 minutes
weeding time - waxing time = 1 hours, 48 minutes
adjusted work time = 11 hours, 3 minutes

11.3 友元

C++控制对类对象私有部分的访问。通常公有类方法提供唯一的访问途径,但有时候这种限制太严格了。这种情况下C++提供了另外一种形式的访问权限:友元,一共与3种友元:

  • 友元函数
  • 友元类
  • 友元成员函数

通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。
我们要明白为什么需要友元。在为类重载二元运算符时,我们经常需要友元,将Time对象乘以实数就属于这种情况。
就像前面重载运算符,加法和减法都结合两个Time值,而乘法运算符将一个Time值和一个double值结合,这限制了该运算符的使用方式。就如下面的语句:

A = B * 2.75;

将被转换为下面的成员函数调用:

A = B.operator*(2.75);

但是如果反过来:

A = 2.75 * B;

会怎么样呢?
一般来说A = B * 2.75和A = 2.75 * B是相同的,但是A = 2.75 * B不对应于成员函数,因为2.75不是Time类型的对象。所以要记住,左边的操作数应是调用对象,但2.75不是对象。因此编译器不能使用成员函数调用来替换该表达式。
解决这一问题的方法就是强制告诉自己只能写成B * 2.75的格式。
但是,还有一种方法——非成员函数(大多数运算符都可以通过成员或非成员函数来重载)。非成员函数不是由对象调用的,它使用的所有值(包括对象)都是显式参数,这样,编译器能够将2.75 * BA = operator*(2.75, B);,该函数原型如下:Time operator*(double m, const Time & t)对于非成员重载运算符函数来说,运算符表达式左边的操作数对应于运算符函数的第一个参数,运算符表达式右边的操作数对应于运算符函数的第二个参数。而原来的成员函数则按相反的顺序处理操作数,也就是说,double值乘以Time值。
使用非成员函数可以按所需的顺序获得操作数(先是double,然后是Time),但引发了一个新问题:非成员函数不能直接访问类的私有数据,至少常规非成员函数不能访问。然而有一类特殊的非成员函数可以访问类的私有成员,它们被称为友元函数。

11.3.1 创建友元

创建友元函数的第一步是将其原型放在类声明中,并在原型声明前加上关键字friend:

friend Time operator*(double m, const Time &t);

该原型意味着:

  • 虽然operator *()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用
  • 虽然operator *()函数不是成员函数,但它与成员函数的访问权限相同,

接着编写函数定义。因为不是成员函数,所以不要用Time::限定符,且不要在定义中使用关键字friend:

Time operator*(double m, const Time &t)
{
	Time result;
	long totalminutes = t.hours * mult * 60 + t.minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}

这样定义以后,A = 2.75 * B;就可以转换为A = operator*(2.75, B);
类的友元函数不是成员函数,但是访问权限和成员函数相同。

11.3.2 常用的友元:重载<<运算符

p392扩展了解

11.4 重载运算符:作为成员函数还是非成员函数

对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载。一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据。例如Time类的加法运算符在Time类声明中的原型:

Time operator+(const Time &t) const;

这个类也可以使用下面的原型:

friend Time operator+(const Time &t1, const Time &t2);

加法运算符需要两个操作数,对于成员函数版本来说,一个操作数通过this指针隐式的传递,另一个操作数作为函数参数显式地传递;对于友元版本来说,这两个操作数都作为参数来传递。
注意: 非成员版本的重载运算符函数所需的形参数目与运算符使用的参数数目相同;而成员版本所需的参数数目少一个,因为i其中的一个操作数是被隐式地传递的调用对象。
这两个原型都与表达式T2+T3匹配,其中T2和T3都是Time类型对象。也就是说,编译器将下面的语句:

T1 = T2 + T3;

转换为下面两个中的任何一个:

T1 = T2.operator+(T3);
T1 = operator+(T2, T3);

记住,在定义运算符时,必须选择其中一种格式,而不能同时选择这两种格式。因为这两种格式都与同一个表达式匹配,同时定义这两种格式将被视为二义性错误,导致编译错误。

11.5 再谈重载:一个矢量类

p398扩展了解

发布了101 篇原创文章 · 获赞 1 · 访问量 1961

猜你喜欢

转载自blog.csdn.net/weixin_43318827/article/details/105008592
今日推荐