11.1运算符重载
运算符重载就是将参与运算符设置到用户自己定义的数据类型里,使我们自己定义的数据类型可以通过运算符来进行运算。
如 重载加号 我们可以直接使我们的两个对象相加,而不是通过成员函数,将对象内部的成员变量相加。
语法
运算符重载函数有两种,一种是作为成员函数出现在类的声明里,一种是通过友元函数出现在类外。
第一种的格式是这样的
返回类型 operator 运算符(类的引用)
第二种的格式是这样的
返回类型 operator 运算符(x,y)x和y是参与运算的两个算子
举个例子,C++中没有 复数的数据类型,下面我们将自己定义一个复数类型的类,并实现两个复数对象直接相加
#include <iostream>
using namespace std;
class complex
{
private:
int a,b;
public:
complex (int aa=0,int bb=0):a(aa),b(bb){}
void display();
complex operator +(complex &p);
};
void complex::display()
{
cout<<a;
if(b>0)
cout<<'+';
cout<<b<<'i'<<endl;
}
complex complex::operator +(complex &p)重载+
{
complex c;
c.a=a+p.a;
c.b=b+p.b;
return c;
}
int main()
{
complex x(1,1),y(2,2);
(x+y).display();
return 0;
}
实际上编译器发现了x+y 它会将其转换为x.operator+(y).
这样也就存在一个问题 如果x是一个int类型的变量,他不是complex类的对象,那么就不能使用这个成员函数,这种情况下将重载函数作为成员函数就行不通了。
这时候应该使用友元函数
什么是友元函数呢,友元函数就是可以使用类里的私有成员的函数。
例如
#include <iostream>
using namespace std;
class complex
{
private:
int a,b;
public:
complex (int aa=0,int bb=0):a(aa),b(bb){}
void display();
friend complex operator +(int i,complex &p);//友元函数
};
void complex::display()
{
cout<<a;
if(b>0)
cout<<'+';
cout<<b<<'i'<<endl;
}
complex operator +(int i,complex &p)//不需要加作用域解析符,因为友元函数不是成员函数
{
complex c;
c.a=i+p.a;
c.b=p.b;
return c;
}
int main()
{
complex x(1,1),y(2,2);
complex z=1+y;
z.display();
return 0;
}
通过使用友元函数,就可以实现 一个整型变量加一个对象了。
编译器在发现1+y的时候会将其自动换为 operator+(1,y)
注意事项
友元函数只有在类声明中的原型中才能使用friend关键字。
友元函数不需要使用限定符(::)
11.2.2重载限制
重载不能修改运算符的优先级
有些符号不允许重载如 sizeof . :: ?:等等
不能创建新的运算符 如 不能重载 **作为幂运算
某些运算符只能通过成员函数来进行重载 如: = () [] ->
友元函数重载和成员函数重载只能使用一种,不然会发生二义性问题,也就是编译器不知道该用哪一个函数,编译器会报错。
11.3.2常见的友元:重载<<
如何直接使用cout 输出一个复数呢,答案是重载 <<,但是应该重载哪个类呢?
要做到 cout<<a(a是一个类的对象)
肯定用成员函数不行,因为 cout在前,cout属于ostream类
所以重载的是ostream类的<<,但是我们去修改系统的类库很麻烦,而且总不能你写一个类就去改一下ostream,所以还是用友元函数吧。
#include <iostream>
using namespace std;
class complex
{
private:
int a,b;
public:
complex (int aa=0,int bb=0):a(aa),b(bb){}
void display();
complex operator +(complex &p);
//由于我们包含了头文件iostream,所以就可以用到ostream类
friend ostream &operator <<(ostream &os,complex &p);
};
void complex::display()
{
cout<<a;
if(b>0)
cout<<'+';
cout<<b<<'i'<<endl;
}
complex complex::operator +(complex &p)
{
complex c;
c.a=a+p.a;
c.b=b+p.b;
return c;
}
ostream& operator<<(ostream &os,complex &p)
{
os<<p.a;
if(p.b>0)
os<<'+';
os<<p.b<<'j'<<endl;
return os;
}
int main()
{
complex x(1,1),y(2,2);
complex z=x+y;
cout<<z;
return 0;
}
为什么返回类型是ostream&呢,因为 你输出的时候可能是cout<<a<<b;
cout<<a 返回类型是一个ostream的对象,cout<<a会被解释为 operator<<(cout,a),它的返回值是一个ostream类的引用,于是可以继续进行运算,相当于(operator<<(cout,a))<<b。
为什么输出用os而不是cout呢
是因为我们不一定使用cout<<a 也有可能是fout<<a 将a输出到文件里。
11.6类的自动转换和强制类型转换
C++可以对内置类型进行自动转换,对类也可以进行转换。
如我们可以让一个类的对象等于一个整型值如:
#include <iostream>
#include <fstream>
using namespace std;
class fun
{
int a,b;
public:
fun(int aa=0,int bb=0):a(aa),b(bb){}
void display();
};
void fun::display()
{
cout<<a<<" "<<b<<endl;
}
int main()
{
fun A;
A=1;//把一个整型变量赋给对象A
A.display();
return 0;
}
输出结果为
从这个例子我们可以看出,如果一个类的构造函数有一个参数,或者有多个参数但都提供了默认的参数,就可以实现这种类型转换。
具体的原理就是程序自动地执行了构造函数,创建了一个临时无名对象,并且把这个对象赋值给等式左边的对象。
考虑到C++还可以对内置数据类型进行自动转换,还可以把构造函数改成这样
fun(double aa,int bb=0):a(aa),b(bb){}
原理就是,编译器首先会查找参数为int类型的构造函数,找不到,编译器发现了参数为double类型的参数,然后编译器发现int转为double类型是可行的,所以编译器就将int转为了double类型然后调用了这个构造函数。
但这种将构造函数作为自动转换函数作用并不都是好的,C++提供了explicit来关闭构造函数的这个自动转换的功能。
例如 explicit fun(double aa,int bb=0):a(aa),b(bb){}
这样 如果使用 A=1 就会报错。
11.6.1转换函数
上面我们讲了将数字转换为类的对象,那么反过来将类的对象转换为数字行吗?
可以这么做,但是必须要使用转换函数
转换函数格式
operator typeName()typeName是类型的名字
转换函数必须是类方法,不能指定返回类型,不能有参数。
举个例子
#include <iostream>
#include <fstream>
using namespace std;
class fun
{
int a;
public:
fun(double aa):a(aa){}
operator int();//将fun类转换为int类型的转换函数
void display();
};
void fun::display()
{
cout<<a<<endl;
}
fun::operator int()
{
return a;
}
int main()
{
fun A(0);
int x=A;//编译器查找是否定义了相应的转换函数,若有则执行,无则报错
cout<<x<<endl;
return 0;
}
同样的也存在一个基本类型自动转换的问题,如果你定义了一个double类型转换函数,但是你却将一个对象赋值给了int类型,编译器会使用转换函数,将其转换为double类型,然后在赋给int类型。