文章目录
1. 运算符重载
前面我们介绍了C++函数的多态或者称为函数重载。运算符重载是一种形式的C++多态,它将重载的概念扩展到运算符上,允许赋予C++运算符多种含义。
实际上很多C++运算符已经被重载。例如*运算符用于地址,它也可以用于两个数字相乘。C++允许将运算符重载扩展到用户定义的类型。例如我们可以重载一个数组相加的类等。但是重载的运算符必须是有效的C++运算符,例如,不能对"@"作为运算符重载。
1.1 重载加法举例
下面进行类的加法重载:
my_class.h代码如下
#ifndef MY_CLASS_H
#define MY_CLASS_H
class my_class
{
public:
my_class();
my_class(int h, int m = 0);
my_class operator+(const my_class& t) const;//加法重载
void show();
private:
int hours;
int minutes;
};
#endif // MY_CLASS_H
my_class.cpp代码如下
#include "my_class.h"
#include<iostream>
using namespace std;
my_class::my_class()
{
hours = minutes = 0;
}
my_class::my_class(int h, int m)
{
hours = h;
minutes = m;
}
my_class my_class::operator +(const my_class& t) const
{
my_class sum;
sum.minutes = minutes + t.minutes;
sum.hours = hours + t.hours + sum.minutes/60;
sum.minutes %= 60;
return sum;
}
void my_class::show()
{
cout<<"After operator +: \n";
cout<<hours<<" : "<<minutes<<endl;
}
main函数代码
#include"my_class.h"
int main()
{
my_class time1(2, 40);//2小时40分
my_class time2(3, 30);//3小时30分
my_class total;
total = time1 + time2;//重载+,调用
total.show();
}
输出:
After operator +:
6 : 10
上面的式子也可以进行三个对象的相加,因为每次相加返回的一个对象。
1.2 重载限制
- 重载后的运算符至少有一个或一个以上操作数是用户定义的。比如不能将加法运算符重载为double值的和,因为double类型不是用户定义的。
- 重载运算符时,不能违反运算符原来的句法规则,也不能修改运算符的优先级。也就是要保证原来的运算符规则。
- 不能创建新的运算符。
- 不能重载下面的运算符
- sizeof运算符
- "."成员运算符
- 作用域运算符::
- 条件运算符?:
- 一个RTTI运算符typeid
- const_cast、dynamic_cast、reinterpret_cast和static_cast强制类型转换运算符。
1.3 操作单个对象
创建操作单个对象的运算符重载,比如让一个对象乘以倍数。它的原型如下:
my_class operator* (double n) const;
定义:
my_class my_class::operator *(double n) const
{
my_class result;
long totalMinutes = hours * n * 60 + minutes * n;
result.hours = totalMinutes/60;
result.minutes = totalMinutes%60;
return result;
}
调用:
total = total * 1.5;//对象扩大1.5倍
total.show();
2. 友元函数
2.1 完善操作单个对象
除了使用公有方法的方式访问私有成员访问,我们还可以通过友元的方式进行访问。友元有三种:友元函数、友元类和友元成员函数。这里只介绍友元函数,其它两种将会在后续讲解。
我们看到上一个示例代码当中,一个对象乘以扩大因子的方式为:
A = B *2.75;
它实际上等价于:
A = B.operator*(2.75);
但是如果我们遇到A = 2.75 * B;应该怎么处理呢。因为乘法的左侧必须是调用对象,而2.75不是对象,因此编译器不能使用成员函数调用来替换该表达式。
因此就出现了非成员函数的调用方式,为了访问私有成员,就出现了友元函数。
friend my_class operator* (double m, const my_class& t);
定义重载运算符时,不需要使用作用域限定符,而且定义的时候不需要使用friend。
my_class operator *(double m, const my_class&t)
{
my_class result;
long totalMinutes = t.hours * m * 60 + t.minutes * m;//注意需要使用t.hours
result.hours = totalMinutes/60;
result.minutes = totalMinutes%60;
return result;
}
main函数调用:
total = 2 * total;
total.show();
我们还可以对上面友元函数进行修改。
my_class operator* (double m, const my_class& t)
{
return t * m;
}
2.2 常用的友元:重载<<运算符
(1)输出对象
实际上<<运算符已经被重载了很多次了,最初它是C和C++运算符的位左移。后来,ostream类对它进行了重载,将它作为一个输出工具。我们还可以对它进行重载,比如输出一个对象类型。
我们知道,如果使用my_class来进行重载,那么它的对象就是第一个操作数,则我们的输出形式将会是time << cout;这样将会变得很别扭。于是我们使用友元来解决。
friend void operator << (std::ostream& os, const my_class& t);
原型当中可以看出,cout作为第一个输入对象,而my_class为第二个对象,我们需要访问my_class的数据成员,因此需要用到my_class的友元函数,而不需要ostream的友元,只需要用到cout的别名。它的定义形式如下。
void operator << (std::ostream & os, const my_class& t)
{
os << t.hours <<" hours," <<t.minutes<<" minutes";
}
然后我们就可以正常使用<<输出对象了:
cout<<total;
(2)与常规cout结合使用
上面的形式不能用于类似于下面的操作:
cout <<"Trip time:" <<total<<endl;
我们知道cout是按从左至右输出的,所以cout<<x<<y;意味着它等同于
(cout << x)<<y;
所以我们必须要求<<运算符操作之后,返回一个ostream对象的引用。也就是说(cout << x)本身就是一个ostream对象cout,从而可以让它可以位于左侧。于是我们继续对友元函数进行修改。
//原型修改为:
friend std::ostream& operator << (std::ostream& os, const my_class& t);
//定义修改为:
std::ostream& operator << (std::ostream & os, const my_class& t)
{
os << t.hours <<" hours," <<t.minutes<<" minutes";
return os;
}
2.3 重载运算符:作为成员函数还是非成员函数
对于重载运算符,非成员函数形式一般使用友元函数形式,因为可以访问私有成员。对象相加的运算符重载,下列形式效果一样。
my_class operator+ (const my_class& t) const;//成员函数形式
friend my_class operator* (const my_class& t1, const my_class& t2);//友元函数形式
加法运算符需要两个操作数。对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显式传递。对于友元版本来说,两个操作数都作为参数来传递。
3. 类的自动转换和强制类型转换
我们先来复习一下C++是如何处理内置类型转换的:如果两个标准类型兼容,则将C++自动将这个值转换为接收变量的类型。例如
long count = 8;//自动将8转换为long
double time = 11;//自动将11转换为double
int side = 3.33;//自动将3.33转换为int
C++不自动转换不兼容的类型,例如
int *p = 10;//不能进行转换,错误!!!
但是在C++中我们可以使用强制类型转换,将10强制转换为int指针类型,将指针设置为地址10,这种转换有没有意义就是另一回事了。
int * p = (int *) 10;
3.1 对象自动隐式转换
如果有这样一个构造函数:
Stonewt::Stonewt(double lbs)
{
stone = int(lbs) / Lbs_per_stn;
pds_left = int(lbs) % Lbs_per_stn + lbs - int(lbs);
pounds = lbs;
}
那么它可以这么调用构造函数:
Stonewt myCat(19.6);
还有一种隐式转换的调用方法:
Stonewt myCat;
myCat = 19.6;//隐式转换
如果有两个以上参数的构造函数,就不能使用隐式转换。如果不需要这种隐式转换,可以在定义构造函数时,添加关键字explicit,用于关闭这种自动特性。
explicit Stonewt(double lbs);//不允许隐式转换!!!
但是,我们还可以进行显式转换,即显式强制类型转换。
Stonewt myCat;
myCat = Stonewt(19.6);
//或者
myCat = (Stonewt) 19.6;
3.2 转换函数
以上可以将double转换为Stonewt类型,那么可以将Stonewt转换为double类型吗?这里就要用到C++特殊的运算符函数——转换函数。
转换函数是用户定义的强制类型转换,可以像使用强制类型类型转换那样使用它们。它的定义方式:
operator typeName();//typeName为要转换的类型
请注意以下几点:
- 转换函数必须是类方法;
- 转换函数不能指定返回类型;
- 转换函数不能有参数;
我们在类中定义int和double两种转换函数:
operator double()const;//转换为double类型
operator int() const;//转换为int类型
它们的实现:
Stonewt::operator double()const
{
return pounds;
}
Stonewt::operator int()const
{
return int (pounds + 0.5);//pounds为类的数据成员,也就是对象的数据成员
}
main函数中的使用:
int x = int(myCat);
//或者
x = myCat;
cout<<x<<endl;
如果只定义了一种转换函数,我们还可以配合cout输出对象(自动转换),因为没有其它转换函数使得造成歧义。但是如果有两个以上的转换函数,将会造成歧义,导致编译错误。
cout << myCat<<endl;//如果没有歧义的情况下,可以使用
long gone = myCat;//如果没有歧义的情况下,可以使用
在C++11中,增加了explicit用于转换函数,这样的话,就必须显示的使用转换函数。当然读者也可以自己编写普通的成员函数,而不是使用转换函数!
3.3 转换函数和友元函数
下面我们为Stonewt类重载加法运算符。我们可以使用成员函数或者友元函数实现:
成员函数实现方式:
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 stone1(9, 12);
Stonewt stone2(12,8);
Stonewt total;
total = stone1 + stone2;
如果提供了Stonewt(double)构造函数,则可以这么做:
Stonewt stone1(9, 12);
double stoneDouble = 12.8;
Stonewt total;
total = stone1 + stoneDouble;//必须是stone1在前面
但是只有友元函数可以像下列方式操作(对于成员函数,第一个参数必须是对象):
Stonewt stone1(9, 12);
double stoneDouble = 12.8;
Stonewt total;
total = stoneDouble + stone1;//stone1在后面,不允许!!!
所以我们对于对象和double的加法有两种选择,第一是将加法重载为友元函数,并且让Stonewt(double)构造函数将double类型的参数转换为Stonewt类型的参数。第二种就是前面介绍过的重载一个double类型的加法。第一种方式代码少,出错的机会少,但是每次都调用转换构造函数,增加了时间和内存的开销。第二种方式代码多,程序员完成的工作更多,但是运算速度快。
第二种的实现方式为:
//成员函数与友元函数的结合方式实现加法重载
Stonewt operator+(double x);
friend Stonewt operator+(double x, Stonewt& s);
总览目录
上一篇:(八)对象和类
下一篇:(十)类和动态内存分配
文章参考:《C++ Primer Plus第六版》