黑马程序员C++核心编程笔记--类和对象--运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
加号运算符重载
因为C++引入了自定义的类型,所以当两个自定义的类型需要做运算的时候,就需要运算符重载。
class Person
{
public:int m_A;int m_B;}Person P1;P1.m_A = 10;P1.m_B = 20;Person P2;P2.m_A = 20;P2.m_B = 30;Person P3 = P1 + P2 //如果不重载运算符,编译器不知道如何运算自定义的类型
下面是加号运算的重载 编译器统一命名的
1.通过成员函数进行运算符重载(在一个对象内部,所以一个使用this,一个使用(*传的时候使用P->,&传的时候使用P.)
2.通过全局函数进行运算符重载
对象相加返回对象 所以operator前面加void
// 1.通过成员函数进行运算符重载
Person P3 = P1.operator+(P2)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;
}
// 使用的时候可以用 简化
Person P3 = P1+P2;// 2.通过全局函数进行运算符重载
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;
}Person p3 = operator+(p1,p2);
// 简化为
Person p3 = p1 + p2;// 运算符重载的同时也支持函数重载
Person operator+ (Person &p1, int num)
{Person temp;temp.m_A = p1->m_A + num;temp.m_B = p1->m_B + num;return temp;
}Person P3 = P2+10;
左移运算符重载
没有与这些操作数匹配的 运算符
只能利用全局函数重载左移运算符
int a = 10;
cout << a <<endl; //左移运算符直接输出整形Person p;
p.m_A = 10;
p.m_B = 10;
cout<< p <<end; // 左移运算符无法直接输出自定义的类// 通常不会使用成员函数重载左移运算符,因为 operator<<(cout) 简化完为 << cout,想要的是cout<< 成员函数运算符重载规则 【p 运算符 传的参数】
// 只能利用全局函数重载左移运算符 全局函数运算符重载规则 【传的参数1 运算符 传的参数2】
void operator<<(ostream &cout,Person &p)
{cout << p.m_A << p.m_B <<endl;
}cout<<p; // 重载完之后就可以直接输出p对象的属性// 但是想继续换行,也就是 cout<<p<<endl; 会报错,因为上面定义的重载返回是void// 链式编程思想需要每次返回的都是相同类型的 每次返回都是cout类型才行
//链式调用需要返回对象引用,所以是ostream&,而不是直接ostream
ostream& operator<<(ostream &cout,Person &p)
{cout << p.m_A << p.m_B <<endl;return cout;
}// 可以链式调用了
cout<<p<<endl;
递增运算符重载
通过重载递增运算符,实现自己的整形数据
+= 重载
int a = 10;
cout<< ++a<<endl; // 11 前置递增运算符 先运算再表达式
cout<< a <<endl; // 11int b = 10;
cout<< b++<<endl; // 10 后置递增运算符 先表达式再运算
cout<< b <<endl; // 11
实现自己的递增运算符,重载
#include <iostream>
using namespace std;class MyInteger {friend ostream& operator<<(ostream& cout, MyInteger myInt) ;public:MyInteger() {m_Num = 0;}// 重载前置++运算符MyInteger& operator++() { // 返回引用是为了链式调用 //仅返回值的话 链式调用不能对同一个对象进行操作,使得链式调用返回的值是对的,但是对象本身没有进行操作,单独输出对象的值,就是原来的值。// 先进行++运算m_Num++;// 再自身做返回return *this;}// 重载后置++运算符 int为了实现函数重载 是占位参数MyInteger operator++(int) { // 后置递增返回值,因为temp是局部对象,当前函数执行结束后temp对象就会销毁,所以返回值不能是引用// 后置递增与前置递增不同,先记录当时结果MyInteger temp = *this; // 创建一个局部对象,让这个对象等于 this的解引用 就是自身的值// 但是这里两种自增,多个访问// 后进行递增运算m_Num++;// 最后将记录结果做返回return temp;}
private:int m_Num;
};// 重载 << 运算符
ostream& operator<<(ostream& cout, MyInteger myInt) {cout << myInt.m_Num;return cout;
}void test01() {MyInteger myInt;cout << ++myInt << endl;cout << myInt << endl;
}void test02() {MyInteger myInt;cout << myInt++ << endl;cout << myInt << endl;
}int main() {test01();test02();return 0;
}
赋值运算符重载
c++编译器至少给一个类添加4个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
(默认浅拷贝)也就是copy后的对象属性中有指向同一个堆区地址,后面手动释放的时候会产生堆区内存重复释放的问题。程序崩了。
需要用深拷贝解决,需要在堆区重新开辟一块内存,放相同的数据。后续释放对象,就不会有冲突了。
class Person {
public:int *m_Age;Person(int age) {// 将年龄数据开辟到堆区m_Age = new int(age);}// 赋值运算符重载Person& operator=(Person& p) {if (m_Age != NULL) {delete m_Age;m_Age = NULL;}}
// 当开辟在堆区的数据需要释放时~Person() {if (m_Age != NULL) {delete m_Age;m_Age = NULL;}}};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;
// 重载赋值运算符 这里上面传int age 是Person的构造函数括号法初始化
// 这里operator是重载传入参数,代表运算符前后的类型关系 p=p 所以传person参数
Person &operator=(Person &p)
{// 编译器提供的是浅拷贝如下:// m_Age = p.m_Age;// 我们要进行深拷贝,所以要开辟一块新的堆内存m_Age = new int(*p.m_Age); // m_Age 是指针类型 所以要解引用取值 开辟heap内存值m_Age放进堆区return *this; // this是指针 指向对象本身 *this解引用 相当于返回对象本身// return this; 是返回指针 return *this是返回指向的内容
}
// 之后再进行释放就不会有堆区重复释放的问题了
完善的
Person &operator=(Person &p) // 这里返回值 返回自身 一是符合赋值 二是符合链式调用 返回相同对象的引用
{
// 先判断是否有属性在堆区,如果有就先释放干净,然后进行深拷贝
// 如果不先判断一下,直接就进行赋值 容易出现原内存无法释放的问题等等。
if(m_Age != NULL)
{delete m_Age;m_Age = NULL;
}// 再进行赋值m_Age = new int(*p.m_Age); // 深拷贝return *this;
}
关系运算符重载
#include <iostream>
using namespace std;class Person {
public:string m_Name;int m_Age;Person(string name, int age) {this ->m_Age = age;this ->m_Name = name;};bool operator==(Person &p) {if (this -> m_Age == p.m_Age && this -> m_Name == p.m_Name) {return true;}else return false;}bool operator!=(Person &p) {if (this -> m_Age == p.m_Age && this -> m_Name == p.m_Name) return false;else return true;}
};void test01() {Person p1("Tom", 10);Person p2("Tom", 10);if (p1 == p2) {cout << "p1 和 p2 一样" << endl;}else cout << "p1 和 p2 不一样" << endl;if (p1 != p2) {cout << "p1 和 p2 不一样" << endl;}else cout << "p1 和 p2 一样" << endl;
}int main() {test01();return 0;
}
函数调用运算符重载
函数调用运算符()也可以重载。
由于重载后使用的方式非常像函数的调用,因此成为仿函数。
仿函数没有固定写法,非常灵活。
#include <iostream>
using namespace std;class MyPrint {
public:void operator()(string text) {cout << text << endl;}};
void test01() {// 重载的()操作符也叫仿函数MyPrint myFunc;myFunc("hello world");
}class MyAdd {
public:int operator()(int v1, int v2) {return v1 + v2;}
};
void test02() {MyAdd myAdd;int ret = myAdd(10, 20);cout << ret << endl;// 匿名对象调用cout << MyAdd()(100, 100) << endl;
}int main() {test01();test02();return 0;
}
仿函数非常灵活,没有固定的写法
匿名函数对象,当前行执行完,立即被释放。