C++学习-6 如何很多全面学习C++面向对象特性;已基本完成、缺少总结和补充,后期补上

笔记目录
一;如何学习面向对象以及如何实现面向对象,面向对象要考虑什么问题
1、如何学习面向对象
	具体的学习方法就是先理念;再不同面向对象的语言特性,最后就是学习实战案例
2、面向对象的编程步骤
	需要注意的是一般面向对象的实现都是两部分人一个是类库的设计实现者,还有一部分是类库的调用者,学习面向可以需要有这种思维是分工合作的,很多特性都是为了保证这两部分的协作而引入的一些特性。
3、原生支持面向对象和非原生支持面向对象
	都能实现面向对象,只是原生的方便简单,而非原生的需要自己去实现一遍面向对象的细节。
4、面向对象思想总结
5、典型C++面向对象编程
	分成两部分;类的实现者(类的hpp和cpp),类的调用者,调用类库实现功能解决问题的

二;C++的面向对象之封装特性
1、C++的对象相关的语言特性-构造和析构
	对象相关的C++的构造和析构
		1)概念;
		2)为什么需要引入构造和析构函数
		3)析构函数被调用的两种情况以及如何申请释放动态内存
		4)介绍一个Valgrind查看内存泄露的工具
		5)构造函数的语法细节
			(1)C++编译器会提供默认空的构造函数
			(2)C++成员初始化列表
			(3)构造函数使用参数默认值
		6)拷贝构造函数的引入以及其引发的深浅拷贝问题
			(1)为什么要引入拷贝构造函数
			(2)拷贝构造函数的语法细节
			(3)深浅拷贝

2、C++的访问权限相关的语言特性
	访问权限的概念
		1、为什么要设计访问权限,而c里面没有这个
			1)保护资源;
			2)隐藏外部无需知道的细节
			3)体现的封装特性
		2、通过访问权限带来的编程理念
			“全关闭,再按需获取
		3const常方法和mutable打洞原则
			1)先通过一个报错引出常方法用在面向对象的哪里
			2mutable打洞破坏const常方法的完全绝对不改变任何一个属性
			3class的前置声明

三;C++继承和多态相关的语言特性
1、继承
	1)、继承的特性,本质
		继承是让子类一瞬间拥有父类所有属性方法的语法糖
	2)、继承中的访问权限管理
		(1)三个访问权限实际是四个级别
			有一个比private还有严格的等级
		(2)这四个级别分别在什么时候出现
		(3)如何设计访问权限
	3)继承体系下子类和父类的关系
		子类父类是两个独立的类
	4)隐藏覆盖和类型兼容裁剪机制
		父子类同名成员函数问题(隐藏覆盖机制)
		类型兼容的裁剪机制
	5)父类子类构造析构的关系
		(1)子类不继承父类的构造析构函数
		(2)为什么子类构造析构的时候会调用父类的构造析构
		(3)子类到底是怎么实现的
	6)继承的优势与不良继承
		代码复用及构建层次框架
	7)组合和继承;都是代码复用的方式
		优先组合再继承
	8)多继承和二义性以及虚继承解决二义性问题
		(1)多继承语法
		(2)多继承如何引发二义性	
		(3)C++专门引入虚继承virtual的语言特性来解决二义性问题
			转载用内存结构解释虚继承

2、多态和虚函数
	1)、多态的引入
		对比隐藏和多态、以及多态需求根据对象来调用成员而不是引用
	2)、多态的特征及关键实现

四;C++其他一些辅助的语言特性
1、、纯虚函数
2、虚析构函数
3using重新定义继承访问权限问题

五;运算符重载
1、重载
	函数重载和运算符重载都是语法糖
2、运算符重载的本质
	执行相应的成员函数,用operator运算符为函数名
3、理解运算符各种细节
	1)运算符重载总的规则:运算符左边的是this,右边的是other,运算符加操作数的整个表达式的返回值就是返回值
	2)特殊的运算符重载--------赋值运算符重载
	默认提供、
	与拷贝构造调用时的区别、
	避免赋值运算符中的自赋值、
	赋值运算符重载函数返回引用,少了一次调对象赋值、
	赋值运算符中涉及动态内存时的深浅拷贝问题
	++和–运算符的前置后置如何实现
	实现运算符重载的两种方法

六;静态类
1static关键字的三种用法
2、静态方法,静态类概念及相关属性
	从本质理解一些规则
	为什么静态成员不能在构造函数,初始化成员列表初始化
	为什么静态成员需要在外部进行初始化
解析静态成员的本质;限定作用域为类作用域的全局变量,他就是通过全局变量来实现的只是将其划分绑定到具体的类中
3、静态成员的用途
	静态数据成员的用途之一是统计有多少个对象实际存在
	静态成员与面向对象
4、静态类

七;友元函数和友元类
1、友元函数的引入
	需要有外部函数要访问该类的私有属性的时候
2、使用方法
3、友元函数的两种实现
	一种为外部函数,一种为其他类的成员函数。
4、友元类
	友元类其实就是批量制造友元函数的一种方法,将该类中所有方法都统一全部声明为那个类的友元函数
5、为什么会有友元函数
	使用友元函数的优缺点
	用友元函数的两种情况
		(1)运算符重载的某些场合需要使用友元
		(2)两个类要共享数据的时候
	友元函数和类的成员函数的区别
		根本区别就是友元函数是外部的,不属于该类
	共有友元函数
		1个函数同时成为2个类的友元函数

七;一些剩余特性
1、嵌套类
	嵌套类主要是限定了内部类的作用域
2、局部类
	定义在函数内部的类,叫做局部类,只在定义他的作用域内可见,也是一种类型隐藏技术
3、总结
4、数值与对象互转
	数值与对象概念
	C++中数值与对象互转
		(1)数值转对象,实际是调用形参类型相匹配的构造函数来实现
		对象转数值,不能默认转,必须调用对象的相应转换函数来实现。
		(2)对象数组
			注意如果是用new来分配的对象数组,则销毁时要用delete[] xx;
八;总结
主要进行总结归纳如
C++中哪些语言特性涉及"打洞";
C++中哪些语言特性是语法糖,只是编译器为辅助开发增加的语法;
C++中哪些语言特性是保证类设计实现者和类库调用者的协作能力的;
大致总结一些这些C++特性引入的需求;

一;如何学习面向对象以及如何实现面向对象,面向对象要考虑什么问题

1、如何学习面向对象
要有框架思想,从已知到未知,多从案例优秀源代码中去学习去感受才能学到东西。并且正在学会面向对象这种思想也是需要一定代码量的。
具体的学习方法就是先理念;再不同面向对象的语言特性,最后就是学习实战案例
理念就是面向对象的思想是怎么回事,如何实现面向对象的
面向对象的语言特性;这个注意,面向对象的语言特性一般都是非常多的,特别是C++还需要兼容效率,因此其语言特性是非常非常多的,这也导致C++难学的重要原因。

如何学习语言特性细节;要知道语言特性都是某门语言为了解决某个具体问题而引入的,那么我们应该站在问题的角度来思考反推这个语言特性,再结合语言特性的学习验证如何解决这个问题的,相互佐证的方法来学习加深印象,并且通过这种学习方法还可以在后续案例学习和自己实战过程中对于这些语言特性反应得更快,达到看到使用了这个语言特性就知道他是在干嘛,是为了解决什么问题才这样做的,达到我现在有什么问题需要用什么语言特性来实现更好。

2、面向对象的编程步骤
(1)第1步,是从理念上把你要处理的任务当成一个对象
(2)第2步,将你的“任务完成”分解成“对象”的多个方法(function)以及需要的相关属性的依次完成
(3)第3步,抽象出你的“对象”的共性成为模型(类,class),类就是对象抽象化的模型,用于快速构造对象的语法糖。【注意这一步就相当于我们在.h文件中类模板的声明定义】
(4)第4步,方法中需要用到的变量(variable)定义为属性(property),用class内包含的变量来实现
(5)第5步,实现你的“对象”抽象出的calss的全套数据结构和函数实体;就是干活实现具体模型的一系列具体内容。【注意这一步就相当于在对应.cpp文件中对类方法的实现】
(6)第6步,调用类库及相关方法,顺理成章的完成项目。

注意;在面向对象中一般工程都比较庞大,那么分工协作是非常有必要的,
那么面向对象语言有很多语言特性都是为了不同人不同层级之间有很明了的分工协作而引入的。在这里面向对象的编程步骤而言,一般步骤1到步骤5是如何设计面向对象,设计类库的,而步骤6才是最后做事情的人通过调用类库来实现一些功能。那么为了协调这两部分人的工作也成了面向对象这么语言的一个重要工作。

3、原生支持面向对象和非原生支持面向对象
C语言就是非原生支持面向对象的,而C++就是面向对象的语言则表示他原生支持面向对象。但是C语言仍然可以实现和去设计面向对象的思想来实现案例,linux庞大工程就是C语言实现的,而其中肯定不乏缺少面向对象的思想。
那原生支持和非原生支持的实现面向对象的区别只是原生的因为引入了很多支持面向对象的语言特性,实现面向对象起来很方便简单,而非原生的就要自己去实现这些语言特性从而达到一样的面向对象的效果。例如非原生支持的则表示要自己去绑定设计对象模型及用函数指针等一系列手段才能实现,而原生支持的则直接提供了class直接定义对象模型即可,不需要一些底层手段来实现,不需要额外做什么,大大节约了人力,但是代价也是多出了很多语言特性需要学习。从而可以看出我们学习的面向对象的语言特性其实本质也就是非原生编程语言去绑定创建的,因此我们有时候去学习一个语言特性的时候完全可以看他c语言是怎么实现的,从本质去了解他。

4、面向对象思想总结
(1)面向对象是针对面向过程说的,编程时关注对象由“过程”转为“对象”,(对象是谁,有哪些属性方法,在抽象出来成为类模板,之后就可以利用类模板来创建对象从而调用方法属性来完成具体功能任务)
(2)面向对象的外部表象就是一种组织代码,写程序的方式
(3)面向对象的本质是一种封装数据和看待问题的更高层次的视角,一种方式,一种思想,设计想法。
(4)面向对象是应对越来越复杂问题,是处理越来越庞大程序的更有效的方法,因为他可以实现人的设计理念从而让问题更加简便清晰。
(5)语言由非面向对象升级到面向对象是自然而然的成长,和人一样

5、典型C++面向对象编程
1)一个完整的C++程序应该包含3个部分
类的头文件.hpp 用于类模板的声明定义(是类全部细节的声明,没有实体,不占内存),这一步就是讲类对象抽象出来成模板进行定义
类的源文件.cpp文件 用于类模板的方法实现(是将类抽象出来的方法进行一个具体的实现)
主程序;类对象的创建及通过调用方法实现具体功能(是创建抽象出来的对象实例,来调用里面的方法,解决问题来编程)

其实这三部分可以分为两部分,其实也就是我们分工合作也要分开协作的两部分人, 类的抽象声明定义实现;也就是类库的实现者,
创建类对象解决问题;也就是类库的调用者,
注意;解决实现者和调用者的良好协作,保证程序的正常运行这个理念支撑了很大一部分C++语言特性
根据分工分成这两部分,那么其实C++整个工作也是分为这两块
1;建模和编写类库的,就是将问题抽象化为类并实现里面的方法,如写opencv图像解析开源库的人
2、使用类库来编写主程序完成任务的,就是创建类对象实例来调用相关方法解决问题,如在自己主程序中调用OpenCV类库完成某向具体的图像识别任务的。

在这里插入图片描述
2、C++学习的3重境界
语法层面;先学会如何利用C++来建模、来编程,学习语法时先别解决难度大问题。如直接上来就研究OpenCV开源库,这里面就很多麻烦东西,如果语法层次没有过得很熟,那种一看就知道这是什么,这样做是准备干什么,最好可以知道他的优劣那就更好,否则不要一上手就研究难的,因为C++语法特性多而杂,语法不熟直接上代码很难读懂的,不然一个语言特性还要研究半天那就很难搞的。
解决问题层面;学习如果理解并调用现成类库来编写主程序解决问题,如调用OpenCV,这个时候就可以查看opencv源码,协作调用写程序解决问题,以及可以根据需求修改源码
自己编写类库和sample给别人用;这个不仅需要基础好而且更强调一定的架构思想

二;C++的面向对象之封装特性

封装特性相对于其他特性而言是相对简单的,只是引发的各种语言特性需要掌握。应该做到理解每一种设计原理为什么需要引入这种语言特性,具体用在哪里,怎么用。

C++的对象相关的语言特性-构造和析构

对象相关的C++的构造和析构
1)概念;
构造函数constructor 用来构造对象的函数,在对象产生的时候自动调用(也是回调hook函数会被自动调用,可以重载的),主要用于对象属性的初始化以及动态内存的分配。
析构函数destructor 用来析构对象的函数,在对象消亡的时候自动调用,(也是回调hook函数会被自动调用,函数名唯一不用传参,唯一的不可重载),主要用于回收构造函数中分配的动态内存,避免内存丢失。
2)为什么需要引入构造和析构函数
对比对象和普通变量的初始化;

——由于普通变量简单可以直接赋值,而对象是复杂类型,其中不仅其中可能包含各个属性还有方法还需要申请内存,不是可以直接赋值这么简单的,因此引入构造函数用一个函数专门用来完成对象的初始化式是十分有必要的。
[扩展一下C语言中struct也被gcc扩展了初始化式也就是我们看到的struct中.b = 1;这种赋值形式]

关于动态内存方面引入构造和析构是十分方便的;

——可以使用构造函数来为当前对象申请动态内存,在析构函数中释放,则形成了动态内存的完整使用循环,并且容易管理。对比C语言中的struct则没有构造析构函数,则他时候动态内存只能在定义变量完成之后再由程序员手动申请内存再进行赋值,并且之后还要去手动释放,这些都需要程序员来特定场景完成,让程序变得复杂而且还容易出错。而C++中引入构造析构则很好的解决了这个问题,并没有给程序加大复杂度,就对在使用类库方面而言,调用则是完全不用管申请释放的,我只需要调用构造析构就行了,而内存方面的问题就由设计类库模板和实现类库的人来考虑,并且也是统一管理的,在构造中申请,析构中释放。这样就有很好的隔离封装,是C++支持面向对象的一大语言特性。

3)析构函数被调用的两种情况以及如何申请释放动态内存
如果用new创建对象,那么必须使用delete才会调用析构函数,如果没有delete是不会调用析构的
如果是在栈上分配对象,那么在当前生命周期到的时候就会被释放调用析构函数的

怎么申请释放动态内存;如
pi = new int(55); delete pi;
pisz = new int[50]; delete[] pisz;//释放数组的方式delete[]

4)介绍一个Valgrind查看内存泄露的工具
C++是十分容易内存泄露的,就是因为他是在构造函数中申请动态内存,方法中使用,析构函数中释放,但是这一套流程在复杂程序中如引入继承,多态,友元等复杂特性之后这个简单的申请,使用,释放的流程是是否容易出错的。例如后面的浅拷贝中存储动态内存,进行释放的时候就会出现重复释放,导致第二次释放的时候指针已经为空了还去delete就会奔溃了。因此引入一个检查内存泄露的工具也是是否有必要的。
valgrind工具介绍:引用参考一下博客:Linux下几款C++程序中的内存泄露检查工具
安装:sudo apt-get install valgrind
编译程序:主要是添加-g参数便于调试时有行号 g++ person.cpp main.cpp -g -o apptest;-g 表示已Debug模式在编译时在可执行程序中吧符号表加载进来
使用:使用工具查看前先需要编译链接得到最新的可执行程序再执行valgrind命令,注意valgrind它存在代理执行,意思就是他会一边执行可执行程序一边监控内存并最后显示检测结果
valgrind --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes ./app
在这里插入图片描述
在这里插入图片描述

5)构造函数的语法细节
构造函数的一大功能就是完成类成员的初始化
(1)C++编译器会提供默认空的构造函数,A(){};这种并且A a直接这些创建对象就是调用这个构造函数,但是注意如果我们已经定义了其他非空的构造函数那么编译器就不再提供默认的空函数并且如果你还有需要空构造函数的需求那么就要自己实现一个了。案例实践一下

在这里插入图片描述
(2)C++成员初始化列表
一般用于带参构造函数中,用来给属性传参赋值
成员初始化列表和构造函数之间用冒号间隔,多个列表项之间用逗号间隔
初始化列表可以替代构造函数内的赋值语句,达到同样效果
在这里插入图片描述
3)构造函数使用参数默认值
就是在函数声明的定义对形参赋值则为默认值,从而在传参的时候可以不要传已经有默认值的参数,注意默认值要从右开始连续赋值默认,不能断开的否则实际调用的时候不能识别。并且仍然传参会优先使用传参值而不是默认值的,但是如果不传参则会用默认值的。
特别注意使用参数默认的时候要注意一个参数默认函数可以顶几个函数,这时候要注意不能让调用有歧义,否则编译器是通不过的。
在这里插入图片描述
6)拷贝构造函数的引入以及其引发的深浅拷贝问题
(1)为什么要引入拷贝构造函数
同样对比普通变量的用另外一个同类型普通变量来赋值,直接赋值就可以了,与构造函数引入同理,因为对象类型复杂不可能可以靠简单的直接赋值就来完成,那么引入一个函数来完成通过一个类型兼容的对象来初始化也是很有必要的(这里将的类型兼容后面会进行描述),从而就引入了拷贝构造函数来解决这个问题。
(2)拷贝构造函数的语法细节
首先拷贝构造函数是构造函数的一种,那么目的也是为了完成类成员的初始化和内存分配,只是拷贝构造函数只作用于让对象来给对象赋值,注意其实也就是让对象在初始化的时候能够向简单变量一样直接被=等于赋值,但是注意对象这里的直接=等于赋值涉及到C++编译器默认提供的赋值运算符重载相关的知识点而不是这里的拷贝构造函数,我们说的拷贝构造函数是这样的 A a; A b(a),对象b直接使用对象a来进行初始化这样的。
构造函数不需要重载,引入其函数原型是固定的,因为他也只能传入对象类型一个参数
A::A(const A& a)也就是只能是这种形式。他很适合用初始化成员列表来完成它的赋值任务。
在这里插入图片描述
3)深浅拷贝

注意;C++编译器又提供了默认的拷贝构造函数,并且是对类所有属性的复制的构造函数,同样也因此我们不是涉及到深拷贝那么就不用去特意显示再去实现一遍拷贝构造函数,从而在C++编程中也就默认了,如果你显示实现拷贝构造函数那么就一定是涉及到了深拷贝,那就要注意动态内存了。

引入一个奔溃的例子来引出拷贝函数有个的深浅拷贝问题
在这里插入图片描述

错误纠正反思;PS;这个奔溃代码根本原因是自己编程错误,将后面要拷贝对象要用的对象p在使用前就delete导致奔溃的,修改后在本电脑编译器运行是不会奔溃的,还是回去delete那块内存,但是其实也是没有问题的,但是我们不建议这样做,因为delete之后那么计算机就可以将那块内存又分配给其他程序变量去使用,而你又无缘无故再去delete它那么就可能出现偶发性的大bug,因此这次编译器没有给我们检查出来的bug我们更加注意,使用动态内存要一思二思,多思考。

在这里插入图片描述

引发奔溃的根本原因就是二次释放同一块堆内存导致的,但是为什么会有二次释放相同推内存呢?原因就是浅拷贝(只直接对属性进行复制),直接复制那么就有了两个对象中各自的那个指针变量都指向同一块内存,那么第二个释放的对象肯定会去释放野指针呀。如何解决这样的问题,就引出了深拷贝,

C++默认提供全属性的浅拷贝,但是如果遇到动态内存相关的申请释放的时候则使用浅拷贝则会出问题,这个时候就需要自己显示定义一个拷贝构造函数并且里面对动态内存那块自己做处理,这就是深拷贝,对内存方面做处理的拷贝。
深拷贝;深的意思就是不止给指针变量本身赋值,更重要的是要给指针指向的空间分配内存(如果有需要还要复制内存内的值)。

以上面案例如何修改才能正常运行;
在这里插入图片描述

C++的访问权限相关的语言特性

1、访问权限的概念
研究c++ 的访问权限其实就是考虑类的成员属性和方法在类的方法中(这里面还会包含继承友元等复杂属性)和在外部代码中都是什么情况,能否访问,怎么访问。就是研究这些东西。

public访问权限是全局的;在类的成员方法中可以直接访问,在任何外部代码中可以通过类的对象来直接访问
private就是对内不设防,对外完全设防的;在类的成员方法中可以直接访问,注意继承的情况,在任何外部代码中不可以通过对象来直接访问,注意友元,静态方法的情况。
protected是第三种访问权限修饰符,介于public和private之间,只有在继承的时候有区别,在继承的时候说明

在这里插入图片描述
2、为什么要设计访问权限,而c里面没有这个
其实也是因为c++面临的设计层面变大了,需要团队协作的需求更大了,那么引入访问控制权限其实也就是实现面向对象的封装特性,隔离组成保护,也注意引入这些特性以及下面介绍的更多语言特性都是为了让C++兼顾效率的同时还可以实现庞大的业务,正事因为这些所以才需要提供很多的语言特性来满足效率和业务的平衡。
1)保护资源;
首先要知道private,public这些访问权限是C++编译器层次的权限访问保护,通过报错的方式保证访问权限按照对应的规则访问,但是成员属性在内存这个角度是没有访问权限这一说法的。他是针对编译器而言的。
要记得在C++开发中设计实现类库和调用使用类库的一般都是两拨人,分工合作的,那么就可以通过访问权限进行增加两者的协作,因为如果类库中定义为private属性的成员属性和方法,那么调用者直接可以很明确的知道,这些属性方法是我不能访问的,这些方法是实现类库的人自己实现某些功能定义的,只供他们使用,我调用类库的人是不能使用的。这些很好的做了资源的保护,将类库中使用的资源进行隔离,只能在类内部使用,而外部是无法使用的,那样就保证了资源的封闭不外漏,从而也可以验证如果private访问权限的出现问题那么一定是类的内部引起的,不可能是外面引起的,也做了很好的分割。

2)隐藏外部无需知道的细节
这个也就是将只因class内部要使用的属性方法限定在类的内部,而外部无法知晓调用,以避免外部的干扰或者被其修改,隐藏这些细节不让外部调用这也降低了使用类库的人的难度,他们只需要关注public自己能够调用的东西即可,其他的无需关注。

3)体现的封装特性
抽象,隔离;隐藏了不必要的细节
组成和保护;便于整体和外部更合理的交流,就是类的实现者和调用者的整体和外部的交流

3、通过访问权限带来的编程理念

“全关闭,再按需获取”,保护封装特性,用方法替代公共定义,保证类这边的封装独立,只提供出需要的接口供外部来使用,而不允许外部直接访问类的内部,只能通过提供的接口进行访问

这种理念;在访问权限这里的表现就是
利用访问权限加特定的方法,将属性设置为private类型,但是再提供一个内部public方法来读/写(根据需要)这个属性,从而达到只能让外部通过提供的公共接口方法来操作类的属性资源,这种函数式访问替代了public变量的直接使用,即保护了资源的不外露又对类进行了更好的隔离封装。

案例实践
在这里插入图片描述
4、const常方法和mutable打洞原则
const常方法;概念就是该类的这个方法承若保证在这个函数内部不会修改class的任何一个变量,注意是完全绝对的任何一个属性。

1)先通过一个报错引出常方法用在面向对象的哪里;
在这里插入图片描述
这个为什么报错呢?嘿嘿,这个其实就是编译器为常方法定义的一个规则,也是通过这个规则来达到设计实现类的人和调用类库的人之间的程序的一个正确性,保证其不会出隐在的错误。

语法规则;const常引用调用的方法必须是常方法,否则会编译报错
以上面案例进行解释为什么需要这个规则可以保证两者之间调用程序的正确性呢?A.a类库的调用者想实现打印A类的所有属性方法,并且A类中提供了print方法可以打印所有属性,但是一般调用者不会直接去调用类库,不然如果项目复杂会出现很多代码冗余,因此他们也会进行一层封装,正如上面自己封装的print函数打印全属性,但是因为站在调用者的角度我明白我只是需要打印类的所有属性的值,不需要去修改属性,那么按照C++开发规范那么就应该将传参修饰为const类型,告诉别人也让编译器检查我这个函数不会修改参数对象的属性值的。但是正如案例我是又调用类库的方法进行属性的打印的,如果类库打印的方法里面不小心修改了属性值那么最后调用者那边看到的现象是我传入了const类型保证不修改了,但是你最后调用执行完毕后的现象却是将参数进行了改变,那外面就会全乱套出错的。我传入const我就得保证我不会修改属性值,但是我如何保证我调用的类库也不会修改属性值呢?这就引入了这个语法规则,const常引用必须调用常方法来保证类库那边也不会修改属性值。

因此可以看出const常方法的引入其实也还是保证设计实现类库的人和调用类库的人的协作,并且这里还有保证这种调用程序的正确性。

案例验证;
在这里插入图片描述
2)mutable打洞破坏const常方法的完全绝对不改变任何一个属性
其实完全的绝对肯定会存在问题的,世上无绝对,这就来了,肯定存在需求我这个类存在很多很多属性,但是这个方法只会修改其中的一个,如果我不修饰为常方法,其实会影响效率的和类库之后的调用变复杂,因为修饰为const常方法则很明了我不会修改属性,没有修饰那么就要思考他是否会改变。因此最后的需要是我还想用const来修饰这个函数,但是我在这个方法中仍然要修改这个属性,只是这个,这时候就引入了mutable打洞原则。

其实这个与之前的访问控制权限的那种“全关闭,再按需求打开”的编程思路是一样的,
先定义为常方法,全部关闭我不修改任何一个属性,再按需将属性定义为mutable则表示在const常方法中可以修改mutable修饰的属性。

案例;以看书为例,以书为类模板,其中包括很多属性如当前页数,当前页的信息,书的总页数,书名等等,也有一些方法看书(表示看了一页则要打印所有当前这一页的信息,并且将当前页数+1,到下一页)这个案例的翻页则需要修改页数这个属性,但是打印其他信息我又不需要修改,则可以把他定义为常方法,页数修饰为mutable。

在这里插入图片描述
3)class的前置声明
因为编译器在编译的时候是按单个文件按行顺序从头开始编译的,那么就会存在我在一个类中要使用到另外一个类的类型,不是全部细节,而另外那个类又还没有编译到则编译的时候就会报错提示不识别这个类型,这个时候就需要class前置声明,提前告诉编译器我这个符号是一个类,你当成类来处理就行了,但是细节你还是不知道的。因此class类前置只能用于在该类细节编译前使用与类的引用指针的创建,不能用于定义对象。

案例:嵌套调用的时候验证以上结论
在这里插入图片描述

三;C++继承和多态相关的语言特性

继承

1、继承的特性,本质
1)继承了父类的一切东西
2)继承是让子类一瞬间拥有父类所有属性方法的语法糖
3)用处;继承的特性是天然存在的,并且大型项目中的各个上下层次关系都是由继承实现的,

2、继承中的访问权限管理
1)三个访问权限实际是四个级别
public
protect
private
比private还严格
2)这四个级别分别在什么时候出现
public;只有public继承的时候父类的public属性还是public

protect;在public继承中父类的protect依旧保持还是protect,protect继承中父类的public和protect都变成protect

private:在private继承中父类public和protect属性都变成private

比private还严格;在private继承中父类的priva属性到了子类中是比private还要严格的等级,因为在子类中新扩展的方法也不能访问到。父类的private成员,在三种继承下都会被变成在子类中是比private还可怜的这种成员。

结论进行验证;
在本类中,子类自己扩展的方法中,子类覆盖父类的方法中,外部利用对象进行访问,四个地方进行访问。
验证public直接看在外部是不是能用对象直接访问到,验证protect则需要再次protect继承出孙子类在孙子类的内部调用是否能够访问到。

继承中public只有public继承父类public属性这一种情况
在这里插入图片描述
验证private的情况;private继承其父类的public和protect方法在子类中降级为private属性。
在这里插入图片描述
验证public继承下父类的public、protect还是保持原样,只是private还是变成比private更严格的了,只能让父类自己访问,其实这种机制是应该的,父类自己的私有属性的确不应该让子类访问得到的。在这里插入图片描述
验证protect继承下父类的public和protect属性都变为protect属性,则在类内部和子类内部都能直接访问到
在这里插入图片描述

3)如何设计访问权限

需要被外界访问的成员直接设置为public
只能在当前类中访问的成员设置为private
只能在当前类和子类中访问的成员设置为protected。

3、继承体系下子类和父类的关系
1)子类父类是两个独立的类,继承只是快速构建类的一种方法而已,两者之间没有关系

2)为什么private继承中父类的private属性继承过来却无法访问,为什么要这样设计;
只是C++编译器选择的一种决策而已,可以实现不继承过来但是如果要单独进行处理不继承过来则要做很多特殊处理太麻烦了,不如全继承过来但是加强权限让其访问不到即可达到同样的效果

4、隐藏覆盖和类型兼容裁剪机制
父子类同名成员函数问题(隐藏覆盖机制)
在c语言中小范围隐藏覆盖大范围的定义,隐藏了就没有了,不可以选择不隐藏,而在C++中子类隐藏了父类的方法,但是可以选择不进行隐藏,只是默认调用子类隐藏父类,不进行隐藏则需要显示指明调用父类的方法。
在这里插入图片描述
类型兼容的裁剪机制
就简单类型而言long类型可以隐式直接转换为int类型,并且如果不超出int范围也是可以读写正常的,这就是类型兼容,读写解析内存二进制的机制一致则能够兼容,而int和float则不能兼容一样的道理,因为他们解析内存二进制的解析方法不同导致他们互不兼容。
在面对对象中,子类父类是兼容的,子类对象可以直接隐式转换为父类,这个转换直接是兼容裁剪掉子类多余的直接变成父类。
派生类是基类的超集,基类有的派生类都有,派生类有的基类不一定有,所以这2个类型间有关联,派生类对象可以cast后当作基类对象,而基类对象不能放大成派生类对象,否则就可能会出错
验证
在这里插入图片描述
可以在这里区分一下,隐藏覆盖,重载,重写
类型兼容对象裁剪赋值;子类对象可以隐式裁剪转为真正的父类对象
隐藏覆盖;在父子类中没有虚函数的时候,子类重写父类的方法,那么父类的方法就会被隐藏,默认调用自子类的方法。
重载;重载其实与类无关,是两个同名函数不同参数导致函数重载,根据传参调用相应的函数
重写;则是在父子类中多态虚函数相关的,子类重写父类虚函数方法,调用规则遵从多态机制

5、父类子类构造析构的关系
1)子类不继承父类的构造析构函数,因为子类父类是两个独立的类,那么对象也是独立的对象,而构造析构是创建销毁对象的回调函数,因此没有必要继承则设计的时候就没有继承。

2)为什么子类构造析构的时候会调用父类的构造析构;
就地原则,从父类继承的那部分资源就有父类的构造析构方法进行管理,隔离不要引入到子类中来,增加复杂度,而子类自己定义的资源就有自己本身的构造析构进行管理。各管个的。
以及其调用顺序,验证。

构造函数中有初始化列表,继承类也可以用参数列表的方法调用,使用方法
析构函数不用显示调用,因为他是唯一的,直接编译器会默认去自动调用
在这里插入图片描述
3)子类到底是怎么实现的
从父类继承的所有属性加自己新扩展的方法属性就构成了子类

6、继承的优势与不良继承
1)优势;代码复用及构建层次框架

2)不良继承是天然存在的,在程序中体现是基类中总会有一些额外的方法而在某些特定的子类中却无法满足。
解决方法;
弱化基类,让这些特定的方法由多个子类自己去实现,不用继承的方法
去掉这种继承,重新构建设计

7、组合和继承;都是代码复用的方式
优先组合再继承

8;多继承和二义性以及虚继承解决二义性问题
1)多继承语法

2)多继承如何引发二义性
C继承A,B,但是(A,B中有同名成员的存在,则一起都继承给C了,从而产生二义性)
在这里插入图片描述

解决方法有可以尽量去避免继承的类中没有相同元素,但是这个很难的,
还可以调用的时候明确指定调用的是哪个类的属性,把类作用域写上。如c.A::public_i。
还可以自己去重写实现一下,再封装一层,实现的时候看觉得调用哪个父类写明白即可。
这些都可以解决问题,但是都未完全从根本上解决。因此C++引入虚继承

3)C++专门引入虚继承virtual的语言特性来解决二义性问题
实现原理;类似于头文件定义的那个宏防止重复定义的那个,这里虚继承只是在继承的时候对成员多了一个条件判断保证子类中成员的唯一性。
注意虚继承的原理是;虚基类表指针vbptr和虚基类表virtual table,他是特意用来解决二义性问题,与虚函数多态是没有一点关系的。
自己准备使用显示内存布局的工具,vs和gdb都失败了,vs安装使用命令行工具后提示c1命令非法命令,gdb安装的时候安装-m32总是提示一系列包未安装,尝试几次无果,感觉跟上次安装交叉编译工具链一样的32.64不兼容问题。

转载用内存结构解释虚继承

多态和虚函数

1)多态的引入
为了解决什么问题;根据对象是什么调用什么对象的方法,跟对象走,而不用再看指针引用 的类型来调用方法
对比隐藏和加入virtual虚函数引入的多态,案例实践
在这里插入图片描述

2)多态的特征及关键实现
宏观上;多态就是要实现一套逻辑多种具体适配的执行结果。猫就应该是猫的叫声,狗就应该是狗的叫声。
微观上;多态就是要一套代码在运行时根据实际对象的不同来动态绑定/跳转执行相匹配的具体函数
实现的关键是将成员方法修饰为virtual,则编译的时候会变成运行时动态确定调用类型

3)对比覆盖 重写 重载

4)多态是一种编程思想

四;C++其他一些辅助的语言特性

1、纯虚函数
1)语法;基类中只有原型没有实体的一种虚函数
virtual 函数原型=0;
2)与虚函数对比;没有区别,纯虚函数是特殊的虚函数,没有实现体的虚函数
3)为什么要引入它这个语言特性;因为有这样的语义需求,需要子类必须实现这样的方法才能实例化对象。
4)不能实例化对象,
5)抽象类;带纯虚函数的类,保证子类必须具有实现的行为,否则就不能实例化继续当父类。
6)接口;只包含纯虚函数的类。

2、虚析构函数,
用于多态对象在new申请堆内存的时候只会去调用父类的析构,
虚析构函数就保证了无论在什么情况下都保证析构函数调用正确,但是也会消耗效率的,因此一般只有虚函数的时候才会申请需析构函数,
实现原理;也是运行时动态绑定,RTTI机制
在这里插入图片描述

3、using重新定义继承访问权限问题
这个在前面关键字博客中已经介绍了

五;运算符重载

1、重载
函数重载;就是在一定作用域内,多个相同名称但是不同参数列表的函数重载。
其实现原理是编译时编译器根据实际传参的情况来判断调用哪个函数,这个过程叫重载机制,其实函数重载就是很多个独立的函数,然后根据重载机制可以使用相同的名字根据参数不同准确调用对应的函数,重载机制是在编译期间作用的而不是运行时,编译生成可执行程序的时候就已经确定好了要使用哪个函数了。其实他的作用只是为了避免我们胡乱起名,方便编写类库覆盖所有可能操作,是一种语法糖。从而运算符重载也是一种语法糖,只是对运算符进行重载,他的作用只是让运算符代替了名称很怪的成员函数,让程序’'看起来更简洁,更优雅"。

运算符重载
其实运算符重载的引入的需求与构造函数,拷贝构造函数的原因一样,都是程序由普通变量类型变成了复杂类型的变量了,普通变量的运算符直接加减乘除即可,原因是因为C语言就对这些变量进行了某种“预定义”的运算操作,(预定义,因为±*/已经是人为确定好了的要怎么运算,那么编译器就可以直接预定义将这些普通变量的这些运算操作直接写死预定义为机器码了),但是复杂对象就不能预定义了,必须设计实现类的本人针对自己本类实现合理的重载函数,这个步骤就是运算符重载。
在这里插入图片描述

则运算符重载的原型为;类名 operator运算符(const 类名& type)

而operator运算符就是编译器最后要去匹配真正执行操作的函数名,这个函数名是C++编译器已经预定好了的。

2、运算符重载的本质
表面上,运算符重载是对C++源生运算符的意义,在某个class中做重定义
本质上,运算符被映射到执行相应的成员函数,所以运算符重载其实是重定义对象的运算符所对应的函数
因此可以说我们其实不需要运算符也能完成功能,直接调用成员函数实现运算操作即可,只是运算符让我们看起来更舒服简洁,因此也就说运算符重载其实是个语法糖而已,只是提供方便使用,让代码看起来简单明了,其实是将复杂的实现隐藏在类的内部,类的实现者来实现,而在类的外部,类的调用者可以轻松简单调用。

3、理解运算符各种细节
1)从c=a+b到成员函数中谁是this,谁是传参other,谁是返回对象
a + b; -> a.operator+(b),a对应this,b对应函数参数other,a+b的表达式的值对应函数返回值,返回值一般
c = a; -> c.operator=(a); c对应this,a对应other,c=a整个表达式的值(其实就是c)对应函数返回值,一般是否需要返回值看是否需要进行连续处理
a += b; -> a.operator+=(b); a对应this,b对应other,a+=b的整体表达式对应返回值
运算符重载总的规则:运算符左边的是this,右边的是other,运算符加操作数的整个表达式的返回值就是返回值

2)特殊的运算符重载--------赋值运算符重载
默认提供
=运算符有点特殊,编译器会提供一个默认的=运算符的重载,所以不提供时也能用,默认类似浅拷贝那种,默认提供一个拷贝全部属性的赋值运算符重载函数。如果自己显式写了=运算符的重载函数,则会覆盖编译器自动提供的那一个。

赋值运算符重载与拷贝构造函数
初始化时的赋值(一般就叫初始化),和非初始化时的赋值(一般就叫赋值)
初始化调用拷贝构造函数,赋值的时候才是调用赋值运算符重载的
实践验证;
在这里插入图片描述
注意;赋值拷贝构造函数重载是不改变传参的,因此如果形参定义不是const则会报错的

避免赋值运算符中的自赋值
自赋值就是Person a; a = a;
自赋值如果不处理,轻则浪费效率,重则导致内存丢失(该深拷贝时做了浅拷贝,在2.4.8中详解)
避免自赋值很简单,只需要在赋值运算符重载函数所有操作前加上一个判断 if (this != &other)即可

赋值运算符重载函数返回引用,少了一次调用对象赋值
返回引用和对象的区别;返回引用可以避免一次返回值值传递的对象复制,这需要消耗资源的。
在这里插入图片描述
赋值运算符中涉及动态内存时的深浅拷贝问题
这个其实与深浅拷贝差不多,都是

++和–运算符的前置后置如何实现
最底层规则分析
a++; a是this,other是空的,a是返回值,附带操作是a+1,
++a; a是this的,a是other,a+1是返回值,附带操作是a+1,
可以看出其实他们只有返回值不一致,那么也就是说其函数名是一致的,我们可以尝试去写重载函数,
在这里插入图片描述

实现运算符重载的两种方法
使用其他类的成员函数;
或者使用外部独立函数;
两个方法都需要声明为其类的友元函数否则无法访问到类的私有成员
在这里插入图片描述

六;静态类

1、static关键字的三种用法
修饰局部变量;只是改变了存储域从而改变了生命周期使得该局部变量可以使用到程序结束,注意只会初始化一次
修饰全局变量,限制外部链接属性,限定到文件内部使用,无法链接到外部去。
第三种就是C++引入的修饰类的成员属性和方法,
注意这三种方法之间没有一点关系的,只是不愿意引入新的关键字从而都使用static关键字的。

2、静态方法,静态类
用static修饰成员变量,即为静态成员变量;用static修饰成员方法,即为静态成员方法。静态成员属于class本身,而不属于对象。

验证静态成员的一些属性;
静态成员变量在类的多个对象中均可访问,且是同一个实体,被多个对象“共享”。
静态成员变量和方法可以用对象调用,也可以根本不产生对象而用类本身来调用。
静态成员函数在类外实现时只需在类内声明时加static,类外实体无须加static关键字,否则是错误的,因为会产生歧义,C++编译器不知道这是静态方法还是静态函数。
静态成员仍然遵循访问控制权限private、public、protect的

在这里插入图片描述

#include <iostream>

using namespace std;

class A
{
    
    
public:
	static int A_public;
	static void print();
	A(){
    
    };
	A(int a);
	~A(){
    
    };
};
//静态成员不能初始化成员列表初始化;
//‘int A::A_public’ is a static data member; it can only be initialized at its definition
A::A(int a):A_public(a)
//A::A(int a)
{
    
    
	//静态成员不能在构造函数中初始化
	//A_public = 0;
	cout << "A::A()" << endl;
}
//只需在类内部声明时添加,实现的时候不要加static否则有歧义
//static void A::print()
void A::print()
{
    
    
	cout << "A::print() ;" <<  A_public << endl;
};
//static int A::A_public = 1;//同样也有歧义
//如果静态成员没有在外部初始化则链接时报错
int A::A_public = 1;
int main()
{
    
    
	A a1;
	A a2;
	cout << "A::A_public = " << A::A_public << endl;//类名直接访问静态成员
	A::A_public++;
	cout << "a1.A_public = " << a1.A_public << endl;//通过对象直接访问静态成员
	a1.A_public++;
	cout << "a2.A_public = " << a2.A_public << endl;
	//根据最后输出结果;得出静态成员是类和该类所有对象共享的存在,都可以进行修改读写相同的一份
	return 0;
}

从本质理解一些规则
为什么静态成员不能在构造函数,初始化成员列表初始化
从之前了解可以知道构造函数是用于初始化对象使用的,如果静态成员能够在构造函数中使用,那么岂不是每创建一个对象初始化的时候都会去修改静态成员(因为静态成员共享),这个不符合实际的,因此干脆让编译器做保护静态成员不能在构造函数,初始化成员列表初始化

为什么静态成员需要在外部进行初始化
否则报错;‘int A::A_public’ is a static data member; it can only be initialized at its definition
理解这一点需要从静态成员的本质出发;
静态成员的本质;限定作用域为类作用域的全局变量,他就是通过全局变量来实现的只是将其划分绑定到具体的类中。
因为在类声明定义的时候并不占内存的,但是静态成员又是不跟对象跑的,因此在使用前必须先初始化申请好内存位置,才能供类和该类所有对象一起方法,否则链接不到变量的。

解析静态成员的本质;限定作用域为类作用域的全局变量,他就是通过全局变量来实现的只是将其划分绑定到具体的类中
class定义时只是定义类型,并不定义变量和对象,静态成员变量真正定义是在外部,类似于全局变量。
静态成员变量的生存周期是从编译时开始分配内存,程序结束时释放,与全局变量属性一致
静态成员变量在对象中不占用存储空间

3、静态成员的用途
静态数据成员的用途之一是统计有多少个对象实际存在,比如声明一个学生类,其中一个成员为学生总数,则这个变量就应当声明为静态变量,应该根据实际需求来设置成员变量。而普通变量是无法做到的,因为普通变量都是跟对象走的,那么每个对象都有独有的一份这样的普通变量,则该变量都不知道还有其他对象的存在,因为他只存在于自己这个对象中。而静态成员则可以很好的做到这一点。
静态方法就是与该类相关的,是类的一种行为,而不是与该类的实例对象相关。
在这里插入图片描述
静态成员与面向对象
(1)静态成员仍然在class内,仍可通过对象调用,因此表面上遵守面向对象规则。
(2)静态成员一定程度上破坏了面向对象,因为没有对象直接用class名也可以调用静态成员。
(3)静态成员可被看做是类外部的全局变量和全局函数被封装到了类的内部
(4)一个类的静态成员和非静态成员是完全不同的,两者唯一的关联可能就是隶属于同一个class的作用域内。

3、静态类
首先说明C++不原生支持静态类,但是自己可以去实现
什么是静态类
(1)class声明时使用static,整个类是个静态类
(2)静态类内部全是静态成员,没有非静态成员
(3)静态类不能被实例化
(4)静态类是密封(sealed)的。
(5)静态类不包括构造函数
(6)静态类不能指定任何接口实现,不能有任何实例成员
(7)静态类的成员不能有protected或protected internal访问保护修饰符。
(8)静态类不能包含构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状态。
静态类的优势
(1)编译器能够执行检查以确保不致偶然地添加实例成员。编译器将保证不会创建此类的实例。
(2)静态类是密封的,因此不可被继承。
C++不支持静态类
(1)Java/C#等高级语言支持静态类,而C++并不支持。
(2)C++中创建静态类与创建仅包含静态成员和私有构造函数的类大致一样。私有构造函数阻止类被实例化。

七;友元函数和友元类

1、友元函数的引入
什么需求需要引入友元函数;需要有外部函数要访问该类的私有属性的时候要引入友元函数来解决这个问题,引入之后相同与把这个外部函数在访问权限上等同于该类的成员函数,注意只是访问控制权限上,实际上还是一个外部函数。
友元函数的意义也是“打洞”,在类的封装的访问控制权限上打洞,打开一个洞让这个函数可以访问到所有的属性,突破类的封装特性去访问。因此则是对面向对象的破坏,不能滥用。

**2、使用方法;**在类内部声明一下,并前面加上friend,注意这个声明位置随便在哪里,只有在内部声明了则可以使用友元函数特性。

3、友元函数的两种实现
一种为外部函数,一种为其他类的成员函数。
这个在关键字那篇博客中讲解了。注意第二种情况的时候需要类的前置声明的情况

4、友元类
将类A声明为B中的friend class后,则A中所有成员函数都成为类B的友元函数了。
友元类其实就是批量制造友元函数的一种方法,将该类中所有方法都统一全部声明为那个类的友元函数,相当于一次对类的封装打了很多个洞,极大的破坏了面向对象,因此除非确实有这个必要,一般不要使用,尽量按需声明为友元函数,尽量维护面向对象,让代码更加安全健壮。

5、为什么会有友元函数
使用友元函数的优缺点
(1)缺点:破坏了封装机制,尽量不使用友元函数,不得已才使用友元函数
(2)优点:在实现类之间数据共享时,减少系统开销,提高效率。

使用友元函数的两种情况
(1)运算符重载的某些场合需要使用友元
这个在运算符的时候讲解了,在关键字那篇也使用到了。
并非所有运算符重载都可用友元函数,有四个运算符 =, ->, [], ()就不可以,例如=,因为C++编译器已经默认在自定义的class提供了默认的赋值运算符重载函数,如果你再在外面定义该运算符的重载函数,则调用的时候会产生歧义。
详解可参考:https://www.jb51.net/article/40143.htm
(4)总结:C++的语法特性确实大而细,正确的方法是去理解而不是死记硬背。

2)两个类要共享数据的时候
两个类如何共享数据;通过友员函数来打洞,从而可以访问操作那个类的所有数据,这就是达到数据共享。

友元函数和类的成员函数的区别
(1)成员函数有this指针,而友元函数没有this指针。为什么?因为友元只是朋友,并不是类内“自家人”
(2)友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友。
(3)友元关系不具有传递性。类B是类A的友元,类C是B的友元,类C不一定是类A的友元,要看类中是否有相应的声明

共有友元函数
(1)1个函数同时成为2个类的友元函数
(2)共有友元函数可以是外部函数,也可以是某个(第3个)类的成员函数
(3)共有友元函数内可同时访问2个类的受保护成员,间接将2个完全无关的类的数据打通了

七;一些剩余特性

嵌套类和局部类
1、嵌套类
(1)在一个类(叫外围类)的内部定义一个类(叫内部类)。代码演示
(2)嵌套类技术也是一种类的组合技术,和前面讲的继承、组合有类似。
(3)嵌套类主要是限定了内部类的作用域
(4)嵌套类的内部类和外围类各自有各自的访问权限限定符,且遵守传统权限规则
(5)嵌套类中的成员函数可以在它的类体外定义,但是要附加类名的作用域限定说明
(6)嵌套类的内部类中声明的友元,并不是外围类的友元
(7)定义嵌套类的目的在于隐藏类名,减少全局标识符,限制用户使用该类建立对象。以提高类的抽象能力,强调两个类(外围类和嵌套类)之间的主从关系。
2、局部类
(1)定义在函数内部的类,叫做局部类,只在定义他的作用域内可见,也是一种类型隐藏技术
(2)局部类除作用域外其他和正常类一样
(3)局部类一般不需要访问权限限定,因为本身作用域就很小了
(4)局部类内还可以再做嵌套类,如果有需要的话
(5)C++允许在函数内定义类,但是不允许在函数内定义函数,所以没有局部函数一说
3、总结
(1)不管是嵌套类还是局部类,都是为了隐藏类型,将没必要给外部看的类型隐藏在实现内部
(2)没必要纠结嵌套类和局部类的各种访问细节,真的需要用时写代码验证让编译器告诉你即可
(3)不要求会写这些,不写框架是用不到的,只需要知道,见了能认识即可。
(4)模板中会用到嵌套类,讲到模板时再说

数值与对象互转
1、数值与对象概念
(1)数值是简单类型,如int,float,double等,是C++从C继承而来的
(2)数值类型是源生类型,数值类型定义的是变量,非面向对象概念
(3)纯正的面向对象中是没有数值类型和变量的,会提供类库来替代数值类型,用数值对象来替代变量
2、C++中数值与对象互转
(1)数值转对象,实际是调用形参类型相匹配的构造函数来实现。
在这里插入图片描述

(2)对象转数值,不能默认转,必须调用对象的相应转换函数来实现。
3、对象数组
(1)就是一次定义多个对象
(2)对象数组的访问和普通变量数组没区别
(3)要注意如果是用new来分配的对象数组,则销毁时要用delete[] xx;

八;总结

主要进行总结归纳如
C++中哪些语言特性涉及"打洞";
C++中哪些语言特性是语法糖,只是编译器为辅助开发增加的语法;
C++中哪些语言特性是保证类设计实现者和类库调用者的协作能力的;
大致总结一些这些C++特性引入的需求;

猜你喜欢

转载自blog.csdn.net/zw1996/article/details/109631904
今日推荐