IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++学习笔记(第一、二阶段汇总) -> 正文阅读

[C++知识库]C++学习笔记(第一、二阶段汇总)

文章目录

cmake

cmake,按照它的语法描述,它可以自动生成makefile文件

命名空间

C++里面有一个命名空间的概念,就是说它那个namespace,他会把一个就是你的定义的一个变量,它是放到一个命名空间里面,也就是放到namespace里面,然后通过这样子去进行一个管理。这个是跟C语言不同的一个点,就是这个C++他有命名空间的概念,但是C语言没有这个命名空间,主要是用来干嘛的呢?它本质上设计来就是用来解决全局变量跟函数名重名的问题的,但是其实这个问题的话,C语言的本身也可以解决,但是解决的不够优雅,C语言,它解决的方法方法是他把一些函数名,全局变量啊,他在前面加一个前缀。

就比如说他在他定义的一个全局变量的前面加一个前缀,比如说他是一个串口的,那么他就加一个UART,然后一个下划线,什么什么什么意思?就是说,他是通过这种前加前缀的方式去区分不同的变量,但是这个方法的话,在小规模的C代码里面是可以用的,但是如果代码一旦庞大起来,这个还是有点难度。而且主要是还是不够优雅,那么,C++他就通过命名空间解决了这个问题,就是你你的,你可以把你的变量放到一个命名空间里面,比如说它有不同的命名空间,那么这些不同的命名空间里面的变量,就算重名了也是没有问题的。

这个命名空间,本质上它归根结底,也是去改变了他的一个最底层的编译的时候的链接属性,比如说你把你在这个命名空间里面的变量,跟另外一个命名空间里面的变量,它的链接的时候,链接的时候是不一样的意思,就是说他本质上改变的还是他链接的那个底层的一个链接的区别,这个其实也跟C是没有太大区别的,只不过C++他多了一个。命名空间的概念之后,他就这样就比较处理的就比较,方便一点,对就是你有一个命名空间之后呢,就是你这个命名空间里面的东西跟另外一个命名空间东西是相同的,也是可以的,因为最后链接的时候,他们不是链接在一块,就不会出现重名。

其实函数名跟全局变量重名的这个问题是比较严重的一个问题,因为因为最终他不管是C还是C++,他们最终都是要编译链接的,它编译链接的话,就根据这个函数名或者全局变量去找到那个东西去链接起来,所以当你的函数名跟全局变量名重名的时候呢,它链接的时候就不知道怎么链接了,所以说就是函数名跟全局变量重名的问题是一个。比较麻烦的问题必须要去解决,C的话,它就是用一个加前缀的方式去解决,C++的话就更加优雅一点,它就用了一个命名空间去解决它。但是其实本质上面归根结底还是跟链接相关的,解决的根源就是在链接时候,它是不一样的。

C加了前缀,那么它链接的时候就发现它是两个不同的变量,是不是那么链接的时候就就会按照不同方式链接,然后你如果是C++,你把它的变量放到不同的命名空间,那么它的链接的方式也是不一样的,本质上归根结底还是在他的链接的方向上面做了一个改变,最终链接不同,这才解决这个问题。

匿名命名空间

C++的那个匿名命名空间跟那个C语言里面的全局变量的static的用法很相似,就是它那个匿名命名空间,本质上就是你用了之后,那么它这个文件里面的东西,你把它放到匿名命名空间里之后,那么其他文件就不能访问这个东西了,就是这么简单。

也就是说这个匿名命名空间它没有名字的,所以就是你如果在这个C++文件里面用了这个匿名命名空间,那么你在另外一个C++文件里面是不能访问这个匿名命名空间里面的一些东西的,这是跟C语言的全局变量的static,关键字是一样的,它是可以用来一个它有一个限制的功能的,他限制你其他文件去用这些东西。

所以匿名命名空间里面放的东西一般都是他这一个,就他目前所在的这个C++文件里面,它所要用到的一些,它所要用到一些只有他要用到的东西的,只有他这个里面才需要用到的一些变量啊,函数名啊,还有一些结构体啊,还有一些累啊,都放到这个匿名命名空间里面,意思就是说这个匿名命名空间里面放的东西,只用在这个文件里面用就行了,其他文件里面没有必要用,就是这样子,他用法就是这样用。

然后他跟C语言的那个全局变量的static还有点不同,就是C语言里面的全局变量的static,它只能是用来修饰函数,还有全局变量,但是呢,那个C++的匿名空间里面不仅可以放函数变量,还可以放一些结构体,还可以放一些类,就说他这些结构体这些类,这些类型,它放到匿名命名空间里面之后,也可以实现一个。框死的作用,限制的作用,不让其他文件可以用到,它就它比全局变量的static的一个不同,就是它扩展了一点,它还可以把那个结构体啊,然后那个类呀,放到命名,放到匿名命名空间里面去,然后作为一个限制。

从一个更高的一个角度上看,这个匿名命名空间,它体现了C++的一种优雅性,就是它不算是一个特例,它不会说是一个特别的一个情况,它是可以在逻辑上面是说的通的,因为你这个匿名命名空间,它本身就是没有,本身就是没有名字的,那么你既然没有名字了,从逻辑上来讲,那你在其他文件里面自然就找不到这个匿名命名空间了,是不是?所以说他并不是一个特例,所以它也是符合匿名,他也是符合正常的命名空间的一些逻辑的。

这种没有特例,很少特例的情况,就是说他一切都是有一个逻辑的,所以这就体现了C++的一种语言实现的优雅。

C与C++混合编程

C与C加加混合编程,首先有两点先要搞清楚,第一.c加加的那个编译器居家家里面,它定义了一个下划线,C plus plus,这个东西是一个C加加编译器里面定义的一个东西,这个东西会根据的C加加C加加的版本,然后不同而不同。比如说现在的版本是118版本的,或者是八九版本啊,或者是二零版本呢?那么这个c si plus,它的这个里面的这个符号的关,它的值就是不就是不一样的,第二点就是要理解C跟C加加的那个底层的最后的链接是怎么链接的,它其实都是这样子的。
就是不管如何,她最后都是会变成一个.o文件,就是它是先从点C和点C加加文件,通过那个预处理变成一个点AI文件,点文件再通过一个编译变成一个汇编文件,就点S文件,点es文件,再通过汇编就变成了点o文件,点文件就在通过链接就变成了可执行文件,然后C与C加加混合编程,他们的编译都是到了的不同,它都是相同的,就是到了最后,这个都是可以到那个点o那里的。
然后点o文件本质上就是一个静态库文件,可以理解为它就是点I文件,然后就是点o文件,这种就是一个库文件,就是类似一个库文件,如果想要把它变成静态库或者动态库,那就去定义一下,把它定义成点I静态库或者点,So动态库就行了。
然后C与C加加混合编程的难点就在于它这个C加加,它有一个函数名重在机制,就是说它的函数名是可以是相同的,然后它中通过预处理,通过那个汇编之后等等等等处理之后,它得到了那一个可执行文件嘛,它的函数名是可以重在的,但是C是不可以的。
就比如说同样都是定义了一个ad,然后嗯,然后里面传参传的是in te gen in te,然后如果用C的话,最后出来的那一个点o文件,把它嗯,反汇编得到那一个反汇编文件,那么它里面的那个关键字还是ad,但是如果用的是C加加,那么他通过得到一个点o。文件之后,把它通过反汇编得到的那一个文件反汇编文件,它里面就不是ad了,而是ad ii。也就是说,它在它的那一个C加加,它有一个函数名重载,它最后会对一个函数名进行一个重载,如果虽然的那个名函数名是爱,但是最后它通过这个机制,它就会变成ad ii。
所以说C跟C加加的混合编程,他是有一定的难度的,就是难度的本质原因就是在于这里。所以如果要解决这个问题,那就必须要加一个extensi,就是当用C用C的时候呢,就加一个extensi,在那个extensi的那个括号里面,那么他就按C的方式去编译,如果没有在括号里面,那就是按C加加的方式去编译。
所以别人一般写C的库文件的时候,都会在他那个定义的函数前面加一个F的si si plus,然后ix ten si,原因是什么?原因就是为了防止以后如果这个C库文件,万一后面要要跟C加加混合编程的时候,那么他的这个C的话,就可以通过ex ten si,然后去实现一个C跟C加加的一个混合编程。因为通过if define c下划线cplusplus这个这个cplusplus只有C加加的编译器才有,所以当出现了这个关键字,那么就说明要用excelc就要,这就要把这个里面的东西当成C的编译器编译方法去编译,就不用函数名,重在机制。
那个点I文件是预处理后得到的文件,然后点a文件才是那个静态链接库的文件。这两点有经常会搞混,就点I才是那个预处理的文,得到预处理之后得到的文件,点a才是那个静态链接库文件,然后点so是动态链接库文件。
C加加调用C的时候呢?其实很简单,只要的C的头文件里面都用了那个if define c plus plus,然后extend c,那么它C加加调用C就很容易,但是如果是C调用C加加的话,那就没那么容易了。所以必须要得到C加加的那个反馈编得出来的那个函数名,然后这样子才能够去调用C加加,就这样才能用C去调用调用C加加。

引用

C加加里面多了一个引用的概念,就是如果是要交换两个参数的字,之前的话在C语言里面是用一个指针嘛,传地址给他改改地址,但是C加加很奇怪的是,它竟然可以传参的时候,在那个传参那里写个N的符号,然后就不用传地址,不用传地址的情况下也可以改值。
那个引用其实就是关联,然后关联的话,就是可以理解为他是一个弱化版的指针,就是他弱化了指针的功能,但是呢,他却提高了安全性,它从何体现提高安全性呢,就是它每一个引用,也就是每一个关联,他只能绑定一次变量,它后面不能再换了,就比如说是in te n de a等于。B,那么这个时候呢,那那个B就跟那个a是绑定关联起来了,后面就不能再换了,也就是其实就是个七个别名,但是这个别名呢,它一次只能他只要定义了,那一直都只能是跟那个绑定了,不能换。
所以也就决定了在定义引用关联的时候,要在定义的同时就初始化了,不能够定义的时候没有初始化,第二部在初始化,这就是这是由于它一次只能它,这是由于它永远只能绑定跟一个变量进行绑定造成的。总之这个就是一个引用,C加加引用的关联,这是C加加里面一个嗯,一个独特特有的一个点。它是一个弱化版的指针,它的目的就是为了降低指针的,它虽然弱化了指针的功能,但是却提高了安全性,然后一般用这个引用关联的时候呢,它不是正常,不是说定义了之后用的就是不是正常的情况下,这样用的,这种情况一般都常见于在返回值中,或者是在函数传参的过程中用这个引用跟关联。
关于引用和那个cos的关系,如果直接这样子cost,然后N的a等于B,然后这个时候呢,它其实就是的,如果用它引用的那个别名去改的话,改不了她的值,但是如果用它本来的那个变量,是可以改它的值的,这样子一看,似乎觉得他那个没有什么用处,但其实这个的用处是。不是这么用的,它是放到那个函数传参里面去用的,就是说当这个变量,为了安全起见,不想改它里面的值,那么就可以用这个方式,然后到了那个函数传参的时候,引用的时候呢,加一个cost,那么它就不会改到里面的值了。
这个关联的引用虽然是指针的弱化版,但是它跟指针完全不一样,就这个引用的话,它指的还是它那个,指它的size of,还是那个数据类型的大小,但是指针的话,它的size of就是那个指针地址的大小,永远都是四个字节,但是这个引用的话呢,它就是那个变量来的,它跟指针完全不一样,它没有地址的概念,它所以它还是那个数据类型的值,就比如说是double类型的,那它就是八个字节。

共用体

关于西加加的一个共用体,它的这个共用体,它有一个匿名的,一个竖一个特性,它的这个特性的主要来由就是有一些时候呢,他们懒得给这个供体起各类型了,直接就给它起个变量名了,这样的话也就说他只用一次,不用多几次了,我干脆这个供体我就只用一次,所以我干脆就只定义这个,就叫这个变量,那就可以把这个类型给省略掉了,就我不会再教他另外一个名字了,他就是也不会再用这个类型去多定义,另外一个变量,那么我就直接把这类型省了,直接就给他一个变量,就这样子,用他的变量就行了。

inline内联函数

关于inline内联函数,一般他是放到头文件里面的,他因为他在预处理的时候,他是原地展开的,它就很像那个井号提发音的那个预处理那种方式,所以他应该是放到宏定义里面啊,所以他也是放到那个头文件里面。还有就是它放到头文件里面之后,他就不用声明了,你在为头文件是在最前面的,你在一个函数这样一写,所以他这最前面,所以他就没必要声明了,所以就可以省去声明的这个步骤,所以这两点就决定了应该放到头文件里面去。

其实在C语言的时候就已经说你是online,内联函数,其实它很大程度上就等于是一个宏定义来的,它就是原地展开的,有一些函数,它也是用宏定义的方式去实现的,但是为什么要有内联函数呢?就是内联函数,它比宏定义多了一个参数类型校验,它可以进行一个参数类型的一个校验,这样校验之后就可以使用,就可以嗯,有一个多的一个,这样一个参数类型校验,它比宏定义。

之前在C语言里面没有讲的这么细过,因为函数它是可以被多次定义的,就是说他可以多次定义,为什么呢?因为它它相当于一个红来的,他在预处理的时候,它就把它有一个函数给展开了,展开到函数里面去了,所以当它展开到函数里面去的时候呢,所以他这个函数本身就已经不存在了,他已经展开进去了,跟红一样。所以你这个东西,你这个内涵是可以重复定义了,因为你每定他就,他就展开了,就放到里面去了,他就这个函数就消失了,因为没有了,那肯定可以重复定义。

关于多次定义

注意,这里指的多次定义,重复定义,它的意思是说,你定义时候它的里面的参数啊,那那参数体校要是一样的,不是说你在在那里定义的是这样,现在那里定义就是那样,这叫这就不要重复多次定义了,这个样子就就是不行的。你只能说在不同地方可以重复的去定义它,定义这定义里面的参数类型是相同的,不能不一样。

但是一般的话不建议重复定义,因为你重复定义之后你改,那你如果真的要有有一天你要改它,这个内敛函数或者是宏的话,你要改很多地方,所以你每一个定义到的地方都要改,所以这很不方便,所以最好的方法就直接用红,不不不不,所以最好方法就直接放到头文件,改头文件就行了。

但是这个多次重复定义啊,他从原则上来讲,他并没有重复占用内存,就他在占内存方面并不会造成影响,因为它最后都是要原地展开的,不存在说你多定义了一个,就多占了一个内存,它不建议重复定义的原因并不是因为占内存,也而是因为他你在维护代码,然后就是在管理代码的时候不方便,你要改的话,要找到他所有的你定义过的地方去改,这样就很不方便,所以要放到头文件是这个原因。

注意这个,原地展开,这个那个,还说原地展开它,你只是对编译器的建议而已吧,就是你是建议编译器在原地展开,但是最终能不能原地展开还是要看编译器,它自己有一时候,有些时候它是并不能原地展开的,编译器还是会把它当成函数正常函数去用的。

class类初步了解

Si家家里面的class类,它其实本质上来讲是一个简易版的一个structure,就是他C加加,他喜欢做一些封装吗?那些东西就跟之前的引用一样,所以其实class这个类呢,也是从struck的过来的,所以就是他跟stride一个唯一的区别就是struck里面的只能放函数指针,它不能放函数进去,但是class它已经可以把函数也能放进去了。但是这个的话,其实本质上来讲,这class类,它本质上里面的函数,本质上还是通过struck那个函数指针去实现的,只不过C加加帮我们做了一层,做了一做,多做了一步,所以就变得比较简易了一点。实际上这class就是从struck的演变过来的,它本质上还是一个struck的类,它是一个简易版的一个structure。

所以总结一句话,就是说class这个类,它是由struck演变过来的,它本质上就是一个struck类,只不过C加加帮你做了一些简化,使得这个class类它里面可以直接放函数了,而不是只用放函数指针,不不是,而不是,只能像struck的那种类,一样是struck,那样子只能放函数指针了。

一个正规的类的写法,正常的类的写法就是你要把函数的声明写到类里面去,然后你把函数的定义写到外面去。

然后这个类配合上那个line的话,就说你如果要在类里面用line的话,你在函数这个类外面的函数的一个定义里面,要写online,在那个类里面那个函数的声明也要写inline,但是其实从后面的话,你可以在那个类里面的online把那online省略掉,但是函数定义那里的online是不能省略的。

这音量函数呢,它有个特点,就是你如果要是在一个C文件里面定义了的话,它就只能在那个C文件里面去使用了,它跟红是一样的,你所以你如果不把它放到头文件里面,你把它放到C文件里面的话,它就只能在它放到那个C文件里面去使用的。

inline函数在c++中唯一一个多出来的特性

依赖函数在C加加中的多维一多一个特性就是就是C加加里面啊,它的类里面是可以写函数的本体的,就是正常来讲的话,应该是只放一个函数声明就行了,然后,然后他C加加编译器肯定会在编译器阶段把它转化成指针啊,不转化成函数指针,但是你如果是在这个类里面呢,放一个函数的一个定义,有函数体的话,虽然很奇怪,但是其实编译器也是可以做的。就是编译器,它也会把你这个函数体定义的,这函数体,它也会去找到一个它的函数指针去执行它,这样也是可以做的。然后它跟in line的关联,就是,如果你一旦把这个函数体函数的定义放到了类里面的话,那么它就默认它是一个内联函数了,就是一个in line函数了,这个时候你就算不加inline关键字,它也是online函数,所以这时候online是可以省略掉的。

这个特性的目的

Si加加他为什么要这么设计,其实本质上也是为了提高它的简便性跟效率,因为它这个本质上来讲,它就是深度绑定的嘞,因为这个函数跟这个类就是深度绑定了的,你这种情况下,你何必不加inline,这何必不用依赖呢?所以他应该就是用依赖的,所以这就是省去了我们的一些麻烦,就是他已经是本质上本身就深度绑定了,那就是依赖吗?是吧,如果你没有这个规则的话。那么你就要把你的那个类,还有你的那个银赖函数,再写在外面的那个依赖函数都要放到那个头文件里面去,然后你这就挺麻烦的,你后面的话调调调起代码来是有一定的麻烦。所以就是从一个代码的开发的性开发方便性来说,他这么搞的话,是本身是深度绑定的,你这么一做,那就是非常的切合。

也就是说,你如果是把它的内联函数放到这个类的外面,其实你在用的时候呢,你这两个都是要一直放到一起的,要一起放到同个头文件里面的,不然的话那类找不到那函数嘛,是吧,但是你这么一想到,它既然一直都放一起,那还不如那C加加编译器就想还不如就把它干脆就缩进那个类里面去,这样写代码多方便,少写了多少,写了几行,所以本质上是这这样除法的设计的。

c++中的NULL

C和C++里面的NULL是不一样的,C里面的NULL,它本质上是一个(void *)0,比如说你定义了一个

int *p = NULL;

那么他这个NULL,他用宏展开之后就变成了(void *) 0,它在C语言里面是可以的,这个(void *)0它会隐式转换成int,到时候它会转换成int *的,但是C++里面呢,它不行,为什么他无法这样子。把这个(void *)隐式转换成int *,因为C++它的类型的检验比C还要强,因为C++它的复杂程度比C高了一点,它就更容易触发一些稀奇古怪的错误,所以它在这里就更加要注意,它不允许你有这这样一个程度的自由度,所以它为了一个保证安全,就必须得要把这个禁掉,就不允许这样子用。

C++中的NULL直接就是0

C++里面他直接NULL就是个零了,所以int *p等于0,但是这也很奇怪,它也没有强制类型转换了,按理说你C++如果是一个强类型的话,你这不是也不行,但就是可以,他就是你把那定义成零了,那就是他可以,他这种情况下,它就可以把它这个零隐式转换成int*,就是这样子,你如果单单纯把它定义成那等于零,它可以隐式转换。

函数重载时怎么解决

在C++里面,它是允许函数重载的,函数重载的意思就是说,他有可能有两个函数,它的函数名是一样的,但是它里面传的参数是不一样的。然后你在用的时候,它C++就会是,就是看你到时候用了这个函数名里面,你传的参是什么,然后去找它对应的函数。比如说一个传参是普通变量,另一个函数传的参数是指针变量,但他名字是相同的,那你在用的时候呢,你如果传的是一个指针,那么他就会找到那个传指针的那个函数名。如果你用的是一个传变量,那他就找到传变量那个东西,那个函数,函数名是一样的,这就叫做函数重载,它本质上还是刚刚还之前说了C跟C++混合,编程里面它有一个预处理,C++的预处理,它会把它的函数的那个传参,比如说它是int int,它就会变成ii,所以它两个函数,其实本质上在预处理里面已经变得不一样了。

所以说函数从在本质上还是预处理的时候,C++在预处理的时候,它把这函数名也变了。所以本质上讲,这函数你就算写的时候是一样的,但它在预处理的时候,它已经函数名是不一样的。

C++里面,他这个NULL这么定义,它有一个问题,就是在那个函数重载的时候,比如说你这函数重载,你的第一个是第一个传的参数是int类型,第二个传的参是那个int *类型,那你最后你再调用这个函数的时候,你给他传个NULL,那他到底是int类型还是int *类型呢,所以C++在这个时候呢,他为了避免引起歧意,他就在这里,就选择了报错,就不让你这么做。

C++中引入了nullptr

然后C加加为了避免这种歧义,他就发明了nullptr,你用这个的话他就不报错了,你用这个他就默认你这个用的是指针了,那你就是函数重载的时候呢,他就是用传的是指针的那一个函数。

NULL和nullptr的区别

这个nullptr的话,他跟NULL一个区别就是NULL,他是一个宏来的,但是nullptr是一个关键字来着,它就是发明了一个空指针给你用。

所以其实在C++里面的话,你定义一个指针,然后让它指向空的时候,那就不应该不应该是用NULL,而是用nullptr,比如说char *p等于nullptr,而不是做成NULL,因为他是在C++里面,NULL是一个宏,他直接就是个数字零,这是不对的,虽然他不报错,但是也是不太恰当了,因为他那是零,然后char *p的话,它是指针。虽然C++里面它是可以允许你这样编译通过的,但是其中严格上来说,你应该是用用C++里面它给你的关键字nullptr。nullptr就是C++里面为了解除歧义,然后专门发明了一个关键字。

nullptr的本质

这个nullptr的本质是一个类来的,就是你定义出来的一个类,它这个类里面呢,有一个在里面它会返回一个零,然后它重载呢,是把它变成了一个指针,所以说这个nullptr的本质就是你定义了一个类,然后这个类,它的类型是指针类型的。

const class nullptr_t
{
    public:
        template<class T> inline operator T*()const {return 0;}
        template<class C, class T> inline operator T C::*() const {return 0;}
    private:
        void operator&() const;
} nullptr={};

C++的一个编译时的版本设置

在C加加里面,你去进行编译的时候,你可以选择你要编译的版本,就是说你在后面加STD等于什么什么,比如说你要九九版本的STD99,或是STD等于一三呐,你用一三版本之类的,就是说他C加加可以按不同版本去编译的,按你需要的版本去编译,你就后面加STD等于什么就行了,编译的时候。

nullptr的缺点

其实这个nullptr它没有解决掉实质的问题,它说可以解决那个变量跟指针变量的一个重载的一个冲突问题,但是如果它的函数重载两个都是指针的,那这样的话,它nullptr也是无法解决的,就是说它两个指针nullptr也不知道你重载的是哪一个。

nullptr和void挺类似

其实这个nullptr他有点和void相似,因为他你看我们之前在C里面用的是void *,它可以是任意的一个数据类型,都可以,他到时候根据选择去做,然后这个nullptr的话,它其实也是跟void很相似,他都可以支持,就是你是哪一种类型都可以,到时候我都可以支持,你到时候是哪一个,nullptr就变成哪一个。

断言

断言这个东西就是他。他不是在编译时出错,它是运行时你程序编译是成功的。你运行的时候,只要你运行的过程中一旦触发了这个断言,那你程序那里去就停止了。就是这个意思。

断言有点像内核的Kernel panic

断言有点像那个C源里面,内核里面那个kernel panic那个kernel panic就是你一旦遇到了,就直接运行的时候,就就就在那里断掉,就是这样子就停止。

这个断言的使用场景其实也是跟Linux内核的kernel panic一样的,你用它,那就代表你这个东西是你无法接受的一个错误了,一定要在这里卡住了,终止掉了才用断言。比如说你在打开一个文件系统文件的时候,你你一般情况下用不上断言,就比如说你这个文件没有打开成功,你返回一个那,那么你就可以用衣服闹一幅他。等于闹,那么你就反,你就打印一个什么error或者是一个重新来一次,重新做一次打开,是吧,就是你会有一些其他系列的操作可以弥补,如果你在这里直接写了一个断言的话,那程序你就是,就是认为你是认为这个是很严重的事情啊,就直接就把这个程序在这里就终止掉了,所以断言一般是这个比较紧急情况下,你认为紧急才用的。

断言的使用场景

这个断言呢,一般用在什么场景呢?就是用来解,就是用在你文件的最开头,你这个代码最开头,你传参传进来之后,他传参是否是正确的,一旦传参都不正确,你就只用断言了,你就直接就是开头就断言掉,它就终止掉,一般都是在这里用,就在函数开头传参的时候用。

静态断言

这个C语言里面的断言他是在运行时报错,但是其实运行时报错是不太理想啊,最好的情况就是你在编译时就报错了,所以C加加里面它就引入了一个静态断言的这样一个东西,就是你这个静态断言就是一个家一个static下划线,再来一个assert,然后这个静态断言C加加里面静态断言的使用就是你用这个static,下划线assert,然后括号。里面写上你要判断的表达式,然后后面写上你要出报错的一个打印出来的东西,你写上这个之后呢,一旦你的表达式里面的那个不符合条件,那么他就会打印出它,在编译的时候就可以打印出错误了。他跟C语言就的断言不一样,它编译时就可以打印出错误了。

静态断言在模板中的用处

然后这个C加加里面的静态断言,我们在平常用的地方比较少,因为没有这个必要,我们直接,因为我们都一般都知道吗,比如说他,你他一般是用来做一些校验的吗?我们一般肉眼都可以看出来他到底对不对,是不是,所以一般用不到,所以他这个用的最多的C加加的静态断言用的最多的地方是在他模板那里,因为他C加加的模板里面,他的一些函数类型是比较模糊的,你不知,你很很难去判断他这个函数它的传的参数到底是什么类型,模式很模糊的,所以这个时候你就需要有一个静态断言去用它。

这里静态断言就是用来,就是一般是那些写模板的人,而不是用模板的人去做的。就是那些写模板的人,他本身自己是清楚的,他是怕你那些用模板的人,他可能没那么熟悉,所以他写模板的时候就提前预想到可能会有问题,就帮你在那里写,加了一个静态断言,然后你你到时候你用模板的那些人呢,那么你就可以哦,有个静态断言在那里,你就可以大概知道错哪里,否则你连静态断言都没有的话,你用模板,那些人一旦运行时候出错,你很难查找出问题所在的。

静态断言的缺点

但是这一个C加加里面的静态断言他有个不好的地方,就是它打印出来的报错,它其实是很抽象的,就是没有那么明确的,你可能最多就只能知道他那里有一个静态断言打印出来出错了,但是你可能并不知道他,你不能通过他的文字信息得到它到底是什么问题的,什么类型的错误,然后这个静态断言他是C加加11里面出现的,它的C加加11里面出现的一个问题,就是这个问题,就是他打印的那个信息,很难,通过这个信息去判断出它是什么类型的错误。然后到了C加加20,然后它又有多一个关键字叫concept concept concept concept,就是你可以理解为就是那个静态断言的一个进一步的一个版本,它的话打印出来的信息可能就比静态断言更好理解一点,就是这样子。

总的来说,这个嗯,静态断言,它主要就是用去后面的模板的一些他的模板里面的一些参数,它的是否是常规合理的,你因为你模板那里不不清晰的,所以你可能一些参数的一些东西不是太合理的,所以你要通过一个静态断言你开发模板的人,你要写模板的时候,给他一个静态断言进去,用模板的人才能够大概清楚至少有一个方向在那里,至少至少知道他这里是错的,但是什么清清不清楚,是什么类型错,他文字信息是很难够推断出来的,但是至少知道有个错。

内存对齐

alignof测的是什么

alignof测的是结构体中你定义的变量的最大的那个变量类型所占的字节的大小,这个是错误的,这个没有看到本质。其实本质这个alignof看的是你按多少个字节对齐。所以由这点你也可以推断出来,其实,你在结构体中写了double后,alignof测出来是8,那么其实它就是按8字节去对齐的,而不是按4字节对齐的,所以可以得出结论,这个不是死的,不一定一直都是4,只有结构体里面最大的那个是int的时候才是4,而如果结构体里面最大的那个是double,那么就是8个字节了,而事实上,这个不仅不固定,而且还是手动可调的,下面就讲了如何手动调。

调大与调小

这个有关C加加的内存对齐,嗯,它这个里面就是首先它有一个a line的这个东西就是他跟size of,一般这个课程里面是拿了a line跟size of一起去用,一起去对比这个aline的话,它就是用来查看你这个这个结构体的,对其的最小的一个单元,就是你设置好的结构提的一个对齐的最小单元。然后size of就它整个结构体的大小嘛,然后你一般是用aline去看的,如果你没有,然后这个结构体内内存的最最对齐的最小单元,你是可以调的,你往大了调,你就要你就可以用一个那个aline的那个a line a of什么的一个东西,然后你去调,然后给它可以给它设置的。

然后你往小了调,可以用一个parent,然后什么packed,然后往小了调,把它那个结,那个结构体的那个最小,对其单元往小了调,就是意思就是说,嗯,它有一个可以查询你结构体对齐的一个最小单元的一个东西,叫line的line of,然后你可以把你可以人为的把它结构体内存对齐,往大了调,往小了调往大了调,你就用一个那个东西往小了调,你用那个pad。

调大 __attribute__((aligned(n)))

调完之后,你在用alignof去测,就会发现得到是最大的那个是你设置的那个大的了,比如之前最大的是8,你手动设置成32了,那就变成32了。
就是你往大了调,就是用那个attribute,下划线是用了下划线,下划线,Attribute,然后括号,Aline的,然后里面你写你要写的那个,往大调调到多大,然后就你对齐的那个最小单元往大了调调多大,然后就是这样。

调小 #pragma pack()

这个往小了调,也会把alignof测的那个值给改变了
然后你往小了调,就是用那个一个井号,然后一个paragraph pragma peck,然后括号往然后里面填数字,一般是上面有一个井号调调,完数字之后下面再写一个井号,然后把那个东西置空,里面括号变空,这样的话,你下面的那个就不会变,不会变,不会也受到你的影响,只有上面中间夹住的那部分受到影响。

他这个往小了调,就是有个夹层,你你上面一个井号,下面井号,上面井号写的你要调大小,最小多调多少,你下面那个井号就给他一个空白,就是夹到这里,然后,这里面的东西就是调的,这里面几个题,就是你要调的,在这里面之外的其他的几个题就不受这个影响。

当调小成1时

当调小成1时,你会发现sizeof也变了,直接就是有多少个字节,就是多少个字节了,这个情况其实就是按1字节对齐了,所以内存不存在空出来的情况了。

这里我们试了之后往大调,要用那个才行,然后往小的调用那个才行。然后这个的话不一定是绝对的,可能是我们没有注意到某一些细节的语法,说不定改一改他们也可以,大的可以往小调,小的也可以往大调,但这里就不深究了。

再往小了调的话,是上面那个井号填数字下面那个井号填空就是夹层嘛,它上面有个井号,下面有个井号都写了一个pack parent的pack,然后上面的井号是填数字,你要设定的数字,然后下面的井号就是空,下面井号就不用设置的是这样子的,下面井号的数括号里面是空的,不用设置数字,就上面那个井号,你的那个里面的package的括号里面采用设置你要设置的大小。

内存对齐,还有一种往上调的方法,就是用那aline as aline as的话,它是写在那个struct定义的那个,那那句话里面的,你比如说你是struct什么什么什么是吧,然后加个变量名是吧,然后你那个aline as加个括号,写上你要定义的往大了调的那个对齐数,它是写到struck的后面,紧挨着后面写的,然后它这个的话也是只能往大了调。

C语言中强制类型转换的本质

补充记一下关于C语言里面的一个强制类型转换的一个经典题目,就是他一会儿是那个类型,然后相加到右转那个类型的时候,这个这个东西是怎么处理的。从最深层的原理上来讲,你要理解就是强制类型转换的本质,在我的理解是复制,就是他,如果你要把一个硬盘类型的强制类型转换成其他类型的,其实他应可类型,它那个变量本身是不会变的,他是把它复制的,另一复制了一份放到另外一个内存空间里面。然后用的是另外那个内存空间的里面东西,然后去给了那个强制类型转换的那个值,就是说,它是有一个复制的复制的一个过程的,所以它后面你一些题目如果是加减,啊加完之后再去强转也是的。

也是的,就是你要先是相加完,相加完后得到那个值,你发现你要强转了,那你就把你相加后得到那个值,它其实在内存里面是复制到另外一个内存单元,然后把那另外付到另外内存单元那个值再付给那个,你要强转那个职是这样子的。所以他你这个你要理解这个本质就是你要深层次去理解它,强制类型转化其实是一个复制了一个,他把他那个值复制了一份,到另外一个内存单元里面去了,然后用的是另外一个内存单元里面的东西,它本身它那个。这个值它是没有变,它类型是没有变的,它只是用的是不是它的那个值,而是它复制之后的那个,那个内存单元里面的那个值,其实也不是叫复制,就说重新开辟了一个内存单元,然后用就放了一个值,这个值就是你要强转那个值,也就是说它用的不是它本来的那个值,那个值是不会变的,还是那个类型的,反正总结你可以大致一些,就是复制了。

反正总结你可以大致理解,就是强制类型转换,它本次用的不是之前的值,而是他重新理解为你可以理解为复制到另外一个内存单元里面用的是那个纸,然后你无论是加加减减加加,最后,最后你要强转的时候都是最后他先把你加到几点,加得到那个值之后,复制到那个内存单元,那个内存单元地址,你再去拿去拿那个去用是这样子的,但是它这个跟船舱的形参跟行参传传参那个东西。还是有一点点小小区别的,也没有必要把他们这两个放到一起去理解,你就分开理解,但是你这个这个东西强制类型的话,真的是他把他那个值经过处理,然后把他得到处理之后,得到那个值放到另外一个内存单元里面的,用的是那个,另用的是另外那个内存单元里面的值,你可以最终核心你要理解这一点,后面的那些题目就比较容易理解了。

typeid

有一个东西叫太拍的,太拍的是用来查它,那个你的这个变量的类型的,就是比如说你的int类型的变量,它的这个里面用了type ID,他是个类类的type ID,你可以把那个变量放到里面去,然后点面去查他这个这个类里面的名字,那么,如果你是in的type ID给你返回来就是I,然后这里还有个unsigned跟赛的一个区别,比如说你如果是差类型的type ID给你返回的是C,然后你如果是an cy X类型的,他type拍的给你返回来是H。然后如果你是的child类型的,那么它type ID给你返回来是一个a,就是说,它其实这个东西深层次挖这个type ID是很有用的一个东西,它跟编译器有关的,它底层是跟编译器有关,就是我们平常编译时候,它编译器到底是怎么判断我们的类型的呢?它其实用的就是的ID,类似这样的东西。

就是他平常平常编译器判断我们的类型,校验帮我们进行校验,然后报错什么的,就是用了type ID type ID类,用了type ID类似的样东西,它把它这个变量它有一个名字,有用一个字符串,这字符串可能是一个字符,也有可能是两三个字符,通过这个去对比,这底层原理就是这样子,而这个type ID是个非常有用的东西,就是它的深层次的说明是这样子的。

静态类型与动态类型

就其实它这个那个编辑器啊,帮你做那个就是一个参数类型的一个,就是类型的一个校验的时候,它有分为一个静态的,一个静态类型,还有一个动态类型。静态类型,简单说,就是在编译的时候出现的动态类型,就是说在运行的时候出现的,就说他你如果在编译的时候就出现错误了,或者编译的时候就已经确定你是哪个类型了,那么这个静态类型,比如说你之前写的啊。然后这些char这些,然后你如果是在运行的时候,你才知道这个东西的类型,那就是动态类型,比如说你之前用了void void,它编译的时候,它是不知道它的类型的,就运行的时候它才知道它的类型,比如说运行的时候,你发现你给它用的就是int,那么这个Y代表的就是int,然后运行的时候,你如果用运行的时候,你发现你给的它是char,那么这个Y就代表char。

然后这个动态类型就是你运行的时候,他经常会出现崩溃,就就是你可能如果你在某个情况下,它的类型不匹配了,然后你在运行的时候,你你你这个是个动态类型的一个东西,你运行的时候他们不匹配,那就崩溃了,所以这个程序就这样子就没有了,见状性见状性就差了点,然后你就可以通过这台拍的,你在它运行的时候就是动态类型吗,你在它运行的时候。然后你经常的抽出来,然后拿他的那个参数啊,变量的类型校验一下,然后发现他是不是匹配,如果不匹配的话,就做下一步的操作,然后如果匹配的话,就好不匹配的话,就是就是程序,就做下一步操作就处理处理一下怎么样的,这样的话就提高了一个程序的健壮性,就你在时不时的抽出一点东西来,然后拿拿他的那个那个钛白的去匹配。

然后我刚刚说了这点还是有一点点出入,就是说他可能不是进行一个匹配的问题,是它运行的时候,它的变量的类型,如果是这样,他可能会往内部去执行,如果他这个变量类型是那样的话,他可能会往那个方向去执行,那么这个时候呢,你你就要需要用一个东西来去查它的这个变量类型是什么,查出来之后,他如果是那样就那样走,如果是这样就这样走,这样的话,就是就不会说你本来是这个类型的,但是你却走了那个方向。这样的话,程序就会运行时就会出现一个问题,就会崩溃,就就可以解决这个问题。你可以通过一个方式,就是白ID方式得到它的这个类型的一个特点,然后去判断,从而判断你该怎么去走,这个就是一个提高程序健壮性的一个方法。

typeid的真正大用是在动态类型中

所以总结来讲,这个type ID,它最真正的用法是用在这个运行时的时候,这个动态类型的一个检查上面的。

这太拍的呢,既可以返回动态类型,也可以返回静态类型,然后你如果是你,如果他这类型的静态跟动态的时候都是一样的,那它就返回静态类型,如果她动态类型和静态类型不一样,那它就返回动态类型,然后他这个太白应该真正显示用处的地方,就是在引入了后面的东西才有用呢,这里我们就大概了解一下就行,就是大概了解一个这样概念就行,后面的话等他真正派上大用的时候再去看。

就是说这个type ID真正派上大用场了,是在动态类型里面派上了大用场。然后,但是我们现在目前还没有接触到动态类型,大部分都是在静态类型里面的,所以目前还没有体会到它的用处,等到后面接触到一些动态、动态的时候,它就会有大用。

4种cast转换

static_cast的强制类型转换

跟C里面的强制类型转换一样
只不过写的时候变成了

int *p2 = static_cast<int *>(p1);

这个static cast总结来讲用的并不多,但是你要认得他就是C加加里面一个强制类型转换,但是他这个强制类型转换是只能转安全的,就是比如说你是in te,你就只能把它转成in te就是这样子,如果你本身是差,你要用这个强转转不了的,在C加加,它比那个CC的一个类型校验更严格,就是你如果是在C加加里面,你强制类型转换是不行的,你用一个static cast把一个int转化成差,他是不给你的。会报错,甚至你在C加加里面,你用C的那种强制类型转换的方式,直接来个括号,然后里面写上类型去强转,它也会报错,就是C加加它比C要严格一点,它对你束缚多一点,它C的话呢,是完全是放任你自由的,这是一个C加加强制类型转换的特点,它不给你强转。

刚刚说错了,它这个C语言里面那个强转直接加括号,然后来一个超星或者是什么强转是完全可以转过去的,C加加里面也是允许的,它唯一不允许的是你用static cast这种方式强转,这种方式强转是不行的,但是C里面那种强转在C加加里面是可以编译,是可以通过的。

总的来说又是一点,你写成static cast就不能强转,你写成C的那种,直接来个括号就可以强转在C加加里面是这样子的。

reinterpret_cast的强制类型转换

这种类型的强制类型转换就是可以强制类型转换的

在C中这样写

unsigned int *p = (unsigned int *)(0x530000e0);

在C++中使用reinterpret_cast写

unsigned int *p = reinterpret<unsigned int *>(0x530000e0);

这个inter interpret cast就是让编译器完全放弃你的这个类型检查,就是告诉编译器要放心,我这边是可以的,你完全放弃类型检查就行了,我这边是可以把关的,就是这个意思。

在C加加里面关于cos是这样子,你如果你定义这个变量是cos的变量,那么你如果要用个指针指向它,那么你的这个指针必须也是定义成cos的才能够用的。

如果你定义的那个指针没有带cos的,你去指向那个cos的变量的话,它是无法成功的,因为你一个是带cos的,一个是不带cos的,这是不能指向成功的。

在这里回顾一下cos,其实cos的就是一个警告牌而已,就他并没有在物理上面使它这个数变成可读可写,使它这个变量变成不可读,不可写,它只是给你个警告牌,就说它本质上还是可读可写的,是这样子的,它只是给警告一下,在编译阶段说你既然定义了cos的,那么就警告你一下,这个不能用,但是你这个实际上是可以用,可以改的。

所以本质上是编译器把你这个拦截掉了。

就是这个有一个问题,就是你会发现你用这个方法去改,他地址里面的值,然后你打印出来之后,你会发现a和新批是不一样的,你a还是之前的那个五,但是新批已经改成了14了,然后你会觉得很奇怪,那么我们就可以通过一个方法去看,就是我们去打印出那个a的地址,还有新批的地址。不是新P的地址,就是P的地址,就是你通过打印出那个a的地址和P的地址去看一下他们到底指向的是不是同一个地址,你因为你可能怀疑他有没有中间做一个中间变量啊,或者用一个做了一个影视转换,然后他们这两个地址是不一样的呢?是不是,所以你要去看一下他地址是不是一样的。

然后你最后发现a的地址跟批的地址是竟然是一样的,但是a的地址跟P的地址是一样的,但是a的值却没有变,他还是五并没有改,改成14,这个就是一个很奇怪的东西,它中间并没有做任何的一个中间变化,也没有说提前复制啊,一个值到另外一个地址去,他地址是一样的。所以这里就是其实是C加加的一个优化来着,是C加加的编译器做了一个优化,就是说它你可以理解为它很像一个红。就是你用cost去定义a等于五的时候,很像去用一个宏去定义了a等于五,那么这个时候它的编译的时候呢,就已经就说他这个a,他就已经跟五是已经绑定起来了,跟红一样,那么你在编译的时候呢,他就已经用这个五把a给替代掉了,就是类,类似于这样。

所以说你后面无论是如何怎么样,你只要碰到这个a的话,那它自动就会把它变成五,他跟五个绑定起来了,但是实际上a的值,它在运行的时候已经通过你的指针去改变了a的值其实已经是他的那地址面积已经是变成了14了,就是你那个批跟a是一个地址,你通过改变pi就就把那地址面值给改改成14了,那那个是运行时候的,你是无法左右他滨一时候的那个变的,他的那个空是a,你可以理解为它就是一个红了,她在编译的时候就已经把那个a用五给替代掉了,所以你打印出来a就是还是之前的五。

const_cast的强制类型转换

int main(void)
{
    const int a = 5;
    int *p = (int *)&a;
    *p = 14;
    cout << "a = " << a << endl;
    cout << "*p = " << *p << endl;
    
    return 0;
}

本来以为通过这种强制类型转换的方式,简单说就是把之前原先定义的const int给强制类型转换成int,然后再去改它的值,就可以把const类型的a的值给改掉了,但是最后发现虽然*p已经改成14了,但是a还是等于5,

上面那个是老式的C的写法,C++中的写法可以写成const_cast这样

int main(void)
{
    const int a = 5;
    int *p = const_cast<int *>&a;
    *p = 14;
    cout << "a = " << a << endl;
    cout << "*p = " << *p << endl;
    
    return 0;
}

这两种写法的效果是一样的,但是新式的写法更好,因为这样可以让人清楚的知道,你是有意这么做的,而不是不小心搞错了类型的。

//int strcmp(const char *p1, const char *p2);
char a[] = "dfdfdf";
char *pa = &a;
strcmp(const_cast<const char *>(pa), const_cast<const char *>(pb));

cost cast的用法,主要是用在drink compare那里,比如说你drink compare里面你传的参数,它要求是cost的类型,但是很多时候你要传进去的那个参数,它并不是一个cos的类型,那你就要用costcast把你要传进去的那个参数先全转成cost的类型,然后再去传。

dynamic_cast的强制类型转换

前面三个都是编译时起作用,这个是在运行时起作用,这个涉及到后面的东西,所以后面再细看

const关键字

C中const的用法

const int *p;
int const *p;

这个两个是一样的,都是说指针指向的那个值是常量,而指针可以变,这种用法比较常见

int * const p;

这种是指那个指针是一个常量,这个指针指向的值可以变,这个本质上其实就是C++的引用,所以这种用法在C++中就很少用到了,因为一般要用,就直接用C++中的引用了

const int * const p;
int const * const p;

这个就是综合了前面两种,指针和指针所指向的变量都不能变,都是常量。这种用法不常见

C++中const的用法

C++里面多了两个const的用法

用法一

第一个就是在引用前面加const,然后去传参

发现一个细节,就是引用的话,什么都不用管,就是你在定义的时候给变量加一个&就可以了,其他的就按照正常变量来写,注意,一定要把引用和指针的写法完全分隔开,他们的写法完全不一样,不要搞混了

之前在输入输出型参数中有讲过,传参的时候,加了const就是说明是输入型参数,只是传进去不用改的,在C语言中传参就是这样

int fun1(const int *a)
{
    if(*a > 100)
        return 0;
    else
        return -1;
}

而在C++中你可以理解为这里它更倾向于使用引用,而使用引用可以完成上面的这个功能,而且在完成上面那个功能的同时,还多了一个功能,就是把指针也给变成常量了,而不单单是指针所指向的变量,这样就更明确地告诉了编译器,我这个就是输入型参数

int fun2(const int &a)
{
    if(a > 100)
        return 0;
    else
        return -1;
}

仔细去品这两个中指针和引用的区别,还有要有一个意识,就是意识到传参里面写的东西其实就是一个定义,所以上面那个是指针的定义,a就是定义了一个指针,那么用的时候,取里面的值就要加*p,而下面那个是引用的定义,引用的定义是C++经过封装了的,所以直接对应的就是那个变量了,很方便的,所以a直接就是那个变量了。所以综上,指针和引用的用法是完完全全不一样的,要完完全全分隔开来看

用法二

第二个就是const成员函数

struct A
{
public:
    int i;
    int j;
    int func10(void) const;
};


int A::func10(void) const
{
    this->i = 5;
}

你在这个成员函数的后面加上const,就说明这个成员函数里面是不会修改成员变量的值的,比如这里,是不会在这个func10中去修改这个成员变量i和j的,向上面那样写就会报错,因为你在成员函数里改了成员变量的值

const的本质

本质上是编译器在帮你保护,实际上物理上并没有设置成只读,只是在编译器层面帮你做了一个检查而已,因此后面想要强行改也是可以的。

与const有关的另外几个关键字

mutable

mutable是用来突破const成员函数的限制的,就是你可以单点地突破,选择一两个特殊的成员变量,设置为可以改变的。

constexpr

这个本质上是一种优化,就是利用编译时的计算能力,增加运行时的效率。你加了这个关键字就意味着你允许编译器去优化,比如你要算一个乘,那就不用在运行时再算了,而是在编译时就帮你算好了

constexpr int multiply(int x, int y)
{
    return x * y;
}
const int val = multiply( 10 , 10 ); //在编译时就会计算了

constinit和consteval

这两个是C++20才出的,和constexpr差不多,都是为了提高效率用的

exception类

标准库统一定义的异常抛出,便于抛出异常和抓住异常的两个人的统一,他是一个基类(父类),很多派生类都用到了它作为父类

当你没有写throw列表时,就代表该函数可能会抛出任何一种形式的异常。不过i一般不建议这样用,这样用模棱两可,如果你知道要抛出异常,那就用throw列表写出要抛出异常的类型,如果你知道没有异常,那你就写noexcept,否则代码写得就很烂。其实这本质上是写代码的人和编译器和看代码的人的一种交流方式

noexcept关键字(C++11引入)

本质是对throw()这种方法的抛弃,用来替代throw(),当函数不会排除任何异常时,就不用用throw(),而是用noexcept了

noexcept(bool);

括号里面是一个表达式,如果是true,就表示不会排除异常,如果是false,就表示会抛出异常,所以当看到这样一个函数就放心大胆用,绝对不会抛出异常。

RTTI运行时确定对象类型初步理解

这个C加加里面有一个运行时确定对象类型,就是原因就是在于它其实面向对象的时候,他没有向面向过程的样子,你很轻松的就可以判断出对象类型,你再面向过程的时候,你可能你自己内心是很清楚,他是什么类型的,但是当你在面向对象的时候,他会有一些什么继承啊,蜂多肽啊,那些东西,那些东西之后,他就跟外国人起名字一样,就是比如说他的你,你就可像百年孤独里面。那个什么荷赛阿卡,迪奥什么,就他阿卡迪奥,阿卡迪奥,它一直都是这个名字的,所以就是它会有很多那种负类啊,子类啊,它名字基本都是那种一样的,总的来说就是你很难判断它的一个类型,就是说在面向对象的时候,你很难判断它的类型,然后它这个地方就有一个运行时,对象运行时确定对象类型的这样一个东西。

三种cast的深入理解:并非一刀切

七里面的话,他其实基本上是一刀切,就是说他要么是编译器帮你处理了,就是隐式类型转换,它编辑器,你交给编译器他也是处理给你,要么就是显,那就就是完全就是强制类型转换,一刀切就完全给你程序员去做决定了,我编译器不管,然后C加加里面的四种cars,其实是对于这一种方式的一种升级,他把那种你强制类型转换嗯,变得更细化了,一下细分了一下不同领域。比如说你如果他你本来这个是对的,编译器可以帮你稍微做一些检查的那种的话,那就是用的是那个,第一种,那个什么就是用的是第一种的那个cast,然后如果你用的是你,你不想让编译器帮你做强制类型做类型检查,然后只是你明确你这边可以知道的,那你就用第二种cast,然后你如果用。

然后你如果是用了一个是关于cos的cos的,你就用了那种细分领域吧。总的来说,就是它是一种对C的一个一刀切,完全一个强制类型转换,交给你程序员自己去管兵器,不管的一种优化,一种升级,让你可以让编译器尽可能的帮你去做一些东西,去帮你去查一些错误,你比如说他之前那个第二种cast,你那样写的话,那就证明你是知道的。你是明确的是知道你是这里是要进行强制类型转换的,而不是你不小心写错的,是吧,如果你是像西那一刀切,那很有可能是你自己写错了,这样的话就有点难搞,所以他就是有一个比较升级的,帮你更加的一个,帮助你更加的一个去查找错误。

自动推导

C加加里面的自动推导,就是说你在定义一个变量的时候,你没有明确告诉他的类型,然后编译器是通过他的一些周边,通过他的一些判断去推导,比如说他周边的一些东西,去判断推导得到数据类型,这就是C加加里面的自动类型推导。

auto关键字

Auto关键字,其实在C语言里面就有了,就是我们其实在C语言里面定义一个局部变量的时候,其实它本来它的局部变量的前面就应该加一个auto的,只不过我们省略了,就比如说我们定义了一个局部变量,比如说a,其实实际上就是auto into a,只不过我们是省略掉了auto而已。

在C加加里面auto,他做了一个废物利用来着,所以说C加加里面auto,它是一个全新的一个关键字。

Tu在C加加里面,你必须版本要不能低于C加加11才能够用。

他那个有一个auto自动推导吗?它有一个oppo跟一个declare type这两种方式,然后呢,他这两种方式都是可以进行自动推导的。那个oppo的话,它有一个问题就是你会觉得他为什么要用这样一个自动推导,其实就是他在那个后面,她用了她用的地方是在后面的,比如说他的一个模板饭行啊。这些东西在那个情况下,你其实你自己是很难知道你要定义的这个东西的类型的,就你当时是肯定是不能确定的,只能是用这个o头去进行一个自动推导,然后这个o头的话,它有一个注意事项,就是你o头后面的那个变量,它不能够是你,比如说你o头后面。

比如说你o tu后面,它有一个有一个in te,然后你就是不是就是,比如说你OO后面它有一个那个两个值,就是你你同一行里面定义了两个变量,然后这两个变量的话,你是不能够让他其中一个是硬和另外一个差的,就是你o tu里面那一行的,后面的两个变量也必须要保持相同才行,所以他也不也不是说你o tu肉,你前面跟后面可以不一样,比如说你o抽一个a等于一个五,然后B等于一个3.3,那这样是不行的,因为你a是等于应适应类型的,B是3.3是大事float类型的,这样就不行。

declare_type

int i = 5;
decltype(i) j;

auto与declare_type

然后这个declare type呢,这个declare type也是那个一自动类型推导的,然后这个declare type跟这auto有有区别就有总共有就是有有一些区别,第一个区别就是这个auto的话呢。

啊,有一个区别,就是你用auto的话呢,它是不区分cos的,就是说你如果这个变量,你之前你并定义的那个变量是一个就是是一个那个cost类型的话,他那个o是不会说识别出你是cost类型的,它只是识别那个那个inter,就比如你定义那个cost inter,然后它不会识别到之前的前面的cost,他只会识别到那inch,然后然后如果是declare type的话,它就可以识别到前面的那个cost。

比如说你定义一个cost inch I等于五,然后auto j等于I,这个时候呢,那个J它的一个变量,它auto只会帮你变成一个int类型,不会帮你变成cost int类型。

然后第二个区别就是因为你的这个oppo,他是需要你的幼稚去推断你的这个数据类型的,所以你这个oppo定义的时候,必须得要定义同时进行初始化,只有这样子的话,他才能够嗯,得到它的数据类型吗?就是说你oppo你定义的同时就要进行初始化了,但是这个的CAD的话呢,这的客太谱,你可以不用初始化,你定义就行了,为什么?因为他的课APP它的类型推导,它是通过你那个DK2TOP后面那括号里面的那个表达式推导得到的,不是根据你的幼稚推导得到的,所以你可以在定义的同时不用初始化都可以。

那个cost那里,你如果用的是oppo的话,你他是不会报错了,但是你如果cos的那里,你用的那个是declare type的话,你把它改了,它就会报错,但是你用auto的话,你把它改了,他竟然没有报错,这就说明他oppo里面,它是不识别你前面的cos的,那是declare type它是识别你前面的cos的,所以才会报错,因为要改他,所以他cos就不能给你改,报错OO的话,你改它,它不识别前面cos的,他认为他不,他不知道他是cos的,所以它就可以改,就没有报错。

这个auto跟declare tap,它在后面涉及到一些引用,还有一些指针的节引用,注意前面的引用是引用,后面只是指,后面的说的是指针的节引用,在这两个地方用的时候是会有点不太一样的,这个你知道一个有一个印象就行了,后面遇到再说。

还有一个区别就是这个auto,它其实本质上你要去运行啊,那I它才能够推导出来,但是那个declare type的话,它只需要解析一下就可以知道了,就可以推导出来了。

struct和class的理解

这个structure和这个class其实是这样理解的,就是其实这个structure本身也就是为了打包,然后,其实这个class其实就是这个struck的一个升级版,就是它都是用来打包的,但是你会发现到后面打包的东西越来越多的时候,你就最后就会升级到一个class,就说其实struck,根根class,其实它的功能都是一样的,就是用来打包的,只不过后面庞大了之后就用了class。就定义了class出来,然后你硬要说它有区别,就是class,它是可以定义函数,可以定义一个函数的,里面可以有方法,但是其实这个是不不是太太怎么说的,过去的,因为是drug,它本身结构体里面它也可以定义函数,也可以定义方法,它是用函数指针的方式,间接定义函数,也可以有方法在里面的。

所以其实struck跟class这两个东西其实是功能是是基本上是相同的,Class虽然是struck的一个升级版,但是其实class能做的事情,Structured也能做,Struck能做的事情class也能做,那为什么他会有这两个东西出来呢?其实这个就是,就是有一个比如class,他比strike更升级了一点,就是他这两个东西最后发展之后,出来之后,他强调的东西是不同的,就是你如果强调。C里面的一些面向过程的时候,那么你一般就会用struck的,然后当你强调用面向对象的时候,那么你一般都是用class就。但从本质上来讲,这两个东西没什么太大区别,就是它各自强调的东西不一样,你强调面向对象,那你就用class。

然后这两个有一个区别,就是它struck它是没有权限的,就是你在C里面,哪怕是在家里面,你用struck的话,他是没有访问权限的,就你谁都可以访问,里面东西什么里面的什么东西都可以访问,但是class它有一个权限,就是就就有一些东西,它是不能访问的了。然后他根据权限的层级可以分成private,然后protected跟PUBLIC3种权限在class里面,然后这个这个其实就像你一个一个楼房里面。的一个电,电房一样,这class它里面可有一些变量啊,他可能是不希望你去知道的,也是不希望你去篡改的,因为这是一个嗯,就是说他没有,就说他为了隐藏掉,他觉得这东西你没有必要知道,或者说你知道之后可能会篡改,不安全,就种种细节,他就设置了个权限。

比如说这个楼房,楼房里面有个店店房,靛房一般只有电工才有权限去用,其他人是没有必要知道,也没有权限,就没有必要去做,去知道你知道的可能会需要去篡改,篡改的可能就会导致整个楼房的电用电出现问题,所以他在class类里面,它就会有一个这样这样一个权限的东西,但是在structure里面,就是没有struck里面,你你任何东西都可以访问,它里面的什么东西都可以访问,但是class类的话,它里面有一些东西,它有一个权限,设置了一个权限,权限权限,访问权限,那个东西有一些人他是隐藏掉的细节,然后你他不希望你去篡改他的野,他设了一个访问权限的。

静态分配与动态分配

首先知道一下什么是静态分配跟动态分配,就是全局变量跟局部变量里面的这些,比如说他存在数据段,BSS段,还有那个站里面的这些数据,他就是静态分配的,然后再堆里面的,这这个就是叫做动态分配,就是你需要malloc跟free是动态分配,然后C加加里面有个对象的概念,其实对象就是变量的一个升级吧,就是他的。东西比变量更丰富一点,然后就是对象,就是你可以理解为就是一个丰富版的变量,它也是需要一个有一个内存在在里面用的,就是它也是需要占内存的,然后它的一个它是这个变量,它这个对对象呢,它是跟你在申请堆一样,它都是需要申请的释放的。

而全局变量和局部变量呢,他这种在静态分配里面的,它是自动申请自动释放的,但是你这个对象对象是一个,就是它需要一个申请跟手动申请跟手动释放,就跟那个堆申请堆内存一样,然后他这个对象的一个申请的一个东西的话,就是一个。然后他这个对象申请的话,就是就是有一个申请内存跟释放内存呢,就是用的是一个六根迪利特堆的话,用的是malloc跟free,这两个就是有点不一样,但是其实都是差不多的,只是对象里面他用的是六根迪利特。

那么这个对象跟变量的差别究竟在哪里呢?对象,他在变量变量的基础上多了一个构造函数跟C,够函数就是这样子,就是因为它多了,这个构造函数跟C够函数,所以他才需要手动申请内存跟释放内存,所以才要手才要,才有了一个六根迪利特出来,然后这个嗯,就是说他这个东西它只是提供一个机制而已。就是C加加里面他为什么会用的比较多,这些动态动态分配,就是为什么会用这对象这么多,其实这个是不是他的语言决定的,是他的业务决定的,C加加他的业务就决定了他更多情况下,要用了这种情况。

就是si,他处理的可能是一些比较小的一些业务,所以这个时候呢,你就就是这这个业务就决定了,你可能是走,可能用了那个静态分配多一.si加加,他走一般做的是一些比较大的业务,这些大的业务的话呢,可能比较比较稍微复杂一点,所以他就会涉及到一些,所以他用到了动态分配可能就会多一点,所以用到那个对象的一个六根delete可能就会多一点,这是他C加加的一个业务决定的,这是C跟C加加的业务决定的。

就是说C加加他这个业务决定了他可能要用到的对象的一个六跟迪delete会比较多是这样子来。

这个静态全局变量,它的这个在C加加里面就不建议用了,因为已经可以用命名空间的机制去替代了,但是这个静态局部变量的话,在C加加里面还是大量的使用的,继续沿用的,其实这个静态局部变量本质上就是改变了他的弟子玉跟生命周期嘛,就他本来在站里面,你改变了他弟子欲改变到这个代码段里面去了,不是代码段就是把它放到,把它改变到数据段,BSS段里面去吗。就改变他弟子域,还有生命周期也改变了,就他不是那个时候,站站的那里面就没了,而是他一直带着,然后就是说静态局全局变量,他已经可以用秘名功勋代替代替了,但是静态局部变量的话,在企业家里面还是经常用的,还是大量使用的。

这个static关键字在C加加中又多了一个用法,就是在class类中用,就是你如果用了一个class类里面,如果你是静态的话,那么它静态的它的是属于class的,如果它不是静态的,它就属于对象的,这个你现在目前是听不懂的,所以先记住静态的是属于class,非静态的是属于对象后面,等到学到面向对象之后再去思考,再去想。

这个class类里面放函数其实就是把函数声明放进去,就它跟class,它跟struck不一样,Strike的话你就不能放函数声明,你只能是把函数指针放进去啊,放函数指针class,它升级就升级在它比struck升级,在它直接可以把函数声明放到那个class里面去。

然后他甚至可以把函数体放进去,这就跟之前讲过的inline有一点关系的,可以翻回去之前看online那部分。

不过一般都是直接把函数声明放进去就可以了。

这个类呢,里面定义的函数,你在外面去不是这个类,它声明里面声明的函数,它在外面定义的时候呢,需要用一个两个冒号去指明是这个类里面的函数,因为你想想看,如果不是这样的话呢,是不是所有的类里面的函数都不能重名呢?是不是,所以他肯定就是有一个,你要定义函数的,肯定是先写这个类的名字,在冒号,冒号再写这个函数,所以你就可以理解为,其实这个类的命名空间是有点像的,这类里面的那个东西就是一个命名空间。

然后你去用这个类里面的东西的时候,就跟结构体,跟用结构体是一模一样的,就是什么什么点什么什么,你用里面函数也是你用类的名字,再点函数名就行了。

这个静态的类就是类里面的静态成员,还有静态方法,它是这么用的,就是首先你在那个类里面用一个static去定义那个类里面的变量和函数,当然你这个类里面的函数只是声明而已,然后你再变那个类里面的函数用了static之后。你是不是还要在累的外面去对这个函数进行定义?按道理说,在外面定义也应该加static,是不是?但是这里不用加,为什么?因为为了避免引起歧义,因为你在外面一旦加了static,那么他可能就是跟静态全局变量分不清了。所以C加加设计的时候就是在这里,在外面定义的时候没有用static。

而是指在类里面声明,这个函数的时候用了static,那么这个加了static跟不加static,这个静态成员跟静态方法,跟普通的成员,跟普通方法有什么区别呢?区别就是静态的是属于类地尔,而普通是属于对象的意思就是说,如果你的这个东西是静态的,那么你可以直接用这个类,然后去使用它。而如果你没有用静态,只是用了普通的,那么你首先要定义出一个对象,根据这个类定义出一个对象,再用那个对象加个点,然后去访问他。而如果你用的是静态的,你直接就可以用那个类的名字加一个冒号,冒号,然后再把这个成员变量成员方法,然后给他。

给他写上去,那么就可以直接访问,而不用创建一个对象。

这个类里面的静态成员跟静态方法,实际上用的很少,他在那个单例模式里面会用到,为什么用的很少,因为它这个已经破坏了这个面向对象的思想,在C加加里面一切面向对象,那么一切都是面向对象的的话呢,那你类里面不可能这样子定义,这等于就是说是破坏了这个思想,所以这种情况是一个特例,很少用,但是他在单例模式里面是用到的。

int *p = new int;
*p = 5;
delete p;
int *p = new int(3);

this关键字

void A::func1(void)
{
    //cout << "A::func1, i = " << i << endl;
    cout << "A::func1, i = " << this->i << endl;
}

Si家家里面有一个this关键字,这个是C加加里面新增的一个关键字,这个关键字本质上就是一个指针变量,它这个指针变量是指向那个对象的,就是说他其实是在,它其实是编译器,它自己帮你自动生成的一个指向了他的类里面的那个对象的那个地址的一个指针变量。这个是编译器帮你自动加上去,加在你的定义的那个类里面的,就是说,它是一个帮你自动加进去的一个指针,变量指在你的类里面指向你将它在你们你那个变那个类里面的意义,就是它预先帮你指向了你后面要定义,但是现在还没有定义的那个对象。

就是说他这个历史就是Xie加加在编译器在编译的时候,帮你在你的类里面自动加上去的,这个这个东西,然后他指向你将来可能要用到的,基于这个类的对象,但是现在还不知道是哪个的哪个对象的一个东西,就把它用this来代替,然后这个日子就是指向了那个在未来就是要指向那个对象的,然后为什么会出现这样子,就是因为你。在写这个类的时候呢,可能并不知道你未来的对象是什么,但是你要提前用到这个对象的去进行一个代码的书写,所以你要用一个历史去找去代表他这个对象,等到将来你未来的真的定义对象之后,他的对象的地址就是这个历史的地址了。

然后这个东西就是其实没有那么简单,就是,比如说你如果在你的类里面定义了一个成员方法,也就是一个函数,你这个函数在外面再累的,外面写了之后,比如说你这个函数在外面定义的时候,你用了他这个类里面的一个成员变量,那么你刚刚呢,我们在演示的时候呢,只是比如说你定义那个成员变量叫爱,那么你在这个成员方法里面定义的时候,直接用了唉。其实并没有那么简单,这个是一种省略的写法,其实真正的写法就是用了一个does,然后一个箭头I,这样的话就是代表的是你这个方法,你这个成员方法,你就是这也就是这个函数里面,它的这个I其实就是用的就是你未来的那个对象里面的那个I。

所以他就应该是用了一个历史,然后再指向他那个爱就历史代表未来的那个对象的地址,地址里面的那个爱,而我们为什么只是用了一个爱呢?是因为我们省略了,所以严格来讲应该是does,然后一个箭头,唉,因为你未来要用到这个成员方法,这个函数的时候呢,它其实是基于对象去用的,你是指向的是对象里面的那个爱,所以他应该是历史爱历史箭头,唉。

这个历史理解到这个程度就可以了,具体的深度讲解到后面再看。

这个继承呢?包在里面的是父类,然后外面的才是子类。

子类是继承了父类的所有的东西,然后除此之外还多了一些其他的东西。

C++新增语言特性的目的

C加加里面新增了很多语言的特性,很多都是从一个点出发,就是让编译器能够更好的去理解你的意图,就是很多新增的关键字都是从这点出发,他想让你更好的想让编译器跟你更好的进行一个理解,理解你要干什么。

是他新增的这些关键字,就是尽可能的希望让编译器能够知道你这个程序员写这东西的本意,然后,如果他发现他跟你的本意是相同的,就通过本一跟你的本意有所违背,那就报错,就是尽可能的让编译器知道你的想法,知道你这个程序员的本意。

就是一个出发点,就是希望编译器尽可能的知道我的本意,然后,一旦我们的本意跟我们不小心出现了一个错误,然后他就会发现得到他,我们写东西跟我们想实现东西不一样,那就尽可能的去报错,反正一切语言的是新增的原特性,都是希望能够尽可能的完成这一点。

const变量和宏定义相似,但是区别在哪

之前说过的那个cost变量跟那个宏定义很类似,但是其实它有一点区别,就是cost的变量,它是占内存的,但是宏定义它是不占内存的宏定义,就是预处理之后直接替换掉了,不占内存的,但是cost它是占内存的。

Cost在C语言中有三个用途,一个是cost变量,然后一个是cost数组啊,一个是cost指针,Cost变量。其实跟cos的数组是比较简单的,Cos的数组就是你一次定义多个cos的变量嘛,所以难的地方就是那cost的指针那里。

inline函数本质提高的是运行时的效率

这个inline函数,它本质上提高的效率是在运行时的效率,而不是编译时的效率。其实提升效率本质上就是提升。运行时,因为你编译只有一次嘛,那运行是多次运行,所以一般提升效率都是提升运行时效率。你把online放到那个里面去的话,那它就是运行时效率就高了。

模板的初步理解

template <typename T>
T add(T a, T b)
{
    return (a + b);
}

C++语言的高级之处:抽象

CC加加这种语言教C语言,高级就高级在了抽象上,就是说C加加你去学它,有10%是在学他的基础概念,40%是在学它的面向对象,50%是在学他的抽象,那么模板就是其中一个很典型的抽象模板,它是从何而来的,他就是从函数重在发展出来的,就是你一个函数虫在。你同一个函数名,你可以有多种存在方式,那么你是不是你要配合这个函数名,使它能够支持多种类型的话,你如果没有模板,没有抽象,你是不是一次性要定义出十种,20几种十多20种不同数据类型的同一个函数名的一个函数,这样子才可以存在?

但是这样是特别麻烦的,因为你要每一次定义一个函数,你就要定义十几20种类型,让他去虫在,所以因此就出现了一个模板,模板的意思就是说,他把这个函数给抽象出来了,他只用写一次,比如说用个template的T,这个T就代表了它可以试的数据类型。那么后面他会根据一些你实际的一些操作,推断出你实际的数据类型是什么,就是说她在写的时候,就把它抽象成了一个T,这个就是模板的有模板。

然后模板是就是一种C加加的抽象,S te l库就是就是一帮大神写的这个模板,给你们这些人去使用,S te l就是一个标准模板库standards template re,所以他就是一个标准模板库,后面他就是全部都是这样的一个模板,然后后面的人就可以基于这个标准模板库去使用模板。

然后这个模板呢,它有一个不好之处,总结来说,就是写模板的人跟用模板的人是分开的,它们之间是很难进行交流,甚至不知道彼此的。这个时候呢,你的写模板的人,他可能只提供了这么多部分的数据类型,你用模板的人呢,并不知道他写模板的人,基于这个函数,它提供了多少种你可以用的数据类型,所以这里就会出现一个问题就是。彼此之间不知道用模板的人可能不知道写模板的人,他给他提供了多少种可以用的数据类型,所以这个时候就需要有一些参数的约束,给他,就是说有一些信息通过一个编译器去传递,编译器告诉,告诉编译器,编译器之间进行传递,告诉你哪一些是可以,哪一些是不可以,这就是参数约束。

然后si Jia jia20版本它引入了很多特性,这些特性很多都是围绕着这个参数约束去增加的,就是希望增加一些特性,让你这个抽象的东西,彼此之间能够知道互相的约束,是什么,就是彼此本身是很难够知道的,就是想要通过一个参数的约束去传递,告诉对方我的这个订阅抽象,他的餐约束条件有哪些,这就是西加加二零,它的一些新增的特性的,主要的围绕解决的问题。

比如说这个C加加二零引入的request就是其中的一个参数约束,然后除此之外呢,还有一个细枝末写,就是一个export,这个export export关键字是专用于模板的,它类似于是对模板的一个声明,这个的话暂时可以先不用了解,知道就行,包括C加加二零引入了一些关于参数约束的一些关键字,比如request,不是request,是requires,比如这些关键字你大概知道就行,现在先可以不用理解,先不用了解。

就是现在可以先不用深入去理解。

异常处理机制

try catch机制

西加加中引入了try catch机制,就是异常处理机制,这个在Java里面也是有的,然后我们重点需要关注的是他为什么要有这个异常处理,也就是try catch机制。我的思考是有两点,其中我发现第一点并不正确,但是他会对你这个差cache的理解进行加深,第二点才是他真正为什么要有try catch的原因。第一点就是它的这个程序,他不想死,就是就说他如果你try catch的话,你就可以把你的那个try catch try里面的东西,然后就可以跳到那个catch里面去,你throw一个东西之后,你就可以跳到那个那个catch那里面去,你执行完catch之后呢,你就可以继续执行程序后面的东西。

你执行完这个东西之后,你执行完catch之后,你就可以继续去执行程序后面的内容,这样程序它就会继续跑下去,就不会突然间卡死中断掉。er c里面你如果是用衣服,然后判断他是否出现异常,然后呢,再一个返回,返回一个错误,这样的话他程序就在这里,就卡死了,就不会再执行下面的东西了,所以拆开他就保证了这个程序能够不死,然后就一直从上往下运行下去,然后他可以抓住一个异常catch异常,但是它程序不会卡,不会停住,它还会继续执行。

就好比你使用一个小程序或者一个软件,比如说QQ,他的如果你是他程序卡在一个地方,卡死之后呢,就就停止了,中途退出了,他的现象就是,比如说你打开了一个QQ里面的一个窗口,它就它就会弹出说你这个打开失败,然后直接就是直接QQ就退掉了,这个程序就死掉了,就是他可能没有打开窗口。

就是比如说你在QQ里面,你如果想打开一个窗口,你如果是没有程序,程序没有继续跑下去,那么就是他直接就闪退了,这QQ程序就直接死掉了。然后你如果是有一个异常机制的话,那么你打开这个窗口,可能这个窗口里面有bug,他可能就会踹开去,抓住了他,给你打印出来一个说此窗口不可使用,然后你点个确认之后,整个那个QQ程序还是可以继续运行的,只是在这个窗口这里有个bug用不了,但整个程序QQ还是可以继续运行的。

为什么要用try_catch机制

这是我的第一点,思考思考为什么他要用差cash,本质上来说,第一点的思考,他的原因是因为差catch,它可以程序不死,而C的那个衣服的话,程序会返回就死,直接死掉了。后面我发现这个可能不太准确,因为它的程序不死,是因为你没有返回,你如果C里面你也可以这么写,就是你的衣服她什么什么,然后你打印个什么东西,然后打印个出错,但是你不返回,你不返回的话,你也程序也是可以继续运行的。简单说,就是这个并不是引入try catch的主要原因,你就算不引入try catch,你用C语言的if判断,也可以直接让程序不死,只不过就是这样的话,你只要加一个,这样的话,你只要不返回它C语言里面的if程序,也可以不死,所以这个并不是主要的原因。

真正引入差cache的一个原因是为了实现他的一个代码,跟那个报错,就代码跟异常的一个完美的分离,这才是最关键的一点,就是你如果用了try catch,你的代码结构会非常的美观,你的把所有的cash都放到下面去,然后你的代码就放到上面去,这样的话你就可以实现了一个代码跟一个代码,跟一个那个立场的一个分离。如果你没有用try catch机制,那你每用每写一个函数,你这个代码你就要对它进行一个反,就进行一个评估,就是用if判断它会会是否会出现错误,然后做出下一步的反应,就是说你每用一个函数,你都要在后面加个if,这样的话,你的异常跟代码跟正常代码是不能分离的,每写一个正常代码都要写一个异常。

每写一个正常代码都要写一个异常,在后面衣服去判断他,但是你如果用了差catch机制,你正常代码是可以继续写下去的,然后你把正常代码写完之后,你在后面再写上一堆的一个catch,去catch他的错误,然后你再正常代码里面你多写一个输入的一个一个一个一行代码,你就自动的就可以输入到了后面的那个CAD里面去。总的来说,这才是真正的引入try catch的一个本质原因,它可以实现代码的一个美观性,简洁性,实现正常代码跟try catch异常的一个代码的一个分离,这样的话程序就看着就整洁了很多。

那这个差catch异常处理机制,它有一个through,它本质上是通过这个思路去找那个catch的,比如说你输入它里面你不同的情况,你会输入不同的数据类型,比如说你第一次你这个这个这个功能,这个函数它的异常呢,你输入的是int类型,你第二个函数的功能,你出现异常,你输入的是double类型,你第三个函数你出现异常,你输入的是一个float数据类型,然后你到后面的话,你到catch里面catch,它就会根据你,你的那个through的一的数据类型去分别进入到不同的cache中去。

首先你知道这个函数,它是一层一层去调用的,他可能有两三层,四五层,然后他可能在最底下的那一层呢,它只是抛出了一个异常,但是他并不catch,然后这个时候他就会程序就会层层的往外去推,你看你外面一层有没有开取,如果外面一层没有开去,在看在外面的一层,如果在外面一层还是没有开始,那在看在外面的一层,直到最后找到面面函数里面去。如果面函数里面还没有catch,那就说明你这个抛出来之后没有catch,最后就会慢,再往上一层就是操作系统了,那么操作系统就会认为你没有抛,你抛出了这个异常,但是你却没有catch它,那说明你这个程序就已经崩溃了,就已经死掉了,就是这样子。所以它是一个层层的往上去找它的catch,所以不一定在本函数里面就会有catch,它会往上面它的前一集去找。

为什么要有异常的throw列表

void func(void) throw(A,B,C);

然后就是还有一个情况,就是他你的血异常的人跟你这个开始异常的可能交流上面会有点不太方便,一般的话你就要,你如果这个函数里面你写了一些异常,你写了会产生一些什么异常,输入会输入一些什么异常,那你就要在函数的后面补上一个异常的一个,抛出异常的一个throw列表,有个思路列表之后,别人就知道你这里面会有什么异常,这样的话,人家在用你这个函数的时候才心中有数,知道你可能会抛出什么异常。

至于为什么他的那个你抛出异常,都可能会在本函数里面,没有开始,他异常会流到上一层函数,甚至上上层函数,再去开始他的这个异常呢,这个原因就是可能再抛出异常的时候呢,那个函数,它不知道你要对这个异常进行一个什么样的处理,比如说同样是一个处,被除数等于零的一个异常,可能有一些人他处理这个异常,他就直接。报报打印出一个东西,说不能这样用,可能有一些人他面对这个异常,他就想要处理成这个函数,直接就先停掉,先停止掉是吧,所以不同的人对不同的抛出的异常可能处理是不同的,所以这就是可能你无法在本函数里面就确定怎么对这异常进行处理,所以就先不在本函数进行catch,留给他的上一层,甚至上上层函数去处理他的这个异常。

这其中的一个最关最最典型的例子就是写标准库的那些人,他可能写标准库,那些人大多数都是只给你抛出一个异常,就是指给你输入一个异常,然后在那个函数的后面给你补上,我输入了什么样的异常。至于这个异常你怎么catch,那就是你的事了,因为写标准库的人不知道你要对我这个异常进行什么样的处理,是不是,所以他只能够是抛出了这个异常给你看,你最后你调用他的时候,你的上一层函数,乃至上上层函数。是怎么catch他的,Catch他之后怎么处理是吧,所以写标准库那些人,他是标准库里面基本上是没有catch的,他只是给你输入了异常,然后在函数的后函数外面补充告诉你,给你一个输入列表,输入列表能告诉你哦,我写标准库的时候,我会抛出什么异常,那么你用这标准库的人大概就心里有数,哦,你有你用了,这你出现你会可能会出现这些异常,我后面要怎么样,Catch你这些异常去怎么处理。

我后面怎么去catch你的异常,在我的上一层函数去去catch到你的这个异常,然后去进行处理。

自由存储区

徐佳佳里面多了一个自由存储区,它是专门跟六跟迪丽特相关的自由存储区,它也是属于堆的里面的东西,就是说自由存储区,它也是属于堆的那的一部分。

内存泄漏和内存碎片

然后C加加里面很大一部分的灵活性,还有一些风险性都是出现在这个镦镦的使用上,也就是说对内存的使用上,西家里面最经常出现的问题就是内存泄露和内存碎片,内存泄漏,这个我们现在写的一些短,比较短,然后比较小规模的代码是体现不出来的,因为他跑完自动就退出吗?所以你内存泄露你感受不到。但是如果是用C加加开发一些大型的服务器呀,这种大型的代码,它在后台要运行个一年两年甚至三年的,很长时间的都不关,那么这个时候内存泄露的风险就会体现出来了,比如说你这么久都不关,但是你如果,所以这时你如果出现内存泄露,你某一个力量忘记释放内存了,那么你内存就会一点一点的。

那么你内存就会一点一点的被吃掉,然后最后越来越久,越来越久,可能过个一段时间你的cpu就被搞搞死了,就是内存被吃完了,这就是长时间的一个运行之后,你如果内存泄漏没有处理好,长时间运行之后,这程序就会崩溃。

还有一个出现问题的地方,就是有关内存碎片的,这个就是比内存泄露更高级的一个话题了,内存碎片就是本质上就是说你反反复复的去六根迪利特,你反反复复的像这个操作系统去申请六申请迪利特,这样就造成了很多很多种频率的很多频次去申请了释放的内存,这样的话就会造成一种内存碎片,然后就这个东西会给那个操作系统的内存管理的效率增加很大的一个负担的,这个东西是更高级的话题了,后面深入再去说,就是内存碎片这方面的东西。

new和malloc的区别

关于六合malloc的区别,你可以理解为si家家里面之所以还会有一个malloc,是因为他为了要保持C的向下si的兼容,就说其实C加加里面现在可以,其实没有malloc都可以了,直接用六,然后我们后面就写C加加的时候呢,一般也是直接用六就行了,不用malloc的,有了六就不用malloc的,只不过你要很多东西,很多时候你要向C去兼容C加加,要兼容C,所以还保留了malloc。

然后六其实就是在它的它里面有包含malloc的,它是六,它这个是执行了,卖了之后他还多了一个构造函数,就说六,它比mile多的一个步骤,就是他在执行完买了之后,还多了一个构造函数的构造函数,就是你要构造一个对象的时候,所要所要使用的一个函数,然后这个函数就可以使得你六的那个东西。它可以初始化掉,所以这也就是六跟mylove的一个区别,Mylove的话,你申请一个内存,那里面的内存里面的那个值是随机的,它不会帮你清掉的,就类似于是一片荒地,但是六的话,它因为比mylove多了一个构造函数,所以构造函数那里,它可以把你申请一段内存里面的东西给初始化掉。

也就是说这个它的六它可以初始化,比如说你如果是一个inch,新批等于六,然后in te,那么它这个就会返回一个里面的地址里面的值为零的一个初始化的后的一个地址给你,然后你如果是6INCH,后面加个括号写上你的数字,那么这个就是你要初始化之后得到的那个数字,就你返回的那个地址,那个地址里面的纸就是你的括号里面的数字,你可以理解为如果不写括号,直接一个6INCH的话,那就是你的构造函数里面是默认了它的构造的初始化是为零的。

简单说就是六,他多比malloc多了一个构造函数,这时他可以初始化,而malloc是不可以的脉络可就直接就是申请一个那个内存,申请一个地址给你,然后malloc的话,它在C语言里面,它是一个是一个库来的,他在C库函数里面,但是六呢,直接就成为了C加加的一个关键字了,这也是他们的一个区别,还有一个区别就是。Mylove的话,它它返回值的类型是void的新类型的就是它它它没有一个固定的类型,你用的时候你要加个括号,把它强转,比如强转成int新类型你才能用,然后六的话呢,你写上一个六,那么它自动的就会认为你的这个返回值就是int新类型的。

new不能强转

然后六的这个特性呢,也决定了你是不能够像mylove那样是强转的,因为mylove是void星,它可以随便强转任何类型,但是你六的话,你是不能强转的,比如说你等于6INT的话,那你就只只它的返回值就是6INCH星了,就是int星了,而你6DOUBLE的话,它的返回值就是double星了,它是不像mylove可以强转的,也就是说它六它没有mylove那么自由了。

C++基本都用new,没必要用malloc

但是尽管如此,在C加加里面基本上都是用六了,不会用mylove的,就像前面刚刚说的,有了六就根本没有必要使用mylove了。C加加之所以要保留mylove,是因为它要保持对C的兼容,其实六能做的c mylove可能做了六已经帮它做出来了,而且做的比mylove还好,他帮你还初始化了,所以后面C加加写的时候基本都是用六了,而不用mylove了。

智能指针

多封装了一些东西,使其变得更智能

西加加的智能指针,简单来说就是它封装了一个类,这个类里面除了有指针,还有其他的一些其他的东西,比如说他有一个类,它这个类里面多了一个函数指针,函数指针指向的是他的一个自动回收的函数,指针指向的是他的一个回收,这个这个智能指针的那个地址,就是清理清理地址相关的一个函数指针,他把这些功能都放都打包成一个类,这叫智能指针。所以智能指针的话,它会省下一些功,省下一些事,你不用手动去回收啊,但是呢,它会消耗一定的资源,会比普通的程序员,用指针消耗更多的资源,为什么?因为比如说你直接用一指针,那你的那你就是一个四字节的地址嘛,那你如果用一个智能指针,那你就它它就打包这个类里面了,那类里面的话,它除了有指针,还有其他的,比如说你的一个函数指针去回收它的一个内存之外。

智能指针比普通指针消耗资源多

所以这种情况的话,智能指针它的消耗资源肯定就会比你普通定义一个指针消耗资源多,这就是智能指针。

智能指针除了这种情况,还有其他的几种情况,它不止这样一种实现方式,还有很多种实现方式,后面的话就会可以细看。

然后其中有一个s std auto,然后智能指针的那个东西,那东西是C99899的那一个特性,那个现在基本上已经废了,没有人用了,除了之外,反正这个后面细看就行了,现在还不用细看。

java垃圾回收机制

然后Java的话,它是一个,它有一个垃圾回收机制,它就比C加加的这智能指针更加离谱了,就你程序员完全不用管那个,那地址是怎么回收的,它Java里面有个Java虚拟机,J bm Java虚拟机,它帮你把这些东西都做好了,所以你程Java程序员,你直接这样用就行了。

就说Java,它有一个层级的,它其实Java的,你你你写的Java源代码,它是会把它编译成一个Java字节码,Java字节码,它是必须得要有Java虚拟机才能够使用的,所以它Java跟C加加是完全不在一个层次的,就C加加它是面向操作系统的,Java它是面向于Java虚拟机的,然后Java虚拟机本身就是用C或者C加加或者其他语言写出来的,所以Java跟C加加就不在一个层次上。

这里就是说了一个Java的一个跨平台性嘛,因为他Java它并不依赖于操作系统,Java它是依赖于Java虚拟机的,那么Java虚拟机呢,你只要有个Java虚拟机,那么它就可以用Java程序自解码就可以运行是吧?所以它其实依赖的不是操作系统,依赖是Java虚拟机,你只要在Linux Windows有个Java虚拟机,那么它就可以用Java,所以Java的跨平台性是体现在这里的。

java的效率不取决于程序员而是JVM虚拟机

阿扎保的话,你Java程序员,你是没有任何的一个能力可以对内存进行一个优化的,就说你程序员是没有权利的,就是你程序员的写出来代码效率高不高,程序员是无法决定的,真正决定的是扎把虚拟机,你的Java虚拟机越好,那么你程序员写的代码就效率就越高。所以真正取得效率是Java虚拟机,而不是程序员他写的东西,程序员他写的再好,他也无法改变效率的这个问题。因为取决于Java虚拟机呀,所以说安卓安卓,为什么基于Linux,他做了一个Java虚拟机出来,然后它的效率就,所以基于安卓做开发的那些人,那些程序员做Java,安卓程序员,他无法取决于效率,为什么?因为效率是取决于谷歌开发的那个安卓。

所以它的效率是取决于谷歌基于Linux开发出来的卓的这个操作系统,它的这个里面的那个Java虚拟机,它的一个优劣,它里面的那个Java虚拟机的效率并不高,所以很多人都会骂安卓,说安卓不好,所以就是因为它这个Java虚拟机不好,是这个原因,所以程序员他是无法无法改变这个现状的,他他没有权利,他没有这个能力去改变他这个效率问题,他写的再好代码也不行,因为真正决定你的效率也是Java虚拟机,然后你让Java垃圾回收机制也是在Java虚拟机里面去去实现的,不是程序员去实现的。

垃圾回收用的是GC线程

然后一般的Java的一个垃圾回收机制的话,就是它有一个巨溪县城,就是在ZB里面,你实现了一个GC线程,是Java虚拟机里面实现一个守护线程,这个肯定是用C后期在家或者其他原写的,不是用Java写的,然后这个车现成的话,就是他隔一段时间就会去进行一次,每运行一次,他就会把你的那个垃圾回收处理掉啦,基本上就是内存吗,就是你不用用没用了的内存,你要回收回来吗?否则就会内存泄露嘛。所以这个Java的一个垃圾回收就是用了,距县城去回收,每隔一段时间车县城就会回收。

垃圾回收的两种方法

对Java的垃圾回收机制,它有两点需要注意,第一个是什么时候需要回收,第二个是回收,哪些是需要回收的,什么时候需要回收,就根据线程有关了,你就有个算法嘛,算他是什么时候需要回收,什么时候需,需要执行这个车现成儿。第二点呢,就是它这个Java的这个,嗯,它会判断什么东西是得回收的,他的判断哪些是垃圾,哪些不是垃圾,是不是这里的话就是也是一个问题所在。这里呢,他就有一个算法,有两个算法,一个是引入引用计数法,第二个是可达性分析法,引用计数法简单说就是它会有一个引用。

引用计数法

引用计数法就是它会判断,它里面有个算法,它会判断你的引用,对于这个这个内存的一个引用次数,如果引用多了就加就是正的,如果引用少了,那他它就会一直一直减,只要减到负,那么它就通过这个引用技术的一个情况来判断你这个到底是不是一个垃圾,如果引用的少了,变成负了,那它就是垃圾,就回收。

可达性分析法

然后第二个就是可达性分析法,可达性分析法就是说他一个函数或者一个内存,它是有前后链接的,如果他前后链接通透的,一直都是我用了你,你用了我,我用了你用了我,他有一条线呢是通透了,那么就说明他这个内存是要用的,还不用回收,还不是垃圾。但是如果你这个内存它的前后不通透了,比如你用了我用了,你用了我,但是其中前面东西没有人用了,那么它这个前面就没有人用了的话,那它就是断了断了的话,就不通透了。他前面有断掉了,所以他这个可能就是没有,没有用了的,已经没有用了的,他前面比如说a用了B,但是没有人用a,那么这个时候呢,就是断了的,一旦断了的话,就说明他就没有人用,那么它就是垃圾了,这就是可达性分析法,通过可达性分析法就可以实现判断它是垃圾去回收。

这就是整个一个Java的一个关于他的一个从,从他的出现,就从她的一个起源,到它的一个展现到我们的面前的一个整个一个流程,就它是其实是从先是从操作系统,再到一个Java虚拟机,再到一个Java字节码,再到一个Java源代码了,所以他Java,他跟操作系统还差的远呢,它中间是用操Java虚拟机的,Java虚拟机本身是操作系统,用C加加写出来了,所以Java跟C加加不在一个层次上,Java的话,前面还有个Java虚拟机才能到操作系统,所以就是这样子。然后Java的回收机制的话,刚刚前面也说了,他有一些东西在里面,我前面已经说了,现在就是基本上已经讲完了,就是这么多东西了。

所以总的来讲就是有三个阶段,就si阶段,还有C加加C阶段和传统的C加加阶段,它是没有任何的一个介入的,对于内存没有任何介入,完全靠程序员手动,然后第二个阶段就是C加加里面,它引入了一些些一小点的一个智能阶段,就是一个智能指针,然后这个智能指针,指针呢,它会有一定的帮助程序员去进行内存管理,但是大部分还是要程序员自己手动操作的,你像我刚说他就就就是一个打包成一个类吗。类里面多一个函数指针,函数指针里面指向的功能就是去进行垃圾回收,去进行一个内存回收嘛,但它其实大部分还是要程序员去做的,这是C加加的一个高级的一个阶段。然后还有一个更高阶段,就是Java阶段,Java就纯粹就是把内存回收、内存管理全部交给了JPN全部交给了那个,那个一个全部,不用程序员操心。

就是Java,他把所有的东西都已经内存管理,东西都都把都不给程序员做,程序员呢,只是负责写代码,他不用关心内存了,内存的管理啊,内存泄露的东西全部交给Java虚拟机这边去做的呢,就是通过操作系统慢慢演变过来,通过CC加加写出来,实现一个东西,就是这样子,整体来说就这样子。到了第三个Java阶段的话,这这些语言,它完全就是不需要程序员去对内存进行管理的,比如说Java、西沙华、python的这些。它基本上就是有一个,它就是用CC加加实现那个g bn Java虚拟机嘛,C shop肯定也会有一个类似的一个GBN之类东西,Python的话也是,凡是这种东西的话,就是有这样东西。这就像这也就是叫一种解释型语言,解释型语言就是这样做出来的,它不是基于操作系统直接来的,它是有个JBN的,先用操作系统CC加加开发出个GPN,然后让你去用,其实c Java啊,然后C韶华,Python都是这一类东西。

就是都是这一类形,通过操作系统开发出了一个用来解释你的这个这个语言的个东西,然后基于你这个解释的语言的东西,然后你去开发语言,所以他重点提升效率,然后对内存进行管理,是对于你这个语言进行解释的那个程序,也这个就是在Java里面,就是在JVM,虚拟机在拍摄里面可能就是体现别的代替效果,肯定是就可能就体现别人,但他就跟操作系统就已经是不在同一个层次的,C跟C加加,就是直接用操作系统,可能他的那个解释解释东西,就是用C加加写出来的。他解释,比如说GVNJVN解释的那些东西就用CC来写出来,所以这其实是两个层次的东西,所以到更高级啊,Java啊,Java啊,然后拍成这些,它就是一个程序,也完全不用管了,不用管内存管理啊,把它交给你对这个语言进行解释的那东西进行处理,是这样子,它层次就划分的很很远了。

面向对象与面向过程

面向对象的理解

面向对象,它是一个语法层面的东西,就说它在二进制层面,它层面它是体现不出来的,你cpu根本不知道你的代码,它是用的面向对象还是不还是没有用,面向对象,因为最终转换成二进制的时候是一样的东西,所以它面向对象只是停在语法层面,就你程序员写的时候是这种思想去写而已。

面向对象呢,它不是一个单一的概念,它里面有一篮子的解决方案,也就是说它不是只有单一一个概念,它有很多细节,最经典的就莫过于它的三大特性,嗯,封装、继承、多态是吧,所以它其实不是一个单一的概念,面向对象,它是一篮子的一个解决方案来的。

简单说就是面向对象,它是有三大特性,封装、多态、继承,再加上其他一些细小的细节。把这些东西合在一起,就是封装、继承、多态,再加上其他一些零零碎碎的一些细节,加一起,这合冲合起来统称为面向对象。

就是说多菜是一个面向对象,但不是全部,然后封装,它也是面向对象,但它也不是全部继承,它也是面向对象,但它不是全部。还有一些其他细节,它也是面向对象,但不是全部,是他们这些东西全部统称在一起,才叫面向对象。

设计模式,它就是基于面向对象去发展出来,它是比面向对象更高深的一个东西。就是说面向对象是设计模式的基础。设计模式是什么呢?就是你后面可能用到一些框架,比如说MVC、框架,然后一些其他的林林总总的框架,这些就是设计模式,它的根基就是面向对象。

总的来说,一个哲学路线就是从非面向对象到面向对象,再到框架,然后再到一个设计模式。

面向过程的理解

面向过程是什么意思?面向过程的意思就是,你把你要实现的这个任务呢,他就是你他可能要完成这部任务,他可能有有三四个过程吗?比如说他,比如说你可能要点亮三个led,或者说你这个它有个过程,就是比如说你第一步,你要先让第一个led灯亮,第二部先让就按第二个led灯亮,第三部,就让第四个led灯亮吗。所以这个就是一个过程,然后面向过程的重点就是你先把这个任务分剖,剖开成一个一个的过程,这个任务就有一步一步的过程实现的,然后你就再去单独的看,我要实现这个过程,我要实现什么功能,第二过程用一个函数,第二过程用一个函数,第三个过程再用一个函数,这样就实现了一个面向过程,就你是面向过程去编程的。

简单概括总结就是面向过程,就是你把你的要完成的一个任务分成第一个过程,第二个过程,第三个过程,第四个过程,然后每一个过程呢,就有一个函数去实现,然后你要关注的点就是你的第一、第二、第三、第四个过程分别用什么函数去实现,你就把那函数写好,然后按照相应的过程然后堆叠在一起,那就是一个过程了,然后就这个过程组合在一起就完成了这个任务,这个就是面向过程编程。

把面向过程改造成面向对象

版本1

#include<stdio.h>

void led1on2off3off(void);
void led1off2on3off(void);
void led1off2off3on(void);
void ledonoff(int led_num, int led_sta);

int main(void)
{
    while(1)
    {
        led1on2off3off();
        sleep(1);
        
        led1off2on3off();
        sleep(1);
        
        led1off2off3on();
        sleep(1);
        
    }
    return 0;
}


void led1on2off3off(void)
{
    printf("led1_on\r\n");
    printf("led2_off\r\n");
    printf("led3_off\r\n");
}

void led1off2on3off(void)
{
    printf("led1_off\r\n");
    printf("led2_on\r\n");
    printf("led3_off\r\n");
}

void led1off2off3on(void)
{
    printf("led1_off\r\n");
    printf("led2_off\r\n");
    printf("led3_on\r\n");
}

版本2

#include<stdio.h>

void led1on(void);
void led2on(void);
void led3on(void);
void ledonoff(int led_num, int led_sta);

int main(void)
{
    while(1)
    {
        led1on();
        sleep(1);
        
        led2on();
        sleep(1);
        
        led3on();
        sleep(1);
        
    }
    return 0;
}


void ledonoff(int led_num, int led_sta)
{
    printf("led%d_%s\r\n", led_num, (led_sta == 0)?("off"):("on"));
}

void led1on(void)
{
    ledonoff(1, 1);
    ledonoff(2, 0);
    ledonoff(3, 0);
}

void led2on(void)
{
    ledonoff(1, 0);
    ledonoff(2, 1);
    ledonoff(3, 0);
}

void led3on(void)
{
    ledonoff(1, 0);
    ledonoff(2, 0);
    ledonoff(3, 1);
}

版本3

#include<stdio.h>

void led1on(void);
void led2on(void);
void led3on(void);
void ledonoff(int led_num, int led_sta);

int main(void)
{
    while(1)
    {
        led1on();
        sleep(1);
        
        led2on();
        sleep(1);
        
        led3on();
        sleep(1);
        
    }
    return 0;
}


void ledonoff(int led_num, int led_sta)
{
    printf("led%d_%s\r\n", led_num, (led_sta == 0)?("off"):("on"));
}

void led1on(void)
{
    int i = 0; j = 0;
    for(i=1; i<4; i++)
    {
        if(i == 1)
            j = 1;
        else
            j = 0;
            
        ledonoff(i, j);
    }
}

void led2on(void)
{
     int i = 0; j = 0;
    for(i=1; i<4; i++)
    {
        if(i == 2)
            j = 1;
        else
            j = 0;
            
        ledonoff(i, j);
    }
}

void led3on(void)
{
     int i = 0; j = 0;
    for(i=1; i<4; i++)
    {
        if(i == 3)
            j = 1;
        else
            j = 0;
            
        ledonoff(i, j);
    }
}

版本4

#include<stdio.h>

void ledon(int k);
void ledonoff(int led_num, int led_sta);


int main(void)
{
    while(1)
    {
        ledon(1);
        sleep(1);
        
        ledon(2);
        sleep(1);
        
        ledon(3);
        sleep(1);
        
    }
    return 0;
}


void ledonoff(int led_num, int led_sta)
{
    printf("led%d_%s\r\n", led_num, (led_sta == 0)?("off"):("on"));
}


void ledon(int k)
{
    int i = 0; j = 0;
    for(i=1; i<4; i++)
    {
        if(i == k)
            j = 1;
        else
            j = 0;
            
        ledonoff(i, j);
    }
}

版本5

#include<stdio.h>

void ledon(int k);
void ledonoff(int led_num, int led_sta);


int main(void)
{
    while(1)
    {
        ledon(1);
        sleep(1);
        
        ledon(2);
        sleep(1);
        
        ledon(3);
        sleep(1);
        
    }
    return 0;
}


void ledonoff(int led_num, int led_sta)
{
    printf("led%d_%s\r\n", led_num, (led_sta == 0)?("off"):("on"));
}

struct led_control
{
  int i;
  int j;
};


void ledon(int k)
{
    struct led_control lc; //先不初始化了,这里重在理解代码的演变,初始化这个细节就先不看了
    for(lc.i=1; lc.i<4; lc.i++)
    {
        if(lc.i == k)
            lc.j = 1;
        else
            lc.j = 0;
            
        ledonoff(lc.i, lc.j);
    }
}

然后对他这个led的面向过程的个代码进行改造,你会发现他这个改造它都是朝一个方向去做的,就是尽可能的把东西都封装在他所所传的那个参数里面去。

第一步他先是改造是怎么改造呢?它就是把你要传的参数吗?你总的来说就要传两个参数,一个是你那个流水灯的一个编号,一个试试你那个它的开关亮面的状态,就是这两个参数,所以你就把它改一下,就是把它改成传这两个参数,然后比如说你要设置三个流水灯,那你就是写这三个函数,三个函数分别传传这两个参数进去。

然后他第二步更改的地方就是你在这个里面,你是直接在这函数里面,你一开始的时候是根据你传进来的参数,然后直接去进行一个控制嘛,然后他在这里就加了一个for循环,就是通过for循环去遍历,去找到你传进去的参数是什么,如果是那个的话就亮,如果不是就不亮。

然后这个时候呢,就是你会发现加个for循环之后,你有个I z ma,一个LZ,然后你最后是通过你,你就可以把这三个函数,本来是通过for循环写进去的时候,他还是三个不同的函数,但这个时候你可以把这三个不同的函数已经可以收缩成一个相同的函数了,就是你去你通过或循环,你去便利,那么便利的时候,它就有个爱吗,你就要对应,不是他变的时候你给个K给他,就是他可能会有一个K,你让他第第二个灯亮呢,K等于二,第三个灯亮就可以等于三,然后你把这船舱的参数然后传进去之后,他自己做个或循环便利,那么就知道哪个灯是亮的了。

所以这个时候你通过在for循环的基础上,你通过只传一个参数K给他,只传个K参数给他的话,那么他就可以实现控制这几个灯亮,那么就把这三个不同函数都凝结成了一个函数,然后这个函数呢,你要传的参数是K。

然后最后一步就是你把他的那个这个函数,它的ig ma ig,你也可以把它封装成一个结构体,这个封装成了,这个结构体呢,就是就是后面的话,这样的话,你后面船餐你就不用传两个参数进去了,你就不用传I跟G了,你就传结构体进去就行了,你传结构体进去之后,你在你函数里面,你在用结构体点,唉结构体点J,这样的话就说你传参又直,又少传了个餐,只传一个结构体进去,所以最后改造就改造成了这样子。

所以这里还是有两个函数的,就这两个函数你要分开,就它有一个是专门点亮的函数,这里面传的参数是IZ,它在这个角度上面,他就用个结构体,然后把它IC封装在了一起,我刚刚说的把它封装,结构体是在这里封装,然后另外一个角度,它还有另外函数,是从另外一个角度去看的,另外函数就是把他的这些亮面放到一个for循环里面去,包装成一个函数,然后这个函数的话,你只用传一个参数K进去就行了,这个函数,这个函数K,他就告诉你他是在那个。这个函数K是在那个它的函数里面的for循环里面进行判断的,比如I等于几G等于几嘛,然后它用,如果I等于K的话就亮哪个灯,所以这K是用来控制灯哪个灯亮了,所以它这个函数还是控制,把三个不同功能函数封装成一个功能函数了,但是这个一个功能函数里面的那个函数,它还是有IZ的,这个IZ你再用结构体把它封起来,是这样子的。

然后他那个控制传你传AZ的就传你的第几个,第几个led亮,然后就你的led的编号,然后led的亮面的状态的那个函数就是一个led on of函数那个函数里面它就直接用打印吗?打印哦,首先第一个打印出你这个led的编号,第二个就打印的参数就是打印出来的那个东西,就是他用了一个,嗯三三项表达式是不是这么说的,如果就是说如果他让你传进去等于零,那么就是打印出量,如果你传进去的是。一那么就是表示是灭,就是有一个这样一个判断,判断之后量,然后否则就灭,就是这个控制量又用了这个函数,这函数里面传的长就是I z ir,你最后你还是你,最后我刚刚说用结构体把ig封起来,就是封在这里。

注意这里面用了这个结构体,还不是传参传进来的,而只是你在外面定义了一个结构体之后,你在里面然后定义了一个结构体,是这样子的,你在里面有一个struck的led control,然后那个什么LC了,然后你定义的这个结构体,然后这个结构体再去传的,是这样子的,就说他不是传参的方式去传,把结构体传进去的,就朱老师这里,他还是只是通过了一个一直接钉在外面,定义结构体,在里面用,没有通过传参方式实现,但只是意思,这整体的意思只是跟我们说,就是他他会尽量去把这个东西封装起来,尽量变成一个东西。

linux内核里面的用C语言实现面向对象

用C语言实现面向对象,其实在Linux内核里面大把的使用啊,就是其实就是一个方法跟属性问题吗?是你不管是struck还是class,你那里面就会有一个属性跟方法吗?这跟驱动那里讲的一样,驱动就是一个面向对象了,其实感觉就是你属性是它的数据,然后那个方法是它的那个就是它的函数嘛,然后面向对象就是这样子做的呀,然后那个函数指针。他放了函数指针,然后这里朱老师用了面向对象,用C实针面向对象,你就是用那个函数指针的,然后你用函数指针指向那个函数,你先定义一个drug,你可以理解为就是个类,然后你再做成一个对象,把它实例化,然后你把它那个函数指针给实例化之后给它放到一个真的函数里面去,那么它就指向那函数。哎,这就是面向对象哎。

就是你把所有这个这个问题总结成属性的方法,两个,两个两大块,这就是面向对象,你然后你就看这个问题有多少种方法,然后把它方法总结在一起,这就是面向对象。所以驱动里面其实跟Linux内核那个实现方法,其实就是面向对象,用C语言实现了一个面向对象而已,所以这个其实你已经理解了的。

这里就是用函数指针嘛,如果你没有私加加,那你用C实现面向对象,那你就是用函数指针指向的函数去实例化,你先把你定义的那个函数定义呢,是drug的一个结构体,它的属性跟方法写好方法,然后就练成一个函数指针的形式,定义成一个函数指针形式,到时候你实例化它这个类之后,他就变成了一个对象,然后你就把它你索要的函数的那个函数指针的地址,然后把他给绑定到你那个类里面那个。那个里面那个函数指针上去,那么就自自动找到那个函数了,这就是面向对象啊,你把他东西就这样做,这就是他把东西又变成属性的方法,只不过他这里的方法,他使用了在C里面实现面向对象,他方法用的是个函数指针,去练到那函数去实现的,就是就是C的一个面向,面向对象就是多了一个函数指针,从本质上来讲,他用函数指针去练去绑定的。

这个真的在Linux内核里面就司空见惯了,就都是这样做的,所以面向对象对你来说并不难,你只要是你要去理解面向对象,那你就去了解驱动的方法跟属性,他的属性的方法,两个东西,然后你再去理解它,所有的问题都可以,归根结底归总总结为就是它的一个,嗯这个东西就可以理解为它有一个属性的方法,那么你方法是怎么写出来的,你这个东西要实现它,要用到什么方法,你把它都都总结到一起去,这样的CC的话就。把那方法总结的起之后,然后凝练成一个函数指针的形式,C加加的话就有另外一种形式,总之就是这样。但是我感觉C加加肯定也是基于函数指针去做的,做了一些封装而已。所以C加加本质上面向对象,它那些方法也是凝练成了函数指针,只不过它进进行了一系列的封装,封装成了一个东西而已。

面向对象与面向过程是共存的

面向对象与面向过程是共存的,并不是说面向对象有了之后她就取代了面向过程,你会发现我们鞋代码,它也是有一个过程的,是实现这个任务,第一步是实现了哪个过程就实现这个任务,第一步要什么,第二部要什么,第三部在什么,它也是有个过程。所以并不是说面有了面向对象之后,她就取代了面向过程,本质上是面向对象和面向过程同时共存的,然后面向对象解决的是一部分问题,然后面向过程解决的又是另一部分问题,应该是这么理解。

面向对象的核心思想不是函数指针

然后面向对象的关键并不是说函数指针,不要把这地方给给强行连接在一起。面向对象的本的关键就是我们的一个思想方法,而不是说一定要有函数指针。我面向对象关键是有一个思想方法,就是之前前面说那种思想方法。

C++原生支持面向对象

用C加加实现面向对象就很方便,它不像C语言一样,他要涉及到一个函数指针,那么你就要嗯,把这个函数地址跟这个函数指针绑定在一起,就说有一个有一步是要把它的那个函数指针定义的那个变量跟你那函数地址绑定在一起才能用在C加加里面,就不用这一步了,他原生支持的,所以它直接就帮你搞了命名空间,你只要把这个类创建好,那么它就自动的生成了这个命名空间,然后你只要在函数前面加上两个冒号,然后把那类的名字写上去,就是那个类的名字,加上两个冒号,再写函数,那么它就原生支持啊,这时候你只用在那个类里面,把这函数声明一下就可以用了。

但是本质上这两种方法并没有任何区别,C加加的内部的运作机制也是按照C语言这样来的,就说他C加加的内部运作机制也是有一个函数指针的,只不过他帮我们封装好而已。

这个C加加原生支持,面向对象都有很大的优势,就是它的方法里面可以很很直接的调用他的那个那个属性里面的东西,这个在C语言里面没有面向对象的机制是很难做到的,比如说你那个C语言里面,你这个类,你这个结构体里面的这个方法,你想要去定,去用到它,你上面定义的那个属性,这是难如登天的,因为本身这个方法跟这个。上面的属性它是独立开的,分隔开的,只是我们强行把它用个面向对象的思想去做而已,但是你如果在C加加里面,你把它放那个类之后,你的方法里面可以非常非常直接的调用你的那个属性里面的那个东西,直接用个历史之争,指向你将来要构建的对象,那么对象里面的属性有,通过这样一个抽象的方法,就可以直接找到属性的东西。

所以就可以直接通过方,通过一个历史一个箭头,直接找到他的那个,从方法里面直接直接找到那个属性的东西,这样连接起来了,这就非常的舒服,这在C语言里面是很难实现的,是难如登天的。那C加加里面,他设置了这面向对象的机制之后,这样就很很简单的可以实现啊,后面这个历史是可以省略掉,是这样子,就说西加加里面,它实现了面向对象之后,帮你做了一个这样的功能,你可以很随意很轻松的通过,很轻松的把在在类里面的方法通在类里的方法里面,去访问他的属性,访问类的属性。

那关键技术就是用那个未来的历史一个观念,它历史代表的是未来,你要定义那个对象,他目前是不知道的,但是你现在用个历史去代替,用未来的日子去指向他的那个属性,通过这个东西去实现的。

迄今为止,我们在C加加里面用了还是是用struck来表示它的这个类,你现在如果想用类很简单,你把struck的这个东西改成类就行了,完全兼容,你把struggle然后删掉,把它换成类class,就可以继续编译,不报错可以用,就是这样的。所以struck跟class本质上是一个东西,只不过看你用在哪个方面,你偏向于面向过程,那你用struck,你偏向于面向对象,那就用类。

C++从struct到class,本身无变化,变化的只是观念的升级

所以说引入这个class就是从本质上,就是从那些设计编程语言的人,从C升级到C加加的时候,然后他发现用了这个struck的在C里面这么用,然后在C加加里面还是这么用,你会发现C你赋予了struck的太多的一个功能意义了,这个时候呢,你会觉得struck就有点尴尬了,那你就必须要引入一个新的词,然后class,这class本身跟struck的本质区别就是。它是一个升级,是个观念的升级,但是实际上在设计C加加的那那帮人那里,他们原生就是先是从struck c的struck,然后过渡到C加加的struct,所以,本质上C加加的structure也是有也是有class的这个类的功能的,只不过后来他们觉得这样太尴尬了,那就把drug的再升级了一点,引出引入了class观,造成做成一个观念的升级,Class一个观念升级,但是drug还是可以用的。

但是struck他在企业家里面原生设计的还是支持这个class的功能的,是这样子的。

因为一开始设计C加加那帮人,他就是用struck的,从C引入到C加加,就是用的struck的这struck,他是有完全有class的所有功能的,只不过后来他觉得尴尬了,他就干脆又换了个名字叫class,就是两个都是可以,只不过,一个是只不过class代表了一种新的一种理念。

所以简单说你在C加加里面能用class写的,你可以把完全把class把T换成struct,因为它们两个是一样的功能的,只不过它是一个理念升级,所以一般就用class,但是你用struct去代替class的话完全是可以的,你把所有的class换成struck都可以,但是这只是一个代表的理念升级,所以大家一般都是用class。

#linux驱动也是典型的用C实现面向对象的例子
这个Linux的驱动就是一个典型的,非常典型的一个用C语言去实现面向对象的一个思想。你看我之前总结那些东西,完全就是一个面向对象的思想,是一模一样的,在C语言的。所以Linux里面的驱动完全就是一个用C语言去实现面向对象来的。

包括Linux内核里面的一些函数也都是一样的,就反正Linux涉及到一些后面的复杂的函数,复杂的代码,基本上就是总结一点,就是用C去实现的面向对象。

STM32的HAL库也是典型的用C实现面向对象的例子

单片机中的那个STM32,它有两个库,一个是标准库,一个是HL库。标准库就是纯粹的用面向过程的方法实现的库,然后大部分人用的都是这个标准库,然后HL库是HTM32,它试图用面向对象的方法去血库,这个HL裤就很多写单片机的人是看不懂的,因为他不理解为什么用这个面向对象,然后这HL库也很少人用,因为很多人不会用HL库的话,他其实也没有。100%的面向对象,它里面也用,也用了一些非面向对象的东西,为什么?因为他做了一些折中,因为你如果纯粹用C语言实现面向对象,它是个很麻烦的,你反而会觉得代码很复杂,看不懂,所以他就是用了做了个折中,在做面向对象的时候没有100%用,面向对象,有一小部分还是用了面向过程。

SOL裤,他大部分用的都是面向对象的思想去写,但是他并没有完全的用面向对象,因为他考虑到用C语言实现面向对象,有一些晦涩,很多人不一定看得懂,有点麻烦,不是晦涩就麻烦,很多人不一定看得懂,所以他做了一些折中,比如说他是在那个他那个GPL的初始化里面,他对GPL的属性就用了面向对象的思想,先做了个类,然后对这类处那个对象,对这个对象进行填充,但是最后最后呢?他没有在那,那这个里面没有用方法,在那个类里面没有用方法,而是直接把你填充好了,只填充属性,只是填充属性的这个对象,把填充好之后,然后把它作为一个东西,把它地址传传到一个函数里面去,这函数里面,你把它那个填充好的东西填充好对象的地址,传到这函数里面去,用这函数去实现功能,它这里就不是实现,这里就不算是面向对象。

就最后那一步,把他这个全部只有属性的一个类填充好之后,变成对象中,作为把他地址传到一个函数里面去,这个就是C语言里面的东西,就不是面向对象,但是他前面的那一部分,用那个用类的方式去把它属性放在一起,然后对创建的对象,把他属性进行填充,就是一个面向对象的事情,他他后面的没有用方法,这就是她做了一个折中,而是用了一个函数,把它你填充好了,那个对象的地址传进去,他做了一个折中。

但是这个Hal库,它比它比HTML标准库好很多,有个很好的优点,就是它的扩展性变强了,因为它用的是面向对象,所以它的扩展性变强了很多,所以后面的话,基本上它会越做越大的话,它就基本用Hal库了。

面向对象的本质

面向对象的本质就是以更高的视角去看待问题,把一个问题封装成一个由属性跟方法,两种两种东西组成的东西,这个问题他有一定有一定的属性,然后这个问题它有一定的方法在里面,把他分分成两种东西,俩一个属性的方法,用这两个方法封装一个问题,每个问题都有,都用这样的一种思考方式去看,这就是面向对象的一种本质,他用提高了一种认识,用另一种更高的一个层面去看待一个问题,把这个问题封装成属性和方法,两种层面的东西,然后对这个问题进行探讨跟封装。

在C加加里面,你去,他跟class跟struck有个不同的是,你定义了struck的时候,你后面你要去用struck的时候,你是你要先写上structure,然后你的那个名字再定义一个,你要的名字是这样定义的,然后class的话,他可以省略掉,就是你不就,你不用写class都可以,就是比如说你是struck的话,你是struck,什么什么什么等于什么什么是吧,然后class的话,你class那可以省略,直接什么什么等于什么什么什么。

然后你去在C加加里面,你给这个类进行实例化的时候,有两种方法,第一种方法就是你直接就是直接一个什么什么什么等于一个六,然后后面再写上它的那个类型,然后这个的话就是你直接就是直接一个什么什么什么什么分号,这个的话就是你就是把它定义在堆上的。

一个namespace里面是可以有多个class的

一个name space里面是可以有多个class的。

面向对象C++开发的三大文件

西加加的一个开发,主要是有三个文件,一个是头文件CPP,还有一个是那个妹妹文件,就是一个是si,一个是HPP,一个CPP,一个是面,然后一般是在HPP里面把你的这个模型给建出来,就是你把这个类给建出来,然后在CPP里面呢,把你这个类给填充好,就是风丰满你的那个类,把它变成一个对象。然后在面点C里面点CPP里面呢,你就把它的这个这个前面两个,一个是把类写好,一个是把对象创建好,根据类把对象创建好,然后填充好,把你做好的这个东西,再就在面点CPP里面就用了。就是说它有三个,有三大块东西,一个是H头HP,一个是这个类的CPP,一个是这个面的CPP。

然后就是在这APP里面把你类构建好,然后在这个类的CPP里面把你这个类,不然后就是在这个对象的CPP里面,不是类的CPP,而是对象的CP,然后在你这个对象的CPP里面,把你在类的,那里的的把你这个类给封封满,然后填充好成为一个对象,然后再在这个main的CPP里面,使用你前面这两个东西做做出来的那个对象,然后在main的CPP里面去使用,总的来说就是三块东西。

总结来说就是三块东西,一个是HPP,一个是对象的CPP,一个是man的CPP,就是这样子,就这三个东西,HPP对象的CPP man的CPP。

就是一个头文件加两个C文,CPP文件头文件写类,然后其中一个CPP去根据类去构建对象,然后另外一个CPP去使用这个类构建出来的对象去使用它。

构造函数与析构函数

构造函数和析构函数其实是一种回调函数(钩子函数)

关于析构函数跟构造函数,你在调用那个六的时候,他就背后C加加就帮你调用了这个构造函数,然后你在调用那个比例的时候,C加加就会帮你调用那个析构函数。

然后这里就涉及到一个回调函数的概念,回调函数就是说你写好了之后你不用管,然后到了某个条件之后,它就自动就会回调回那个函数,自动就会就会自动调用那个函数,它们是之间是相勾的,勾连的,你只要调用了,到了某个阶段它就会有,就触发回调函数,所以它自动的,所以你用六的时候,它就自动帮你调用构造函数,然后你用BD的时候,它就自动帮你调用析构函数。

所以你可以理解为构造函数跟虚构函数就是一种回调函数,也就是钩子函数。

你可以理解为构造函数和析构函数是写类的,那帮开发者写的,然后它是隐藏给你了,就是隐藏在背后的,你只要知道有这样一个机制就可以了,你写的时候其实不用管,你用六它自动就帮你了,所以它是背后的写类的,写库的帮帮你做好了。

C++自动帮你构造了构造函数和析构函数

他这个C加加里面,你用六,它会自动的帮你做一个构造函数,但是他并不会自动地帮你做一个西勾函数的吧,就是说它会自动帮你构造,但是不会自动帮你吸够,你到后面必须要用迪丽特,然后调用delete,然后把你那指针传给他,才能够进行虚构。然后,其实这个跟那个C语言malloc跟付费是一样的,你福利的话,它也是,你只要把它的指针的地址传给他妈,不是指针地址,就是你只要把它这个指针传给他,只要把指针变量传给他,他就可以帮你把那个指针变量指向的东西给释放掉,这个,那你其实很容易就可以联想到迪利特的也是,你只用传个指针给他嘛,是吧。

所以说delete后面加一个指针,然后就可以释放掉,就可以删除掉它的销,毁掉它的那个对象,销毁掉它那个对应的那个地方的内存,这是很正常的,你你因为你free的话也只是传一个指针嘛,你delete也是只是传一个指针嘛。

C++与C的一个区别,C++用动态内存比较多

C加加相对于CC的一个区别,就是它用动态内存的时候用的比较多,就是说他基本上用堆用了很多,为什么就很容易想得通呢?你看你如果是不用动态内存用站,那么你在一个站上拟定一个对象呢,对象里面又是有属性,又是有方法的,是不是很多内容一个对象里面有很多内容的,你要是在定义多几个对象,那站是不是被你撑爆了?所以C加加里面因为有对象,对象里面有属性,有方法,懂的东西是很大的,所以他必须得要放到堆里面去,所以他C加加里面动态内存就明显就比C多了很多。

普通构造函数

由于传参造成的写法细节

只要存在带参的,那么不带参的就不能省略

在写构造函数的时候,你只要有另一个是带参的构造函数,那么你那个不带参的构造函数就不能省略,就必须得写上,然后这里如果不写上的话,你在后面用的时候,你如果不传参的话,那你就用不了他那个不带参的构造函数,它就会报错。

后面用到不带参的方法时写的时候要去掉括号

这里还有一个细节,就是你后面用了不带不带参数的,那个就是后,后用到后面的不带参数的构造函数的时候呢,你是不能够说。加个括号,然后写分号的,这样的话,他会那个编辑器会认为你是这个函数,所以当你想要用默认不带参的时候,你不仅要把括号里面的东西去掉,还你也要把括号也去掉,这样的话操作那个编辑器才会认为你要用的是个默认的那个不带参数的构造函数。

所以你如果一旦用了定义了一个代餐的构造函数,那么你一定要那你,那么你那个不代餐的构造函数,就必须不能省略了,即使它里面是空的,什么都没有,你也要,你也不能省略,否则就用不了那个默认的不代餐的构造函数了。

这个也就是为什么你在看别人写的,累的时候,经常会看到他会构造一个默认的构造函数,就写上一个默认的构造函数,然后即使里面什么都没写,但是也要写上去。

不仅在构造的那个类里面有写一个默认的构造函数的声明,而且在定义里面的话,在另外一个CPP里面,你也要写一个什么都没有写的,一个没有带参数的构造函数,你必须得写一个什么都没写,里面是空的的一个构造函数,这就是这这个这个现象,别人别人写代码写写出这个现象来,就是因为这个规则决定的。

不在外面写了,把中括号写进类里作为内联函数

然后你见到更多的可能是他直接就不在那个CPP里面写他那个默认的构造函数了,因为它里面本来就是空括号嘛,所以他就你会见到他更多情况下,他不是这么写,他是直接在那个累呀,那个类里面的那个默认的空的构造函数的那个括号后面再加一个中括号,然后里面是空的,就是用个内联函数把它那个直接放到那个类里面去,然后用那个括号吗?括号里面个是空的,就是你会看到更多情况是这样子,而不是在另外一个APP里面重新写一个函数,然后写个中括号里面是什么都没有,是空,他不会,他直接把它集中到那个累连累,连到那个class类里面去,在类里面直接在后面加个中括号,里面什么都不写空。

为什么要去掉括号

就刚刚说了,你如果是在那个类里面啊,然后你如果要用一个空的本部是在类里面,是在主函数幂函数里面,你要用一个空的构造函数的话,不是他你不能留括号的,你就算不船舱,就算没有没有任何的船舱的一个构造函数,就是它的里面也不能够,你也要把括号去掉,因为你一旦加括号,它就很可能他就就这就是个函数,但是本质上你你,你这个这个构造函数本身不是个函数啊,他只是一个对象是吧,所以你不能够把那个括号加上去。

这里其实挺扯的,其实没有任何道理可言,你其实你你留个括号在那里是空的,也是对的呀,但是没办法,这是私家里面是这样规定的,不能有那括号。

这个其实也不算太扯,就是有道理可言,就是C跟C加加,它在很远古的时候,他当他出现个空括号的时候,你会发现他就是在调用函数,它就不是在定义,不是在什么声明什么的,就是在调用函数了,你会发现那些用函数的,你但凡只要定义一个空括号,那么就是在调用函数,所以其实也不算太扯,所以C加加C里面它远古有这么个规定,他没办法,所以你必须要赢,避免歧义,那你就不能写空括号,你把括号那个东西去掉,不要括号。

就是说你如果是空括号,那C加加C在远古时候他就以为你是在在,他在以为你是在调用函数了,注重注意一点是以为你是在调用函数调用函数了。

所以为了避免引起歧义,就是不用把,就算里面是空的,那你也要把括号去掉,然后这样的话,你才能够是定义一个函数,定义一个创建个对象。

刚刚强调这点是注重,它是它是调用函数。为什么要谈到这里?因为你现在要做的功功能不是在调用函数,你现在是类的初始化,你现在是对这构造函数,因为构造函数它有两步,第一步是申请动态内存,第二步是初始化。这里我们要做的步骤是初始化,所以没有调用函数,所以它为了引起,避免引起歧义,所以就是这样的。

C++成员初始化列表

西家家里面,它会在函数的后面加个冒号,然后写上一个东西,就是它其实是一个简单说,就是把它这个函数里面的内那个一行移到了函数名上面去。为什么要这样一呢?原因就是你函数里面那一行是初始化来的,比如说你是使他的某一个东西等于某个东西吗?函数里面那个东西,但是你使它等于某个东西,那个东西是直接是直接邮你。这函数里面的传参,传这些东西决定的,就说他这个传参传进来这个东西直接就是用到你这个初始化里面的这个东西,初始化给了他,那么C加加他设计时候就会这么想,既然你这一个初始化,直接用的就是我传参的东西了,那我为什么不直接把你这个初始化是吧,提到那个函数名后面去呢,这样的话就就整洁一点,直接把你提拿出来放到函数名上。

所以就是就出现了把他那个函数里面的内内行初始化的,直接由它传参传的东西去初始化那个东西,然后一道函数上面加个冒号,然后冒号后面写上你的那个,你像你函数里面本身写那一行代码的左值,然后再写个括号,括号里面就是写你的那个函数里面本身的代码的又直,然后这个又值呢,一般是跟你传的参数是有关系的。你可能传的参数是个指针变量,然后这个你最后用的这个右值是一个取星号,然后用指针变量就取里面的值,也有可能是直接你传的参数是这个变量,然后你取的右值就是直接用的这个变量没有指针取值,反正就是他传的这个参数是跟他的这个,反正就他这个右值是跟他传的这个参数直接决定的,所以他才敢于把它放到函数第一行去把它提出来。

把它从函数里面提出来,放到函数名上面去,用个冒号后面写上一个那个左值加括号里面写右值,右值里面写的跟参传的参数那个是95%相关的,要么就是它本身,要么就是取其他的地址啊,要么就是取星号取她取她这个地址里面的值。

然后然后说那个如果后面加一个冒号嘛,然后把它那个函数里面提出来,提到函数头上去,如果你是有多个多个传参,然后要把多个提上去的话,那你就是在冒号后面嘛,你写了一个之后嘛,然后那你写个逗号,再继续写写,完那个之后再写个逗号,继续写,就在冒号后面,可以写很多了,用逗号分隔就行了。

这个加冒号是摄影,然那写函数起的时候加,然后你在那个类里面声明这个函数的时候,没有必要加那种放冒号后面的东西就没有必要加冒号了,只有在你在另外一个CCPP文件里面去写,这函数起的时候才要加后面冒号。

这个东西它有个名字叫做成员初始化列表。

构造函数参数带默认值的情况

关于你的构造函数里面的参数带默认值的情况有三点要说第一点,就是说你的这个构造函数参数带默认值的情况呢,你的参数带默认值是在你的命函数里面啊,不是在幂函数里面,是在你的那个类里面就写类的那个头文件里面。声明的时候,里面加上你的参数的带默认值的那个东西就行了,但是你在另外一个定义这个构造函数的时候,你不用在那个定义的那个函数体在里面写上参数的默认值,你只需要在那个类的那里声明的时候把你参数默认值加进去。

第二点就是这个参数带默认值的话,它是有歧义的,这很容易引起歧义的,就是说你如果参数带默认值,然后你另外一个没有带默认值,那么你举个例子,就比如说你如果想要用参数,没有带默认值的,你把那个你在用的时候呢,你把他那个括号去掉吗?加个分号吗?那样的话他就会引起歧义,编译器就会报错,他不知道你是要用的是参数带默认值的那个,还是用的是你的默认构造函数,所以这个是有歧义的,所以一般是很少用的参数带默认值的情况的。

参数带默认值的好处

然后第三点就是参数带默认值,它其实也是有很大的好处的,这个好处就是它可以一个函数可以顶你多个构造函数,一个构造函数顶你多个构造函数,就比如说你如果用了参数带默认值的,比如说你这个那个函数带了三个参数的,三个参数都给他带了默认值,然后你这样的话,在这个前提下,你可以把前面你的默认构造函数和你只传两个参数的构造函数和你都可以注释掉了,用一个函数可以代替前面两个,为什么呢?

就比如说你用了这个带了三个参数的默认,而且带默认参数的构造函数,你在后面,你在用它的时候,你如果不传参,你如果不传参的话。

你如果不传仓的话,他匹配的时候就会认为你的这三个参数都没有匹配到,那么他就会用默认的构造函数嘛,然后默认的构造函数用的就默认的三个参数。

所以你就不用写一个空括号,你就不用在类里面写一个空括号,然后构造函数了,也不用再定义函数的时候定义一个空括号的构造函数了。

但是你定义时候你一般也不用定义构造空,你不用定义默认构造函数的,你在那类里面的时候,你在构造函数后面加个空括号就行了,那一行同一行加空括号就行了。简单说就是它可以顶替掉那个,那个不用写。

然后再比如说你如果是只有一只写一个参数,你只传一个参数进去,那他就会拿这个参数跟你那个去匹配嘛,然后他发现匹配到了一个你,你匹配到的,但是另外那个没有匹配到,那么他就认为你就只传了一个参数,另外两个就是用你默认传的那个,所以这就这就可以代替掉你只传一个参数的那个,然后,比如再比如说你传你传两个参数啊,他也会拿去匹配,然后发现那两个匹配到了,但是另外一个呢。你没有匹配到,那你就用你的那个默认的构造函数,你用了第三个参数里面的默认的那个参构造,你传的参数默认的值,那么这样的话,他也可以匹配到两个传两个参数的,这样的话你就不用写前面三个传零个参数,传两传一个参数传两个参数的,你直接用一个默认函数默认够,你直接用一个参数函数里面参数带默认值的,那么就可以顶替到前面的多个构造函数。

理解的关键:匹配机制

这里一个最关键的点就是匹配机制,你你用一个参数一个函数里面的参数带默认值的话,你前面东西你你少一个的话,他就匹配不到,匹配到的话他就用,他就用带默认的那个就就应该就关键是一个匹配,所以导致了它可以兼容前面的所有的带两个带一个带两个参数的,你如果带两个,那你少了一个,那你就默认了,如果带一个,那里少了两个,那你就默认了,是吧,如果用两个,那里少了一个,那就带默认的,如果用一个,那就少了三个带三个默认的,所以他去把前面三个全部都顶替掉了,用一个就可以了。这核心的核心就是在于匹配,然后核心就是在于它可以,这个好处就是它可以顶替。

所以你这就是高手写C加加的时候的用法,用一个可以顶替到前面三个,可以顶前面三个。

C++定义对象的两种方法

浠家家里面定义对象有两种方法,一种是直接定义嘛,就之前说的就是直接初始化定义,同时初始化不是定义对象。我跟他说西加加初始化对象的时候有两种方法,第一种是直接初始化吗?之前说的,你在写括号,括号里面把你参数写进去,如果没有参数,那就不用括号,直接分号,然后第二种方式就是用你定义好的对象去初始化。你新定义的对象,然后就是就是比如说你定义好了一个person,一就你定义好了一个P1的对象,而你现在要初始化一个P2的对象,那你然后这个两个对象都是属于person类的,那你就是 personp2,括号P1,然后它也可以写成 personp2等于P1。

也就是说第二种方法,用你已经定义好的一个对象去初始化,另一个对象,有两种表达方法,一个是 pythonp2括号P1,另外一个是 personp2等于P1,第二种他其实是为了C加加概括一点,就是第二种呢, personp2等于P是C加加,为了营造出一种极力的去营造出一种对称感,给你设计出来了,因为C里面的话,它就是in te be等于a吗?它就可以用这个已经定义好的一个初定类型去初始化的一个类型嘛,所以就是就是。

极力营造对称感

就是C里面,它本身int b等于a就就int a等于四,然后int b等于a,那么,A就是可以用于定义好的一个a去初始化B嘛,所以C加加里面就是为了极力营造这种对称感,为了让你觉得从C过渡到C加加是合理的,很顺畅的,所以它就它就多了一个这样的一个初始化对象的一个写法,就 personp2等于P1。

有一点就是刚刚说了,他从未让你从C过渡到C加加,然后很顺畅,这里有一种对称感,还有一种对称感,就是说C加加和C原生的类型就可以这么使用,然后就是可以用in te a,类似比特币的a ma c和C加加原生的类型,就可以这么使用。那么你自己自定义的一个类,那也等于是各类型啊,你自定义的一个东西也可以这么使用,那就说明就是那就很对称吗。原生的是这么使用你定义的,也是可以这么使用,你无非就是你无非就用了你自己定义的一个类型嘛,自己定义的类嘛,所以这么写的话,就一个非常完美的一种对称美,原生的是这么用的,你自己定义的也是这么用,凡是定义出来,凡是一个这么类型,就是这么用,所以这是第二种对称感。

所以总的来说,它有两种对称感,来的两种对称感,使得这这个企业家是这么设计,这么设计就特别好。

我感觉这里还跟si过渡到C加加的一个顺畅程度没有关系,这个并不是对称感,所以他只有一种对称感,就是说他原生的类型是支持这样的,那么你自己自定义的类型也是支持的,这就是对称感对称的,而不是说从C过渡到C加加,因为C加加里面跟C是一样的嘛,是吧,大家都可以用的,A等于四,然后inTo B等于a吗?这C跟C加加都是一样的,就是问题的。重点不在这里,重点不在于从C过渡到C加加很顺畅,这里C加加设计这个语言并不是有设计,这种方式,并不是主要的出发点不是这个,这个就不是说出发点不是这个,是这个根本就不是这个问题的焦点,问题的焦点是什么?就是说他这么搞就是为了你原生的数据类型是这么用的,你自己自定义的数据类型也是这么用的,这就有一个很完美的对称美。

总结就是只有一点,就是它为了让你自己定义出来的类跟和C加加原本就就原生的那些类的用法变得完全一样,这样就有一种对称呗,总的出发点总结就是这一点,所以它这么设计,所以可以 personp2等于P1。

总结就是一点,让你自己定义的类跟原生的类的用法变得一样,那么你就会变得顺畅,那么你的想法就会顺畅,然后就会这个设计就变得对称,就很舒服。这就是最关键的一点,让你自己定义的类跟原生的类型一样的用法,这就是一种对称。

拷贝构造函数

首先要注意的一点:你不写就代表用C++默认的

首先有一点你要注意,就是无论是构造函数,还是拷贝函数,拷贝构造函数,它的其实是在它内部的,他有一个类已经帮你自动写好了,你没看到而已,他你没有看到而已。对,然后那么为什么朱老师写的那些默认构造函数可以用的上呢?这就是涉及到一个函数存在吗?这是涉及到一个存在的问题,就是。你又写了一个拷贝函数,不,你又写了一个构造函数和拷贝构造函数,那么还有你的传参是那么传,那么C加加就就用你的了,他就不用他的了,就是这么简单而已。如果你不写,其实C加加就用他自己的。

但是他自己的那个呢,你是看不到的。

然后这个拷贝构造函数,先说一点就是零散的,就是它,首先它在运算,嗯,它是不支持重载的,为什么?因为这拷贝构造函数,它的一个用法就是拷贝吗?所以它的参数列表传的餐永远都是那么那么那么些参数,所以它就不存在存在,你不可能再传别的参数给他,这是第一点。第二点就是这个拷贝构造函数,尤其适合用那个初始化列表去写,因为你拷贝吗,你就把他的东西拷贝进去而已,那么你用初始化列表写就很方便,写在冒号后面。

所以他这里就有一个,他总的来说就是无论如何都是有两部,就是一个首先要申请个内存地址,然后内存地址,然后把纸放进去,只不过,宁可B的a是把a的值复制拷贝到那个那个逼里面去,这个的话是,如果你是直接印的a等于四呢,那就是没有两步,他就是直接一步,就是你直接把那个再申请一个int类型的内存。然后地址是放给a,申请了一个值给a,然后你把四写进去,然后呢,Int b的a就是两步,首先你申请了一个内存,这个是硬的类型的内存,然后放给B,然后呢,把那个a的值复制,它多了这一步,把a的值复制到那个B的地址里面去。

如果你是对象类跟对象的那个初始化,它就跟C加加原生的那些硬核是完全不一样的,最不一样的一点就是最不要脸,注意最不一样的就是它有一个构造函数的概念嘛,就你用你用类跟对象去做的话,他首先前面第一步,也是跟你的这个是一样的,跟原生的是一样的,你就是你比如说你用一个创建各类然后。初始化它,它前面也是也是帮你申请在向内向地址里面去申请个内存嘛,这是一样的,但是不一样的是你初始化的时候,它是用了,它是就是本质上申请内存的内那那一步骤,大家都是一样的,原生跟初跟对象类都是一样的,不不一样的点就集中在后面初始化那里。

就是说你原生的话,它是不用构造函数去初始化的,但是你如果是用类对象的话,那么你就要用构造函数去初始化,这就是不一样的点,简单说就是这样子啊,不一样的就是它多一个构造函数,它要它是用构造函数然后去初始化的,但前面申请内存那里大家都是一样的。

所以这里就引申出来了,你ink b等于a,那是他那个C加加是可以帮你实现的,那你如果是对象用另一个对象去定义另一个对象去初始化,另一个对象,那你这里就涉及到一个构造函数的问题了,是吧,你都没有另一个对象的构造函数啊,是吧,那你怎么去构造是不是?比如说你一个 python python pp2等于P1是吧,那你这里不可能是跟原生那么简单,原生那里的话就是一个复制是吧,但是你这里的话,你你这里的话类对象呢,涉及到一个构造函数是吧,那你怎么构造呢?是吧,所以这里不可能就说你还调用之前的构造函数,因为你之前的构造函数是是做别的呀。

所以当你用那个科森P2等于P1的时候,他用的就不是之前的构造函数,它si加家里面他就用内部,他偷偷的给你写了一个拷贝构造函数,当你用科森P2等于P1的时候,他就是用的是拷贝构造函数,它不是用了之前的构造函数,然后这拷贝构造函数呢,是把你之前的PPT里面的东西的属性全部拷贝起来,拷贝到哪里去做一个拷贝,构造函数就是这样子。

然后拷贝构造函数,里面就传,参传就是你批的那个参数,把你那个原原本的构造函数的那个地址传进去,他用了引用来吧,它这里用了引用就控死他,然后一个什么什么破损,然后一个取址符这,但是这里不是曲子意思啊,注意就那个end服的end服,然后加个PE,这个传进去,他用那个引用就一就一直一辈子绑定那种的引用,把它传进去,传进去之后你就你就可以用他那个东西去拷贝。然后他一般是写用初始化列表去实现的,而不是写在函数体里面的,比如说冒号,然后什么name啊,然后p1.name,然后呢,然后agep1.h name括号,p1.name H,括号p1.h,然后尔括号p1.fair就是这样实现的,所以它就是实现了拷贝构造函数去实现了对象,用另一个用一个对象去初始化另一个对象,它有一个拷贝构造函数在里面。

然后同样就是刚刚我说的C加加里面内部就有了,你你不自己不写,然后他就用他就用他的,你写他用你的。

深拷贝与浅拷贝

关于这个深拷贝与浅拷贝,那我们现在主要是要分析一下浅拷贝的不不足之处。

首先要对一个代码的框架有一个整体的理解,首先它会在一个文件里面定义好它,这个类,然后,再然后他在另外一个文件里面去,对这个类里面的方法进行一个写,然后。

然后这里你关键要领悟的一点就是他在写方法的时候,他有一个意思吗?这意思是之前讲过是指向未来的对象,所以他这里虽然没有填充,但是他用了未来的对象去进行一个运用。

第一个段错误

然后这里朱老师在课里面第一次会触发一个段错误,这个段错误是怎么来的呢?

这个段错误从本质上来讲,就是那个朱老师,他在用那个拷贝构造函数的时候,他因为他讲课的时候,他把那个那个指针的那个东西引入进去吗?就是把动态分配内存的那个,就在你的类里面,之前注释掉了那个硬的新批,把它给取消注释,就引入了动态内存吗?然后然后你那个段错误是第一个段错误是什么呢?就是你你把朱老师他把那个。注释掉了之后,把那注释去掉之后,然后在那个拷贝构造函数里面,他忘记把这个东西加进去了,就忘记在那个成员列表里面,把这个P1的那个点,然后硬着新P把万一把这个东西也加进去了,所以这就会导致什么呢。

所以这就会导致什么呢?这个就会导致你在未来你如果用到这个拷贝构造函数的时候,然后你未来有创建对象的时候嘛,然后你没有没有这个这个成员在里面,那么你最后你虽然是拷贝,拷贝完之后,他是他也会找不到,这个就是他是个野子针来的。

这里是关键点,就是就是这里有个什么关键点,就是它的这个,它的这个嗯,构造函数,构造函数,如果你是加入了那个指针变量的嘛,那么它的构造函数的方法里面,他就他是他是在那里给他这个这个指针分配分配内存的,就他不是在类里面分配内存的,就是分配内他的,你在类里面只是一个硬核新批。但是这批还没有给他分配内存,你是在这个方法里面,然后用那个未来的对象历史,然后给他这个批也分配内存,所以他在构造函数那里,他已经给那个,给那个分配内存了,但是你在拷拷贝,拷贝构造函数那里,你并没有给他分配内存。

所以当你用拷贝构造函数的时候,你就会发现你的那个指针是怎么说,你那个指针就是野指针,你如果没有用那。

简单地说,这里还有一个问题,就是人家系统里面,人家系统里面自己默认了一个拷贝构造函数,然后你如果是在你的本身的构造函数里面写了这个病的新批的话,那西加加系统里面自己默认的那个拷贝构造函数,它就会帮你,也会帮你写上一个硬的新批就是这样子,但是你在那个只有这样,因为只有这样子,你才能够跟那个考,跟那个构造函数是拷贝起来的嘛,这样的话你构造函数里面你做了一个。做了一个在方法里面给这个指针是分配内存的时候,你拷贝函数里面用的还是你的那个构造函数里面那个指针的地址,所以它本质上指向是同个地址,所以都已经分配了内存,所以我们写构造函,我们写拷贝构造函数的时候,朱老师那个段数就是他没有把那个补上去,所以他就跟默认的那个拷贝构构造函数有区别。

所以他没有写进去,没有补进去的话,那么你在用那拷贝构造函数的时候,C加加就会,本身你如果正常用它,C加加系统给你提供了拷贝构造函数的话,默认的拷贝构造函数的话,它它就会有一个指针嘛,你这里是没有指针了,你这里自己写,你自己写漏了,然后加加,就是又是用你的,那最后的话,他就会发现哎,他这个拷贝构造函数跟这个构造函数不一样,不一样的话会导致什么后果。就是他本身以为你是有了,本身你以为你是有这个,这个,这个指针在里面的,但是你的指针呢,并没有跟那个构造函数联系在一起,所以呢,他就找不到了,就你,你就找不到你,那个那拷贝构造函数里面的那个指针的地址,那它就是一个野指针了,就你未来的对象并没有指向它,那就野指针了,未来对象并没有分配内存。

你未来的对象的那个指针并没有分配内存,所以就不就野指针了。

所以这本质上就是你自己写的默认的拷贝构造函数,就你自己写的拷贝构造函数跟其他系统提供的默认拷贝构造函数,少了一步,你没有把你的那个定义出来,指针跟你那个构造函数绑定,所以你就应该在后面,在城参数成员列表里面再写一个那个指针,就是,就是,就是定一个一的新推P2嘛,然后等于那个PE里面的那个指针,这样的话,你的拷贝构造函数里面,他的那个指针就是绑定,在你那个构造函数里面的,指向是同一个东西。

这样的话,朱老师的第一个段错误就就解决了,但是这里的话只是解决第一个段错误,你发现这样做之后还是会出触发段错误,这个才是问题的关键,这第二个段错误才是问题的关键。

第二个段错误

因为你这里你想你很容易想到这里很容易就出现问题了,为什么?因为你的构造函数跟拷贝构造函数,你的最后的那个指针剑指象是同一个地址,那你这不是等于同一个东西的嘛,这肯定不对是吧?你拷贝构造函数本身就是给另外一个对象给找地址,那你另外一个对象跟你之前的对象的地址是一样的,那不是乱套吗?就就不对嘛,是吧,所以他肯定会报错的,那他具体报错题,具体段错误体现在哪里呢?就是在后面的,那肯定会有一个地方出现错误,就在这里出现错误了。

在哪里出血错误呢?就是在最后,因为你这个你用了动态内存吗?你最后肯定是要给力的去删除他的,释放他的,那你呢?有两个,现在有两个对象了,一个是P1P2,然后你用的是同一个指向是同一个地址是吧,然后你总总结来说,就是你P1P2用是同一个地址,然后你你PP2个字都要释放一次,是不是因为大家都要各自都要释放,各自要执行是自己的虚构函数吗?各自都要释放一次内存。那你P1的时候,你先是执行完P1P1都吸购完了,P1释放内存了是吧,那他那个内内存地址就已经消失掉了就没了,已经释放掉了,那P2到P2,然后P2他执行完他要去虚构了,你会发现他P2跟P12个对象的指向内存地址是一样的,那你P1都已经释放掉了,那P2哪里了?还有内存地址,所以之后也也只这时候就出现触发断错误了。

这时候P2他你delete的时候,它本身就已经那个地址就已经被delete delete掉了,那你还delete那,那不是已经找不到那个地址吗?那就已经是,那就肯定是出错了就断错误嘛。

这里的话就是有一个小小一个小的注意点,就是你在用一个之前的对象去初始化一个新的对象的时候,它的构造函数是有区别的,各自都要有各自的构造函数,然后就是你有一个构造函数,然后有个拷贝构造函数,但是呢,大家的虚构函数用的都是同一个哦,所以问题出问题的节点就在这里,你用的都是同一个析构函数,你你如果,如果你在拷贝构造构造的时候。你你用的是一个分配,同一个内存地址,那你用了又是同一个析,同一个析构函数,那你前面的析构掉了,那你后面的话用的是同个地址,你后面再用P2的时候,你析构又没地没东西析构了,你前面第一个已经析构掉了是吧?所以问题症结在这里,大家的构造函数是不一样的,之前用的是构造,然后后面你新创建的是用的是拷贝构造函数,但是呢,大家的两个的之前的跟。

但是新的对象跟旧的对象,他们用了虚构函数,都是同一个析构函数来的,所以就是问题出在这里。

所以总结就是这里这个东西就是前拷贝的一个不足之处呢,前拷贝的话,你一旦引入动态分配内存,就引入那个申请堆内存,就是就是一旦一旦引入这个,你需要六根迪利特,需要需要申请的释放内存的时候,你会发现前拷贝他出大问题了,就是前面我刚刚说了一连串的这些东西就会总结出来,就是你用钱拷贝就会出大问题,所以这里就是前拷贝的不足之处。所以后面如果当我们要用到动态分配内存的时候,就要在堆上面申请,需要申请释放的时候,那么就要深拷贝。

那个申请内存啊,就是你把类里面的东西去申请内存,它这个申请的内存是在构造函数里面去申请,就它那个印的新批等于一个六,然后它这个六它是在那个那个构造函数里面写出来。

就他分配内存,它是在它构造函数里面去溜的。

你刚刚说了什么,他跟原生的就对象创建对象跟原生的那个类型不一样,有两部两有两个不一样呢,刚刚那个是说错了,其实从第一部那里就就不一样了,第一步的话,那个对象他要申请内存,要在堆申请内存,他是在那个六要用六申请内存吗?他你要手动申请的,跟那个硬核那些原生的完全不一样,内存申请内存那边就不一样了,然后那个申请内存六,它一般是写在构造函数里面的。

其实前拷贝的这种方法也是可以解决的,就是你总结一点,就是你不要把你那个指针的有关的初始化放到你的成员成员列表,初始化成员列表里面儿是你放回到那个拷贝函数的那个拷贝,构造函数的函数体里面去,然后在函数体里面去重新再溜一次,溜一次之后就是比如说六,然后就拼车,然后遛遛一个就是。另一个F,然后int里面,你再传,传你的那个,你要拷贝之前,你不是要拷贝吗,你之前原本的构造函数里面的那个指针,然后取到它原本那构造指针,然后加个星号,取里面的值,然后这样的话,你就把你拷贝里面的那个值,然后传给了那个,那个这样就把你原本构造函数里面的值传给了拷贝函数里面去了。

而且在这个基础上,他是他是重新申请了另外一个内存在,然后再把你的拷贝那个值复制到把你拷贝的值复制到你这个新申请的内存里面去了,就说你在那个构造拷贝构造函数里面,你定义指针,你定义一个六,你你在你重新再拷贝构造函数里面定义个六,然后不要在成员,不要在他的成员参数列表里面初始化成员列表里面完全,你用他那个之前的构造函数的地址吗?你重新录了一段,你录了一段地址。那么这里就会有两段地址了,然后你要,你如果硬要说,你硬要想要跟之前的构造函数一样,其实也不是硬要,你必须得一样,因为它是拷贝构造,函数拷贝一模一样的嘛,所以你到时候你把你拷贝的你之前构造函数里面,原来说实话的值赋,就是把它给赋值到那个,你新新得到那个拷拷贝构造函数的那个地址里面的那个值,把它放到那个地址里面去。

就有两个地址啊,你到时候这就没完全没有问题了,这样的,这样的一个前拷贝是完全没有问题的。这样的话,你最后就算析构函数析构,大家都是调用的同样的虚构函数,但是因为你的拷贝构造函数和你原本的构造函数,它的申请指针的时候的地址完全是不一样的,两个都溜了,就是有两个六在里面的,你申请了两个内存,所以你的构造函数你吸够的时候,那你西勾掉了,然后你拷贝构造函数析构的时候,因为你吸够了地址,不是之前你构造函数的那个地址,而是你自己在函数里面录了一段地址。所以他西购也是有地址去给你西购的,所以这样的话就不会触发断错误了。西购也是可以西购两次的,因为这因为你构造函数和拷贝构造函数,你们的两个之间你都有六类次,大家都六类次,大家都有两个地址可以用,就大家都各自有一个地址可以用,所以这样的话就可以解决问题,所以前拷贝它也是可以解决这个问题的,但是可能解决的不够优雅。

刚说错了,其实这种把这个,就你在那个拷贝构造函数里面,你又重新溜了一下,又溜了一段东西,而不是直接把他那个考,把它构造函数,把它直直接用,拿来用,不是直接在那个初始化成员列表里面用试纸,而是重新录了一段内存在你那个拷贝构造函数里面,这种方法其实不叫钱拷贝了,这种方法其实就是深拷贝了,所以解决问题前拷贝是解决不了的,深拷贝才能解决这个问题。刚刚说了解决了这个问题,这个并不是钱拷贝,这是这个本身就是深拷贝了,刚刚说那个是深拷贝来的。

用这种声拷贝的话,就你不能够去用C加加它本身给你提供的拷贝构造函数,因为C加加它本身给你提供了拷贝构造函数,它默认就是浅拷贝,所以你要用声拷贝,你自己要去自己写的,就是像刚刚那样子自己写,这个是C加加没有提供的声拷贝,只能只能自己写的。

深拷贝与浅拷贝的命名来由

为什么叫声拷贝前拷贝这么拷贝这么一说,就是声拷贝,它是不单单是给指针变量分配的内存声拷贝,它是给指针变量指向的值也分配的。内存深拷贝显拷贝就这么来的,就这么是从这个原因输出发去命名。

然后一般情况下,你作为一个拷贝函数嘛,你肯定也要拷贝,所以你升拷贝完之后,你还是要取你原来你要拷贝的那个,那个东西它的值,那么这个时候你就取值,就取一个它的星号,取之前的你要拷贝的那个东西里面的那个指针里面的值嘛,就取星号,把它这个里面值放到你这个升拷贝里面的那个指针变量,你申请的这个新的地址,那个地址里面,内存把它放到那里面去。

在这个函数体里面这么写,P ent等于六,然后一个in te,然后括号,然后新批恩典拼车,这个PN呢,就是你之前你之前的那个,你要扣构造的构,你的之前的构造函数,你用里面的指针拼车取纸,纸它里面只取它里面的值,然后放到你这个新申请的这段地址内存的里面去,就是要拷贝了。

如果你拷贝你升,拷贝你想要,你想要在在这基础上,你要完美的再复制,那你就做我刚刚说的那一部分内容。但这一步的话,你不做也可以,只是你如果想完美的复现,那你就做。

他的这个声拷贝必须得要程序员自己写,所以从这基础上你也可以推断出来,一般你如果写了这个手动程序员写的拷贝函数了,基本上就是声拷贝了。如果你不是声拷贝,那你程序员为什么不用ci加,他本身提供的呢?只用显拷贝是吧?Ci加他默认提供显拷贝,所以一旦程序员他在代码里面写了声拷贝了,人家代码,里面写了自己手动写了一个拷贝,构造函数绝对就是声拷贝了,不然没必要写。

类的权限管控

类的内部和类的外部的理解

这里有个概念,你首先要理解什么是类的内部和类的,外部类的外部它其实就是外部吗?就是除了类的内部之外的东西。那么什么是类的内部呢?你的类的内部,你不要以为你的方法写在类的外面,那么它就是类的外部了这不对,就是你类里面的方法的那个声明,然后你声明的那个函数,它也是属于类的,也是属于类的。内部的就说你的方法也是你在类里面写的方法,它虽然在外面写,他是写在外面的,函数体是写外面的,但是它也是属于类的内部的。

为什么是这样的,因为比如说你定一个类,这个类叫person,然后你在外面写它这个类里面的方法的时候,你是不是用了person,然后两个冒号再写这个方法,那它就是在这个类类的里面的,因为你是用了person,然后冒号,冒号方那个类,那个那个方法的嘛,所以它肯定就是在类的内部的,只不过你这写的外面而已,它本质上它是属于类的内部的东西。

你会发现就是你这个类里面,比如说你这里面有两三个方法是不是,然后你这两三个方法你写在外面了,然后这两三个方法之间互相调用,你是可以直接用函数名的,不用什么什么的,什么其他乱七八糟的,比如说你在比如说你类里面有方法一,方法二,方法三,然后你方法一,方法二,方法三在外面定义的吗?你如果要在外面定义的时候,你在那个你的方法一里面调用了方法二,或者说你的方法二里面调用了方法一。你直接在方法一里面用方法二的函数就行了,为什么这样是可以的呢?其实这个很简单,很容易理解的,为什么呢?你想想看,你在方法里面是不是可以随便调用那个你的类里面的属性,它本质上你调用类里的属性,你是本质上就是用了this,然后一个箭头,然后加你类里面的属性去用是不是。

所以所以这就很正常,你可以推导,那你类里面的方法,你是不是也是一样的,也是可以用个this,然后加个箭头,然后去调用这个方法是不是就,所以你在方法里面,你去用你的类里面的其他方法也是可以用,肯定是可以用的,它本质上也是用this,然后加个箭头,然后那个方法调用的函数嘛,只不过它this省略掉吗嘛,可以直接所以它就可以直接这样用嘛,是不是这很容易理解的。

public和private

这个private它这个权限是对内完全不设防,对外完全设防,怎么说呢,就是你在,就是你如果是在方法里面,你在你的那个,就比如说你private了一个,首先就是从先是看成员变量吧,你如果是在,你如果把这个成员变量给private掉了,那么你你在外面再累以外,你是不能用它了,但是在类以内的东西,比如说你用了那个方法,你用了一个类的方法,然后那个方法里面用到了他,那么是可以的,然后你在外面再用这个方法,那是可以的。

你在是从方法成员,方法类的成员方法角度去看,如果你把这个成员方法,比如说这个方法一,你把他pass掉了,那么你在累以外是用不到,用不了他是不能用他的,但是呢,你如果这个private的这个方法一拿,他被方法二调用了,然后方法二呢,他不是在private的里面的,他是在public外面的,那就那么你在外面,你在类以外的东西去调用方法,那么其实就可以调用方法一。

总的来说就是你如果把一个东西private掉了,那么这个东西只能在类里面的东西去用了,比如说,你把那个一个成员变量派别的把一个成员方法拍比掉了,那么你这个pad的成员的成员方法呢,只能在你类里面的方法里面去调用它了,只能在你类里面的那个方法去里面去,只能在你类里面public那里的方法啊,也不一定是public的里面的方法,反正就是只能是在类里面的那个方法去调用它这个成员变量和成员函数,然后再用,然后你如果要要外面,你如果要用那个private,那个如果要在外面用pa

你只能是这样,间接的在外面调用这个public里面的那个方法,然后那里那个public里面的方法调用了private里面的方法,这样才能够间接的调用这个private里面的方法跟成员总结一下,就是你如果把一个成员变量和成员卡放到类里面了,把它放到private里面了,那么你类以外的东西,你就别想去访问他了,你只能够你类以外的东西,你只能访问public的东西,而你如果把你的这个类里面的private的那些东西,通过public的方法去调用它的,然后你再通过类以外的东西去调用这个public的这个方法,那么你就可以间接去调用private里面的方法的成员。

protected

然后还有一个就是protect protect就是介于public跟private之间的,Private是管的最严的,Public是管的最松的,Protect介于这两者之间,然后其实介于这两者之间呢,不不一定只有price,不一定只有protect,可能还有其他两三个,但是一般情况下不会超过三四个,然后这个public跟private跟protected,这三个一般是很多语言都支持的一个访问权限的的一个关键字,然后可能中间呢,除了protect还有其他的可能,但是其他原因可能还有其他的,或者说还有其他的一些东西,介于中间的这些嘛,但是一般不会超过,一般不会超过三四个。

对,这个访问权限啊,它跟cost是一样的,它是语法层面上的一个保护,就是说,它硬件上面并没有设置这样的一个权限的,只是它编译器你设计的编译器语法上面帮你做了一个这样的一个保护,就你编译的时候都不通过,但是你如果强改是可以改,只不过编译器那里立了块警告牌给你,就跟cost是一样的,是语法层面的保护,你硬要去打开它,一定要去用它是可以。

权限访问的好处

这个权限访问权限,它带来的灵活性,还有安全性,就我们为什么说灵活呢?因为你之前只能用cos的去实现,只读是吧,你不能实现止血,那你这个时候你用那个访问权限,你就可以实现只读跟止血,就你把你要那个,比如说拿age举例,你把age这个变量放到private里面去,但是你把使用H的那个方法放到public外面去,然后你使用age方法分成两个,一个是读H一个是写a。写ag很简单,你写ag那个函数参数里面,你放放个参数传进去,传进去,然后写个函数体,函数体里面写的只那个this,只这样的age,然后等于什么?然后要等于,那就你传,传参,参传的那个参数就可以直接只写,只读的话,你写个函数体如一看就成了。所以就是你把age这个本身放到了private里那里去,然后。

然后这个使用APP的方法放到public外面去,那么你就可以实现只读只写,那么你如果一旦不想让用户实现只读或只写,那你就把这个方法给去掉就行了。H本身是锁住了,用户是用不了,但是你你可以给用户提供一个使用H的一个只读的一个方法,还有一个止血的方法读取,可以给H提供一个读取方法,一个写入的方法,那么你如果只提供写入方法,不提供读取方法,那你就只读。啊,那你就只写,如果你只提供只写入的话,不提供只你,如果你只提供只图方法,不提供只写的方法,那你就,那你就只写是吧,总的意思是说,他把这个里面age放到了private里面去,把使用它的方法放到public里去,这样的话,你就一般都是用public里面的方法去调用就行了,而不用直接调用它的这个变量,直接用你调用这变量的方法去用。

这样既灵活又安全,你只用给用户提供方法,方法是public,这里这这种这种用法在c shop里面是非常常见的。

访问权限的思想对架构师尤为重要

访问权限的思想在架构师里面尤为凸显,就架构师的话,一般就是帮你把架构假好,假好骂,他就会帮你设置一些访问,他就着重要考虑访问权限的问题,就是什么层级的人该了解什么什么,乘机人他有权限访问什么,把架构一层一层里好,然后给你们这些不同层级的去写代码,然后其实这是第一点。第二点是C加加的这个访问权限,包括所有高级语言的访问权限。他是以牺牲的效率为代价的,所以为什么C没有C,首先一,他C是更比C加更加强调性能,然后没有那么强调业务逻辑,只是强调底层逻辑,所以他就没必要牺牲,那这个性能去做访问权限。

访问权限牺牲了一定的效率

然后C加加的话,他就偏业务了,所以它的代码量会很大,然后可能会多人开发是巴西可能就是少人开发了虾,就是多人开发,所以他就注重注重到访问权限,而且是偏业务了,但是访问权限,他终究还是以牺牲了效率为代价的,所以他多多少少也会牺牲一点效率,你你就比如说最突出的一点,就是你本来可以直接这个访问H的,但是你现在把H放到private里面去了,然后你用了public里面的方法调用了里面你。Private age,这是不是就多了一步?你是不是多了一步,通过方法才能去访问这age,而不能直接访问age,所以这就肯定是多了一步,所以它肯定是以牺牲了一定的效率为基础,所以任何的东西,只要有好处,绝对也也有不好的东西,这就是访问权限,它虽然有访问权限,但是它牺牲了一定的效率,它没有,既然没有的话,效率就更高。

我们在Linux内核里面看到那个structure里面很奇怪,就是他struck里面有个点,什么什么等于什么什么点什么什么等于什么?这个其实就是就是CC,它扩展的语法就是C语言本身是不支持的,他是GCC扩展出来的语法,他是一个初始化是,就是你结构体吗?你的结构体要初始化,你要把里面的一个东西,一个东西都给初始化掉,那么你就是这样用的。所以这个东西struck里面括号点什么什么等于什么点,什么什么等于什么?这其实就是一个struck的一个初始化来了,这是C语言本身没有了,是GCC扩展出来的语法,他这么写就是为了把里面的一个一个去初始化了,这样囊括这些就方便一点。

struct和class在默认情况下访问权限的区别

这徐佳佳里面这个类呀,你如果不写,他就默认他也是private。

然后如果用的是struck的话,它默认的就是public,为什么?因为你在用struck之前就根本没有class嘛,所以根本没有权限的这个概念,所以它就默认是public,但当你呢,他也是为了向C金融,但当你用了那个C加加里面class之后,它它就CAD里面就引入了private吗?那既然那我那么就说我既然引入private,那我class又心累,那我为什么不默认是private呢?所以它是默认是private。

这里朱老师解释了一下,你他理解为什么他也觉得是在class里面,默认private就是豪,这他,他以那个SPL库为例库里面他里面他很多的那个时钟默认都是关闭的,你用哪个你再开哪个。然后这个的话就是一个避免浪费的一个理念,你用哪个在开哪个,跟private一样,你class里面默认是private,你默认是哪个,你再去top哪个,就你默认是哪个,你再去用哪个就不是,就是你要用哪个你再去,你再去开哪个。是不是,这样的话就有个避免浪费,所以他是先先是默认private,你要用公开,你再把它公开掉。

先全部禁掉再按需打开的理念

所以他这个默认的差别就是说,它默认了你所一开一开始所有权限都是关闭的,那你要使用它权限,你打开它要用哪个打开哪个的权限,这种理念才是比较合理的。

那么怎么打开呢?两种方法,第一种你把那个东西放到她里面去,第二种方法就你给这个东西设计一个嗯,读取跟写入两个方法,这两个方法把它放到pub里去。

struct和class交叉继承:远古大神

然后还会有一种比较少见的情况,就是class跟struck交叉继承,这是远古大神写下来的,他当时可能是从struck过渡到class的时候写,他当时可能是这样一个情况,我们现在写代码就不要这么写。但是,但是如果当到遇到这种情况怎么样,他的权限是遇到这种情况怎么办呢?他的权限是由子类去决定的。

就是说此类是什么默认的权限,那么它的这个权限就是什么,就是默认的权限是取决于此类的。

模板之后用的全是class

后面使用模板的时候呢,基本上全部用的都是class,不可能出现strong,这里会很奇怪,Strong跟class不是一模一样的吗?为什么模板的时候,他用的是只能用class,不能用strong,原因很简单,因为模板它是在出现了class之后出现的。

class和struct的一个剩余的小区别

这个class he struck还有一个小区别,就是初始化这块,如果你直接家里面用的是structures,去订一个类,那么如果你里面没有构造函数,那么它就可以用si里面那个初始化是就是用那个,就是用那个刚刚说的什么里面的只有一个点的什么什么,等于什么点什么什么等于什么就初始化,但是你如果一旦你的这个类里面用了构造函数了,那么你这个structure就不能那样子初始化了。

C的struct和C++的struct是不一样的

C的,里面的struck跟C加加里面的struck是不一样的。

常函数

这个C加加里面的长函数的意思就是说,他这个类里面,他那个方法方法,它不能够去修改它类里面那个属性,它可以读取,但是不能修改,不能修改它类里面的属性,像这种函数就叫做常函数,长函数的,一般是在那个函数名后面加个cost,这个常函数的用法是什么呢?一般情况下是跟cos结合在一起,用的,就是比如说你写了一个函数,这个函数不是类里面函数啊。就是外面的是内,外面的函数啊,这外面的函数呢,它用到了一个传参,它传参传的是一个cost person and pn,这个时候呢,意思就是说你不能够改变它那个PN,就是你传进来的那个东西里面的那个的值,所以它前面那个cos修饰。

他前面用了cos的去修饰的话,那么他这个函数里面调用的,它这个类里面的方法就必须得是常函数,而不能够是不是常函数,必须得是常函数,比如说你调用了一个pn.popn.h get,这批n.hkipn.h的话,如果不是常函数,那么它就会报错,必须得是常函数才能够工作,因为你在这个函数,你在这个这个函数的。就是你在你在外面的这个函数,它的船舱写个cost,所以这个时候常函数就派上用场了,你必须得要用用的时候呢,如果前面一旦加了cost,那么里面的函数必须得是常函数,常函数的话就是要在前面加cost,就是说意思就说这个函数不能够改变它的里面的类里面的属性的值。

这个不能改变它类里面属性的值,跟它那个函数里面传的那cost person and pn,那cost不能改变那个指针里面对应的那个值,这其实是有点错位的,它可能有点参差的,不是完全对应的,但是C加加语法就规定,你如果一旦前面的这传参你全是cost,那么你里面就用长函数了,就是必须得是这样子。

这个cost常函数就是你不单单是不能修改它类里面的private的函数的属性,也不能修改类里面的public的属性,只要是类里面的所有的属性都不能修改的。

multiply也遵循先全禁再按需打开的思想

这个multiple也是遵也是遵循了一种设计思想,就是先全部进了,再按需打开的一个思想。

类不占内存

对你在写那个你在PP里面写那个类的时候啊,那个类是不占内存的吧。

就是类本身是跟结构体一样,它不占内存的,你是把这结构体实例化,你把这个类对象化,这样之后它才能占内存,它它那个创建出来的对象还是占内存的。

就是说把类给实例化,没有对象化这个说法,就把类给实例化才能够那个实例化之后的那个对象才是占内存的。

你可以理解为就是在HP里面写的那个class类呀,它是类的一个声明,所以它这个并没有内存,它只是声明,就跟shocked一样,它只是声明,然后真正定义这个类是在那一个CPP里面去把它填充了,实例化填充了,那个才真正把类定义出来了,定义成一个对象。

哦,刚刚说错了那个类的定义呀,并不是你把它在PPT里面填充实例化了之后,能对象那个就是对象了,那个就不叫类的定义了,类的定义是说你在另外一个CPP文件里面,你把它那个类里面的方法,就把那个函数体,它不是写到外面去吗?就是就是你类里面的那些方法,他是不是也要定义写到外面,但是它是类的里面啊,但是只不过写到外面去而已。在那个CPP文件里面,那些方法的展开都写了里面内容,这个才是定义,然后而我刚刚说说错了,刚刚那个对象并不是定义那个是在使用类的,把类给填充了,然后给实例化成对象的那个,不是定义,那个是创建对象的定义,是指说那个方法在另外CPP里面写出来。

类的前置声明

Or c加加里面累,他还有个前置声明,就是说比如说你这个可person这个类啊,你前置声明呢,他就把所有细节都隐掉了,隐藏掉了,直接是一个class person封号,这个东西有什么用呢?这个前置声明,因为C加加他所有的代码都是面向对象去编程的吗?你基本上钱没每走一步都要用到一个类或类里面可能又夹着其他的类,所以是类与类之间是很很多类的,很多各类在里面的。万一你在用到这个类的时候呢,这个类还没有声明就还没有,你还没有那个,因为函数是从上到下执行的嘛,所以你用到这个,你执行到这一步函数,可能你用到这个类,它在后面才声明啊,那你这个时候你这个编辑器就不知道嘛,它不知道是什么东西,所以你就要需要把这个类都放到前面,先声明一遍,然后在后面又搜才知道哦,原来是它这个类,它是一个类要用了它是个类来的。

所以说就是说他这个这个class person分号,这个很简短的一个东西,它声明了之后,你放到前面,那么他就每次用,因为然后他下面可能类类之间的用互相用的是很交错,交错复杂的,然后可能后面后面才用,还才有,才有它的整个一个大的一个,这个普通的类的一个声明出来,那你就提前在前面声明一下,先告诉编译器,哦这是一个类,所以这个简单的class person分号就放到前面,先告诉编译器,哦这是一个类先声明,以免她的泪出现了后面,然后你在前面用到它了。

是说代码是从上到下执行的,然后编译器他会做类型检查,所以你你在前面,你先先把一个前置类的前置声明写上去,你告诉编译器哦,他在做编译器的类型,检查的时候呢,先提前知道,哦,这个是一个类来的,你按照类的类型检查,按照你这个你这个类型是一个类的类型,那编译器在做类的编译,在做例行检查的时候,从上到下,他要先提前知道,这个累啊,他后面就可以用了。否则的话,你从上到下执行你编译器做类型检查的时候,你不知道它是什么,可能它在后面才是明,所以你必须得提前在前面做一个前置声明,这样的话才能够让编译器做类型检查的时候,编译器类型检查的时候比较好。

这个主要还是西加加的编译器不够给力,他没有沉得住气,他不能把疑惑藏在心里,就是他如果有,有这种沉得住气,把疑惑藏在心里,他先看到了这个东西之后发现她在做参数,做编译器类型教育的时候,不认识的时候他先藏在心里,然后等到后面用,后面刷到了,从上到下运行代码,刷到这个类的时候,哦,他发现这是类这个生这个声明的这个东西是个累啊,然后他就知道了。但但C的编译器没有那么聪明,他沉不住气,他遇到了这个东西,他不知道的话,你,你如果你声明在后面,他沉不住气了,他不知道了,他他就就,就就觉得有问题,就是他不能把这个疑惑藏在心里,然后Java的话,他的编译器可能就聪明一点,他可以把这疑惑藏在心里,你不用前置声明,他如果遇到了这个东西,他没有,他前面还没有声明过,他就把这疑惑藏在心里,等到后面运行的时候。

然后等到后面运行时候遇到了这个这个类的声明之后,他说哦,原来这是个类呀,然后再把它给重新认识一下,然后这是个类Java的一个编译器,可能就有这么聪明,但C加加编译器沉不住气,它不能把这疑惑藏在心里,所以你要提前用个前置声明在前面给它,这样它显着。哦,这个这是一个类呀,后面它才能用,不然的话它沉不住气的,它这疑惑一旦出现了,它不会藏在心里。

他这个疑惑如果出现了,他不会藏在心里,等到后面知道了再去解决,他不知道就不知道。

他遇到这个问题不知道了,就卡住了,就直接报错了,就是卡住了,就说嗯,他不会这样,他不会像其他的玩比较聪明,他不会。

inline的进一步理解

Line是怎么来的?你要理解这一点,你就必须要知道静态类型检查这个东西,它跟这东西是百分百挂钩的,阴赖本身就是一个宏定义的扩展宏定义,它跟阴赖区别是阴赖比宏定义多了一个进参数类型校验,就是这一点。

宏定义在设计的时候就是没有充分考虑到设计者跟使用者之间的沟通,你没有参数类型校验码那内参数你特别是代参宏,你怎么传我?万一传错了,你用不校验给我是吧?

所以,本质上inline函数就是宏定义的一个升级版。

你在Linux内核里面,你会看到很多只有写了一行的函数,那么,其实这个一行的函数调用开销是特别特别特别特别特别大的,但是为什么要这么写呢?它就是为了函数这样写出来,对程序员、程序员来讲比较看起来比较清晰。

是你清晰了,但是你从程序员的角度看清晰了,但是你从程序运行的角度来看,它效率越大,打折扣了,那么这时候就要依赖嘛,是依赖的出现,其实是有要有它的原因的。

In line跟类的结合会出现什么东西呢?这个在我们之前已经讲过了,就你如果在这类里面,你写了函数题,它默认就是in line的了,所以你可以把inline那个关键字给省略掉,它就是inline的,就是这样子。

它里面如果是空的,他也是一个依赖的,就比如如果你那个中括号里面是空的,就比如说你那个默认构造函数啊,特森然后一个一个括号,然后里面一个中括号里括号,然后后面是一个中括号,中括号里面是空的,他把他这个函数写成了一行,这其实也是内联函数来的啊,你知道吧,因为你他已经把函数实体写到类里面去了,你中括号在里里面你只写了一行的,然后这个的话,其实他也是依赖函数了哦,它只不过把依赖给省略掉了,因为你只要把函数体写到类里面去,那么他就是依赖函数,依赖函数它的依赖是可以省略掉的,实际上它本身是,所以他所以实际上它本身前面有依赖的,只不过把依赖这个关键字省略掉而已。

也就是说,你如果把函数体总结一点,就是你如果把函数体写到类里面,你的中括号写到类里面,那么这函数你就,它就会C加加,就处理时候就把它依赖化,把它进行online处理。

还有一种传统的方法,就是你在函数,你在类的外面类的,外面再写它那个方法的一个实题,在那里再写online。但是朱老师这里推荐什么呢?就推荐你如果真的要online了,那你就不用这么写了,不要按传统方法写了,你真的要online,那你就把函数实题写到类里面去就算了。

应该用不怕把类写得太长

你不用怕把泪写的太长了,这个不用怕的。

朱老师,他个人认为,把泪写的长一点是没有太大关系的。

inline如果写外面,要写在下面

这里还有一个注意的点,就是你如果真的要用传统方法把赖就是写到外面去嘛,用做传统方法的话,那么你这个online函数就不能写在CPP文件里面了吧,就不能写在那个第二个CPP文件里面了吧,就必须把这个online函数放到头文件里面去吧,就放到你的类的后面跟着。

这个为什么呢?因为C跟C加加的编辑器啊,它都是单个文件去编译的,然后链接的时候在链接在一起的,那你如果这样的话,你把inline函数写在了其他CPP文件里面去,那你这个时候是不是出现问题了?当我编译器在单个编译的CPP这个CPP文件的时候,你发现在依赖展开,不知道转到哪里去,是吧?它的原型是在那个,他的声明是在头文件APP里面,那你你在CP里面展开依赖,你没法展,你单个文件不知道哪里去,所以你必须得把它放到你那个APP里面去,把依赖那个函数放到APP里面去。

不然的话就会报错。

继承

设计继承的本质目的:代码复用

需要考虑一个很重要的问题,就是C加加他为什么要设计继承它?本质上,设计继承就是为了代码复用。

在C语言里面,我们是通过结构体里面包含另一个结构体,从而实现代码复用,就结构体里面包含结构体,然后在C加加里面,我们C加加它本身原生,它就支持继承这一个特性,然后我们就可以通过继承可以实现代码复用。

继承的本质就是结构体包含结构体

所以C加加他的这个继承,本质上其实也就是一个结构体,包含结构体,你可以理解为他私加他,他原生支持的一个特性,这个继承特性,然后他是结构支持的结构体,里面包含结构体,然后呢,它这个结构体呢,他为了方便,他就不不写到那个结构体里面去了,而是把它提出来,然后放到那个,你定义那个结构体的后面有个冒号,然后把结构体写进去,然后在结构体的前面再加上个public或private。设置访问权限,你就可以理解为它就是为了美观、方便、简洁,然后设原原生设计知识的一个继承,你把本身的结构体里面包含结构体,这里面的结构体直接把它提出来,放到那个你定义的结构体的那后面去写上去,然后,然后你就不用再把结构体再包包进去,就不用再写进去了。

这个继承关系最好的理解方式就是用结构体包含结构体去理解,它本质上就也是通过这个这样子去实现的,直加加底层,我猜肯定也是通过结构体,包含结构体去实现的,它只不过他只不过是通过这种方式封装给你用了,封装一个继承的特性可以用,所以你要去理解底层,你就去理解结构体,包含结构体就行了,他只不过你把你包含在里面的结构体提出来了,然后放到那个,这个你新定义的这个结构体的那个名字的后面,那个冒号写到后面去。

继承中的访问权限

类的权限管控和继承的权限管控时两码事

这个类里面有public private protect吗?然后你继承的话,你在那冒号后面是不是也写了public private跟protect,这两个东西是两码事来着,就类里面它有它的权限继承,里面也有它的权继承的权限,虽然都是public private protector,但是其实这三这两个一个是类的,一个是继承的,这两个是两码事。

public继承

如果复类里面有一个public,有private是吧,然后如果你继承的时候继承了这个父类,继承的时候你写的权限是public,那么你这个子类的继承这个父类的时候,如果你他复类里面的public在子类里面是可以访问的,他是以public的形式存在的,但是后面这一点就奇怪了,如果你的父类里面private。那么在子类里面,它不单单是private那么简单,它在子类里面,它比private还要严一层,正常来讲,如果你是private的话,你只是外界访问不到嘛,你里面子类里面的方法也是可以调用这个东西的。

public继承中的private继承之后比private还严

但是你如果你继承的那个父类里面的那个private,它虽然在子类里面是存在的,但是子类里面的方法竟然不能访问他,就是说尽管是private的,他连纸类里面的方法都不能访问它,这个是一个特别关键的一点,这个就是他严了一格,严了一层,它不是private那么简单。你父类里面的private继承过来给子类之后,子类里面方法是不能访问它,这个已经不是private的,如果是private的话,那么子类的方法,它可以访问它的子类的方法,是可以访问它那个里面的东西的嘛,这个子类方法是访问不到这个东西的,那肯定不是private那么简单,它是父类的,里面的是子类,是不能访问父类里面的private子类里面是不能访问的,如果是public的话。

就是如果继承的那个权限是public的话,是这样的。

既然比private还严格,用都用不了,那为什么还有

注意是存在在他子类里面的,就你父类里面的那个private的,里面的东西,他还是在子类存在的,那你不能访问,那是不是就是没有用了,不是的,他肯定存在就有存在的意义,它不能够用子类里面的方法去访问,但是呢,他却可以用父类里面的方法去访问,就是说你子类里面的父类的方法是可以访问到。你这个复类里面的private的东西的,只要你这个复类里面的这个方法是在放到父类的public里面去,那么它继承到子类里面去的时候,它也是个public的方法,那么它这个public的方法,它如果是属于父类的的话,它就可以去用它这个复类里面的private里面的东西。

简单说就是它可以被继承过来的复类里面的方法,而且是放在public里面的方法去访问。

注意仍然是子类而不是父类

注意它这样这样子的话,你在那个子类里面啊,去调动那个负类里面那个方法,然后那方法在public里面的,然后它里面用到了负类里面的private里面的变量,但这个时候你一定要注意,这个不是负类了哦,这个是子类里面的东西吧,你用的是子类里面的成员变量哦,一定要注意这个哦,这个不是属于负类的,这是属于子类的哦。

再把该方法放进父类的private中就是死局了

就有一个很好笑的意思,这如果是在这基础上,你要是但凡把你的,你要是一旦把你那个复类里面的方法呀,把它放到你复类里面的private了,你连复类里面的那个方法呢,放到复类里面的private了的话,那基本上就是,就是死局,你就基本全完全用不了了,因为你连复类里面的那个方法都是private了,那你。根本就子类里面,根本就你继承到子类里面去的话,它也是属于private,它也不单单是private那么简单,跟成员变量一样,跟也不是跟单单是private那么简单,它是连比private还要再低一层的,所以你根本在子类用不了,它放到用不了负类放到private里面的方法的。除非该方法又被父类另一个public里面的方法调用,然后你在子类调用这一个方法

protected如果不考虑继承的话,它跟private是一样的。

你如果继承的时候,水龙头是public继承,就是你在大的情况下是public的情况下,父类里面是protect的话,那么你父类里面的protected里面的东西,然后你继承到子类里面之后,子类可以调用方法,然后去用父类里面的protected里面的东西,这个就是protected在继承里面的用法。如果不考虑继承的话,它跟private是一样的

你可以在子类里面写一个方法,然后子类里面的那个方法的话呢,它可以用父类里面那里面的protected。

protected再往后走细究与private的区别,就要看继承该子类的那个子类了

然后又单单从那个权限的角度去看,你父类继承过来的这个子类里面那个protective,就是说你不从,你不从那个public父类的public去看,你就从父类,它里面那个protected继承到了子类之后,咱们看这个他继承过来,如果你的大的那个水龙头是public的话,你继承过来的那个东西是protected的话,到子类里面还是protected。然后到时候子类如果要又被另一个子类去继承的话,那么这个时候这个子类里面的那个父类的那个protected继承到了这个子类里面的之后,他也还是protected,然后它是作为protected,然后去对这个子类的下一个新的子类去继承的。

private继承

然后如果你把那个继承的水龙头啊,改成了private的话呢,复类里面的坡private成员的话,肯定在那个那个纸类里面是用不了了,就想都不用想子类,就算用它之类的方法调也调不了,他想都不想肯定不行。然后,如果你复类里面的是protected的话呢,他到了子类里面可能是private也可能是protected,通过实验发现可能是protect也可能是provide provide,因为他你你复类里面如果是protected的话。你到了子类里面,通过实验你会发现子类里面通过方法可以调用可以调用的子类里面写方法可以调用它的,所以它有可能是protected,到了子类,有可能是protect protected,也可能有可能是private provide,因为它子类里面是可以用它子类的方法去用它的,是用父类的这个protected的。
然后如果你在private的水龙头继承下,你的父类里面用的是public,那么它继承到子类里面就是变成private的。
也有可能是变成protective,这个都都说不定,反正就是变成politic或者private的。

protected

然后如果是水龙头里面是protected的话,那么,复类里面的public跟protected成员到了子类中可能是protected或者是private成员,就是说不能直接访问,但是通可以通过成员变量,可以通过子类的成员,方法不是成员变量,是可以通过子类成员方法去访问他,所以他到了子类可能是private成员,也可能是private,也可能是protected,然后这个水龙头如果是泊protected的话,如果你复类里面的那个东西是private private的话,他继承到子类里面是比只比private还要可怜的那种成员,就是访问不能访问你用方法,你用子类里面的成员方法,也不能访问的那种那种类型。

默认继承

如果你那个水龙头是默认的,就是那个继承那里权限是默认的,那么他会怎么处理呢?他会根据你的那个纸类去看,你如果只类是用class写的,他就默认那个是水龙头是private的,如果你只类里面是指利用的是shock的,那么它就默认你的那个水龙头是public的,这里有点奇怪,就是我之前也有过,也有过一次总结,就是如果他是。Class的话,那么它那个那个类里面的那个东西就默认是public的,默认是private的,如果是struck的话,类里面的东西就默认是public的,我不知道这两个是是之间是不是刚好巧合了,都是一样的,还是说我之前的总结是错的,之前说那个不对,嗯之前看错了,应该说是继承里面才是这样子。

现在就暂定为两种都对吧。

就是在类里面是这样子,在那个继承那个水龙头那里也是这样子。

听朱老师的意思是说,这个好像是在继承那一块儿,就是在水龙头那块儿是还是这样子的,这个类里面的那个默认的话,没有写public private,它不是不是这样子的,不也不是说不是这样的,是朱老师,他说他讲的是在继承那里的,在水龙头那里的,那么那类里面默认呢,这不知道不知道的话,那就先暂定,暂定就是也是跟水龙头那块是一样的吧,如果是class的话,就class的话,就默认的是private,如果是struck的话,就默认是public。

从哲学角度看C++继承的全继承

从哲学的角度去看一下他这个C加加的设计,首先它这个继承西加加,从哲学角度上呢,他选择了全祭承,这其实是跟现实生活是不符的,而且跟C加加里面的一些要解决的实际问题,也是不服的。就举个最简单例子,你爸的牙刷是不是你的儿子是不能够用,这是不能继承的。再比如说你爸的一个智商,你儿子是不能继承的,是不是在在现实生活中,他不可能是全继承的。但是C加加在设计这个继承的时候选择了全继承,这个就代表了C加加一种哲学,他不希望再在这个基础上再复杂下去了。如果在这基础上再复杂下去了,再细化了,你这个语言就变得过于的晦涩复杂了。虽然说他解决了一些问题,但是他变得复杂了,变得太复杂了,所以C加在哲学方面,他选择了全继承。

然后虽然是全继承,但是父类里面的东西呢,有一些是子类,也是不能访问的,无论如何都无法访问的,虽然他是全部都继承了,但是他无论如何都不能访问,这也是C加加他设计的一个哲学,他是这么选择的,他选择了全继承,所有都继承过去了,但是有一些东西是完全无论如何,任何方法都无法访问。

注意这个语言之前说的,其实是不算严谨的,你比如说你说子类的子类去访问父类的方法,其实这个是这个严谨来说是子类去访问子类,从父类继承而来的属于子类的方法,但是我们平常说的时候没有那么严谨,就是说子类去访问父类的里面的一个方法,但是其实严谨说就是子类去访问属于子类的,从父类里面继承而来的,但是真的就是属于子类的,不是属于父类的那个方法。

是严谨的说,是我们去访问子类,从父类继承而来的那一部分东西,但是仍然是属于子类的的东西。

之前没有说到的一个细节,就是其实在继承的时候,他是CL里面设计了,你不能够去继承它的构造函数和析构函数的,就是说他还是有个地方不给你用,就是那个构造函数和析构函数不能用,为什么?因为构造函数和析构函数本身是为了构造跟析构这个类的吗?那你父类跟子类是两个不同的类了,是吧,那你负累肯定有它的构造,它的类的子类有构造它的子类的构造,它的类的嘛,是吧,所以构造函数和析构函数不能继承各自,因为它的功能是用来构造类的。

因为构造函数你一个是构造它的父类的,一个另子类的构造函数构造它的子类的嘛,是构造它的嘛,所以构造函数是针对每一个函数量身定制的,它是构造它那个构造函数是为它那个类量身定制的,它是就是构造那个类的嘛,所以它就C加加里面就设计了,你不能继承构造函数跟析构函数。

有趣的现象:打印顺序

这里有个很神奇的现象,你记住它打印出来的结果是person,然后man,然后取反man取反person。这个现象是很神奇的现象,它是先构造了它构造函数,它是先调用了你的里面的负类的那构造函数,接着再调用你里面子类的构造函数,当你要析构掉的时候,它是先析构你子列那个析构,然后再去析构你负类的析构。

他这个打印是怎么打印出来的,就是你在那个媚函数里面,你创建了一个你的派生类,就是你的一个,比如说你派生类是阿man ma man pi r man m1分号,你这样写一样东西,那么就说明他是初始化的啊,就是不是初始化的,那么就说明你是这个,你是通过这个类去创建一个对象吗?创建对象呢,就自动去引用,它就会自动去调用构造函数和析构函数嘛,就当你这个这个函数跑完了,会调用析构函数吗。那他自动调用机构函数跟自动调用创构造函数跟析构函数的时候,你会发现你虽然是创建派生类的对象,但他是先把你的那个基类,也是复位的那个构造函数先执行了一遍,再去执行你的那个派生类的那个构造函数,然后然后吸构的时候也,啊,他,他就又是吸构了你的那个派生类的,之后他还去,还回去吸够了,你那个基类里面的那个吸构函数。

冒号的功能又多了一个

这里到了这里的话,冒号的功能又多了一个,就是你在写你派生类的构造函数和析构函数的时候,其实你直接写的那个你派生类的名字man,然后括号,然后中括号,马上写他这个其实是一种简写,其实真正的写法是什么?那个写法是man,括号,冒号,Person,括号,中括号,就是说,其实你要用这个派生类的构造函数的时候,你其实是调用了那个。基类的构造函数的,然后构造函数是又多了一个冒号的写法,就是冒号,然后后面写上person括号,然后你如果简写的话,它就默认用的是你的这个括号,里面没东西的那个构造函数就不带参数的构造函数。

也就是说,你简写就其实就等于你用的是一个man,然后括号,冒号特森,括号中括号就是你用简写的话,其实就默认你用的是不带参数的那个父类的构造函数,然后如果你用你要用了,你要用你复类里面的带参数的构造函数的话,那你就要用一个man括号,冒号,然后特森然后,后面括号里面写上你要带的参数,这样呢,才能够用到你复类里面带参数的那个构造函数。

刚刚说了说那个构造函数里面带参数就父类的,构造函数里面带参数的时候就是这样子,然后接下来对于西勾函数而言呢,虚构函数就比较简单,你直接就是默认的吧,默认的话就那样子取反面,然后冒号,然后取反python括号中括号吗?啊只有这种情况,他吸构函数就只有一种情况,他就没有说带不带参数的问题了。所以刚刚说问题焦点主要在构造函数上面,析构函数默认的那个取反面括号就等于就就是其实也就是你取反面,然后冒号person括号嘛,但是其实你这个不用太纠结了,因为它就只有这种情况,真的就有点情况,后面没了,就是西勾函数,很简单,所有的参数都是一样的。

说不存在,说西构函数还在参数这种情况。

然后也正是由于他这个息垢的一个特殊性,然后你就算是写曲,反面括号,冒号取反person,括号中括号,这样显示的写出来的技是给会给你报错的,因为吸完之后只用一次的,不是因为吸完之,它只有一种,没有别的那种,所以它不支持你这样写,不能显示写出来,所以只能直接这样写。曲反面,然后括号,然后中括号只能这样写,不能后面再写冒号,取反person括号。

那么C加加里面为什么要用这样一个这样一个机制呢?就是就是一个man括号,然后冒号,然后person括号呢,然后这样子传参实,这样的话就是说,你到时候构造函数的时候,你传参,你在麦里面去传里面的餐,你person里面传,你person里面看,就有一个很关键的一点就来了,你写慢的时候,你就不用一直盯着person里面的参数了,就是这一点是最关键最关键的。因为你如果不这么设计,你也可以写你在那个man里面,然后传参的时候,你传传person的参,比如说person里面它有age跟name的参数,然后那个man里面有那个lens的参数,那么你lens跟age跟then它同时都可以用啊,因为它也是属于它是,它因为它是属于那个man里面的呀。

你完全构造函数,你可以这么传,你在一个括号里面,括号里面传三个参数进去,一个是Les,一个是念,一个是age,但是这样的话,你写代码,你要一直盯着person里面的东西看,你就算是再写卖,你也要盯着person看,所以这样是不怎么好的一种方式,所以就干脆你person里面的,你就用person的里面的,然后你man里面的,你用per man里面的,然后你加个冒号,Person括号,这样的话就。清晰明了,要简便。你写的时候你不用再看person里面的哪些参数要要要初始化,你直接用个冒号、person、括号,然后你具体的东西交给person里面的东西放到里去写,这样的话你写代码的时候你就不用,一直你写man的时候就不用一直盯着person看了。

这里你会发现C加加里面啊,他传参呢,你顺序可以自己安排的哦,就是说你传参你虽然写的可能是里面有三个不同的数据类型,但是你最后用的时候,你三个数据类型,你不一定要严格按照你写的那个顺序去传,你可以比如说ABC,然后你传的时候可能可以用BC这样的一个顺序去传的。

子类传三个参数,父类用其中两个,使得父类传的参可以不是定值

上午说下,下午说那个说什么船插呢,ABC可以变成BC呢,是完全错误的,那时候是没有理解那个不是传差来的,那个是本质上是为了让你那个复类里面的就是鸡肋里面那个船刹那传递参数不是一个定值,可以是一个常见的,直本质是为了这个的,就是你在那个鸡子类派生类里面写上这个,嗯into a b吗。然后你在那个那个后面的冒号,然后person后面括号里面给他传一个a跟B,那么这样的话,你你的里面的那个负类里面的那个a跟B就不不用,你是不用,你提前设定个设定,提前设定一个预值,预先的值,你就用那个,你最后传来的,再从子类里面传下来的in和a跟B,然后用到负类里面的括号里面,A跟B。

父类的构造函数和析构函数是不能继承的

注意这种写法,它不是直接继承的,它的那个父类的那个构造函数跟析构函数是不能继承的,此类是不能继承的,它这种写法就是冒号,然后python括号,这种写法不叫直接继承,它叫调用。

然后你如果是要写的时候呢,你想把这个类的定义啊,写到那个外面去的话,那么你只用在写到外面那里的时候,你把那个冒号person,然后括号写上就行了,然后你在那个类里面,你去声明这个东西的时候呢,你就不用再声明,那里面再写冒号,Person括号了。

两种冒号的用法竟然可以放在一起混合着用

我靠,这个是真的离谱,这个那个累啊,那个出之前说的那个初始化是不是用冒号吗?然后那个现在用的那个,嗯用那个就是你用那个继承的时候吗?不是你用那个构造函数的时候啊,那构函数是不是也是用冒号破损括号吗?这两个东西就能够放一起,然后用逗号凑在一起吧,就是你,你如果想要在后面用一个初始化是,就是想要在构造函数本来已经写了冒号,Python括号后面用一个初始化是的话,你竟然直接逗号后面写初始化是就可以了。

当基类和派生类中出现同名方法的情况(重定义)

如果你的基类跟派生类都有一个相同的函数,它们的函数原型一模一样,就是传的餐也一样,就名字一样,除了仓也一样,就是这个,这个就不是所谓的函数存在,这是崇明了,就是子类跟父类有重名的函数。那么这个时候调用的时候他怎么处理呢?如果你是用的是那个,嗯,子类就派生类帽,两个冒号,然后去调用它,那么它就会默认使用派生类的那个函数,如果你调用的时候是用的是鸡肋,也就是父类冒号,冒号,然后那个函数名,那么他就会用的就是鸡肋的那个函数。

也就是说你用子类,子类,冒号,冒号,然后用的函数名,它本来是有两个选择的,一个是子类,你写的那个函数,一个是父类,因为他写的函数,这两个名字一样,然后常常一样是重名的,那他会他他怎么选的,他是把那个鸡类的那个隐藏掉了,用你子类的那个,也就派生类里面那个。

这种操作方式这种机制嗯,江湖上就叫做隐藏或者叫做重命名,啊不是重命名,是重定义,就是隐藏或者叫重定义。

记住,这个是叫重定义,它是这重新定义了一个,在你的子类里面重新定义一个相同的函数嘛,然后他就把父类那隐藏掉了,它不叫重命名,重命名是另外一码事,这个不叫重命名,这个叫重定义。

就是重新定义把它那个原本的那个重进行一个重新定义,把原本的那个在子类里面进行一个重新定义。

那如果要跳过重定义机制,用父类的那个同名的怎么做

那么这个隐藏的机制,也就是这个从定义的机制,你怎么跳过呢?比如说你要在子类里面用到你的那个负类里面的那个方法,虽然是重名的,但是你要用它怎么办?你只要用的时候呢,你在前面,你在用这个函数名的时候,那函数名前面加一个person冒号,冒号,然后再加函数名,这样的话,他用的就是嗯,你那个基类里面的了,就是你在那个函数名前面加个person冒号,冒号就行了。

正常来讲子类重名前也要用man::,只不过省略了

其实正常来讲,你只类调用你那个里面重名的,但是你是在子类面那个方法把那个函数啊,比如说speak函数啊,正常来讲的话,你也要用一个man,冒号冒号,然后speak括号分号,然后只不过他是把那个man冒号,冒号省略掉了,因为你在这个方法的,整个的名字开头是不就已经写了void man冒号,冒号了,然后,这就说明它在就是你在整个这个函数开头已经写了man of voice man,冒号,冒号。Test括号,然后封号然,啊然后中括号里面,所以你在里面,如果你是用的是子类的话,你就不用,你就可以省略掉,Man冒号,冒号speak,括号分号了,所以你它是可以省略掉了,所以它就直接就是speak括号分号了。所以它其实你用speak,它默认就是子类的,其实它是一个省略,它本来应该是有一个man冒号,冒号的,然后你如果是要用,那里面用到了父类的重名的,那你就要。

但你要用父类的重名,就不能省略

用到了基类里面的重名了,那你就要必须得要加上一个person冒号,冒号了,这样才能够用,这就不能省略了。

前面讲的一直是成员方法,成员变量也一样

前面讲了,这些都是成员方法的,从定义跟隐藏机制,然后你如果是成员变量的话,其实是一模一样的,你按照成员方法的思路去推导成员变量是一模一样的,一个隐藏跟重定义的机制。

说如果派生类跟基类的成员变量也重名了的话,他的处理的方式也是跟前面的成员方法的重定义,跟隐藏机制是一样的。

重定义的本质

这个隐藏规则啊,本质上是一个大小作用域里面重名的一个认领机制的一个问题,就是说你在C语言里面也碰到过一个小的作用域,跟一个它,它包含着他一个较大的作用域,包含一个小的作用域,那么大的作用域跟小的作用,域里面都有一个相,碰巧都有一个相同的名字,那么你你是怎么样去认领呢?它是其实这本质上是一个这样的一个问题。

然后在C语言里面的话,它是以小作用域的去认领的,大的作用域的就被屏蔽掉,在这里就很像你是在你在那个,你是在派生类里面去认领,你这这个的,然后大一更大一块的更抽象,上一层的更抽象到上一层的鸡肋的就被屏蔽掉,它是有一个很相似的一个思想的。

就是说在小的作用域里面,小的范围里面的同名变量,把大的作用,域里面大的范围里面的同名变量给隐藏掉了。

但是其实这两个,其实这两种情况,一个在C语言里面作用域,然后一个是在这里的派生类跟基类,他们都有一个特别重要的一点,是他们尽管隐藏掉了,但隐藏归隐藏,但他们还是存在的,还是在的,然后C语言里面,他不,他无论如何,他就算存在,他也无法去反问,为什么,因为C语言里面没有作用域,然后之间的一个机制。

也就是说C语言他没有支持一个域作用符,你可以这么理解,他没不支持一个域作用符,你如果是跨域,那个大一点的作用于跟包含的小一点的作用域,那么你这样的话他是不能支持,不像那个C加加里面这个子子类,跟父类派生类跟鸡肋一样,它是可以用一个冒号,冒号去实现的。本质上重点问题是在这里就是那两个冒号,因为你要跨作用域。你要破打破这个东西的话,你就必须要用冒号,冒号,然后找到它所对应的的,你想要指定的那个,那个命名空间嘛,那个作用域嘛,这个意思就是这样子,你一个class类,你个类本质上也是一个命名空间,也是一个作用域的吧。之前也讲过,所以你要用到鸡类里面的东西,你就用个冒号,冒号去用鸡类,所以机械家里面它支持,这样可以去使用。

所以C加加里面它是可以支持这样去使用的,但是你C元里面,你跨作用域了,那是不能做的,因为它C加,因为它C元里面没有冒号,冒号这样一个机制,没有这个预作用,服了冒号,冒号就是预作用服嘛,就没有这个东西,那你就无法去往里面的其他的那个域去找它里面的那个重名的东西了,这C加加里面,它可以找其他域的重名的。

类型兼容规则

你分别定义一个person,然后PN1,然后men pi ao man,然后MN1,然后你这个时候你娶个地址,就是取它那个man的那个地址,星号end and m1取他的地址,然后你把它复制给你重新定义的一个新的一个指针变量P就是。然后那个指针变量P还是person类型的,就是person新P等于N的MN1,你会发现这样是可以的,就是说你那个你取那个子类的那个MN1的那个对象,它的地址,你可以把它复制给你的父类,也就是鸡类的那个那个类型。

是说你定义了一个person新批这种类型的一个变量,本身这个批指向的那个里面的纸也指向是person类型的,但是你竟然可以取,你竟然可以把纸类的那种man类型的取他的地址赋给这个P8,所以他这个所以说他这个其实你只类里面的那个那个对象啊,你取纸,把他付给那个指针变量类型是person类的那个变量pi,你会发现这样是可以的。

对呀,这个是从最底层的原理,上面说设计这样可以的,他不是说像其他那种骗过编译器吗?他不是骗编译器的,这样就是可以。

这个事情告诉我们子类的对象啊,可以当父类的数据类型用,因为你这个批嘛,你定义出来就是person新批的,它的数据类型,在你写的这个字的时候就已经定了,它就是person类型的。那么你这个M1的取地址,它是一个指向的,是一个子类的里面的,所以这件事情就告诉我们子类的对象,它是可以当做父类的,也就是鸡类的数据类型的。

如果是这样写的话,他就是真的把你这个M1取地址,把这个你本身是子类的对象,他就真的把你这个这个东西当成person,也是七类父类的对象,这种数据类型去用去看。

但是呢,如果反过来就不行,就比如说你这个man新P等于一个的PE,这样就不行,就说你父类的这个对象不能当做子类的对象的这个数据类型去用。

画图理解类型兼容规则

这个理解其实是很简单的,你画个图,一个大圈,一个小圈,大圈是子类,小圈是负累,然后小圈被大圈包含,那你很简单的嘛,通过这图一看,那么子类肯定是可以当做负累去用的,只不过子类肯定是可以子类的,对象肯定是可以当作父类对象去用,只不过当你当作父类对象去用的时候,你的范围就从大圈变成了小圈,但是你反过来,父类对象肯定不能当做子类对象用,因为你父类对象不可能从小圈变成大圈吗?它本身就没有到那没有扩展到那一步是吧,就说你大圈可以缩小,那小圈不能扩大呀,你小圈扩大,你你扩大不啊,你没有那东西呀,那大圈可以缩小,它可以丢弃掉一些东西呀,只用小圈,只用缩小它,丢弃那些东西之后,它就变它就缩小了,是吧。

这个就是C加加里面的类型兼容规则。

类型兼容规则对引用也是一样

这对于指针来说是这样,然后同一对于引用来说也是这样,比如说你一个person and p等于一个嗯,M1这样也是可以的,就是你子类的那个对象可以当做负类的那个类型嘛,所以person and p可以等于M1。

类型兼容规则对普通赋值也是一样

然后如果不是指针也不是饮用啊,直接是对象赋值也是可以的,就此类对象也可以无条件的复制,到那个父类的对象上去,就是你可以直接PN1等于一个M1,当然这背后如果用到一些拷贝构造函数之类的,这些就先不用管,总之就是它是可以这么用的,然后你也可以直接在初始化的时候这么用,比如说比如说这样子,比如说嗯,Person p n1嘛,Person p n1等于M1分号可以直接这么用。

从子类赋值到父类去,它的范围是缩小了的,他是可以接受缩小,只不过他会损失一点东西吗?那缩小是可以的,那你但是你从负累变成组织类,它是需要放大了,他没能力放大了,他只要内那么点东西,他不可能凭空多出来,其他东西你舍弃可以,但是你不可能凭空多东西,所以父类赋值给子类是不行的,但是子类是可以赋值给父类的,就是可以就说可以PN1等于M1的,但是M1不能等于PN1只能是PN1等于M1。

哦说回到那个指针的,就你一旦是用到了,嗯person Xing pi等于M1的时候,person新pi等于N的取值M1的时候呢,你这时候的这个PR,它虽然是你是子类的地址给他的,就是你是M的M1给他的,但是你在批,然后箭头指向那些东西的时候,你只能是用父类里面的籽类的里面滴是用不了的。就说你随你把子类的复制到了这里去是可以的,但是你并不能通过这个机会去用子类的东西,你还是只能用父类的东西的,你的批箭头你后面指向东西,不能是不能是父类,不能是你那个父类没有的。

就是说你这个批指向的那个东西啊,不能是子类特有的,必须是子类跟父类都有的,就说必须是子类里面的那个,嗯,从父类继承过来的,属于之前是属于父类,那里面从属于从父类里面继承过来的那一部分,你才能够指向它,子类特有的,你是不能指向的。

不良继承

一个概念叫做不良继承,不良继承简单就是说鸵鸟不是鸟,你记住这个就行了,然后这个不良继承其实是一个天然的,不是我们人为能决定的。

就是说鸵鸟不是鸟,但是鸟它是一个父类,鸵鸟是子类嘛,按理说那那鸟它里面有飞的功能,但是鸵鸟它不会飞,所以这个时候就存在了不良继承,因为鸵鸟本身不会飞,但它的负类里面的东西会飞,这就是一个不良继承。

解决不良继承的方法

然后你如果要解决这个不良继承的话,有两种方法,第一种是把你的鸡肋弱化掉,比如说鸵鸟不是鸟鸟类,它多了一个会飞的功能,那你就把那个会飞的功能去掉,但实际上这个问题,这种体的方法是一种逃避问题的方法,你积累这样一直弱化下去,那没完没了是不是还有第二种方法,就是说取消他们之间的继承关系,然后用它们,然后用一个抽象的父类去包含这两个子类,就是鸟跟鸵鸟取消之间继承关系,然后用一个抽象的父类,然后去作为这两个的鸟跟鸵鸟的负累,这个的话就涉及到一些比较深刻的东西,就不深入往下讲下去了。

组合

组合,以我的理解就是结构体包含结构体吗?但是他的结构体不包含的,那个结构体不写到开头那里去了,就直接用一个结构体里面去用,这样说还不太严谨,他不单单是结构体,包含结构体,它是在他那结构体已经实例化了,但是继承它里面的结构体可能不一定需要实例化,但是组合,你如果结构体包含结构体。你那个里面的class类必须得实例化,总之来说也就是一个struck,包含struck,但是组合它可能不放开头,而且它这个组合要要用的话,结构体包含结构体的话,你那个那个结构体里面包含的要实例化掉它,比如说竖杠杠分号,那这样子。

组合与继承的区别

继承与组合的区别跟对比,继承他实际上是破坏了类的一个封装性,就比如说如果你爸有有贪污了100万,那么你继承给你儿子,你儿子可以选择举报,他就说他对于父类来说,是不安全的,他破坏了父类的一个封装性,然后组合的话呢,它是对象似的反问,他权限是对象是的权限,就你会发现你要你做组,你在进行组合的时候,你是创建的对象的,所以他就不会破坏类的封装性类类,它本身是不会被破坏了,然后她你是基于这个类创建一个对象,然后拿去组合了,是这样子的。

虚继承

为解决菱形继承而存在

下面讲讲虚继承,虚继承虚继承本身是为了解决那个菱形继承的问题而出现的,他的这个思想很像呢,F define re no define define,就说如果你定义了之后呢,你就用了后面,如果if if defy了,那就那就不的,那就不用了。所以比如说你现在有三层,三个层次,一个是a一个是第一层是a,第二层是B1跟B2,第三层是C,那么你这样的话,你如果要如果B1跟B2里面都用到了相同的a里面的东西。是吧,那么这个时候你可以用在B的这个层次,用个虚继承,就是一个class b1冒号,冒号virtual,然后public a,然后第二个,然后B2也是class b2,嗯冒号,冒号virtual public a,然后你这时候你在C的那个层次,你再去用,就不会存在这种继承问题了。

这个时候菱形继承问题就解决了。

跟if not define if define一样的,所以说你那virtual加上去,它如果没有出现菱形继承这种问题的话,它就没事,就当没事发生一样的,如果当一旦出现了菱形继承它,就会触发这个virtual,他就会出,就帮上忙了。

注意,这里讲的是虚继承,而不是虚函数,虚继承和虚函数是两码事,两个完全不一样的东西,虚函数在后面才会讲的。

继承这个虚继承,它是解决这个菱形继承的恶意性问题的,然后虚函数,它是到后面的多肽那里才才要用的上的。

虚继承的本质

然后这个虚继承的本质啊,跟一个vb point有关,就是你可以写为就是本来你你去查类的关系的时候嘛,本来他是a它的a是有二义性的,A是存在二义性了的,但是你如果用了vb pointer,你把二义性就转转给了vb point了,你会发现他二义性就体现到了vb point上,然后,这vb pointer它的二义性是不用我们管的,就是C++内部自己会处理的,这个时候当vb point它存在二义性之后,A的二义性就没了。

就是说,他把你原本存在二义性的那个那个东西两份给他干成一份,然后再给你添加了一个虚机类表,Vb pointer。

用这个vb point去找到你干掉之后,剩下的唯一的那一份a。

他是这样子,你没有加vb point,之前没有加徐济成,之前他是这样的,就是B1里面包含a,这是第一个,然后B2里面也包含a,这是第二个,没有加虚继承的,他是这样子的,然后你一旦加了虚继承,那他就会有vb point,它就会变成B1里面包含vb point,然后B2里面也包含vb point,然后你把这,然后他把这a单独拎出来了,然后又有第三个,直接就是一个a,就是这样。

多态

什么情况叫多态

这里讲到一个多肽啊,然后就跟曲函数挂钩了,就就先拿举个例子啊,比如说你现在是定义了一个animal的一个负类,然后有一个汪汪汪狗的一个子类,然后有一个喵喵喵猫的一个子类,是吧?然后你首先在幂函数里面,你三个对象都创建出来,比如说animal a,然后dog。D kat c,你把三个对象都创建出来,然后这个时候你再定义一个animal类型的一个指针,比如说animal新P,然后这个时候你用P去指向你取的那个D里面的那个地址。

就说你animal新批嘛,然后批等于一个and的,就是你取你这个,你这个P指向的是animal类型的,但是你这个的呢,他的是dog类型的,但是你之前说这样是可以的,And取只dog,然后等于他那个animal类型的那个P指针,然后你这时候你去执行这个,你去执行这个函数。你会发现什么,你会发现嗯,它的这个方法,这个呢,你去执行它那个那个那个D的speak方法,比如说d.speak是吧,按理说,那个d.speak里面写的是汪汪汪才对是吧,然后呢,你d.speak你执行,你在那里面写的时候你执行,你会发现它执行的是animal speak也是说执行的是那个animal,那个父类里面的speak。

它执行的是a.speak,也不是d.speak,这个时候的话,你就就怎么去解决这个问题呢,按理说你你这个的本来就是只狗,应该就是汪汪吗,但是你他无法让他汪汪,只能是让他animal speak,让它变成子类,反而让它变成负累的那个animal,因为你用的是animal数据类型吗,这时候怎么办呢?这时候你在那个定义,那个animal,那个方法里面啊,你在定义animal里。方法里面你用virtual把那个方法给virtual掉,就你那个animal里面是不是也有一个speak方法,你把那个speak方法前面加个virtual,就是virtual speak括号,分号嘛,就你把那个东西也watch掉掉,你把这类里面的那个方法,那声明的时候啊,那个speak那方法声明的时候,你在前面加个virtual。

那么这个时候你再去重新执行内函数,你会发现就变成汪汪汪了,这个前面加个visual就是虚函数。

最前面刚刚是animal新P,然后P等于N的D是吧,然后你这个时候下面用的是不是d.speak啊,是D箭头speak啊,因为它这个是指针来的吧,是吧,所以它不是点是是箭头,是谁是所以说是D箭头speak啊。

你如果是想要用猫叫的话,那你就是,嗯你就是animal新批嘛,等于那个end c,就是然后你在这个时候,你再用c箭头speak,就可以用K喵喵了,因为你在那个animal年方里面那个类里面定义的时候,你声明的方法,你那个animal speak方法的时候,你前面加了一个virtual,变成virtual speak括号就是,就变成。你本来声明的时候,你在那个类里面声明是是不是void,然后speak括号里面写个void封号,是不是你本身在那个animal那个类里面声明,这方法这么声明的,你在写你要你你变成区函数,就变成virtual void speak,括号void,分号嘛,就在你的那个声明的前面加virtual嘛。

这种现象,你看你现在看到的这种处理方法,这种做法你就可以理解为这就是多肽,因为你多肽本身是一个比较抽象的,我们平常生活中没有没有过这个词,你可能不知道多肽什么意思,这个就叫多肽。

覆盖重写overwrite

这种实现多态在父类,也就是积累的方法,前面加一个virtual,这种方法叫做重写,也就叫overright,也叫覆盖。

区分好覆盖重写overwrite和重定义

一定要区分这个重定义,还有这个覆盖也就重写,宏观上来看就是重定义,宏观上来看就是那个,嗯,那个覆盖重写,这个宏观上来讲,它是实现了,它是有多肽的。从微观上来讲的话,覆盖也就是这个重写,它是有watch的,它的负累是有watch的,负类的方法也是有watch的。

纯虚函数和抽象类

那接下来讲一下纯虚函数,纯虚函数就是你在virtual后面还加个等于零就你父类的方法吗?你复类里面方法之前不是加了virtual吗?这叫虚函数吗?那你这复位的方法呢,你后面再加个等于零分号,就是在他这个函数的声明的最后啊,给他付一个零,然后它他等于零就是virtual void speak括号,Void等于零星号,这时候他就变成纯虚函数了。那么纯虚函数有什么用呢?纯你用了纯虚函数,你用了纯虚函数的话,它这个函数,你只需要在类里面声明就就行了,你这函数可以不用实体的,就你不用在另外一个CPP文件里面把这函数的定义写出来,它可以不用定义的,它没有实体都行,你只要声明了就行,你只要在类里面声明了就可以。

都会有程序函数这个概念呢,这个从语法上面是完全没问题的,他其实是语义上面有问题,就从从语义上来讲,如果我们要实现多态的话,那么我们这个复类里面,这个函数是完全没有用的,完全没有实际实际作用的,就是那个,所以他你要时,你如果要实现多态的话,你关键是你只类里面那些多肽的方法是吧,所以你父类的方法是没有用的,没有意义的,所以从语义上来讲,这种这个函数体是可以,这个方法提示这个函数的定义是可以省略的,也不用写那没意义的,所以它是从语义上来讲的,所以可以省略的,它是这样子的。

那你如果要实现多肽的话,那么负类的那个,那个本身相同名字的那个方法体,它本身是没有意义的,因为你要执行的不是它,而是你多肽那些猫啊、狗什么的,所以它存不存在都都无所谓的,它是没有意义的。所以它从语义上来讲,是不需要的,但是它,但它从语法上是过得去的。

不是从语法上面去看,语法上面是没有问题的,完全合乎常常理,但是他他为什么要用纯虚函数,是从语义上来看呢?

因为你这个函数啊,你这个函数你的方法如果在外面定义了,他有方法体,它就要占用内存的,所以你不要浪费毫无意义的内存嘛,所以就把他干脆就用纯虚函数,所以你用纯虚函数,它是不占内存的,因为它是在类里面的,她在类里面定义的,它是在这个class里面定义的,它就是一个类别,它是不占内存的,但是你那个你类里面的方声明的那些方法,你再累的外面去用吗?Y你在内的外面去定义吗。那些定义的东西是要占内存的吧,所以你如果那个speak,那方法你定义在外面,它是占内存的,然后你如果它是存续函数,你把它给函数题给它,给写空就把,或者把它删掉,不用你就把它写个程序函数放到内里面,它这样就不占内存了。

你用纯虚函数写的方法,包含就你包含了纯虚函数的方法啊,不是你你做你用,你包含了纯虚函数的种类啊,就你是只包含了纯虚函数的种类,它叫做抽象类,抽象类是不能够定义对象的,所以你啊,他也是不占内存的,所以你用了animal a,这个时候呢,你定义这个对象他就定义不了了,因为你用了它里面用了纯虚函数了,所以用了纯虚函数,它是不能定义对象的,他是不占用内存的,然后这个抽象类啊,它不能用来定义对象,他只能用,他只能作为一个鸡肋,然后去给派生类去使用。

虚函数是不占内存的,尽管你是,你最后你调用了它的一个派生类吗?Pad里面不是也包含了他,那个那个鸡肋的那个方法嘛,是不是,但是其实这里是用了一个right吗?是重写的,所以你尽管那个派生类里面用了,比如说比如说用那个思必客那个那个方法,但他用的也不是激烈的那个那个虚函数的方法,它是派生类里面它自己重写的一个方法,所以它不属于几类里面的方法,所以鸡肋呢,实际上也还是不占用内存的。所以纯虚函数这种类啊,这种抽象类啊,是100%不占用内存的,即使你派生类包含了这个鸡类,但是其实你那个包含同名的那个方法是派生类,它重写之后,过后的不是之前的那个基类里面那个虚函数的方法,所以它无论如何虚函数那个抽象类啊,它是不占内存的。

但是你要注意啊,抽象类里面,你只是要求里面那抽象类里面至少有一个纯虚函数,就说你至少有一个纯虚函数,就叫抽象类了,但是你并不一定只有一个纯虚函数抽象类里面,它是可以除了纯虚函数之外,有别的有实体,有函数实体的一个方法的,就你可以写一个Bo的print void进去,那个抽象类里面的就说他不可不一定只有不一定要求一定只能有需有纯虚函数有实体的函数也是可以有的,但是你只要有一个纯虚函数在里面,那么这个类就叫抽象类了。

他所谓的抽象类不占内存啊,其实是错的,是抽象,是抽象类里面的纯虚函数不占内存。比如说你这个抽象类里面,你写了一个纯虚函数speak,然后你又写了一个有实体的函数,你又写了一个有实体的函数。

你又写了一个有实体的函数print,那么你当你这个抽象类作为基类去被派生类继承的时候,它的那个print是有占内存的,它只是那个speak不占内存而已,因为speak是纯虚函数,但是print是占内存的。

抽象类和定义对象相关的规定

那这时候你就很奇怪,就是你刚刚说的那个抽象类吗,他不能实例化对象吗?那你这时候你又抽象类里面又可以写有实体化的东西,那你里面有实体化的那个盘数的话,就你刚刚说抽象类里面只要有一个,只要至少有一个程序函数就行了,其他都是可以有实体化的,那你其他有实体化的,你又不能定义对象,你有实体化的方法干嘛呢?所以它这里有个规定,就是你抽象类里面。你必须保证你里面的那个存续函数被使用了,也就是说被你的积累呀,不,也就是说被你的派生类给重写了,给覆盖了,给overright了,这个时候呢,它这个抽象类才能够去创建实体对象,就是说,一旦你的抽象类里面的存序函数被你的派生类去使用了,就是去覆盖重写overright了,你才。

情在这种情况下呢,你的那个怕你的那个抽象类就可以打破那个他不能创建对象的这个这个规定了,他就可以去创建对象了,就可以用他这个抽象类去创建对象了。

也不是说那个抽象类里面的那个纯虚函数被使用掉了才能创建,对象是他的抽象类里面的纯虚函数被你的派生类给覆盖重写,Override掉了,才可以就说你的你的派生类里面必须要写上他的这个几类里面的这个,这个也就是抽,也就是这个抽象类里面那个纯虚函数,你在派生类里面必须要写上去,要覆盖重写override,让他,这样的话,你才能够创建派生类的对象。

如果你的这个派生类啊,你没有over write,要他这个这个抽象类,也就是几类里面的纯虚函数,那么你这个派生类也会被传染,它也会变成抽象类,也不能创建对象,就比如说朱老师他这里,他又重新定义了一个类,这个类叫class X啊,然后继承了那个鸡肋那个抽象类。然后但是它这个X这个类呢,大写的X,那这个大写的X这个类呢,它并没里面并没有overright,也就说并没有写上那个speak,就是抽象类积累的那个,那个纯虚函数并没有写上,所以它就并没有覆盖重写,也就说并没有overright,那么这个时候它的这个大写的X的这个类,它也会被传染。

他也会被传染,然后就也同样变成了一个抽象类,他也他这个造型X这个类呢,他也不能够创建对象。

接口

这里有一个接口的概念,接口是什么?你可以理解为就是它的这个派生类,啊不不不,他的这个抽象类啊,然后里面没有实体的方法,就没有实体的函数,只有虚函数啊,只有纯虚函数,那么这个时候只有纯虚函数,没有其他实体的函数的这种抽象类就可以称之为接口,就是它里面没有任何一个实体函数,只有一个纯虚函数,或者是只有多个纯虚函数,反正是只有纯虚函数的一个抽象类,那么这就叫做纯粹的就是这,这种东西就叫就可以叫做接口。

加加里面它是没有interface这个关键字的,就是没有接口这关键字,但是它从本质上来讲,它完全就是实现了接口,你那个抽象类里面只有存虚函数的情况下,它就是一个接口啊。

然后从这里重新加加,这里也是可以知道他这个接口一般是怎么使用的,就跟我们之前用多肽一样吗,就是你先定义一个接口这种,就这种抽象类的这种类型的一个指针,用这个指针指向你的一个纸类,然后填充好他的这个接口里面的这个程序函数的一个新的函数的那个内容只类的地址,把它传给他,创建个他这种纸类的对象,把他按的传给他这个接口,这种接口,这个抽象类的这个类型的一个变量批就是这么去用的。

然后创建好了之后,你那个P就你就用那P拿去指针,用那个指针去访问嘛,那批箭头speak括号分号嘛,然后这样的话就可以用了,用指针的箭头这指向它就完全搞定了。

P这个指针变量,它的指向的是and and那个你的子你的子类的那个对那个类嘛,所创建的对象嘛,所以你最后你它是一个指针变量嘛,那你就P箭头什么,你就可以用它这个接口的东西了。

虚析构函数

所以讲下须虚构函数,就如果你的这个累啊,你的鸡肋,至少有一个或者有多个虚函数的时候呢,至少有一个或多个虚函数的时候,那么你的这个类的虚构函数要写成虚析构函数,也就是要在虚构函数前面加个virtual。注意,这里指的是虚函数就行了,不一定是需要纯虚函数,就不一定需要后面等于零,你只要在那个前面加virtual变成虚函数的基类里面只有虚函数,至少有一个之后,那么你的这个类就要他的虚构函数要写成虚析构函数。

我们之前写那个不太合适,因为我们当时用的是一个有有虚函数在里面,但是我们的析构函数呢,用的是他C++默认给你的,那么这个C++默认给你的肯定不可能是析构函数,C++默认给你的肯定是一个普通的析构函数,默认的嘛,所以这个就不太恰当了,所以你自己需要写,自己手写一个析构函数给他。

刚刚说的是不一定是纯虚函数的,意思是说你虚函数都可以,那纯虚函数就更可以了,但是你不一定是纯虚函数,纯虚函数,你可以是普通的虚函数,但是你如果是纯虚函数是更可以的。

为什么要写虚析构函数

为什么你当你的那个类里面,它用到了抽象类抽,就你的基类是抽象类里面用到了虚函数的时候,你的这个子类的那个虚构函数写成虚析构函数呢,原因是你朱老师他做了一个实验,你如果是创建了这个子类的,他的对象分配在栈上的,那么它在用的时候,他也会虚构的时候,你不加虚,虚构不加过去。他也会先打印出来dog,然后再打印出来animal,但是你如果换成了把这个子类,它你把对对象定义在了堆上,就是一个六的一个堆上,那么你这个时候再去调用它的时候,你会发现你如果不写虚构啊,不写虚虚构函数的话,就是你虚构函数不是虚的的话。

那么它打印出来就不打印animal了纸打印出来一个dog,这个就是不符合常理的,按理说应该是先打印出dog在打印出animal,这样才能虚构掉,这样才能虚构掉嘛。所以说,当你把它分配在了堆上的时候,那么你你如果你的虚构函数之类的那个虚构函数不写成虚析构函数的话,他们就不正常打,不会打印出dog animal,而是只打印出dog。所以说为了要保证你的这个类之类的创建对象,不管在。分配在哪里?不管是分配在站上还是分配在堆上,都能够正常的使用。虚构函数都是先打印了再打印animal。那么就建议你,如果你一旦使用了抽象类你的鸡肋,一旦使用了抽象类里面有虚函数,那么你的子类的那个虚构函数就就要写成虚虚构函数,只有这样的话,你才满足他,不管是分配在哪一个地方,它都能够正常使用。

因为你写这个类的人,你到时候你根本不知道人家要怎么用你这个类,万一他是要把它分配在站上,万一他是要把分配在内那堆上,你是不知道的,所以你就必须得要保证两种情况都可以使用,都正常使用。

virtual的本质

这这个,至于为什么他会在,你如果在分配堆上就不行,分配在那个粘上就就可以,然后你要加Virtue才行呢,这个你要然后溯源Virtue的本质,Virtue本身就是他让你在运行的时候,动态帮你去决定的,就是说它是在运行的时候,嗯,他看你运行的时候,你你选你觉得需要哪一个,它就变成哪一个,它有点像磨的,我感觉它运行的时候,他再给你决定做决定。所以它watch的本质上一个功能是这个,也就是RTS,就是动态实时,就它在运行的时候,动态的帮你看,你你是哪一个,再帮你给给的是哪一个,它是做了一个这样的一个动态的一个运行时动态决定。

如果你不加watch的话,那么编译器在编译链接的时候,它这个时候就能决定你的这个东西是什么了,但是你加了QQ之后,它是在编译链接之后还不决定的,它是到你运行之后才决定。

这个virtual它,它的本质就是一个r t ti机制,就是wrong time template identify就是动态的运行时进行类别识别的一个机制。

其实加了这个virtual其实很简单,你但凡加了virtual,你其实就是告诉他不要再编译时候确定,在运行时再确定,所以你任何方法你都可以加well,这八家well的意思就是说,你不要让他在编译时确定,在运行时确定,那么这个时候嗯,是不是就很好呢?其实不是的,你加了virtual就代表着你的开销增大了,为什么说开销增大呢?因为你如果是编译时确定的话。你从他编译成汇编之后,在汇编里面,他只需要一个跳转指令就可以实现了,跳转就可以实现跳转了,那么它是很方便的,但是你如果是在运行时确定的话,它在编译的时候编译成汇编上,它就不是单单纯纯的做一个跳转指令,它是要到你的那个运行时候做一个判断。

然后你很容易可想而知,他运行时候做的判断是,它会根据一个你的一张表,然后去一一的去找你对应的是哪一个,你找到对应的是哪个,再去再去跳转,所以它多了一个这一步,所以它比在编译的时候就确定要开销大,消耗的资源要多。

也就是说你要经过一个rg ti的机制,就是runtime template identify,你要经过一个这样一个动态动态类型识别,动态运行类型识别才能够找到,所以它开销是很大的,所以你能尽量确定它不用virtual能可以,可以不用virtual也可以的时候,你就尽量不要加virtual。

所以就是说嗯,你加不加virtual就是看你,就是看你想不想要,让它在运行时确定,然后其实这个时候你就可以意识到,其实多肽的本质就是一个动态实时动态实时类型校验啊,动态实时类型识别就是RTTI,你这时候你现在你可以理解的很深刻的多肽的本质就是让让那个那个识别类型识别,不在编译时候进行类型识别,而是在他到了运行时候再进行一个类型识别,再用http runtime template identified去识别它,在运行中去识别,这个就是多肽的本质。所以多肽呢,其实就是用virtual,然后多肽的话,它其实资源开销是很大的,所以这个就是一个取舍问题。

using的打孔

不用using的解决方法,新写另一个方法间接调用,但开销大

你之前说的这一个就是,就是你的那个负累吗?你的鸡肋里面本身是public,但是你通过继承的那个水龙头加个protected之后,你到了子类之后,他就不是public的吗?那这个时候你就不能直接去调用你那个,比如说你就不能直接用第一.speak吗?因为你的speak可能是是几类里面的吗?然后你通过水龙头之后,那你就变成了。一个protected,所以说你不能直接去访问,这个时候你要在你的子类里面重新写一个方法,然后重新写那方法,那那个方法里面包含了你的那个鸡肋的那个被水龙头限制之后的那个speak方法,把它包含进去,这个时候你再去使用的话,那么你就可以你在,你在使用的话,你就用那个,第一点,你用串串联在子类里面那个方法,那么它就可以去用了。也就是说你要新建一个方法,在子类里面新建个方法,然后在方法里面包含基类的那个被权限限制掉了的那个方法,这时候你在幂函数里面,你去调用那个新建的方法,那么就可以使用,但是这样的开销是很大的,因为你多调用了一层函数吗?是不是很容易就想明白,开销是很大的。

使用using

那么如何在这种情况下有更更好的方法呢?更好的方法就是你在那个纸类里面去,去用using把你那个打孔,把你那个你需要的。那个个别的十个里面有有九个是你要权限管控,那有一个是你想放开权限的,那你就用using,你在纸类里面用一个using,然后using animal冒号,冒号,Speak分号,你用这格式之后,你就可以把这个打孔,单独的把这个speak拎出来,使它的权限又重新变回public。

说你在子类里面写上using冒号,冒号animal,啊不不不不,不是using animal冒号,冒号speak分号,这样的话,你在内函数里面用的话,你就可以直接用第一.speak括号分号了,你这样的话可以直接用了,因为它已经变成public了,你用using的,就把它的权限打孔了,把它这个一个单独拎出来,使它变成public,那么你这样的话就可以在内函数直接用了,这样就节省了开销。

访问权限控制本质是编译时编译器帮你检查的,而不是运行时

为什么会这么神奇呢?因为权限这东西本来就不是运行时做的决定,是你在编译的时候,编译器帮你做了决定,所以说你加个u型,那你是告诉编译器这么做这么做这么做嘛,是不是?所以它其实就是告诉编译器,你这个东西就能单独拎出来。所以这其实是一个很简单的一个操作,就是它是在编译器阶段的,就是你加个u型之后,你告诉编译器啊,你要把这个东西单独拎出来,让它public。权限的东西都是在编译器时候,编译器帮你做,层层的判定,是编译器层面帮你做的,那么既然是编译层面层面帮你做,那就好说话了,你用一个你设计,你发明个特殊的关键字是吧,然后跟编辑说一下,那就可以了。

文章目录

函数重载

关于函数重载,它是在编译阶段就已经完成了,就是你这个过程叫做重载决策。

为什么要函数重载

这个运算符重在在c++里面,主要是因为它的面向对象的,那他不是单纯的变量的项相加相减的。举个例子,比如说a加B,他在面向对象里可能是person a + person b,那person的a+ b是什么呢?这个时候他就不是,不是一个普通的一加二加三这样的数字,它是一个两个对象的相加相减,这个时候的话就需要有运算符重载了,然后这个运算符重载呢,他是在一个,他是在那个person对象里面,你自己要给它定义的,你要自己给它定义一个person里面的a加B是什么意思。

之前的inta加B嘛,是a加B是比如说是数字相加,这个其实是我们所有人的共识,所以它在C加加啊,它C跟C加加编译器里面,他就自己就就它定义了这样的一个数字的相加吗?但是你如果是用person相加的话,这个的话不同有不同的看法嘛,所以这个时候你就不能够就没有达成一定的共识的。所以C跟C加加编译器,他不可能在底层帮你做项一加二等于三这样的一个运算符的处理,那你就要自己去定义自己去定义一个运算符存在,告诉自己定义person里面a加B是什么

如何函数重载

这个运算符重载,它有一个关键词叫operator,比如说你现在要定义一个坐标的相加吗?你这个坐标的类叫做coordinate Co operate,然后你,那么你一个坐标a加坐标B吧,然后他就是两个坐标相加,那还是返回值,还是一个坐标,所以你就在那个坐标的方法里面补上,补上一个方法,这个方法,返回值是coordinator,这个Co operate然后,然后再来一个。关键字,Operate,然后你再把那个你要进行的运算符存在,那符号写上去,这里是运算符存在加那么就写的,那么就写成operate加。注意这个运算符存在这个关键字,Operator是必须写的,这个是ci加里面帮你设计好了,你要用运算符存在就要写上这关键字。

这个你如果要用加号做运算符,那么你用operator加这个operator加你中间不能加空格了,Operator加是放在一起的,作为一个一块的,你operator跟加号之间不能有空格的。

然后你的那个重载运算符重载函数是这样写,coordinator operate+,然后括号,然后里面放你要传的参数,然后这里有个细节,因为你是只是把你的那个传进去,参数拿去运算,你不用改参数里面的值,所以你要加入cost,然后这还有一个细节,就是第二个细节,因为你这个里面你不希望他是复制吗?你希望他就直接改它的值,改成实参的值就是。不是,就是你希望用它实参的值,你不给你改,但是你希望直接用,你不希望直接不希望用来复制,所以你就用指针啊,这还有个细节,就是你可以不用指针,你用引用就行了,引用的话就是你传进去,它就只绑定那个那个那个变量了,它一辈子只绑定那个变量了,这个是引用跟指针的区别嘛,所以这就是变成cost coordinate &然后other。

只用传一个参

所以这里有个非常重要的细节,他传参只传了一个coordinate,就只传了一个coordinate and就是他只传了一个cos的coordinate and other吗?它这个就是说它有个本,就是有个主体跟次次要体的,一个一个一个一个理解,就是你的那个X跟X跟那个Y码是不是就是,也就是说你其中一个当does,当它这里面的用,然后其中另外一个你要加的那个当成其他的,就是a加B吗?你把a当成他这个里面本身的,那你就不用船餐,然后你把B当成他,令他他其他的要传进去的,当外面的那他,所以他只用传外面的那个,所以他只传了一个餐。

然后自己本身里面那个a呢,它用this去指向就可以了。

就是你传参的时候是不是传了一个coordinate类的这种类型的一个进去,那就是你要加的那个,然后就是假设说你要a加B,那么那个要加的那个,那个就是B,你要传的是B的那个那个东西进去,然后a你就不用传了,你就就你现在目前的这个类,你就把它当成B,所以只用传一个的。

this->和 other.

所以这里就是a加BC等于a加ba,就是B就是other,然后你要注意,这里this它是一个指针,所以它要用一个,所以它要用一个箭头,然后other,它用的它是个引用来的,所以它用的是点。

前面的.是class的.,后面的点是引用&的.

所以这里就变成temp.X等于does箭头X加上点点X,注意,这里的temp.X跟does箭头X跟other.X前后的两个点X的一些意义是完全不一样的。你那个前面那个temp d X,因为你temp定义的是一个考点,哪的类型的,他她是个class吗?那你可以理解为它也是个struck,那么这个struck的话呢,就是你会发现,就是你会发现这个东西就是就是一个结构体变量吗?所以它没有指向,没有定义结构体指针,没有定义它。你用的不是结构体的指针嘛,变量你用的是变量,所以你就是不点X,然后后面的R的点X呢,就不是这么理解的,它为什么用点,因为你这里传的参用的是引用,引用的话,它虽然是指针的一个简写嘛,但是它不是指针那种箭头方式,它有它的一个C加加的规则,引用的话就是用点去访问,所以就是的点X,所以前后两个点是不一样的意义的。

C++完全支持,java不支持,python有限度支持

关于这个运算符,重在C加加,它是完全支持的,但是Java它是不支持运算符存在的,然后python呢?它是有限度的支持运算符存在的。

C++完全支持也不是所有都支持

这个C加加全面支持,意思是大部分都是可以的,只有少量不可以,但也不是100%,每一个都可以,但是Java它是完全不支持的。

运算符重载的本质是映射

这个C加加的运算符存在本质上是有映射,你写一个operate,加他之后,你就可以映射到那个,直接变成一个家,比如说你是C等于a加B,是吧,他其实本质上是一个C等于a.operate加括号B,它是这样子的,那他就把那个operate加,然后括号那个映射成一个直接简,简化成一个加号,本质上是一种映射,那么Java他如果不支持这种运算符存在的话,他就只能是写成。C等于a.operate加括号B分号,然后这时候他可能在那个方法里面就不是写operate加,可能就是写ad了,反正总的意思就是说,它其实这两个本质上是一样的,只不就是ac等于a加B,跟这个跟这个C等于a加B,跟这个C等于a.operate加B是一样的意思,然后只不过C加加里面它可以支持operate的关键字,把它给重载,把它给重载成一个,把它给重载成一个C等于a加B简化掉,那Java就不行,Java你就只能用之前那种。

运算符重载一定程度体现多态

这个运算服从在从一定程度上体现了C加的多态性的思想,因为你比如说你在person类跟coordinate类是吧,这两个类里面,你如果都用了opera的加啊,代表了不同的含义,那么你在最后用的时候都是写的C等于a加B,那么你在a加B,你在不同的类里面代表的意思是不同的,那这个意思就是多肽了。

为什么返回值也要是coordinate

这里等号的赋值啊,有一个细节,就是它直接牵引,直接关系到你,为什么你要写函数的时候,直接写corda narrator Co or din aries冒号,冒号是不是正常来讲应该是void coordinate就行了,是吧,这个就涉及到你后面的,你这个等号的副职,可能会被另一个等号去用,所以比如说你他可能会变成be,等于括号C等于a,那么你等号里面的东西就要被C,等于a那个返回值要被逼给用掉。所以这个时候你就必须得要在前面设coordinary coordinary冒号,冒号,而不是void coordinate,你用了coordinary之后,你你在那个函数体里面,你就用你用个return新历史,你把你赋值之后的那个返回值的那个指针的取得里面的值就是星号,历史然后返回出来,那么这样呢,你就可以作为一个返回值去赋值,给另外一个等号了去。

那么这样它C等于a括号,它就可以又把它复制到另外一个B上面去,你如果没有写coordinate的话,你直接用Roy的冒号,冒号coordinate什么什么的话。

不是void冒号,冒号coordinary,就是你直接用void coordinate冒号,冒号的话,这样的话你就没你的返回值,就没有就没有返回值吧,没有返回值,那你最后你的B你的你的C等于a括号,这个东西就不能够再为别人用了。所以这个就是为什么你要会,后面会发现很多时候用的都是coordinate,然后coordinate,冒号冒号,然后里面return新历史为什么会这样用?

编译器默认提供了一个等号运算符重载了

这等号运算符你会发现很奇怪,就是它跟加号运算符不一样,你加号运算运算符,如果自己不写,那么它编译器就报错,但是你等号运算符,你自己不写,编译器没有报错,就说你如果,所以这,这从侧面说明啊,编译器它自己是给你写了一个默认的等号的运算符的等号的一个存在的,然后你如果是自己写了等号存在,它就用你人的等号啊,如果你没有自己写等号的话,那么它编译器它自己会给你用个默认的等号,它跟加号不一样的。

为什么编译器默认提供等号运算符重载-减轻工作量

这里解释一下为什么等号,他私家要给你做个默认的,但是其他的比如说加号啊,减号啊,加等于号啊,哪怕是大于小于号的一个判断,他都没有给你默认的,你要自己写等号,他给你默认了,为什么?他从一定程度上是帮你减轻了工作量,因为你实际代码中肯定大量会用到赋值的,这是肯定的,所以他需要他帮你减轻一定工作量,所以他就等号给你做个腹直等等号,给你做一个默认的,但是你如果要自己定义也可以,但实际上它本身给你提供了默认的,其他的他就没提供。

初始化和赋值的区别

初始化跟副词的区别,初始化呢,是说定义的同时进行一个赋值,这就叫初始化。但是你赋值的意思是说,你事先已经初,你是事先已经定义好了,你只是这里是副词而已。

也就是说你在定义一个东西的时候给他赋值,这个就不叫赋值,这个叫初始化,然后你再先是定义了之后,你后面在第二行上面再给他一个负值,这个就叫赋值。

等号运算符重载和拷贝构造函数的优先级:优先拷贝构造函数

这个等号的运算符存在啊,跟这个拷贝构造函数,它会优先使用拷贝构造函数,就是说比如说你用靠近瑞特B等于a,然后这个时候你会发现它调用的是拷贝构造函数,而不是说把那个等号给运算符从载掉,然后把那个a赋值给,把那个a初始化给B,他不是的,所以他不是把a复制,AB,它是用那个拷贝构造函数,它里面写了拷贝构造函数,用了拷贝构造函数了,所以他没有用等号,尽管你在那个类里面写等号的运算符存在,他也不用等号,运算符从载,他用的是拷贝构造函数。

赋值用的就是等号运算符重载

然后你如果不用初始化,单单用腹直就是你一个Co operate b,然后分号,第二行大写,B等于a,那么这个时候呢,就会发现他用的就是运算符存在的那个,那个打印,打印出来,就是那个等号运算符存在里面的信息,就说他有,他用了那个等号运算符存在了

奇怪的一点:还打印出了拷贝构造的内容,原因:返回值值传参

然后这里就很奇怪一点,他还打印了出来了那个拷贝构造函数里面的内容,这个是为什么呢?这个就涉及到一个返回值的。传、传、参问题的就是你的那个等号的运算符,同在你等号的运算符同在,你是不是我们用的那个是是一个return seeing lis的,然后你的那个返回值类型是Colin是吧?这个等号的运算符同在里面,你你的返回值是return in this,它这个是值传递的,就return返回值返。

返回值值传参复制时调用了拷贝构造函数

返回值是值传递的,所以说它是有值传递,代表什么意思?值传递代表它有复制。
你既然是这个是复制啊,复制是不就是拷贝了,你把值给复制了一遍了,那是不是就是,就是你把这个值返回的这个值里面的东西本身就是一个对象return新粒子吗?粒子指向的是个对象吗?你新粒子的话,那你就是一个对象来了,对象的值在多少,那你复制了一个对象的值,那是不是就意味着你复制啊,就拷贝了,所以你这个对象肯定要被拷贝一份,然后拿去用,是不是?所以他就调用了拷贝构造函数,所以他在这里不单单打印了等号,运算符从载还打印了拷贝构造函数,那个打印信息。

他还打印了拷贝构造函数里面,你写的那一条打印信息,就他不单单打印了等号,运算符从从在里面你写的那条那条打印信息,他还打印了拷贝构造函数里面,你写的那条打印信息,就说明她也用到了拷贝构造函数,这个是跟返回值,它是一个值,就是它返回值,他返回的是一个值,然后这个值你要是一个复制的。他是复制了一份,他重新复制了一份的,他是这个意思,所以他就他的他是直传餐返回值是直传餐,所以他是复制了一份返回值,是复制了一份,所以历新历史,他是把那个对象又重新复制了一份新的,然后给了你,所以他是一个拷他用了,所以他肯定是用了拷贝构造函数的。

引出了返回值中要加入&提高效率

所以说从这里我们也看出来了,就是因为你返回值是直传参的,它是复制了一份的,它是把你这个对象又重新复制了一个新的对象出来,再传出去,这里效率是非常非常低下的。

返回值是变量,复制变量,返回值是对象,调用拷贝构造函数复制

说你无论如何,什么情况下它的返回值都是直传餐呢?你如果返回值返回的是一个普通的变量,那么它就把这个变量复制一份,再返回给你。如果你返回的值是一个对象,比如说新,即使是吧,返回值是个对象,那么他就会把这对象复制一份,再返回给你,那复制一份对象就是考,那你肯定就要动用拷贝构造函数才能复制对象,所以他就是拷贝构造函数用了他复制了。所以返回值本质是值传参本质是复制,所以他效率是非常低下非常低下的。

回归正题:初始化用拷贝构造函数,赋值用等号运算符重载

这个是有一些细节带出来的一些更细的细节,但我们现在主要关注的点是,什么时候用等号运算符存在,什么时候用拷贝运算函数啊,拷贝构造函数,就你如果初始化的时候,他用的就是拷贝构造函数,如果你是赋值的时候,他用的就是等号的运算符,重在总结的,总结之后就是这样子。

##为什么等号运算符重载要加const,其他的运算符重载不用加
他这个运算符存在,你如果是加oppo的,家里面加不加控制的都无所谓,但是你包括你其他的,比如说其他的任何的东西加不加控制的,其实没有所谓你家控制的,只是代表你更好一点,就更细节一点,但是运算符存在里面的等号里里面,你如果不加控制的,它就会报错,为什么呢?这就涉及到它的返回值的关系,因为你一般你用。等号的时候,你一般都是要用它的返回值给它赋值的返回值,它从C语言里面去看,它是放到了一个函数调用堆栈里,然后它的编辑器,它自动会把这个你的返回值变成cost类型的,所以你一旦用了等号,运算符重在。

那么你的这个返回值就要被用到了,这个被用到的返回值呢,它是辑,它会帮你自动把它变成cost类型的,那么你如果用它这个返回值的时候,你不写cost,那就会报错类型不匹配,所以你用,当你用了等号的运算符存在时,里面的参数必须要写成cost。

自赋值

这里还有一个字赋值的概念,就是a等于a,你会发现a等于a,你如果放到他的那个等号运算符存在里面,他是不是就是历史点a啊,历史箭头X等于啊,则点X,历史箭头Y等于的点外,那你传进去的,跟你本身是一样的,A等于a的情况下。那么你就会发现他们两个虽然是两个东西,但是他们共用了一个指针,这个东西是不是跟之前遇到了一个情况很类似,就是拷贝构造函数的时候,当你涉及到指针的时候,当你涉及到箭头的时候,是不是它就会出现一个机构函数?

地址一样,带来与深拷贝时类似的析构时问题

它就会出现一个西勾函数,你前面的勾掉了,后面就没得吸够了,因为你用的是同一个地址,所以这里就涉及到深拷贝跟浅拷贝了,你这里的话,你用到你一旦这么用a等于a,那么你就是普通的拷贝,构造函数是就会出现问题,他必须得用生拷贝,所以这样是不合不合适的。就目前来讲,所以当你你为了,所以你为了避免这种情况,你最好就是在等号运算符存在里面。加一个条件判断,避免他这么用,就说如果他这么用了,你就报错,或者说打印个什么,说不能这么用,就不能a等于a这么用,比如说你打你说一个就加个条件,判断符,比如说你加个条件判断if this不等于and other就说如果他的。

就说,如果它的历史,这个地址不等于它的and other这个地址的时候,你才执行你的等号运算符的存在,否则你就不执行,就说你保证它不要出现a等于a,这种情况,因为你出现a等于a就出现了,那就出现了声拷贝,你这里用的只是普通的浅拷贝,所以是不能用声拷贝所以避免出现这种情况,你就加一个条件判断,不让它出现这种情况。

而且换另一个角度讲,你自赋值a等于a,它是不是就是同一个东西了?那你就没有必要说你的这个等于你的,你就没有必要说再用力使箭头X等于啊,De dian X,历史箭头Y等于点歪了,是不是你不用执行这一步了,所以你条件判断,如果他一旦抢,一旦它相等,你就不用执行这一步,你直接一个return新历史就行了,是不是因为你a是等于a的,你没有必要再一个再复再重新写了,是不是。你直接你你你既然你这么傻,用了那个a等于a本身这就不合适的,两个都是都是一样的是不是,那你就不用执行它等号预算符存在里面东西了,你直接一个在等号预算符里面,把里面的你那些操作全部都都一个条件判断不执行掉,然后后面直接保留一个return新例子,你把一个新例子再返回出去就可以了。因为本身是同一个东西嘛,你返回出来返回你要用的话,你就返回出去就行了,没有必要操作是不是。

解决方法:直接return *this,直接用值

就是说你把里面的操作都条件判断去掉,条件判断不执行,然后直接直接返回出来就行了,因为你a等于a嘛,同一个东西了,所以直接返回出来就行了,所以直接return this就行了。

为什么会看到别人写等号运算符重载时都会写条件判读那,就是为了处理当有人写a=a时的情况

所以你会发现很多写等号运算符存在的都会写上这个条件,判断if this不等于N的other,为什么呢?因为你会因为你写类的人,你是不知道他用类的人,他到底会不会真的会出现这么一个低级的一种写法,A等于a,完全没有必要的一种多余的写法,它一旦这么写了,你没有这么处理,那么它就会出现一个深拷贝的一个问,带来一个深拷贝的问题,所以你为了避免出现这问题,你就,你就提前把这个东西给解决掉,让他当他出现这问题之后,你跳过不执行里面的那个操作,直接返回出来。

返回值中加入&提高效率

那么你要如何避免它的返回值,然后是值船餐,然后它要复制从而降低效率呢?那么你就要把它返回值改成引用,比如说你这里,你其他任何东西都不用改,你也是return,新历史这里也不用改,你唯一要改的是把它的返回值改成那个Korean air e de and加个end附上去就是在函数开头,他不是写了返回值吗?你在那返回值,那里加个end就是corda nary coordinate and coordinate and。

C++引用的发明就是从这个等号运算符重载而来

C加加的引用,就是来自于这个运算符重载里面的那个传参的尴尬,发明出来的就是就饮,就是C加加里面本身那个你运算符重,再比如说那个等号等号的运算符虫在你后面的传参,你按理说应该传指针是吧,那你传指针之后,那你家里有对象吗?你对象不是指针呢,你对象不是指针的话,那么你你下面用的时候,你传参传的是。是指针变量的话,你后面是不是对象,你要要取个星号不,你要不不不不,你对象要取个取个and符取他的地址才行,是不是那你后面肯定要写,肯定不能写C等于a了,那肯定要写C等于na了是吧?因为你要取拿的是and的那个地址,是不是啊,你要拿的是a的那个是吧,你要那a的地址是不是。

但是你这样的话,那你你右边就是a了,你左边是不是应该是and c才对,因为你的数据类型要一样吗?那and c那你C也要去变成end,那就是and and c等于a了是不是?那有些人他可能没有注意到这点,那就写成a等于C了是不是?所以说a等于CAE等于en de re c等于a了是吧?所以C等于AC等于a and c and and a,这三种情况很多人都不一定能搞得清楚是不是?然后加他早期的时候呢,可能就。都允许你这样写,都允许你是对的,因为他怕你写写错嘛,所以他说允许你这三样写都对的,那这样他就尴尬了,那指针是不是概念就混乱了是不是,这不严谨是不是就是很很尴尬是不是?所以这个时候呢,没办法,他就发明了个引用,就引用就从这里出来的,你把传舱你传引用的话,那就可以直接是C等于a给了个新概念,给他。

你给了一个新概念给他,你遗传引用的话,你就可以直接用C等于a了,不用FC等于a了,这个新的概念引用就出来,就这样出来了,那最后变成了从NGC等于a,然后C等于a或C等于直接就变成C等于a最好就可以直接用C等于这样就不尴尬吗?因为你用了这个新的概念吗?不是指针的概念吗?是不是,但是他早期由于也是支持这样子的一个情况了,所以你按的C等于a,还有C等于a都可以,所以这就是一个比较隐晦的一个尴尬的地方。

其实你就算传的是指针,你按着C等于AC等于ACDA也可以啊,C等于a的话,可能不可以,哦好像是可以的,Cdma也是可以的,反正好像用纸船纸的话,这三个也都是可以的,但是就是比较尴尬嘛,不太对是吧,但是他还是可以,所以他必就是为了避免这种尴尬,他最好最好还是引了一个新的概念出来,要引用引用,引用的就不是指针的,那你可以用引用去解释这个现象,那引用的话就可以用直接CD了,从语法上面就不会说很尴尬很很别扭了,你用引用嘛,那就可以直接C了。

Java里面的字符串,它是比较特别,它是固定的,如果你要字符串,你要变大,那你还要重新申请一个字符串,然后把之前的废掉,然后用个新,用新的大的字符串,然后企业家好像是这样子,就是他用一个数组指向那个字符串,然后放在堆里面,这个还不是太清楚,后面看看。

C++默认的析构函数没有delete,所以一旦用了new就要自己手动写析构函数

这里再回顾一下,C够函数啊,西勾函数什么时候该写什么时候不该写。如果你分配的是静态内存,那就不用写析够函数,但是你如果分配的是动态内存,你要申请堆了,你要堆里面的东西,那你必须得要用C够函数,把你的那个堆也是放掉,为什么呢?因为C加加里面的si go函数是默认没有delete的,这是绝对千万十分要注意的,一.c加加里面默认的析构函数是没有delete的,所以你一旦用了动态内存分配,你用了的利特,不是你用了,嗯那你用了那个六,你申请的那个堆,那么你一定要自己手动写析够函数,然后把delete加进去。

数组的delete要加[]

然后还有一点就注意的是,你如果那个你要释放的内存呢?是一个数组,那你就是不能用直接delete了,你要delete中括号,你要用到这个关键字,就它关键字就不是delete了,而是delete中括号。

++a和a++的运算符重载是一个特例,先跳过

这++a跟a++是一个特例,就是他在做运算符重载的时候是个特例,他前面的变成了后面的,后面的变成前面的,这个的话遇到了再说吧,后面当你真正工作时候遇到了,然后再看,再回过头回过头来看。

友元函数

下面说一下友元函数,友元函数的一个初步理解,就是说你的我们目前现阶段接触的函数基本都是写在类里面作为成员方法的,然后你如果要把这个函数放到类的外面去,就是完全的累的外面去,他已经完全不在累的外类的里面了,这个时候呢,这种函数在C里面是否存在呢?其实是存在的,那么你这种函数你到时候要调用时候怎么调用呢?这个时候就涉及到了一个有元函数,你可以把它的这个函数的名字放到那个类里面去,然后在那个函数名字前面加一个friend关键字,就是说你写个friend关键字,然后把那个函数名字写上去,然后还有它里面的括号传参也写进去,然后后面加个分号。

这个时候呢,虽然这个函数它不是本质上不是这个类里面的成员方法,但是它是作为一个有原函数存在,你这个类也是可以使用的,也是可以使用这个函数的,虽然它是在外面的,但是你用个friend关键字,就把它作为一个有元函数就可以使用。

运算符重载的第二种方法:友元函数,不是主次而是平行了

用运算符重载的第二种方法,就是用友元函数去实现,你用友元函数去实现的话,那么你里面传的餐,就是你要把它的类型放进去,就是说你这样子的话,你你一旦这样子的话,你就不是说把其中的一个作为例子,然后把另外一个作为着,然后你这样的话就是没有主次之分了。这样的话就是平行的两个两个运算符,存在你之后的两个要运算的东西,你是放到,你就一起,一起放到那个参数船舱里面去了,两个是平行的关系的,不是主次的关系。

什么时候用第二种友元函数写运算符重载

那么什么时候适合用独立函数,然后类里面再去把它作为一个friend友元函数去做,运算符虫在什么时候适合直接写在类里面,作为运算符虫在呢?这个就涉及到刚刚说的主从跟并列的关系,比如说你如果是C等于a加B的情况,那么你右边的a加B,他们两个应该是一个并列的关系才对,那么这样的话就应该是用独立的函数,然后。再作为一个有元函数,把它放到里面去,为什么呢?因为有原函数这个这个独立的函数,它里面的传参是传两个参的,这两个参之间是相互平等,相互相互并列的,但是你如果是用之前第一种方法,那种运算,写在类里面的运算符存在的话,它是有一个主从关系的,这样的话就不太恰当。

不过,这一些都是一些没有100%说一定要按照这这么做的东西,就是说这个不是一个讨论的焦点,就是你想,其实这个运算符存在本身就不是太有意义的东西,没有必要在这里说宽了,这么死,到时候怎么用就怎么用,也就是说这里不是问题的焦点,也不是一个一定要讨论出一致的结果。

c++并非所有都可以运算符重载

然后C加加里面并非所有的东西都可以做运算符存在,比如说size of啊,然后点那冒号,冒号啊,Sharp,符号这些东西,就是除了这些东西之外,还有一些其他的,就是说,有一些东西在C加加里面是不能做运算符,重在的就是它不支持运算符存在的就有一些。

静态类

什么叫做静态类,就是这个类里面只有静态成员的类,就叫静态类。

c++没有静态类

但是注意一点c加加里面,它是没有静态类这个东西的。

静态成员变量和普通成员变量的区别

区别一:静态成员变量需要在类外面重新定义一次

有关这个静态成员变量,它跟普通成员变量的区别是,你除了在那个类里面定义了一次,你还要在在类外面再定一次,比如说你在类里面已经定义了一个static inter height分号,然后你除了这个之外,你还要再累的外面再定一次inter person冒号,冒号,Height分号,就是说你那个静态成员变量还要再累的,外面再重新定义一次。

区别二:静态成员变量不用创建对象就可以用

静态成员变量和普通成员变量的区别是什么?静态成员变量,你可以不用这个类创建一个对象,然后再用对象去调用,你可以直接用它这个类,然后冒号,冒号,然后去调用,比如说你这里有一个静态成员变量,然后你。

比如说你这里有一个静态成员变量height,那么你在main函数里面你可以直接person,冒号,冒号,Height等于25,分号直接可以这么用,你不用再一个,再用这个person这个类去创建一个对象,你直接用这个类去用它就行了。这个就是静态成员变量,后面的包括静态成员方法也是一样的。

区别三:静态成员变量创建的所有对象都是指向同一个,共享

这里有一个很神奇的事情,就是说你你创建了五六十个对象,然后你这个对象都指向一个成员变量,比如说你创建了P1P2 P3 P4 P5 P6 P7 P8P9,这么多的对象,然后你都指向那个类里面,同一个成员变量height,那么就有问题了。这些指向的是不同的,害特还是相同的害特,它指向的是同一个害。它是共享,包括你的静态成员变量,你直接用类冒号,冒号去用,你没有创建对象,这种情况下它也是共享的,就是说都是共享,指向的都是同一个成员变量height。

由此导致静态成员不能用构造函数初始化,也不能在类里面初始化,智能在类外面

注意我之前说的对象,它所有的都是共享的,这个是针对静态成员来说的,如果你不是静态的话,是不是共享的,所以这也是决定了为什么你的静态成员变量,你不能在构造函数里面,说实话,为什么呢?因为构造函数它是独立的,你你构造了100个对象,这100个对象不是共享的,但是你静态呢。你静态它是可以共享的,所以这跟构造函数是矛盾的,你不能用构造函数的,所以你那个静态静态成员变量,你是不能用构造函数去初始化的,然后你的这个静态成员变量啊,它也不能够在类里面初始化,它必须得在内外面进行初始化。

由此又导致静态成员变量不能用成员初始化列表初始化

所以同样这个静态静态那个成员变量,它也不能用成员参数列表去初始化,为什么呢?因为你这个初始化列表就是在构造函数里面的嘛,你构造函数都不能用来初始化,那你怎么可能用初始化列表呢?

普通成员方法访问静态成员变量

关于普通的成员方法,能否访问类里面的静态成员变量,是可以的,而且有三种方法可以访问,一种是你在普通的成员方法里面用

person::height=333;

第二种是。直接

height=333;

第三种是用this

this->height=333;

,这三种都可以。

但是建议用第一种,为什么呢?因为这种方式,人家一看就知道你用的是静态成员变量,而其他两种别人看不出来,以为是普通的成员变量,所以最好用第一种,让别人一看就知道是静态成员变量。

静态方法不能访问非静态的成员变量和方法

但是呢,在静态的方法中是不能访问非静态的成员变量跟成员方法的,就是在静态的方法中,你只能访问静态的成员变量跟成员方法。

硬要访问只能靠传参,但是已经很间接了

你如果硬要在静态的方法里面去访问非静态的成员变量的话,那么你只能通过传参去实现,就是你在那个成员静态的成员方法的那个船舱的括号里面传入你的那个非静态的成员变量。

然后用你传进来的这个成员变量,在你的静态的成员方法里面去使用。

但是你这个通过传参的方式去访问,这已经是完全是另一个维度了,就是说它已经是非常间接的方式,任何的东西都可以通过传参去访问的,所以其实抛开这个不讲,那么就是这样子,就是说静态的成员方法,就是本身是不能访问非静态的成员变量的。

变量是什么时候被分配地址的?链接时候

所有的变量都是在链接的时候给他分配地址的。

就是说,所有的变量分配给他分配内存分配地址的时候,都是在链接的时候,然后程序在加载的时候就落实到这些内存里面去。

静态成员变量和方法从生命周期角度看等同于全局变量

这个静态成员变量跟静态成员方法,它的它的这个生命周期啊,完全跟全局变量,跟全局函数是一模一样的,完全等同于全局变量跟全局方法,在生命周期这里来看。

换句话说就是通过全局变量的方式实现的

也就是换一句话说,C加加里面的静态成员方法跟静态成员变量,就是通过全局函数跟全局全局变量去实现的。

普通成员变量和方法就比较像局部变量或者用堆申请出来的变量

然后他这个普通的成员变量跟成员方法,它是跟对象绑定的,你每生成,每创建一个对象,就有一个新的普通的成员变量和成员方法,你美迪丽的一个对象之后呢,这个成员普通的成员变量跟成员方法就不在了,这就很像局部变量的那种思想,是不是就很像那种局部变量,你用的话就有新的,不用的话就是迪丽的这样。啊,这个也很像那个mylove申请内存,就是这个也很像,就是堆嘛,就是堆内存,用的时候申请,不用的时候就delete掉,是吧,它跟全局变量就不一样,全局变量是你程序一开始就有要等个程等等等,到整个程序结束才没有是吧,它不是这样子的,它是局部的。

就是说你这个对象,如果是你没有用六根迪利特,你用的是普通的一个,直接加分号那种,那他就很像站里面的嘛,他就是占了他就很像局部变量那种感觉,然后你对象如果是就说你你什么东西很像局部变量的感觉呢,就是说你这对象的绑定的那个普通的成员变量的成员方法吗,很像局部变量的感觉嘛,然后如果你是。那个对象是用六根迪特,然后分配在堆内存上的,那么你的这个对象对应的绑定的那个普通成员变那个方法就很像,就是也是就分配在堆内存上面的那种感觉嘛,其实不是那种感觉是完全就是嘛。

区别四:静态成员在类里面不占内存,简单说就是没有在里面

然后静态成员变量啊,他在这个心你创建的对象里面,是不占存储空间的,就比如说你用这个person有个破损的,这个累吗?你这个类里面有一个静态成员变量,然后叫做害特是吧,然后你现在你新建了一个对象,根据这个person创建一个对象,那么你根据这个person创建了这个对象,他是不是也包含了这个静态成员变量在里面,是吧,但是他这个。它不占内存的,就这个对象里面,它虽然有这个类里面这个静态成员变量,但是它不占内存,它你就你新建那个对象嘛,这对象是占内存的嘛,但是这个对象里面虽然写了静态成员变量,但是它里面的静态成员变量是不占内存的,简单说就是

简单说就是它不占不占内存嘛。

就是形式上好像写上去了,但其实没有分配内存

简单说就是它形式上面写上去了,好像是有的里面实际上他是没有给他分配内存的。

因为本质上它自身早就已经创建了内存了

因为从本质上来说,他这个已经创建了一个内存吗?你,你们因为你这个本质上已经有内存了,他是跟全局变量的逻辑是一样的,当你这个类出现的时候,定义的时候,他的类里面的那个静态成员变量就已经作为全局变量申请内存了,那么你再新建一个对象的时候,他是作为一个局部的吗?局部变量那种那种感觉的吗?他没必要再给你这个全局变量在申请内存了,因为你之前已经申请过了,他没有必要了,所以他是在让你从轻新建一个对象的时候,它里面的那个静态成员,静态成员变量跟静态成员方法,它是不占内存的。

静态成员的用处

静态成员变量的用处

共享计数器

这个静态静态的这个怎么用呢?他今属于就在于这个,比如说你呢,Pad加加,你打开一个窗口,两个窗口,三个窗口,四个窗口,你每打开一个窗口,你要记个数是吧,然后方便,你最后说,如果你你关到最后一个窗口的时候呢,技术变成零,它就自动把整个程序关掉是吧?那你这个技术就应该就应该做成静态类型的静态,静态的一个成员变量要独立出来吗?要共享吗?你每打开一个就加加,每打开一个就加加码,最后技术统计你打开多少个窗口,所以它是共享呢?必须得是共享是吧,所以他静态成员让他用在这里,这是其中一个例子。

即公共的东西,就用静态

简单来说就是处理所有的人的,公共的东西的就用静态共享,比如说你创建一个人的类是吧,你自己的身高,你自己的体重,这就这就不是静态的,然后你可能这个人的这个类里面可能还你,还要加上一些其他的成员变量,比如说全国的人的平均身高,是不是你可能也要把这个这个产面加到你这个人的这个类里面去,是吧,但是呢,这个就是共享的了,不是只有你和只有你才有,这个就作为静态。成员变量静态成员方法呢,就是处理这些共有的东西,就是说静态的东西就是共有的,共有的一般就是用的、用的静用的静态的,那么刚刚说的是静态成员变量。

静态成员方法的用处

处理这些共有的东西的时候,就用静态方法

那么静态成员方法是什么呢?静态成员方法用在哪里?静态成员方法就是也是用在处理这些共有的东西的情况下用。

我们平常讲C加加是面向对象和面向过程的结合体,它并不是完全的面向对象,这可怎么解释呢?这也可以,其实也可以从静态成员的也就可以,其实从静态这里可以看出来,因为你这个静态,当你没有创建对象的时候,你也可以用,是吧,就你没有创建对象,你直接用类的那个东西也可以用啊,他根根本没有对象就可以用,这就是违背了面向对象吗?他不是面向对象,所以他C加加就是并不是全部面向对象,就在这里体现了。

这个C加加的,它不是纯面向对象,这里只是其中之一个的一个体现,其他地方也有体现。

这个C加加的,它不是纯面向对象,这里只是其中之一个的一个体现,其他地方也有体现。

静态呀,本质是把全外面的全局封装到了类里面去。

就是说它本质上是全局的,只不过你把这个外面的这个全局,把它封装到类里面去,作为类里面的东西。

这个静态变量跟静态方法,跟类里面的普通面的普通方法,它们是有质的差别的,那是100%完全不一样的,你要写的时候,你要完全认清楚,它们之间是有质的差别的。

这个静态变量跟静态方法,它本质上是其实你本质上是全局的,就是他为了他,那他为什么要写到类里面,它就是为了你能够最后你用的时候,他能够跟这个类关联起来吗,这样才能好用吗?所以他就跟这个根据这个类有关的静态的全局变量,就是根据这个类有关的全局变量,放到这个类里面去,仅此而已。所以他这个跟普通成员变量成员方法是完全是两码事,是有质的差别。这个静态的本质上来说,就是他把全局的给他放到类里面去了,让他好用,嗯,仅此而已,他唯一的跟跟这个普通成员变量,跟成员方法的唯一一个相同点,就是他们都在这个类里面,这是唯一的一个相同点,其他的都不同,其他都是有本质不同的。

关于蜜蜂是什么意思,就当一个类,它是蜜蜂的时候,意思就是说我们不希望它这个类作为父类被人继承。

静态类属于一种密封类

这个静态类就是属于一种密封类。

这个C加加里面的静态成员变量跟静态成员方法,有点像,那个C园里面的静态局部变量,你去仔细品是真的很像的,那静态局部变量的话,他是就他,他是只是限限定于在这个这个局部局部内的嘛,但是他又不希望他的这个东西会会在随着它的生命周期去去死掉吗?那么他就用了个静态局部变量,然后静态静态成员变量跟静态成员方法呢,他也是。他希望这个,希望这个方法或者变量,他在这个类里面的是吧,希望他能够在这个类里面,然后不被其他的用是吧,就只是属于这个类是吧,但是呢,他又不希望这个静态程序变量跟静态程序方法,那么快的就随着他的生命都期死掉,因为他你如果是创立了个对象的话,你对象你如果用完了,那你就滴滴掉吧,那就死掉吧。

但是他如果这个静态成员变量,静态方法,成员方法它属于类的话,那么他就是累的话,他就他就不会随着那个对象的迪丽特尔死掉,他就还是会在的,所以它这个就很像,就很像那个静态局部变量的感觉,就是他也是放放到一个范围里面去,但是它它不会随着正常的那种生命周期,而死掉,他就是还是会在就类似于把它做成全局变量的这种功能,但是还是圈在一个范围里面去,这就很像那个静态局部变量。

上面讲一下静态类,首先你要知道西加加,他本来是本身是没有静态类的,它不支持静态类的,他就说他不直接支持静态类的,就是这样,为什么什么叫做不直接支持啊,就是说他没有静态类的一些功能,比如说你如果是在Java c sharp里面,你如果你把这个静态类说明为啊,你把这类说明为静态类的话,当你做出了一些静态类不允许的事情的时候,他Java c sharp就会报错,编译器阶段就给你报错,但是西加加他不会,你如果把他家家里面你认定这个,你把它设为静态类,那你做了静态类不允许做的事情之后,他编译器不会给你报错,这月的C加加不直接支持静态类,但是你是可以写静态类的。

静态类的话就是它就是直接在那个class前面加个static吗?就作为一个静态类的,静态类里面的话,它是什么叫静态类,就是里面只包含静态成员的类,它不包含其他东西,所有的东西都是静态成员,无非是静态成员变量或者静态成员方法,所有东西都是包含,这个只是招,只是包含这个,那么这也就决定了他不能有相关的构造函数,因为你一旦要构造函数的话,那你的那你是不是就等于可以创建对象了,那就等于是不是静态的。是吧,所以他就决定了,他不能有构造函数,还有一些其他的一些特殊的一些规定,那么这个静态,这这个这些规定的话,因为你没有实际接触过,你也不知道他具体要要做什么,你就这个东西你可以先跳过,不用管。我们现在主要知道静态类,它主要是用来干嘛的,它主要在c shop跟Java里面,它主要是用来写一些纯方法的类。

就说他写一些纯方法的类,这些类的话呢,放的都是所有都是一些方法纯方法,然后呢,我不希望这些这些类呢,是去创建创建对象,他这个方法的话,你就拿来就可以直接用,就是它是个纯方法的类,就是怎么说,就是纯粹就是把所有的方法集中在一起,变成一个类,然后这样的话,它这个静态类的话,它虽然不能继承,但是你可以用它吗。用它的话就是直接用它里面的东西,对静态类它是不能被继承的,所以它也不存在说什么private protected public这些概念,它没必要,因为它不是为了继承而存在的,它就是把你就是把一些纯方法,然后把它放到一个类里面去,就是这样子,然后你那些方法的话,你拿去用。

这种静态类它是不能用来继承的,就说他一般都是用来做一些,就是只有统一一个的一种定律,比如说你说一个物理定律,你不可能有物理定律一,物理定律二,物理定律三,他可能只有一个物理定律,那么你就把这,那他就就不能被继承,你不能在他这个基础上进行拓展是吧,那么这个时候它就是一个物理定律,里面都是一些纯方法,是吧,这个你就很容易理解,拿这个例子一举,它里面实现的这些纯方法,你直接拿这方法来用就行了,你不可能在这基础上做什么扩充,它就是一个物理定律,把一些纯方法,物理定律,还有一些纯变量,纯纯粹就是静态变量的物理的物理定律放到一起,那么这个时候你拿来用就行了,你不用再拿来,他拿来做什么继承什么的。

在这个静态类里面,它是完全面向对象的,然后静态类里面,它是它有一些就是注意事项,首先它不能够继承吗?他是一个密封类,然后他所以他也不能够,没有然后,所以他对于那些所谓的public和protected和private,这些已经没有意义啊。第二点就是它不能含有构造函数,它不能被实例化,所以他也不能含有构造函数啊,这是它的第二点。正因为它没有不能构造函数,所以它进更进一步的什么接口也不行,也不能走任有任何的接口,不能实现任何的接口。然后,但是你要注意一点,第三点,它虽然不能够有构造函数,但是它可以有静态构造函数,静态构造函数是什么意思?就是你可以理解为就是全局变量的那种感觉,就是它,它可以在那个类里面写一个静态构造函数。

然后在那个静态构造函数里面写上你的一些类似于全局变量,类似那种那种的初始化定义,就是你在里面写,然后他就会把他分配到全局变量区,全局区就他可以有一些静态构造函数,这是个很特殊的东西,之前没说过这种,他可以,他可以写静态构造函数,在里面放一些全局性的东西,全局性质的一些初始化的初始化的东西可以放到里面去,这个就是她要注意的几点,然后它的用处的话刚刚已经说过了。

这个静态构造函数,简单说就是你如果没有用这静态构造函数的话,你是不是就是可以把他那些初始化,就像我们之前用的时候,没有静态类的这种概念是,我们是出要初始化的时候,是不是要把他那个声明写到类里面去,然后把他的这个定义写到外面去,是吧?作为一个全局变量吗?就是就是你,你是不是要把那个定义写到外面去啊,初始化写到外面去,写到那个外面去,那你用了静态构造函数的话,你可以把你的这个定义啊。把这些定义呀,然后初始化作为全局变量的一些数,全局区的放到全局数据,需要一些变量啊,放到这个静态构造函数里面去啊。这个就叫静态构造函数,它这个静态类是可以有静态构造函数的,但它不能有普通的构造函数。

这个静态类的出现,本质上是做了一个完全的面向对象,就是他把所有的静态的那个成员变量和成员方法呢,都封装到一个类里面去嘛,形成了一个类是吧,这是一个对于完全面向对象的一个完善整,本质上来讲就是这样子。

C加加它是不能优雅地实现这个静态类的,只有高级一点的语言,Java后期shop,它才能实现这种完全优雅地实现。这种静态类,就比如说你C加加里面,你是没有这种静态构造函数的,你要做静态构造函数,你必须想一个比较绕弯子的方法,就把它这个方法,把这里这个普通的构造函数啊,放到private里面去,你这种方式去实现构造函数,静态构造函数,它不能直接用静态构造函数,他没有这个概念,他没有这种语法,所以你要巧妙的用,就是要把他这个普通构造函数放到这个静态,放到这个private里面去,所以他就不是太优雅,他不是原生支持的,静态类的。

Java跟c shop怎么优雅地实现呢?就是他在那个类前面也可以加STATIC8遍static class吗?那么你class前面加static,那么它在语法上面就说明了你里面的类的里面都是静态的了,所以里面你的一些静态的成员变量,成员方法前面就不需要,就不需要加static,这就很方便了,不用每一个成员变量与方法,前面都加static了,你只要在class前面这些类class前面加static就可以了,这就是比较优雅的家里面就不行,他就没有能够这样子,没有支不支持,不直接支持这个静态类。

这个静态类吗,刚刚说了,他你要当,你要用一些纯方法,然后把那些纯方法作为静态成员方法放在一个静态类里面去,是吧,那么这些纯纯方法可能是一些物理定律,或者一些对大自然的一个最自然的一个,一个一个一个总体是吧,它是一个只有一个的,他不能有两个三个了是吧,所以他就把字和治,和所以这种东西,它就适合作为静态类,不可能有物理定律一,物理定律二,他物理定律,把所有的物理定律、方法都放到一个类里面,作为静态类也是。那么他怎么用呢?你就不需要创建对象了是吧,他你他是事业纯方法,或者纯的一些静态的一些东西,纯的静态变量之类的东西的,你不需要再创建对象用了,你直接用他这方法就行了。所以这就是静态类的一大用法,你不需要创建对象,他也没有给你提供创建对象的一个功能。

你就是直接用它的方法就行了,用它的方法跟变量就行了,你直接用它的变量的方法,你不需要创建新的对象,在这个对象的基础上再去用它,没必要,你直接用它方法跟变量就行,这就是静态类,在高级语言里面,Java实shop就这样用,把这纯方法放里面去,你不用再基于这个类做,创建一个对象,你直接用它的方法跟变量就是这样子。

友元函数不管放到public,private,protected中都是可以的

有元函数的话,你只要把它放到那个类里面去就行了,你不管它是放到public里面,还是放到,放到protected里面,还是放到private里面,它只要能够放到那个类里面去就可以,你只要看到它有就行了。所以你当你看到有人把有元函数放到private里面去,其实这很正常,不用太太惊讶,这个东西是很正常的,它只要写到里面去就可以用了。

有源函数的访问权限是跟成员函数一样的,但是你要注意,它不是在类里面的,它不属于类,它是属于类的外部的,它只是加了个friend,然后使它变成了有跟普通的成员有一样的访问权限。这样的本质上是类的,外面的东西,它不属于类里面,它是,但是它反问权限,你加了friend变成有元函数之后,访问权限是跟类的内部的一些成员是差不多的。

是差不多是实际上是一样的。

有原函数,它实际上是单向的,就你在有元函数你那个那个函数里面,你可以访问那个,那个你你的那个类,但是呢,你那类里面,其他的方法是不能访问你写在这个类里面的,有元函数的,总之就是单向的,你只能是有缘函数去访问它的那个类,你不能在类里,不能在那个类里面的方法去访问的有源函访问那个有原函数的。

这点你要十分注意,就是如果你这个类里面它就有public public里面那些东西,你是可以不用有元函数去访问的,所以有原函数,它本质上是为了解决你访问不了类里面的private跟protected的里面的变量而设计的。

在使用那个设计类的时候,尽量不要让多个类之间相互继承,相互关有关系,如果可能的话,尽量避免这种互相引用,就互相这个类引用那个累,那个累,引用那个类的,这种情况当然不可避免,当然在某些不可避免的情况下,必须得这样用,那没办法,但是我们在设计的时候尽量不要这么设计,就比如说这个友元函数啊,这就涉及到一个问题。

就是我们之前刚刚说有个函数,那个函数是定义在外面的吗?都不是在任何一个类里面,但是呢,我们现在如果要把一个函数放到这个类里面去。

就是说有两个类,然后比如说有class类a,还有class类B,然后这个class类逼里面有个函数叫做F函数,这个F函数的话,他如果要用那个class类a里面的成员变量的话,那是不是不是,他如果要用class类a里面的那些不是public的成员变量的话,那是不是就要把这个class类逼里面的这个F函数,把它作为class类a里面的一个友元函数。

就是说有两个类,然后比如说有class类a,还有class类B,然后这个class类逼里面有个函数叫做F函数,这个F函数的话,他如果要用那个class类a里面的成员变量的话,那是不是不是,他如果要用class类a里面的那些不是public的成员变量的话,那是不是就要把这个class类逼里面的这个F函数,把它作为class类a里面的一个友元函数。

然后这个作为一个友元函数之后,然后你就会发现那你这样的话,你在声明的时候你该先先声明谁呢?是不是就是你如果事先声明了class类a,那么,但是你class类里面你用了class类B,但是你class类的都还没有声明,那你怎么用class类B?如果你事先声明的class类B,那么你的class类a里面就是如果事先声明的class类B,那么你的class类a都没有声明的话,那你的class类class类B里面怎么用class类呢?是吧?所以这就是存在一个谁先声明的问题。

这个就是很麻烦了,就是就是互相的一个引用了,这个时候呢,就是需要用一个类的前置声明去解决。

类的前置声明

然后类的前置声明,注意这里有个最关键的点,去理解类的核心声明的一点,就是它的类的核心声明,它是只能够是识别它的这个类的名字,这个地址的这个名字,这个类型的,它的它不能够识别出一些细节出来。就你声明一个类的前置声明的话,它只能知道这个类的名字哦,它叫一个class类a啊,但是你不能知道class类a里面的一些细节。

所以一般用类的前置声明都会配套的使用引用或者是指针,而不是直接单纯的创建一个对象,因为你如果在交叉引用的时候吗?比如说你刚刚说那个有原函数,那里交叉引用时候,你如果是生利用她生了一个对象,或者是做一些可以要用到类里面的细节的东西,那么你用类的前置声明也没有用,因为类的前置声明他只知道一个函数地址,只他只知道一个类的名字吗?所以你一般你用类的前置声明的时候,你的交叉引用也只能是传指针或者传引用,因为传指针的传引用,他只是知道这个指针类型指向的是一个这个class类a类型的是不是?然后class类a类型呢,他不会不用知道细节,你只要一个类的前置声明就行了,就是你的这个指针的话是这样子是吧,所以你就会看到很多人在交叉引用的时候,或者其他引用的时候,类里面一般用的引用的时候一般都是传指针或者传引用。

所以一般用类的前置声明都会配套的使用引用或者是指针,而不是直接单纯的创建一个对象,因为你如果在交叉引用的时候吗?比如说你刚刚说那个有原函数,那里交叉引用时候,你如果是生利用她生了一个对象,或者是做一些可以要用到类里面的细节的东西,那么你用类的前置声明也没有用,因为类的前置声明他只知道一个函数地址,只他只知道一个类的名字吗?所以你一般你用类的前置声明的时候,你的交叉引用也只能是传指针或者传引用,因为传指针的传引用,他只是知道这个指针类型指向的是一个这个class类a类型的是不是?然后class类a类型呢,他不会不用知道细节,你只要一个类的前置声明就行了,就是你的这个指针的话是这样子是吧,所以你就会看到很多人在交叉引用的时候,或者其他引用的时候,类里面一般用的引用的时候一般都是传指针或者传引用。

这个这个传指针传引用的出发点就是由于类的前置声明,它是只能是一个简略版的,它不能知道类的细节,所以你用类的前置声明的话,就大部分都是要传指针,传引用,不是大部分是你必须。

然后这里补充个小细节,就是你如果在那个class类a里面,你要把那个class类逼里面F函数作为友元函数,那么你要这样写friend b冒号,冒号F就是你要你要肯定要用个B冒号,冒号的为什么?因为你的这个如果不用逼冒号,冒号的话,那么他有可能以为她是他有可能以为他是累的外部的吗?QQ语言可能以为不是逼的,他可能以为是既不是a的也不是逼的,而是在所有的类室外的那个函数是吧?所以为了避免引起协议,你要加上be冒号,冒号变成friend b冒号,冒号F。

友元类

除了友元函数之外,还有友元类,比如说刚刚说的class a跟class b,如果想要让class be成为class a的友元类,那么直接在class a里面写一个friend class b,这样的话就可以的,就可以成为a的友元类,那么成为a的友元类之后,Class b里面的所有方法,所有函数都是class的友元函数。那么什么时候用有源类呢?就是,假如说你的class b里面有100个方法,如果你有80个都是要。要作为class a的有源类去使用的啊,Class的有源函数去使用的,那么你就把这class b作为class的有源类,但是你如果class b里面有100个方法,只有一个函数,只有或者是只有一两个函数是作为B的有源类的啊,是作为B的有源函数的啊,是作为a的有源函数的。

这个class b里面只有一两个方法,100个你class b里面一一百多个方法,只有一两个方法是用作为a的,那个有原函数的,那么你就不用把class b整个的申请,把整个的作为class a的有元类,你就直接把它里面那一两个需要作为a的,有原函数的方法把它作为a的,把写到class a里面去,作为a的有原函数就行了,这就是一个取舍问题而已。

之前说的有源函数是单项的,有源就是这个,有源类也是单项的,就说你的class a作为class b有源类的话,是单向的,那class a不能作为class b的有源类,就说class b可以调用classa里面的所有东西,但是呢,Class a不能调用class b里面的所有东西。

就说class a可以把class b作为class a的,有元类的话,那么class b可以去访问class a里面的所有的一个私有的一个private跟protected里面东西吗?但是你class a是不能够访问class b里面的private跟protected的东西的。

这个很好理解,简单说就是单向的嘛。

注意,虽然不是单向的,但是你可以在class a里面把class b定义为友缘,然后再在class b里面把class a定义为友元,这样的话,两个就是互为有缘了,这样就是双向的了。

像这个还有一个不用多说,就是你既然互为有缘类都可以了,那互为有缘方法也是可以的,是吧?之前在讲有缘函数有缘方法中没有说可以互为有缘方法,有缘函数,但你这里既然互为互为有缘类都是可以的,那互为有缘方法肯定是更是可以的了。

友元类的缺点,极大破坏面向对象,编译器不能帮你排除错误了

很多人觉得这个友元类是占了便宜,其实并不是,因为有缘类是极大的破坏了面向对象,他无视那个private跟protect的这些权限,而这些private protected这的权限是编译器给你提供的福利,他可以,编译器可以帮你,你如果按照这个方式写,编译器可以帮你去找一些错误什么的,你如果完全跳过的话,那边译器也帮不到你了,你你呢?就可,你就没有,你就失去了编译器给你的福利,你自己编译的时候,万一有错,编译器也帮不到你了,所有人类其实是是一个极大的破坏了面向对象。

不推荐使用友元类和友元函数

有源类跟有元函数,这些是不太推荐使用的。

友元函数破坏了类的封装机制

友元函数破坏了西家的一个封装的机制,因为它可以直接访问到那个类里面的private跟protected里面的成员成员,然后本身这个private跟protect的成员,他是被编译器保护起来了,封装起来了,就是编辑器,那里的话会帮你把这破protected跟private跟provide保护起来,但你要外面是看不到的,然后你是访问不到的,你一旦访问编辑器就报错,那边仪器上面不给你访问,但是你如果用了有缘的话就可以了,那就把这个封装的一个机制给破坏掉了。

运算符重在一般都要用有元函数会比较好一点,然后呢,是有一些特例,不能用有元函数,比如说等号啊,两个大于号啊,中括号啊,这些为什么不能用有元函数呢?

本次原因是他这个,这个友元函数本质原因是他这个运算符存在,就拿这个等号举例,我们知道C加加里面等号呢,是有一个默认的运算符存在的,然后它本质的原因是他不会去找这个类以外的东西的运算符存在,它只会去找里面的。

就比如说你一个 personp1p1等于七。

那么他这个地方有两还有包含两部影视转换,第一步是他这个等号的隐式转换吗?这等号的话,他先是找默认的,然后你没有定义这个预算等号运算符存在,他又找默认的,然后接着就是七,我们定义的这个破损,他是定的类型的。那么这个七呢,它是一个啊,不是我们定义那个person,它是一个类类来的嘛,然后我们这个七呢,它是一个int类型的。

所以他会发现类型不匹配,所以他就会去找去,你这个类型去找构造函数,把这个题也构造掉,你会发现它会自动找一个构造函数,然后就把这个题也够,造成了那个特种类,然后再进行一个负值,这个时候呢,你会发现呢,朱老师讲的,它里面有两部打印两部的就是。你看到他打印出来的是那个构造函数里面的那个,是你那个有代参的构造函数里面的值,但是其实你定义一个person PE分号之后,你按理说你没有传参,那他应该是调用是你的那个不传参的构造函数,空的构造函数,但是他却打印出来了,有传参的构造函数,就是因为他有一个把七,他在遇到七的时候。

就是他在遇到七的时候,他就把这七个隐式转换成person类,这个地方呢,他就调用了person的那个传代传参的构造函数,因为你定了那个代传,那个传参的构造函数里面刚好传了差就是硬的,七刚好传差,就硬核类型的,刚好这个七就是硬核类型的,他就说刚好合适,那就刚好就把这个构造函数给执行掉了,那么你就会发现他打印出来你那个带传参传的是硬核类型的构造,函数里面的打印性打印出来了。

现在话题回到那个运算符存在等号,为什么不能作为有原函数,总的来说就他它他,他会默认找,就是他只会找内里面的,他不会找内外面的,你就这么知道怎么这么记就行了。

你想的还不是太清楚,先暂停吧,后面再讲。

啊,想明白了,这个本质上是因为他他家里面规则冲突,重点是规则冲突了,就是你C加加里面等号运算符,它有一个规则,就是你的那个,你如果你累了里面,它没有定义你的运算符存在的话,那么我就用我默认的了,他不会再去检查类的外面的,所以就是他有这么一个规则,所以他就就,就不能够,他就不会去查找,再去查找友元函数里面的东西了。不会再就再去查到类外面的东西了,那有源函数虽然是有缘,但是它也是属于外面嘛,所以它有一个规则,它的规则是这个规则,规则就是它如果在类里面没有查到相,没有查到你的等号,没有查到你的等号预算符存在的话,那它就有一个默认的东西可以用,这是它的需要一个规则,所以就是跟这个,跟这个规则,就跟那个有缘冲突了。

所以就是一个规则冲突问题,所以他就嗯,等号运算符重在,因为他有一个默认的这样一个规则,所以就是你找不到了就用默认的了。所以他就因为这个规则,所以就不,所以他就不能够用有元函数去实现那个类,实现那个运等号的运算符存在,因为他不会去有元函数里面去查找,他直接用默认的了。

也就是说,那些不能进行友元函数的这种运算符重带就是本质原因,是他们会找默认的,那如果他如果那些不用,没有默认这个规则的,基本上都可以用用那个有原函数进行运算符存在,比如说加号啊,你就可以定义到外面去,然后在用友元函数,嗯是是可以的,反正总的来说就是那些不能用友元函数进行运算,俯冲带的都是一些他会找默认的,他不会去外面找有原函数那个东西,他只会找类类里面的,当类里面找不到的话,他就用它默认的都是这一类,这一类的符号它规则冲突,所以他不能用有缘有缘函数。

徐佳佳,编译器的这个宗旨,还是把它这个有元函数当成内的,外部的,而不是内的,内部的,这也就是为什么前面的那个等号运算符存在不能作为有元函数。然后还有一点就是它这个this指能,你只能在成元函数里面用,你不能在有元函数里面用,这也是本质原因,还是因为徐佳佳它在,它的一个宗旨是把有元函数当成了内的,外部的。

而且有缘函数它也是不能被继承的。这里就是有个很简单的例子,很简单的例子你就明白了,父亲的朋友未必是儿子的朋友,你记住这个例子就行了,父类的有缘不一定是子类的有缘,它是不能被继承的。

本质上还是因为有缘,函数就是累了,外部的本质上是宗旨没变。

而且有原函数,它本身就是想要获取你这个类里面的一些私有成员吗?但他不见得,他,他不见得想要获取你这个类的子类啊,类类的继续上面一层的子类的私有成员,或者是他的一些其他的他的一些父类的私有成员,他仅仅是想要获取你苯类的私有成员,所以从这个角度看,他也不存在说继承,没有存在继承的关系的,就是他只是反问他,他跟这个类才是朋友,他跟他的,他的上一级跟下一集都不都未必是朋友。

对,有缘还不具有传递性,比如说。

比如说B它是a的有缘,然后C呢,就是B的有缘,但是你不能说C是a的有缘。

他不能连了,不能连过去了就没有传递性的。

共有友元函数

有一个概念,就是共有友元函数,这个是不矛盾的,就比如说你的有一个ABC,你C是a的友元函数,那么,同时你C也可以是B的友元函数,这个是没有矛盾的,就你C,你可以往a打动,你也可以往B打动骂你,你只要想把这段当成有缘,那就写个friend就行了,所以是完全会出现共有友元函数这个东西的。

对共有友元函数有一个重大意义,比如说刚刚说ABC是a的友元函数C也是B的友元函数,那么,C它就既可以用a里面的私有变量,私有成员,也可以用bi里面的私有成员,这个时候就等于是共有原函数这个C啊,他把这个a跟B给打通了,就见就是把这两个完全无关的a跟B类给打通了,这是一个很关键的一个一个注意点。

C就是C,他把a跟B2个完全没有关系的一个类的,两个类的私有成员给打通了,就靠C,这个有元函数把这个a跟B给打通了,这是一个非常重要的一点。

嵌套类(大部分时候用不到)

再来说一下嵌套类,嵌套类,我们写代码的时候大部分是用不到的,它是类里面包含了一个类,那么这个时候你如果要去访问他,或者是怎么样的,你要用的,比如说你的person类里面有各类,那么你后面访问就用person,冒号,冒号,Dog冒号,冒号,像这样去反问的,然后你要注意这个友元函数呢,它也是如果你在友元函数定义在dog类里面去的话,它的嵌套在外面滴泪也是不能用到友元函数的,这是其中一点。总之这个嵌套类其实就是类里面包含了一个类,然后你在外面用的要用两层冒号,你才能找到他。然后这个嵌套在里面类,对于那个全局外面是隐藏了的,这就是这个嵌套类的其中一个设计出来的一个核心思想,它是可以把这个类隐藏出隐藏掉的,这样的话就避免了外面全局的类太多了,然后导致了一些嗯,很麻很很杂很很多很庞大的一个现象。

局部类

哦,还有一种叫局部类,局部类就是在函数里面定义类,这个就更少见了,它也有一些东西在里面,但是这个太少见了,就先不用管,暂时不用管了,你只要知道有这样一个情情况就可以,就是在函数里面是可以定义类的,C加加是允许的。

C加加,它允许函数里面可以定义一个类,就是函数里面可以包含类,但是他肯定是不会允许函数里面包含函数的。

不是说函数里面包含函数,就说它是不允许你在这函数里面再定义一个函数的,这是绝对不允许的,就你不可能把一个函数体放到另外一个函数体里面去,这绝对是不允许的。

这个都是常识函数,里面肯定不能再有定义一个新的函数在里面的,不能再把函数试题放里面去吗?这是常识类的,就不用管了。

函数里面定义类的话,这其实是一个很容易想通的东西,因为你累,本身就是一个结构体,是吧,那你结构体,你在函数里面定义,跟在函数外面定义都都是一样的,就是不是说都是一样的,就是说都是允许的吗?你想你如果某些情况下,需要把结构体定义在函数里面,也就是说,把类定义在函数里面是完全允许的嘛,是吧,没有什么大毛病。

之所以说C加加他不是纯面向对象,就是他还是支持一些inter啊,大宝这种类型的,你直接用了,但是相C#这种像这种纯面向对象的语言的话,他会把一个封装到一个类里面去啊,这个类的名字叫大写的in t,就是也是个inter累,他会建议你用这个in te累啊,不建议你直接用小写的inch啊,他小写的in,它是它是封装在这个大写的类里面去的,封装那个大写的in te类里面去的,这样的话它就实现了纯的面向对象,所有都是面向对象的了。

数值转对象

数值转对象的本质

本质上来讲,这个数值转对象是调用了他的那个形参类型匹配的构造函数去实现的,就比如说之前说in te等逾期,这种情况不是,就比如说之前说的person等逾期这种情况,它实际上就是调用了形参类型,跟英特尔类型的漆是一致的,这样子的一个构造函数,在里面找到那个构造函数,先把它构造成一个对象,然后再去进行匹配的,你像这个,你像那个,你像字符串。String PE等于双引双引号儿写上Linux,其实它Linux它是一个字符数组嘛,不,它是一个对呀,字符串数组嘛,但是你左边是一个是个字符数组,然后你的左边是一个字符串,那类类型是不匹配的,所以它肯定就是在那个string那个类里面肯定有一个构造函数,它的传的参传参是跟那个,你右边写那个Linux。

你写的右边写那Linux字符数组传的,它的形式是一样的,它就会影视着去那右边那个字符数组,它就会影视去调用你的那个类里面的那个构造函数,先把它构造成一个字符串类型,再去进行一个匹配。

模板

这个模板它本质上是编译时的一个过程,你其实在最终生成的程序里面,它是看不见的,就是比如说编译器要从上到下,他发现你这个是个模板,模板类他就会先噢,这是模板类,先暂存下来,当他到下面编译的时候,看到了他这个有一个东西用到了这个模板类了,那么他这个模板类就自然而然就变成那个那个累去了,所以他就会就到了真正程序生成程序的时候,他就不存在这个模板,累了就变成那个具体的累了。

以你可以理解模板,它是一种编译时候的多肽,就它编译时候,它显示待定的,到你编译到后面发现它是这个类型的,它再它再把它变成那个类型。

但是这个跟面向对象的多态不一样,面向对象的多态是编译的时候都不知道,连编译的那个过程的时候都不知道的。

静态多态与运行时多态

面向对象的多态是运行时多态

所以在面向对象那里的多肽,它是一种运行时的多态。

模板的多态是静态多态

这种运行时的多态,也可以叫做动态多态,然后像这种模板的这种在编译时的多态,也就可以叫做静态多态。

模板前面有个template,然后一个尖括号type name t,它这个也可以写成template,尖括号class t,就是说只有这两种写法都可以订,都可以说一个模板,但是呢,你平常用的时候,你看人家是你的那个代码开,你的那个源码是用什么,那你就用什么,你的公司要求用什么就用什么,但本质上这两个都是可以用的,不过你要注意一点,就是这里的这个class t啊,这class跟我们。之前说那个类是完全不同的,它只是用了相同的一个英文单词而已,它这个class跟之前说那些类是完全不同的,这个不是一个类,它只是在定义模板的时候也用了相同的一个字母,相同的单词class,所以不要混淆在一起,它们是完全不同的两个概念。

模板类

除了这个函数模板之外,还有一个叫做模板类,其实他跟函数模板的理念是一样的吗?就是你要省省功夫,你而且是一个多肽,就可能你这个类里面你的一些参数,你你可以使它为多种多种情况,比如说你这个类里面它的一些参数,你可以是in te,也可以是double,那你就也可以是其他的。那你就把这个东西实现成一个模板是吧,然后你最后你再用这个类的时候,你再根据你实际写的这个这个类型,去对你这个模板进行一个多态化的一个变化,变成对应的那个是不是。

到这里就要注意,就是什么时候写template play的,什么时候不写template,就是你要把一个实现这个类跟用这个类的分开,就是你在实现这个类跟定义这个类的,就是包括写class的那个,括号里面东西的,还有class里面的类里面方法,在外面定义的这些,这两个部分都是属于实现这个类的,实现这个类的这部分你就用模板去做,你就是要把这个。Template兼括号什么的,写到这个class的前面,然后写到你这个方法的声明的定义的前面,在支出在前面,就是说在前面写的template,然后兼括号写个模板啊,但是你在用方法的时候啊,用这个类的时候,比如说你在使用类,比如说你在main函数里面使用这个类的时候,你就不能够再写template了。

因为你到了用用这个模板类的时候,你就要把这个模板类给,把这个多肽给解决掉了,就让它变成一个实际的某一个指定的一个一个类型了。那么你在用的时候,比如说你在面谈数理的时候,你再用这个模板类的时候,你要给他传一个实际的东西了,所以这个时候就不能用template了,也不能用踢了,你就得必须得把一个实际的东西传给他,比如说people,尖括号into,把实际变成一个这样的累了,而不是people,尖括号踢了,而不是这种。Template people监控号踢了不能这么来,不能template people监控号踢了,直接直接是直接把要把temple template去掉变成people,然后监控号里面写个你实际要用的类型的,比如说int啊,比如说double,就是总的来说,你要把实现类的那一部分的人的工的的快,那块跟用类的那块给分开。

然后实现类的那块,你就要用template,然后用模板提,然后那个用这个类的时候,你就不能够再用template的,你,你需要就要把这个你做的这个模板类给变成个具体的一个类型了,这时候就不能template了,就传一个具体的类型给他。

模板友元函数

这个模板友元函数,它本质上也是用了那个模板吗?就是他的那个T,到时候可以根据你的需要去变吗?然后这个模板友元函数有两种方式,第一种方式就是你写到这个类里面,写到这个模板类里面的时候,你家的friend吗?然后你你把你这个友元模板函数的函数体。放到这个类里面去,这样子的话,编译器是不会给你报错的,这样写是可以的,但是这样写是有点奇怪的,因为你把模板有元函数的函数体放到人家类里面去,但是你这个模板有原函数呢,又不是属于人家这个类里面的成员呢,你却把函数体放到里面去了,虽然这样可以,但是这样会怪怪的。

然后第二种方式就是你把模板友元函数的声明放到这个模板类里面去,然后把定义,然后再定义那里呢,在定义那里写啊,你在外面定义,然后这里就会有一个细节,就是你它的语法上的细节,就是你如果是这样的这样写的话,那么你在那个模板类里面声明的那个模板,友元函数那个函数名。它后面要加上尖括号,然后里面写上T,但是你的定义在外面的这个模板有元函数的函数体定义在内,外面的那个你的函数名不用加尖括号,里面写题它是不一样的,两个是一个,里面声明的要写尖括号题,外面的不用写。

还有一个注意事项,就是你在内函数里面,就是用这个类的人在内函数里面写这个模板类的时候,你要用的时候呢,这个这个你要写这个模板友元函数,你要用这个模板友元函数的时候,你在内函数里面用的时候,你要么用的时候呢,不写尖括号直接用,要么就写上尖括号,在函数名后面写上尖括号。把具体的实类型给写上去,而不能写题,你可以把你要把具体实类型写上去,你要么就不写精括号,不用写这个,这个精括号里面内容,也不用写精括号,这样的话,他就会根据你的传参自动的去推导,要么你就你不想自动推导,要么你就在函数名后面写上精括号,把你要要用的十类型给写进去。

然后还有要注意的一点,就是当你在这个模板类里面用了模板有元函数,那么你就需要在这个类的前面,把这个模板有原函数的声明给写上去,然后你会发现你在那个模板有原函数的声明里面又写到了这个模板类,那么你就又需要在这个模板函数声明的前面写上这个类的,写上这个模板类的前置声明。

然后还有要注意,就是我刚刚说的那个,你需要在声明的函数名后面加尖括号题,而不需要在类外面的那个模板友元函数,函数的那个名字后面加尖括号题,这个里面的J。我说的这一部分的声明指的是在这个模板类里面的模板友元函数的声明,而不是说在这个类的,上面的那个写上的那个模板。友元函数的声明。在类的上面那个模板友元函数的声明是不用写尖括号的,就在他的名字后面,不用写监控,监括号的是在这个类里面的模板,友元函数的声明才用写类的那个尖括号,还要写那个函数名后面的尖括号。

你在友元函数啊,一旦你的友元函数,你用运算符存在的话,那你就不能够用历史箭头了,简单说就是你友元函数,一旦用运算符存在的话,你就不能是一个是主一个是次,你必须得是两个都是刺就是并列关系的,然后你把那个你的那个之前是主的也要放到那个传参列表里面去,就是说要给他传两个餐,然后都要用着点不能够用,也不是送点点。你要就是说你要传两个参,你这两个参就变成a跟B了,两个参,你知道叫a跟B,那就a点跟B点了,而不是只像之前那样只传一个参叫other,然后另外一个用this,你有元函数是外部的,你不能用this了,所以你就不能够用之前的主从那种那种关系了,只能用并列关系,就不能只传一个参了,要传两个参,然后分别用这两个参,然后去进行运算,不能用this,它不在含它不在类里面的,它不能用this。

然后后面试了一下,还有第三种方法,就是你在那个模板类里面的那个模板友元函数,你在他前面加一个,同样也是加一个template,然后菜谱,然后尖括号吗?尖括号菜谱,但是你那里面不能写题啊,你要把它写成u或者是其他的,你这样写成其他的之后他就可以,这个是一种新的一个方法,就是算是第三种吧。

运算符存在的友元函数模板这里啊,朱老师他有一个没有解决的问题,就是为什么加号用第二种方法是不行的,就是它有三种方式啊,方法表示那个模板友元函数吗?然后表示模板友元函数的运算符虫在吗?用就是表示运算符存在的模板友元函数,但是他那里有,他是朱老师罗列了三种方法,就是第三种,就是直接把那个template的替换成碳培的。T,把它换成time play的u或者time play的B嘛,就是就是在那个类的里面的那个模板类,把改一个类的名字嘛,把T改成其他的或改成u或者B什么的嘛,就这是第三种,就总就总共它有三种方法,你用加等于的运算符存在的话,这三种方法都可以,但是你用加的时候只有加你用加的运算符存在的话,第二种方法竟然不可以。

这个问题朱老师他没有解决,现在不知道是为什么,但是其他的运算符存在三种方法都是可以,唯独世家的运算符存在。它的第二种方法,是不可以的,就是说你在那个类的里面去声明,然后在内的外面去定义,然后再在类的前面去做,再做一次声明,然后在在这个声明的前面再做一次类的前置声明,这种方法对于加号的运算符存在的模板,友元函数是不可行的,但是对于加等于的模板,友元函数的运算符存在是可以的,这个一点就是唯独加号的第二种方法是用不了了,这个目前先放过了先,不知道为什么不知道为什么先放过了。

就是唯独加号的第二种方法不行,其余的运算符存在的三种方法都是可以的,加号的第一种的,第三种也是可以的。

第一种方法就是直接把那个,那个运算符存在的模板有原函数写到这个类里面去,就把它的中括号,把它定义,把整个函数体都写到那类里面去,这是第一种方法。

第二种跟就是我上面说了,第三种我上面也说了,就是第三种就是在那个类的里面那个声明,把那template t改成template u或者改成template b,然后同样也是在类外面写它的那个有元函数的那个函数题,跟那个就是它的定义,那这个时候你就不用在前面写那个前置声明,还有那什么什么声明之类的。

注意,这个第三种方法,你可以理解为是在第二种方法的基础上,就把类里面那个模板有原函数的那个,那个temp的,把T改成u或者B,唯一要做的就是这里,其他的跟第二种方法是一样的。

就说他在类外面定义的时候,他用的template还是T,只是类里面把它写成了一个B,或者是u,然后在类的前面还是要声明,还有类的前置声明的。

哦,刚刚说错了,就是第三种方法,它跟第二种方法它还有一个区别,就是第二种方法,它要,它要需要类的,前面的一些声明,还有类的前置声明,第三种方法是不用这个的,但是第三种方法还是需要在内,外面去定义的嘛,但类而而且第三种方法,在类外面定义的时候,你就不用写人template u或者template b了,直接还是的T就行,但它跟第二种方法相比之外,前面是不用写声明和类的前置声明的了。

这个父类和子类的继承的这个模板类的话,有一个思想叫填充思想,就是你复类里面有个T,你只类里面也有个T,你继承的时候你是不是写的那个template type p class man,冒号,冒号public people尖括号T是不是,那实际上这一行里面的。这两个T呢,都是TY来的,然后你之前的那个负类people里面,你定的那个people的那个监控T是TX来的,这里其实它是用TY填充了,TX就说他虽然写的都是T,你的那个负类里面的那个定义写的是T,然后你这一行里面写的也是T,但实际上它是两个不同的T来的。

实际上是两个不同的T来着,你这里你这个纸类里面,你在继承父类,你是用了纸类,你是不是也是用那个template太太plan t,这个你可以理解,他是把这个TT填充到你那个继承的那个,那个冒号后面的那个父类的里面的T,也就是说,你这里的父类的T已经不是那个T了,而是T已经不是那个TX了,而是体外了,就这里只类的TY填充了那个父类的TX。

注意,我刚刚说错了,刚刚是那个纸类,那个是叫template type t classman冒号,Public person t尖括号,就它是一个冒号,不是两个冒号,不是冒号,冒号是只有一个冒号。

是说你负累那里的那个T,你可以理解为是T1,然后子类那里面也有个T,你可以理解为T2,他全程就出现了三个,出现T的地方也是负累,那里出现了一个T1,然后紫薇那里出现了两个T2,你注意这里就是,就是子类那里的两个T啊,其实他是都是T2来着,他是填充了,把T给填充掉了。你可以理解为对于父类来说,这个子类把T2填充到T1里面去,就是一个实参把个形参填充掉的过程。

但是呢,你没有必要把复位那写成T,把纸的那个写成T2,你直接把他们全部写成T就行了,因为编辑器他认得出来的。

总的归结成一句话,就是他这里经历了一次时长填充的过程。

这个关于认顺序还是认符号,这里你其实你的思路跟逻辑跟C加加里面的思路跟逻辑设计的是一样的,你不用单独的去记了,你就按你的思路逻辑去想就可以想的通了,这个就是这个思路的话,你的思路跟C加加的是一致的,那就可以不用记了,后面按你的思路来的话就是对的。

当然这里很绕是真的,就是他是特别绕,特别绕的,因为它有一个什么时候去看顺,什么时候认顺序,什么时候认,符号的一个东西是很绕在里面,但是他的你其实,但是你听完了讲解之后,你会发现跟你的思路是同一条思路的,没有任何偏差,所以虽然绕是绕,但是你跟按照你的思路去绕进去,你就可以绕的绕的明白了,所以这里你就可以放心,这里是完全没问题的,你虽然很绕,但是你按照你的思路进去绕,你可以绕的出来的,因为他的思路跟你的思路是完全一致的,尽管很绕。

你讲的那个普通类,继承那个模板类的时候啊,他的继承的时候,你如果要让普通让他那个纸类是个普通类的话,你必须要在继承的时候,你就要在那个父类那里填充掉你的实际的类型了,就是就是说你要在父那里直接写个应酬,进去监控号里面写个应聘,或者写个其他进去,在那里要填充掉了,要不然地话你普通类继承模板类的话,你是无法完成的,那如果不是这样的话,你普通类就会变,又会变成一个模板类了,你又要在那个普通类前面加个template,然后type name t,这时候普通类又会又变成模板类了,你后面用的时候还要加普通类那个子类,那里还要加点括号,这就不对了,所以你如果要真的用普通类继承模板类的话,那你模板类就需要就需要去先是把它实。

那你继承的那个模板类那里,你在写继承那里,你就要你就要已经要把它给实际的类型填充进去了。

这个模板它有点像函数的形参跟实参,就是你,如果他的这个你先试模板定义的形参,如果你用这个模板的时候呢,那么就要传个实参进去给他,这样才能用,如果你没有传实参呢,那么你就要连同你的这个东西,要在跟模板在一起打包封装,然后又作为一个形参给出去,然后给下一集,然后用下一集用的话,他就需要把一个实参传进去,这里这个封装在里面去。如果你还是不能传实仓的话,又得把这一坨东西,再一次打包起来,然后作为一个又是一个封装,作为一个形仓给出去,要给下一集,下一集必须要给一个实仓进去,也就说你只有到了给个实仓进去,它才能够真正的到终点。如果你没有给个实仓进去,它会一直在,在这个成绩的基础上,再一次封装给行仓出去。

然后还有一个就是模板的一个细节的一些东西,就是一个非类型模板参数,就是你的那个模板里面,它一般的话,模板就是用来用来搞类型的嘛,但是呢,就说你类型的话,比如说你写个T给他,那么后面你指定他是什么类型嘛,是吧,但是呢,其实你不单单是类型可以,你这个非类型的模板参数也是可以的,比如说你你给他一个in te v l,那么你到时候模板的话,你可以给他一个。特int类型的一个数传给他这最简单的应用例子就是站,你定义一个站的模板,那么站的模板是不是你首先要给他一个类型是吧,然后呢,你这个站里面放的是什么样的一个,你放了,你站里面放了多少个数据呢,是不是你这个时候你是不是就要给他一个站的大小,所以他就会传两个参数,一个是类型的参数,一个是大小的参数,那个大小的参数呢,就是非类型,模板参数。

然后还有一种更奇葩的做法,就是你的这个你还可以用你的那个模板参数的那个类型去定义你的这个非类型模板参数,比如说你定一个ta,这样的话也是可以的,就是你前面这个template,你前面这个type type Lan t,后面又定义了一个ta,就是你,你在定义了一个模板参数的类型之后,你又马上用这个模板参数的类型去去定义一个非类型的模板参数,然后它这个这个模板参数的类型就是你那个模板参数的类型。

非类型模板参数的话,有注意事项就是它不能够是一些,他不能是浮点的,就你不能是double的,不能是double跟float这种类型的,就你的模板参数非类型模板参数,你不能定义成这样子。还有就是你不能定义成类目类,类对象类型的就是,比如说你不能用那个STD冒号,冒号stream定义一个这个非类型模板参数,就是你如果是含有一个冒号,冒号的有个飞,有个对象的就是有个类,这个类里面的对象呢,比如说STB冒号,冒号,Stream用的是字符串类型的。他是在这个ST类里面的这个命名空间里面的这个东西的,就这种类型的有个冒号,冒号的这种是不能用的,然后但是呢,还有一种是可以用的,指针是可以用的,因为指针本质上也是一个也是一个数嘛,就是也是一个也是一个硬的类型的数嘛,它是一个也是个32位,就是三十三十二位的话,就是一个一个。

就是指针的话,它其实本质上是个地址嘛,地址的话就是一个int类型的数,你32位就是一个int类型的数嘛,是不是它就是个地址来的嘛,就是就是一个地址,所以指针它是可以用的,但是呢,你那个刚刚说了浮点数doublefloat啊,还有那个类被对象类型的,比如说STD冒号冒险这种的话,它是不能用来作为非作为非类型模板参数的。

这个编译器,他如果你有个in te a等于一分号,然后inch and be等于ae,你编译器跟模板类型相结合的话,它这个不会,就模板那里,他不会去用你那个引用类型的,尽管你引用的时候你传的,尽管你模板的时候你传的是B,但是你。跟模板结合到它,最后它编译器它会影视着把你这个B呀,变成它的一个,把它引用去解引用,解成那个a序,也就是说它模板跟编译器结合的话,编译器它是会把你的那个影视转换的,它不会用你那个引用类型的,它会用你引用之后解开了的那个实际的值的。

包括你,如果你定义了一个数组类型,然后比如说你定义一个数组a,然后中括号,比如说十是吧,那么那么你把这个a呢,你传进那个模板,模板里面的话就是你的尖括号里面,如果写a的话,它模板里面,他也是认你的这个那个应他,他绝对是应类型的,因为他你模板的话,你传一个a尖括号里面放a的话,它是认的是你的数组里面那个指针来了,任你数组里面的头指针,它不是说是那个数组类型,它是这个指针类型的。

还有一种情况就是cost into a等于五,你如果这样子,你把这个a传进去的话,就传到那模板监控号里面的话,那么,A的监括号这个地方,它会把cost去掉的,它只能传int,它没有cost。

然后这个这一点结合上C加加他的一个强类型的一个,这一点就会产生一个矛盾,当你用cos的img星去做模板的话,然后你后面它会转成了一个int型吗?这个时候C加加编译就不通过了,就是你最后的话,这个你就会出现一个隐藏了一个很深的一个bug,因为C加加它是强类型语言,你哪怕是cos的img星。跟int星都是不一样的,比如说你一个cos的星a跟一个int星B,这两种类型是在CA里面,它是编译器,编译器,它是认为是完全不一样的两种类型的,因为它是强类型的,当你当你把那个模板,你模板里面传的是cost int的时候,它把这个cost去掉,都变成int星了,到时候肯定会出现个隐藏的问题,你发现不了,很难发现一个问题。

因为他虽然都是指针,但是你会发现虽然都是指针,但其实西医里面他也是认为他们是不一样的,就你从控制的ins新批变成了ink,新批,本质上那个批他还是个指针类型的是吧,按理说应该是按理说从C的角度看的话,应该类型还是一样的,但是C加加他认为他们类型是不一样的,所以你把控it新批,然后你放到模板的监控,就变成了ink星期之后,你后面用的时候,C加加就会认为你这两种类型类型是不一样的,所以就会出现bug。

刚刚说的第三点是说传是cos的into a等于五吗?A cost in a等于一吗?然后我刚刚说的那个,他又在这基础上变成了cost心,就是说你首先在cos的Intel,这个这个这个阶段肯定是不行的吧,我们后面刚刚的后面讨论的是在cost it新的类型下会出现一些什么样的一些奇怪的事情,也就是说刚刚说的那个点,是在刚刚第三点的那个cos的it基础上,变成了constant心之后,在这在这个问,在这个上了一个台阶的基础上去讨论问题的。

然后有一个比较有意思的一点,就是这个模板它是不能单独编译的,因为你编译器如果用模板的话,那肯定要给编译器一个具体的一个到后面必须要给他一个具体的东西的,如果你单单这个T,你最后没有落实到实操的话,编译器编译是不通过的,所以模板它不能单独编译的,你必须要在在它基础上,有给他一个实际的参数类型,参数类型,这样才可以用的,所以他就决定了模板,它不能够编译成一个静态链接库,作为一个lab编译成静态链接。库,然后把这个库给隐藏起来,然后给别人用,这是不行的。所以模模板的话,他必须得开源,因为你你必须得把你,你必须得把你这个模板,它的这个这个源码,还有它的一些它的实际的这个这个参数,实例化把把就把它那个参数类型实例化之后,开源之后给人家,不然的话你编译器编译不通过的。

所以一般的模板都是开源的,它不,它无法实现单独的给一个模板给你,因为你模板必须得实例化,要不然编译器编译不通过,所以模板它不能作为静态库做编译成那个live静态链接库,然后封装起来,然后不让你看见,给你这样的不行,因为它里面的话必须得要实例化,你不实例化的话,编译不通过,所以模板它必须伴随着开源。

这个模板的本质就是参数化,然后延迟绑定,你可以这么理解,它的本质就是这个。

容器类

array容器类

这array容器类,它有一个empty方法,这empty方法的话,你如果里面的东西是空的话,它就返回一,如果不是空返回你,但是你注意他这个空不空,他判断的不是你那个数组里面的值有没有给他定义,而是在他那个模板里面,那个监控号里面的那个,那个数组的那个大小那里有没有给他定义,因为你就算你,你一旦数组大小,那里给了他定义的话,比如说你是一个in te3,你给了个三了,他就那个数组的大小就是三了,然后他是分配在站里面的,所以它是会给你随机分配的,所以他只肯定是有的,只不过是随机的,但它是有的,所以他你用MP方法的话,它返回还是零。

那么这个MT方法什么时候返回空返回的是一呢,就是你在那个尖括号里面那个,就你在那个瑞那个方法,然后你能不能模板的尖括号里面,你写的是不是INT3,而是int,逗号零,你写的是零的话,那么它就返回是空,那它就是一了。

哦,还有一个方法,就是叫做size方法,Size方法它就不是返回零一这么简单了,比如说它是返回你的大小的嘛,比如说你是te逗号三,你在那个模板里面写int逗号三,那么它的size就是三,如果你只写int逗号二,那么你的size就是二。

然后还有一个方法叫max size,这个max size其实是跟SYS返回的值是一样的,到这里你就会有一个疑问,其实刚刚的那个MT是没有任何意义的,反正现在这个max size其实也是没什么意义的,因为他跟SYS是一样的吗?他这里为什么还要写上这两个东西呢?因为他要维持一个规整性,就他定,他希望很所有容器类的一些共有的方法都是这些,他因为你到了vector这种容器里面的话,那那个MT跟那个max size可能就有用了。但是你在这个aray里面,可能empty跟那个max可能就没有太大意义,但是他为了一个维持这个完整性,所以他干脆就在这里也写上了这个方法,在里面对aray也支持这个empty跟exercise的方法,但是他本身是没有太大意义,当然你也不能说完全没有意义,他可他在某些地方可能也是有意义的,但是总体来说,他是为了完维持一个完整性才写上去的,本总体上没有太大那么大的意义。

这个array类,你容器类里面还有一个方法叫swap方法,这swap方法是用来交换的,但是其实这个交换本质上也是把它的值复制了一份再拿去交换的,所以它自己本身的值是不变的,它是一个把它的值复制了一份,到另外一个地方去,再用它另外复制的那个纸拿去交换的。

他这个array类模板里面,他有一些方法用了函数模板,所以你在函数,你在那个幂函数里面,你会看到它前面用阿瑞,里面有尖括号,然后你用那个,你在下面看到,然后她用,比如说你用了里面有个方法叫做思维是吧,它有一个有一个含有模板的一个swap的这样一个方法,那么你就会看到有个swap,尖括号是不是?这个时候你就要不要搞混了,然后顺便复习一下类模板跟模板函数前面的锐尖括号,那个是类模板来的是模板类来的。然后后面那个swap间括号,那个是模板函数来的,你一定要时刻分清楚,这两个东西要要,不然的话概念不清楚的话,会很会很很混淆的。第二个那个swap间括号,它并不是一个类,它是一个模板函数,但是上面那个锐间括号那个是一个模板类。

using在C++中的第二种用法,跟typedef差不多

Using在C加加里面还有另外一个用法,就是把它当成C里面的type type define去用,就是把一个东西定义成另外一个名字,就是type,跟typedef的用法是一模一样的,然后再用using,比如说你的using t等于什么什么什么的,那就是把后面的什么什么什么,然后取一个新的名字叫T。

这个C加加里面有一个temple的一个东西,Temple其实就是把东西集中在一起,类似于struct,然后比如说它有个pair的这个东西呢,Pair是有两个类放到里面去,比如是class a class b,把class a跟class b放到一起就变成pair,那么temple就是这个pair的一个升级版,它不单单放两个类,可能放多很多类。

然后这个array容器类里面滴这个叹剖啊,他好像有一个占位的意思,就是你先把它那个地方给占掉,那个位置也占掉占位,然后因为你后面的话,你还不知道你那里要写什么,你瞒你只是写累,你还没有到用,所以你不知道要写什么,所以它本质上是用的这个temple这个东西去占位,先占着个位,然后后面根据什么,再根据你的具体情况,你要给什么,他在他在变成对应的。

然后还有一个exam the exam,这个is下划线,Sam这个SM这个东西它是用来判断类型的,他这个X下划线Sam这个判断他是连cos的inch跟inch这两种都要区分开的,你看它那个,那个说明文档,说明文档里面,他是先是定义了一个int类型的,然后又定义了一个cos类型的,然后拿一次下划线Sam去去把它跟拿他这两类型判断,所以它是区分cos的类型,跟孔子他是区分cost in类型跟int类型的。

然后这个他的这里创建了那个cost int类型跟int类型,这里就用了那个using的一个type define的这个用法,它是用了个using using,然后using t把这个using t,把这个CT作为那个cost int类型,它是用了一个重命名嘛,就是用type,类似于type define。

然后这个using t等于后面的那那坨东西,就是关于模板类的一些应用啊,这里对这里还不是比较,不是太熟悉比较生僻,先看看听后面的继续在模板类再熟一点,然后再回过头,或者说等到熟一点之后,自然而然就看着就看着就自然了。

它本质上那个usingT等于后面的那些东西,就是模板类的一个写法来的,就是用模板类写的一些东西,就是你把模板类那理清楚就行了,看准尖括号,然后看准他哪一块是代表是在一起的,哪一块是一个整体,最关键是看准哪一块是一个整体,然后因为他有时候一个一段东西会很长,你可能会把它分成两三个东西,一起看的,实际上是一个整体。所以看这个最关键是你要把一个东西看成一个整体,它是很长,那你要把它看成一个整体,这样的话就好分析。

你之所以没有理解他这个优点,T等于后面内错东西,主要是你卡在了那个declare type上,这declare type是之前讲过的,你翻回去查,我翻回去查完之后发现这个declare type就是一个自动类型推导,他就是把declare top尖括号里面写的叼它,就它自动类型推导,推导成那个根雕她类型一样的,这个类型就是这一步,你把这不理解了,基本就就可以了,因为那个o它里面也用到那个auto。但是oppo那个的话,Oppo那个你是熟悉的,你看就知道是自动类型推导,你唯一忘的是那个declare type,那declare type你忘掉了,所以你不知道它自动类型推导,所以你看了有点困难,你把这个理解完之后,看了也不困难了。

这using t等于后面他用到的是一个temple element,它也是用了temple,但这个temple跟刚刚那个站位都不一样,这个temple element是取元素,不过它也是就是跟有一点点占位的意思,但还是取元素。

他这个is Sam is下划线Sam,它的这个说明文档里面,他要解释他是怎么解释的,他一个思路的一个逻辑是这样子的,他先是创建了一个,他先是创建了一个int类型的,然后又是创建了一个cost in te类型的,然后他他再创建映射类型的时候,给他做了一个重命名,然后变成了一个T,然后他在创建cost类型的时候,也给它进行了重命名,变成CT,然后,这他们在创建的过程中分别都用了auto跟declare type。这样的一个自动类型推导的一些功能,然后包括一个用了一个temple element,取它里面的值的一个类,他用这许多值类都他还用了一个冒号,冒号,然后把里面的type,主要就是用里面的type,也也就是通过这这种方式实现了一个嗯,实现了,把他这个int提出来,然后把它cost int提出来。

就变成那个T就重命名成T,还有CT,然后他最后的一步呢,就是用is下划线三,这个累啊,用is下发现这个方法,然后去进行一个跟na si GE na t跟CT进行一个比较,就是尖括号里面写上C和CT进行比较,写上T和CT进行比较,然后还有一部,就是他拿那个CT跟那个cos的event进行比较,就是他写两步,第一步是用拿。

就是第一步,她是先拿一个T和CT进行比较,就是看他那个MSN,他区不区分那个inter gen cost in the,还是说他把这两个东西当成一样的,这是第一步。第二个,他是拿那个CT跟cos tint进行比较,他就是看一下那个CT跟cost in是不是一个东西,就是就是你,你用的不是邀请邀请CT等于那个嘛,你把它重命名吗?你把重命名等cost in,但是你确不确定他到底有没有重命名成cos的呢?因为你这个并不是,就是你用的是一种自动类型推导的一个方法嘛,然后你是前面用了auto,你是在auto前面加了cost,就是cost auto嘛,你不能确定这cost auto它最后是不是可以变成真的变成cost te。

所以你用这一步就是为了去查缺补漏,因为你前面判断呢,是一个拿,你前面是拿那个cos跟cost in去判断嘛,但是呢,你前面是na in te跟cos的去判断吗?你当你判断是相同的话,你也不能够盼你,你也不能够确定它是不是你在前面自动类型推导的时候,嗯,那个cos的根本就没有,没有,没有变成cos的,而是指还是。原来int,所以你在后面用了那个意思,下划线Sam,然后CT,然后cos int拿这两个相比,就是为了看一下你到底是不是真的变成cos int,如果你一旦真的是变成constant的话,那你前面的那个CT跟T进行比较的时候,你的理由就证据就更加更加确凿了,不管是出现什么现象,更证据都会变得更加确凿。

这个容器类,容器类,还有一个叫make a re的这样一个方法,实际上这个方法他跟那个图儿瑞是一样的,它都是途锐的用处,就是它把一个C语言形式的那个数组转换成他这个away形式的数组吗?这个make carry也是差不多用法,但是这个to make瑞你会发现它是放到了STD冒号,冒号experiment里面,就说明他是一个试验阶段,像这种试验阶段的那种方法的话就不用。我们就尽量少用,因为它可能在下一个版本就不支持了,比如说这个mercury,他后面就不用了的,C加加里面后面就不用了的就放弃了。所以他像这种XTDSTD冒泡泡experiment里面的这种方法都是有一有一定的试验的那个意图的,所以它不一定是稳定的,所以尽量不要用这里面的是experiment里面的方法。

迭代器iterator

迭代器就是一个高层次的指针

这个迭代器就是一个高层次的指针,你可以这么理解,就是因为他的C加加里面它很多的数数据结构,它不像数组那么简单,直接批加加吗?比如说你如果有个结构体的那种类型的,他的第一个是in te,第二个是差,第三个是short的,第四个是露娜,你就不可能简单的批加加是吧?再比如说是链表类型的,你不可能链表的话。你还是简单P加加,它是有个P件号,还有一个箭头,Next嘛,指向next,然后这样去便利的,就是说不同的数据结构,它的便利方式,是不一样的,然后这就是底层的指针,就是我们在C语言中用的指针。

那么迭代器就是一个高层次的指针,他是在你底层的指针的基础上封装起来,然后变成一个迭代器,然后作为一个迭代器呢,他只要普通的加加减减就可以了,就是说你的底层指针无论怎么包装,它最后都会底层指针,无论怎么实现,实现的方法各是各各种都不一样,但是他上他打包封装成上层的一个迭代器之后,他就是单纯的加加减减就可以进行指针的操作了,无论是链表啊,还是还是结构体啊,都是可以的结构,结构体里面的成员都是可以的,所以迭代器就是一个这样的东西。

然后迭代器他还有个,它的里面有个,他会把那个都凝练成一个成员变量,就是这个成员变量的推特,每一个每个类的迭代器的it or,每个迭代器的成员变量都叫特瑞,特瑞特瑞特,你可以理解为就是它的指针,然后你到时候你要用它迭代器,你就调用它这个成员变量interest就行了,然后用那个it加加减减,就可以去便利了,就可以简单的通过加加减减去便利了,不用不用关注底层的一些各式各样的那种便利的实现。

然后这个迭代器的一些,比如说迭代器的里面的一些加加减减运算,那他肯定是通过这个C加加的运算符重在去实现的,这个一点肯定是通过运算符重在实现的。

然后这个迭代器的一些,比如说迭代器的里面的一些加加减减运算,那他肯定是通过这个C加加的运算符重在去实现的,这个一点肯定是通过运算符重在实现的。

注意这个it Rita,它只是迭代器的一个指针类型,就说你如果要真的用迭代器的话,你要重新定义一个变量名的,就说这it rate,你可以理解为是一个只是一个类型,还没有变量名,你要用的话要要给它加个变量名。

有那个begin跟end,去取他那个APP里面第一个跟最后一个,还有一个c begin的跟CN的,这个CBN跟CN的,就是这个方法呢,就是说,他只能读不能写的,你只能读出来的,不能写,然后那个还有一种叫cost literature,这种cos to trigger之中也是只能读不能写,然后你如果你把那个你把那个begin end end。然后赋值给了it Rita,那是没问题的,然后你把那个began and赋值给C啊,把它赋值给cost eat Rita,那也是可以的,就等于是你把那个began and它的大了权限缩小成了一个cost eat literature,这个权限从大到小是没问题的,但是呢,你如果是一个it,然后你用的是c begin跟c and。

就是你把CBN跟CN放到了,放到了那个it,那个cons it rate,放到那it rate里面去的话,那你就是一个小范围,你复制给了大范围,那你的权限就会被破坏,那这个就是不允许小权限,不能转转成大权限。

begin和end的左闭右开

然后这个begin和end你仔细去体会的话,它是有一个半,有一个半开半闭区间的,一个盖一个一个意味的,因为它是指针的吗?它是指针,比如说01234它指针的话,你他是从他是你可以心中想象一个图一个表,一块一块一块的,它是一个一个指过去的嘛,然后他这样的话,他只能只到前面前面的那个,比如说零的指针零啊,零的左边那个指针就可以就可以包含零,但是呢,最后那个指针呢,它就不能包含后面的东西了,因为后面没有东西了,所以它这个它是一个左左臂半左闭右开的一个区间,这个begin和end。

为什么它是左臂右开呢?因为它这个end它最关键的理解的点就是它这的,它不是指向最后一个元素就不,它这and不是指向最后一个元素,而是指向最后一个元素的右边的那个,那个地方,它是指向最后元素的右边的那个地方,所以它在那个基础上,你再看右边它就没东西了,所以就是开的。

就是最后一个元素,它是一个正方形嘛,它指向的不是正方形的左边那里,它指向是正方形的右边那里,它按的就是指向那里,那指向那里的话,你右边就没东西了,那就是开的了。

就是说它的这个的不是指向墨元素的,它是指向那个指向那个正方形的右边的。

是指向最后那个元素的右边的。

他用这个半开半闭区间,就是为了让我们写代码的时候能够直接用一个不等号,等于就是冒感叹号,等于直接这样写方便,如果你不是半开半闭区间的话,你最后是B的话,你最后就只能是写他,如果他这个东西不等于and加一是不是?如果这样他才能够退出,是不是,或者是说要写成,如果这东西大于的,是不是这时候它才能够退出,所以为了这样子方便,直接就直接用,不等于,那么它这个指针是直接指向那个最后个元素的右边。

就是说,为了让我们写这个病历代码的时候比较方便,因为你这个大于等于这些,这个或者是说不等于N的加一,这种它是比较比较,麻烦的,就是不是太太规整,你直接写的不等于什么,那规整一点,所以就是为了让我们写病历说方便,所以它直线了最后一元素的右边。

而且你用大于小于去判断这是不合理的,因为你这个指针,如果你一旦便利的不是一个数组,这种从小到大这种地址变了而是链表的话,说明链表他最后一个元素,它的地址是是小于那个前面那个元素的地址的,他不一定是从小到大一步一步排出来的链表,所以你如果用大于的话,那你最后其实是会出出bug的,就是他地址不是从小到大排列的,他地址说不定前一个比后羿还要小啊,比后羿还要大都有可能什么情况都可能,所以大于这种情况下是不能用的,会出bug,最好的方法就是用不等于去判断。

逆向迭代器

还有一个逆向迭代器,就rb he rn的,注意是RB和RN的,它是你可以是,你可以理解为是倒车,所以倒车你也要给游客在往后走,所以他是也是加加的,就是从后往前加加,他虽然是从后往前遍历,但是你也要去给他加加他,因为他从后往前一个夹过去了,夹到前面,而且呢,这个他的,那么这样的话,你可以换过来去思考,那他最后一个元素就是指向最后一个元素的,但是前面那个元素就不是指向前面那个元素了,而是指向前面的元素和那个正方形的左边了。

如果你加加减减,绕不清楚,你就这么想,加加永远指向的是下一个元素减减,永远指向的是前一个元素。

当这个迭代器越界会怎么样?他会一直遍历循环下去。它编译时候是不有问题的那个C加加的编译器上是不会不会插手,你这个关于越越界的事情。而你在,但是你在编译好之后运行,它会一直在运行,一直在运行,直到某它运行到某一刻,触发到了他不能访问的某一个地方的时候,某个地址的时候,那么它就会触发段错误,就是,如果他没有,没有,没有跑到一个不能访问的地址的话,他会一直跑一直跑,一直循环,直到跑到一个无法不能够访问的地址,那么它就会触发这个段错误就会停下来。

所以那个begin跟end是不能越界的,如果你是加加的话,如果你是正向是加加的话,那你就不能超过那个and呢,你是减减的话,就不能超过那个那个begin,如果你是逆向的话,你加加的话,你就不能超过那个begin,如果你是减减的话,就不能超过那个end。

就说你如果是正向的,你反过来去做的话,那么你就是减减嘛,那么减减的时候,你就不能超过begin,如果你是反向的,然后你还是倒过来操作的话,你就从那个begin开始嘛,然后begin开始的话,你就减减减减就不能超过的嘛。

就这里会有点晕,但是总之减减的话,就是从那个最后一个开始,如果是正向的话,最后一次end如果是如果是反向的话,最后一个就begin,如果是减减的话,正向就从and开始减起,然后如果是反向的话减减,就是从最后一个就是begin从begin开始减起。

有个输入迭代器的一个概念,这个输入迭代器概念,你拿串口的一个缓冲区去思考类比,你就很容易想明白了。首先他这个是输入迭代器,它只能是读取出来,不能写入,因为你串口那个缓冲区就是接收你收进来的东西吗?放到缓冲区里面,然后拿拿去读取嘛,是不是?然后呢,他也只能读取一次,就是等于就是你那串口那个缓冲区那里,你肯定只能只读一次数据啊,你下一次数据可能就变了,是不是?那他不断在接收嘛,所以你基本上只读一次就够了,你下一次要变了,那你再读的话,就读必读了另外一个数据,所以它这个输入迭代器,你跟串口的那个那个缓冲区那里去类比,你就想通,你就想通了。

而且这个有这种输入迭代器,它的一个只能是加加不能减减,它只能是往前走,不能往后退的,不能减减,只能加加。

所以当你后面看到什么单向容器啊,什么单向迭代器呀,这种概念存的时候,他的意思就是说,他这种这种类型的东西,它是只能加加不能减减的。

输入迭代器呀,就input inter这种级的迭代器,你是不能保证每一次去迭代它的时候的顺序都是不变的。这是好比一个迷宫,你从出口,你从入口走到出口,有很多种走法的,不能保证它每一次的顺序都不变,每次迭代的时候的那个便利的顺序都不变,不能保证的。

这个是由一个容器跟迭代器跟泛型算法三个组合在一起的容器,就是数据结构算法,这个泛型算法就属于是一个方法。这个算法迭代器呢,是把这个数据结构跟这个方法作为一个桥梁结合在一起的,就是把它那个把容器跟范行通过迭代器去这个桥梁去结合在一起的。所以总共就有三个东西,一个是容器,一个是迭代器,这是泛型算法。

说这个泛型算法的一个触手点,就是一个切入点,其实就是从迭代器开始,先从迭代器开始。

就比如说这里,它有它有一个输入迭代器嘛,这个输入迭代器就是它是单通只读的嘛,刚刚分析过了,它是单通只读的那单项只能加加不能减减,只读是它只能读取不能写入嘛,这种输入迭代器它就是一个单通,只读了,那么它这种迭代器,它就是可以跟那种单通只读的这种泛型算法相匹配,就适用那种单通只读的那种算型泛型算法。

叫做输出迭代器,这输出迭代器就是跟输入迭代器是相反的,只能写不能读,比如说那个显示器,当然你要硬说它可以读截屏,那也算可以,然后这个的话,它也是只能加加不能减减的,而且是单通只写迭代器。

如果你的显示器,你,你作为这个单通只写这种迭代器的话,那么你就真的不能截屏啊。

除了这个输入迭代器,输出迭代器,还有这个单向迭代器,其实还有双向迭代器跟随机访问迭代器,这个双向迭代器是可以双向的,就是可以加加,也可以减减,然后这个随机访问迭代器是在可以加加景点的基础上,他还可以随机访问,就说你前面加加只能加一个,他在可以加两个加三个,一下子访问加三个之后的那个东西。然后这个几个概念,它是有一有有一些重合的,一个是一个比一个的基础上再扩大了的,是这样子的,然后这个你可以在那个网站的那个说明文档里面查它的一个迭代器的使用,它是一层第一层一层第一层的。

然后他这个有一个说法,就是她到了C加加二零之后,他就用了一个叫concept的东西重新重新重新设计了一套一套迭代器,所以si Jia jia20之后,他把这些刚刚说那些迭代器,它都在他这些迭代器的前面加了个legacy,就是说变成了legacy it rich or,那其实其实这个legacy it rich or跟之前的ritter是一样的。只不过在C加加二零之后,他用concept重新定义了一套迭代器,所以之前的那个就变成C了,然后但是C加加,十一幺四幺七这些还是可以,这些都是直接用那个,直接用it Rita的直接是支持的,不是没有legacy。

vector容器

这个vector容器它的用法,其实是array的基础上衍生出来的,就艾特他是可以,他也是数组,但它数组的大小可以拓展,这是跟阿瑞,阿瑞不同的一点,他vector里面,他,他会先预先给你留有一定的扩展的位置,就是比如说你定义是三,但是它大小是三,但是他可能会预定给你一个四或者是吧。那么,如果你最后你你的速度要扩容的话,只要没有超过他这个四或者是个这个八,那么他就还是可以轻轻松松的扩容的,那一旦超过这个四或者是八之后呢,他就必须得要做他真正的扩容的动作了。

就真正扩容的动作是,它是把它这个你的目前的这个数组的地址换了一个地址,然后把你数组里面的值原封不动的复制到那个地址里面去,它底层是这样实现的。

然后这样的话,所以你其实你迭代器是这种迭代器是不能,你只能是用迭代器这种类型,只能去访问这个vector比较好,你如果用最底层的那种指针去访问他,这vector里面的纸是会出大问题的,因为它一旦扩容之后,它的地址就变了,它是把那里面只复制到另外一个地址里面去,地址都变了,所以你用底层的指针是不能用来访问这vector的,只能用迭代器。

也正是因为这样,他会存在一个开销的不同,比如说你同样是加了一个,加了一个元素进去,让它开销可能不一样,如果你加了加了这个元素的,刚好他还是在他预留的那个大小之内的,那么他就只是帮你加进去而已,开销是很小,那你刚好如果加那个元素,在那个家那个元素之前,他的预留的那个大小已经满了,那么他就需要去把这个值复制到另外一个地址里面去,然后坐,然后把换换个地方,换到那个地址里面去了。这开销就很大很大了,所以可能同样是一个动作,如果有,有些时候呢,他的开销是很小,有些时候他的开销就会很大,因为他这里涉及到一个,他在不在他的那个预留容器之内,如果在他预留的大小之内的话,开销就很小,一旦超过预留容器,预留那个大小之后,他就要复制这个,复制这个值,放到另外一个地址,这里开销就大了。

为什么要预留位置

原因一:节省开销

至于这个为什么他这个外科他要预留一些东西呢,预留这个呢,一方面是因为他为了方便吗?就是你如你如果没有超过太多的话,他就用预留那就行了,这样就节省开销,另一方面这个也是为了减少内存碎片,因为你如果定义的时候,你又是三又是五又是七的,这跟这样内存上面就内存碎片就不规整嘛,所以他干脆就就按照四八十二十六这种类型。给你规定这样的话,内存碎片就这样,就不会出现太太过于严重的内存碎片,就是你的那个都是比较规整的,这也是一方,这这是一方面的原因,有两方面,总之是有两方面的原因,一个是为了给你预留了之后,你可以尽量的你可以,你可以减少开销,然后如果没有超过的话,就直接用预留的。

原因二:减少内存碎片

另一方面就是减少内存碎片,尽量用规整一点的统一的四八十二,这样的类型啊,这样的话也有利于效率,因为没有太多的内存碎片,没有说三五这种不规整的东西。

刚刚忘记说了,在C加加二零这段,新加了一种迭代器,这种迭代器要连续,迭代器叫做continuous。

这种迭代器的意思就是说,它不仅在迭代器的层面上,它的指针是连续的,它在物理的礼物理的指针,就是你的最底层的C语言的指针上面,它的也是连续的,就比如说数组这些,这种就是数组,就是经典的连续迭代器,就是它在不仅在迭代器层面上是连续的,它在物理层面上也是连续的,它在物理的这个在物理地址上也是连续的。

所以说数组是个连续迭代器,然后vector也是个连续迭代器,这个vector就说aray,它是连续迭代器嘛,Aray就是数组嘛,Vector它也是个连续迭代器,因为vector它也是一个数组,只不过它是一个可以扩容的数组。

也叫做动态数组。

刚刚说了这个外特,他是预留的比你定的那个还要大的那个元素嘛,比如说你定的是三个元素,它可能会给你预留变成四个八个12个,那么,具体它是变成四个八个12个试试,哪变成怎么样呢?是变成四还是八还是12呢?这个你是不能知道的,你是无法知道的,你也不需要知道这个,他的C加加不同的一个分配器,它就不同了,它不同的内存管理的方法的话,在不同情况下,你他如果就是说它取决于他的家里面不同不同方式的内存管理方法,这个就是看他的,它的内存管理方法呢,他是怎么管理就怎么管理,它有不同的一些分配器,就是取决于它用的是哪种。

当然,如果你想,你也可以自己去设计一个这种内存管理方法,用你设计的那一个。

所以你去看那个vector的定义的时候,它里面除了定义的class t之外,还定义了一个educator的模板K,就是一个内存分配器,它里面有个T,然后你根据你不同的内存分配器,你给他传不同的东西,它就按照你的内存分配器去进行使用。

这个K就是一个类来的,这个类是用来帮助我们管理堆内存的。

这里说了有一个这个说明文档里面有一个迭代器非法化,那你刚刚不是说这个迭代器不是跟着那个指针走的吗?就是他是正是对的,永远都是对的吗?那为什么还有个非法话呢?为什么他其实意思就是说,他他换了个地址之后啊,那你可能要把你的迭代器重新初始化一下,或者是就是就是当你换了个地址之后,你要重新用一次迭代器重新使用一次迭代器,可能是这个意思。

就是当你扩容之后,它跳转到另外一个地址,复制到另外地址的话,你可能要重新获取一次迭代器,这就叫迭代器非法化。

这个迭代器出非法化里面有有几个,有一些方法,比如说那个swap啊,或者是reset,这又是用来获重新获取迭代器的。

这个C加加有很多细节的,你就拿这个vector的构造函数来说它的,你去看他说明文档里面构造函数就有,起码有十几个,然后有一些是C加加11支持的,有一些是四加一四支持的,有一些CC加加,一七支持的,有一些是四加120支持的,这么多的构造函数,千万不要去记记不过来的,你只要知道一些常规的就行了,然后当你真正用在用的时候,发现有,有一个用到了跟你不太熟悉的一种构造构造函数的方法的时候呢,你就反过来一个说明文档里面查就行了,就当你遇到不熟悉的,你就去查就行了,你只要知道这个常规的就行。

新的遍历方法for(auto &ch : s)

有一种新的类型的,嗯便利方法就是for,循环的方法叫做range base的for循环,就你发里面只有两个元素,然后这两个元素用冒号分隔开,左边跟右边,你左边那个是你的一个要便利的一个指针,对象啊,必要便利的一个指针,一个临时指针诮息,然后冒号,右边是你的那个你要遍历的对象,比如说是那个,比如说那个character character,你这是一个对象。定了一个这个数组的一个对象,你就直接这么写,它就可以便利了,然后到时就,然后你在这个包里面直接打印C就行了,它会一个一个把这对象对,你把这个对象里面只一个一个放到C面去,一个一个帮你便利出来了,你直接打印C就可以了,不用什么加加,这是一种新的便利方法。

然后这一种迭代的话,它前面那个冒号,左边一般用auto的帮你做,你一般都是写auto觉得比较好一点,他让他帮,让编译器帮你做自动类型推导,比如说刚刚我们写的是charc,我们现在写的auto c,这样是会比较好一点,让编译器帮你做类型推导,用auto,当然你不用auto,用char直接你想指定的类型也是可以的。

这个vector里面有一个方法叫吊塔,不是吊塔,不是方法,有可能是变量叫吊塔,反正就叫吊塔,吊塔它是指针来的,它是指向你第一个元素的那个地址,就是真正的最底层的那个指针的地址,不是迭代器的指针,地址是指针它最开头啊,那个真正最底层的那个指针的地址。

非法化的意思就是地址发生了改变,就叫非法化,所以说,正常情况下,你将扩容也是有非法化的,也是叫非法化的,只不过你迭代器的时候,他可以重新获取迭代器,所以你就算非法化也没事,是这样子的。

关于这个list你要注意一点,就他有一个list跟一个forward list,这个list就是双向的,这个forward list是单向的,你可以理解,你以为这是一种链表,链表类的容器,然后那种双向的话,它在空间上面的话,就是就是占劣势的,但是在时间上是占优势的,但是单向链表的话,他是在空间上占优势,但是在时间上占劣势的意思就是说他双向链表吗?你肯定在相同的空间内,你要一方面要兼顾前面的,一方面要兼顾后面的。所以他一般都要把它同一个空间,把它分成两份,一个是前,一个是后,所以在空间上面的效率就比单项链表低,但是在时间上的效率就比双向列表高,因为你要访问前面后面的肯定比那个单项列表方便,就是他往后访问的话,肯定比单项列表方便,所以他时间上面肯定是比单项链表效率高。

相反,如果是单项链表的话,它空间上面的效率就很高,就是相同的空间呢,全部都是往前的,所以它的空相同空间下面,它空间效率是高的,但是时间效率就是低的,因为它如果你要往后往后去访问的话,逆向访问的话,它可能的开销就比双向列表大很多,它要用一些更加复杂的方法去逆向才行,所以时间上面的效率就没有双向列表高。

关于da qi da q,这是一个非常有意思的一个数据结构,你要理解他这个带Q,你最关键是理解他有三,他有三个东西,一个是map,一个是一个是它的图里面的左下角的那个starch,然后,一个是它里面图里面右下角那个N的那个map是总的单纯,就是用来存存那个地址的一个结,一个数组。然后你这个map里面,你一展开,每一个格子里面都是一个数组,每个数组里面假设都有八个元素是这样子,然后左下角那个start那个数组呢,它原有四个四个元素,第一个元素叫做current current是指在你那个map里面的开头的那个元素里面的那个当前正在指向的那个地址。

然后右下角的那个and里面那个current也有个current,那current是指向于那个map里面的最后一个数组,你那最后一个数组那里当前指向的那个地址。

然后你要注意的是就是它这个map里面,左边的数组,它是从从右往左插的头插的时候,然后那个嗯,Map里面最后一个数组,它是从左往右插的,就是尾插的时候。

然后然后这个时候那个左下角,那个start除了有current之外,还有一个starch,就是它里面也有还也还有一个开头跟结尾的一个元素,这个是指向的是map里面的那个,第一个元素的那个数组里面的头跟尾,然后那个右下角的那个APP里面的,除了开始之外,也是还有一个开头跟结尾的两个元素,它也是,它指向的是map里面数组的最后一个元素里面的那八个数组的头跟尾。

然后左下角跟右下角里面,除了start跟and,除了前面说之外,它还有个note,那note就是指,比如说左下角那个start里面那个数组的note,它是指向那个map里面的开头的那地,那个那个数组的地址,然后那个右下角那个and那个数组里面的那个note指向的是map里面的那个数组的右下的那个数组的最后一个元素的地址。

刚刚没有想起来那个左下角跟右下角里面分别指向那个map里面的那个地址的,那Mac里面的开头的地址里面的开头跟结尾的那个名字叫什么呢?名字叫finish跟跟lost,就是你左下角那个start数组里面有一个finish的last,右下角的N的数组里面有个finish的last,它的功能就是我刚刚说的那个,只不过我刚刚把这个finish的last的名字搞忘而已,那个名字叫finish跟last。

泛型算法

容器类中本身包含了少量的泛型算法

这个泛型算法的初步理解,首先,第一点就是其实它的容器里面也是有包含了一些少量的泛型算法的,比如说那个swap或者是那个。

再比如说那个sort算法,其实每个容器里面它都提供了这short算法,容器里面都有,而且它这个容器short算法是不同容器,都是一样的,都是有个short算法,所以就是说这个泛型算法第一点,它在各个容器里面都有少量的泛型算法的,但是不多,然后大部分的泛型算法都是C加加就把它抽象出来了,作为在外面独立,独立起来的一个泛型算法是这样使用。

然后第二点就是嗯,这个泛型算法,虽然它支持很多种容器,就是他独立出来的那个泛型算法,它支持很多种容器,很多种容器都可以用这泛型算法,但并不意味着这个泛型算法适用于全部的容器,每一个泛型算法它都会有一些规定的,就他的说明文档里面会有些规定,它能用于什么样的迭代器,比如说有一些,比如说这个,比如说这个扫他,然后他。那个泛型算法,它只能用于遗留随机访问迭代器,然后我们知道瑞跟vector,它是属于可以随机访问的,是属于遗留遗留随机访问迭代器的,所以它是可以用这个sort独立在外面的那个算法的。

双向遗留迭代器

但是那个list它是属于遗留双向迭代器,它并不,它并没有到遗留随机访问迭代器这个层级,所以它没有法无法达到这个条件,所以它是不能用独立在外面的那个sort的泛型算法的。

所以说也不是泛型算法,虽然它支持多个容器都可以用它,但是并不是说它所有的都支持,它有一定的条件的,你要看它是支持什么样的迭代器。从这一个角度看,你也可以知道迭代器就是容器跟泛型算法之间的一个桥梁,达到了什么样的迭代器的条件之后,他才能够去对这个容器进行操作。总迭代器就是一个桥梁,是用来判断它的条件是否成立的。

刚刚说的那点是对的,再明确一下,就是这个独立在外面的这个sort泛型算法,它是支持遗留随机访问迭代器的,所以它是可以用于瑞跟victor的,但它不能用于list,因为array跟victor它是属于遗留连续遗留随机访问迭代器的,但是历史的并不属于遗留随机访问迭代器,它只是属于遗留双向迭代器,所以历史它是不能用用那个独立在外面的那个thought放行算法的,但是a跟victor这两个容器就可以,历史的这个容器就不可以。

函数对象

函数对象本质本质上是一个类,它创建出来的对象,它本质上不是函数,它只是仿造出来的函数,它在看起来很像函数。

哪里像函数呢?它在调用形式上面很像函数。

这个函数对象本质上是一个类,你在定义函数对象的时候,你就用class去定义的。

关键是理解括号的运算符重载

要理解这个函数对象,就像是理解这个括号的一个运算符存在,其实函数对象跟函数是它看起来是一样的,他唯一多出来就是他多出来个对象的定义,因为它这个函数对象写的就是,他就给了个写了个运算符,重在而已,就是这么简单,他把一个括号变成运算符,从载了他实现,就是这么简单的实现的。所以说他没有什么很高深的东西,就是一个括号的运算符存在,然后他本身他是一个对象。你在用这个运算五重在肯定要先定义这个对象,想要肯定要先创建这个对象,基于这个类创建一个对象,然后为什么要有这那个函数对象呢?这就是因为,因为类呀,因为类它是可以有模板的,它它它在定义模板时候就就好一点,那函数模板可能就不太方便,所以你函数要写重在的时候,你你就要写很多个重在,而且函数模板可能没有类的模板这好用,所以它就没有那么方便,但是类的话。

但是类的话,你是可以你是可以做模板的,然后这个时候你你再这样的话,你等你用各类的模板的时候啊,你再加上这个括号的运算补充,再把它变成一个函数对象,那么在调用函数的时候,那么里面的参数你传差呢,就不一定是指定啊,什么硬核什么的,才行了。你可以事先的时候不用写那个是什么类型,先给个T,然后后面说你的函数里面X,再根据里面的呢,那船插去确定你的值的类型,它是本质是因为这一点,他才有了函数对象。就是因为函数重在麻烦,然后函数模板可能没有类模板好,所以他就用了类模板,用类模板的话,它在里面做个括号的运算符,重在使它变成一个很像一个函数的一个东西,然后在里面去传参,传参的话就就可以传多种类型的参,然后这就是函数,对象本质上就是这样子,就是多了一个括号运算符,重在方便一点。

然后用起来比函数单纯的函数好用,因为它可以传多种类型。

谓词

谓词是什么?谓词就是说就跟主谓宾一样,谓又是动作,所以函数啊,函数对象这些东西就是属于谓词,还有个叫拉姆达表达式,拉姆达表达式也是属于谓词,你可以理解为就是谓语动词,就是你要去操作某个东西,这个操作方法是函数啊,函数对象啊,这什么拉姆达表达式啊,就是谓语,也就是谓词。

函数对象可以用模板,但是函数不能用模板

这个函数对象它是可以用模板的,但是函数它是不能用模板,你要让它支持多个类型的话,你只能用函数里面的运算符重再救,你只能够用,不是运算符从载你就只能用过,你只能用重在技术,就只能用那个函数,重在才能使它函数有多个,支持多个类型吗?韩束重再是这样的吗?但是函数,函数是这样的吗?但是函数对象呢?它可以支持模板,这就是为什么要用函数,对象不用函数。

函数对象实现了静态局部变量的感觉

第二个原因,关于为什么使用函数对象,原因就是函数对象,它实现了一种局部变量,静态局部变量的感觉。

就是你这个函数对象,它是本质上这类类里面的一个方法,一个括号的运算符存在,那么它这个public它它是在public里面的,但是它可以调用里面的private一个变量,比如说你在他那个函数对象,这个类里面定义一个private的变量N,那么然后你再可以用你,那么你就可以用这个恩去技术,然后去在那个public里面,那个运算符存在括号,那个运算俯冲,在里面进行N加加。那么,因为你这个N是属于这个函数对象的类里面的private的,所以呢,只有它只有动用了这个运算符存在的时候,它才会计数,其他任何时候呢,都不会计数,而且它计数完之后呢,也不会被其他东西给改变被被被干扰,因为它是private的。

所以他private,它只能被那个函数对象里面那个运算符存在括号的那个给访问,所以你每次调用的时候,你只能是通过那个去访调用,所以它这个东西加加不会被其他任何东西影响,他就可以用来做计数器之类的,然后加完之后它不会变,它会保留在那里,所以这个就是函数对象的一个。第二个好处就是可以用来做一些计、计数器之类的,它可以把那些那个变量放到她的那个函数对象类里面的private里面去作为,然后拿个变量去加加作为技术,这样的话就只能通过那个public的方法去访问private,这是第二个方法啊。这就是第二个好处,有点类似于那个用C加加方式实现了C里面的静态局部变量的感觉。

这个泛型算法的一般使用他的括号里面就是会有三一般会有三个元素的,就是左边两前面两个就是是一个迭代器来着,就是圈定你要迭代的范围,比如说是begin到end是吧,前面两个是这样,然后第三个参数的话呢,如果你写的是一个指定的位置,比如说是begin。或者是说什么的,这个就不涉及到我后面说的这个,它就是一个指定的位置啊,这就不属于位置,然后它是第三个的话,除了我刚刚说这个,它还有一个更重要的使用方法,就是它不是指定某个位置,它是真的就是一个位置,这位置就是用来操作方法的。给你这个那范型算法给他传递一个操作方法,告诉他怎么做。

然后这个这个位置这里一般都是用函数对象,这里的话你就要非常的思路要非常的清楚。

就比如说你的一个函数对象,你在这里括号里面写就在那个泛型算法的第三个参数,那里写是greater括号啊,然后你要问题的关键点,就在于你怎么理解这greater后面这个括号。

就是说你这个grater是一个类是吧,它是一个类,你写在写在外面的一个类,这个类是用来判断它的这个大小的是不是,然后我们知道,我刚刚忘记补充一点说明,就是一般这个函数对象,他的作为位置吗?它的函数对象里面的方法,就是类里面的方法,他一般都是返回布尔值的,就是就是拿这,他这大部分,绝大部分都是返回布尔值的。然后你这个要回归正题,然后你这个函数对象嘛,你的great,然后他在外面定义的这个类类里面有一个返回值,是布类型的一个方法嘛,这方法也叫做great嘛。然后嗯。

啊这个方法不叫greater,这个方法就是叫做operator,然后括号是吧,就是这个greater这个类里面有一个方法叫做布尔operator括号是吧,然后它的括号右边还有一个括号,这个里面就是你要传进去的一些参数是吧,你的形参吗?比如说it什么,然后你就在这个这个布尔operator括号,这个括号的运算符,从载的那个里面对你传的这个参数进行一些处理吗?然后如果他大舅可能就返回。不返回真呐,然后如果小的返回奖是吧,然后你注意,我要想我要重要,重点要讲的是这里它有一个括号了,是不是就这个operator括号的右边是不是还有一个括号,里面写你传的型状是不是?然后现在我要重点讲的是它的这个泛型算法,它的第三个参数,它是不是叫做greater括号。

那么这个规则括号里面的这个扩规则括号,这个括号,他跟我刚刚讲那括号是两个括号来着,是他们不是一样的,就是说他这个规则的阔,这个泛型算法的第三个参数的规则,括号,这个是用来构造它,这个规则的一个构造函数的,就是它的这个括号里面传参,传的是那个参数啊,跟刚刚我讲的那个。Operator括号后面的括号里面传参数不一样,它是两码事。这个泛型算法里面的这个,那个greater括号,这个是用来判定它这个,则这个函数对象也就是这个类,它的构造函数是默认构造呢,还是按照有传参的构造。

就是说这个greater括号,这个括号里面一般大部分都是写空的,就是不写东西的,你不写东西,那么,就意味着你,这你在构造这个类的时候呢,你就你就是按照这个默认的构造函数去构造,如果你要在这个括号里面写参数的话,那么你就必须注定要在你外面的那个类里面写上你对应的参数的构造函数,总的来说就是这个,这个是跟构造函数是相关的。

然后还有关键要理解的就是你要理解这个泛型算法,他的第三个参数,这个规则括号,他是传到这个泛型算法里面去用的,就是它这个泛型算法,它也是一个方法来吗?这个泛型算法它里面绝对会用到这个东西的,那么你用到就是说他用到这里,它其实它的那个括号的运算符存在啊,他是在那个泛型算法里面才用得到的,就是说。这个括这个,这个外面的这个规则括号,这个跟运算符存在的括号是完全不一样的东西,这个括号外面那个是仅仅是传参,就是那个运算符存在那括号里面第三个参数,那个的规则,括号仅仅是传了一个参数进去,在传参的同时,顺便把这个函数的对象,也就是这个类型给实例化了,构造出一个对象。

然后这个对象他的是你用的是默认构造函数去构造的这个对象还是用的,按照参数,你指定的参数去去构造的一个构造函,构造对象就是取决于你这个,你传进去的这个发型算法,第三个参数里面括号有没有写参数,如果没有写,那么他就按照这个默认构造函数去构造你这个对象,如果他写了,那就按照你写的那个参数去构造这个函数对象,构造你这个这个对象,这个类的对象。然后他是在那个泛型算法的里面,就是它的这个泛型算法里面,它才用到了你这个第三个产参数,这个就是这个位置里面的那个括号的运算符存在,也就是说他在这个泛型算法的里面,它才用到了这个,比如说括号这个这个东西。

然后这个对象他的是你用的是默认构造函数去构造的这个对象还是用的,按照参数,你指定的参数去去构造的一个构造函,构造对象就是取决于你这个,你传进去的这个发型算法,第三个参数里面括号有没有写参数,如果没有写,那么他就按照这个默认构造函数去构造你这个对象,如果他写了,那就按照你写的那个参数去构造这个函数对象,构造你这个这个对象,这个类的对象。然后他是在那个泛型算法的里面,就是它的这个泛型算法里面,它才用到了你这个第三个产参数,这个就是这个位置里面的那个括号的运算符存在,也就是说他在这个泛型算法的里面,它才用到了这个,比如说括号这个这个东西。

也就是说你在那个泛型算法里面,你肯定也会按照我的理论推导,它里面肯定也会有一个greater括号这样的东西。

然后他当他的泛型算法里面出现了规则,括号之后,这个就是这个括号,那就是真真正正的这个operator括号的这个运算符存在的括号了,也就是说,他这个中这个括号的运算俯冲在也是有个特殊的一个点呢,就是他在用这个运算符存在的时候,他前面肯定要加上你对应的这个对象的名字的,也就是归特,所以这个就是为什么你会容易跟那个混淆的原因,就是这里它有个特殊点。它不像加减这种运算符中那样,你直接写个加写个减就可以看得出来,它是运算符中,在,它不是的。你在写这个括号的同时,你前面要写上它对应的这个类的名字,Greater再加括号,这样你才能够把它当成一个运算符,重在去用。那括号运算符重在去用。

这一点就是就是导致你容易混淆的原因,所以说当他的在那个泛型算法里面,他有他肯定我来推倒它,里面肯定有一个greater括号,然后里面括号里面肯定也是传参数了,那么这个泛型算法里面的这个规则,括号的,那它这个就是真正的一个括号的运算符存在了,然后它这个就是你那个规则方法里面的那个布尔operator括号,然后传参,比如说是传XX。然后这个方法这个东西的运算符,这个东西的内容就这个方法,就是你那个泛型算法里面的那个great括号,然后这个东西就说它泛型算法里面的great括号,它就是函数,对象就是它,就是那个运算运算符存在中括号,它那个也是,那个就不中括号,不是中括号,就那个括号的运算符存在。

然后这个时候他的泛型算法里面的great括号里面传的参数,这个里面传的参数才是真正的你在这个函数对象里面定义的那个不operator,括号右边有个括号定XX,比如说船什么的,这个这个参数它才传到了,他才才是这个,这个这个泛型算法里面的那个,那个规则,括号里面括号之后,然后你然后他再传再传参数。它这个传的参数才是你的那个类里面的那个不operate括号右,然后后面的那括号里面对应的那个参数,它对应的那个参数是传到了泛型算法里面那个规则,括号右边再加个括号里面的对应的这个参数里面去了,讲的有点绕,但其实我已经讲明白了。

就是他的这个泛型算法里面肯定也有个greater括号,然后右边再多个括号,然后有个传参,这个里面参就是真正才是对应的那个你的这个函数对象里面的那个那个public,那个pool operator括号,然后右边还有一个号对应的那个参数类型,就这个跟这个是才是对应着的,这个才是真正的括号的预算存在,然后因为它有一个有个这么个特点,括号的预算存在前面必须要加上这个。对象的名字比如说greater才能用,而不像加减啊,运算符那直接写加写减就行了,所以种种原因就导致你应该这么去理解,这个想的是有点绕,很绕在里面,但是我已经讲的很明白了,就讲到这个层次,你去继续去,按照我之前讲,刚按照我刚刚讲的逻辑去走,你是可以理解完理解彻底的。

为什么会觉得这么绕?因为这里面包含了两三个很复杂很细节的一个东西,然后这两三个很复杂很细节的东西呢,包括了比如说你那个泛型算法,第三个参数它既可以是为什,也可以是指定的值啊,包括后面的一些operator括号,这个运算运算符存在,它就跟加号减号运算符存在不一样,必须要加对象名,然后还有就是包括那个。括号它对应的是,其实你在那个泛型算法第三个参传递参数,那个立邦的括号,它先它不是指的是你的那个运算,补充的括号里面的东西来的,它它默它在那个阶段还只是构造函数的一个概念来的,它只是取决于它是默认构造函数还是代参的构造函数而已的,就总共就是大概就是这三个细节吧。

也不一定是三个,可能刚刚说的就是三个半或者是四个细节吧,就是三四个,345个细节,每个细节都值得去深入探讨,去深入的,去去哇,你才能看懂才能才能理解懂。然后这三四个细节,35个细节呢,他又杂糅在一起,然后组成了一个大的一个很庞杂的问题,所以你要去理解他的话就很麻烦,所以刚刚讲起来就有点绕,但是没办法,但是绕归绕,他无非也就是一个庞杂的问题,然后有。三三十五个小细节去组合在一起,然后把你绕晕你,我刚刚的已经把它绕明白了,所有的细节我都把它理清楚了,刚刚的这些所有的话你重新再听一遍,你是绝对可以理解完的,它无非就是那么几个细节,345个细节,你把那东西细节完全理解完之后,这个大的细节你就完全可以理解透了,把我之前思路里面你是完全能彻底理解了的。

lamda表达式

本质上是一个匿名函数

这个拉姆达表达式,它的本质上是一个匿名的函数,就他没有函数名,所以这就决定了他只能在他写的那个地方,用一次,就是他只能在他写的地方用一次,因为其他地方你找不到这个这个函数,因为它是匿名的,它的主要形式就是有三个括号,第一个中括号,第二个小括号,第三个大括号,那个中括号从本质上来讲。就是用来省略函数名的,这个中括号里面,无论什么时候都不要写东西的,它是用来省略函数名的,然后那个小括号,它是写那个你的那个要传的参数的,就是你这个函数,它可能要传参数嘛,那就把参数传进去,大括号里面是要写这个函数体里面的内容的,然后它是就是这三个东西组成。

然后其实这三个东西是有省略的,就是他省略了一些东西,比如说,当你要写明确标明你的返回值是什么的时候,那么你就要写个箭头,然后右边写上你返回值的类型,如果你没有明确写返回值的类型的话,那么他就可能就是会默认你是布尔类型的,就是你们要写箭头的话,返回值它默认是布尔类型的,然后他箭头的左边还可以在写东西的,就是写你这个声明,比如说你这个函数是table行还是。还是其他类型的,就是他可以写一些声明,箭头的左边,嗯,这就是他省略掉一些东西,最基本就是那三个括号,然后它还有一个功能,就是如果你真的要给这函数命名的话,那你可以在这个整个的东西左边加上个等号,比如说你方选一等于中括号,小括号大括号,这样是可以的,那么你就给这个匿名的这个。

那么你就给这个匿名的这个拉姆达表达式,这个匿名的函数起了个名字,写在了左边,比如说方程一等于中括号,括号大括号,这就是给他起了个名字,意思就是说他可以在右边给个等号,然后再写个名字给他起个名字给他的,然后这个拉姆达表达式就是这么多东西了,就是其实挺简单的。那么他为什么要做出一个拉姆达表达式出来呢?就是为了省事,他可以把一个函数写到一行里面去。那么你就不用把这个东西写到写,额外写个东西,然后腾出腾出个东西来,写个函数,写个写个类,写个那种函数出来嘛,你写个函数就要两三行四五行了,是不是?那么他把这些函数写到了一个栏目的表达式里面,就一行,这是很方便,就很简洁,他本质设计出来是为了这个的。

然后你如果要用这拉姆达表达式的话,那你这个函数就只能用一次啊,那是因为它是匿名的,所以一般用拉姆达表达式来做的话,一般就是你要首先你要确定这个函数,它基本上就只能在你这个地方用一次了,其他地方都不会用到了,那么你才会用它,这也是它的第二点一般用它的时候,你就要确定,它一般是在这里只用一次,其他地方不用才会用到拉姆达表达式。

也就是总的来说,当你确定你这个函数只是在这个地方,用一次,然后你又为了省的麻烦重新写一个函数提出来,那么你就把它写到一行里面去,然后审视,然后这个函数又只在这里用一次,那么你就去在这地方用拉姆达表达式去写,然后在拉姆达表示,本表表达式本质上就是要匿名函数,它也是属于位词的一部分,它是写到泛型算法的参第三个参数那里去的。

哦,然后再补充一点,当你要给这个拉塔数,也就这个匿名的拉姆达表达式,也就是匿名函数给他起名字的时候吗?不是刚刚说可以有这个功能吗?在左边写那个,等号再给他起名字了,那么这时候你给他起名字的时候,他的名字类型你就可以用oppo,而是不用去写那个复杂的函数类型名啊。因为你这个时候你如果不用auto的话,因为其他的复杂的函数名字的话,函数,函数类型名字的话,那就很麻烦,每次都去推去想,这时候你直接给个o tu让他自己去推导,这就很舒服。

lamda函数只是写起来简便,但是效率并没有提高

首先你要注意一点这个,那么这个表达式它只是少写的东西,把它写到一行去,它只是写起来简便,但是在效率上面并没有比普通的函数高多少,甚至说没有高是一样的。所以它体现的优势并不是在效率上,而是在你写的简便性上。

中括号是可以写东西的

之前说那个中括号里面没有写东西,然后是匿名的函数,但是实际上在中括号里面是可以写东西的,这个东西叫参数补货,我总结来说,就是当你写上一个普通的参数,普通的一个变量的话,它就是值传参,当你在这个变量的那里加上个案的符号的话,那么就是引用传参。然后如果你只加一个等号的话,那么就是把所有的东西都直传插进去,然后如果你是只加个and的话,那么就是把所有东西都引用传插进去,然后你如果是等号加上其他的逗号,另外两个是and的话,就是除了这两个and之外,其他的都是引用传,都是直传插进去,然后那两个是引用传插进去。

然后如果是N的,然后另外两个是什么都没,什么都没有,直接一个变量名的话,那就是除了那两个变量之外,其他都是引用传参进去,那两个就是只传参进去。

然后如果是中括号里面写历的话,就是把那个this的那个传进去,然后你会发现这个史它是一个也是一个值传参的,你把历史的值传参进去,那么你就可以用这个this,然后去指向里面的其他的值了。

像刚刚说的那个等号啊,直接中括号里面写等号,还有直接中括号里面写M的符号,它是包括了日子在里面的,就是当你这么写的时候,你也是把子给涵盖在里面去了的。

然后这个参数补货,他也是只能在它对应的那个层叠对应的那个层级里面的参数才能补货,她再上一个层层级了,那上面的参数就补货不了,只能是跟它对应的那个层级的那个参数才能补货的。

适配器

适配器本质:是不同函数变成相同的传参个数

然后有一个适配器的一个这样的东西,适配器的本质上就是使你不同的函数,它变成相同的传参个数,就比如说函数a,它有三个船舱函数B,它只有一个船餐,那么你这两个函数一旦要是配的话,那么,就需要把这个函数一函数a的两个叉的三个餐变成一个餐,这个就是函数适配器。

这个函数适配器呢,在C加加九八的时候用的是bind first跟bind second,这两种东西去做函数适配器,然后到了C加加11的时候,他用的是就是直接用一个bind,然后去做STD,冒号,冒号,Bind去做那个函数适配器。

然后这个函数适配器,这个bind一般怎么用呢?就是你这个ban的括号里面,你第一个参数填的是这个函数的名字,然后呢,后面的话就是你可以用一些占位符去占位,占位符的名字叫做place hold,然后place hold之后,他还有一个,后面还会写上一个下划线一还是下划线,二还是下划线,三这个下划线一,下线二,下划线三,这就是就是指的就是你要占位的,你要你要把那个函数里面哪个参数先进行占位,保留下来。

举个例子,就是比如说你这个函数a,他传了三个参数,一个是一个是硬核类型,第一个参数是硬核类型,第二个参数是char类型,第三个参数是第三个参数是那个,第三个参数比如说是double类型。

然后你要用函数设备器把它这个三个参数变成两个参数的函数,那么你就可以用ban的就是呢,然后括号里面的第一个参数填上那个函数名字a,然后逗号,这时候你如果是要保留他前面的int类型,还有那个差类型的这两个参数,然后去掉那个第三个那个double类型的参数的话,那么你就这样子写,就是就是place hold,下划线一,然后逗号place hold下划线,二逗号,然后在后面的话你再填,随便填,随便填上一个double类型的参数就行。

然后这个时候你的这个新的这个函数就变成了只传两个参数的类型了,然后你这样,这个时候呢,你就在用这个函数的话,比如说你是写个方选B,等于这个a等于办的a这个东西吗?那么你最后B的话,你这函数传参你就可以只传了inter类型,跟那个ta类型的给他了,你就可以本质上那个配套的就是占位,保留就是保留站位是把那两个参数保留下来,然后呢,又最后那个double类型参数你随便填那个就给他了,就没有了,就剩两个了。

然后这里还有一个细节,就是如果你在用的时候,你是用这,你是这么用的,就是败的括号里面是函数名字a,然后逗号,然后你第一个写的那第二个参数,你填的不是下划线,一是填的是place hold的下划线二,然后第三个参数填的不是下划线二,而是place hold的下划线一,然后当然第四个的话就是逗号,随便写个大宝参数给他们。我这里想要说重点是你的有个顺序调换的,就是你是先是place hold的下划线二。然后再是pay凑的下划线,一这个时候的话,你是重新你这你,你以这个为基础,重新生成的这个函数B,那么它你要你传的第一个参数就不就不是之前的这个int类型的参数了,而是差类型的,然后你传的第二个参数才是int类型的。

因为你在构建这个新的函数的时候,你的第一个参数,你用的是用的是place hold,下划线2PLACE hold下划线,二对应到那个a那个函数里面,它就是对应的就是第二个参数,第二参数是差类型的,所以你的你的那个你把下划线二放到第一个,就是你把差类型的放到了新生成的那个函数里面的参数的第一个,然后他第三个是下place hold下划线一,也就是说place or place hold下发现一在。A,之前那个函数里面的参数是int类型的,它是第一个嘛,对应的是第一个int型,你把它放到了对应的生成的C的函数里面的第二个参数,所以它的生新生成的那个函数,它的参传参顺序就是先是play hole的下划线,二再是place hole下划线一,Plays hole,下划线二对应的是里面的之前的char,然后play hold下划线一对应的是之前的那个。

是,所以你新生成的函数B,它的传参类顺序不应该是之前和之前一样的什么int char,而是先是char,再是int,因为你你是先写play后下角线,二再写play place下角线一所以它的你对应起来就是先是把下角线二作为它的第一个参数,再把下角线一作为它的第二个参数下角线二对应的是char,下角线一对应的是int,所以它的新生成的函数B的传参顺序是先是传了一个char给他,再传int给他的。

for each

然后后面还看了两个遍历的,一个算泛型算法,一个是for each,一个是全凤for each的话就是单纯的便利,便利完之后你去,你要得到哪个值,你就娶她,那个值就行了,全缝的话就是你便利之后,你的第三个参数还可以传一个处理,就你遍历完之后对你这个参数进行处理,可以这样子,比如说你便利的是一个字符串,Hello,然后呢,你可以把它处理,把LL都处理成那个20个类型的吗。的变成int类型的数字,然后或者说你便利完之后,你把它的那个int类型的每一个数字都乘以二,就是它,它可以这么搞,就是全放的话,它可以在你便利完基础上,之后再做一些操作,返回出来给你,然后一放一起的话,就是单纯便利,便利完之后,你你要得到哪个值的话,你就把那个值写进的的第七个写进去,它就可以返回,你要得到那个值给你。

就说transform在博弈学的基础上,多一些可以操作的余地,你可以对你遍历完的数据进行一个更进一步的操作,这是我目前的理解,可能有点不准确,不过这个不是重点,先跳过吧。

谓词补充:一元谓词和二元谓词

关于谓词再补充一点,它的英文叫做predicate,然后还有个叫january january就是单一的意思,所以当写到january predicate的话,就是它是说一个它是一个一元谓词,一元谓词就是它,它这个谓词的叫函数,它只传一个参数。

然后这个泛型算法之前不是说他有三个参数吗,一个是前面那两个参数是迭代器,就是从就是便利嘛,然后第三个参数就是位置吗?就是操作嘛,然后你知道这里你要注意到一点,就是它这个便利,就是她每遍历到一个,他就用一下第三个的参数,那个位置每变益美便利到一个,他就用到第三次参数的那个位置,每遍历一个,就就用第三个参数的位置,然后直到他遍历完那迭代器遍历完,所以他那个位置,他是每遍历一个都用一次的,每遍历一个都用一次的,他的位置,他泛型算法他是这么设计的,那三个参数是这么几,它的机制是这样子的。

之前在讲泛型算法的时候,说他那个第三个参数是吧,就是第三个参数,如果他第三个参数他,我刚刚那个时候说错了,其实第三个参数,他不一定一定会是那个with的,他可能还是一个迭代器,就可能还是个it类型啊,就是说他泛型算法,你没有强制要求说他一定要有位置的,当他没有位置的时候,他可能第三个参数还是一个迭代器,比如说a or。那么这个时候它没有,没有喂食的情况下,它就会按照它默认的方式去排序,就说你喂食,只是在这基础上,按照你喂食的方式去做操作,当你没有写上你喂食的时候,那么它就按照它默认的那个唤醒算法里面默认的去排序,就是这样而已。就说它犯刑法里面可以不传喂食的第三个参数甚,甚至可以还是一个迭代器。

举个例子,他有个partial上这一个泛型算法,这泛型算法他的他就没有位置,他第三个参数还是一个迭代器,就还是一个指针,那么它这个就是它这个算法它是干嘛用的呢?就是把拿你前面那两个参数不是一个迭代器吗?从begin到end吗,去迭代,迭代完之后呢,然后把你可能会他可能会就是说,就这中间可能会迭代之后,它会那泛型算法里面,它会有一些操作,他会得到一个新的东西是不是。那么得到这个新的东西,他就放了,放给那个他的传进去的第三个参数,就那个以特瑞特第三个迭代器,第三个参数,他可能会把得到的那个新的东西放到那个第三个参数里面去,放到第三个迭代器里面去,因为第三个迭代器本质上就是指针嘛,就他可能会把得到一些新的东西之后,放到那个第三个指针里面去,就是第三个迭代器里面去,他操作就是这样的,所以没有位置也可以。第三个参数是迭代器,就是这样子。

他说,之前说那个usually就是单一的就是一元的,然后那二元怎么说,二元就是finally finally就是二元。

这里你会发现你又遇到了一个,除了这个传,第三个参数除了传那个为词,还有传那个迭代器之外,它还有可能会传别的东西,比如说这里传了一个binary operation这种东西,这就是一个二元的函数的操作,就他可能还会传一个这种东西进去,他可能他可能还是会传一个函数,这个函数不一定是那个,不一定是那个位置,就是不一定是那种我们之前说的那种位置,就是我们之前说的那种位置的话。它可能就是我们用的是括号嘛,括号运算符存在嘛,这个地方我怀疑它的这个operation,就那Barry operation,那可能它不是括号的运算符存在,它是对于另外符号的一种运算符存在,它是对于另外符号的一种运算符存在,然后这个也是可以作为泛型算法第三个参数传进去。

这个winery operation,这个可能里面它会有一个对加号的运算符存在,那他他是一个二元函数finally of就是二元函数,它可能它这里面肯定是传两个参数进去,那肯定是传,拿了两个参数进去改了,可能会用他所自己定义自定义的一个加号运算符存在,然后去进行操作,这个时候你就可以传一个班operation进去,这个时候的话就是不是括号运算符,存在就是加号运算符存在。也是这样,就是你除了传位置普通的括号运算符存在的位置,作为第三个参数,还可以传那个,第三个也是一个迭代器,作为第三个参数,你还可以传,不是运算服从在是括号的,而是运算服从在是加或者其他的,这种叫做binary operation,或者叫做union operation就自定义的一个运算,服从在作为第三个参数传到那个泛型算法里面去,也是可以的。

要有这个加号运算符从载这个传到泛型算法里面呢,就是因为他可能家不是普通的一加二等于三可能是字符串的相加,那你肯定要自己制定一个,他字符串之间相加是等于什么呢?一个这样的一个算法的一个函数操作方法,传到那个泛型算法里面去的,所以有这个第三个参数是传的operation finally operation或unit operation呢,也是合情合理的事吧,他其实也是一种喂食来着,所以只是一种跟我们刚刚讲那个位置有点出入的一种为什。你就把它当做特例去记就行了,它它它的存在是合理的,因为你要加的话,你不一定只是硬的加嘛,有可能是字符串加,那不同的人呢,对于不字符串的,加了它可能的它当时的那个设定可能是不一样,所以你要自己把你自己的设定传进去,然后你定义它是怎么加它怎么处理,这也是一种位置嘛,只是跟我们刚位置有的出入,你把它当特例记就行了,就是个by the operation或者是union operation这种。

总的来说总结就是他第三个参数,可以传迭代器,也可以传喂食啊,喂食的话还可以有一个比较有有意思一点的位置,就是跟我们之前的位置里输入了就是unity operation或者是banner of operation就定义运算符存在,除了存在括号之外,还存在一些加号啊,其他的运算符,你可以自己自定义,就是它可能会遇到这些情况,你要字符串相加之类的,那你要自己自定义一个,他将加厚是什么,你要传一个这种人。这样的一个运运算符存在进去给他,这也是位置,这是所以只是跟我们刚刚讲那种括号运算符存在的那个位置不太一样,就是总的来说就是这两种啊,就是一个传迭代器,第三个参数传迭代器,第二参数传递,第三或者是第三个参数传那个位置,位置里面有一个稍微一点小的分支,就是那个by the of operation跟union operation。

然后就到了这个关于策划的这个概,这个这个章节,这个策划的话,其实就是你首先第一点你要明白,就是这个编译器,他编译的时候,他是优先去找,还有优先级的,首先去找你的,如果你是有同名同名的吗,如果有同名的话,你首先去找他有没有普通函数普通方法,然后接着再次去找你有没有策划的策划的模板,然后接着再次去找没有策划的模板,它是有三部的,就是在编译时候确定的,运行时候就不用确定了,现在效率高。

然后这个策划呢,就是说你原本你那个模板嘛,模板里面它是不是个T,你把它策划之后,比如说你策划成string,让它变成string啊,然后你当你下面你要用一个这个模板,那但但但是你要用是string类型的时候,它的编页时候就直接用你策划那个了。

模板的全特化和偏特化

然后关于全特化和偏特化这个概念就要涉及到,你要传两个参数,就是你的模板要有两个参数才能有这个权。策划跟偏侧化的概念,就是当你的模板有两个的时候,模板也有两个T的时候,你这你,如果是把这两个题全部都策划掉了,全部都给他类型具体化了,那么就就是全策划,如果你只策划其中一个,另外一个还是一个模板T,那么就叫偏策划,然后事实上偏策划用的比较多。全策划用的比较少,为什么?因为一旦全特化,那你就失去了模板存在的意义了,是不是?所以肯定是偏策划比全策划用的多,偏策划的话,就还有一个是是模板类型的,是T的,那么你就还是有一些留用的,可用的余地的,如果是全策划,你全部都给类型全策划了,那你就没有余地了,你就是这样子。

然后这篇策划里面呢,刚刚说的那种偏策划的方式,就是两个题,有一个策划掉了,给他指定具体类型,另外一个题保留,这是偏策划了其中一个方式,他后面还有一些其他的方式,比如说涉及到指针的呢,后面的话下午再看了,就是说偏策划,不仅只有不仅是有两个参数,把其中一个策划掉,另外一个不策划,这种较偏策划,其他的还有几种情况也较偏特化后面再看。

这个偏特化,除了有之前的那个传两个模板,模板里面有两个参数,然后把其中一个特化掉,这种方式叫特化之外,还有另外一种,还有另外两种,一个就是策划成指针,就是变成指针类型的,然后当你再用这个模板的时候,你用的是指针的话,那么他就不去不去找你那个泛化的模板儿,去找你那个带有指针的策划的模板。另外还有一个就是那个里面还带了一个类的一个策划,就是你的那个模板类里面还带了一个模板类,这样子一个策划。

是这样的,然后如果是这样的话,你在那个写的时候,你用的是比如说他的那个vector,你用的是外科,那么他就会去找里面的,他就不会去找你放话那个,而是去找你那个策划的那个模板类里面又有一个模板,又又有一个类的那一个策划的版本。

就拿vector来说,他是一个尖括号,里面又有一个vector尖括号,他如果是去,你如果是用这个东西去做策划的话,那么你就会他就他就会找你这个策划了,而不是去找你那个泛化了。

第二种偏特化的理解:特化成指针

简单来讲就是它这种策划跟之前那种策划,就之前那种两个参数值策划一个参数的这种策划方式不一样,他这种策划方式就是说,当你用这个东西的时候,你用的是你传给他的是一个类,这种性质的话,那么他就会找你策划的,有类的性质,那个如果你用的是地址用的指针的话,那么他就会找你用的指针的策划的那个,其实策划的本质就是让你去找到一个。范围更小一点的东西是不是,是不是这个意思,所以他你策划出一个指针,策划出一个类出来,他范围就缩小了,到时候你再用这个东西的时候,他如果你你用的是指针,用的是类,那么他就会去找你范围更小了。所以这个其实也好,很好理解,他策划就是把范围缩小了,那他就是把一个范围缩小到了一个直线,定于指针,还有把范围缩小到一个直线定于类,是不是就是这个意思。

这个就是第二种理解的偏侧化,就是把范围缩小,其实第一种也是把范围缩小了,就把范围缩小了,就不是全部的一个范画,他偏嘛,就有点偏向于特化了,但他又不是完全的把范围缩死到一个地方去,就这个意思就是,嗯这个怎么说呢,也不能这么说,就是总之就是他把范围缩小了,那就是偏策划了就是。可以,这话的意思就是把范围缩小,你可以就是这么理解,把你圈定的范围缩小了,之前是所有的范,那你那你现在从两个参数变成了一个参数,那不是也把范围缩小,缩小只有一个参数可以可以去泛化,是不是,但是它还是有点细微的差别的,所以这两种策划方式,你还是要区分开来,去理解。

函数模板为什么不能偏特化:和函数重载功能一样,造成一定的尴尬冲突

然后还有一个要重点解释,就是函数模板为什么不能偏策划,这个就涉及到一个C加加的一个哲学的一个抉择,一个取舍,因为函数模板的一个偏策划跟函数同在,它有它是相同的功能,这里还要再补充一点,就是这个模板函数啊,就是函数里面带模板呢,它也是可以存在的,他是顺延的吗。你既然模既然函数可以重在,所以C加加设计的函数模板肯定也要重在嘛,不然的话说不过去嘛,不自然,所以模板函数,这种函数模板肯定也是可以重在的,那么模板函数的虫在跟这种模板函数的这种偏策划,它的功能其实是一样的,它就是本质上就是去找更加合适的一个东西。

正是因为这个,他们两个功能都是一样的,那么就涉及这个取舍问题,因为模板函数它也是去找他,他的一个存在,也是去找它对应的那种类型,然后去用那个类型的,而不是用那个范华那个吗?是不是那个模板函数,如果它是偏策划的话,函数模板如果是偏策划的话,他不也是去找那个范围更小的,然后不用放放华那个吗?他们功能上是一样的。如果他们两个都一起,私家家如果都承认的话,那么就存在一个说谁的优先级更大的问题是不是?如可,如果他如,但是这个的话就很麻烦,因为你没有任何一个理由规定说哪个的优先级更大,是不是?

所以说他肯定不能够就凭空设置出一个优先级嘛,这样更麻烦是不是?因为你如果不射,你如果不设优先级会怎么样,你不设优先级的话,他就直接就让他去找的话,那你可能有些人用的是模板,函数模板的存在,有些人用的是用的是,用的是函数,模板的偏策划,那有一些人是这样用,有些人用你肯定要同时出现,你肯定要给个优先级嘛,是不是?所以这很麻烦,然后又考虑到了什么。那个函数模板,它的一个存在,它是从古至今一直沿用起来的,就是它的函数本身是可以存在的,对应过来函数模板它也是可以存在的,那既然你都已经前面都有这个功能了,那你就没有必要再去支持一个新的一个函数模板的拼特化吗。

而且你支持一个新的函数,模板的偏策划之后,你又要去考虑这个函数模板的存在跟函数模板的偏策划,哪个优先级高,是不是越想越麻烦,但是呢,你又不能够,你肯定是不能废弃之前的函数模板的存在的吗?因为这里肯定是绝对要有的嘛,因为他要沿用之前的嘛,你模板可以存在函数,模板你函数可以存在,那模板函数肯定也是可以存在的嘛,所以函数在函数模板存在不能取消前前提下,这个已经沿用了,之前已经有了。那你就直接用函数模板的重在不就行了,就可以解决这个问题了,那你就没有必要用函数模板的偏特化了是不是?你加了一个函数,模板偏特化之后,反而又要多加一个优先级的一个规定,这就麻烦很多,所以,所以他们根本就没有必要去支持函数模板的偏特化,直接用函数模板的重在就可以解决问题了,所以这就是为什么函数模板,他最终决定不偏策划,不没有偏策化的一个。

的一个抉择,他最终就决定了函数模板不能偏策划,原因是在于此。然后,如果你是用了函数模板的偏策划会怎么样呢?你如果是用了函数模板的偏策划,那么他编译器那里就过不了关了,他编译器那里就拦着你,不让你去通过了。

刚刚要说刚刚说的里面你尤其要注意一点,就是函数模板它也是可以存在的,这个函数模板他崇在的时候呢,你会很容易,你你一开始的时候容易混淆,就是你可能会容易跟那个函数混那个普通的函数模板混淆,因为你的函数模板的存在呢,也是要写上template的,你要注意这一点十分重要,函数模板的存在也是要写上template的,Type那些的也是要写上尖括号的。所以你一旦没有看清楚,你就可能以为它是一个普通的函数模板,所以这里你要注意,就是当你当你看到一个函数,它出现一个尖括号这样东西,它不一定只是一个函数模板而已,它可能还是函数模板的存在,函数模板的存在也是这么写的,就函数存在这点是肯定不用说啊,肯定是你是知道它就是。

他就是写上不同的类型嘛,就可以构成函数存在,那函数模板的存在就是在你这个函数存在你里面写上不同类型,是不是里面写上不同类型的这个基础之上,那还要在这个函数的上面加上一个template的之类的东西,所以就可以容易让你引起误导,就是你可能以为他是一个普通的函数模板,但实际上它是函数模板的存在,所以这点你注意就看它里面就不一定是函数,不一定写特里的函数,就是普通函数,他写的特里的函数可能还是一个普通的函数模板的。同在就它不一定只是一个模模板函数,它可能还是模板函数的同在,你要再记进去,再看它下一行,它函数里面传的参数有没有规定,就是一个特定的类型,如果它有规定特定的类型,比如说int啊,比如说int星啊,还有它的template下面的函数名字里面规定了特定的类型,这个参数类型里面,传参里面规定了特殊的类型,它很有可能就是。

他很有可能就是函数模板的存存在了。

总而言之一点就是函数模板,它是不能偏策划的,因为没有必要支持你直接用函数模板的虫在就可以解决这个问题。如果两个都存在,那就会存在一个又要先要在设定一个优先级哪个优先级更高的问题。然后又因为函数模板的虫在本质上是从沿用了之前的,没有必要去舍弃。综上的种种原因综合考虑就决定了函数模板,它是不能重在函数模板,它是不能偏策划的,函数模板只能是用来函数模板,它可以用来虫在,但它不能用来偏策划,然后你仅仅用函数模板的存在,你就可以解决问题的,没有必要用函数模板的偏策划。

这里有一个编译器的匹配规则,就是编译器,他首先是去找你普通函数,普通函数没找到,然后再去找你的放行放画的那个模板,当你还是没有找到放话的模板的时候,就说明报错了,就有问题啊。如果他找到放话的模板之后呢,它就会接着再去找有没有策划的,当你找到策划的时候,他就用策划的,如果没有找到策划,他就会退一步。用回之前的前一步的泛化了,然后在这个过程中,他每一步都会去看,他都会去找你的重在,比如说你的函数,普通函数里面会有,重在你的泛化里面也会有,重在你的特化里面也会有重在的嘛,那泛化里面是是就就是嗯,就是重在嘛,是不是他会每一步都会去找重在。

然后这里有一个小细节,就是你的普通函数的重,再跟你那个函数模板的策划里面的一个虫在,当然是全特化了,函数模板只能全策划,它的功能是一样的,就是说你普通函数里面,比如说有个硬核类型的一个函数存在,然后你的那个。它还有个对应的函数,模板里面有一个全策划,策划成了一个int类型的,那么你如果,如果你的这个数就如果你要你要操作那个数啊,刚好是要是int类型的数操作的话,那你就既可以用这个函数模板的全策划的这个int存在,也可以用普通函数的这个int存在。

当然用的时候写是不一样的吧,当然用的时候int的话普通函数int,你就直接int就行了,然后你用的时候,如果用是函数模板的全策划的那个int的存在的话,那你就是直接就是就是一个int,就是你要监加监括号才再加int才行是吧。

然后嗯,这两个虽然是重合的,就是它功能是可以一样的,但是它并不会冲突是不是?就说你两个都写了的话,那么你看你怎么写,是不是你如果写的是硬核类型的家境括号,那就是函数模板的全套那种存在,如果没加进括号,那就是硬核类型的存在是吧,所以说这一点就可以得出结论,什么得出就是函数模板的一个存在,就是就是也不是就是函数模板的一个全策划,他不参与函数存在。

因为你一旦参与函数存在的话,那你两个都是硬核类型的,那你用哪一个呢?是不是我刚刚前面说的有点乱,你现在听我这里说的,如果你那个函数模板就全策划之后,你参与了函数存在,那你写了你这个函数模板,你写清楚,你普通函数也硬的,那它是用哪一个呢?他不知不确定是不是他,他是不参与函数存在的,它是独立的,用个监控号去实现的函数模板的全特化的一个东西,它不参与存在,你写inter的话,它就默认就是用你那个。普通函数的硬盘,你如果加精括号,那才是用函数模板的全套化,所以它是不参与重在的函数模板全套化,是不参与重在的,它不会去普通函数里面去,这样重在不会去参与,重在重在只是重在普通函数,它那个普通函数参与重在全函数模板的全套化,它不参与那个函数里面的重在。

模板的全特化

C加加的函数,模板的全策划就全策划的函数,模板本质上是一个实力,但是呢,他又不参与函数,重在就是说模板的策划跟模板的实例化。实例化是什么意思,实例化的意思就是。你把它真正的变成了一个指定的锁定只有一种情况的类型,就叫实例化,所以函数模板的全特化,它就把它全部指定了,不单单是函数模板,就主要是模板的全特化,它就就把它全部都锁定成一个了,那就是变成实例化了。

模板的全特化就已经是一个实例了,而模板的偏特化还不是实例

所以说全策划模板的全策划,它就是一个实例啊,但是呢,偏策划就不是实例,因为实例它只能锁定一个类型,偏策划只锁定了部分,还有其他东可以选择,还有其他的多种可能性。

所以说刚刚说母模板的全特化,它本质上是一个实力是一个模板的实例化,但是呢,他这个又不参与函数的存在,函数的重说,这句话的背后意思就说函数存在,它本身也是一种实力话,是不是你想想看,就你再写一个函数的时候。你把它的确定类类型给确定下来了,是不就是函数存在,不是实例化,就是你在使用函数的时候,你把那个类型给确定框死住,那个就叫实例化是不是?那它根据你实例化这个去找你函数重在对应的那一个,所以说函数重在就是这意思,然后模板的全特化,它虽然是实例化,但是呢,它却不参与,函数重在。

这里有一个po的概念,剖的就是plan old delta,就是into a double这样类型的数据类型,然后向string啊,这种类型的话,是C++定义的一个类来着,试用类的数据类型,就我们后面定义的一些类的这种数据类型,根这种的pop就不一样,他们不一样体现在哪里,体现在当你要进行复制的时候就。声拷贝跟浅拷贝那里会出问题,就是普通的pod类型,普通类型C语言里面本身这种类型,你用钱拷贝就可以的,你直接用memory copy是可以用的,但是呢,你如果是用那种string这种类的话,你不能用memory copy的话,你是把地址复制过去了,那你指向的还是同个地方,这就是跟我们之前说了声拷贝浅拷贝的问题。

你如果用memory copy你,Copy是这种类型的话,你只把地址复制过去,那你其实只那两个东西都指向的都是还是同一个地址,所以当你要用拷贝的时候,你就不他的在那剖的,跟你自己的那种这种类型的话,他就有不一样,一般你如果要用那种不是po的类型的,就是累那种类型的话,那么你就要你就不能用memory copy了,你就需要嗯,在他的那个方法。里面你要你就在那个类里面,就你用那个类的时候,你要自己写一个自己,你要自己用一个那个S,比如说你用字符你用菌类的嘛,是吧,你自己要写一个SS2中括号I等于S1中括号I这里的话,你要在这个类里面用嘛,你在这个类里面的话,它类里面,它这个菌类里面,它有运算符重载的吧。

就是说他这个类里面的运算符,重在他把那等号运算符重再调了,他把那等号运算符重再掉之后,它这里就是直复制,就不是,就不单单是把地址付给你,就他不是,他就只上就不是同个地址了,他是真正的重新调用了一块地址,然后把你那个值复制到那块地址里面去,他的那个最里面的等号运算符崇拜就崇拜成这种功能了,所以你必须要用到它那最里面的等号的运算符虫在,你才能够解决这个深拷贝的问题,这就是非po Li的那种类型的一个,跟po的类型的区别就体现在生拷贝和浅拷贝上,就体现在迈入科比能不能用上。

嗯,这还有一个补充点,为什么要区分这个po的跟非poe的深拷贝和浅拷贝吗?是不是为什么呢?因为你现在想,其实你其实那个身,你那个等号,运算符重带,它本质上是重新申请了一块地址,把那地址复制到另外地址上,这效率是很低的,你刚刚可能会在想,那为什么po的类型不直接也是这样做呢?这样就统一规划了,是不是这样做效率的啊,你如果是直接用memory copy,你直接是地址复。把地址给给他了,你用的就是他那里面的值,是不是迈瑞copy,就是你直接把那地址给他,你掉了还是那个地方,他不用复制,他效率就高,但是你如果要重新申请一块动态内存,一个内地址,然后你把那值复制到那个地址里面,这效率就低了。所以你要区分好,如果他真的是pod了,你就直接用a copy,效率就高。

然后C加加里面它有一个SD,里面有一个is pod这样一个东西,这种方法你可以用这方法去判断它是不是pod类型的。

他那个s po的这个方法里面有一个成员变量,就是那个value,如果你是剖的类型的,他value就返回处,如果是不是pad的类型的value,就返回false,所以你可以拿这个po Li里面的这个value拿去做条件判断,就是如果他是的话,你就用memory copy,如果他不是的话,如果返回false的话,你就用那个复制,如果他是的话,你就用直接memory copy,你可以把它拿来当条件判断,把那个value,把那个s po里面的冒号,冒号,Value拿来做条件判断,放到if里面去做判断。

这个SD它是C加加11引入的,然后它C加加二零已经弃用了。

这个SD它是C加加11引入的,然后它C加加二零已经弃用了。

这个类型萃取本质上就是判断它是pod还是非pod,就是这个意思,这就是类型萃取。

这个类型萃取本质上就是判断它是pod还是非pod,就是这个意思,这就是类型萃取。

然后我们现在来关注一下这个类型萃取,这个e po Li是怎么实现的,你要实现这个s po的内部的实现的话,有两种方法,一种方法是你用一幅一个一个去判断吧,就是你把所有可能性的一个po都集合在一起,然后到时候拿拿去便利是吧,运行的时候去便利,便利到如果遍历完之后发现没有,那就是不是剖的,如果有那就是剖的。

但是这种方式效率很低,因为它在运行时的,然后还有第二种方法,就是用那个策划的方式去实现,就是拟定一个,先制定一个泛化的,然后再把po的那种类型定义成策划的,这么一来,你只要你用的时候呢,你你判断的时候呢,当你用的是po的类型的,那么他就会用你策划的,如果他发现都没有po的类型的,那么就用你泛华的,这个的话就是在编译时确定的效率就高很多。

总结来讲就是两个方法,一个是运行时候病例,病例之后判断站在里面,如果不在就不是在的话,就是第二种方法,就是用策划把你的一个一个策划出来,编译的时候确定你是pod,还是不是pod。

然后这个这个第二种方法,它虽然写代码量很多,就你要把一个一个特化出来,就是把一个一个的po都要策划出来,而且是一个非常丑陋的动作,你每一个都是一样的,只是把类型改了,策划这么写吗?都是每个都是一样的,只是把类型改,但是呢,他代码量虽然多,然后也重,看起来也很重复,但是它的效率却很高,为什么?因为他这些代码都是在编译时候就就已经没了的,就他是在特他是策划嘛,策划是在编译的时候,他就会找你对应的是哪一个,他就会用那个,其他的就不看了,所以他编译的时候就已经就已经搞好了,所以运行时候的效率就很高。

你第一种方法,你把所有的东西都写到一起,然后去便利,便利完之后一个个去比较,看是不是它。这个是在运行时便利,然后拿来一个个去比较,这个是在运行时所以效率就低了很多,虽然它代码量少,但是就低了很多,但是它运行时效率就低了很多。

我们刚刚讲了这第二种方法,它在定义的时候,他用的是静态类型,静态成员变量就是有static,然后那他没有static可不可以的,也可以的,下面就是要讲没有static的情况,然后这里可以稍微回顾一下,就是静态成员变量,就这是是什么意思,它就是说它是属于类的,不是属于对象的。

就是说你用静态的话,你直接用类去做就行了,不用新产生一个对象,再用它这个成人变量,所以就方便这里就是这个意思,那么你如果不用静态是怎么样呢,你不用静态的话,那也可以。

就是你如果用的是静态的话,那么你就是只需要在那个类里面声明一下静态,然后你在那个在那个,还需要在在类的外面再把那个静态初始化一下嘛,就是这样用,然后你到时候用的时候,你就直接用这个类冒号,冒号用这个,然后用里面的那个value就可以了是不是?然后你如果不是不是用静态,你用普通的,那么你这时候你就不能这么用了,你首先第一步要你去掉去,去掉了代替之后。你内外面那个定义就不用了,删掉了内外面那体那那个static类在内,外面那个就不用了,然后你在那个类里面,你要先写一个构造函数,默认构造函数,括号里面不用传差那种,然后在里面中括号里面写上,就是给它赋一个初值,比如说value等于多少啊,Value等于two或者value等于force是吧,在那构造函数里面给它赋初值,然后。

然后你除叻写的构造函数之外,你肯定要在肯定要在类里面定义那个成员成员变量吗?就是写个不value是吧,然后你就这是其次,然后你就在用的时候呢,你就不能用冒号,冒号了,因为它是不属于类的,它是属于对象的,所以你在用的时候要先创建个对象嘛,所以他朱老师,他是在一个条件判断那里直接写上那个类的名字,就是说my is po的,然后。加个括号加个括号都就就就证明你创建了一个类了,就是是这意思,你加了个括在后,在后面加了括号就证明你创建了个,就说明你创建了个对象了,不是等于就证明这基于这个类创建那个对象了,然后my is p,然后括号,再点,然后value就用,就是用点value就用对象结构体点,然后value,这样去访问那个value了,就不是像之前那样静态那样子用冒号,冒号去访问。

然后你除叻写的构造函数之外,你肯定要在肯定要在类里面定义那个成员成员变量吗?就是写个不value是吧,然后你就这是其次,然后你就在用的时候呢,你就不能用冒号,冒号了,因为它是不属于类的,它是属于对象的,所以你在用的时候要先创建个对象嘛,所以他朱老师,他是在一个条件判断那里直接写上那个类的名字,就是说my is po的,然后。加个括号加个括号都就就就证明你创建了一个类了,就是是这意思,你加了个括在后,在后面加了括号就证明你创建了个,就说明你创建了个对象了,不是等于就证明这基于这个类创建那个对象了,然后my is p,然后括号,再点,然后value就用,就是用点value就用对象结构体点,然后value,这样去访问那个value了,就不是像之前那样静态那样子用冒号,冒号去访问。

总的来说就是这样子,就刚刚说那样子。

然后当你想要把那个value保护起来时候,那么你就把那个value放到那个类里面的那个private里面去,然后你再在那个public里面写一个方法,你可以调用那个方法,然后那个方法里面你写个return value,那么就可以把value返回给那个方法,这样的话你就保护了那个value,你就用了调用了那个value的那个方法去用,这样就更好一点点。

然后你如果是要用那个成员函数,然后里面去用那个value,然后把value放到private,那么这个时候你再用那成员函数的时候,你就是在那个条件判断里就是这么写,卖s po d,括号点,菜谱括号。就假如说你那个成员函数名字叫type的话,就是这么写,就是点扩点菜谱括号,你会发现前面的括号是用来定义对象的创建对象的。后面的括号是创世用对象里面的方法的括号,我想说的是这个意思。

然后朱老师的那个成员函数名字不叫type,叫做get type,所以最后是变成my is pod括号,点get type,括号。

这个在类里面写type define是创建了一个子类,你可以这么理解,类里面创建了一个子类。

然后他本质上也是用了台比赛的这种重命名的规则。

就是他可能外面有一个类是吧,然后你想要把它放到这个指这个,你现在这个类里面去,但是呢,你又不想要用外面那个类的名字,你想要给他重新重命名,一个类的名字,那么你就可以这么用,就在那个类里面写个top Li fan,然后把外面滴泪名字写进去,然后右边再写上,你想要给她这个类的重重新命个名字,那么它就变成了你那么,那么它在那个类里面,你如果用你重新命名那个名字,那么你就可以找到它的命名之前的那个累,就是给简单说,就是在类里面给你的类型重命名,给你的子类重命名,其实很好理解的。

然后因为它本质上也是一个类,所以你在你然后你用的时候,你就会觉得会有一长一长串的一个一个式子,就是你肯定要先上,比如说可能会是这样子,A冒号,冒号B,然后再次括号点什么什么什么,就是它会比比较复杂一点,但是也不会复杂到哪里去。你本质上你就记住它的这个泰迪范里面的东西也是一个类来的,它是那个类里面的一个子类,所以当你要用到它这个东西的时候,你也要是给它创建对象的,所以你用了这个东西的时候,你要是用,你也要是a冒号,冒号B除此之外,你还要再加个括号给它,让它变成一个创,让它在这个地方创建对象,因为加括号就创建对象嘛,然后再点什么什么,就可以用它那个子类里面的子类的那个里面的成员变量是这样。

这个写泛型算法,这些人他肯定要知道你写的容器,他他的一些基本的东西嘛,要不然的话,你不知道怎么去做,所以,比如说他要不知道容器的一些就是他的一些元素的话,那怎么做呢?他就有迭代器吗,你看我们之前前几次看那个泛型算法的时候,他的船X的前两个参数不就是。迭代器来的,它就是通过那个东西,然后得到它的容器的信息,是不是?所以说它就是通过把迭把那个容器降级为迭代器,这种方式,然后去使得泛型,算法可以去了解容器的它里面的东西,所以迭代器就是桥梁。

这个迭代器呢,是可以使得泛型算法可以知道迭代器里面的每一个元素啊,可以知道容器里面的每一个元素,那么它怎么知道容器里面的每个元素类型呢?这个就需要用迭代器萃取器。

迭代器萃取器

这个迭代器萃取器,本质上就是用了type defined,这种方法就是之前讲那种类里面写type def I的这种方法,给他的类型重命名,这种方法去做的,他这里,他会做一个中间累,做做一个中间就是一个中间代码,它这个中间代码里面。会把所有的名字都统一好,然后你在用的时候呢,你就用给它,你就用它那个中间的这个类,然后它就会通过这个type吧,把它这个名字给重命名了,对应成统一的,它这样的话呢,就会实际上名字虽然是相同的,但实际上它对应的它再倒推上去对应的就是不同的。

对应的就是不同的那个类型,就是不同的各种容器里面的类型,因为他是用了汰的饭,他把不同容器里面类型名字全部用一个中间类,把它的定义成一个统一的名字了,然后你再用这个泛型算法的时候,他用的是中间的这个类,统一成同一个名字的这个类,用type的饭。然后那么你用的时候呢,你就可以直接用就行了,它自动的就会把那个,那个对应的东西对应回它各自的这个不同的容器里面的那个类型,因为你是type define,它只是重命名,它会对应回之前的名字,所以它迭迭代器萃取器,它就是这样子做的,能够用type define,核心是用type fan,然后去让泛型算法找到。

让泛型算法去找到它容器里面的各个各个数,各个的元素的慈济的类型,让他真正的找到他的类型,就是这样子,这个就是萃取器的作用,萃取萃取器的作用本质上就是就是这样,就是这个,这个用法就是本质上萃取器,就是在泛型算法里面跟容器的一个桥梁,他就是作为一个迭代器萃取器的这样的。这样的用法去用它,本质上是用来得到它的这个元素里面的数据的类型的,用的是twenty five这样子的一个方法,但是它这里呢,会有点绕,所以具体的代码你就具体看到再去分析,但核心的思想就是用time define重一名去反推回去对应的类型,只不过有点绕而已。

绕的话这里就不讲了,就是你没有必要去记,你到时候代码现看就行了,你只要你顺着这条思路去走,他思路你知道,你知道他为什么这么做,那么你的代码再绕你也看得懂,你也可以推出来。

三种适配器

关于适配器,有三种适配器,分别是函数适配器,容器适配器,还有迭代器适配器。

容器适配器

然后这个我们重点讲,现在讲的是容器适配器,容器适配器呢,又包括三个,一个是占,一个是队列,一个是优先级队列,就是一个是塞,一个是QQ,一个是priority queue。然后这个容器适配器,本质上就是在容器的基础上做了一些封装,就是这样,然后比如说这个栈队列跟这priority队列,它就是可以在vector啊,List啊,这些aray这些基础上这些容器。

在这些容器基础上做一些普通的封装,在这些普通容器的基础上做一些封装,然后就变成了容器适配器。所以说这些你也可以把它叫容器,让他就是你发说说的不标准,他们那些帐啊,队列啊,然后u形队列啊,这本质上你说的不标准,他们也是容器,但实际上他们是容器适配器,他们跟容器还是有点区别的,首先他是在容器的基础上封装出来的。然后他没有迭代器的就是这些容器设备器,它没有直接的迭代器,但是由他封装的外壳,这些肯定是有迭代器的,就但,但是他直接是没有迭代器,这是原来源于什么?来源于他本身就不需要迭代器,对象战啊,队列这种东西,它本质上是种操作的,他重要的是看中的是你的出,出站入账啊,出队入队这些操作,他没有必要去把你的数组的元素,像你像如果是用外壳的数组,他没有必要把数组的元素,一个迭代出迭代器迭代。

这个strike啊,它的push是没有返回值的,就是说他不能便利的,然后你如果一定要便利的话,你要先是push出来,然后弹弹,弹弹出来之后,然后你再用一个top top它就可以返回给他栈顶的值,所以你每谈一个出来读一下,站的直,每弹一个出来,他读一下它栈顶的值,你这种方式才能够获取它里面的元素,它根遍历是不一样的哦,这个不是便利。你要分清楚便利的话,它是便利完之后,它这个元素没有被破坏,但是你这里的话你就要弹出来一个在读站点,弹出来一个在读站点,所以当你全部读完之后,它就全部弹出来了,这个数据就已经没有了它,所以它不是便利,便利之后是不变的,但这个它读完数据就变了,它就弹出来了,所以它不是便利。

从迭代器适配器理解C++为什么效率高

大部分工作在编译期间就做好了

刚刚在说迭代器适配器的时候,忘记说一点,就是你可以从这里体会出来C加加为什么效率高,因为C加加他大部分的工作都是在编译期间就做好了,他到真正运行的时候,它是就已经锁定了只运行那一个了,他把其他东西都删掉,然后运行的时候,就他是编译器编译的时候去判断的,但是其他语言很多时候都在运行时,在判断运行的时候判断效率就慢了,还要在运行的时候一个个去判断,C加加是在编译时候决定了,编译的时候他就把很多种选择。都删掉,只留一种,到运行时候他就很快了,他就不用再做选择。所以ci他ci就是因为在大部分工作在编译期间就把工作给做好了,他运行时就不用再做抉择了,所以效率就高很多,像其他的还是在,都是在运行时去判断,运行时判断效率就慢。

这个站sight里面有一些Swift方法,就是交换两个站,还有一些判断大小的方法,比如说那个运算符同在,就是他可以同在的,同在完之后,你可以用来判断大小,它这里的判断大小是用字典顺序的,按字典顺序就是他第一个判断之后,再看第二个判断,再用第三个判断,它先从第一个开始判断,然后他的站的方法就这么点了,因为他作为一个站来讲,它就是只要提供这些操作就可以,不需要提供其他的。

它不像那个容器类一样,普通的容器,比如vector啊,List啊,Decu这种普通容器类,它提供很多种方法,你要去操作它,但是占你既然定义出来它这个功能了,那你就是只要实现它的这些功能就行了,它只要支持它占作为一个占所需要提供的功能就可以了。

他的这个priority queue这个优先级队列,它跟普通的queue队列有什么区别呢?就是你在压压入队列啊,你不就是你在入队列的同时,你再入队的时候,他你每入队一个元素,它就会帮你先做一个判断,按大小排序排好序,你美呀,进一个,就你每入一个,它就按你给按大小排个序,你每入一个,它就按你按大小排序啊,最后他是他整个队列是有顺序的,有大小顺序的,就是这样子而已。他就是多了一个,每每入一次,他就给你做一次判断,然后排序,把序列排好给你。所以他这个priority q跟普通Q的区别是,他会去读你的入队列的元素,然后去判断你的大小,他会去读取你的元素的,然后拿去判断大小,然后帮你做排列,每每进来个元素,他就帮你做一次这样的操作,帮你排好,这就是priority q优先队列,优先级队列。

这种容器适配器就是种栈啊,队列啊,优先级队列,它在设计的时候,他其实是不太关心内存是怎么分配,怎么管理的,他重点放在了操作上,所以他的提供的方法是比较少了,都是一些出对呀,入队啊,出站入站这样的,这样的一些方法,它提供方法比较少,因为它关注就这些动作,他他就是希望支持这些动作就行了,真正要对内存管理这些东西,对操作是他由他,就就是由他封装而来的那些东西。不就是它封装,就是真正对内存管理这些重点关注啊,是那vector瑞这种类型的普通容器,像它这种封装出来的这种容器适配器,它主要是重操作的,而不是重点在那个什么分配内存上。重点分配内存是在之前,它在它下一层的普通容器上,那你才是重点关注了内存之类的东西。

然后这里他重点是关注怎么操作,提供了几个操作方法就可以了,就那几个操作方法提供出来支持操支持那几个操作就可以。

顺序容器

容器从本质上来讲就分两种容器,一种是顺序容器,一种是关联容器。你像之前说那个什么磁带和Q啊,就是战争队列优先级队列,它本质上也是顺序容器来的,他虽然是容器适配器,但是它本质上也是由顺序容器封装而来的嘛,所以它本质上也属于顺序容器。那么顺序容器是什么意思呢?顺序容器就是说按照它在内存中的位置来,就是他那个元
素在。在容器中的那就是的位置来顺按顺序去保存的就是他按照他的位置,一个一个顺序保存呢,谁在前,谁在后,谁在前谁在后这样子。

关联容器

那么关联容器什么意思?关联容器就是数学上的映射,就是像,就是他拿一个T跟一个value,不同的key对应不同的value,就是一个映射,这叫关联容器,而关联容器里面又有有序关联容器跟无序关联容器。

但是从本质上来讲,有序关联容器跟无序关联容器,本质上也是关联容器。所以总的来说,容器本质上就分成两大类,一个是顺序容器,一个是关联容器。

有序关联容器用二叉树和红黑树,无序关联容器用哈希表

然后有序关联容器,就是说你在插入的时候,它就帮你做了排序,然后你再取出它就很方便,就比较好取出,这就叫有序关联容器,然后有序关联容器呢,一般是通过二叉树和红黑树,甚至红黑树去实现的,然后吴旭关联容器呢,就是你插入的时候他不帮你排序的,没有帮你排序,只是纯粹插入而已,它这个的话就是一般是用那个哈希表。去帮你实现的那哈希表本质上就是一种映射,所以它无序管理。容器就是管理,容器的本质就是T对一个value就是映射,所以哈希表就是映射。所以普通的无序管理容器没有帮你做排序的。这种纯粹的管理容器就是用哈希表映射去实现的。

两大关联容器:set容器、map容器

然后这个关联容器,它主要有两大东西,一个是set容器,一个是map容器。它所有的关联容器都是基于这个set容器的map容器去延展出来的。

set容器

set的管理容器,它本质上是key,跟value是同一个东西,你可以理解为key就是value value就是ki,这就像一个集合,数学上就是一个集合,然后他因为他是一个映射吗,这个集合,然后他是,所以你就算在那里面你放了三个cat,就是你放了三个相同的东西之后,他也是把他当一个的,他不可能有三个相同,然后如果你还有个方法要raise,如果你用erase的话,你去查,比如说一个看里面有一个CAD,一个一个horse。一个dog,然后你erase的东西去查,然后你erase,比如说erase,一个horse,它其实本身就是拿你这个host跟你那个,在你那个容关于游戏里面,那个在里面遍历一遍,如果有相同了,那就对,上去就用了,就是找到了,就是这样子,那比如说你那个erase里面用的是一个,用的是一个pig,那你它就会拿这个pig去那个set。

去那个set里面去找,去便利,如果便利到就就对应就找到了,如果没有对应到就没找到,那pig里面肯定就没有,没有便利的就肯定没找到嘛,那就返回false,就是这样子。

像你刚刚像刚刚说那个,如果你这个像里面有三个cat,一个house,然后一个dog,那其实你要,你要能你再用个方法,用size方法去看它大小,它实际上只是三而不是,而不是五,因为它三个cat是算一个cat的,它只算一个的,所以就是三个size是三,而不是五。

然后,这就拓展出了monkey set mouth set就是解决这个set重复只算一个的问题,就三个cat,只算一个care的问题,然后在mouth set就得到解决。monkey set里面的话就是你三个cat,就算算就是算三个,就是这样。mouth set是属于有序管理容器的一种,就基于set延伸拓展出来的。

刚刚说那erase就是删除里面的那个P也是那个元素的,就比如说你的erase horse,他去找里面的horse删除掉,那你写的如果是erase pig,它就会去找里面的pig,删除掉,你会发现没有pig,那么它就返回false。

这个auto让编译器帮你自动推导,这个是不会降低效率的,因为它这个也是在编译时确定的。

关于这个set的便利有三种方法,就是之前都讲过了,一个是院子方法,就是直接写个冒号,两边写东西的那种啊。第二个是你用for循环,然后用迭代器的for循环去遍历,就是用迭代器加for循环,它你会记,你记得之前说过那个不等于嘛,就是感叹号,等于那里的一个用法的一个微妙之处,这就是用的这种方法。后第三种就是用for语句,For it就是一个泛型算法,用法语去加上你那个lambda表达式,去便利就是for,第三个参数,你写上那么表达式,用那么表达式,然后用法语去便利,就是用这三种方法去遍历set,这个观点容器。

原地构造

顺便讲一下关于原地构造相关的一些东西,就是这个赛特引出了一些原地构造的东西,首先我们知道一些元素的插入,一般是有颜色的吗?银色的插入元素,但是银色的插入,她有个特点,就是你首先要在它的外部,要在他那的外部构建一个新的对象,再把他对象插进去,虽然是有两部的,这个效率就低了很多,然后还有一种就是用的是那个。In place插入这个in place的话,就是就是原地构造,就是它不是在外部构造一个对象再插入,在复制插入这它没有两步,它直接在你的那里,直接在你那个类里面直接原地的构造出来原地插入,所以它效率就高。

然后还有一个叫in place him in place him,它是这个用的好的话,他就会比in place的效率更高一点,用的不好的话,就会比in place的效率更低一点。他是什么一个意思呢?就是说他in place him,他可以,他可以允许你传一个迭代器,给他传个参数迭代器的参数给他,这个迭代器就是指定说,嗯,你大概是要在他这个位置插入,在他我指定这个迭代器的附近插入,是这个意思。但是如果是insert,还有in place的话,它是它是它怎么插入,插入在哪个位置,它是由算法它决定的,我们是不知道的,但是呢,In replace him,它是可以让我们自己决定我们是插在哪里的,所以如果你知道你,你作为一个程序员,如果你知道你的数据是怎么排列的。

然后还有一个叫in place him in place him,它是这个用的好的话,他就会比in place的效率更高一点,用的不好的话,就会比in place的效率更低一点。他是什么一个意思呢?就是说他in place him,他可以,他可以允许你传一个迭代器,给他传个参数迭代器的参数给他,这个迭代器就是指定说,嗯,你大概是要在他这个位置插入,在他我指定这个迭代器的附近插入,是这个意思。但是如果是insert,还有in place的话,它是它是它怎么插入,插入在哪个位置,它是由算法它决定的,我们是不知道的,但是呢,In replace him,它是可以让我们自己决定我们是插在哪里的,所以如果你知道你,你作为一个程序员,如果你知道你的数据是怎么排列的。

就比如说如果你是个程序员,你知道你的数据排列的方式是怎么样的,你知道你的这个数据,这个元素插在你这个数据里面哪个位置效率会更高,然后你想插到那个位置去,那么你就用increasing,那么效率就远远的大很多,就比in place高要大很多,Impress只是原地构造,但是他在茶原地构造的,在在元素差在哪里呢?他也是不知道的,但是increasing的就知道了,但是你如果increasing的用不好,比如说你程序员,你本身不知道他怎么排骨,你还乱搞。Impressing的说不定本身应该插在开头,效率会是最高的,但是你却把它插到结尾了,这样的话效率就会比replace效率还要低,加到impressing的效率就比replace效率还要低,就是这个意思。所以impressing的就是更细化了,他可以给你程序员说你插到哪里去,你如果知道的话,如果他你如果知道插哪里效率高,插哪效率低,你用impressing效率会很高。

上面来讲一下那个赛特的这个关联容器的一些方法,就是有一个是有一个是extract方法,这个extract方法的话呢,就是它是用来,它是用来提取出来它那个元素的,为什么要提取出来元素呢?因为你如果是单纯在它就是用来提取出。把元素提取出来之后,然后在外面改了,再用来插回去,为什么?因为你如果不不提取出来,你直接在里面插不,你直接在里面改元素的话,那么他没有那插入这个这个步骤,他就不会帮你做自动排序。所以说你如果没有提取出来,然后改改完之后再插,那么你就会你就会在你在里面直接改元素的话,他没有插入动作,他不会帮你排序。

所以这个本质上就是为了他能够排序,所以先提取出来,改名出来参具,所以这Excel是用来提取出来的。

然后第二个方法就是那个merge方法,那个墨菊方法就是你把它这个两个赛特容器墨菊墨菊一起啊,就是说比如说有个S1S2,你把S2末期到S1之后,那么他S2里面有的S1里面没有的那些元素就会被移到S1里面去,然后S2里面有的S1里面,也有的元素就不会被移过去了。然后呢,把S2默许到S1之后呢,通过这一个操作之后,那么S2里面它就只剩下S1没有剩下S1只剩下S1有的那个那些元素了,因为它没有被移过去,而S1里面没有的,S2里面有了被移的S1之后,S2里面就没有这些元素了。

还有一个就是find find这样一个方法,Find这个方法就是,就是用来查看你的那里面的第几个元素有没有的。比如说你定义了一个关联容器set,它有四个元素,那么你就可以用fan的一饭,二饭三饭四,你就可以发现他返回的就是有的,就是处,没有的话,比如说你拿个FIND5。犯了七,犯了八,它没有那个元素,它就会返回force,那还有一个是抗的方法,抗的方法,其实也是跟犯的方法是一样的,只不过它返回的是个迭代器,就说比如说你是COUNT1,那么它返回的是一这个元素的迭代器,然后你再返回来,如果你是用的是抗的三,那么你返回的是三这个元素的迭代器。

那么,如果用你那个返回了抗一的这个这个东西,比如说你把它定义成it rate1,那么it it1加加在it rate一再加加,那么它就会得到一个跟你看了三返回那个迭代器,比如说叫it rate3一样的一个值,因为它返回是迭代器,你一加再加,那就变成三了,所以是相等。

还有一个是那个关于那个range那个就是范围的一个方法,有取上限的方法,然后取下限的方法,那个就是棒的,还有个棒的什么,应该是max bond还是就是有上限跟下限,还有一个范围,这三个方法。

要取范围的叫equal range,然后那个取下线的那个叫lower bound取上线的叫做upper bond,然后除此之外呢,还有一个叫key compare方法,这key compare方法呢,就是用来返回一个东西的,返回什么呢?

他就是返回你这个sat这个容器的操作方法的这个类型的,因为你的sat关联容器,它默认的是用less这个方法去帮你排序的,那么你用那个ki compare,你就会,它就返回一个,就会返回一个那个她的less less这个方法,这种排序方法的一个类型,它是用来查这个的,就是你查到之后就是就可以用就可以,因为你不一定是不同你不同的人,不同的方法,可能这个用的是Les方法,另外用的是别的方法,那么你就需要判断一下。

刚刚说错了,刚刚那个count才是那个查那个第几个元素,然后返回true跟false的,然后find就是用来查那个。

然后find find就是用来查那个迭代器的,抗才是查他那个这几个元素是处还是force的,然后还有contain contain的话跟count跟find是差不多的,他是C加加二零引入的,他好像是说不用实例化那个key,它的区别是在这里,他不用实例化那个key,他不用把那K实例化就可以用来查来检查。

map容器

然后就到了map map这个关联容器呢,就是它的key跟value就是分开的了,像之前那个sat,它的key和value是同一个,你可以理解为就是value,但是到了map的话题就是fen qi跟value是分开的,然后它里面多了一个方法叫做insert a sign insert in search of a sign,就是说当你那个key里面不是有个value嘛,然后,如果你的那个你又又重新写一个T进去,然后跟ki gen ki是相同的,但是value不同。它就会把你最新的那个key跟value啊,刷新到里面去,就比如说你第一个,第一次K是1VALUE是五啊,第二次K是一,Value是是八,那么它就会把T那个value是一跟八的刷刷进去,把一跟把key跟value是一跟五的给去掉,给刷新掉,然后这里你还要注意点,就是insert assign这个就这种insert类型的,它是改value的,然后那个。

然后那个extract extract的这种方法,它是改key的,之前在ST里面,因为它key跟value是同一个,所以你不容易分辨,但是到了map里面,你就知道key跟value不是同一个了,那么Excel是改改value的,然后然后extract是改key的。

然后这个map里面还有个in place跟trying place的区别,他们都是原地构造,然后他们区别就在于你如果是构造相同的元素会出现怎么样,他们如果是构造一个相同的元素的话,那么你的就相同的,T的话,你in place里面,如果他是先原地构造出来,然后再去看,发现是一样的,之后他再去销毁之前的,然后变成最新的,但是trying please呢,就是你不用先是构造,你是先是判断判断,他如果是有相同的。他就他就再构造,如果没有的话,就就就就不做任何事,意思就是什么意思就是说,意思就是说他的in place,他是无论如何都会先构造一个,当你发现了再销毁,然后trying place是在之前,你先你先去判断,如果有相同了就不再做处理,所以区别在这里,所以in place效率会低,Trying play效率高。

注意我刚刚说的那个那个in place,它那个,如果你新构造那个是相同的,它是它是先原地构造出来,它发现已经有了,再销毁掉新构造的那个,注意是销毁掉新构造的那个,所以效率是低的。然后try in place,它是直接判断,如果你发现了相同了,它就不构造了,它也不销毁,就什么事都不做,那个in place,它是构造出来就销,毁掉它就销毁掉构造出来的那一个,所以效率低。

啊,这里的话你也会深入理解,为什么之前在讲sight的时候,你insert,嗯要拿出来再改改再放进去,是因为T的话不能随便乱,改改起来很慎重,因为他在里面已经帮你排好序了,然后你如果要改T的话,你如果在里面改,那么他续他就不会再帮你排序了,所以要先拿出来,改完T之后再放进去,它才会在在帮你排序,所以他改题的时候用那个extract是非常慎重的。

这个extract,这个extract,它是更改这个而不用重分配的,唯一的方式就是说,如果你要改这个T的话,你可以用erase,然后把它释放掉,然后呢,再再去映射的,这样的话,它就会先是把你内存也释放掉,再创建个新的内存分配给你再插进去,这效率是极低的。但是extract的话呢,它不用分配内存,它只是你把只是把你的T拿出来改一下再插进去。

然后你注意它这个后面map里面,它不是也有靠found found contain,然后这些equal range lower bond up a bond吗?这些的话,它你注意它针对的是key,而不是value,所以它跟set是一样的,就是说它跟set的机制是一样的,因为它key的话,它是只有一个,只能有一个。

包括后面的后面的key compare,然后一个那个嗯,就key compare这些也是就是一样的,就是跟sad是一样的,它用观看的就是key,而不是value。

然后除了这个key compare,不是还有一个value compare吗?这个key compare跟value compare在那个set里面,它是同一个函数,就是它是它是同个功能的,因为key里,因为set里面key跟value是同一个嘛,然后你如果是在那个map里面的话,可能key compare跟value compare就不一样了。key compare可能在map里面,返回来就是key的那个类型,然后呢,Value compare在在map里面可能返回,就是那个value的类型。

还有一个multi said,一个multi map,这个是用来解决key重复的问题的,就你们班可能有两个章程是这意思,然后你用multi set multi map就可以解决这个问题,它是可以这么它就,它就允许这样子。

然后除此之外呢,它跟普通的site跟和就差不多,方法都是一样的,它就是这一个地方是允许的。

这个靠边停车是在雨刮器凸起的最高的位置,然后你用右眼看,把左眼闭住,然后他的防线的最右边的那个地方是在那个雨刮器最凸起的位置,稍微再往左边一点点的地方。

关于这个哈希表,首先哈希表示一类东西的统称就是,所以它在不同地方的不同实现方式是不同的。比如说在那个s te l里面的话,它的实现哈希表用的是are ordered map,用这个去实现的,然后哈希表它本身就是一种映射,一个key对应设一个value,那这个哈希表呢,他会有一些冲突,就是它会有一个存在一个叫做叫做哈希冲突,也叫哈希碰撞的东西。就是可能你对应的东西就是你对应的东西是,嗯就是会重复到两个东西,就是首先你如果首先你如果是,比如说你有1万个数据,然后你这个数组是不是,那你就要定义,不是假如说你这个数据它的最大值是1万。

但是呢,你只有三个数,比如说一个是一一个是五一个是1万,那么这三个数呢,你要存到哈希表里面去的话,那你就正常来讲,你就要准准备1万个格子,是不是要准备1万个格子,然后一然后五一万是不是,那么这样呢,就很浪费空间,那是有可能空间会大,东西空出很大的空间来,所以要解决这个问题呢,就有一个除留余数法,就是就是。你比如说123456,你如果是以五来取鱼的话,那六跟一就是重在一起的,就是这个意思。所以当你人数是一或六的时候,他都放到那一个同一个格子里面去,然后如果你是123456789,把九把九作为取域的话,那么你十这个格子,十这个人数也是跟一放同一个格子里面去的,这样子的话就会避,就会就就会避免前面那种浪费的情况。

但是在避免了前面这种浪费空间的情况呢,只之外他又带来了一个问题,就是那你如果比如说你如果是1234567,如果一五来取,那么六七是不是跟一二就冲突了,那么如果同时存了一跟六,那么是不是一个格子就放不下了,就会发出现哈希冲突,哈希碰撞,那么要解决这个问题呢,他那个有会有一个一个方法,有两种方法,一个是闭散列法,一个是开店法,B闪电法的话,它里面又有两种方法啊,一种是那个就直接地址法就是。

啊不叫直接地址法,直接地址法是刚刚那种一五一万,然后给给他1万格子,这个叫直接地址法。

然后这个B闪电法它有,它又叫做开放地址法,然后里面又有两种方法,一个是线性探测法,一个是一个是什么,二次什么法,二次探索法。

这个线性探测法就是如果你有,比如说你一跟六是冲突的,那么你比如说你先放了,又放了个六,那么六呢,他就会不放到一这个地址,他是顺着往下再找一个地址,比如放到二的那个地址里面去,如果二里面还有元素了的话,他就再顺着往下放到三个地址里面去,就是这样子。所以他的地址不一定是对应的那个格子,它可能是偏了一两个,就叫做线性探测法,然后呢,二次探索法就是说。它不是,比如说同同样是一跟六,你先放了一,然后又把六放进去,一跟六冲突了,那么六它也要往右移,那它移不它移的话,就不跟那个线性探测法,像是移只移一个一个格子,它是以可能是以二的N次方,这样那个格子去移,就是第一次可能是移两个格子,第二是移四个格子,第三次移八个格子是这样子,如果它第二个格子有了,那就移到第四个格子里面去,如果第四格子有了,移到第八格子里面去,所以它就不是一个一个去移,是这个意思,这就叫二次探索吧。

然后还有一种方法,就是他那个解决这个哈希冲突的第二种方法,除了那个开放地址法,也就是B3立法之外,还有一个开店法,开店法的意思就是这就涉及到差系统了,就是他他不是在这个,比如说你刚还是刚刚那一根六,你你先放一进去了,又放六,然后六是不是跟又冲突了,那么他这个六就不往右移了,他是重新在这个数组下面开辟了一条,一个桶就是一个树,下一个列这种东西就叫做哈系统,然后呢,比如说他一他就是放到一的下面,它重新开放了一个。开放了一个结构出来,就往下去顺延,然后呢,这个这个结构可能是,可能是数组,可能是电表,可能是可能是,嗯焊接表也有可能是图什么的,都可能就是他就开了一个新的结构出来给你,比如说那六是放到一的下面去,而不是放到一的右边去,你就记住这点,他六是放到一的下面去了,而而不是放到一的右边去,这个就是。

这个就是开店法,然后开店法,它产生了下面的这一条,这个整个的这个东西就叫哈系统,比如说你如果六放到下面去了,可能一二三四五六七八九十,可能11乘11,也是要放到一溜的下面去了,那么你就是变成一六十一,有个竖着竖着的一个结构,这个数的结构呢,就叫做哈希桶,所以所谓的那个哈希,哈希桶就是它,就就是,就是一个横着的数组,然后每一个,每一个元素上面可能会挂着一些东西,挂着一条链,挂着条件就要统,就是这,就这一条挂着竖下来的东西就叫哈系统。

简单来说,他就是不往右移了,而是新开一个链条出来,往下往下走,新新的链条,放新的链条里面去,这就叫哈系统。然后这种方法就叫开链法,它也是解决哈希冲突的一个哈希冲突的一个解决方案。

智能指针

智能指针本质上是一种思路,你要实现这种思路,他可以有很多种实现方法。

这个oppo pointer这个智能指针,它有三个方法,一个是reset也是release,一个是那个get get跟release的用法都是他都会就他都不会释放,都不会吸购之前的,然后那个reset的话,他就会把之前的那个吸扣掉,然后用你新的,所以get跟release它会吃内存,因为你前面那个没有吸够的,然后但是release,但是但是reset它会把之前吸扣掉,它就不会吃内存。那么get跟release的区别在哪里呢?Release的话它就是嗯,你不能用了,就是就你之前的那个不能用了,但get的话,你之前那个是可以用的新的都是可以用的嘛,那之前那个release是不能用的,Get它是可以用的,但是它之前的那个,虽然release之之前的虽然不能用,但是它内存也没有被激励掉哦,所以它会使内存。

然后这个open还有个等号的运算符重载,这个等号运算符重,再就是你如果他就是他是一个所有权的转移了,就是如果是你把P1等于八,你把P2等于P1啊,那么你最后你后面的话批就不能用了,你只能用P2了,他只能绑定一个,他之前说那个release的话,Release那个他不是放那个布,布迪丽他妈不是那个release不敌利的,就是它你用了release的意思就是说,你是把它那个,比如说P2跟P1,你把PE release掉了,他把那个P1转移给了P2,把PE付给了P2,那么,P2,他拿你再用P2的时候,P2,你如果不调用析构的话,它就不会吸购,是这个意思。

所以当你用release跟get的时候啊,把那个P1RELEASE跟get交给,然后给P2,那么P2的话,你要手动delete,是这个意思,然后reset的话,你把 resetp2 resetp1,然后搞给了那个P2,那么那个那个reset得来的P2,它就可以自动的机构是这个意思。

这o point等号运算符存在啊,他的这个所有权转移,它本质上也是因为西勾函数,只能调用一次,所以他为了合理,所以呢,那你当你把P1转给了P2的时候,那么你P1就不能再用了,因为如果你PE还能再用的话,那你是不是P1 p2同时机构的话呢?Si go是同一个函数,析构是同一个函数的话,那么你吸够是只有一个是不是?你吸够只有一个的话,那你PE机构批就没了,所以他这样的话,就为了合理起见,所以就PP,一就直接放到P2里面去,那么P又不能用了,这样的话你就只可以虚构一次,这就合理,就不会出现错误。

四种智能指针

这个智能指针有四种,然后最主要是前面三种,然后第四种是依附于第三种的,前面三种分别是auto point unique point or gen share point or,然后auto pointer跟unit pointer又可以分为一类,这一类是有,他是独占式的指针,智能指针就是说他跟那个share point不一样。销painter它是可以不独占的,可以多个指针指向一个东西,然后你auto painter跟那个unit painter只能是绑定一个,然后auto pointer跟unit又有区别,就是它是

就是他open,它允许你从a换到B,但是呢,因为朋友他不允许你这样子,他在这里会帮你拦截住,大概就是这个意思,也就是说总的来说就是这样子,然后这个东西就是他们的机制,他们四个的机制都是没有完全没有漏洞的,只是说我们程序员用起来的时候,可能会我们自己这方面出现错误。所以他为了避免我们这方面出现错误,他会设计的人性化一点是这个意思,并不是说他们的优劣劣,就是说他们设计的不合理,他们在逻辑上面设计的是完全合理的,不会有漏洞的,只是用的时候可能会出错,这个auto printer跟那个union painter就是这样子,然后那个shelter就是比auto painter就就自由了一点点,就更像正常指针。

share_ptr

这个share_ptr就更像正常指针了,就是用起来你可以更换啊,都可以,然后更换完之后也不会说什么,嗯,就是这样子,就是他是不是独占式的,但是open open就是独占式的,Open to的话,它虽然允许你那样弄就更换,但是他跟那个小喷,那个可以随意更换,这种非独占,但是还是有那么一点点区别的,这里其实我也还不是太懂。但你只要知道有区别就行,了然后面那个第四个叫weak pointer weak point是依附于shelter的,就是为了解决shelter自身存在的一个问题,就当存在循环引用的时候,可能是这个意思,就是a引用了,BB引用了a,可能是这个意思啊,然后就是说出在这种循环引用的时候,那么shelter就会出现bug,就会出现漏洞。

那么为了解决这个问题,就引入了v point,当你出现循环引用的时候,那你就把a定义成shul pointer,这样的话,Ab之间互相循环引用的话,就不会出现循环引用的问题,就是会就是解决这个问题,然后处出现了v point,然后跟shelter一起配合使用,A作为shelter b作为v point,然后a引用BB引用,B引用a,这种循环引用情况就不会出问题。

然后忘了说了,他这个share point,它里面有一个机制叫引用计数法,它是通过这个机制去运作的。

weak_ptr配合share_ptr使用

注意这个weak pointer,它没有像自普通的智能指针一样,它有那个指针那个箭头的运算符存在的,所以它不能当正常的指针用的。如果你要当正常指针用,你要用那个,你要在weakpoint.lock,你要加个点lock之后,你才能够用它。那个箭头的运算符重在才能当指针用,就是你要点lock,用这个方法你才能够解锁它,你才能够用这当正常指针用。

这个weak point它为什么可以配合下喷的使用,本质原因就是这个weak point它不使用引用计数,这它是不使用引用计数的,所以才能使用。然后刚刚说了那个lock还有个Excel,就是它这两个lock跟expect配合使用的,就是它这个make point,它可以接收,但不能使用,你接收我估计就是用Excel,它使用的话你是不能使用的,就是你要使用就必须要点lock啊,我刚刚说他接收可能是Excel,可能也不是他接收,是可以自己接收的,但是你可能他Excel是用来判断你是否接收到了,是这个意思,然后你但是你不能使用,你不能够用镜头去使用,你那用镜头使用就必须要加点lock。

初步的了解,这Excel可能就是用来检测它达不达成某一个条件的这样一个做法,就它是用来检测你有没有真正的怎么样,怎么样的这样一个东西。

这个迭代器适配器就是拿个例子来说,就是你可能是正序的一个迭代器,那你想要逆序去搞他,你如果不用迭代器适配器,你可能会很麻烦,那你想要但啊,但是你用了迭代器适配之后,你逆序之后就很简单就,但是呢,这个迭代器适配器都不需要你额外的重新去写一个新的迭代器出来,你只要在原有的迭代器的基础上去试配一下就行了,那就变得就可以变得很简单了,这个就是迭代器适配器。简单就是这样子用,就是它可以让一些,比如说逆序变得简单一点,用迭代器适配器就简单很多,啊不,你如果不用的话就就比较麻烦,所以适配器就这么用的,迭代器的适配器,除了这个这个例子的话,还有其他例子,反正就是用来适配的,就一些迭代器适配器,它在一些迭代器基础上去适配了一下,变成新的迭代器,叫做迭代器适配器,这个迭代器适配器就是方便变得很方便,就是这样子而已。

这个函数封装器,它的写法就是一个方审,方审尖括号,然后里面写上什么东西,然后写上类型,然后呢,后面呢把取个名字,这就是函数封装器,函数封装器本来就是为了方便使用的,就是他不在乎你封装里面的东西是什么类型,可能是函数,可能是函数对象,也可能是一些其他的东西,就是他不关心你。函数封装器里面的东西是什么?你只要封装好,变成一个统一的接口,给外面用一个名字加个括号,括号里面传参,那么就可以用了,这就是函数封装器。函数封装器就是用function尖括号来表达,当你看到function尖括号,那你就知道它,它是函数封装器,它里面的实现有很多种不同的方法,可能不可能很多不一样的,但是它对外统一就是。

但是它对外统一是个名字加括号,你传差,这就是函数封装器,你把它封装起来,简单就是这样子,就是函数封装器。

这个可变参数就是有个点点点嘛,C语言里面也有,就你传参的那个参数里面写个点点点,他意思就是说你那个参数的个数,是不一定的吧,不确定的嘛,这个是可变参数,然后在这里会有一个叫做可变参数模板的东西,就需要注意一下,有个可变参数模板。

他这可面餐,他会用递归挨个挨个把你的这个参数解出来,然后拿去处理,跟C语言里面是一样的,都是用递归,然后然后把它那个里面参数一个个解出来,拿去处理,了解到,这样就可以了。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-17 16:02:42  更:2022-07-17 16:04:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/14 0:12:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码