一、运算符重载
对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
二、不同运算符的重载方法
(一)、加号运算符重载
(1)、通过成员函数进行重载
实现两个自定义数据类型相加的运算符。
对于内置数据类型,编译器知道如何进行运算,如下列代码:
int a = 10;
int b = 10;
int c = a + b;
而当我们做一个自定义数据类型时,编译器并不知道要进行怎么运算,这时候就需要我们进行一个成员函数重载+了,如下代码
class Person
{
public:
int m_A;
int m_B;
}
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
则不会实现该功能,因此需要我们自己实现一个相加的运算操作。
Person PersonAndPerson(Person &p)
{
Peson temp;
temp m_A = this->m_A + p.m_A;
temp m_B = this->m_B + p.m_B;
return temp;
}
我们如果每次实现该功能都需要自己实现运算的话,则十分费力,这时编译器则已经给我们提供好了一个重载类的方法:
operator+()
当我们使用这个方法时,编译器会将Person p3 = p1.operator+(p2)简化为Person p3 = p1 + p2;这就是加号重载的一个方法。
样例如下:
class Person
{
public:
Person operator+(Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
int m_A;
int m_B;
};
(2)、通过全局函数进行重载
Person PersonAndPerson(Person &p1, Person &p2)
{
Peson temp;
temp m_A = p1.m_A + p2.m_A;
temp m_B = p1.m_A + p2.m_B;
return temp;
}
如果使用operator+()方法来通过全局函数进行重载时,我们可以通过Person p3 = operator+(p1,p2),编译器会将Person p3 = p1.operator+(p2)简化为Person p3 = p1 + p2;
样例如下:
class Person
{
public:
int m_A;
int m_B;
};
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
(3)、运算符重载也可以发生函数重载
样例如下:
Person operator+(Person &p1, int num)
{
Person temp;
temp.m_A = p1.m_A +num;
temp.m_B = p1.m_B + num;
return temp;
}
本节总结:
1、对于内置的数据类型的表达式的运算符是不可能发生改变的
2、不要滥用运算符重载
(二)、左移运算符重载
作用:可以输出自定义数据类型
在我们之前的学习过程中,c++输出时采用的语法是cout<< " a"<<endl;
当我们输出一个位置类型的变量时,比如一个类时,会报错:没有与这些操作数匹配的<<运算符。这时候就需要我们调用左移运算符重载了。
在加号运算符重载中我们可以知道,可以通过成员函数以及全局函数进行重载,但是当我们使用左移运算符重载时,我们需要传递一个cout参数,语法如下:
p.operator<<(cout)
简化后则变成了p<<cout,不符合我们平时的语法定义,故左移运算符不可通过成员函数进行重载。
全局函数重载左移运算符:
class Person
{
friend ostream &operator<<(ostream &out, Person &p);
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
};
ostream &operator<<(ostream &out, Person &p)
{
out << "m_A" << p.m_A << "m_B" << p.m_B;
return out;
}
void test10() {
Person p1(10, 10);
cout << p1<<"hello"<< endl;
}
解释一下上面的代码,针对全局函数实现左移运算符重载,往往需要将属性私有化,避免对属性干扰。这时候我们就需要对属性私有化,但是在全局函数中调用私有属性时,我们需要使用友元的方法来实现调用,调用方式则为在类内上标出:friend ostream &operator<<(ostream &out, Person &p);,以上的语法中ostream代表的是输入流。而一旦属性私有化之后,在对其进行赋值则需要通过构造函数进行赋值了。
(三)递增运算符重载
在介绍递增运算符重载之前,先讲一下递增运算符的基本运算方法。有如下代码:
int a = 10;
cout<<++a<<endl; // a = 11
cout<<a<<endl; // a = 11
int b = 10;
cout<<b++<<endl; // b = 10
cout<<b<<endl; // b = 11
这是递增运算符的一个运算方法。下面介绍前置递增运算符重载的一个基本语法演示:
class Myint
{
friend ostream& operator<<(ostream& cout, Myint myint);
public:
Myint()
{
m_Num = 0;
}
// 递增运算符的重载,返回引用为了一直对一个数据进行递增
Myint& operator++()
{
m_Num++;
return *this;
}
private:
int m_Num;
};
ostream& operator<<(ostream& cout, Myint myint)
{
cout << myint.m_Num;
return cout;
}
void test11()
{
Myint myint;
cout << "myint" << ++myint << endl;
}
后置运算符样例:
Myint operator++(int)
{
Myint temp = *this;
m_Num++;
return temp;
}
下面复习一下函数重载的满足条件:
1、在同一个作用域下;
2、函数名称一样;
3、参数的个数、类型等不一样
那么为什么前置递增返回的是一个引用,而后置递增返回的是一个变量呢?
因为前置递增时,为了重复递增都针对同一块内存进行操作,所以要通过一个全局变量对其进行返回,而后置递增返回的是一个暂存的局部变量,如果调用局部变量的引用则会报错。
(四)、赋值运算符重载
在分析赋值运算符重载之前先简单复习一下c++编译器在创建一个类时,要添加的4个函数:
1、默认构造函数(无参,函数体为空);
2、默认析构函数(无参,函数体为空);
3、默认拷贝构造函数,对属性进行值拷贝;
4、赋值运算符operator=,对属性进行值拷贝。
如果类中有属性指向堆区,做赋值操作时,也会出现深浅拷贝的问题。
样例如下:
class Person
{
public:
Person(int Age)
{
//将这个变量开辟在堆区,并且返回一个指针
m_Age = new int(Age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//定义一个指针,来管理指针指向的内存
int *m_Age;
};
void test13()
{
Person p1(10);
Person p2(20);
p1 = p2;
cout << "p1的年龄为" << *p1.m_Age << endl;
cout << "p2的年龄为" << *p2.m_Age << endl;
}
对以上的代码进行一个解析,当运行test13时,会创建两个对象,一个p1,一个p2。创建p1时,由于我们的person类的m_Age是存放在堆区的,存放在堆区的内存需要程序员手动的开辟,并且需要手动的清除。当我们通过p1 = p2这一浅拷贝操作时,实际上是将p1的m_Age所存放的地址例如(0x0011),拷贝到了p2的m_Age中,也就是p1与p2的m_Age所保存的堆区地址实际是一个。当手动清除指针时,p1清除完之后,再清除p2时会引发异常: 0xC0000005: 写入位置 0xFDFDFDFD 时发生访问冲突。这是以上代码的问题所在。为了解决这一异常,我们需要对将浅拷贝改为深拷贝。
下面为样例代码:
class Persons
{
public:
Persons(int age)
{
//将这个变量开辟在堆区,并且返回一个指针
m_Age = new int(age);
}
//析构函数,当堆区开辟的内存使用完成之后将其释放
~Persons()
{
//如果保存指针地址的变量不为空
if (m_Age != NULL)
{
//将其清除并且置为空指针
delete m_Age;
m_Age = NULL;
}
}
//这个操作要返回自身,通过*this来实现,进而实现重复使用该块内存
Persons& operator=(Persons &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;
}
//定义一个指针,来管理指针指向的内存
public:
int *m_Age;
};
void test13()
{
Persons p1(10);
Persons p2(20);
Persons p3(20);
p3 = p2 = p1;
cout << "p1的年龄为" << *p1.m_Age << endl;
cout << "p2的年龄为" << *p2.m_Age << endl;
cout << "p3的年龄为" << *p2.m_Age << endl;
}
(五)、关系运算符重载
重载关系运算符,可以让两个自定义类型对象进行对比操作,样例如下:
class person
{
public:
//初始化类属性
person(string name, int age)
{
m_name = name;
m_age = age;
}
//重载相等符号
bool operator==(person &p)
{
if (this->m_name == p.m_name && this->m_age == p.m_age)
{
return true;
}
return false;
}
//重载不相等符号
bool operator!=(person &p)
{
if (this->m_name == p.m_name && this->m_age == p.m_age)
{
return false;
}
return true;
}
//类属性名称
string m_name;
int m_age;
};
void test14() {
person p1("aaa", 18);
person p2("bbb", 18);
if (p1 == p2)
{
cout << "p1和p2是相等的" << endl;
}
if (p1 != p2)
{
cout << "p1和p2是不相等" << endl;
}
}
(六)、函数调用运算符重载
1、函数调用运算符()也可以重载;
2、由于重载后使用的方式非常像函数的调用,因此称为仿函数;
3、仿函数没有固定写法,非常灵活
具体样例如下:
class MyPrint
{
public:
void operator()(string test)
{
cout << "test" << test <<endl;
}
};
void test15() {
MyPrint myprint;
myprint("hwll");
}