目录
一、运算符重载
1、什么是运算符重载
运算符重载将重载概念扩展到运算符上。允许C++运算符多种含义。可以定义一个表示数组的类,并使用+重载运算符,将两个数组相加。
evening=sam+janet;
要使用这种重载运算符,需要使用被称为运算符函数的特殊函数形式,其格式如下:
operator(argument-list)
operator+()将重载+运算符,operator*()将重载*运算符。这样子,当将两数组对象相加时,实际上调用operator函数。
evening=sam+janet;//等价于
evening=sam.operator+(janet);
2、运算符重载处理时分相加
下面以一个例子来学习运算符重载
class Time
{
private:
int hours;
int minutes;
public:
Time operator+(const Time & t) const;
......
}
定义了一个处理时分相加的类,其中operator+(const Time & t)使用了运算符重载。
Time Time::operator+(const Time & t) const
{
Time sum;
sum.minutes=minutes+t.minutes;
sum.hours=hours+t.hours+sum.minutes;
sum.minutes%=60;
return sum;
}
具体的函数定义如上,将传入的显式对象和隐式调用对象时分相加
Time a,b,c,d;
c=a+b;
d=a+b+c;
这样就可以使用+运算符重载,处理时分相加,上面等价于
c=a.operator+(b);
d=a.operator+(b.operator+(c));
3、重载限制
运算符存在以下限制:
①重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。例如不可以将-运算符重载为计算两个double的和。
②使用运算符时不能违反运算符原来的句法规则,不能修改运算符的优先级
③不能创建新的运算符
④不能重载以下运算符:
sizeof、.(成员运算符)、.*(成员指针运算符)、::(作用域解析运算符)、?:(条件运算符)、typeid(一个RTTI运算符)、const_cast(强制类型转换运算符)、dynamic_cast(强制类型转换运算符)、reinterpret_cast(强制类型转换运算符)、static_cast(强制类型转换运算符)。
⑤大多数运算符都可以通过成员或非成员函数进行重载,但下面运算符只能通过成员函数进行重载:
=(赋值运算符)、()(函数调用运算符)、[ ]、下标运算符、->(通过指针访问类运算符)
二、友元
1、什么是友元
在前面说到,类私有对象通常只能由公有类方法提供唯一访问途径,不过C++提供了另外一种形式的访问权限:友元,友元有3种:友元函数、友元类、友元成员函数。
下面介绍友元函数,其他两类后面再看。
2、友元函数
使用前面运算符重载,使用下面语句可以将两个对象的时间相加。
Timer a,b,c;
c=a+b;
//等价于
c=a.operator+(b);
a=b*2.75;
//等价于
a=b.operator*(2.75)
使用如下语句时,由于2.75不是对象,没法调用上述函数
a=2.75*b;
这时可以通过非成员函数来解决,由于非成员函数不是由对象调用的,所以使用的所有值均是显式的。以上语句可以编写一个如下的非成员函数原型。
Time operator*(double m,const Time & t);
这样使得a=2.75*b等价于调用如下语句
a=operator*(2.75,b);
但是由于非成员函数无法直接访问私有数据,使用上述函数,即使传入Time的对象,也无法访问相关数据。这时使用友元函数可以很好解决这问题。
(1)创建友元
创建友元函数第一步是将原型放在类声明中,并在原型前面加上关键字friend,这样意味着operator*()函数虽然在类声明中,但是不是成员函数,不能使用成员运算符来调用,不过具有访问私有数据的权限。
friend Time operator*(double n,const Time & t);
第二步是编写函数定义,由于不是成员函数,不需要使用Timer::限定符,另外定义的时候不用加friend
friend Time operator*(double mult,const Time & t)
{
Timer result;
long totalminutes=t.hours*mult*60+t.minutes*mult;
result.hours=totalminutes/60;
result.minutes=totalminutes%60;
return result;
}
有了上述定义后,再次使用如下语句将调用刚才定义的非成员友元函数
a=2.75*b;
//等价于
a=operator*(2.75,b);
(2)常用的友元:重载<<运算符
常用的输出语句cout<<...中的<<即使用了运算符重载,cout是一个ostream对象,而在ostream类中定义了对应每种基本类型的重载的operator<<()定义,使得cout可以识别所有基本类型并输出在终端。若要使cout能够识别Timer对象,则同样可通过运算符重载让cout知道如何输出Timer,不过由于不太适合修改iostream文件。我们可以将其定义放在Timer类声明中,而当operator<<()定义为Timer成员函数时,意味着需要使用如下语句,Timer对象才会是隐式。
Timer trip;
trip<<cout;
//等价于
trip.operator<<(cout)
这样看着比较奇怪,我们还是习惯于cout在前面,这时则可以引入友元函数;
void operator<<(ostream & os,const Timer & t)
{
os<<t.hour<<"hours, "<<t.minutes<<" minutes";
return os
}
//定义完即可使用如下语句
Timer trip;
cout<<trip;
//由于返回的是os对象,则还可以进行如下语句:
//int x,y;
cout<<"x="<<x<<trip",y="<<y;
最后的语句执行流程为:执行cout<<"x="返回cout对象,执行cout<<x返回cout对象,执行cout<<trip返回cout对象......
四、重载运算符:作为成员函数还是非成员函数
对于很多运算符来说,可以使用成员函数和非成员函数实现运算符重载。而非成员函数一般使用友元函数,这可以访问对象的私有数据。如下两种运算符重载方式等价,不过一个使用成员函数,一个不是。
Timer operator+(const Timer & t) const;//成员函数
friend Timer operator+(conat Timer & t1,const Timer & t2);非成员友元函数
对于某些运算符来说,只能使用成员函数重载,而其他情况下,两种格式没有很大区别。有时,根据类设计,使用非成员函数可能更好。
五、类的自动转换和强制类型转换
1、基本类型到类类型的转换
当我们定义了一个类Stonewt,并包含3个私有数据,3个构造函数
class Stonewt
{
int stone;
double pds_left;
double pounds;
...
public
Stonewt(double lbs);
Stonewt(int stn,double lbs);
Stonewt();
...
}
则可以编写如下代码,由于19.6是浮点型,这将调用 Stonewt(double lbs)来创建一个临时Stonewt对象,并将19.6作为该构造函数参数进行初始化,之后将该临时对象的成员内容复制到myCat中。
Stonewt myCat;
myCat=19.6;
这种特性只适用于一个参数的构造函数,而由于第二个构造函数有两个参数,不可以使用,若给第二个参数赋默认值,则可用于转换int型
Stonewt(int stn,double lbs=0);
这种转换称为隐式转换,有时候这种自动转换会带来问题,C++11新增关键字explicit用于关闭这种特性,使用时将关键字放置于声明构造函数前即可。
explicit Stonewt(double lbs);
这样使得该构造函数只能用于如下显式强制转换
myCat=Stonewt(19.6);
myCat=(Stonewt)19.6;
若为使用explicit,还可以用于如下隐式转换:
①将 Stonewt 对象初始化为 double 值时
②将double值赋给 Stonewt 对象时
③将double 值传递给接受 Stonewt 参数的函数时
④返回值被声明为Stonewt的函数试图返回double 值时
⑤在上述任意一种情况下,使用可转换为 double 类型的内置类型时。(如将int赋给Stonewt 对象时,程序会先将int转换为double,再使用构造函数)
2、类类型到基本类型的转换
前面将数字转换为Stonewt对象,当然,也可以将对象转换为数字。不过必须使用特殊的C++运算符函数——转换函数。转换函数是用户自定义的强制类型转换,可以像使用强制类型转换一样使用它们,例如,定义了从Stonewt到double的转换函数,则可以使用如下转换
Stonewt wolfe(285.7);
//显式强制类型转换
double host=double(wolfe);
double thinker=(double)wolfe;
//隐式强制类型转换
double star=wolfe;
创建转换函数需要注意如下几点:
①转换函数必须是类方法
②转换函数不能指定返回类型
③转换函数不能有参数
创建转换函数需要使用如下格式:
operator typename();
若将Stonewt转换为int和double,只需要将如下函数声明以及函数定义放到类声明和类方法定义即可
//类声明
operator int()const;
operator double()const;
//类方法定义
Stonewt::operator int()const
{
return int(pounds+0.5);//+0.5用于四舍五入,pounds在前面类声明过,为double类型
}
Stonewt::operator double()const
{
return pounds;
}
这样,即可将对象赋值给int和double类型,不过还需要注意如下情况:
Stonewt poppins(9,2.8)
double p_wt=poppins;//可以编译,调用double转换函数
cout<<poppins<<endl;//出现错误,由于定义了两个转换函数,这里编译器不知道使用哪个,将指出二义性转换。若只定义double转换函数,则可以编译。
long=poppins;//出现错误,由于int和double都可以赋给long变量,仍会出现二义性转换
若要应用于以上场景,需要使用强制类型转换。另外,C++98中,关键字explicit不能用于转换函数,但在C++11可以,使用explicit关键字可以使得转换函数只能被显式使用。
3、转换函数和友元函数
当一个类中定义了 Stonewt(double lbs)构造函数和operator double()转换函数,以及一个用于加法的友元函数或成员函数,则会出现如下问题
//友元函数原型
friend operator+(Stonewt & n,Stonewt & t);
//成员函数原型
operator+(Stonewt & t);
Stonewt jennyst(9,12);
double pennyD=146.0;
Stonewt total;
total=penntD+jennyst;//①
total=jennyst+penntD;//②
①若只有Stonewt(double lbs),以及成员函数:语句①不会将penntD转换为临时Stonewt对象,再调用成员函数,会报错;语句②先将penntD转换为临时Stonewt对象,再将jennyst作为隐式对象调用成员函数。
②若只有Stonewt(double lbs),operator double()以及友元函数:语句①语句②会出错,编译器不知道是将jennyst转换为double类型进行单纯的加法运算还是将penntD转换为临时Stonewt对象调用友元函数。
C++的学习笔记持续更新中~
要是文章有帮助的话
就点赞收藏关注一下啦!
感谢大家的观看
欢迎大家提出问题并指正~