语法:
返回值 operator 运算符 ( 参数 )
例:
Point operator+(const Point &p1,const Point &p2);
//输出 Point 类型的输出运算符重载函数
ostream & operator<<(ostream& out,Point &p)
{
cout << "输出运算符重载函数" << endl;
out << "Point(" << p.x << "," << p.y << ")";
return out;
}
//输入 Point 类型的输入运算符重载函数
istream & operator>>(istream& in,Point &p)
{
cout << "输入运算符重载函数" << endl;
in >> p.x >> p.y;
return in;
}
C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型(类)是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。
运算符重载的实质就是函数重载或函数多态。运算符重载是一种形式的 C++
多态。目的在于让人能够用同名的函数来完成不同的基本操作。
不允许进行重载的运算符:
1.三目运算符
(?:)
2.成员访问运算符(.)
3.作用域运算符(::)
4.成员指针访问运算符(-> ,*)
5.sizeof 运算符
6.typeid 运算符
6.四种强制类型转换运算符
运算符重载规则
1. C++规定重载后的运算符的操作对象必须至少有一个是用户定义的类型,为什么呢?假如有两个数,int a,b;现在重载+运算符对 a,b 进行运算,得到一个 a*b 的值,这肯定是不合乎逻辑的。可能重载以后会有二义性,导致程序不知道该执行哪一个(是自带的的还是重载后的函数)
2. 使用运算符不能违法运算符原来的句法规则。例如不能把一目运算符重载为两目运算符。
3. 不能修改运算符原先的优先级。
4.不能定义新的运算符。
5. 大多数运算符可以通过成员函数和非成员函数进行重载但是下面这四种运算符只能通过成员函数进行重载:
= 赋值运算符() 函数调用运算符[ ] 下标运算符
6. 除了上述的规则,其实我们还应该注意在重载运算符的时候遵守一些明智的规则:例如:不要将+
运算符重载为交换两个对象的值。
重载的方式
重载运算符的两种形式:分别为重载为类的非成员函数和重载为类的成员函数
重载为类的非成员函数
重载为类的非成员时,通常我们都将其声明为友元函数,因为大多数时候重载运算符要访问类的私有数据,(当然也可以设置为非友元非类的成员函数。但是非友元又不是类的成员函数是没有办法直接访问类的私有数据的),如果不声明为类的友元函数,而是通过在此函数中调用类的公有函数来访问私有数据会降低性能。所以一般都会设置为类的友元函数,这样我们就可以在此非成员函数中访问类中的数据了。
如下,使用非成员函数方式重载+运算符对类进行加法运算
#include <iostream>
#include<string.h>
using namespace std;
class A
{
private:
int a;
int b;
public:
A(int x,int y):a(x),b(y){}
void show()
{
cout << "a:" << a << "b:" << b <<endl;
}
friend A operator+(A& a1,A& a2);
};
A operator+(A& a1,A& a2)
{
return A(a1.a+a2.a,a1.b+a2.b);
}
int main(int argc, char const *argv[])
{
A a(1,2);
A b(2,3);
A c = a + b;
c.show();
return 0;
}
其他的一些二元算术运算符就不再举例说明了,依次去推即可。我们再举例两个个二元流运算符<<和>>,实现要对运算符进行重载,我们就一定要清楚,运算符的操作数都应该为啥类型,上面我们重载+号是为了实现两个 A 类相加,得到一个 A 类,所以运算符重载函数的参数就是两个 A 类引用,返回值 为 A 类,现在对<<和>>进行重载,我们希望可以通过<<输出类的成员变量,利
用>>去键盘获取成员变量的值,所以右边的类型应该是一个类引用,左边的话 <<为 ostream&,>>为 istream&,返回值也应该是 ostream&或 istream&(
目的是返回值可以做左值并且进行连续的流运算操作。
)
#include <iostream>
#include<string.h>
using namespace std;
class A
{
private:
int a;
int b;
public:
A(int x,int y):a(x),b(y){}
void show()
{
cout << "a:" << a << "b:" << b <<endl;
}
friend istream& operator>>(istream& is,A& a1);
friend ostream& operator<<(ostream& os,A& a1);
};
istream& operator>>(istream& is,A& a1)
{
is >> a1.a >> a1.b;
return is;
}
ostream& operator<<(ostream& os,A& a1)
{
os << a1.a << a1.b;
return os;
}
int main(int argc, char const *argv[])
{
A a(1,2);
A b(2,3);
cin >> a >> b;
cout << a << b << endl;
return 0;
}
接着我们举例一个一元运算符自增自减的重载
自增和自减运算符都是一元运算符,而且都会改变自身的内容,因此左边参数不能是常量而只能是引用类型。又因为自增分为后缀 i++和前缀++i 两种形式(自减也一样,下面就只举自增的例子了)。后缀自增返回的值不能做左值而前缀自增返回的值则可以做左值。为了区分前自
增和后自增,系统规定对前缀自增的运算符函数上添加一个 int 类型的参数作为区分的标志。
#include <iostream>
#include<string.h>
using namespace std;
class A
{
private:
int a;
int b;
public:
A(int x,int y):a(x),b(y){}
void show()
{
cout << "a:" << a << "b:" << b <<endl;
}
friend A operator++(A &a1,int); //后++
friend A& operator++(A &a1); //前++,因为可以++++a,但是不可以 a++++,所以前++要返回引用
friend A operator--(A &a1,int); //后--
friend A& operator--(A &a1); //前--
};
A operator++(A &a1,int) //后++
{
A st = a1;
a1.a++;
a1.b++;
return st;
}
A& operator++(A &a1) //前++,因为可以++++a,但是不可以 a++++,所以前++要返回引用
{
++a1.a;
++a1.b;
return a1;
}
A operator--(A &a1,int) //后--
{
A st = a1;
a1.a--;
a1.b--;
return st;
}
A& operator--(A &a1) //前--,返回引用同理
{
--a1.a;
--a1.b;
return a1;
}
int main(int argc, char const *argv[])
{
A a(1,2);
A b(2,3);
a++;
a.show();
++a;
a.show();
b--;
b.show();
--b;
b.show();
return 0;
}
接下来我们来看一种情况,我们重载一个+运算符,实现一个类加上一个整数的效果
#include <iostream>
#include<string.h>
using namespace std;
class A
{
private:
int a;
int b;
public:
A(int x,int y):a(x),b(y){}
void show()
{
cout << "a:" << a << "b:" << b <<endl;
}
friend A operator+(A& a1,int x);
};
A operator+(A& a1,int x)
{
return A(a1.a + x,a1.b);
}
int main(int argc, char const *argv[])
{
A a(1,2);
A b(2,3);
A c = a + 3;
c.show();
return 0;
}
我们可以看到这样是没有任何问题的,但如果我把主函数中的 A c = a + 3;改成 A c = 3 + a;按 照+运算符的交换规则,这样应该是可以正确执行的才对,可是在编译时却会报一堆错误
因为我们重载+运算符时,第一个参数是一个 A 类引用,第二个参数是一个整型数据,所以就 固定了左操作数要是一个 A 类的对象,右操作数是一个整型,改动位置的话就会有问题,所以我们一般进行类和基本类型进行可调换位置的运算时,类都承担右值。
重载为类的成员函数
在学习类的 this 指针的时候我们知道成员函数是默认有一个隐藏参数指向该类的地址的(this),那么也就是说,如果我们将运算符重载函数声明为类的成员函数后,就不需要传递调用者自身的一个对象了。而且,以下运算符必须作为类的成员函数去重载。
1.函数运算符()2.下标索引运算符[]3.赋值运算符=4.成员访问运算符->
以下运算符不能重载为类的成员函数:
1.流运算符
我们来看示例:
#include <iostream>
#include<string.h>
using namespace std;
class A
{
private:
int a;
int b;
public:
A(int x,int y):a(x),b(y){}
void show()
{
cout << "a:" << a << "b:" << b <<endl;
}
A operator+(A& a1);
};
A A::operator+(A& a1)
{
return A(a1.a + this->a,a1.b + this->b);
}
int main(int argc, char const *argv[])
{
A a(1,2);
A b(2,3);
A c = b + a;
c.show();
return 0;
}
上面是一个+运算符的重载,只有一个参数,因为成员函数默认有一个 this 指针,指向调用对 象,那么当执行 b+a 时,就确实相当于 b.operator+(a);,函数中的 this 就是指向的 b,如果是a+b 那么 this 就指向 a。
再来举例一个一元运算符
#include <iostream>
#include<string.h>
using namespace std;
class A
{
private:
int a;
int b;
public:
A(int x,int y):a(x),b(y){}
void show()
{
cout << "a:" << &a << "b:" << b <<endl;
}
int* operator&()
{
return &this->a;
}
};
int main(int argc, char const *argv[])
{
A a(1,2);
cout << &a << endl;
a.show();
return 0;
}
我们来看看举例说明几个必须重载为成员函数的运算符,首先是赋值运算符
#include <iostream>
#include<string.h>
using namespace std;
class A
{
private:
int a;
int b;
public:
A(int x,int y):a(x),b(y){}
void show()
{
cout << "a:" << a << "b:" << b <<endl;
}
A& operator=(A& a1)
{
this->a = a1.a;
this->b = a1.b;
return *this;
}
};
int main(int argc, char const *argv[])
{
A a(1,3);
A b(3,5);
b = a;
b.show();
return 0;
}
为什么赋值运算符必须要被重载为成员函数呢,因为在类中默认有一个赋值操作,如果我们将赋值运算符重载函数作为类外函数去实现,那么当类去使用赋值操作时,他会优先调用它内部默认的赋值操作,不会调用全局函数。
类成员访问运算符( -> )可以被重载,但它较为麻烦。它被定义用于为一个类赋予"指针"行为。运算符 -> 必须是一个成员函数。如果使用了 -> 运算符,返回类型必须是指针或者是类的对象。
运算符 -> 通常与指针引用运算符 * 结合使用,用于实现"智能指针"的功能。这些指针是行为与正常指针相似的对象,唯一不同的是,当您通过指针访问对象时,它们会执行其他的任务。比如,当指针销毁时,或者当指针指向另一个对象时,会自动删除对象。
间接引用运算符 -> 可被定义为一个一元后缀运算符,如:
class Ptr{
//...
X * operator->();
};
下面我们来看看->运算符的一个重载示例:
#include <iostream>
#include <vector>
using namespace std;
// 假设一个实际的类
class Obj
{
static int i, j;
public:
void f() const { cout << i++ << endl; }
void g() const { cout << j++ << endl; }
};
// 静态成员定义
int Obj::i = 10;
int Obj::j = 12;
// 为上面的类实现一个容器
class ObjContainer
{
vector<Obj*> a;
public:
void add(Obj* obj)
{
a.push_back(obj); // 调用向量的标准方法
}
friend class SmartPointer;
};
// 实现智能指针,用于访问类 Obj 的成员
class SmartPointer
{
ObjContainer oc;
int index;
public:
SmartPointer(ObjContainer& objc)
{
oc = objc;
index = 0;
}
// 返回值表示列表结束
bool operator++() // 前缀版本
{
if(index >= oc.a.size() - 1) return false;
if(oc.a[++index] == 0) return false;
return true;
}
bool operator++(int) // 后缀版本
{
return operator++();
}
// 重载运算符 ->
Obj* operator->() const
{
if(!oc.a[index])
{
cout << "Zero value";
return (Obj*)0;
}
return oc.a[index];
}
};
int main()
{
const int sz = 10;
Obj o[sz];
ObjContainer oc;
for(int i = 0; i < sz; i++)
{
oc.add(&o[i]);
}
SmartPointer sp(oc); // 创建一个迭代器
do {
sp->f(); // 智能指针调用
sp->g();
} while(sp++);
return 0;
}
函数调用运算符 () 可以被重载用于类的对象。当重载 () 时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。
下面的实例演示了如何重载函数调用运算符 ()。
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance(){
feet = 0;
inches = 0;
}
Distance(int f, int i){
feet = f;
inches = i;
}
// 重载函数调用运算符
Distance operator()(int a, int b, int c)
{
Distance D;
// 进行随机计算
D.feet = a + c + 10;
D.inches = b + c + 100 ;
return D;
}
// 显示距离的方法
void displayDistance()
{
cout << "F: " << feet << " I:" << inches << endl;
}
};
int main()
{
Distance D1(11, 10), D2;
cout << "First Distance : ";
D1.displayDistance();
D2 = D1(10, 10, 10); // invoke operator()
cout << "Second Distance :";
D2.displayDistance();
return 0;
}
下标操作符 [] 通常用于访问数组元素。重载该运算符用于增强操作 C++ 数组的功能。在重载下标运算符 “[ ]” 时,认为它是一个双目运算符,例如 X[Y] 可以看成
[ ]-----双目运算符;
X-----左操作数;
Y-----右操作数。
下面的实例演示了如何重载下标运算符 []。
#include<iostream>
using namespace std;
class Vector4{
private:
int v[4];
public:
Vector4(int a1,int a2,int a3,int a4){
v[0]=a1;v[1]=a2;v[2]=a3;v[3]=a4;
}
int &operator[](int bi); //声明下标运算符 [] 重载函数
};
int &Vector4::operator[](int bi){ //定义下标运算符 [] 重载函数
if(bi<0||bi>=4){ //数组的边界检查
cout<<"Bad subscript!\n";
exit(1);
}
return v[bi];
}
int main(){
Vector4 ve(0,1,2,3);
cout<<ve[2]<<endl; //ve[2] 相当于 ve.operator[](2)
ve[3]=ve[2];
cout<<ve[3]<<endl;
ve[2]=22;
cout<<ve[2];
return 0;
}
其实,通过上面很多例子可以看出,运算符重载的目的无非就是为了实现自定义变量的运 算,或者就是为了让程序的安全性提高。例如[]运算符的重载,可以做边界检测