阶段三:C++核心编程

Chapter7:类和对象-C++运算符重载

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

7.1 加号运算符重载

  • 作用:实现两个自定义数据类型相加的运算
    总结1:对于内置的数据类型的表达式的的运算符是不可能改变的,比如int double等等 只有对自定义的可以通过重载改变
    总结2:不要滥用运算符重载;比如看到的是两个数相加的重载 但是函数实现的内部是两个数相减,可以这样做也不会报错,但是这样不规范,也不便于维护代码
#include "iostream"
using namespace std;
/*
上课笔记如下:
对于内置数据类型,编译器知道如何进行计算。
如:
int a = 10;
int b = 10;
int c = a + b;
但是如果是两个自定义类型相加,如:Person,该怎么办呢,编译器是不知道的,因此我们要告诉编译器应该怎么计算
如:
class Person
{
public:int m_A;int m_B;
}
Person p1;//实例化第一个对象p1
p1.m_A = 10;
p1.m_B = 10;Person p2;//实例化第二个对象p2
p2.m_A = 10;
p2.m_B = 10;Person p3 = p1 + p2;//实例化第三个对象p3,等于p1+p2,那么编译器这时该怎么办呢?按照道理应该是:
p1的第一个属性 + p2的第一个属性  = p3的第一个属性 即:p3.m_A = p1.m_A + p2.m_A;
p1的第二个属性 + p2的第二个属性  = p3的第二个属性 即:p3.m_B = p1.m_B + p2.m_B;
但是编译器并不会实现这样的计算方式,编译器不会按照我的这种想法去实现相加
那么我们应该怎么做呢?首先抛开这里要学的新技术 运算符重载
我们可以通过一个成员函数来计算
如:
通过自己写成员函数,实现两个对象相加属性后返回新的对象
Person PersonAddPerson(Person &p)
{Person temp;temp.m_A = this->m_A + p.m_A;//让对象自身的属性加上当前传进来的对象的属性temp.m_B = this->m_B + p.m_B;return temp;
}
这时候,这里的PersonAddPerson是程序员自己起的名字,每个程序员起的都不一样,因此编译器希望自己起一个名字来统一规范
编译器给起了一个通用名称:
operator+
那么就写成了这样
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.operator+(p2);
当我们使用了编译器统一的名字之后,这种写法可以简化为:
Person p3 = p1 + p2;
而简化后的写法正好符合我们的平常的认知。
这就是通过成员函数重载+号
*/
/*
这里分析
通过全局函数重载+号
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;
*/class Person
{public:Person() {};Person(int a, int b){this->m_A = a;this->m_B = b;}//成员函数实现 + 号运算符重载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;}public:int m_A;int m_B;
};//全局函数实现 + 号运算符重载
//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 operator+(const Person& p2, int val)
{Person temp;temp.m_A = p2.m_A + val;temp.m_B = p2.m_B + val;return temp;
}void test()
{Person p1(10, 10);Person p2(20, 20);//成员函数方式Person p3 = p2 + p1;  //相当于 p2.operaor+(p1)cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;//函数重载的版本;这里相当于是一个Person + int的类型Person p4 = p3 + 10; //相当于 operator+(p3,10)cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;}int main()
{test();system("pause");return 0;
}

7.2 左移运算符重载

#include "iostream"
using namespace std;/*
* 4.5.2 左移运算符重载
*
* 左移运算符:<<
* 作用:可以输出自定义数据类型
*
* 总结:重载左移运算符配合友元可以实现输出自定义数据类型
*/
/*
如:int a = 10;
cout << a << endl;class Person
{
public:int m_A;int m_B;
};
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p.m_A << endl;//这样是ok的 因为这里的m_A和m_B是整型的,是内置运算符
cout << p.m_B << endl;
但是这样可以吗:cout << p << endl;
我这里想实现的就是输出p的所有属性数据,这样行不行呢?
这样肯定是不可以的,因为编译器不知道p里面是什么,这里的p是一个对象,不是内置运算符
一旦这样写就会报错: 没有与这些操作数匹配的“<<”运算符
但是我们怎么操作呢?我们需要重载这个符号
*/
class Person
{friend ostream& operator<<(ostream& out, Person& p);//友元,可以访问私有属性public:Person(int a, int b){this->m_A = a;this->m_B = b;}/*成员函数 实现不了  p << cout 不是我们想要的效果如果这样可以的话,那么最终实现的时候应该这样写:p.operator<<(p);这样是什么?不是我们想要的效果,我们只有一个对象,或者:void operator<<(cout){},那么简化的时候:p << cout 也不是我们想要的效果 因为我们想要的是 cout << p因此我们不会利用成员函数去重载左移运算符,因此我们只能用全局运算符去重载左移运算符*///void operator<<(Person& p)//{//  //}private:int m_A;int m_B;
};
/*
全局函数实现左移重载,成员函数不行
ostream对象只能有一个
当返回值未知的时候我们先写一个void,当知道了是什么返回值的类型的时候,再替换掉void
void operator<<(cout, p) //operator<<(cout,p)  这样简化成 cout << p
这里需要知道cout是什么数据类型,转到定义 __PURE_APPDOMAIN_GLOBAL extern _CRTDATA2_IMPORT ostream cout;
ostream翻译过来是输出流对象 cout是标准的输出流对象,o是输出 stream是数据流对象
ostream是一个类 因此就写成了这样
ostream& operator<<(ostream& out, Person& p)
endl是换行的意思
*/
ostream& operator<<(ostream& out, Person& p) //这里用out来替换cout,是因为这里写了out相当于给cout起了别名,因为这里是引用
{out << "a:" << p.m_A << " b:" << p.m_B;return out;
}void test()
{Person p1(10, 20);cout << p1 << "hello world" << endl; //链式编程,本质就是输出一段之后输出的类型和下一段的类型是一样的
}int main()
{test();system("pause");return 0;
}

7.3 递增运算符重载

#include "iostream"
using namespace std;/*
* 4.5.3 递增运算符重载
*
* 作用: 通过重载递增运算符,实现自己的整型数据
*
* 总结,注意重点: 前置递增返回引用,后置递增返回值
*
*
* 思考,学会了递增,也要学会递减
*/
/*
内置数据类型的 ++ 是如何进行的呢?
int a = 10;
cout << ++a <<endl;  //11    前置递增是要 先 递增 后 进行表达式运算
cout << a << endl;   //11int b = 10;
cout << b++ <<endl; //10     后置递增是要 先 进行表达式运算 后 递增
cout << b <<endl;   //11然后我们要自定义,要递增运算符重载 ++
我们要怎么去写呢?
先试着这样写
class MyInteger
{
public:MyInteger() //构造函数里面对这个数据进行初始化{m_Num = 0;}
private://有一个私有成员属性int m_Num;
};
当我们去创建对象的时候
MyInteger myint;
cout << myint << endl;  // 输出就是0
cout << ++myint << endl;// 输出就是1
cout << myint++ << endl;// 输出就是1,这里是因为上面那行代码已经先执行了
cout << myint << endl;  // 输出就是2,这里是因为上面那行代码已经先执行了
因此我们就要分开写前置递增和后置递增
//前置递增//后置递增*///自定义整型
class MyInteger
{friend ostream& operator<<(ostream& out, MyInteger myint);//不写友元,外部无法访问私有属性public:MyInteger() //构造函数对属性进行初始化{m_Num = 0;}//重载++运算符//前置++/*这里一定是要返回引用,不要返回值,因此要写成 MyInteger& 而不是 MyInteger因为如果对于内置数据类型而言,如:int a = 0;cout << ++(++a) << endl;//这里输出为2cout << a << endl;      //这里输出为2说明内置数据类型 ++(++a)是对a进行递增所以我们自定义也要达到这样的效果,而如果我们返回值类型是值而不是引用,是 MyInteger 而不是 MyInteger&就会出现这样的结果,如:MyInteger myInt;cout << ++(++myInt) << endl;  //此时输出是2,因为执行完(++myInt)这个之后,就是一个新的对象了,//因此再执行++(++myInt)时候,就是对(++myInt)这个新对象进行递增,故而读取myInt的时候是1而不是2cout << myInt << endl;        //此时输出是1因此,要返回值是引用,这样就保证了一直是对一个量进行操作,我们要做的是一个高仿 不能只仿制 内置数据类型的一部分功能 要全部*/MyInteger& operator++() //{//先进行++的运算,这样让自己的属性先进行++的操作m_Num++;//再将自身做一个返回return *this;}//后置++/*MyInteger operator++()这里如果这样写,就会报错函数重定义,因此要来一个区别,就来一个参数不同,这里的int就是一个占位参数,这里就是让编译器区分前置和后置递增返回值不能作为区分函数重定义与否的标志,参数可以后置要返回值而不是引用,因为局部对象temp再返回之后,就被释放掉了,如果在对其进行操作就是违法会报错*/MyInteger operator++(int) {//先返回MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;m_Num++;return temp;//最后把记录的结果返回}/** 注意:前置递增返回值类型必须是引用后置递增返回值类型必须是值*/
private:int m_Num;
};//只能用全局函数来写
ostream& operator<<(ostream& out, MyInteger myint)
{out << myint.m_Num;return out;
}//前置++ 先++ 再返回
void test01()
{MyInteger myInt;cout << ++myInt << endl;  //重载前置递增运算符之后,此代码才OKcout << myInt << endl;    //重载左移运算符之后,此代码才OK
}//后置++ 先返回 再++
void test02()
{MyInteger myInt;cout << myInt++ << endl; //重载后置递增运算符之后,此代码才OKcout << myInt << endl;
}int main()
{test01();//test02();system("pause");return 0;
}

7.4 赋值运算符重载

#include "iostream"
using namespace std;/*
* 4.5.4 赋值运算符重载
*
*
* 前面学的是c++编译器至少给一个类添加3个函数
* 这里会增添一个,C++也就学这4个编译器默认的添加的函数
*
*
* c++编译器至少给一个类添加4个函数
* 1.默认构造函数(无参,函数体为空)
* 2.默认析构函数(无参,函数体为空)
* 3.默认拷贝构造函数,对属性进行值拷贝
* 4.赋值运算符 operator=, 对属性进行值拷贝
*
* 如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
*/class Person
{public:Person(int age){//将年龄数据开辟到堆区m_Age = new int(age);}//重载赋值运算符 /*为什么要自己写 重载赋值运算符 函数呢?编译器不是会自动创建吗?这是因为编译器创建的是浅拷贝,如果我们的数据是存在于堆区,那么就会误入浅拷贝的大坑,因此要用深拷贝来解决浅拷贝带来的问题这个问题存在于析构的时候,会造成二次释放,报错所以这是要自己去写这个函数的原因而这个函数有一个需要注意的地方就是 这个函数的返回值一定要有 且是自身这样才能达到和编译器默认的内置数据类型一样的赋值功能 就是连续赋值:a = b = c;*/Person& operator=(Person& p){/*编译器提供的代码是浅拷贝m_Age = p.m_Age;我们不应该是向编译器那样去做,因此我们要先做一个判断*/if (m_Age != NULL){delete m_Age;m_Age = NULL;}//提供深拷贝 解决浅拷贝的问题m_Age = new int(*p.m_Age);//返回自身/*对于编译器自身的内置数据类型含有以下的功能int a = 10;int b = 20;int c = 30;c = b = a;cout << "a = " << a << endl;//输出10cout << "b = " << b << endl;//输出10cout << "c = " << c << endl;//输出10因此我们这里也要写出来有这样的功能,所以我们需要有返回值,因此返回值类型不能是void 也就是必须有返回值且返回值得是自身;那就是this指针,还要找到指针本身 就要解引用 因此就是*this*/return *this;}~Person(){if (m_Age != NULL){delete m_Age;m_Age = NULL;}}//年龄的指针int* m_Age;};/*
该函数就是为了验证 a = b = c;就是返回值类型的问题
*/
void test01()
{Person p1(18);Person p2(20);Person p3(30);p3 = p2 = p1; //赋值操作cout << "p1的年龄为:" << *p1.m_Age << endl;//18cout << "p2的年龄为:" << *p2.m_Age << endl;//18cout << "p3的年龄为:" << *p3.m_Age << endl;//18
}int main()
{test01();//int a = 10;//int b = 20;//int c = 30;//c = b = a;//cout << "a = " << a << endl;//cout << "b = " << b << endl;//cout << "c = " << c << endl;system("pause");return 0;
}

7.5 关系运算符重载

#include "iostream"
using namespace std;/*
* 4.5.5 关系运算符重载
*
* 作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
*
*
*/
/*
首先我们先看一下 内置数据类型 在关系运算符方面实现的功能是什么
int a = 10;
int b = 10;
if(a == b)
{cout << "a和b相等" << endl;
}
那么我们要在自定义类型中实现同样的功能
如:
Person p1;
Person p2;
if(p1 == p2)
{cout << "p1和p2相等" << endl;
}
if(p1 != p2)
{cout << "p1和p2不相等" << endl;
}
这就要用到重载关系运算符
*/
class Person
{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;}else{return false;}}bool operator!=(Person& 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 test01()
{//int a = 0;//int b = 0;Person a("孙悟空", 18);Person b("孙悟空", 18);if (a == b){cout << "a和b相等" << endl;}else{cout << "a和b不相等" << endl;}if (a != b){cout << "a和b不相等" << endl;}else{cout << "a和b相等" << endl;}
}int main()
{test01();system("pause");return 0;
}

7.6 函数调用运算符重载

#include "iostream"
using namespace std;/*
* 4.5.6 函数调用运算符重载
*
* 函数调用运算符 ()  也可以重载
* 由于重载后使用的方式非常像函数的调用,因此称为仿函数
* 仿函数没有固定写法,非常灵活    在后续的STL里面用的非常多
*
*/
/**/
//打印输出的类
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 add;int ret = add(10, 10);cout << "ret = " << ret << endl;/*匿名对象调用上面的方式是创建了一个对象,然后用上述的写法。如果我只是调用一次仿函数,而不是继续对该创建的对象进行其他的操作,那么其实也可以不创建对象,利用匿名对象调用的方式就是下面的写法 MyAdd()(100, 100)直接是仿函数跟上参数列表的数据就可以了*/cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
/*
为什么说仿函数非常的灵活
看着两个例子
第一个函数 void operator()(string text)
第二个函数 int operator()(int v1, int v2)他们的返回值可以不同 参数个数也可以不同 因此很灵活
*/
int main()
{test01();test02();system("pause");return 0;
}

C++学习笔记 - 阶段三:C++核心编程 - Chapter7:类和对象-C++运算符重载相关推荐

  1. 【黑马程序员 C++教程从0到1入门编程】【笔记4】C++核心编程(类和对象——封装、权限、对象的初始化和清理、构造函数、析构函数、深拷贝、浅拷贝、初始化列表、友元friend、运算符重载)

    黑马程序员C++教程 文章目录 4 类和对象(类属性[成员属性],类函数[成员函数]) 4.1 封装 4.1.1 封装的意义(三种权限:public公共.protected保护.private私有)( ...

  2. C++ 学习 ::【基础篇:17】:C++ 类与对象:运算符重载介绍、运算符重载函数(类内与类外区别)写法及简单设计实现

    本系列 C++ 相关文章 仅为笔者学习笔记记录,用自己的理解记录学习!C++ 学习系列将分为三个阶段:基础篇.STL 篇.高阶数据结构与算法篇,相关重点内容如下: 基础篇:类与对象(涉及C++的三大特 ...

  3. C++基础学习笔记(五)——核心编程PART3

    参考链接:https://www.bilibili.com/video/BV1et411b73Z?p=99&vd_source=b4d9cee68649c8adcb1e266f7147cd5c ...

  4. 第四部分—C++核心编程_4. 类和对象

    4.1 类和对象的基本概念 4.1.1 C和C++中struct区别 c语言struct中只有变量 c++语言struct中既有变量,也有函数 4.1.2 类的封装 我们编写程序的目的是为了解决现实中 ...

  5. C++核心编程<类和对象>(4)

    C++核心编程<类和对象> 4.类和对象 4.1封装 4.1.1封装的意义 封装的意义1 封装的意义2 4.1.2struct和class区别 4.1.3成员属性设置为私有 4.2对象的初 ...

  6. C++核心编程4— 类和对象

    C++面向对象的三大特性为:封装.继承.多态 C++认为万事万物都皆为对象,对象上有其属性和行为 例如: ​ 人可以作为对象,属性有姓名.年龄.身高.体重-,行为有走.跑.跳.吃饭.唱歌- ​ 车也可 ...

  7. python学习笔记(十 三)、网络编程

    最近心情有点儿浮躁,难以静下心来 Python提供了强大的网络编程支持,很多库实现了常见的网络协议以及基于这些协议的抽象层,让你能够专注于程序的逻辑,而无需关心通过线路来传输比特的问题. 1 几个网络 ...

  8. C++_类和对象_C++运算符重载_左移运算符重载_链式编程_实现直接打印对象---C++语言工作笔记056

    然后我们再去看左移运算符,实际上就是那个<< 小于号 为什么要重载他呢?因为我们想实现一个功能,比如我仅仅是cout << p 就可以打印这个对象,现在肯定是不行对吧. 我们用 ...

  9. 【黑马程序员】C++核心编程2 -类与对象(封装、继承和多态)-this指针-友元-运算重载符-文本操作(附测试用例源码、测试结果图及详细注释)

最新文章

  1. c语言 浮点型数据怎么存放,C语言学习之浮点型数据存储
  2. 在C#中调用windows API函数
  3. java thumbnails 中心点_java Thumbnails 图片处理的使用
  4. MATLAB保存数据为dat格式,將matlab中數據保存為txt或dat格式
  5. Quick BI 功能“炸弹”:即席分析、模板市场、企业微信免密登录等强势功能
  6. python删除第一行_Python删除文件第一行
  7. php 获取最后执行的sql,如何获取ThinkPHP框架最后一次执行SQL语句及变量调试
  8. linux鼠标键盘被禁用了,debian squeeze下鼠标、键盘突然被系统禁用
  9. MyEclipse:新导入一个项目时中文乱码
  10. Rust或C#,Python 等如何封装C++的接口 (比如CTP)?
  11. 计算机进化史(纯科普)
  12. 常见python基础面试题_常:汉字常的意思/解释/字义/来源 - 新华字典
  13. 计算机思维测试题,孩子逻辑思维测试题有哪些
  14. jmeter插件之Dummy Sampler
  15. MYSQL 数据库详解
  16. 狼、羊、菜、农夫过河问题 穷举 Python实现
  17. android实现悬停效果代码,Android StickListView实现悬停效果
  18. 硬件知识:独立显卡和集成显卡的区别
  19. ExecuteNoQuery()返回值
  20. 开关电源电路图及原理详解

热门文章

  1. 前端项目jenkins自动化部署
  2. OPPO第二颗自研芯片来了,首次实现192kHz/24bit无损音频蓝牙传输,台积电6nm工艺...
  3. Word里安装Mathtype插件
  4. warez和0day
  5. 10月24号,我们腾讯滨海大厦见!
  6. android 上传图片进度条,Android带进度条的文件上传示例(使用AsyncTask异步任务)...
  7. 周末一天的计划安排,收获满满的成就感
  8. Velocity Map
  9. CentOS、RedHat、Fedora安装FFmpeg环境及解码器
  10. 老男孩 redis mysql_老男孩教育2020年Mysql,NoSQL-Redis 数据库(上)