wrdoct

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

(5)运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。用到的代码是operator
①加号运算符重载
实现两个自定义数据类型相加的运算。
【注】对于内置的数据类型,编译器知道如何进行运算。
在这里插入图片描述
【注】重载加号有两种办法:一种是通过成员函数重载加号;另一种是通过全局函数重载加号。需要使用到:operator+
【注】成员函数重载本质为:
在这里插入图片描述
但是可以简化为:在这里插入图片描述
【注】全局函数重载本质为:
在这里插入图片描述
但是可以简化为:
在这里插入图片描述
【注】运算符重载,也可以发生函数重载。

【总结】对于内置的数据类型的表达式的运算符是不可能改变的。
【总结】不要滥用运算符重载。(如+号变-号等等)
②左移运算符重载
可以输出自定义数据类型。
在这里插入图片描述
【注】不会利用成员函数重载<<运算符,因为无法实现cout在左侧。
【注】只能利用全局函数重载左移运算符:
在这里插入图片描述
【注】ostream //输出流 的返回类型
③递增运算符重载
通过重载递增运算符,实现自己的整型数据。
【注】++a和a++区别:
在这里插入图片描述
【注】区分前置和后置递增使用占位参数int
【注】前置递增返回引用,后置递增返回值。(假设后置返回的是引用,那么会是一个局部对象的引用,当前函数执行完以后,这个对象就会被释放掉,所以如果此时还要返回它的引用,便会是非法操作)
在这里插入图片描述
④赋值运算符重载
C++至少给一个类添加4个函数:
a.默认构造函数(无参,函数体为空)。
b.默认析构函数(无参,函数体为空)。
c.默认拷贝构造函数,对属性进行值拷贝。
d.赋值运算符operator=,对属性进行值拷贝。
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题。
【注】在堆区开辟内存要释放,但是加上析构函数后会导致释放两次内存,致使程序崩溃。——要利用深拷贝解决这个浅拷贝带来的问题。
在这里插入图片描述
【注】返回的 *this 一定要是引用 &
⑤关系运算符重载
重载关系运算符,可以让两个自定义类型对象进行对比操作。
在这里插入图片描述
所以才需要重载关系运算符。
在这里插入图片描述
在这里插入图片描述
⑥函数调用运算符重载
a.函数调用运算符()也可以重载
b.由于重载后使用的方式非常像函数的调用,因此称其为仿函数
c.仿函数没有固定写法,非常灵活
【注】匿名函数对象:
在这里插入图片描述
(6)继承
继承是面向对象三大特性之一。
有些类与类之间存在特殊的关系,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码。
①继承的基本语法
语法:class 子类 : 继承方式 父类
【注】子类也称为派生类;父类也称为基类。
在这里插入图片描述
继承使用方法是在不同界面的类后面加一个:再加上public再加上公共页面的类名称即可。
在这里插入图片描述
【注】派生类中的成员包含两大部分:
一类是从基类继承过来的;一类是自己增加的成员;
【注】从基类继承过来的表现其共性,而新增的成员体现了其个性。
②继承方式
继承的语法:class 子类 : 继承方式 父类
继承方式一共有三种:
a.公共继承
b.保护继承
c.私有继承
在这里插入图片描述
【注】对于公共继承:
父类中的公共权限成员 到子类中依然是公共权限;父类中的保护权限成员 到子类中依然是保护权限;父类中的私有权限成员 子类访问不到。
在这里插入图片描述
【注】保护权限——类外访问不到:
在这里插入图片描述
【注】对于保护继承:
在这里插入图片描述
在这里插入图片描述
【注】对于私有继承:
在这里插入图片描述
在这里插入图片描述
【注】私有继承后全变为私有权限,即使是儿子也访问不到:
在这里插入图片描述
③继承中的对象模型
父类中所有非静态成员的属性都会被子类继承下去。
父类中私有成员属性 是被编译器隐藏了,因此访问不到,但是确实是被继承下去了
在这里插入图片描述
【注】查看继承的信息(利用开发人员命令提示工具查看对象模型):
在这里插入图片描述
首先跳转盘符,回车之后跳转文件路径(cd 具体路径下),
回车之后查看命名(cl /d1 reportSingleClassLayout类名 文件名)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

【注】reportSingleClassLayout+类名——————报告单个类布局。
最终输出信息如下(m_A、m_B、m_C是父类继承过来的,m_D是子类特有的):
在这里插入图片描述
④继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数。
在这里插入图片描述
【注】继承中的构造和析构顺序如下:
先构造父类,再构造子类;析构的顺序与构造的顺序相反。
⑤继承同名成员处理方式
访问子类同名成员,直接访问即可;
访问父类同名成员,需要加作用域。
【注】如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数:
在这里插入图片描述
在这里插入图片描述
如果想访问到父类中被隐藏的同名成员函数,需要加作用域:
在这里插入图片描述
【总结】
子类对象可以直接访问到子类中同名成员;
子类对象加作用域可以访问到父类同名成员;
当子类与父类拥有同名的成员函数,子类会隐藏父类中所有同名成员函数,加作用域可以访问到父类中同名成员函数。
⑥继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致:
访问子类同名成员,直接访问即可;
访问父类同名成员,需要加作用域。
在这里插入图片描述
【注】上图中“通过类名访问”中,第一个::代表通过类名方式访问,第二个::代表访问父类作用域下。
在这里插入图片描述
【注】与上一个类似:当子类与父类拥有同名的静态成员函数,子类会隐藏父类中所有同名静态成员函数,加作用域才可以访问到父类中同名的静态成员函数。
⑦多继承语法
C++允许一个类继承多个类。
语法:class 子类 : 继承方式 父类1 , 继承方式 父类2 , ……
多继承可能会引发父类中有同名成员出现,需要加作用域来区分。
【注】C++实际开发中不建议用多继承
【注】父类中有同名成员出现,需要加作用域来区分:
在这里插入图片描述
⑧菱形继承
菱形继承概念:
两个派生类继承同一个基类;
又有某个类同时继承着两个派生类;
这种继承被称为菱形继承,或者钻石继承。
【注】可能引发二义性:
在这里插入图片描述
【注】当菱形继承,两个父类拥有相同数据,需要加以作用域区分:
在这里插入图片描述
【注】菱形继承导致了数据有两份,即子类继承了两份相同的数据,资源浪费且毫无意义。
在这里插入图片描述
利用虚继承 解决菱形继承的问题。在继承之前加上关键字 virtual 变为虚继承,而最大的类——Animal类称为 虚基类。

【注】vbptr:虚基类指针,指向对应的vbtable:

(7)多态
多态是面向对象三大特性之一。
多态(一个接口有多种形态)分为两类:
a.静态多态:函数重载和运算符重载属于静态多态,复用函数名。
b.动态多态:派生类和虚函数实现运行时多态。
静态多态和动态多态区别:
静态多态的函数地址早绑定—编译阶段确定函数地址;
动态多态的函数地址晚绑定—运行阶段确定函数地址。
①多态的基本概念
【注】如果想执行让猫说话,那么这个函数的地址就不能提前绑定,需要在运行阶段进行绑定,即地址晚绑定
【注】动态多态满足条件:
有继承关系;子类要重写(函数的返回值类型相同,函数名相同,形参列表中所有的内容也相同)父类的虚函数。

【注】动态多态使用条件:父类的指针或者引用指向子类的对象。

【注】空类的大小是1,即占1个字节。
可是加上virtual变为虚函数之后(相当于Animal中有了一个指针,其所占大小为4字节)
而当子类重写父类的虚函数,子类中的虚函数表内部会替换成子类的虚函数地址。

【验证】没有加virtual时:
在这里插入图片描述(1个字节)
加上virtual时:
在这里插入图片描述
(4个字节)
Cat没有重写函数时:
在这里插入图片描述
(Animal::speak)
Cat类中重写了函数时:
在这里插入图片描述
(Cat::speak)
【注】多态优点:
代码组织结构清晰;可读性强;利于前期和后期的扩展以及维护。
【注】在真实地开发中,提倡 开闭原则。
开闭原则:对扩展进行开放,对修改进行关闭
【总结】C++开发提倡利用多态设计程序架构,因为多态优点很多。
②纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。
因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 ( 参数列表 ) = 0 ;
当类中有了纯虚函数,这个类也称为抽象类。
抽象类特点:
a.无法实例化对象; //(实例化:通过一个类,创建一个对象的过程)
在这里插入图片描述
b.子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
在这里插入图片描述
在这里插入图片描述
③虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。(父类指针在析构时候 不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏)
在这里插入图片描述
解决的方式为:将父类中的析构函数改为虚析构或者纯虚析构。(利用虚析构可以解决 父类指针释放子类对象时不干净的问题)在这里插入图片描述
在这里插入图片描述
【注】在使用纯虚析构时要在额外的加上具体的实现功能(需要声明,也需要实现)在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
虚析构和纯虚析构的共性:
可以解决父类指针释放子类对象;都需要有具体的函数实现。
虚析构和纯虚析狗的区别:
如果是纯虚析狗,该类属于抽象类,无法实例化对象。
虚析构语法:
virtual ~类名( ){ }
纯虚析构语法:
virtual ~类名( )= 0; //声明
类名::~类名( ){ } //实现
【总结】
a.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象;
b.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构;
c.拥有纯虚析构函数的类也属于抽象类。

5.文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放。
通过文件可以将数据持久化。
C++中对文件操作需要包含头文件
文件类型分为两种:
a.文本文件:文件以文本的ASCII码形式存储在计算机中。
b.二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们。
操作文件的三大类:
a.ofstream 写操作
b.ifstream 读操作
c.fstream 读写操作
(1)文本文件
①写文件
写文件步骤如下:
a.包含头文件: #include
b.创建流对象: ofstream ofs;
c.打开文件: ofs.open(“文件路径”,打开方式);
d.写数据: ofs<<“写入的数据”;
e.关闭文件: ofs.close();
【注】文件打开方式(in读out写):
在这里插入图片描述
【注】文件打开方式可以配合使用,利用 | 操作符。
在这里插入图片描述
【总结】
文件操作必须包含头文件fstream;
读文件可以利用ofstream,或者fstream类;
打开文件时需要指定操作文件的路径,以及打开方式;
利用 << 可以向文件中写数据;
操作完毕,要关闭文件。

②读文件
读文件与写文件步骤相似,但是读取方式相对于比较多。
读文件步骤如下:
a.包含头文件: #include
b.创建流对象: ifstream ifs;
c.打开文件并判断文件是否打开成功: ifs.open(“文件路径”,打开方式);
d.读数据: 四种方式读取
e.关闭文件: ifs.close();
【注】读数据的四种方式:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【总结】
读文件可以利用ifstream ,或者fstream类;
利用is_open函数可以判断文件是否打开成功;
close关闭文件。

(2)二进制文件
以二进制的方式对文件进行读写操作。
打开方式要指定为 ios::binary
①写文件
二进制方式写文件主要利用流对象调用成员函数write。
函数原型:ostream& write(const char * buffer , int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数。
【注】以下两张图等价:
在这里插入图片描述在这里插入图片描述
【注】
【总结】文件输出流对象,可以通过write函数,以二进制方式写数据。
②读文件
二进制方式读文件主要利用流对象调用成员函数read。
函数原型:istream& read(const char * buffer , int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数。
【注】 在这里插入图片描述
【总结】文件输入流对象,可以通过read函数,以二进制方式读数据。

posted on 2022-07-03 20:28  wrdoct  阅读(31)  评论(0)    收藏  举报