运算符重载

C++中的运算符本质上就是函数,函数可以重载,那么运算符也是可以重载的,C++允许将运算符重载扩充到用户定义的类型,例如可以使用+将两个对象加起来。
要重载运算符,要使用运算符函数。它的形式是:
operatorop(argument-list)
如果一个类重载了+a,b,c是三个对象。则我们可以使用:

c=a+b;
c=a.operator+(b);

上面这两种调用+,是等价的。

d=a+b+c;

上面代码也是可行的,因为+是从左到右结合的上面式子等价于

d=a.operator+(b+c);
进一步的
d=a.operator+(b.operator+(c));

例子:重载+-*的类

我们设计一个类,用来表示时间的数据类型,时间的加法减法乘法可以通过重载运算符的方式。

//使用类1.h
#ifndef TIME
#define TIME
class Time
{
private:
int hour;
int min;
public:
Time();
Time(int h,int m);
void show() const;
Time operator+(const Time &t) const;
Time operator-(const Time &t) const;
Time operator*(double b) const;

};
#endif
//使用类1.cpp
#include"使用类1.h"
#include<iostream>
Time::Time()
{
hour=0;
min=0;
}
Time::Time(int h,int m)
{
hour=h;
min=m;
}

void Time::show() const
{
using namespace std;
cout<<hour<<"小时 "<<min<<"分钟"<<endl;
}
Time Time::operator+(const Time &t) const
{
Time sum;
sum.min=t.min+this->min;
sum.hour=t.hour+this->hour+sum.min/60;
sum.min%=60;
return sum;
}
Time Time::operator-(const Time &t) const
{
Time ret;
int tot1,tot2;
tot1=60*this->hour+this->min;
tot2=60*t.hour+t.min;
ret.min=tot1-tot2;
ret.hour=ret.min/60;
ret.min%=60;
return ret;
}
Time Time::operator*(double b) const
{
Time ret;
double tot=this->hour*60+this->min;
tot*=b;
ret.min=(int)tot;
ret.hour=ret.min/60;
ret.min%=60;
return ret;
}
//使用类1main.cpp
#include"使用类1.h"
#include<iostream>
int main()
{
using namespace std;
Time a,b,c;
a={13,10};
b={8,45};
cout<<"a: ";
a.show();
cout<<"b: ";
b.show();
c=a+b;
cout<<"a+b: ";
c.show();
c=a-b;
cout<<"a-b: ";
c.show();
c=a*1.5;
cout<<"a*1.5: ";
c.show();
c=b-a*0.5+a-b;
cout<<"b-a*0.5+a-b: ";
c.show();


}
PS D:\study\c++\path_to_c++> g++ -I .\include\ -o 使用类1 .\使用类1.cpp .\使用类1main.cpp
PS D:\study\c++\path_to_c++> .\使用类1.exe
a: 13小时 10分钟
b: 8小时 45分钟
a+b: 21小时 55分钟
a-b: 4小时 25分钟
a*1.5: 19小时 45分钟
b-a*0.5+a-b: 6小时 35分钟

重载限制

多数运算符可以重载,而且重载运算符不必是成员函数,也可以是常规函数。

实际上,重载运算符确实有约束

  1. 可以重载的运算符如下表
+ - * / % ^
& ~= ! = <
> += -= *= /= %=
^= &= = << >> >>=
<<= = != <= >= &&
|| ++ , ->* ->
() [] new delete new[] delete[]

这表格中,有些运算符是只能通过成员函数来重载的:=()[]->
2. 重载后的运算符至少有一个操作数是用户定义的类型,这防止用户为标准类型重载运算符。
3. 使用运算符时仍然遵循原来的优先级法则。
4. 不能创造新的运算符。
5. 存在不可以重载的运算符,例如.::?:sizeof;

友元函数

1.1中那个时间类,有一个缺陷:在重载*的时候,它只能识别c=a*0.5;但是不能识别c=0.5*a;这是因为我们使用类成员函数的方式重载符号的时候,调用对象只能放在运算符左边,a.operator*(0.5)a*0.5等价,而0.5*a确无法找到匹配的函数。
如何解决这个问题?
一种思路是,采用非成员函数重载运算符

Time operator*(double n,const Time& t)
{
return t*n;
}

好了,perfect!由于乘法交换律,很轻松解决这个问题。但是很多时候运算并不满足交换律,非成员函数有个致命的问题:无法访问对象的私有成员,也就是说,它无法使用数据t.mint.hour.
有没有一种可以访问对象私有成员的非成员函数?----友元函数

  • 友元是什么?

    C++控制类对象的私有部分的访问,通常我们只能用公有类方法访问私有成员,但是C++还提供的一种访问私有成员的方法----友元
    总共有三种友元:
    友元函数
    友元类
    友元成员函数

  • 友元函数的原型:

friend Time operator*(double n,const Time& t);
就是非成员函数前面加个关键词friend,但是友元函数的原型是放在类声明中的

  • 友元函数的定义

Time operator*(double n,const Time& t)
{
Time ret;
double tot=t.hour*60+t.min;
tot*=n;
ret.min=(int)tot;
ret.hour=ret.min/60;
ret.min%=60;
return ret;
}

友元函数的定义和常规函数一样,

  1. 它没有friend关键词.
  2. 虽然友元函数的原型在类声明中,但是它不是类成员,自然也不需要作用域解析符::.
  3. 友元函数的独特之处在于,它可以访问数据t.mint.hour

总之,如果要给类重载运算符,并将非类项,作为其第一个操作数,可以用友元函数反转操作数顺序!

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

首先<<是个什么运算符?他可以是移位符号,也可以是ostream类中的重定向符。
我们希望使用

Time a={13,45};
std::cout<<a;

上面这种方式直接显示对象,我们首先了解一下<<的特性。
ostream是一个类,coutcerr都是它的对象。
ostream类重载了<<,而且它对每一种标准类型它都重载了一遍<<,使其变成一个很智能的运算符,<<是一个二元运算符,他左边是一个ostream对象,右边是要输出的数据,它的返回值是一个指向ostream对象的引用。

int x=5;
double y=8;
cout<<x<<y;

cout<<x<<y写成显式调用成员函数的形式就是:
(cout.operator<<(x)).operator<<(y)
先是调用类成员函数operator<<(int),显示x的值,然后它的返回值就是cout对象,然后再次调用类成员函数operate<<(double),执行cout.operator<<(y)显示y的值。
那我们就能轻松推断出,ostream类中operator<<()函数的一种重载定义应该是:

ostream & ostream::operator<<(某标准类型){
...
return *this;
}

它的返回值就是cout,就是调用对象。所以返回类型一定是对ostream对象的引用。

  • 如何在我们定义的类中重载<<?

    绝对不能用成员函数来重载,因为用成员函数重载,那调用对象一定要在运算符左边,然而运算符左边是cout对象。所以我们采用友元函数的形式重载<<
    这个友元函数,接受两个参数,左边是ostream &类型,右边是const类的引用(不采用引用传参也行),返回类型也是ostream &

    那么这个友元函数应当这么写:

std::ostream & operator<<(std::ostream& os,const Time& t)
{
os<<t.hour<<"小时 "<<t.min<<"分钟"<<std::endl;
return os;
}

第一个参数和返回类型一定要是引用参数,因为它就是cout,第二个cons Time &主要是为了高效,也可以按值传递,不过对于类来说,我们一般都是采用引用传递或者const引用传递。
cout<<a<<b时,相当于调用operator<<(operator<<(cout,a),b)函数。

重载>>运算符

同样的,我们希望通过cin>>a;这种方式,输入对象a的值。
那么肯定也是用友元函数了

std::istream & operator>>(std::istream &is,Time &t)
{
std::cout<<"输入小时和分钟\n";
is>>t.hour>>t.min;
return is;
}

那么我们更新一下我们的Time类:

//使用类2.h
#ifndef TIME
#define TIME
#include<iostream>
class Time
{
private:
int hour;
int min;
public:
Time();
Time(int h,int m);
Time operator+(const Time &t) const;
Time operator-(const Time &t) const;
Time operator*(double b) const;
friend Time operator*(double n,const Time& t);
friend std::ostream & operator<<(std::ostream &os,const Time &t);
friend std::istream & operator>>(std::istream &is,Time &t);

};
#endif
//使用类2.cpp
#include"使用类2.h"
#include<iostream>
Time::Time()
{
hour=0;
min=0;
}
Time::Time(int h,int m)
{
hour=h;
min=m;
}

Time Time::operator+(const Time &t) const
{
Time sum;
sum.min=t.min+this->min;
sum.hour=t.hour+this->hour+sum.min/60;
sum.min%=60;
return sum;
}
Time Time::operator-(const Time &t) const
{
Time ret;
int tot1,tot2;
tot1=60*this->hour+this->min;
tot2=60*t.hour+t.min;
ret.min=tot1-tot2;
ret.hour=ret.min/60;
ret.min%=60;
return ret;
}
Time Time::operator*(double b) const
{
Time ret;
double tot=this->hour*60+this->min;
tot*=b;
ret.min=(int)tot;
ret.hour=ret.min/60;
ret.min%=60;
return ret;
}
std::ostream & operator<<(std::ostream& os,const Time& t)
{
os<<t.hour<<"小时 "<<t.min<<"分钟"<<std::endl;
return os;
}
std::istream & operator>>(std::istream &is,Time &t)
{
std::cout<<"输入小时和分钟\n";
is>>t.hour>>t.min;
return is;
}
Time operator*(double n,const Time& t)
{
Time ret;
double tot=t.hour*60+t.min;
tot*=n;
ret.min=(int)tot;
ret.hour=ret.min/60;
ret.min%=60;
return ret;
}
//使用类2main.cpp
//使用类2main.cpp
#include"使用类2.h"
#include<iostream>
int main()
{
using namespace std;
Time a={13,10};
cout<<"a: "<<a;
cin>>a;
cout<<"a: "<<a;
Time c=a*0.5;
Time d=0.5*a;
cout<<"a*0.5: "<<c;
cout<<"0.5*a: "<<d;
}
PS D:\study\c++\path_to_c++> g++ -I .\include\ -o 使用类2 .\使用类2.cpp .\使用类2main.cpp
PS D:\study\c++\path_to_c++> .\使用类2.exe
a: 13小时 10分钟
输入小时和分钟
7 15
a: 7小时 15分钟
a*0.5: 3小时 37分钟
0.5*a: 3小时 37分钟

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

例如对于+的重载
你既可以采用成员函数

Time operator+(const Time& t);

这样子,一个参数通过this指针隐式传入,另一个参数,显式传入。

也可以采用友元函数

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

这样子,两个参数都是显式传入的
这两种形式没有太大区别,不过采用友元函数的形式,应该会更好(类的类型转化时)。

再谈重载:矢量类

这个矢量类不是标准模板库(STL)中的vector模板类
而是在工程中,我们常说的矢量:有长度和方向的量。

//矢量类.h
#ifndef _VECTOR_H_
#define _VECTOR_H_
#include<iostream>
namespace VECTOR
{
class Vector
{
public:
enum Mode {RECT,POL};
private:
double x;
double y;
double mag;//length of vector
double ang;//directionof vector in degrees
Mode mode;

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;}
double yval() const{return y;}
double magval() const{return mag;}
double angval() const{return ang;}
void polar_mode();
void rect_mode();
//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 &a );
};
} // namespace VECTOR

#endif
//矢量类.cpp
#include"矢量类.h"
#include<cmath>
namespace VECTOR
{
using std::sqrt;
using std::sin;
using std::cos;
using std::atan2;
using std::atan;
using std::cout;

const double Rad_to_deg=45.0/atan(1.0);
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);
}
void Vector::set_x()
{
x=mag*cos(ang);
}
void Vector::set_y()
{
y=mag*sin(ang);
}

//pubic methods
Vector::Vector()
{
x=y=mag=ang=0.0;
mode=RECT;
}
Vector::Vector(double n1,double n2,Mode form)
{
mode=form;
switch (mode)
{
case RECT:
x=n1;
y=n2;
set_mag();
set_ang();
break;
case POL:
mag=n1;
ang=n2;
set_x();
set_y();
break;
default:
cout<<"the 3rd argument is incorrect! in Vector() --\n";
cout<<"Vector set to 0\n";
x=y=ang=mag=0;
mode=RECT;
break;
}
}
void Vector::reset(double n1,double n2,Mode form)
{
mode=form;
switch (mode)
{
case RECT:
x=n1;
y=n2;
set_mag();
set_ang();
break;
case POL:
mag=n1;
ang=n2;
set_x();
set_y();
break;
default:
cout<<"the 3rd argument is incorrect! in Vector() --\n";
cout<<"Vector set to 0\n";
x=y=ang=mag=0;
mode=RECT;
break;
}
}
Vector::~Vector()
{
}
void Vector::polar_mode()
{
mode=POL;
}
void Vector::rect_mode()
{
mode=RECT;
}
//operator overloading
Vector Vector::operator+(const Vector& b)const
{
return {x+b.x,y+b.y};
}
Vector Vector::operator-(const Vector& b)const
{
return {x-b.x,y-b.y};
}
//reverse sign of Vector
Vector Vector::operator-()const
{
return {-x,-y};
}
Vector Vector::operator*(double n)const
{
return {n*x,n*y};
}
//friends
Vector operator*(double n,const Vector& a)
{
return a*n;
}
std::ostream & operator<<(std::ostream &os,const Vector &a )
{
switch (a.mode)
{
case Vector::RECT:
os<<" (x,y) = ("<<a.x<<", "<<a.y<<")";
break;
case Vector::POL:
os<<" (m,a) = ("<<a.mag<<", "<<a.ang*Rad_to_deg<<")";
break;
default:
os<<"Vector object mode is invalid";
break;
}
return os;
}
} // namespace VECTOR

Vector类有一些特性。

  • 使用状态成员

    Vector类中的mode成员是表示状态,它会控制其他函数,而且为了得到类作用域中的常数,它采用的是枚举类来表示状态。

  • 重载算术运算符

      Vector  Vector::operator+(const Vector& b)const
    {
    return {x+b.x,y+b.y};
    }

    return {x+b.x,y+b.y};会调用构造函数,它是将新的xy传给构造函数的方式完成运算。

  • 对已重载的运算符再次重载

    -既可以表示减法,又可以表示负数,所以这边对于-的重载有两个。

接下来,看一个使用Vector类的随机漫步程序:

//矢量类_随机漫步.cpp
#include<iostream>
#include<fstream>
#include<cstdlib>
#include<ctime>
#include"矢量类.h"
int main(){
using VECTOR::Vector;
using namespace std;
srand(time(0));
double direction;
Vector step;
Vector result{0.0,0.0};
ofstream fout;
fout.open("thewalk.txt");
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)
{
/* code */
//cout<<result<<endl;
direction=rand()%360;//random integer in [0,359]
step.reset(dstep,direction,Vector::POL);
result=result+step;
steps++;
}
cout<<"After "<<steps<<" steps,the subjuct 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;
fout<<endl<<"the target distance: "<<target<<endl;
fout<<"the step length: "<<dstep<<endl;
fout<<"After "<<steps<<" steps,the subjuct has the following location:\n";
fout<<"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;
}
fout.close();
return 0;
}
PS D:\study\c++\path_to_c++> g++ -I .\include\ -o 矢量类_随机漫步 .\矢量类.cpp .\矢量类_随机漫步.cpp
PS D:\study\c++\path_to_c++> .\矢量类_随机漫步.exe
Enter target distance (q to quit): 300
Enter step length: 2
After 38973 steps,the subjuct has the following location:
(x,y) = (297.689, -37.6848)
Or:
(m,a) = (300.065, -7.21477)
Average outward distance per step = 0.00769929
Enter target distance (q to quit): 1000
Enter step length: 0.5
After 558557 steps,the subjuct has the following location:
(x,y) = (845.075, 535.457)
Or:
(m,a) = (1000.43, 32.3592)
Average outward distance per step = 0.0017911
Enter target distance (q to quit): q
Bye!

谈谈随机数,标准ANSI C库中有一个rand()函数,他会返回一个随机整数,但是这些随机数,实际上是,伪随机数,因为rand()函数是根据种子来生成随机数的,种子不变随机数序列就不变。然而srand()函数允许覆盖默认的种子值。time(0)会返回当前时间,srand(time(0))就会按照当前时间设置种子。
还有一点就是fout<<endl<<"the target distance: "<<target<<endl;这里的<<运算符也会被重载,这是因为类继承属性,可以让ostream引用指向oftream对象,从而调用友元函数重载运算符。