C++类的运算符重载

11.8 类的运算符重载

运算符重载是一种形式的C++多态

运算符重载将充值该的概念扩展到运算符上,允许赋予C++运算符多种含义。

C++允许将运算符重载扩展到用户定义类型,例如,允许使用+将两个对象相加。

11.8.1 操作符重载定义

要重载运算符,需使用被称为运算符函数的特殊函数形式。运算符函数的格式如下:

operator op(argument-list)

例如,operator+()重载+运算符,operator*()重载*运算符。op必须是有效的C++运算符,不能虚构一个新的符号。例如,不能有operator@()这样的函数,因为C++中没有@运算符。然而,operatoe函数将重载[]运算符,因为[]是数组索引运算符。

11.8.2 调用重载的操作符

11.8.2.1 使用重载的操作符(常用)

假设有一个Salesperson类,并为它定义了一个operator+()成员函数,以重载+运算符,以便能够将两个Salesperson对象的销售额相加,如果district2, sid, and sara都是Salesperson类的对象,便可以编写这样的等式。

district2 = sid + sara;

编译器发现,操作数是Salesperson类对象,因此使用相应的运算符函数替换上述运算符。

district2 = sid.operator+(sara);

该函数将隐式地调用sid(因为它调用了方法),而显式地使用sara对象(因为他被作为参数传递)

11.8.2.2 使用operatorop()函数名
//比如这个加法
total = morefixing.operator+(total);
11.8.2.3 是否可以实现连加(or 连减、连乘…)
t4 = t1 + t2 + t3; // valid?
t4 = t1.operator+(t2 + t3); // valid?
t4 = t1.operator+(t2.operator+(t3)); // valid? YES

11.8.3 重载限制

重载的运算符(有些例外情况)不必是成员函数,但必须至少有一个操作数是用户定义的类型,以下是函数重载地限制:

1.重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。因此,不能将减法运算符(-)重载为计算两个double值的和,而是他们的差。虽然这种限制会对创造性有所影响,但可以确保程序正常运行。

2.使用运算符时不能违反运算符原来的句法规则。例如,不能将求模运算符(%)重载成为一个操作数。

int x;
Time shiva;
% x; // invalid for modulus operator
% shiva; // invalid for overloaded operator

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

4.不能创建新运算符。例如,不能定义operator**()函数来表示求幂。

5.不能重载以下运算符:

在这里插入图片描述

6.表11.1中的大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载:

在这里插入图片描述

7.本章不介绍这里列出的所有运算符,但附录E对本书正文中没有介绍的运算符进行了总结。

11.8.4 举例

11.8.4.1 源代码
#pragma once
// mytime0.h -- Time class before operator overloading
#ifndef MYTIME0_H_
#define MYTIME0_H_
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;
};
#endif
// mytime0.cpp -- implementing Time methods
#include <iostream>
#include "mytime0.h"
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
{
	std::cout << hours << " hours, " << minutes << " minutes";
}
// usetime0.cpp -- using the first draft of the Time class
// compile usetime0.cpp and mytime0.cpp together
#include <iostream>
#include "mytime0.h"

int main()
{
	using std::cout;
	using std::endl;
	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; // use operator+()
	total.Show();
	cout << endl;
	diff = weeding - waxing; // use operator-()
	cout << "weeding time - waxing time = ";
	diff.Show();
	cout << endl;
	adjusted = total * 1.5; // use operator*()
	cout << "adjusted work time = ";
	adjusted.Show();
	cout << endl;
	return 0;
}
11.8.4.2 运行结果
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

D:\Prj\C++\Function_OverLoading\Debug\Function_OverLoading.exe (进程 3524)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
11.8.4.3 注意事项

不要返回指向局部变量或临时对象的引用。函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据。

11.8.5 重载运算符: 成员函数重载VS非成员函数重载

大多数运算符都可以通过成员或非成员函数来重载,非成员函数是友元函数,因此它可以直接访问对象的所有私有数据。

Time operator+(const Time & t) const; // member version
// nonmember version
friend Time operator+(const Time & t1, const Time & t2);

加法运算符需要两个操作数。对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显式地传递;对于友元版本来说,两个操作数都为参数来传递。

T1 = T2 + T3;
T1 = T2.operator+(T3); // member function
T1 = operator+(T2, T3); // nonmember function

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

那么哪种格式最好呢?对于某些运算符来说(如前所述),成员函数是唯一合法的选择。在其他情况下,这两种格式没有太大的区别。有时,根据类设计,使用非成员函数版本可能更好(尤其是为类定义类型转换时)

11.8.6 矢量类操作符重载

矢量(vector)是工程和物理中使用的一个术语,它是一个有大小和方向的量。

11.8.6.1 多种表示方式和类

可以用不同但等价的方式表示一个实体。

类非常适于在一个对象中表示实体的不同方面。首先在一个对象中存储多种表示方式;然后,编写这样的类函数,以便给一种表示方式赋值时,将自动给其他方式赋值。

11.8.6.2 为Vector类重载算数运算符

如果方法通过计算得到一个新的类对象,则应考虑是否可以使用类构造函数来完成这种工作。这样做不仅可以避免麻烦,而且可以确保新的对象时按照正确的方式创建的。

11.8.6.3 对已重载的运算符进行重载

在C++中,-运算符有两种含义;首先,使用两个操作数,它是减法运算符。减法运算是一个二元运算符,因为它有两个操作数。其次,使用一个操作数时(如-x),它是负号运算符。这种形式被称为一元运算符。即只有一个操作数。对于矢量来说,这两种操作都是有意义的,因此Vector类要有这两种操作。

因为运算符重载是通过函数来实现的,所以只要运算符函数的参数不同,使用的运算符数量与相应的内置C++运算符相同,就可以多次重载同一个运算符。

11.8.6.4 对实现的说明

Vector对象可以只存储x和y分量,而返回矢量长度的magval()方法可以根据x和y的值来计算出长度,而不是查找对象中存储的这个值。这种方法改变了实现,但用户接口不变。将接口与实现分离是OOP的目标之一,这样允许对实现进行调整,而无需修改使用这个类的程序中的代码。

一种实现是只存储直角坐标,另一种实现是存储直角坐标和极坐标。这两种实现各有利弊,存储数据意味着对象将占据更多的内存,每次Vector对象被修改时,都需要更新直角坐标和极坐标表示;但查找数据的速度比较快。如果应用程序经常需要访问矢量的这两种表示,则第二种实现比较合适;如果只是偶尔使用极坐标,则另一种更好。可以在一个程序中使用一种实现,而在另一个程序中使用另一种实现,但他们的用户接口相同。

11.8.6.5 一个实例–使用Vector类模拟随机漫步

将一个人领到一个街灯柱下,这个人开始走动,但每一步的方向都是随机的。提出的问题是:这个人走到离灯柱50英尺处需要多少步,从矢量的角度看,这相当于不断将方向随机的矢量相加,直到长度超过50英尺。

11.8.6.5.1 code
#pragma once
//添加命名空间是为了复习之前命名空间的内容
// vect.h -- Vector class with <<, mode state
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
namespace VECTOR
{
	class Vector
	{
	public:
		enum Mode { RECT, POL };
		// RECT for rectangular, POL for Polar modes
	private:
		double x; // horizontal value
		double y; // vertical value
		double mag; // length of vector
		double ang; // direction of vector in degrees
		Mode mode; // RECT or POL---Such a member is termed a state member because it describes the state an object is in.
		// private methods for setting values
		void set_mag();
		void set_ang();
		void set_x();
		void set_y();
	public:
		Vector();
		Vector(double n1, double n2, Mode form = RECT);
		void reset(double n1, double n2, Mode form = RECT);
		~Vector();
		double xval() const { return x; } // report x value
		double yval() const { return y; } // report y value
		double magval() const { return mag; } // report magnitude
		double angval() const { return ang; } // report angle
		void polar_mode(); // set mode to POL
		void rect_mode(); // set mode to RECT
		// operator overloading
		Vector operator+(const Vector& b) const;
		Vector operator-(const Vector& b) const;
		Vector operator-() const;
		Vector operator*(double n) const;
		// friends
		friend Vector operator*(double n, const Vector& a);
		friend std::ostream& operator<<(std::ostream& os, const Vector& v);
	};
} // end namespace VECTOR
#endif
// vect.cpp -- methods for the Vector class
#include <cmath>
#include "vect.h" // includes <iostream>
using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;
namespace VECTOR
{
	// compute degrees in one radian
	const double Rad_to_deg = 45.0 / atan(1.0);
	// should be about 57.2957795130823
	// private methods
	// calculates magnitude from x and y
	void Vector::set_mag()
	{
		mag = sqrt(x * x + y * y);
	}
	void Vector::set_ang()
	{
		if (x == 0.0 && y == 0.0)
			ang = 0.0;
		else
			ang = atan2(y, x);
	}
	// set x from polar coordinate
	void Vector::set_x()
	{
		x = mag * cos(ang);
	}
	// set y from polar coordinate
	void Vector::set_y()
	{
		y = mag * sin(ang);
	}
	// public methods
	Vector::Vector() // default constructor
	{
		x = y = mag = ang = 0.0;
		mode = RECT;
	}
	// construct vector from rectangular coordinates if form is r
	// (the default) or else from polar coordinates if form is p
	Vector::Vector(double n1, double n2, Mode form)
	{
		mode = form;
		if (form == RECT)
		{
			x = n1;
			y = n2;
			set_mag();
			set_ang();
		}
		else if (form == POL)
		{
			mag = n1;
			ang = n2 / Rad_to_deg;
			set_x();
			set_y();
		}
		else
		{
			cout << "Incorrect 3rd argument to Vector() -- ";
			cout << "vector set to 0\n";
			x = y = mag = ang = 0.0;
			mode = RECT;
		}
	}
	// reset vector from rectangular coordinates if form is
	// RECT (the default) or else from polar coordinates if
	// form is POL
	void Vector::reset(double n1, double n2, Mode form)
	{
		mode = form;
		if (form == RECT)
		{
			x = n1;
			y = n2;
			set_mag();
			set_ang();
		}
		else if (form == POL)
		{
			mag = n1;
			ang = n2 / Rad_to_deg;
			set_x();
			set_y();
		}
		else
		{
			cout << "Incorrect 3rd argument to Vector() -- ";
			cout << "vector set to 0\n";
			x = y = mag = ang = 0.0;
			mode = RECT;
		}
	}
	Vector::~Vector() // destructor
	{
	}
	void Vector::polar_mode() // set to polar mode
	{
		mode = POL;
	}
	void Vector::rect_mode() // set to rectangular mode
	{
		mode = RECT;
	}
	// operator overloading
	// add two Vectors
	Vector Vector::operator+(const Vector& b) const
	{
		return Vector(x + b.x, y + b.y);
	}
	// subtract Vector b from a
	Vector Vector::operator-(const Vector& b) const
	{
		return Vector(x - b.x, y - b.y);
	}
	// reverse sign of Vector
	Vector Vector::operator-() const
	{
		return Vector(-x, -y);
	}
	// multiply vector by n
	Vector Vector::operator*(double n) const
	{
		return Vector(n * x, n * y);
	}
	// friend methods
	// multiply n by Vector a
	Vector operator*(double n, const Vector& a)
	{
		return a * n;//这里就会调用a的成员函数*
	}
	// display rectangular coordinates if mode is RECT,
	// else display polar coordinates if mode is POL
	std::ostream& operator<<(std::ostream& os, const Vector& v)
	{
		if (v.mode == Vector::RECT)
			os << "(x,y) = (" << v.x << ", " << v.y << ")";
		else if (v.mode == Vector::POL)
		{
			os << "(m,a) = (" << v.mag << ", "
				<< v.ang * Rad_to_deg << ")";
		}
		else
			os << "Vector object mode is invalid";
		return os;
	}
} // end namespace VECTOR
// randwalk.cpp -- using the Vector class
// compile with the vect.cpp file
#include <iostream>
#include <cstdlib> // rand(), srand() prototypes
#include <ctime> // time() prototype
#include "vect.h"
int main()
{
	using namespace std;
	using VECTOR::Vector;
	srand(time(0)); // seed random-number generator
	double direction;
	Vector step;
	Vector result(0.0, 0.0);
	unsigned long steps = 0;
	double target;
	double dstep;
	cout << "Enter target distance (q to quit): ";
	while (cin >> target)
	{
		cout << "Enter step length: ";
		if (!(cin >> dstep))
			break;
		while (result.magval() < target)
		{
			direction = rand() % 360;//rand()产生随机数的范围为0~RAND_MAX,RAND_MAX值最小为32767,最大为2147483647
			step.reset(dstep, direction, Vector::POL);
			result = result + step;
			steps++;
		}
		cout << "After " << steps << " steps, the subject "
			"has the following location:\n";
		cout << result << endl;
		result.polar_mode();
		cout << " or\n" << result << endl;
		cout << "Average outward distance per step = "
			<< result.magval() / steps << endl;
		steps = 0;
		result.reset(0.0, 0.0);
		cout << "Enter target distance (q to quit): ";
	}
	cout << "Bye!\n";
	cin.clear();
	while (cin.get() != '\n')
		continue;
	return 0;
}

这个程序用一个变量来表示位置(一个矢量),并报告到达只当距离处(用两种格式表示)所需的步数。

11.8.6.5.2 程序笔记

标准ANSI C库(C++也有)中有一个rand()函数,它返回一个从0到某个值之间的随机整数。上述程序使用求模操作数来获得一个0~359的角度值。rand()函数将一种算法用于一个初始种子值来获得随机数,该随机数将用作下一次函数调用的种子。这些数实际上是伪随机数,因为10次连续的调用通常将生成10个同样的随机数。然而,srand()函数允许覆盖默认的种子值,重新启动从某一个随机数序列,该程序使用time(0)的返回值来设置种子。time(0)函数返回当前时间,通常为从某一个日期开始的秒数(更广义的,time()接受time_t变量的地址,将时间放到该变量中,并返回它。将0用作地址参数,可以省略time_t变量声明)。因此,下面的语句在每次运行程序时,都将设置不同的种子,使随机输出看上去更为随机。

srand(time(0));

头文件cstdlib(以前为stdlib.h)包含了srand()和rand()的原型,而ctime(以前是time.h)包含了time()的原型。C++11使用头文件random中的函数提供了更强大的随机数支持。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jasmine-Lily

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值