运算符重载
C++中的运算符本质上就是函数,函数可以重载,那么运算符也是可以重载的,C++允许将运算符重载扩充到用户定义的类型,例如可以使用+
将两个对象加起来。
要重载运算符,要使用运算符函数。它的形式是:
operatorop(argument-list)
如果一个类重载了+
,a
,b
,c
是三个对象。则我们可以使用:
上面这两种调用+
,是等价的。
上面代码也是可行的,因为+
是从左到右结合的上面式子等价于
d=a.operator +(b+c); 进一步的 d=a.operator +(b.operator +(c));
例子:重载+
、-
、*
的类
我们设计一个类,用来表示时间的数据类型,时间的加法减法乘法可以通过重载运算符的方式。
#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
#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; }
#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分钟
重载限制
多数运算符可以重载,而且重载运算符不必是成员函数,也可以是常规函数。
实际上,重载运算符确实有约束 。
可以重载的运算符如下表
+
-
*
/
%
^
&
~=
!
=
<
>
+=
-=
*=
/=
%=
^=
&=
=
<<
>>
>>=
<<=
=
!=
<=
>=
&&
||
++
–
,
->*
->
()
[]
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.min
和t.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; }
友元函数的定义和常规函数一样,
它没有friend
关键词.
虽然友元函数的原型在类声明中,但是它不是类成员,自然也不需要作用域解析符::
.
友元函数的独特之处在于,它可以访问数据t.min
和t.hour
总之,如果要给类重载运算符,并将非类项,作为其第一个操作数,可以用友元函数反转操作数顺序!
常用友元:重载<<
运算符
首先<<
是个什么运算符?他可以是移位符号,也可以是ostream
类中的重定向符。
我们希望使用
Time a={13 ,45 }; std::cout<<a;
上面这种方式直接显示对象,我们首先了解一下<<
的特性。
ostream
是一个类,cout
,cerr
都是它的对象。
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
类:
#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
#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; }
#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
模板类
而是在工程中,我们常说的矢量:有长度和方向的量。
#ifndef _VECTOR_H_ #define _VECTOR_H_ #include <iostream> namespace VECTOR{ class Vector { public : enum Mode {RECT,POL}; private : double x; double y; double mag; double ang; 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 () ; Vector operator +(const Vector& b)const ; Vector operator -(const Vector& b)const ; Vector operator -()const ; Vector operator *(double n)const ; friend Vector operator *(double n,const Vector& a); friend std::ostream & operator <<(std::ostream &os,const Vector &a ); }; } #endif
#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); } 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; } 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}; } Vector Vector::operator -()const { return {-x,-y}; } Vector Vector::operator *(double n)const { return {n*x,n*y}; } 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; } }
Vector
类有一些特性。
使用状态成员
Vector
类中的mode
成员是表示状态,它会控制其他函数,而且为了得到类作用域中的常数,它采用的是枚举类来表示状态。
重载算术运算符
Vector Vector::operator +(const Vector& b)const { return {x+b.x,y+b.y}; }
return {x+b.x,y+b.y};
会调用构造函数,它是将新的x
和y
传给构造函数的方式完成运算。
对已重载的运算符再次重载
-
既可以表示减法,又可以表示负数,所以这边对于-
的重载有两个。
接下来,看一个使用Vector
类的随机漫步程序:
#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) { direction=rand ()%360 ; 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
对象,从而调用友元函数重载运算符。