函数重载
1.对象属性 对象方法 和 类属性 类方法
2.const函数read only,不会修改任何数据
3.class默认private,struct默认public
4.!!!!C++编译器优化!!!《个人理解》当需要调用拷贝构造时,就一直往前传递this地址指针,省掉中间的拷贝构造,直接让对应接口的构造函数在目标对象的地址上进行构造
5.函数重载的原理是根据目标文件进行区分,参数列表不同,目标文件中的函数名也会不同
6.函数重载时的冲突
有默认值的函数会涵盖无默认值但其余参数相同的函数,比如:func(int a, 7) 可以接收单个int参数,也能接收两个参数,此处就与func(int a)冲突了
底层const是一类,顶层const和非const是一类,同类会冲突,所以顶层const和非const不能进行重载
运算符重载与仿函数
1.默认函数的说明与禁用
/*此处仅仅做说明,表明编码者的意图,但并不会禁用默认的构造函数等等
如要禁用,将其放入private即可,运算符等类成员都可*/
2.friend类内声明,声明对象类外即可对类访问
3.委托构造?可以委托其他构造函数的结构给自己构造↓↓↓↓↓↓↓↓↓↓↓↓
4.输出不便时,重载<<运算符,例如:输出数组
5.调用函数接口只需要函数名即可,函数传参时可以传右值,比如类的匿名函数,CMP()拷贝构造一个匿名函数
6.仿函数比起函数,可以应用类的功能,比如重载运算符,构造传参等等
7.↑↑↑↑↑↑↑↑异或运算符的应用,传参0 ^ 判断1,即为1,此时逻辑为判断为真即为真,为假即为假,传参1 ^ 判断1,此时逻辑判断为真即为假,假即为真,与判断相反,即实现传参决定判断顺逆
sort封装
1.基于快排,利用function<bool(int, int)> cmp,可以指向函数与仿函数、lambda表达式
2.↓↓↓↓↓↓↓↓ 1.函数传参默认实参:默认排序方式 2.函数传参决定排序方式
3.继承权限:class 类名 :继承权限 继承父类 {};例:class Cat :public animal {}
; public、protected、private权限依次提高
外部可访问、可被继承、不可访问不可继承 三层权限
父子类权限相交时,有高级权限则取高级权限,例如父类protected、子类public,则权限为protected,以此类推
4.继承时数据成员拷贝,方法可访问
5.智能指针
当指针置空时,会自动释放申请的动态内存
use_count()能统计指向同一地址的有几个指针
继承与虚函数
1.继承时的方法调用:子类调用自身方法之前,必须先调用父类的方法,例如:子类调用拷贝构造,父类必须先调用拷贝构造,运算符同理
2.↓↓↓↓↓↓↓子类构造函数参数列表(必须)调用父类构造函数传参
子类运算符重载也要调用父类运算符重载(当成函数来用,同样是继承的方法)
3.多继承
语法
多继承时继承顺序由继承列表顺序决定
多继承时会产生菱形继承◇
4.继承的运用,作为功能类,利用继承的特性,使继承的子类有了父类的属性
因为继承父类会继承方法的调用,如果父类是不可拷贝的属性,那么子类也将无法拷贝
5.虚函数的使用与原理,
虚函数将只会被作为对象成员调用,而不是被作为类方法调用,因为普通函数是在编译阶段就已经用被编译器确认调用哪一个函数,而虚函数在编译阶段调用的只是一个函数指针,而运行阶段才会决定该指针指向哪里
6.override和final是虚函数重写时的说明:
override可以做说明,也可以提醒有对应的虚函数,如果没有,编译器会报错
final做说明,当前是最后一次重写该虚函数,后面不可再对该虚函数重写,起限制作用
7.用virtual修饰的函数将不会被作为类型的方法调用,比如类A有个指针p指向的是对象B,此时对p进行调用函数,会调用类A的函数,而不是对象B的函数,这显然不是我们想达到的,虚函数就可以在这里防止被同名误调
8.虚表vtable里存放了可能指向的函数指针
编译阶段将重写的函数入口放入vtable里,调用虚函数时,调用的其实是一个函数指针,运行的时候才能确定该指针指向vtable中的哪个函数接口
9.变量的最小单位为1,意义是说变量至少得有一块内存空间,所以分配了最小的一块内存,实际上数据大小为0
10.函数变成虚函数,类方法变成对象方法,对象方法就是说该方法确实存在一个变量里面,就应该有它自己的内存大小,比如虚函数就需要储存它的函数入口地址
11.虚函数总结
有虚函数时会将虚函数表的首地址写入类中,占据8个字节,类似于一个隐形的数据成员
读取一个类型的虚函数表需要将类的类型转换为指针类型,此处感悟,程序中数据的运算形式(即运算符的操作形式)是由该变量的类型决定,int也可以看做一个变量,它是定义int类的那一块空间的地址,将int转换为void *,就可以访问int类定义的地址了,类比自定义的类结构,访问类定义的地址后,前8个字节就是虚函数表的首地址,再对首地址进行遍历函数接口,就可以输出所有的函数了
继承时,虚函数表会像数据成员一样被拷贝到子类中,子类中有同名函数时,便会用子类中的函数接口覆盖虚函数表中的函数接口,也就是重写,然后其他函数的接口也会加入虚函数表中,所有继承类共用同一个虚函数表
封装基准测试工具
1.类成员指针
类成员可以被指针记住,但该指针必须在该类的作用域下,对象访问该指针也就是访问该指针记住的成员了,这样就实现了在类定义外用指针记住类的成员
2.C++强制类型转换的必要性
如果使用(type)小括号进行类型转换,当类型转换出错时,很难定位,但使用static_cast<type>
进行类型转换,类型转换出问题时,直接搜索static_cast即可查找
3.dynamic_cast
父类到子类的类型转换,需要父类有虚函数生成的虚函数表
虚函数表中函数入口数组的头指针的前面还有两个指针,第二个指针存放了类型结构的typeinfo,也叫rtti(run time type information),
typeinfo里面存放了类型的名称,继承的父类,虚表的信息等
从汇编角度讲,dynamic进行类型转换时,是调用了双方类型的typeinfo来对比转换
在使用vrtual虚函数时,-fno-rtti 该命令后缀可以关闭rtti的生成,因为rtti是dynamic_cast使用时必须进行调用,所以不使用dynamic的时候可以关闭rtti,可以节约空间,而使用dynamic_cast也就意味着会耗费更多的时间和空间
如果不使用vrtual虚函数,那么上面汇编码中的vtable虚函数表也不会生成
4.虚析构的必要性
父类指针指向子类对象时,delete父类指针时,只会调用父类自己的析构函数,而不会调用子类对象的析构函数,因为此时只能调用该对象的类方法,然而将析构函数写为虚函数,就可以在delete对象时,先调用对象本身的析构函数,再调用父类的析构函数
5.抽象类与纯虚函数
纯虚函数:待实现的虚函数,具体实现交给子类去各自重写
抽象类:因为纯虚函数没有定义,没有实例,无法运行,所以抽象类不能被构造,只能被继承
抽象子类:因为子类继承了父类的纯虚函数,如果不进行重写的话,意味着该子类也拥有一个没有定义没有实例的函数,该子类也就无法被构造,该子类也就成为了抽象类,所以抽象类的子类通常都要重写父类的纯虚函数,才能实例化
6.智能指针的应用
当使用的指针不需要多指针同时指向一块内存,但同时又需要智能指针的自我销毁功能,就可以使用效率更高的unique_ptr<type> 对象
关键字
1.auto
总结用法:用来替代一些极长的类型,不可用于形参,数据成员,数组
2. const constexpr
顶层const的变量在汇编层就已经全局替换成了对应的常量
const要求在运行时可读
constexpr要求在编译阶段时可读
顶层const和constexpr作全局变量时,在编译阶段直接将对应的变量名进行替换为常量,意思是根本没有进行存储,而是直接存在了代码段里,不在栈段堆段data段,而是在text段
在main函数中时,至少会占一块栈内存,此时constexpr在编译阶段进行替换,const在运行阶段替换
3.nullptr
NULL在C语言里可以表示0和(void *),但在C++里引入了函数重载,test(NULL)究竟调用整型参数还是整型指针参数呢,所以引入了nullptr加以区分
4.左值右值及其引用
左值:内存
右值:临时值
左值引用:对左值引用
右值引用以及移动构造:
1.将右值直接转换为左值,在C++里,通常产生了右值以后,必须将右值拷贝到一个左值才能保存,但这个拷贝如果是面对大型容器多次拷贝,会产生大量的资源损耗,而直接将右值转换为左值,则节省这些损耗,此时,将右值转换为左值的操作成为移动构造
2.在函数重载中作为参数匹配右值
右值引用与对const引用的原理:汇编层来看,这两个操作没有区别,对const引用时,就是将变量先进行隐式转换生成同类型的右值,然后把这个匿名的右值对象存入一块内存,变成存在于内存中的左值对象,另外,const会赋予该左值只读权限
一些左右值函数转换方法
move(),将左值转换为右值返回
forward<int &>() ,显式转换为左值
forward<int &&>() ,显式转换为右值
5.三种拷贝构造的特点
class A{};
A a;
A b(a);
浅拷贝(默认拷贝构造):将a的对象全部拷贝到b,b的指针指向的空间和a指向的空间是同一片空间,此时如果进行析构,会对同一片空间析构两次,产生内存泄漏
深拷贝:构造一个新的对象b,申请同样大的内存,构造一个数据和a一样的对象b,如果程序要求对一大型容器进行拷贝构造,进行函数之间返回匿名对象等操作,会产生大量的资源消耗,大量的没有必要的匿名对象被构造和析构
移动构造:基于浅拷贝的基础上,将待析构的对象的所有对象置空,此时a不再和b指向同一片空间,a被析构也不会产生内存泄露,就相当于a把所有数据以及内存移交给了b,而不是像深拷贝一样构造了一份,也就节约了资源的损耗
三种构造的重载:
浅拷贝是默认构造
深拷贝的参数是左值,接住一个对象然后构造一个一样的
移动构造的参数是右值,接住一个临时值,然后在临时值被析构掉之前将它的数据移交给一个新的对象
强行调用移动构造个人理解为对象重命名,将左值显式转换为右值,然后再移交数据给一个新的左值对象
模板
1.模板的本质
定义模板的代码并不进行编译,所以也不占据内存,而是在调用同名模板时,自动生成一个对象或者方法
2.函数模板
调用函数时写入的参数类型决定该函数的定义,这个参数类型可以用<type>显式强调
当函数参数类型不同时,可先将返回值类型后置(便于decltype读取参数类型,如果返回值位于参数前面,参数就还没生成,decltype就读取不到参数的类型),然后使用decltype读取返回值类型
3.模板类和类模板成员
调用模板类时,根据构造对象时给模板写入的类型来构造一个类型定义以及对象
类模板成员则是,当该模板作为类的成员方法时,调用该类对象的方法时,写入的类型决定该方法的定义,与函数模板类比来讲,就是类的成员函数模板,首先要该类对象才能调用该模板函数,调用的时候写入的类型来决定成员函数的定义
4.引用折叠(奇左偶右)
C++中,单数引用就是左值引用,偶数引用(除了0)都是右值引用
template进行推理类型的时候,如果是引用类型,需要在待推理类型前加上&&,
比如Test(T &&a, T &&b);
此时如果a是右值,T则会推理为非引用类型,比如 int &&a,T是int
a为左值,T推理为引用类型,比如 int &&&a,T是int &,此时参数类型也等于 int &a,
此时就实现了参数类型和推理类型的统一
提问:为什么Test(T &a,T &b)不行
同上推理,如果接收左值,a类型为int &,此时T推理为int,此时类型并不统一,接收右值同理
5.类特化(模板重载)
模板不能推导出指针类型,所以指针类型需要进行特化
template <typename T *>
test(T *a, T *b);
类似于函数重载里的默认实参,但是模板进行匹配时,是优先匹配参数类型定好的模板
模板类用于定义模板,类模板用于对象创建
6.不定参类型
可用于递归参数类型,每次递归传参时,传args,这样第一个变量a,就被使用,递归结束条件为只剩一个参数,此时调用只有一个参数的函数重载
能用于函数,自然也就能用于模板类,模板接收不定参类型,或者类方法接收不定参变量,然后用于递归,仿函数模板也可以接收类型作为形参
递归可以自己写入终止条件,比如在模板类里,进行成员类递归,意思就是不断地调用成员类,终止条件就是自己重载了一个特化模板类,并且里面有一个特化调用成员
有一说一,有点复杂,总是就是玩的很花,会玩怎么都可以,建议自己找代码看
7.模板的用法
模板递归的递归结束点应该是判断条件,模板特化重载时生成不同的数据成员,以此作为判断条件
高级模板
1.类型萃取
给类型加上引用,去掉引用,加上指针,去掉指针
主要是在模板内应用typedef来对类型进行萃取
比如下图,无论写入模板的是什么类型,只需要把对应类型进行typedef成为T & ,就完成了对类型的萃取,萃取的意思也是从写入的参数中,提取需要的部分,再进行加工
2.高级函数指针
如果一个类模板要接收不同的类型来定义不同的模板类,同时还要接收不同的传参,来进行类方法的重载(比如转换构造的重载),
那么首先,因为要接收不同的传参args,所以必须有一个类成员ptr能调用不同的构造函数来对这个传参args进行构造
那么这个类成员ptr,就应该是一个基类base,因为基类才能指向不同的子类的构造函数,以及子类中重写的各种方法
比如,接收的是一个普通函数接口p,那么ptr就重载普通函数的转换构造,并在参数列表里调用普通函数类的构造函数来对p进行构造,然后用p初始化ptr
如果传参是仿函数p,那么ptr就重载仿函数的转换构造,并在参数列表里调用仿函数类的构造函数来对p进行构造,然后用p初始化ptr
对该类模板定义的类构造的对象进行调用时,比如重载运算符()
就对ptr调用重写的run()函数,因为此时的ptr是构造好的子类对象,为了避免传参过程中右值引用的左值化,可以调用forward(<变量原type>变量),保证变量类型不变
线程池
公共资源:任务列表,凡是涉及读写,需要抢锁
分为多个部分
1.线程池实现
构造函数:开辟n个线程,绑定线程函数
线程函数:
功能实现:调用获取任务函数,运行任务
生命周期:由自身的线程id绑定一个bool值的键值对,以此通过键值对对bool值进行操作,操控该线程是否运行
任务队列:
获取任务函数:条件变量等待响应(避免惊群效应),响应后返回并弹出释放任务队列中的任务
关停函数:
终止任务:函数内容将线程的键值对bool值设为false
终止线程池函数:加入n个终止任务到任务列表,被线程获取并运行后,线程全部终止,此时join给线程收尸,并delete释放内存
线程池接口:
加入任务函数:将函数加入任务列表,发出信号,再由线程去抢夺
高级函数指针封装:
模板写入函数名和参数列表,使用bind(函数名,参数列表),将任意函数封装成一个函数指针,统一将此类函数指针加入任务列表
异常捕捉
try{
}catch(错误信息){
}
程序发生错误会抛出错误,如果在try块内,则会对报错进行捕捉,然后跳转到匹配的catch块继续运行程序,没有匹配的catch块就继续往外跳出,跳出到最外层就会被系统捕捉
错误信息可以自定义类,也有定义好的错误类,可查阅资料得知
lambda函数式编程
直接对比代码
面向对象
函数式
[特殊传参(详细查资料)](参数列表)-> 返回值后置(无返回值,或者仅返回语句可不写) { 实现; };
特殊传参内容