目录
一、运算符重载
运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,简化操作 让已有的运算符 适应不同的数据类型。
运算符重载本质:定义一个函数,在函数体内实现想要的功能,当用到该运算符时,编译器会自动调用这个函数。也就是说,运算符重载是通过函数实现的,它本质上是函数重载
语法:函数的名字由关键字operator及其紧跟的运算符组成
eg: 重载+运算符 ==> operator+ ; 重载=号运算 ==> operator= ;
注意:重载运算符 不要更改 运算符的本质操作(+是数据的相加 不要重载成相减)
1.1、运算符重载的三种方式
(1)以成员函数的形式重载
(2)以全局函数的形式重载
1.2、重载的限制
(1) 多数C++运算符都可以重载,重载的运算符不必是成员函数,但必须至少有一个操作数是用户定义的类型。
(1) 重载后的运算符必须至少有 一 个操作数是用户定义的类型,防止用户为标准类型重载运算符
如:不能将减法运算符(-)重载为计算两个 double 值的和,而不是它们的差。虽然这种限制将对创造性有所影响,但可以确保程序正常运行。
(2) 使用运算符时不能违反运算符原来的句法规则
例如,不能将求模运算符(%)重载成使用一个操作数:int x;Time shiva;%x;%shiva;,且不能修改运算符的优先级。
(3) 不能创建新运算符
例如,不能定义operator **()函数来表示求幂。
(4) 不能重载下面的运算符
(5) 下表大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数重载。
= : 赋值运算符;
() :函数调用运算符;
[] : 下标运算符;
-> : 通过指针访问类成员的运算符;
二、运算符重载示例
2.1、重载加号运算 — ’ + ’
2.1.1 以成员函数的形式重载
class MyPeople11
{
public:
MyPeople11() {}
MyPeople11(int a,int b)
{
this->m_A = a;
this->m_B = b;
cout << "m_A = " << this->m_A << "\tm_B = " << this->m_B << endl;
}
//1、通过成员函数实现重载
MyPeople11 operator+(MyPeople11 &p)
{
MyPeople11 temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
~MyPeople11()
{
}
int m_A;
int m_B;
};
void Class_Func_OverLoadAdd()
{
MyPeople11 p1(12, 34);
MyPeople11 p2(56, 78);
//1、通过成员函数重载运算符
MyPeople11 p3 = p1 + p2;
cout << "p3 m_A = " << p3.m_A << "\tm_B = " << p3.m_B << endl;
}
成员函数重载本质: MyPeople11 p3 = p1.operator+(p2); ===> 简化为 MyPeople11 p3 = p1 + p2;
2.1.2 以全局函数的形式重载
class MyPeople11
{
public:
MyPeople11() {}
MyPeople11(int a,int b)
{
this->m_A = a;
this->m_B = b;
cout << "m_A = " << this->m_A << "\tm_B = " << this->m_B << endl;
}
~MyPeople11()
{
}
int m_A;
int m_B;
};
//2、通过全局函数重载运算符
MyPeople11 operator+ (MyPeople11 &p1, MyPeople11 &p2)
{
MyPeople11 temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
void Class_Func_OverLoadAdd()
{
MyPeople11 p1(12, 34);
MyPeople11 p2(56, 78);
//2、通过全局函数重载运算符
MyPeople11 p3 = operator+ (p1, p2);
cout << "p3 m_A = " << p3.m_A << "\tm_B = " << p3.m_B << endl;
}
全局函数重载本质: MyPeople11 p3 = operator+(p1, p2); ===> 简化为 MyPeople11 p3 = p1 + p2;
2.1.3 运算符重载也可以进行函数重载
//函数重载版本
MyPeople operator+ (MyPeople &p1, int a)
{
MyPeople temp;
temp.m_A = p1.m_A + a;
temp.m_B = p1.m_B + a;
return temp;
}
void Class_Func_OverLoadAdd()
{
MyPeople p1(12, 34);
MyPeople p2(56, 78);
//运算符重载也可以发生函数重载
MyPeople p4 = p1+100; // MyPeople11类型 + int型
cout << "p4 m_A = " << p4.m_A << "\tm_B = " << p4.m_B << endl;
}
2.1.4 总结
总结1:对于内置的数据类型的表达式的的运算符是不可能改变的
总结2:不要滥用运算符重载
2.2、重载左移运算符 — ’ << ’
只能用全局函数重载
原因:利用成员函数重载“<<”,当成员函数 void operator<<(cout){ } 在使用时:
p.operator<<(cout) 等价于 p << cout ; 此时,cout实在<<右侧;重载<<时,cout应该在<<的右侧;
因此,通常不会使用成员函数重载“<<”运算符,因为无法实现 cout 在 << 左侧
class MyPeople
{
friend ostream &operator<<(ostream &cout, MyPeople &p);
public:
MyPeople(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
private:
int m_A;
int m_B;
};
ostream &operator<<(ostream &cout, MyPeople &p)
{
cout << "m_A:" << p.m_A << "\tm_B:" << p.m_B;
return cout;
}
void Class_Func_OverLoadLeftShift()
{
MyPeople p1(1,2);
cout << p1 << endl; //重载 ‘<<’ 运算符
}
利用全局函数从在左移运算符的本质:operator<<(cout,p) ===>简写为: cout << p;
输出结果:
2.3、重载递增运算符 — ’ ++ ’
2.3.1 前置递增
前置递增可以返回引用,这样可进行链式使用
class MyInteger
{
public:
MyInteger()
{
m_Num = 100;
}
MyInteger &operator++()
{
m_Num++; //先加1
return *this; //将自身返回,返回类型为引用类型
}
private:
int m_Num;
};
void Class_Func_OverLoadAddAdd()
{
MyInteger myint;
cout << ++(++myint) << endl; //返回 默认值 + 2
cout << myint << endl; //返回 默认值 + 2
}
2.3.2 后置递增
后置递增 — 返回数值,如果返回的是引用,就相当于返回了局部对象的引用,非法操作
class MyInteger
{
public:
MyInteger()
{
m_Num = 100;
}
MyInteger operator++(int) //int 表示占位符,用于区分前置和后置递增
{
//先记录当时结果
MyInteger temp = *this;
//再递增,记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
//最后返回当时的结果
return temp;
}
private:
int m_Num;
};
void Class_Func_OverLoadAddAdd()
{
MyInteger myInt;
cout << myInt++ << endl; //返回 默认值
cout << myInt << endl; //返回 默认值 + 1
}
2.4、重载赋值运算符 — ’ = ’
c++编译器至少给一个类添加4个函数
1. 默认构造函数(无参,函数体为空)
2. 默认析构函数(无参,函数体为空)
3. 默认拷贝构造函数,对属性进行值拷贝
4. 赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
class MyPeople13
{
public:
MyPeople13(int age)
{
m_age = new int(age);
}
~MyPeople13()
{
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
}
MyPeople13 &operator=(MyPeople13 &p)
{
//编译器提供的是浅拷贝,会出现重复释放内存的问题
//m_age = p.m_age;
//应该先判断是否有属性在堆区创建,如果有,则需要先释放,然后再进行深拷贝
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
m_age = new int(*p.m_age); //提供深拷贝 解决浅拷贝的问题
return *this; //返回自身
}
int *m_age; //将年龄数据开辟到堆区
};
void Class_Func_OverLoadEquals()
{
MyPeople13 p1(18);
cout << "P1 AGE : " << *p1.m_age << endl; //18
MyPeople13 p2(20);
cout << "P2 AGE : " << *p2.m_age << endl; //结果为20
p2 = p1;
//cout << "P2 AGE : " << *p2.m_age << endl; //代码崩溃,和深浅拷贝问题一样,m_age所在的内存被重复释放
//解决方式:重载 = 运算符 (在成员函数中重载)
cout << "P2 AGE : " << *p2.m_age << endl;
//链式调用
MyPeople13 p3(30);
p3 = p2 = p1;
cout << "P1 AGE : " << *p1.m_age << endl;
cout << "P2 AGE : " << *p2.m_age << endl;
cout << "P3 AGE : " << *p3.m_age << endl;
}
结果:
2.5、重载关系运算符 — ’ == ’
作用 :重载关系运算符,可以让两个自定义类型对象进行对比操作
class MyPeople
{
public:
MyPeople(string name, int age)
{
m_Name = name;
m_Age = age;
}
bool operator==(MyPeople &p)
{
if (this->m_Name == p.m_Name && this->m_Age==p.m_Age)
{
return true;
}
else
{
return false;
}
}
bool operator!=(MyPeople &p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
string m_Name;
int m_Age;
};
void Class_Func_OverLoadEqualsEquals()
{
MyPeople p1("tom", 18);
MyPeople p2("tom", 18);
MyPeople p3("tom", 20);
if (p1 == p2) { cout << "p1 等于 p2" << endl; }
else { cout << "p1 不等于 p2" << endl; }
if (p1 != p3) { cout << "p1 不等于 p3" << endl; }
else { cout << "p1 等于 p3" << endl; }
}
结果:
2.6、重载函数调用运算符 — ’ () ’
函数调用运算符 () 也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
class MyPrint
{
public:
void operator()(string test) //重载函数调用运算符---仿函数
{
cout << test << endl;
}
};
void Print(string test)
{
cout << "普通函数调用" << test << endl;
}
class MyADD
{
public:
int operator()(int a, int b) //重载函数调用运算符---仿函数
{
return a + b;
}
};
void Class_Func_OverLoad_Call()
{
MyPrint p1;
p1("ddddddddddd"); //重载函数调用的函数,--由于使用起来和普通函数调用非常相似,因此称为仿函数
Print("gghhh"); //普通函数调用
MyADD ad1;
int ret = ad1(100, 100);
cout << "ret = " << ret << endl;
cout << ad1(100, 100) << endl; //匿名函数对象
}
结果:
2.7、重载下标运算符— ’ [ ] ’
c++规定,下标运算符[]只能以成员函数的形式进行重载。
格式如下:
返回类型 & operator[](参数): 可以访问元素和修改元素。
const 返回类型 & operator[] (参数)const : 只能访问元素
建议使用时同时提供这两种形式,为了适应const对象。如果不提供第二种,无法访问const对象的任何元素。
#include <iostream>
using namespace std;
class Data
{
public:
Data(int length=0);
~Data();
int & operator [](int i);
const int &operator [](int i)const;
int length()const{return m_length;}
void display()const;
private:
int m_length;
int *m_p;
};
Data::Data(int length):m_length(length)
{
if(length==0)
m_p=NULL;
else
m_p=new int[length];
}
Data::~Data()
{
delete [] m_p;
}
int & Data::operator [](int i)
{
return m_p[i];
}
const int &Data::operator [](int i)const
{
return m_p[i];
}
void Data::display()const
{
for(int i=0;i<m_length;i++)
{
if(i==m_length-1)
cout<<m_p[i]<<endl;
else
cout<<m_p[i]<<", ";
}
}
int main()
{
int n;
cin>>n;
Data ob(n);
for(int i=0,len=ob.length();i<len;i++)
{
ob[i]=i*5;
}
ob.display();
const Data pt(n);
cout<<pt[n-1]<<endl;
return 0;
}
注意:
(1)pt是const对象,如果Data类没有提供const版本的operator[ ],cout<<pt[n-1]<<endl;将报错。
(2)虽然只是读取数据,没有修改对象。但调用了非const版本的operator[ ],编译器认为只要调用非const的成员函数,编译器认为会修改对象。