C++QT
环境搭建和程序启动
1、QT安装
QT安装 ----> 环境依赖于WIN + QT
5.8以上都支持c++11标准
QT的路径中不能有中文和空格!!!
安装网站 https://note.youdao.com/s/PctJdFru
2、创建工程
集成开发环境(IDE) ------>用于 提供程序开发环境的 应用程序 -----> 编辑器 编译器 调试器 图形化用户界面等
------> 集成了代码编写功能、分析功能、编译功能、调试功能等一体化的开发软件服务套。

C++基础
	C++基本了解
		1概述
			c++是对C的扩展

任何有效的C程序 都是有效的 C++程序!!! C++ 可以使用 已有的C程序库!!!!
c++语言有面向对象编程和泛型编程的思想这个在Java/Kotlin/C#中称为泛型(Generics),在ML/Scala/Haskell中称为Parametric Polymorphism,而在C++中被叫做模板(Template),比如最负盛名的C++中的STL。任何编程方法的发展一定是有其目的,泛型也不例外。
泛型的主要目的是加强类型安全和减少强制转换的次数。
2程序分析
#include
//# 预处理指令 inlcude 头文件 -------> 引入头文件 iostream 输入输出流

using namespace std;//使用标准的命名空间

int main()
{
//cout和printf 一样!!!!
cout << “Hello World! 你好中国!” << endl;
return 0;
}
c:库math.h --转化—> c++库: cmath
注: c++中【无c和.h】代表类
using namespace std ; 什么意思
using -----> using 编译指令
namespace 命名空间 表示 拓展名的范围 -----> 命令空间只能用关键字namespace 来定义 -------> 是属于C++的一种机制
用来把 大量 有逻辑联系的 程序实体组合到一起, 用一个标识符 来给这个群组取个名字
cout 和endl
cout ----> 是C++ 中标准输出流
endl -----> 输出换行 刷新缓冲区
3linux下C++

			C++  是 C的拓展    -----> 在一个大型项目中可能用到多个厂商(命名空间)的产品(函数),产品名(函数名)可能相同,为了防止冲突

using namespace MM; -----> 就是在使用之前 告诉编译器 我使用的 首选MM 命名空间中的产品!!!
4C头文件在C++的使用注意

		5C++的输入输出demo
			1不能获取带空格的字符串

char buf[128] = “”;
//自动遇到空白字符结束
cin >> buf;
cout << “buf=” << buf << endl;
2获取带空格的字符串
char buf[128] = “”;

// 获取带空格的字符串 获取一行 遇到回车结束

cin.getline(buf, sizeof(buf));
cout << “buf=” << buf << endl;

	C++对C的拓展
		1、::作用域运算符

			1通常优先级:局部变量>全局变量
				
			2解决局部变量与全局变量重名
				
		2名字控制

当一个项目很大时,它会不可避免地包含大量名字。c++允许我们对名字的产生和名字的可见性进行控制
1C++命名空间(namespace)

				在c++中,名称(name)可以是符号常量、变量、函数、结构、枚举、类和对象等等。工程越大,名称互相冲突性的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免,在大规模程序的设计中,以及在程序员使用各种各样的C++库时,这些标识符的命名发生冲突,标准C++引入关键字namespace(命名空间/名字空间/名称空间),可以更好地控制标识符的作用域。
			2命名空间使用语法
				1创建一个命名空间
					
				2命名空间嵌套
					
				3命名空间是开放的可以随时把新的成员添加进已存在的命名空间中
					
				4方法的声明和定义分开
					
				5无名命名空间  ----->只能在当前文件有效!!
					
				6命名空间取别名
					
		3、using声明
			1using 声明 可使指定的标识符可用
				
				
				using 遇到函数重载
			2using编译指令

				注意:使用using声明或using编译指令会增加命名冲突的可能性。也就是说,如果有名称空间,并在代码中使用作用域解析运算符,则不会出现二义性。 
 就是使用 ::
			3命名空间的使用
				   需要记住的关键问题!!  -----> 当要引入一个全局的using编译指令的时候,就表示 为该文件打开了这个命名空间,它不会影响任何其他文件!!!

-------> 所以可以在每一个实现文件中去调整对命名空间的控制!!!!
比如,如果发现某一个实现文件中有太多的using指令而产生的命名冲突,就要对该文件做个简单的改变,通过明确的限定或者using声明来消除名字冲突,这样不需要修改其他的实现文件。
4、全局变量检测增强
总结:C语言能够重复定义,但是不能够重复初始化,C++更加严格,不能重复定义全局变量;

		5、类型检测增强

在C语言中,对于函数的返回值类型和参数的类型要求没那么严格,在没有显性写明返回值和参数的类型时,默认是可以“任意”类型。
而对于C++而言,所有的函数都需要显性指明返回值和参数的数据类型。

		6、register关键字增强

早期的C编译器对代码优化不强,当有些局部变量,需要快速读取时,可以通过register关键字让编译器将该局部变量直接放到寄存器中,加快读取速度。
C++编译器的优化能力较强,即使没有写register关键字,也会对频繁使用的变量进行优化;

		7、变量定义增强

c语言强调变量应该定义在作用域开始的位置,c++强调实用性,使用前定义就行;
8、struct 类型增强
在C语言中,struct并是一种自定义数据类型,只是一个关键字,是一组变量的集合; 在C++中struct是一种新类型;

		9、更严格的类型转换

在C++,不同类型的变量一般是不能直接赋值的,需要相应的强转。 c语言代码:

		10、三目运算符功能增强

c语言三目运算表达式返回值为数据值,为右值,不能赋值
int a = 10; int b = 20; printf(“ret:%d\n”, a > b ? a : b);
//思考一个问题,(a > b ? a : b) 三目运算表达式返回的是什么?
//(a > b ? a : b) = 100; //返回的是右值
c++语言三目运算表达式返回值为变量本身(引用),为左值,可以赋值。

		11、新增关键字  bool

标准c++ 中 的 bool类型 有两种 内建的常量 true(1) 和 false(0) 来表示真假
bool true false 这个三个 都是 关键字!!!!!
bool 类型的大小 -----> 占 1个字节!!!
给bool 类型的变量赋值 时 --------> 非0 的数 会自动转化为 true (1) 0 会自动转化为 false

		12、const关键字
			1C中的const  编译            define是在预处理展开

C语言中 const 修饰的变量 表示一个 不能修改的变量 ----- 常量化!!! 只读变量
既然是变量 肯定会占用空间!!! ------> c中const 默认为外部连接

const int a = 10; -----> a = 100;×
int b = 10;
const int *p = &a; -----> *p = 100; × p = &b;√
int const *p = &a; -----> *p = 100; × p = &b;√
int * const p = &a; -----> *p = 100; √ p = &b;×
int const * const p = &a; -----> *p = 100; × p = &b;×
2c++的const
在c++中,const 不必创建内存空间, 而C中 const 总是需要一块内存空间 -----> 默认为内部连接
在c++中 是否为const 常量创建内存空间 依赖于怎么使用它!!!
一般来说,如果一个const 仅用来把一个名字用一个值来替代(就类似于用 #define ) -----> 那么存储空间就不必创建
如果存储空间没有分配内存,在进行数据类型检查之后,为了使代码更加高效,值也许会直接叠加在代码中!!!!!
一旦你对这个const 取地址,那么就会创建内存空间 -----> extern 修饰 也会创建内存空间 ----> volatile 也会创建空间 禁止优化!!!

			3C/C++中const总结
  • c语言全局const会被存储到只读数据段。
    c++中全局const当声明extern或者对变量取地址时,编译器会分配存储地址,变量存储在只读数据段。两个都受到了只读数据段的保护,不可修改。
    1- c语言中局部const存储在堆栈区,只是不能通过变量直接修改const只读变量的值,但是可以跳过编译器的检查,通过指针间接修改const值。

      			2c++中对于局部的const变量要区别对待:
      				1.对于基础数据类型,也就是const int a = 10这种,编译器会进行优化,将值替换到访问的位置。
      					
      				2.对于基础数据类型,如果用一个变量初始化const变量,如果const int a = b,那么也是会给a分配内存。 
      					
      				3.对于自定数据类型,比如类对象,那么也会分配内存。
    

简而言之,const在c++中用的更好。
4尽量以const替换#define (无参的宏)
#define MAX 1024;
// const int max = 1024
1概述宏:我们定义的宏【便于理解】MAX从未被编译器看到过,因为在预处理阶段,所有的MAX已经被替换为了1024
2特点
1.const有类型,可进行编译器类型安全检查。#define无类型,不可进行类型检查.
2.const有作用域,而#define不重视作用域,默认定义处到文件结尾.如果定义在指定作用域下有效的常量,那么#define就不能用。
3.宏常量没有类型,所以调用了int类型重载的函数。const有类型,所以调用希望的short类型函数
4函数中可以有宏
5命名空间不能有宏

			5函数的const
				const修饰函数的返回值
				const修饰函数的参数
				类中使用const
					- const成员函数,在函数访问方面,只能访问其他const函数,不能访问其他非const函数
  • const成员函数,在变量访问方面,可以访问const和非const变量;
  • 非const成员函数,可以访问const和非const的成员函数和变量;

注意:

  • const 对象只能操作const成员函数,不能操作非const成员函数;
  • const对象可以访问const和非const变量
  • const对象不能修改成员变量
    13、引用 ----- reference
    (1)引用的概念 可以多个引用

实质:const指针
变量名实质上是一段连续内存空间的别名,是一个标号(门牌号) 程序中通过变量来申请并命名内存空间 通过变量的名字可以使用存储空间。
注意:

  • 引用是一个变量的“别名”,对应的是同一段内存,也就意味这可以通过引用来修改被引用变量的值;

  • 引用的基础语法: Type & name = var;
    -&在此不是求地址运算,而是起标识作用。
    类型标识符是指目标变量的类型 必须在声明引用变量时进行初始化。

      		(2)引用的使用
      			引用在定义的时候必须初始化
    

    int a = 10;
    //int &b; //错误的 引用定义必须初始化!!!
    int &b = a; // 给变量a 的空间取了一个别名 b
    引用定义成功了就可以直接使用别名
    int a = 10;
    int &b = a; // 给变量a 的空间取了一个别名 b
    b = 1000; // 可以通过引用(别名)直接修改变量
    cout << "a = " << a <<endl;
    cout << "b = " << b <<endl;
    自定义类型 的引用

      			引用不能修改目标(定义引用必须初始化,初始化就定死了!!!)
      				
      				
      			引用与函数
    
      				引用用作函数参数
    

通过引用参数产生的效果同按地址传递是一样的。
引用的语法更清楚简单:

  1. 函数调用时传递的实参不必加“&”符

  2. 在被调函数中不必在参数前加 “*” 符 引用作为其它变量的别名而存在,因此在一些场合可以代替指针。
    而且不会另外开辟空间
    C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。

     				引用作为函数返回值
     					
     		(3)引用的本质
     			引用的本质 就是 const指针!!!
    

从基本类型的引用来看, 引用 -----> 在定义的时候必须初始化!!!! -------> const变量的定义 也是必须初始化!!!! 定义的要求很像!!!
通用引用可以修改引用变量的值!!!! 所以 & 带有指针的属性
Type & ref = val;
// Type * const ref = &val;

  • 引用的本质:

  • const类型的指针;(编译器实现)

  • 本身所占地址空间与指针一样(跟系统和编译器有关);
    (4)引用的意义
    是指针的一种封装和简化,提升了代码的可读性和安全性;
    (5)引用的难点

      			- 引用与函数的联用 -----> 作为函数的参数      作为函数的返回值
    
  • 常引用 -------> 不能通过引用去修改被引用的变量
    初始化
    - 常(const)引用的初始化,可以用字面量进行初始化,也可以使用变量进行初始化;
    - 非常const引用,不能使用字面量进行初始化

  • 常const引用使用字面量进行初始化时,c++中const本身应该放到符号表中,没有分配内存空间,但当看到&操作符时, C++编译器会单独分配一个内存空间用于存放字面量;
    [const引用使用场景] ----> 防止函数内部通过引用修改外部的值
    1常量引用主要用在函数的形参,尤其是类的拷贝/复制构造函数。
    将函数的形参定义为常量引用的好处:
    引用不产生新的变量,减少形参与实参传递时的开销。
    由于引用可能导致实参随形参改变而改变,将其定义为常量引用可以消除这种副作用。
    2如果希望实参随着形参的改变而改变,那么使用一般的引用,如果不希望实参随着形参改变,那么使用常引用。
    (6)指针的引用
    1区别
    2
    (7)拓展
    引用递归

      	14C++关键字
      		
      混合编程 extern "C"
    

c+c++
那么如果我想在c++调用c的函数怎么办?
extern"C"的主要作用就是为了实现c++代码能够调用其他c语言代码。加上extern "C"后,这部分代码编译器按c语言的方式进行编译和链接,而不是按c++的方式。

	面向对象与面向过程
		1面向过程:是一种以过程为中心的编程思想

通过分析出解决问题需要的步骤,然后用函数 将步骤 一步一步实现出来, 使用的时候 按照步骤过程依次调用
2面向对象
对象=属性+方法(操作)
对象 = 算法 + 数据结构
程序 = 对象 + 对象
任何一个动作都必须通过对象取实现支配,不能定义成普通全局函数。
面向对象三大特性
1封装
操作 客观事物—抽象–>类
类(属性【设置访问权限,只能通过公共方法访问】+方法)
2继承
类之间的关系,A继承于B类,A类拥有B类所有的方法和属性
3多态
接口,一种方法的多种形态,运行时根据具体参数判断使用的形态
函数
内联函数 inline function
摘要
inline(内联)函数的定义将在编译时在调用处展开。提高程序效率。
C语言中: 把一些频繁使用的计算写成 宏的形式!!! -----> 宏不是函数
宏 可以避免一些函数的开销!!! 在预处理阶段完成
c++出现之后: 使用预处理宏存在两个问题:
第一个问题: 宏看起来像一个函数 ,但是会隐藏一些难以发现的问题 (C语言也有)
第二个问题: 预处理不允许访问类的成员!!!! -----> 预处理的宏 不能用作 类的成员函数
C++ 为了保持宏的效率、又增加安全性 、又要在类里使用-------> 引入了内联函数 inline
内联函数 -----> 调用是 不会增加开销,又可以像函数一样传递参数 又有返回值 可以做安全检查 ----> 可以用作类的成员函数

		1概念
			在C++ 中,预定义的宏  由   内联函数实现!!!!   内联函数 属于真正的函数!!!!  ---- 具备普通函数的所有行为!!!

格式 -------> 内联函数必须 将函数体 和函数声明 结合在一起!!!!
2特点
1.增加效率
2.加了inline 不一定管用,编译器决定
3不加inline有可能是内联函数,C++的类中 定义的所有函数 都自动转为 内联函数!
如果 先要内联函数 在多个文件中生效 , 内联函数 需要写在头文件
3重定义
在c或者c++中 在同一工程中,重复定义 一个变量、函数,会报错
内联函数不报重复定义
inline 修饰的函数 编译结果是一个 弱函数 .weak 弱符号 .globl 强符号!!!!

		4总结
			内联函数作用

1: 提升效率,相对于普通函数 效率更高
2 :相对于宏定义#define更安全 ---->
2内联函数条件
inline 内联函数 由 编译器决定:
如果想要进行内联编译:
1:不能存在任何形式的循环语句
2:不能存在过多的条件判断语句
3:函数体语句不能太多 3~5行
4:不能取函数地址
5:不能递归
3内联函数和#define的区别:
宏函数: 内联函数
参数是否有类型 没有 有
替换阶段 预处理阶段 编译
作用域限制 无 有
是否能作为类成员 不能 可以

			inline内联函数使用特点

1.声明和定义必须写在一起
2普通函数: 编译器会将函数类型(函数名 函数参数 返回值 类型!!) 放到符号表中
// 内联函数 ----> 不成功就是普通函数 加了inline 只是表示 想优化!!! ----> 成不成功看编译器!!
3内联函数 实际也会占空间 ----> 相对于 普通函数而言 少了(调用函数) 压栈 跳转 出栈的过程----> 需要时间
--------> 内联函数 实际空间 换时间!!
函数的默认参数
1c++在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值,
2当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替。
3定义在声明的时候 如果只有定义 就写在定义的时候

		注意:  函数的默认参数 从左往右,如果第一个参数设置了默认参数,那么这个参数后面的其他参数也都要设置默认参数(除了占位)
			
	函数的占位函数
		c++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数。

注意:占位参数 只用于 函数重载 ++ – 运算符重载!!!

	函数重载---overload
		1概述
			在传统c语言中,函数名必须是唯一的,程序中不允许出现同名的函数。在c++中是允许出现同名的函数,这种现象称为函数重载。

函数重载的目的就是为了方便的使用函数名。
函数重载 是 静态多态(不能变化)
2基本语法
函数重载实现条件:
同一作用域
参数个数不同
参数类型不同
参数顺序不同

		3函数重载的判断标准
			-  函数的参数个数 , 参数类型,参数顺序不同,  满足其中1个就是函数重载!!!
  • 如果只有函数返回值不同!! 不是函数重载!!! 返回值不同,参数也不同,也可以作为函数重载!!!

      	4补充C++进制转化
      		二进制   0b1010101
    

八进制 0157
十进制 175
十六进制: 0x11fa
如果 想 通过 c++ 打印其他进制呢?
八进制数打印 ----- 通过 oct 的方法:
cout << oct << 【想打印的整数】 << endl;
十六进制打印 ----- 通过 hex 的方法
cout << hex << 【想打印的整数】 << endl
二进制打印 -----> 没有直接的方法
-----> 需要借助函数 bitset -----> 这个函数头文件为 #include
cout << bitset<【打印的二进制位数】> (【想打印的整数】)<< endl;
二进制位数 一般 使用 8 16 32 等整数字节的位数

		5函数重载和函数指针
			因为函数指针在定义的时候已经严格规定了函数的参数类型,所以调用的时候会进行严格的参数类型检查,从而进行匹配, 不匹配时编译会报错;
			
类和对象(封装)
	类和对象
		1、类和对象的概念
			1C++  面向对象的思想:  封装 + 继承 +多态

封装性: 将数据和方法 封装在一起, 加以权限区分 -------> 私有的数据 只能通过 公共的方法才能访问!!!
定义一个类: class
本质上 就是一个自定义类型, 类型不能直接用,只能通过这个类型去实例化一个对象!!!
c和c++ struct区别!
c struct student ------> 里面只能有变量 成员变量
c++ student ------> 既有变量 又有函数
c++ class 和 struct
class 带权限!!! 默认权限为 private 私有的 struct 没有权限的概念!!! ----> 默认是 public
2、类的封装
事物的属性和行为–抽象–>类
类–实例化–>具体对象
所以说封装特性包含两个方面,一个是属性和变量合成一个整体,一个是给属性和函数增加访问权限。
封装
1.把变量(属性)和函数(操作)合成一个整体,封装在一个类中
2.对变量和函数进行访问控制
访问权限
1.在类的内部(作用域范围内),没有访问权限之分,所有成员可以相互访问
2.在类的外部(作用域范围外),访问权限才有意义:public,private,protected
3.在类的外部,只有public修饰的成员才能被访问,在没有涉及继承与派生时,private和protected是同等级的,外部不允许访问,子类内部可以访问父类的protected权限成员

			private:

1、仅可在 本类体中 被访问,类中的成员函数+友元函数
2、不能在外部、或被对象直接访问
2、不能被子类内成员函数访问、也不能被子类对象直接访问
protected:
1、在本类中,和private用法一样:可被类内成员函数调用、不能再外部直接调用
2、能被子类内成员函数访问
3、不能在外部 被子类直接访问(形式child.baseProtectedMember)

		3class 和struct
			
		4成员变量设置为private
			
		5练习
			(1)请设计一个Person类,Person类具有name和age属性,提供初始化函数(Init),并提供对name和age的读写函数(set,get),但必须确保age的赋值在有效范围内(0-100),超出有效范围,则拒绝赋值,并提供方法输出姓名和年龄.  10分钟
				
			(2)设计立方体类(Cube),求出立方体的面积( 2*a*b + 2*a*c + 2*b*c )和体积( a * b * c),分别用全局函数和成员函数判断两个立方体是否相等
				
	对象的构造和析构

注:都是公共方法
摘要:构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化和对象清理工作。对象的初始化和清理工作是编译器强制我们要做的事情,即使你不提供初始化操作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事,所以编写类就应该顺便提供初始化函数。
1、构造函数
作用:创建对象时,为成员属性赋值,对象初始化
特点如下:
1函数名与类名相同,无返回值、void,可有参数
2编译器自动调用
3支持函数重载
4没有手动生成构造函数,编译器会自动生成且调用,但无任何实际操作
2、析构函数
作用:释放对象占用的资源,系统自动调用
特点如下:
1函数名与类名相同,前面多了一个 ~,对象销毁时用,无返回值、void,没有参数
2没有手动生成析构函数,编译器会自动生成且调用,但无任何实际操作
3不支持函数重载
4一般使用在有指针且分配堆区时

		3、为什么要有构造函数

1 用于对成员变量进行隐式初始化(生成类即自动)
4构造函数的分类
按参数分:无参构造 有参构造
按类型分: 普通构造 拷贝构造(赋值构造函数)
1无参构造
2有参构造

			3- 拷贝构造函数一般用于使用一个对象去初始化另外一个对象;
  • 默认的拷贝构造函数,只会对成员变量进行简单的赋值;

  • 拷贝构造函数常见的有4种使用场景

  • 括号法进行初始化;

  • 等号法进行初始化;

  • 注: 这两种使用场景都需要与等号赋值区别开来,等号赋值是等号运算符的重载;

      		拷贝总结
    

1:Student& test(Student &stu)//输入的Student被引用成stu:非拷贝
2:由上 Student lc=test(ls) //返回引用传递,拷贝 lc
3:Student test(Student stu)//输入的Student被拷贝成stu
4:由上 Student lc=test(ls) //返回值传递,拷贝 lc
5:Student lc = Student(1,2);//右值有参构造匿名对象,不拷贝
6:Student dg(lc);//拷贝
7:由上 Student& lc=test(ls) //引用接收不拷贝
8:Student s; Student ss; s=ss;//赋值运算符
注意:构造函数无返回类型,固定有无参构造;
(4)匿名对象

			(5)补充

hl为Student的实例化对象!!!
hl对象不存在 ----> Student(hl); ------> 调用无参构造
Student hh = Student(hl); -----> hl 不存在 会报错!!
如果hl存在
Student hh = Student(hl); ------> 等价于 Student hh = hl; -----> 拷贝构造

		5构造函数的调用
			摘要:

1、如果用户不提供构造函数 编译器会自动 提供一个无参的空的构造函数。
2、如果用户提供有参构造函数 编译器会自动 屏蔽默认无参的构造
注意: 写任何一个类 无参构造 有参构造 需要实现!!!

			构造函数调用规则与调用顺序
  • 当我们没有定义构造函数时候,编译器会提供默认无参构造函数和默认拷贝构造函数;

  • 当我们定义了拷贝构造函数时,C++ 编译器不会再提供无参构造函数(写了拷贝构造函数就一定得写一个普通构造函数)

  • 如果只写了一个有参或者无参构造函数,c++编译器还会提供默认拷贝构造函数么?(还有会默认的析构函数)
    浅拷贝
    简单数据拷贝
    指针地址也是相同
    先构造对象
    拷贝数据

      	6、析构的调用
    

析构的顺序和构造的顺序相反, 先构造的 后析构!!! ---------> 栈的思维!!!!

		7、拷贝构造的调用

			摘要:

对象以值传递的方式传给函数参数
用一个对象初始化另一个对象
1对象作为函数的形参!! 函数调用时 会发生拷贝构造

			2对象作为函数的返回值
				普通对象作为函数的返回值   (visual studio 会发生拷贝构造  QT linux 不会触发拷贝构造 赋值)

老版本c++,在返回时会调用拷贝构造函数生成一个匿名对象,但经过试验发现c++ 11不会调用拷贝构造函数

  • case1: 用于去初始化另外一个对象,这个时候匿名对象不会去析构;

  • case2: 用于去给另外一个对象赋值,其实这个时候调用(=)运算符的重载,赋值完成后会析构掉匿名对象
    3深拷贝和浅拷贝
    浅拷贝 ------ 一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。
    1浅拷贝 ------ 一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。
    深拷贝 -----> 往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝
    一旦涉及到 类成员中有指针的话 ,必须自己实现析构函数!!!

      	8、总结
    

用户只有实现有参构造或拷贝构造 自动屏蔽 默认无参构造。
如果用户只有参或无参构造 不会屏蔽默认拷贝构造。
只有用户实现拷贝构造函数 才会屏蔽默认拷贝构造。
类有指针成员且指向堆区 必须实现拷贝构造 完成深拷贝。
深拷贝:让对象的指针成员 指向独立的堆区空间,只拷贝空间的内容
多个对象的构造和析构
1初始化列表
构造函数 —> 完成初始化, ----》 没有返回值

		2对象初始化列表的原因

类只能通过 初始化列表 调用 对象成员的 有参构造!!

  • 当类中的成员变量是另外一个类时,这个成员变量要进行初始化,要调用构造函数,如果这个变量没有默认构造函数,这个时候必须进行显性的初始化,此时就需要使用到初始化列表,否则无法完成该过程;

  • 当类中有引用时,必须通过成员列表进行初始化(声明后马上初始化),而不能依赖构造函数中的赋值,赋值是赋值,而不是初始化;

      	3总结
    

先构造 成员对象 A 类 ob,再构造自己 lc!!!
要使用A的无参构造 可以用 ----> 显性初始化!! ----> 先无参构造 再显性赋值!!
要用A的有参构造 ----> 只能使用初始化列表!!

	explicit 关键字--禁止对象隐式创建转化
		C++提过了一个关键字   explicit   ----> 禁止通过构造函数进行隐式转换!!!

声明了 explicit的构造函数 不能再隐式转换中起作用!!!
一般是针对单个参数(除了第一个参数没有默认值外,其他参数都有默认值)的构造函数

	动态对象创建 new 和 delete 
		摘要
			静态可能重分配

动态的意思意味着不确定性。
为了解决这个普遍的编程问题,在运行中可以创建和销毁对象是最基本的要求。当然c早就提供了动态内存分配(dynamic memory allocation),函数malloc和free可以在运行时从堆中分配存储单元。
然而这些函数在c++中不能很好的运行,因为它不能帮我们完成对象的初始化工作。
new ----> 申请堆区空间
delete ----->销毁堆区空间
// 都是得到的是首地址
1对象的创建
创建空间—>自动构造函数初始化内存空间(不初始化易错)
2内存的动态分配方法 ---- malloc

		3new和delete

			目的

C++ 为了解决上面的问题(手动malloc分配和释放),把上面的操作结合在一个称为 new 的运算符里面!!!!
当我们new 一个对象时,它就会堆区为对象分配内存空间,并且调用构造函数
1使用方法
开辟: 堆区!!!!
格式: 类型 指针变量 = new 类型 (常量)-------> 这个常量 为初始化的值!! 可要可不要!!!
释放: delete 指针变量

			2注意
  • delete 用来释放new初始化的指针,如果一个指针不是new初始化的,不要用delete去释放;

  • new 和delete会调用构造和析构函数,如果类有多个构造和析构函数,遵循函数重载的规则,调用也遵循函数重载的规则;

  • new是在堆上分配内存;

  • delete 后最好将指针赋值为NULL,因为delete并不是将指针删掉,而是释放指针所指向的那块内存,delete后其实该指针还是指向了该内存空间,如果没有清零,再次使用会有异常;

      	4malloc和new的区别
      		1概述
    

malloc: 不会自动调用构造函数 ----> 需要 手动的 显式初始化!!!
new: 会自动调用构造函数
free: 不会调用析构函数!
delete: 会自动调用析构函数!
new 和 delete: 是 运算符 不是函数 ! 效率更高!!!
malloc free 是 函数!!!!
2建立与释放
malloc 建立, delete 释放 ------> malloc 没有调用构造函数!!!
new 建立 , free释放 -----> free释放不会调用析构函数!!!
基本数据类型 混用的话 没有影响!
类中 有指针,指针指向的是堆区!! 就有影响!!!

		5对象数组
			
			
			
		6delete void*指针 可能会出错!!!
			如果对一个void*指针执行delete操作,这将可能成为一个程序错误,除非指针指向的内容是非常简单的,因为它将不执行析构函数.以下代码未调用析构函数,导致可用内存减少。

void * 指针 释放不了,因为 void 指针不能调用析构 所以不能释放,内存泄漏

	静态成员
		1、静态成员
			在类定义中,它的成员(包括成员变量和成员函数),这些成员可以用关键字static声明为静态的,称为静态成员。

不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的对象共享。
// 简单理解就是静态成员 存储在 全局区 ,所有对象 都共享一个静态成员!!!
类中定义,类外初始化!
静态成员不能作为类的大小!! 所以按道理来说应该是 0 -----> 但是编译器看到 没有东西 送你1个!!!
一旦有了普通变量 就不给这1个了!!

			静态成员变量 根本没有在 对象中!!!没有占用对象的空间!!!

静态成员变量 在 全局区!!!

		2静态成员定义及初始化
			
		3私有的静态成员
			
		4存储总结
			1- 静态成员变量,存放在全局数据区间,所有对象共享;

2- 静态成员函数,没有this指针,属于所有对象共享,可通过类名或者对象名进行调用;
普通成员函数 只能通过对象去调用!! 静态成员函数 类似于全局函数, 可以通过 类名::静态成员函数 去直接调用!!!!
3- 静态成员数据 在定义对象之前就存在,所以必须在类外初始化!!!
5const静态成员属性
如果一个类的成员,既要实现共享,又要实现不可改变,那就用static const 修饰。定义静态const数据成员时,最好在类内部初始化。
6单例模式–重难点
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。
通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;
为了防止在外部对其实例化,将其默认构造函数和拷贝构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
实现单例模式的思路:
1.​把无参构造函数和拷贝构造函数私有化
2.​定义一个类内的静态成员指针
3.​在类外初始化时,new一个对象
4.​把指针的权限设置为私有,然后提供一个静态成员函数让外面获取这个指针
用单例模式,模拟公司员工使用打印机场景,打印机可以打印员工要输出的内容,并且可以累积打印机使用次数。
白话:
单例模式: 所属的类 只能实例化一个对象
demo

	this指针
		1工作原理
			C++ 中内存 存储:

空类 大小 1.
类的成员函数 ----> 不占类的空间!!! -----> .test 代码段!!!
静态成员属性 -----> 不占类的空间!!! ----> 全局区
普通成员属性 -----> 占用了个类的空间!! -----> 栈或堆
C++ 类的成员 分开储存!!!
通过上面我们知道,c++的数据和操作也是分开存储,并且每一个非内联成员函数(non-inline member function)只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?

			c++通过提供特殊的对象指针,this指针,解决上述问题。This指针 指向  被调用的成员函数  所属的对象。

c++规定,this指针是隐含在对象成员函数内的一种指针。
当一个对象被创建后,它的每一个成员函数都含有一个系统自动生成的隐含指针this,用以保存这个对象的地址,也就是说虽然我们没有写上this指针,编译器在编译的时候也是会加上的。
因此this也称为“指向本对象的指针”,this指针并不是对象的一部分,不会影响sizeof(对象)的结果。
this指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数连接在一起,在外部看来,每一个对象都拥有自己的函数成员。一般情况下,并不写this,而是让系统进行默认设置。
注意:静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量。
静态成员函数 属于类!!! 所有对象共享的!!!所以没必要区分 所以不需要this指针!!
2使用场景
1解决形参和成员属性名 同名的问题!!

			2在类的非静态成员函数中返回对象本身,可使用return *this. 完成链式操作
				
		3const修饰的成员函数
			用const修饰成员函数时   ------>  const  修饰的是 this 指针指向的内存区域!!!!

表示 在本成员函数体中: 不可以修改 对象的任何普通成员变量!!!
成员变量类型前 用mutable 修饰除外!!!!
void SetAge(int var1, int var2) const;
void const SetAge(int var1, int var2);
const void SetAge(int var1, int var2);
// 以上3种写法没区别,都是修饰this指针内容;
设计数组类
设计思想: 按照标准数组思想去设计 -----> 定好类型 关注数组的元素个数!!! 而不去关注数组有多少个字节
C++ 设计数组 ----> 需要我们关注多少个字节!!! C++ 数组 有容量的概念, 而真正存储 或使用的 才叫大小!!!
c++ 数组设计是动态的!!! 如果容量满了!!! ---->内部算法会以二倍的形式去追加现有内存!!!
标准算法 往数组装的时候 只提供 尾插 和尾删。

		目的:设计一个类,该类有数组的功能,可以存储数据,可以删除修改数据.

设计核心数据:

  • 属性:指针(指向堆区空间),数组实际存储的元素个数,数组容量

  • 方法:构造(开辟堆区空间),尾插,头插,指定位置插入,尾删,头删,获取指定位置的值,指定位置修改值,获取数组元素个数,获取数组容量,析构函数

      友元声明
      	概述
      		C++ 的特性: 封装 继承  多态  ----->  友元主要就是为了运算符重载做准备  打破C++的封装性!
    

类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部(作用域之外)访问。但是,有时候需要在类的外部访问类的私有成员,怎么办?
解决方法是使用友元函数,友元函数是一种特权函数,c++允许这个特权函数访问私有成员。这一点从现实生活中也可以很好的理解:
比如你的家,有客厅,有你的卧室,那么你的客厅是Public的,所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的闺蜜好基友进去。
程序员可以把一个全局函数、某个类中的成员函数、甚至整个类声明为友元。
1友元语法
- friend关键字只出现在声明处

  • 其他类、类成员函数、全局函数都可声明为友元

  • 友元函数不是类的成员,不带this指针

  • 友元函数可访问对象任意成员属性,包括私有属性

  • 友元函数可以访问一个类私有成员(虽然它不属于这个类);

  • 主要形式: friend 返回值 函数名(类的引用或指针, 形参数);
    1普通全局函数作为类的友元

      	2类的成员函数作为类的友元
      		//成员函数作为友元,   那么成员函数所在的类 必须定义在最上方
    

//成员函数所在的类 的所有成员函数!! 必须在两个的类下方去单独定义!!! -----> 成员函数在类中只声明 类外定义!!

		3整个类作为类的友元
			
		5、总结
			- 友元类声明的时候有friend class关键字
  • 在友元类中,一定有友元的对象

  • B是A的友元类,那么B中的所有函数都是A的友元函数,

  • 友元关系没有传递性

  • 友元类通常设计为一种对数据操作或者类之间传递消息的辅助类;

  • 友元关系不能被继承。

  • 友元关系是单向的,类A是类B的朋友,但类B不一定是类A的朋友。

  • 友元关系不具有传递性。类B是类A的朋友,类C是类B的朋友,但类C不一定是类A的朋友。
    6、案例—demo
    遥控器

      		请编写电视机类,电视机有开机和关机状态,有音量,有频道,提供音量操作的方法,频道操作的方法。由于电视机只能逐一调整频道,不能指定频道,增加遥控类,遥控类除了拥有电视机已有的功能,再增加根据输入调台功能。
    

提示:遥控器类可作为电视机类的友元类。

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

运算符重载与函数重载比较类似,相当于让一个运算符具有另外一种含义;
运算符重载(operator overloading)只是一种”语法上的方便”,也就是它只是另一种函数调用的方式。
在c++中,可以定义一个处理类的新运算符。这种定义很像一个普通的函数定义,只是函数的名字由关键字operator及其紧跟的运算符组成。
2、重载运算符的基本语法–operator
运算符重载的本质是一个函数,是从函数一步步演绎而来;
c++编译器如何支持操作符重载机制: 本质是函数重载,根据运算符前后的数据类型调用对应的函数;

		3运算符重载的两种方法
			步骤:

1、分析运算符的运算对象的个数
2、分析运算符左边的运算对象是 自定对象 还是其他
左边:是其他 只能全局函数实现 (必须使用友元)
左边:自定义对象
可以用全局函数(参数个数 和 运算符对象的个数一致)
成员函数(参数可以少一个) (推荐)
4+运算符重载

		5-运算符和--运算符的重载
			注意事项:
  • 返回值用引用还是非引用?
    返回值使用引用还是非引用取决于返回的内容,如果返回的内容是重载函数中定义的临时变量,那么返回引用肯定是不行的。如果返回的是对象本身(this指针),那么返回引用还是非引用都可以。区别是,返回非引用会生成一个临时的匿名对象,效率略低;

  • 后置–的伪参数能否用其他类型?
    不行,只能用int

  • 二元操作符的参数传递理解
    两个参数,一个参数随对象通过this指针进行传递;

      	6友元函数进行运算符重载
      		注意事项:
    
  • friend关键字
    在类里面函数的声明处要加friend关键字,在函数的实现处不加friend关键字;

  • 返回值
    前置–的返回值,可以是void,也可以是具体的类型;如果是具体的类型,传参数一定是引用,返回的也是这个传参;

  • 为什么需要友元函数重载
    友元函数能够拿到一个类的私有属性;如果没有友元函数,直接用全局函数重载,是拿不到私有属性的;

  • 友元函数重载与成员函数重载的区别
    友元函数没有this指针,成员函数能够拿到this指针;

      	7cout << 输出运算符重载
      		cout  <<  ob << endl;
      			
      	8cin >> 运算符重载
      		cin >> ob;  //   cin   istream
      			
      			
      	9==运算符重载
      		
      	10重载数组[]
      		
      	11重载函数调用符()
      		这个运算符重载 只能通过成员函数去重载!!!  不能通过友元!!!!
      			
      	12&&||这两个运算符不能重载
      		注意事项:
    
  • 逻辑运算符: && 和 || 本身带有阻断特性(短路)(运行完一个如果不满足条件就不会去运行另外的一个表达式了)

  • 而操作符重载本质还是一个函数重载,函数的参数都会进行求值运算,改变了原来逻辑运算符的特性;
    13重载运算符的一般规定

      	14优先级
      		
      		
      设计String类
      	mystring.h
      		
      	mystring.cpp
      	main.cpp
      智能指针-----> 指针运算符重载
      	(*、->)重载   ----> 帮我们delete
    

    继承
    继承–>派生
    1、概念 A(基类)—>B(派生类)
    B继承于A,A派生了B
    核心目的:高效开发,拿现成的类,增加部分自己的东西,代码复用
    2、继承的案例

      	3、demo
      	4、继承的特点
      		C++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。
    

一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类), 类B成为派生类(子类)。
派生类中的成员,包含两大部分:

  • 一类是从基类继承过来的,一类是自己增加的成员。

  • 从基类继承过过来的表现其共性,而新增的成员体现了其个性。

      		主要学习 继承 过来 新增的成员和方法 可能和以前的重名  -----> 需要区别
    

继承过来之后 构造的顺序 析构的顺序 父类 也要构造 子类也要构造 谁先构造?
1.构造、析构方法不继承
2.先构造基类(父)A,再构造派生类(子)B
3.父类的私有属性,如果有公共方法访问,派生后的子类也可以通过该方法访问
5派生类的定义方法
1概述
class 派生类名 : 继承方式 基类名{
//派生类新增的数据成员和成员函数
}
class 子类 : 继承方式 父类{
子类新增自己的成员和方法; -----> 父类的成员和方法 已经继承过来了!
}
继承方式
权限上分:

  • public : 公有继承
  • private : 私有继承
  • protected : 保护继承
    继承源上分:
  • 单继承:指每个派生类只直接继承了一个基类的特征
  • 多继承:指多个基类派生出一个派生类的继承关系,多继承的派生类直接继承了不止一个基类的特征
    派生类继承基类,派生类拥有基类中全部成员变量和成员方法(除了构造和析构之外的成员方法),但是在派生类中,继承的成员并不一定能直接访问,不同的继承方式会导致不同的访问权限。
    派生类的访问权限规则如下:

public 继承: 父类的公共 和保护 全都不变 私有数据 可以通过 公共方法getC() 方法访问
protected继承: 父类的公共 和保护 全都变成保护
private继承: 父类的公共 和保护 全都变成私有
总结: 父类的私有数据在子类中 不可直接访问,公共继承不变 保护继承全变保护 私有继承变私有

		6总结
			基类  ---->  有public 成员  private成员   protected成员

公共继承: 子类 -----> 不能使用父类的 private成员 -----> 但是在类内 正常使用 public和protected成员!! 类外 可以直接使用public成员!!
------> 基类的public成员 还是 子类的public成员 基类的protected成员还是子类的protected成员!
子类再派生子类(public派生) ----> 孙子类 -----> 但是在类内 正常使用 public和protected成员!! 类外 可以直接使用public成员!!
私有继承: 子类 -----> 不能使用父类的 private成员 -----> 但是在类内 能正常使用 public和protected成员!! 类外 不可以直接使用public成员!! —> 可以理解为变成私有的了!
------> 基类的public成员 变成 子类的private成员 基类的protected成员还是子类的private成员! —> 只支持类内访问!!
子类再派生子类(private派生) ----> 孙子类 -----> 但是在类内 不能正常使用 public和protected成员!! 类外 不可以直接使用public成员!!
因为在子类中全变成私有的了!!!
保护继承: 子类 -----> 不能使用父类的 private成员 -----> 但是在类内 能正常使用 public和protected成员!! 类外 不可以直接使用public成员!! —> 可以理解为变成受保护的了!
------> 基类的public成员 变成 子类的protected成员 基类的protected成员还是子类的protected成员! —> 只支持类内访问!!
子类再派生子类(private派生) ----> 孙子类 -----> 但是在类内 不能正常使用 public和protected成员!! 类外 不可以直接使用public成员!!
因为在子类中全变成受保护的了!!! —> 只支持类内部访
7继承中的构造和析构
在上面例子中会发现!
构造子类—> 会先默认构造他的父类!!!! ----------> 析构和构造相反!!

			- 子类对象在创建时会首先调用父类的构造函数
  • 父类构造函数执行完毕后,才会调用子类的构造函数

  • 当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数

  • 析构函数调用顺序和构造函数相反

      		demo
      			
      	8继承中的构造和析构顺序补充
      		
      		
      	9继承中如果设计同名
      		- 当子类成员和父类成员同名时,子类依然从父类继承同名成员
    
  • 如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则)

  • 在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)
    demo1

      		demo2
      			
      	10注意
      		注意: 如果重新定义了基类中的重载函数,将会发生什么?   ---->  就算参数不一样 只要名字相同  父类都会被隐藏!!! 只能通过 :: 访问!
    
      			
      		重定义:子类重定义父类的同名函数。
    

重定义:有继承 子类重定义父类的同名函数(非虚函数)。(只要函数名相同 其他可以不同)
一旦子类重定义父类的同名函数,那么子类就会屏蔽父类的所有同名函数。
继承中的静态成员特性
静态成员函数和非静态成员函数的共同点:

  • 他们都可以被继承到派生类中。
  • 如果重新定义一个静态成员函数,所有在基类中的其他重载函数会被隐藏。
  • 如果我们改变基类中一个函数的特征,所有使用该函数名的基类版本都会被隐藏。
    静态成员函数不能是虚函数(virtual function)。
    多继承
    1、多继承格式
    class 子类: 继承方式 父类1, 继承方式 父类2{

};
我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。

			demo
				
			注意:多继承会带来一些二义性的问题, 如果两个基类中有同名的函数或者变量,那么通过派生类对象去访问这个函数或变量时就不能明确到底调用从基类1继承的版本还是从基类2继承的版本?

解决方法就是显示指定调用那个基类的版本。
2、菱形继承和虚继承
两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石型继承。

			这种继承所带来的问题:
  • 羊继承了动物的数据和函数,驼同样继承了动物的数据和函数,当草泥马调用函数或者数据时,就会产生二义性。

  • 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

    多态
    1多态
    1概念
    通俗来说,就是多种形态
    简单来说多种形态。 -----> 不同对象 做同一件事情 ---->体现出不同状态!!!
    静态多态 ---- 重载!!
    动态动态 -----> 虚函数实现!!! ----> 重写!!! virtual 函数名 返回值 参数 必须一样!!!
    虚函数 ----> 每个子类对象 都类型不一样!!!
    共同点是 同一父类!!
    正常: 指针类型必须匹配!!! 每个对象都有独立类型 都需要独立的指针才能指向!!
    想法: 只定义一个父类指针 根据赋值的对象自动去找对应的处理函数!!!
    2多态之虚函数
    (1)多态的分类
    静态多态: 比如 函数重载 运算符重载 重定义 ------> 在编译阶段 就确定了 函数的入口地址(早绑定,静态联编)
    动态多态: 运行阶段 确定函数入口地址(晚绑定 动态联编) ----> 比如虚函数
    C++动态多态性是通过虚函数来实现的,虚函数允许子类(派生类)重新定义父类(基类)成员函数,而子类(派生类)重新定义父类(基类)虚函数的做法称为覆盖(override),或者称为重写。
    (2)虚函数的知识点引入
    父类指针需要使用子类重写的函数来实现动态多态
    Son son;
    Father *p=&son;//如果没有virtual,只能指向派生类中基类空间,virtual声明后,函数是地址指向子类
    p->show() {根据情况使用不同子类show}

      		(3)父类指针指向子类对象空间  ----> 解决!
      			虚函数(可在子类中被重写):   ----->virtual  关键字   修饰的成员函数  叫做虚函数!!!!
    

父类可以通过虚函数 间接访问子类的 子类重写父类的函数(show)
重写: 有继承 子类重写(返回值类型 函数名 形参 必须完全一致!!!!) ----> 父类的虚函数!!!!

			(4)内存分析
				Animal类:

虚函数 所在的类 如果没有涉及继承 那么该虚函数指针 指向父类的同名函数。
Animal 没有成员 ----> 默认 1个字节 —> 因为 虚函数 设计之后 就变成 4字节了
其实 virtual void speak () 虚函数 -----> 其实就是 一个指针成员是一个函数指针 所以 占4个字节
所以 虚函数 的本质 就是指针变量

				如果没有涉及继承 虚函数表中的指针 指向 自己的speak  偏移量 0 

Cat类:
会发现 cat 类的虚函数指针 是继承 过来的 指向的虚函数表 但是重写操作 就将虚函数表中的 Animal::speak 更新为 Cat::speak

		3多态的定义和实现
			(1)重写/覆盖 的要求

重写/覆盖: 子类中有一个跟父类完全相同(参数+函数名)的虚函数,子类成员函数重写了基类的虚函数
即:子类父类都有这个虚函数 + 子类的函数与父类虚函数的 函数名/参数/返回值 都相同 -> 重写/覆盖(注意:参数只看类型是否相同,不看缺省值)
2多态的要求

  • 被调用的函数必须是虚函数,子类对父类的虚函数进行重写 (重写:三同(函数名/参数/返回值)+虚函数)

  • 父类指针或者引用去调用虚函数。
    3多态的切片示意图
    (1)示例1:父类Person 一个子类 student 创建子类对象(临时对象也行),然后把这个对象赋给一个父类指针,

      			(2)示例2:假设B是子类,A是父类,new一个B类的临时对象,然后把这个临时对象赋给一个父类指针A* p2,通过这个父类指针p2就可以访问子类B的虚拟函数func
      				
      			(3)demo实例1
      				
      			(4)demo实例2
      				
      	4协变----> 虚函数重写的例外
      		子类重写父类的虚函数时,与父类的虚函数  参数  函数名  返回值   必须一样  ----> 重写!!
    

-----> 与父类虚函数的返回值类型不同 ----> 协变 ----> 返回值 返回的是 类的指针或引用
虚函数重写对返回值要求有一个例外:协变,协变是子类虚函数与父类虚函数返回值类型不同,但子类和父类的返回值类型也必须是父子关系指针和引用。
子类虚函数没有写virtual,依旧是虚函数
因为子类先继承了父类函数接口声明(接口部分是virtual A* f() ),
重写是重写父类虚函数的实现部分( 重写函数实现部分是用子类虚函数的{ }里面的函数实现替代父类虚函数的{ }里面的函数实现 )
demo

		5c++ 11 ----> override 和 final

C++11提供了override 和 final两个关键字,可以帮助用户检测是否重写。
(1)final:修饰虚函数,表示该虚函数不能再被重写 基类

			(2)override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。  派生类
				
	2抽象基类和纯虚函数
		抽象基类
			-概念:基类仅仅作为其派生类的一个接口,不能实例化,只能被继承。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象

-实现方法:至少一个纯虚函数
纯虚函数
-概念:抽象基类的一个纯虚函数,没有实际意义,只能(必须)被派生类重写。
-实现方法:纯虚函数使用关键字virtual,并在其后面加上=0
eg: Virtual void fun() = 0;
告诉编译器在vtable中为函数保留一个位置,但在这个特定位置不放地址。
demo

		demo2
			
			
	3虚析构
		概--念:

虚函数–>virtual 有函数体 —>父类指针 指向子类对象!!! 【这样可以通过父类调用子类的方法】->可以实例化对象
纯虚函数—>virtual 没有函数体=0 —>父类指针指向子类对象–>实现一个 抽象基类,不能实例化
1虚析构的作用【释放子类对象空间】
解决父类指针指向子类对象,当释放这个指针的时候,不用虚析构,会造成内存泄漏,虚析构会自动取 释放整块空间

如果父类的析构函数为虚函数,此时子类析构函数只要定义,无论是否加virtual关键字,都与父类的析构函数构成重写,虽然父类与子类析构函数名字不同。
demo

		2、融入了纯虚函数
			
		3、纯虚析构 --- 了解

类内声明,内外定义
为类声明虚析构函数:实现多态 释放派生类空间
纯虚析构 的目的 只是为了实现抽象基类[不能实例化]
虚析构:通过父类指针直接释放派生类空间

	4总结
		1、虚函数和纯虚函数的区别

虚函数和纯虚函数目的都是为了通过父类指针 调用 子类重写的虚函数。
虚函数 不=0修饰,有函数体,所在的类 不是抽象类,可以实例化对象。
纯虚函数 =0修饰,没有函数体,所在的类 是抽象类,不可以实例化对象。 消除二义性!
2、虚析构和纯虚析构的区别
虚析构和纯虚析构 都是为了通过父类指针 释放子类的所有空间(父类部分,子类部分)
虚析构 不=0修饰,所在的类 不是抽象类,可以实例化对象。
纯虚析构 =0修饰,类外实现函数体,所在的类 是抽象类,不可以实例化对象
3、重载、重定义、重写的区别
重载:没有继承,函数重载和运算符重载。函数名相同,参数的个数,顺序、类型可以不同,返回值类型不能作为重载条件。
重定义:有继承,子类重定义父类同名函数(非虚函数)。返回值类型,形参可以相同 和 不同。
重写:有继承,子类重写父类的虚函数。函数名,返回值类型,形参必须和父类同名函数一致。
模板
函数模板
摘要
1关键字template
C++编程思想:面向对象 泛型
模板定义:
模板 是泛型程序设计的基础(泛型—>通用类型的意思)
C++提供了函数模板(function template) ----> 实际上是创建一个通用的函数!!!---->函数类型和形参类型不具体制定 使用 虚拟类型表达
2虚拟类型
可以同一个 虚拟类型来代表!!! ------> 这个通用函数 就成为函数模版!!!
凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。
在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。
c++提供两种模板机制:函数模板和类模板
类属 - 类型参数化,又称参数模板
3总结:
模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
1、函数模版的定义格式
template <typename 形参名 typename 形参名 …> // 模版说明,可多参
返回值类型 函数名 (参数列表) // 函数体!
{
函数体;
}
将函数的形参类型、返回值类型抽象化,用一个虚拟的类型T 表示,就叫函数模板。
当调用的时候根据实参再来确定T的具体类型。

			注意:

1,template是声明模板的关键字,告诉编译器开始泛型编程。
2,尖括号<>中的typename是定义形参的关键字,用来说明其后的形参名为类型 参数,(模板形参)。Typename(建议用)可以用class关键字代替,两者没有区别。
3,模板形参(类属参数)不能为空(俗成约定用一个大写英文字母表示 T),且在函 数定义部分的参数列表中至少出现一次。与函数形参类似,
可以用在函数定义的各 个位置:返回值、形参列表和函数体。
4,函数定义部分:与普通函数定义方式相同,只是参数列表中的数据类型要使用 尖括,号<>中的模板形参名来说明。当然也可以使用一般的类型参数。
5函数模板会编译两次:

  • 函数模板本身的编译

  • 调用处 推导T的类型 再次将函数模板 编译 模板函数。

      		demo
      			
      	2练习
    

使用函数模板实现对char和int类型数组进行排序?

		3特点
			(1)函数模板不允许自动类型转换
			(2)指定函数模板T的类型
			(3)函数模板和普通函数同时使用识别

–普通函数优先于模板函数
–想调用模板函数必须显性调用 函数名<>(参数)

		4编译流程

			esco

e-预处理(宏展开).i
s-编译(生成汇编语言) .s
c-汇编(生成目标二进制文件).o
o-链接(生成可执行文件)a.out

			函数模板机制结论: 

编译器并不是把函数模板处理成能够处理任何类型的函数
函数模板通过具体类型产生不同的函数
函数模板两次编译!
5模板的局限性
目的:解决无法处理多类型参数的具体模板
1直接重载运算符 operator 运算符 = >

			2函数模板具体化
				
		6、作业 -----> 设计算法 可以排序任意类型数组,打印任意类型数组 (对象数组)
	类模板
		1定义类模板的格式

ename 形参名, typename 形参名,…>
class 类名{

};
说明:
(1)类模板中的关键字含义与函数模板相同。
(2)类模板中的类型参数可用在类声明和类实现中。类模板的模板形参(类型参 数)不能为空,一旦声明了类模板就可以用类模板的形参名声明类中的成员变量和 成员函数,即在类中使用内置数据类型的地方都可以使用模板形参名来代替。
2类和类模板的关系
由于类模板包含类型参数,因此也称为参数化类,
如果说类是对象的抽象,对象是 类的实例,
则类模板是类的抽象,类是类模板的实例。
3类模板的实例化
定义了类模板后就要使用类模板创建对象以及实现类中的成员函数,这个过程其实 也是类模板实例化的过程,实例化出的具体类称为模板类。
(1)使用类模板创建对象时,必须指明具体的数据类型。
例如:用上述定义的模板类A创建对象,则在类A后面跟<>,并在里面表明相应 的类型:
A a; //类A中凡是用到模板形参的地方都会被int类型所代替
强调:与函数模板不同的是,类模板在实例化时,必须在尖括号中为模板形参显式 地指明数据类型(实参),编译器不能根据给定的数据推演出数据类型。
即:不存 在将整型值10推演为int类型传递给模板形参的实参推演过程,必须要在<>中指 定int类型。
(2)当类模板有两个模板形参时,创建对象时,类型之间要用逗号分隔开。
(3)可以使用对象指针的方式来实例化 new
4类模板的定义
类模板实例化对象(模板类)时 必须指定T1 T2

		5、类模板的成员函数类外实现 

普通的类成员函数 可以类外实现 (类中声明)

		6类模板的分文件实现
			(1) 错误使用 data.h+data.cpp+main.cpp
				报错相关函数没有实现 ----> 因为调用头文件---> 预处理  就把头文件的内容放在 main,cpp 的前面了!!!

-----> 模板会编译两次 —> 编译的时候 发现有类模板的声明 —> 不使用 不会错
一次编译通过了!! -----> 调用的时候才会二次编译 去指定类型!!!
没有吧 data.cpp 编译过来! 所以 如果 调用 类模板 都会报错!!
不涉及类模板为什么可以呢? 因为第一次把头文件预处理,把.cpp 也独立编译, 最后链接才把两个文件链接在一起 才可以正常使用

			(2)改进----> 类模板两次编译 所以不允许.cpp和.h 分离
				因为头文件 一般是 类型定义  函数声明 变量声明 宏定义    的  一般没有在头文件中  定义函数的

全局函数的话 会造成命名冲突 ----> 所以 c++ 中将头文件 改成hpp
所以 以后见到HPP 不要惊讶 这是头文件和源文件的结合体 这里面在整类模板!!!
data.hpp
main.cpp

	类模板作为函数参数
		1、普通函数作为类模板的友元1
			
		2、函数模板作为类模板的友元
			
	类模板派生一个普通类
		1类模板派生 普通类
			
		2类模板派生类模板
			
	设计一个数组模板类(MyArray),完成对不同类型元素的管理
		myarr.hpp
		main.cpp
			
C++的类型转换
	1概念:
		从一种数据类型转化成另一种数据类型

-特殊:转化指针的时候,char *p -->(int *p)指向空间变大
-C++类型转化更加严格,对类型的检查更加严格
-C++>提供了一种显示转换语法!!!! ----> 显示转换语法 可以增加可读性,更明显得到什么类型!!!
1、上行和下行转换
父类在上 子类在下
上行转换 :父类指针指向子类空间 (多态中虚函数) ---->安全的 — 小指大
下行转换: 子类指针 指向 父类空间
------不安全 —大指小

	2、静态转化(static_cast)可能不安全
		概述:
			安全检查,(部分)不安全则报错

用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
用于基本数据类型之间的转换,如把int转换成char,把char转换成int。这种转换的安全性也要开发人员来保证
1 静态转换格式

static_cast ----> T 就是你想要转换的类型
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全报错的。
2 demo
3、动态转换 dynamic_cast 只支持安全的
作用:主要用于类的层次间转换(父类子类的上行下行转换)
-上行转换—安全---------父类指针指向子类对象------动态静态没区别
-下行转换—不安全的–子类指针指向父类对象
静态 编译能通过 dynamic_cast 检查更加严格

	4、常量转换 const_cast<T> ---->取出const特性
		该运算符用来修改类型的const属性。 

常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;
注意:不能直接对非指针和非引用的变量使用const_cast操作符去直接移除它的const.
5、重新解释转换reinterpret_cast---->最不安全
作用:类似于强转
主要用于将一种数据类型从一种类型转换为另一种类型。它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针.
异常
1异常的概念
1概念:
C语言 遇到段错误,遇到异常发生段错误,提示核心已转储!!!
异常特点:遇到异常写入日志,程序继续,至少支持部分!
目标:会使用标准异常–》写自己的异常(继承于标准异常–不继承不通用) 会在 标准异常的基础上 派生自己的异常(多态)
目的:提供异常的基本目的就是为了处理上面的问题,—程序执行过程中/函数中 遇到无法处理的错误
异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件
2C语言传统错误处理机制:
终止程序: 例如: assert
有缺陷: 用户难以接受 -------> 发生内存错误!!! 0做分母 就会终止程序
int a = 10; int b = 0; a/b
返回错误码: 缺陷: 需要用户去查询对应的错误码 errno
返回errno perror打印错误信息 也可以获取errno 错误码
3C++ 也支持C的错误处理机制!!! ------> return 0 表示成功 return 1 表示成功 ----->不一致
c++ 提供了一种异常处理机制 -----> 异常作为 一个类 ------> 类中就有成员 就可以包含很多有用的信息
异常处理 可以 调用跳级!!! -----> 代码编写时,假设在多个函数的调用栈中出现了问题,使用整型返回码要求在每一级的函数中都要进行处理
-----> 使用异常处理栈的展开机制,只需要在一处进行处理就可以了哦!!! 不需要每一级都去处理!!!
2为什么c++要扩展异常处理
//所以,我们希望:
//1.异常应该捕获,如果你捕获,可以,那么异常必须继续抛给上层函数,你不处理,不代表你的上层不处理
//2.这个例子,异常没有捕获的结果就是运行结果错的一塌糊涂,结果未知,未知的结果程序没有必要执行下去
抛出异常 ----> 解决异常
异常 — 用户决定 比如两个字不同 不同返回-1 就可以认定为异常
返回值可以忽略 但是C++异常不可忽略 如果忽略异常 就会结束进程!!
C++异常格式
(1) 异常基本语法

		(2)总结
			若有异常则通过1throw操作创建一个异常对象并抛出。

将可能抛出异常的程序段放到try块之中。
如果在try段执行期间没有引起异常,那么跟在try后面的catch字句就不会执行。
catch子句会根据出现的先后顺序被检查,匹配的catch语句捕获并处理异常(或继续抛出异常)
如果匹配的处理未找到,则运行函数terminate将自动被调用,其缺省功能调用abort终止程序。
处理不了的异常,可以在catch的最后一个分支,使用throw,向上抛。
c++异常处理使得异常的引发和异常的处理不必在一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。
异常有严格的类型匹配
异常机制和函数机制互不干涉!但是异常捕获 是通过严格的类型进行匹配的!!

	异常抛出和处理的原则
		异常通过  throw 抛出一个对象!!!

该对象的类型 决定了 激活哪一个catch的异常处理代码!!!

  • 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。
    (这里的处理类似于函数的传值返回)

  • catch(…) 可以捕获任意类型的异常,问题是不知道异常错误是什么。 -----> catch(…) 可以捕获任意类型的异常

  • 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获
    如果存在 多个 try/catch 的嵌套 catch选项类型都匹配, 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个

      4、在函数调用链中 异常栈的展开匹配原则
      	1.首先检查 throw 本身是否在 try块内部,如果是再查找匹配的 catch 语句。如果有匹配的,则调到 catch 的地方进行处理。
    

2.没有匹配的 catch 则退出当前函数栈,继续在调用函数的栈中进行查找匹配的 catch。
3.如果到达main函数的栈,依旧没有匹配的,则终止程序。
上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个 catch(…) 捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
4.找到匹配的 catch 子句并处理以后,会继续沿着 catch 子句后面继续执行

	栈解旋(unwinding)
		异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋(unwinding).

因为抛出异常之后,外界捕获对象。 try 模块中的 局部对象 已经没有实质意义了 -----> 为了保证空间的回收 —>所以释放

	6、异常的声明周期
		throw 抛出-catch捕获-处理-对象销毁-资源释放-程序恢复
		throw的异常是有类型的,可以是数字、字符串、类对象。 

throw的异常是有类型的,catch需严格匹配异常类型。

		构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化

析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)
C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在 lock 和 unlock 之间抛出了异常导致死锁,C++经常使用 RAII 来解决以上问题,关于 RAII,智能指针这节进行讲解
简单而言RAII就是指资源在我们拿到时就已经初始化,一旦不在需要该资源就可以自动释放该资源。
  对于C++来说,资源在构造函数中初始化(可以再构造函数中调用单独的初始化函数),在析构函数中释放或清理。常见的情形就是在函数调用中创建C++对象时分配资源,在C++对象出了作用域时将其自动清理和释放(不管这个对象是如何出作用域的,不管是否因为某个中间步骤不满足条件而导致提前返回,也不管是否正常走完全部流程后返回)。
异常的多态
目的: 使用父类的引用 去捕获所有子类的异常!!! -----> 所有的异常都是从父类中派生出来的!!!
------>为标准异常做准备!!

	8、标准异常!!!
		概述
			标准库 提供了 很多的异常类!!!! 他们是通过继承组织起来的!!!  异常类继承层级结构如上:
			8个标准异常类的成员:

① 在上述继承体系中,每个类都有提供了构造函数、拷贝构造函数、和赋值操作符重载。
② logic_error 类及其子类、runtime_error 类及其子类,它们的构造函数是接受一个 string 类型的形式参数,用于异常信息的描述
③ 所有的异常类都有一个 what()方法,返回 const char* 类型(C 风格字符串)的值,描 述异常信息。

		1案例

//异常类头文件
#include <stdexcept
使用案例1

			案例2
				
		2编写自己的异常
			① 标准库中的异常是有限的;

② 在自己的异常类中,可以添加自己的信息。(标准库中的异常类值允许设置一个用来描述 异常的字符串)。
编写自己的异常类
① 建议自己的异常类要继承标准异常类。因为 C++中可以抛出任何类型的异常,所以我们 的异常类可以不继承自标准异常,但是这样可能会导致程序混乱,尤其是当我们多人协同开发时。
② 当继承标准异常类时,应该重载父类的 what 函数和虚析构函数。
③ 因为栈展开的过程中,要复制异常类型,那么要根据你在类中添加的成员考虑是否提供 自己的拷贝构造函数
1、自己的异常必须继承exception
2、必须重写what虚函数

	练习9、练习

案例练习: 自定义异常类 使用多态 父类捕获子类对象

STL
	1STL简介
		STL 是“Standard Template Library”的缩写,中文译为“标准模板库”。

C++ —> STL ----> 就是借助模板把常用的数据结构及其算法都实现了一遍! 并且做了数据结构和算法的分离!!!
例如: vector —> 底层逻辑是顺序表(数组)
list ----> 底层 为双向链表
deque 底层 循环队列!!!
set ----> 底层为红黑树!!
hash_set —> 底层为哈希表
泛型编程的代表作品STL -----> 是一种高效、泛型、可交互操作的软件组件。
STL(Standard Template Library),即标准模板库,是一个高效的C++程序库
STL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator)。
2STL的六大组件
1容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据,从实现角度来看,STL容器是一种class template。
2算法:各种常用的算法,如sort、find、copy、for_each。从实现的角度来看,STL算法是一种function tempalte.
3迭代器:扮演了容器与算法之间的胶合剂,共有五种类型,从实现角度来看,迭代器是一种将operator* , operator-> , operator++,operator–等指针相关操作予以重载的class template. 所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
4仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class 或者class template
5适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
6空间配置器:负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class tempalte.
STL六大组件的交互关系,容器通过空间配置器取得数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数。
各组件在合作关系上,互为依赖,相互之间形成服务与被服务关系。

	3STL的核心使用三大组件

程序 = 数据结构 + 算法;
1 容器
1 概述
任何特定的数据结构都是为了 实现某种特定的算法!!!
STL 容器: 是将运用最广泛的一个数据结构 实现出来!!!
常用的数据结构: 数组(array) 链表 list 栈 stack 队列 queue 树 tree 集合 set 映射表 map
根据数据在容器中的排列特性。 -----> 存放数据的方式!!
数据结构分类: 序列化容器 关联式容器
序列化容器: 强调值的排序!! -----> 每个元素都有固定的位置,除非用 删除 或插入去改变这个位置
vector Deque lis
关联式容器: 是非线性的树形结构,更准确的二叉树结构!!! 各元素之间没有严格的物理上的顺序!!!
Set/mulitset Map/mulitmap
2 算法
STL中的算法库提供了很多种算法(处理方法),可以用来处理各种数据结构,例如数组、列表、栈、队列等等。以下是STL中常用的算法:
查找算法:find、find_if、binary_search等
排序算法:sort、stable_sort、partial_sort等
操作算法:copy、swap、reverse等
数值算法:accumulate、inner_product、partial_sum等
集合算法:merge、set_union、set_intersection等
变异算法:transform、replace、remove等
分类算法:partition、stable_partition、is_sorted等
算法部分主要由头文件 、 和 组成。
是所有 STL 头文件中最大的一个,是由很多函数模板组成的,可以认为每个函数在很大程度上都是独立的,
其中常用到的功能有比较、交换、查找、遍历、复制、修改、移除、反转、排序、合并等操作;
体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作;
中则定义了一些模板类,用以声明函数对象。
算法分为:质变算法和非质变算法。
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等
STL中算法大致分为四类:
非可变序列算法:指不直接修改其所操作的容器内容的算法;
可变序列算法:指可以修改它们所操作的容器内容的算法;
排序算法:对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作;
数值算法:对容器内容进行数值计算。
3迭代器
迭代器(iterator)是一种抽象的设计概念,现实程序语言中并没有直接对应于这个概念的实物。
在<>一书中提供了23种设计模式的完整描述,
其中iterator模式定义如下:提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
迭代器的设计思维-STL的关键所在,STL的中心思想在于将容器(container)和算法(algorithms)分开,彼此独立设计,最后再一贴胶着剂将他们撮合在一起。
从技术角度来看,容器和算法的泛型化并不困难,c++的class template和function template可分别达到目标,如果设计出两这个之间的良好的胶着剂,才是大难题。

		常见迭代器详细介绍
			迭代器是一个可以遍历STL容器内全部或部分元素 的  对象!!

迭代器支出容器中的一个特定的位置
迭代器就像一个指针一样!!
迭代器提供对一个容器中的对象的 访问方法!!!—> 并且可以定义容器中对象的范围!!!!
1 正向迭代器

			2 双向迭代器
				双向迭代器具有正向迭代器的全部功能。

除此之外,若 p 是一个双向迭代器,则–p和p–都是有定义的。
–p使得 p 朝和++p相反的方向移动。
3 随机访问迭代器
随机访问迭代器具有双向迭代器的全部功能。若 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:

  • p+=i:使得 p 往后移动 i 个元素。

  • p-=i:使得 p 往前移动 i 个元素。

  • p+i:返回 p 后面第 i 个元素的迭代器。

  • p-i:返回 p 前面第 i 个元素的迭代器。

  • p[i]:返回 p 后面第 i 个元素的引用。
    此外,两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。
    p1<p2的含义是:p1 经过若干次(至少一次)++操作后,就会等于 p2。其他比较方式的含义与此类似。
    对于两个随机访问迭代器 p1、p2,表达式p2-p1也是有定义的,其返回值是 p2 所指向元素和 p1 所指向元素的序号之差。
    4 常用容器的迭代器

      	常见STL详细介绍
      		总述
      			vector:可以动态调整大小的数组,支持快速的随机访问和迭代器访问,并且支持在尾部添加或删除元素。
    

deque:双端队列,支持快速的随机访问和迭代器访问,并且支持在头部和尾部添加或删除元素。
list:双向链表,只支持迭代器访问,并且支持在任意位置添加或删除元素。
set:有序集合,其中每个元素都是唯一的,支持快速的查找和插入操作。
map:有序映射,其中每个元素都有一个唯一的键值对,支持快速的查找和插入操作。
unordered_set:无序集合,其中每个元素都是唯一的,支持快速的查找和插入操作,但元素的顺序是未定义的。
unordered_map:无序映射,其中每个元素都有一个唯一的键值对,支持快速的查找和插入操作,但元素的顺序是未定义的。
以上仅是STL容器的一部分,其中还包括stack、queue、priority_queue等等。不同的容器类型适用于不同的使用场景,需要根据实际情况进行选择。
string容器
1、string容器的概念
C语言风格的字符串 ----- 以 ‘\0’ 字符结尾!!! -----> 太复杂了!!不太适合大程序的开发!!
C++ -----> 标准库中 定义了一种类 string类!! -------> 头文件
C字符串: char 的指针
C++ string : 是一个类!! ----> 类内部封装了 char 管理字符串 ----> 是一个char类型的容器!!!
String 封装了很多方法: find cope delete replace insert
而且不需要用户考虑越界等情况!!! 也不需要考虑释放!!
string 管理了 char字符串 分配的空间!! ----->每一次string 的复制 取值 都是由String类负责维护,用户不需要担心复制越界,取值越界这些情况!!
2、string容器的常用操作
string 构造函数
string();//创建一个空的字符串 例如: string str;
string(const string& str);//使用一个string对象初始化另一个string对象
string(const char* s);//使用字符串s初始化
string(int n, char c);//使用n个字符c初始化
string基本赋值操作
string& operator=(const char* s);//char类型字符串 赋值给当前的字符串
string& operator=(const string &s);//把字符串s赋给当前的字符串
string& operator=(char c);//字符赋值给当前的字符串
string& assign(const char s);//把字符串s赋给当前的字符串
string& assign(const char s, int n);//把字符串s的前n个字符赋给当前的字符串
string& assign(const string &s);//把字符串s赋给当前字符串
string& assign(int n, char c);//用n个字符c赋给当前字符串
string& assign(const string &s, int start, int n);//将s从start开始n个字符赋值给字符串
string存取字符操作
char& operator[](int n);//通过[]方式取字符
char& at(int n);//通过at方法获取字符
string拼接操作
string& operator+=(const string& str);//重载+=操作符
string& operator+=(const char
str);//重载+=操作符
string& operator+=(const char c);//重载+=操作符
string& append(const char s);//把字符串s连接到当前字符串结尾
string& append(const char s, int n);//把字符串s的前n个字符连接到当前字符串结尾
string& append(const string &s);//同operator+=()
string& append(const string &s, int pos, int n);//把字符串s中从pos开始的n个字符连接到当前字符串结尾
string& append(int n, char c);//在当前字符串结尾添加n个字符c
string查找和替换
int find(const string& str, int pos = 0) const; //查找str第一次出现位置,从pos开始查找
int find(const char
s, int pos = 0) const; //查找s第一次出现位置,从pos开始查找
int find(const char
s, int pos, int n) const; //从pos位置查找s的前n个字符第一次位置
int find(const char c, int pos = 0) const; //查找字符c第一次出现位置
int rfind(const string& str, int pos = npos) const;//查找str最后一次位置,从pos开始查找
int rfind(const char
s, int pos = npos) const;//查找s最后一次出现位置,从pos开始查找
int rfind(const char
s, int pos, int n) const;//从pos查找s的前n个字符最后一次位置
int rfind(const char c, int pos = 0) const; //查找字符c最后一次出现位置
string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str
string& replace(int pos, int n, const char* s); //替换从pos开始的n个字符为字符串s
string比较操作
/*
compare函数在>时返回 1,<时返回 -1,==时返回 0。
比较区分大小写,比较时参考字典顺序,排越前面的越小。
大写的A比小写的a小。
*/
int compare(const string &s) const;//与字符串s比较
int compare(const char s) const;//与字符串s比较
string子串
string substr(int pos = 0, int n = npos) const;//返回由pos开始的n个字符组成的字符串
string插入和删除操作
string& insert(int pos, const char
s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
string& insert(int pos, int n, char c);//在指定位置插入n个字符c
string& erase(int pos, int n = npos);//删除从Pos开始的n个字符
string转c_str
// string 转 char *
string name = “zhangsan”;
const char * cstr = name.c_str();
// char * 转string
const char *p = “lisi”;
name = p;
name = “wangwu”;
3、string 容器的使用

			vector容器
				1vector容器的特点
					(1)认识vector容器

普通数组: 空间连续的 而且大小在定义的时候已经确定 -----> int a[10]; ---->40 char buf[128];
注意数组的越界问题!!!
自己封装了一个Myarr -----> 实现动态内存 ---->支持自动扩容
vector容器 ----> 数据的存储结构!! 就是我们实现的myarr -----> 动态的空间 有自己的空间配置策略
vector容器 ---->支持单端动态数组 -----> 只能在一端进行操作(插入和删除) —> 支持[] a[10] —> *(a+10)
只能在尾巴进行插入 和删除:
push_back()// 尾插法
pop_back(); //尾删法
front(); // 获得容器的首元素
back(); //获取容器的尾元素

					(2) vector容器的定义及使用

#include
vector vob; // 无参构造 T类型
vector(n,val); //有参构造,初始化时 将n个val 拿进去初始化
vector(const vector &vob); // 拷贝构造!!!
vector(v.begin(),v.end()); // 区间拷贝构造将v容器 从 开始位置 到 结束位置 的内容拷贝到新容器中!!
(3)vector迭代器 ----> 操作容器中的位置!!
vector容器 是实现的动态数组; -----> 支持任意位置操作 ------> 随机迭代器 ----> 迭代器的操作就像指针一样!!
STL ---->将数据结构(数据存储方式) 和 算法做了分离
迭代器的本质: 是运算符的重载!! ---->操作像指针 ----->随机访问迭代器
operator-> operator++ operator-- operator+ operator- operator= operator+= operator-=
operator> operator< operator== operator[]
普通数组 可以通过 普通指针 直接操作, 支持上面所有运算符!!!
vector容器 也是数组 空间也是连续 , 它的指针也支持!! -----> 随机访问迭代器

					(4)vector容器的成员方法

				2、迭代器使用
					(1)vector容器操作

容量: 无参构造 默认容量为 0

						容量有参构造
						
					(2)通过迭代器去操作一个vector容器

遍历容器: 遍历算法的实现

						注意:对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就都失效了。

这是程序员容易犯的一个错误,务必小心。
int 类型vector容器 对元素排序
3、vector 的赋值操作 assign
//将[beg, end)区间中的数据拷贝赋值给本身。
assign(beg, end); //参数是迭代器
//将n个elem拷贝赋值给本身。
assign(n, elem);
//重载等号操作符
vector& operator=(const vector &vec);

				4、vector容器的其他操作1
					//容器内元素的操作

1、判断容器 是否为空 有无元素
empty(); -----> bool
2、清空容器
clear();
3、元素个数
size()
4、容器大小
capacity();
5、设置容器长度
resize(int num) // -----> 重新制定容器的长度【元素数量】 如果变长,会填充默认值到新位置 // 变短 丢弃超出的部分,容量只增不减
resize(int num,val) // -----> 重新制定容器的长度 如果变长,会填充val到新位置
// 变短 丢弃超出的部分
6、预设容器长度
reserve(int len); //无参构造 默认长度为 0 容量为0 可以通过该方法 预设容量 容量只增不减

				5、vector容器的其他操作2
					1、返回第i个位子 的元素值

at(int i); -----> i如果越界 会抛出异常 out_of_range //返回值是一个引用!!
2、vector 是数组 []重载
operator[]
3、获取首元素
front();
4、获取尾元素
back()
5、任意位置插入 count 个 val
insert(const_iterator pos,int count, val);
6、删除元素 —>迭代器指向的单个元素
erase(const_iterator pos);
7、删除元素 —>迭代器范围内 -----> end不包含 因为表示最后一个元素 后面的位置
erase(const_iterator start,const_iterator end);
8、交换 两个容器元素互换
swap(vector ob);

			deque容器
				1deque基本概念 
					头文件  #include <deque>

双端的动态数组 ------> 随机访问迭代器!!!
Vector容器是单向开口的连续内存空间, 单端 支持 push_back() pop_back()
deque则是一种双向开口的连续线性空间。
所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,

				2 deque和vector 区别
					vector顺序存储 空间是连续的!!!        尾插   O(1)      insert O(n)

eque允许使用常数项时间对头端进行元素的插入和删除操作 尾插 O(1) insert O(1)
deque没有容量的概念 -----> 分段式存储!!!! 分段式内存组合!!! ----> 随时可以增加一端新的空间 并且连接起来!!!
deque ----> 没有reserve 扩容的概念!!!
它的迭代器 是不普通的指针

					中控器:  记录每一块空间的起始地址!!!  

deque 分段式存储!!
头插 ---->会在头部再分配一块空间 ----> 把分配空间的起始位置 放在中控器前面!!
尾插 ---->会在尾部再分配一块空间 ----> 把分配空间的起始位置 放在中控器后面!!
3deque的构造
deque deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将n个elem拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
4deque的赋值
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
swap(deq);// 将deq与本身的元素互换
5deque的大小操作
eque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置,
//如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem);

push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器第一个数据

				6deque数据存取
					at(idx);//返回索引idx所指的数据,如果idx越界,抛出out_of_range。

operator[];//返回索引idx所指的数据,如果idx越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据

				7deque的插入删除操作
					insert(pos,elem);//在pos位置插入一个elem元素的拷贝,返回新数据的位置。

insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
clear();//移除容器的所有数据

			stack容器
				1、stack容器的概念
					stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口,stack容器允许新增元素,移除元素,取得栈顶元素   -----> 除了最顶端外,没有任何其他方法可以存取stack的其他元素   ---->  不支持遍历!!

没有迭代器!

				2、stack构造函数
					stack<T> stkT;//stack采用模板类实现, stack对象的默认构造形式: 

stack(const stack &stk);//拷贝构造函数
3、stack赋值
stack& operator=(const stack &stk);//重载等号操作符
4、stack数据存取操作
push(elem);//向栈顶添加元素
pop();//从栈顶移除第一个元素
top();//返回栈顶元素
5、stack大小操作
empty();//判断堆栈是否为空
size();//返回堆栈的大小
6、demo

			queue容器 
				1、queue容器概述
					Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素。
					
				2、queue容器没有迭代器
					Queue所有元素的进出都必须符合”先进先出”的条件,只有queue的头端元素,才有机会被外界取用。Queue不提供遍历功能,也不提供迭代器。
				3、queue的构造
					queue<T> queT;//queue采用模板类实现,queue对象的默认构造形式:

queue(const queue &que);//拷贝构造函数
4、queue存取元素以及插入删除
push(elem);//往队尾添加元素
pop();//从队头移除第一个元素
back();//返回最后一个元素
front();//返回第一个元素
5、queue赋值操作
queue& operator=(const queue &que);//重载等号操作符
6、queue大小操作
empty();//判断队列是否为空
size();//返回队列的大小
7、demo

			list容器
				list容器基本概念
					底层实现   ----> 链表   ----->  支持频繁的 不确定位置的任意插入、删除

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
List和vector是两个最常被使用的容器。
List容器是一个双向链表。 -----> 有迭代器!!

  • 采用动态存储分配,不会造成内存浪费和溢出

  • 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素

  • 链表灵活,但是空间和时间额外耗费较大

      			list容器的迭代器
      				由于list容器 是一个双向链表!!  支持 向前移动 和向后移动的能力   ------->  双向迭代器
    

List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效 ----> 甚至List元素的删除,也只有被删除的那个元素的迭代器失效,其他迭代器不受任何影响。
这在vector是不成立的,因为vector的插入操作可能造成记忆体重新配置,导致原有的迭代器全部失效。

				list容器的构造
					list<T> lstT;//list采用采用模板类实现,对象的默认构造形式:

list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将n个elem拷贝给本身。
list(const list &lst);//拷贝构造函数。
list赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
list& operator=(const list &lst);//重载等号操作符
swap(lst);//将lst与本身的元素互换。
list容器元素存取
front();//返回第一个元素。
back();//返回最后一个元素。
list容器操作—> 插入删除
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。

erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
clear();//移除容器的所有数据

remove(elem);//删除容器中所有与elem值匹配的元素。
7、list容器的大小
empty();//判断容器是否为空
size();//返回容器中元素的个数
resize(num);//重新指定容器的长度为num,
//若容器变长,则以默认值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem);//重新指定容器的长度为num,
//若容器变长,则以elem值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
8、list容器反转和排序
reverse();//反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。
sort(); //list排序
9、demo

				注意:list使用双向迭代器,不支持 + N,支持++ --
			set容器
				1、set容器基本概念
					底层实现:红黑树(平衡二叉树)

使用场景:查找具体到某个单位,区别于 vector 一般是某个范围。需要元素有序,查找 / 删除 / 插入的性能相同,效率都是 O(logN)。如对游戏个人得分记录的存储,要求按从高到低的顺序排列。
Set的特性是。所有元素都会根据元素的键值自动被排序。(容器在插入的时候,会根据键值自动排序)
Set的元素不像map那样可以同时拥有实值和键值,set的元素即是键值又是实值。
Set容器的迭代器 是只读迭代器。 -----> 不可以通过set的迭代器改变set元素的值
set 容器 的元素 不允许重复!!!!
2、multiset容器
multiset特性及用法和set完全相同,唯一的差别在于它允许键值重复。set和multiset的底层实现是红黑树.
3、set的构造
set st;//set默认构造函数:
set(const set &st);//拷贝构造函数
mulitset mst; //multiset默认构造函数:
4、set容器赋值操作
set& operator=(const set &st);//重载等号操作符
swap(st);//交换两个集合容器
5、set大小操作
empty();//判断容器是否为空
size();//返回容器中元素的数目
6、set插入和删除操作
insert(elem);//在容器中插入元素。
clear();//清除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);//删除容器中值为elem的元素。
7、set容器的查找
find(key);
//查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
count(key);//查找键key的元素个数
lower_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
8、demo

				9、修改排序规则
					
				10、set存放自定义类型
					
				11、上下限   equal_range
					equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
						
					demo
						
				12、对组
					概念

对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型
两个值可以分别用pair的两个公有属性first和second访问。
类模板:template <class T1, class T2> struct pair.
map容器
1map容器的概念
底层实现:红黑树
使用场景:查找具体到某个单位,区别于 set 范围更大,如按 ID 号存储十万个用户,想要快速通过 ID 查找对应的用户。
Map的特性是,所有元素都会根据元素的键值自动排序。
Map所有的元素都是对组pair,同时拥有实值和键值,pair的第一元素被视为键值,第二元素被视为实值
map不允许两个元素有相同的键值 ----> multimap 允许重复!!
我们不可以通过map的迭代器改变map的键值-----> 只读迭代器
2、map构造函数
map<T1, T2> mapTT;//map默认构造函数:
map(const map &mp);//拷贝构造函数
3、map赋值操作
map& operator=(const map &mp);//重载等号操作符
swap(mp);//交换两个集合容器
4、map大小操作
size();//返回容器中元素的数目
empty();//判断容器是否为空
5、map插入数据元素操作
map.insert(…); //往容器插入元素,返回pair<iterator,bool>
map<int, string> mapStu;
// 第一种 通过pair的方式插入对象
mapStu.insert(pair<int, string>(3, “小张”));
// 第二种 通过pair的方式插入对象
mapStu.inset(make_pair(-1, “校长”));
// 第三种 通过value_type的方式插入对象
mapStu.insert(map<int, string>::value_type(1, “小李”));
// 第四种 通过数组的方式插入值
mapStu[3] = “小刘”;
mapStu[5] = “小王”;
6、map删除操作
clear();//删除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中key为keyElem的对组。
7、map查找操作
//查找键key是否存在,若存在,返回该键的元素的迭代器;
//若不存在,返回map.end();
find(key);
//返回容器中key为keyElem的对组个数。
count(keyElem); //对map来说,要么是0,要么是1。对multimap来说,值可能大于1。
lower_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
8、demo

				9、multimap 案例
					允许键值重复

公司今天招聘了 5 个员工,5 名员工进入公司之后,需要指派员工在那个部门工作
人员信息有: 姓名 年龄 电话 工资等组成 通过 Multimap 进行信息的插入 保存
显示 分部门显示员工信息 显示全部员工信息
// 分析 如果 1个员工一个部门 没必要用multimap 直接map 完成
// 5个员工 3个部门 必然存在 1个部门多人
// 显示 按照部门显示人员信息 ----> 部门 充当键值比较好
// 所以 1号 部门 可能有多个信息 不能用map

		常见算法详细介绍
			算法
				1函数对象 仿函数
					重载函数调用操作符的类,其对象常称为函数对象(function object)

即它们是行为类似函数的对象,也叫仿函数(functor),其实就是重载“()”操作符,使得类对象可以像函数那样调用。
注意:
函数对象(仿函数)是一个类,不是一个函数。
函数对象(仿函数)重载了”() ”操作符使得它可以像函数一样调用。
分类:
假定某个类有一个重载的operator(),而且重载的operator()要求获取一个参数,我们就将这个类称为“一元仿函数”(unary functor);相反,如果重载的operator()要求获取两个参数,就将这个类称为“二元仿函数”(binary functor)。
函数对象的作用:
STL提供的算法往往都有两个版本,其中一个版本表现出最常用的某种运算,另一版本则允许用户通过
template参数的形式来指定所要采取的策略。 ----> 为算法提供 策略。----> set 从大到小排序
2谓词
谓词是指普通函数或重载的operator() 返回值是bool类型的函数对象(仿函数)。
如果operator接受一个参数,那么叫做一元谓词,如果接受两个参数,那么叫做二元谓词,
谓词可作为一个判断式。 -----> 返回值 为 bool 为算法提供判断策略

				3遍历算法
					
				4内建函数对象
					STL内建了一些函数对象。  内部提供了  只要会用就行

分为:
算数类函数对象,
关系运算类函数对象,
逻辑运算类仿函数。
这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能。使用内建函数对象,需要引入头文件#include
6个算数类函数对象,除了negate是一元运算,其他都是二元运算。
template T plus//加法仿函数
template T minus//减法仿函数
template T multiplies//乘法仿函数
template T divides//除法仿函数
template T modulus//取模仿函数
template T negate//取反仿函数
6个关系运算类函数对象,每一种都是二元运算。
template bool equal_to//等于
template bool not_equal_to//不等于
template bool greater//大于
template bool greater_equal//大于等于
template bool less//小于
template bool less_equal//小于等于
逻辑运算类运算函数,not为一元运算,其余为二元运算。
template bool logical_and//逻辑与
template bool logical_or//逻辑或
template bool logical_not//逻辑非
demo

					demo2
						
				5函数对象适配器
					- 函数适配器bind1st   bind2nd

bind2nd将300绑定到第二个参数位置
bind1st将300绑定到第一个参数位置

  • 现在我有这个需求 在遍历容器的时候,我希望将容器中的值全部加上100之后显示出来,怎么做?
  • 我们直接给函数对象绑定参数 编译阶段就会报错
  • for_each(v.begin(), v.end(), bind2nd(myprint(),100));
  • 如果我们想使用绑定适配器,需要我们自己的函数对象继承binary_function 或者 unary_function
    - 根据我们函数对象是一元函数对象 还是二元函数对象
    //总结: bind1st和bind2nd区别?
    //bind1st : 将参数绑定为函数对象的第一个参数
    //bind2nd : 将参数绑定为函数对象的第二个参数
    可以被适配的对象必须是继承了binary_function的

//bind1st bind2nd将二元函数对象转为一元函数对象

				6常见的算法
					1遍历算法
						1 for_each
						2 transform算法
					2 常用查找算法
						1 find算法
						2 find_if算法
						子主题 3
					3 常见排序算法
						子主题 1
						子主题 2
						子主题 3
					4 常用的拷贝和替换算法
					5算术生成算法
		1函数指针适配器
		2成员函数 适配器
		3取反适配器
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐