运算符重载是一种形式的C++多态
运算符重载将充值该的概念扩展到运算符上,允许赋予C++运算符多种含义。
C++允许将运算符重载扩展到用户定义类型,例如,允许使用+将两个对象相加。
要重载运算符,需使用被称为运算符函数的特殊函数形式。运算符函数的格式如下:
operator op(argument-list)
例如,operator+()重载+运算符,operator*()重载*运算符。op必须是有效的C++运算符,不能虚构一个新的符号。例如,不能有operator@()这样的函数,因为C++中没有@运算符。然而,operatoe函数将重载[]运算符,因为[]是数组索引运算符。
假设有一个Salesperson类,并为它定义了一个operator+()成员函数,以重载+运算符,以便能够将两个Salesperson对象的销售额相加,如果district2, sid, and sara都是Salesperson类的对象,便可以编写这样的等式。
district2 = sid + sara;
编译器发现,操作数是Salesperson类对象,因此使用相应的运算符函数替换上述运算符。
district2 = sid.operator+(sara);
该函数将隐式地调用sid(因为它调用了方法),而显式地使用sara对象(因为他被作为参数传递)
//比如这个加法
total = morefixing.operator+(total);
t4 = t1 + t2 + t3; // valid?
t4 = t1.operator+(t2 + t3); // valid?
t4 = t1.operator+(t2.operator+(t3)); // valid? YES
重载的运算符(有些例外情况)不必是成员函数,但必须至少有一个操作数是用户定义的类型,以下是函数重载地限制:
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对本书正文中没有介绍的运算符进行了总结。
#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
#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
#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;
}
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。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
不要返回指向局部变量或临时对象的引用。函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据。
大多数运算符都可以通过成员或非成员函数来重载,非成员函数是友元函数,因此它可以直接访问对象的所有私有数据。
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
记住,在定义运算符时,必须选择其中的一种格式,而不能同时选择这两种格式。因为这两种格式都与同一个表达式匹配,同时定义这两种格式将被视为二义性错误,导致编译错误。
那么哪种格式最好呢?对于某些运算符来说(如前所述),成员函数是唯一合法的选择。在其他情况下,这两种格式没有太大的区别。有时,根据类设计,使用非成员函数版本可能更好(尤其是为类定义类型转换时)
矢量(vector)是工程和物理中使用的一个术语,它是一个有大小和方向的量。
可以用不同但等价的方式表示一个实体。
类非常适于在一个对象中表示实体的不同方面。首先在一个对象中存储多种表示方式;然后,编写这样的类函数,以便给一种表示方式赋值时,将自动给其他方式赋值。
如果方法通过计算得到一个新的类对象,则应考虑是否可以使用类构造函数来完成这种工作。这样做不仅可以避免麻烦,而且可以确保新的对象时按照正确的方式创建的。
在C++中,-运算符有两种含义;首先,使用两个操作数,它是减法运算符。减法运算是一个二元运算符,因为它有两个操作数。其次,使用一个操作数时(如-x),它是负号运算符。这种形式被称为一元运算符。即只有一个操作数。对于矢量来说,这两种操作都是有意义的,因此Vector类要有这两种操作。
因为运算符重载是通过函数来实现的,所以只要运算符函数的参数不同,使用的运算符数量与相应的内置C++运算符相同,就可以多次重载同一个运算符。
Vector对象可以只存储x和y分量,而返回矢量长度的magval()方法可以根据x和y的值来计算出长度,而不是查找对象中存储的这个值。这种方法改变了实现,但用户接口不变。将接口与实现分离是OOP的目标之一,这样允许对实现进行调整,而无需修改使用这个类的程序中的代码。
一种实现是只存储直角坐标,另一种实现是存储直角坐标和极坐标。这两种实现各有利弊,存储数据意味着对象将占据更多的内存,每次Vector对象被修改时,都需要更新直角坐标和极坐标表示;但查找数据的速度比较快。如果应用程序经常需要访问矢量的这两种表示,则第二种实现比较合适;如果只是偶尔使用极坐标,则另一种更好。可以在一个程序中使用一种实现,而在另一个程序中使用另一种实现,但他们的用户接口相同。
将一个人领到一个街灯柱下,这个人开始走动,但每一步的方向都是随机的。提出的问题是:这个人走到离灯柱50英尺处需要多少步,从矢量的角度看,这相当于不断将方向随机的矢量相加,直到长度超过50英尺。
#pragma once
//添加命名空间是为了复习之前命名空间的内容
// vect.h -- Vector class with <<, mode state
#ifndef VECTOR_H_
#define VECTOR_H_
#include
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
#include "vect.h" // includes
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
#include // rand(), srand() prototypes
#include // 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;
}
这个程序用一个变量来表示位置(一个矢量),并报告到达只当距离处(用两种格式表示)所需的步数。
标准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中的函数提供了更强大的随机数支持。