文章目录
运算符重载
运算符函数的格式如下:
operator op(argument-list)
假设有一个类,为他定义了一个operator+()成员函数,以重载+运算符,以便能将两个类的对象相加,如果district2、sid和、sare都是类的对象,则可以这样编写:
district2=sid+sara;
编译器发现操作数都是类对象,因此使用相应的运算符函数替换上述运算符:
district2=sid.operator+(sara);
计算时间:一个运算符重载示例
2小时35分钟和2小时40分钟相加与内置类型不匹配。下面我们将定义一个使用方法来处理加法的Time类。
头文件
#ifndef MYTIME_H_
#define MYTIME_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 Sum(const Time& t)const;
void Show()const;
};
#endif
#include"mytime.h"
#include<iostream>
Time::Time()
{
hours = minutes = 0;
}
Time::Time(int h, int m)
{
hours = h;
minutes = m;
}
void Time::AddHr(int h)
{
hours += h;
}
void Time::AddMin(int m)
{
hours += (m + minutes) / 60;
minutes = (m + minutes) % 60;
}
void Time::Reset(int h , int m )
{
hours = h;
minutes = m;
}
void Time::Show()const
{
std::cout << hours << " hours, " << minutes << " minutes";
}
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;
}
#include<iostream>
#include"mytime.h"
int main()
{
using std::cout;
using std::endl;
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;
total = coding.Sum(fixing);
cout << "coding.sum(fixing) = ";
total.Show();
cout << endl;
}
下面我们对程序进行修改,将Time类转换为重载的加法运算符。
#ifndef MYTIME_H_
#define MYTIME_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;
void Show()const;
};
#endif
#include"mytime.h"
#include<iostream>
Time::Time()
{
hours = minutes = 0;
}
Time::Time(int h, int m)
{
hours = h;
minutes = m;
}
void Time::AddHr(int h)
{
hours += h;
}
void Time::AddMin(int m)
{
hours += (m + minutes) / 60;
minutes = (m + minutes) % 60;
}
void Time::Reset(int h , int m )
{
hours = h;
minutes = m;
}
void Time::Show()const
{
std::cout << hours << " hours, " << minutes << " minutes";
}
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;
}
#include<iostream>
#include"mytime.h"
int main()
{
using std::cout;
using std::endl;
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;
total = coding + fixing;
cout << "coding.sum(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;
}
假如t1 t2 t3都是Time对象,这样做可以吗:
t4=t1+t2+t3;
将这个语句转化为函数调用首先为:
t4=t1.operator+(t2+t3);
然后,函数参数本身被转化成一个函数调用,结果如下:
t4=operator+(t2.operator+(t3));
总之返回值是t1,t2,t3的∑,这是我们所期待的。
重载限制
- 重载运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。因此不能将-重载为计算两个double的∑,而不是他们的差。
- 使用运算符时不能违反运算符原来的句法规则。例如不能将%重载成使用一个操作数,
- 不能修改运算符的优先级。
- 不能创建新运算符。例如,不能定义operator**()函数来表示求幂。
- 不能重载以下运算符
- sizeof
- .
- .*
- ::
- ?:
详细可以看cpplus p314
友元
友元函数
前面我们重载运算符+可以实现两个类的相加,对于将*重载,将Time对象乘以实数,乘法运算符将一个Time值和一个double值结合在一起。下面的语句
Time Time::operator*(double mult)const
{
}
A=B*2.75
将被转换为
A=B.operator*(2.75)
但是下面的语句呢
A=2.75*B
记住左侧的操作数应是调用对象,因此编译器不能使用成员函数来替换该表达式。
然而还有一种解决方式–非成员函数,非成员函数不是由对象调用的,它使用的所有值都是显式参数。
Time operator*(double m,const Time&t);
A=2.75*B
与下面的非成员函数调用匹配
A=opertaor*(2.75,B);
但是非成员函数不能直接访问类的私有数据,至少常规非成员函数不能访问。然而,有一类特殊的非成员函数可以访问类的私有成员,他们被称为友元函数。
创建友元
创建友元函数的第一步是将其原型放在类声明中,并在原型声明前加上关键字friend:
friend Time operator*(double m,const Time & t);
该原型意味着下面两点:
- 虽然operator*()是在类声明中声明的,但他不是成员函数,因此不能使用成员运算符来调用。
- 虽然operator*()函数不是成员函数,但是它与成员函数的访问权限相同。
下面是定义,不要再定义中使用friend,也不要用Time::限定符。
例如:
Time operator *(double m, const Time& t)
{
}
这将被转化为:
A=operator*(2.75.B)
常用的友元:重载<<运算符
要让cout识别Time对象,一种方法是将一个新的函数运算符定义添加到ostream类声明中。但修改iostream文件是一个危险的注意,这样做会在标准接口上浪费时间。相反,通过Time类声明让Time类知道如何使用cout.
要使Time类知道使用cout,必须使用友元函数。因为下面的语句使用两个对象,其中一个是osream类对象(cout):
cout<<trip;
如果使用Time成员函数来重载<<,Time对象僵尸第一个操作数,就像使用成员函数重载*运算符那样。这意味着必须这样使用<<:
tirp<<cout;
这样会令人迷惑,但是可以通过友元函数,可以像下面这样重载运算符:
void operator<<(ostream & os
例如
class Time
{
private:
int hours, minutes;
public:
Time()
{
}
Time(int h, int m)
{
hours = h;
minutes = m;
}
friend void operator<<(ostream& os, const Time& t);
friend Time operator*(double m, Time& t);
};
Time operator*(double m, Time& t)
{
Time result;
result.hours = 1;
return result;
}
void operator<<(ostream& os, const Time& t)
{
os << t.hours << " hours " << t.minutes << " minutes";
}
int main()
{
Time a(3, 25);
cout << a;
}
调用cout<<trip应使用cout对象本身,而不是它的拷贝,因此该函数按引用来传递该对象。
<<的第二种重载版本
上面的实现存在一个问题,像下面的语句无法工作:
cout<<"Trip time: "<<trip<<" (Tuesday)\n"; //can't do
对于一般的cout为什么能这样例如:
int x=5;
int y=8;
cout<<x<<y;
它等同于:
(cout<<x)<<y;
因此ostream类将operator<<()函数实现返回一个指向ostream对象的引用。具体地说,它返回一个指向调用对象(这里是cout)的引用。因此表达式(cout<<x)本身就是ostream对象cout,从而可以位于<<运算符的左侧。
因此可以对友元函数采用相同的方法。例如:
ostream& operator<<(ostream& os, const Time& t)
{
os << t.hours << " hours " << t.minutes << " minutes";
return os;
}
重载运算符:作为成员函数还是非成员函数
例如:
Time operator+(const Time&t)const;
这个类也可以使用下面的原型:
friend Time operator+(const Time & t1,const Time &t2);
但对于下面语句
T1=T2+T3
T1,T3,T2都是类对象。也就是说,编译器将转化为下面两个的任何一个:
T1=T2.operator+(T3);
T1=operator+(T2,T3);
但是在定义运算符时,必须选择其中一种格式,否则会产生二义性,导致编译错误。那么哪种格式最好呢?对于某些运算符来说,成员函数时唯一合法的选择。在其他情况下,这两种格式没有太大的区别。有时,根据类设计,使用非成员函数版本可能更好。
一个矢量类
可以用枚举RECT来表示直角坐标模式(默认值)POL表示极坐标模式,这样的成员被称为状态成员,因为这种成员描述的是对象所处的状态。例如:
#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_and();
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::iostream& operator<<(std::iostream& os, const Vector& v);
};
}
#endif
enum Mode{RECT,POL};
Vector::Vector(double n1,double n2,Mode form)
{
mode=form;
if(form==RECT)
{
}
else if(form==POL)
{
}
else
{
}
}
为Vector类重载算数运算符
在使用x、y坐标时,将两个矢量相加很简单例如:
Vector Vector::operator+(const Vector& b)const
{
Vector sum;
sum.x = x + b.x;
sum.y = y + b.x;
sum.set_ang();
sum.set_mag();
return sum;
}
下面有更简洁的方法:
Vector Vector::operator+(const Vector&b)const
{
return Vector(x+b.x,y+b.y);
}
对已重载的运算符进行重载
在c++中,-运算符有两种含义,只有一个操作数时(-x),它是负号运算符,减法运算符是有两个操作数的。
要从矢量A减去矢量B例如:
Vector operator-(const Vector &b)const
{
return Vector(x-b.x,y-b.y);
}
接下来看一元负号运算符
Vector operator-()const
{
return vector(-x,-y);
}
但对于只有二元形式的运算符(如除法运算符)只能将其重载为二元运算符。
随机数
rand()函数返回从0到某个值之间的随机整数,rand()获得的随机数是伪随机数,通常使用srand(time(0))来搭配使用。
头文件cstdlib包含了srand()和rand()的原型,ctime包含了time的原型。c++11使用头文件radom中的函数提供了更强大的随机数支持。
类的自动转换和强制类型转换
例如:
class Stonewt
{
private:
int a;
int b;
public:
Stonewt()
{
}
Stonewt(int d, int c=0)
{
a = d;
b = c;
}
};
int main()
{
Stonewt one;
one = 3;
}
程序可以使用Stonewet(int)来创建一个临时的Stonewt对象,并将3作为初始化值。然后采用逐成员赋值方式将该临时对象的内容复制到one中。,这一过程叫做隐式转换,因为它是自动进行的。
只有接受一个参数的构造函数才能作为转换函数。c++提供了关键字explicit用于关闭这种特性。也就是说,可以这样声明构造函数:
explicit Stonewt(int a);
但是还是可以允许显式的类型转换例如:
Stonewt myCat;
myCat=Stonewt(19);
转换函数
转换函数的形式为:
operator typeName();
需要注意以下几点:
- 转换函数必须是类方法;
- 转换函数不能指定返回类型;
- 转换函数不能有参数;
例如转化为double类型的函数原型如下:
operator double();
例如:
Stonewt::operator int()const
{
return int(pounds+0.5);
}
假设有以下代码:
int ar[20];
Stonewt temp(14,4);
int Tmep=1;
cout<<ar[temp];
通常编译器会报错,但是由于定义了转换函数,temp将被转化为int从而避免报错,所以说,原则上最好使用显式转换,而避免隐式转换。c++11中,可以将函数弄成显式的例如:
class Stonewt
{
explicit operator int()const;
explicit operator double()const;
};
另一种方法是用一个功能相同的非转换函数替换该函数即可。例如:
int Stonewt::Stone_to_int();
下面语句是非法的:
int a=poppins;
但是可以这样:
int a=Poppins.stone_to_int();
转换函数和友元函数
对于两个对象实现加法可以有:
Stonewt Stonewt::operator+(const Stonewt& st)const
{
double pds = pounds + st.pounds;
Stonewt sum(pds);
return sum;
}
Stonewt operator+(const Stonewt& st1, const Stonewt& st2)
{
double pds = st1.pounds + st2.pounds;
stonewt sum(pds);
return sum;
}
如果提供了Stonewt(double)函数,则可以这样
Stonewt a(1,12);
double b=2.0;
Stonewt total=a+b;
对于友元函数可以:
Stonewt c(1,23);
double d=2.0;
Stonewt total=d+c;
但是如果定义了operator double()成员函数,将造成混乱,因为编译器可以将对象转化成double类型从而实现两个double的加法。导致二义性。
对于
total=b+d;
被转化为
total=operator+(b,d);
这两个参数都是double类型,因此将调用构造函数Stonewt(double),但是不能调用成员函数来相加,这没有意义。c++不会试图将b转化成Stonewt对象因此定义为友元更好。