运算符重载
一、加号+ 运算符重载
实现两个自定义数据类型相加的运算
1.成员函数实现+号运算符重载
Person operator+(const Person& p)
{
Person temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp;
}
此时可以写出Person p3 = p1 + p2
,但其实本质是Person p3 = p1.operator+(p2)
。
2.全局函数实现+号运算符重载
Person operator+(const Person& p1, const Person& p2)
{
Person temp(0, 0);
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
此时可以写出Person p3 = p1 + p2
,但其实本质是Person p3 = operator+(p1, p2)
。
3.运算符重载后的函数重载(全局下)
Person operator+(const Person& p1, int num)
{
Person temp(0, 0);
temp.m_a = p1.m_a + 100;
temp.m_b = p1.m_b + 100;
return temp;
}
此时可以写出Person p4 = p1 + 100
,但其实本质是Person p4 = operator+(p1, 100)
。
【总结】
- 对于内置的数据类型的表达式,运算符是不可以改变的。
- 不要滥用运算符重载
二、左移<< 运算符重载
可以输出自定义的数据类型
class Person
{
friend ostream& operator<<(ostream& out, Person& p);
private:
int m_a;
int m_b;
public:
Person(int a, int b)
{
this->m_a = a;
this->m_b = b;
}
};
不能利用成员函数来重载左移运算符,否则会变成p.operator<<(cout)
等同于p << cout
。因为无法实现cout
在左侧。所以只能利用全局函数重载左移运算符
ostream& operator<<(ostream& out, Person& p)//简化为cout << p
{
out << "m_a = " << p.m_a << " m_b = " << p.m_b;
return out;
}
void test01()
{
Person p1(10, 10);
cout << p1 << "hello world " << endl;
}
特别注意这一行代码:
cout << p1 << "hello world" << endl;
此处当cout << p1
执行完后,返回的是cout
,因此此时代码等同于cout << "hello world" << endl;
。此时因为 "hello world"
和endl
均不是Person
类的对象,因此接下来调用的运算符<<
都是编辑器初始的左移运算符,而不是我们自定义重载后的。
【总结】
1. 重载左移运算符配合友元可以实现输出自定义数据类型
friend ostream& operator<<(ostream& out, Person& p);
三、递增++ 运算符重载
实现自己的整型数据
此处得先定义左移运算符,便于后续的输出操作。
class MyInterger
{
friend ostream& operator<<(ostream& cout, MyInterger myint);
private:
int m_num;
public:
MyInterger()
{
m_num = 0;
}
};
//重载左移<<运算符
ostream& operator<<(ostream& cout, MyInterger myint)
{
cout << myint.m_num;
return cout;
}
1.前置递增
//1.重载递增++运算符(前置递增)
MyInterger& operator++()
//此处返回“引用”是保证后续的操作都是作用在同一个对象上。
{
m_num++;//先进行++运算
return *this;//再将自身做返回
}
- 此处返回“引用”是保证后续的操作都是作用在同一个对象上。
2.后置递增
//2.重载递增++运算符(后置递增)
//int表示占位参数,可以用于区分前置递增和后置递增
MyInterger operator++(int)
{
//先返回结果(具体操作:先记录当时结果)
MyInterger temp = *this;
//后递增
m_num++;
//最后将记录结果做返回
return temp;
//此处返回的是指针而不是引用,因为temp会在函数执行完后就被释放掉,此时再返回temp的引用就是非法操作了
}
- 此处int表示占位参数,可以用于区分前置递增和后置递增
- 此处返回的是指针而不是引用,因为temp会在函数执行完后就被释放掉,此时再返回temp的引用就是非法操作了
3.编译结果分析
void test01()
{
MyInterger MyInt;
cout << MyInt << endl;
cout << ++MyInt << endl;
cout << MyInt << endl;
}
void test02()
{
MyInterger MyInt;
cout << MyInt << endl;
cout << MyInt++ << endl;
cout << MyInt << endl;
}
test01
的输出结果为:0 1 1。test02
的输出结果为:0 0 1。原因是重载后的前置递增是先进行++运算,再将自身做返回。而后置递增是先记录初始结果,再进行++运算,最后将初始结果做返回。
四、赋值= 运算符重载
c++编辑器至少给一个类添加4个函数:
1~3.默认的(构造函数,析构函数,拷贝构造函数)
4.赋值运算符operator=的对属性进行值拷贝
举例:
我们通过重载后的赋值=运算符将一个对象的所有属性(这里是m_age
)拷贝给另一个对象。
class Person
{
public:
int* m_age; //m_age存放的是年龄数据的地址,而不是数据本身
public:
Person(int age)
{
m_age = new int(age);
}
~Person()
{
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
}
//重载赋值=运算符
Person& operator=(Person& p)
{
//应该先判断是否有属性在堆区,如果有,先释放干净,再深拷贝
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
//编译器提供的是浅拷贝
//m_age = p.m_age;
m_age = new int(*p.m_age); //深拷贝操作
return *this; //返回对象自身
}
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1; //赋值操作
cout << "p1的年龄为: " << *p1.m_age << endl;
cout << "p2的年龄为: " << *p2.m_age << endl;
cout << "p3的年龄为: " << *p3.m_age << endl;
}
五、关系 运算符重载
可以让两个自定义类型对象进行对比操作,下面用 ==和!= 号举例。
class Person
{
public:
string m_name;
int m_age;
public:
Person(string name, int age)
{
this->m_name = name;
this->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;
}
};
六、函数调用 运算符重载
- 函数调用运算符()也可以重载
- 由于重载后的使用方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
//打印输出类
class MyPrint
{
public:
//重载函数调用运算符
void operator()(string test)
{
cout << test << endl;
}
};
void MyPrint02(string test)
{
cout << test << endl;
}
void test01()
{
MyPrint myprint;
myprint("hello world"); //由于使用起来非常类似于函数调用,因此称为仿函数
MyPrint02("hello world");
}
由test01
可以看出,通过重载函数调用运算符实现的myprint
也可以实现常规函数如MyPrint02
的功能,因此这种函数调用运算符()重载也被称为仿函数。
下面再举例一个:
//仿函数非常灵活,没有固定写法
//加法类
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd myadd;
int my_result = myadd(1, 2);
cout << "my_result = " << my_result << endl;
//匿名函数对象
cout << MyAdd()(100, 100) << endl;
}
由test02
可以看出,对象myadd
可以实现加法的功能。同时,由最后一行也可以看出直接使用MyAdd()
创建匿名对象,然后再调用函数。
七、总结
- 下列操作符只能通过成员函数进行重载:= [] () ->
- << 和 >> 只能通过全局函数配合友元进行重载。
- 不能重载 && 和 || ,因为无法实现短路原则(逻辑运算符是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算)