C++17

常用

plaintext
1
2
3
①在方法或属性前加上类名::	表示该方法或者属性作用域是这个类
②clion每个工程只允许一个main,所以在创建c++源文件的时候,不要将他添加到CMakeLists.txt即可
③数组长度:sizeof(arr)/sizeof(arr[0])

前言

头文件

自定义头文件,往往以.h为后缀

plaintext
1
2
3
4
5
6
7
8
#include "iostream"			//c++输入输出库
#include<bits/stdc++.h> //万能头文件
#include<vector>
#include<cstdio> //结合c语言的c++输入输出库,推荐算法使用这个printf和scanf
#include<string>
#include "cstring" //字符串库

using namespace std; //命名空间

编译

​ 概述:就是将cpp文件的代码编译成计算机可执行的exe文件。

​ 编译器:mingw(即gcc在window平台的版本)

​ 手动编译命令:g++ 文件名.cpp -o 文件名.exe

基础语法

输入和输出

cout

​ 作用:帮助将内容打印到控制台上

​ 使用:cout<<内容;

​ 换行:<<endl;

​ 输出多份内容:可以通过多个 << 进行自由组合输出内容。

中文输出乱码解决

​ 方法一:

​ 引入windows.h库,即#include “windows.h”

​ 使用SetConsole0utputCP(“CP_UTF8”); //修改控制台输出编码格式

​ 方法二:

​ 直接添加system(“chcp 65001”); //表示将控制台输出编码改为65001也就是utf8

cin

​ 作用:完成从键盘输入数据提供给运行的程序

​ 语法:cin >> 变量; //输入数据对变量赋值

​ 中文乱码解决:在clion中按下Ctrl + Alt + Shfit + / 四个键,选择注册表,把run那个取消掉

注释

​ 单行注释:// 内容

​ 多行注释:/* 内容 */

常量

​ 概述:在程序执行过程中值不会改变的量。

​ 常量类型的决定:在C++中,对于常量的类型确定遵循最小原则

​ 按照所书写的字面量的值,来确定类型,默认int,从小到大进行适配

​ 或按照指定后缀确定,后缀大小写都可以,常用的,U、L、UL、ULL(unsigned long long)、F、D(double)

字面常量

​ 概述:被书写到代码内的常量称之为字面常量,也称字面量。

​ 整型字面常量:整数,不带小数点的数字

​ 实型字面常量:小数,带有小数点的数字

上述两种叫做数值型常量

​ 字符型字面常量:以单引号’ ‘包围的,单个字符

​ 字符串字面常量:以双引号” “包围的,任意个字符

符号常量

​ 概述:使用标识符去定义的常量,称之为符号常量。简单来说,符号常量就是给常量起一个名字,就是符号常量。

​ 定义语法:#define 标识符(名称) 常量 //这个#define是宏命令,标识符即符号常量的名称,建议全部大写

​ 例如:#define FAT_ BMI 28 //肥胖BMI,因为经常使用这个常量,所以使用这种方法定义

​ 注意:

​ ①定义在代码的头部

​ ②不需要添加分号结尾

​ ③满足标识符的要求下,字母全部大写

标识符

​ 概述:即表示某类实体的符号(名称)。

​ 比如:符号常量名、变量名、类名称、文件名、方法/函数名等等,都是标识符。

​ 内容限定:

​ ①只允许字母、数字、下划线的组合,数字不可开头

​ ②大小写敏感

​ ③不可以是关键字

​ 常用命名规范:

​ 下划线命名法:字母一般全小写,单词之间用下划线隔开。一般应用于变量名

​ 小驼峰命名法:首单词首字母小写,其他单词首字母大写。一般应用于函数名,变量名

​ 大驼峰命名法:单词首字母全部大写。一般应用于类名

关键字

​ 概述:在c++内具有特殊意义的单词

变量

​ 概述:在程序运行时,能储存计算结果或能表示值的抽象概念。简单的说,变量就是在程序运行时,记录数据用的。

​ 定义变量语法:变量类型 变量名;

​ 变量赋值语法:变量名 = 变量值;

​ 常见的变量类型:

plaintext
1
2
3
支持一次性声明多个变量,例如:
int a,b,c;
int a=10,b=20,c=30;

变量的作用域

​ {… …}称之为代码块,也是内部变量的作用域,在{}内部声明的变量,只能在内部使用

​ 作用域可以向内层的{}进行传递,但是外部是无法访问到的

数据类型

int

​ int即整型,整型分为4种如下:

​ 可以使用sizeof(变量名)查看占用空间大小

无符号和有符号数字

​ 无符号:仅仅允许正数存在

​ 有符号:可以允许负数存在

上述图中的[ ]表示可选,且默认时就代表有符号

无符号的快捷写法:

​ u_int 变量名 = 0; //其他同理

float

​ float即实型。还分为3种

​ 实型数据全部都是有符号的,并且不同编译器有效位不一样。

​ 注意有效位数,超出了就会不准确,小数点也占一位

plaintext
1
2
3
4
输出控制小数格式
cout << fixed; //设置为小数显示
cout.width(20); //设置显示的最大宽度(最大位数)
cout << num1 << endL ;

char

​ char即字符型。

char型的本质是数字,在内存中是以数字存储的,以ASCII码表转换。

97是a,65是A。并且字符可以进行数字加减。

string

​ string即字符串型。

​ C语言风格的字符串,如下:

​ char a[] = “hello”; //字符数组形式,不可更改变量值

​ char *b = “hello”; // 指针形式

​ C++风格的字符串

​ string c = “hello”;

字符串拼接

​ 方法一:可以直接使用 + 进行连接,但+号只适用于字符串和字符串的连接。可以使用to_string()内置函数将内容转换位字符串

boolean

​ 布尔值只有true和fasle,本质是1和0

转义字符

​ \就是转义符号

​ ASCII表中字符分为两类:非打印控制字符、打印字符

​ 非打印控制字符:无法打印出形态,但是可以用于表示各种控制功能,如换行、制表等

​ \t可以将前面的字符以空格填充的形式,补充至8个字符位

运算符

算数运算符

赋值运算符

​ 概述:是一种双目运算符,用于将右侧表达式的值赋予左侧变量。

比较运算符

​ 概述:是一种双目运算符,用于对两个数据进行比较(大、小、相等) ,得到bool型结果。

​ c语言风格的字符串,比较的是内存地址。可以使用cstring库中的strcmp(s1,s2)方法是比较两个字符串的内容,返回-1,0,1

​ c++风格的字符串可以直接比较内容

逻辑运算符

​ 概述:是一种单/双目运算符,用于对单个或多个表达式进行逻辑判断。

三元运算符

​ 概述:是一种三目运算符,用于对逻辑进行判断,根据判断结果提供不同值。

​ 语法:产出bool结果的表达式 ? 值1 : 值2;

​ 含义:如果?之前的表达式结果为true,那么提供值1的结果,如果结果为false,那么提供值2的结果

位运算符

判断语句

if

if-else

if-else if

控制语句

switch

​ 格式:switch(表达式){

​ case 常量表达式1:

​ 语句体1;

​ break;

​ case 常量表达式2:

​ 语句体2;

​ break;

​ ….

​ default:

​ 语句体n+1;

​ break;

​ }

​ 注意事项:

​ ①default是前面所有结果不匹配后,就会进行这个,他可省略,并且可在任何位置。

​ ②case后面只能是字面量或者枚举类型,不能是变量,且不能重复。

循环语句

for

while

do-while

枚举类型

​ 概述:本质上是一个被命名的整型常数的集合。在C\C++语言中它是种基本数据类型 。

​ 作用:提高代码的可读性、可维护性和键入性。因为枚举可以将一些数字或字符串符号化,以此增强程序的可读性和可维护性。

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
语法:
enum枚举名{
枚举元素1,
枚举元素2,
...
枚举元素n
};

例如:
enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER
};
Season v1 = SPRING;
Season v2 = WINTER;

枚举类型每一个元素,都有整数标号,默认从0开始递增。即上述代码SPRING本质是数字0,SUMBER是2,以此类推。
起始标号可以自行设置,如下:
enum Season {
SPRING = 3
SUMMER,
AUTUMN,
WINTER
}; //从3开始递增,即: SPRING为3、SUMMER为4、AUTUMN为5、WINTER为6

一般和switch一起使用

中断语句

break

continue

goto

​ 作用:提供无条件跳转功能

​ 语法:

​ 标记名字:

​ goto 标记名字;

数组

​ 概述:是由一批相同类型的元素( eLement)的集合所组成的数据结构。分配一块连续的内存去存储

​ 语法:数据类型 数组名[数组长度];

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
特点:
①任意数据类型都能创建数组
基本数据类型: int、fLoat、 double、 char、string、 boolL等
复合数据类型:结构体、联合体等
指针类型: int*、 char*、 fLoat*等
枚举类型: enum
②数组一旦定义,大小即长度就固定了
③内存连续且有序
④通过sizeof(数组)获取元素个数
⑤元素值可以修改
⑥数组对象记录的是索引为0的地址。
数组对象本身就是引用数据类型,即可以看作指针

​ 静态赋值:int v[] = {1,2,3};

​ 遍历:使用for或者while

字符数组

​ c语言风格定义的字符串,本质就是一个字符数组:

​ 表现一:”he”

​ 表现二:char s[] = “he”;

​ 均可以表现为:char s[] = {‘h’,’e’, ‘\0’};

​ 上述2种C语言风格字符串,其存储机制:将每一个字符,作为1个元素,存入字符数组中。在字符数组中,额外在最后添加一个元素\0 (空字符) ,作为结束标记。

​ 注意:中文一定要使用string类型,不要用char数组。

多维数组

​ 概述:对数组进行嵌套,即数组内的每个元素依旧是数组。

​ 二维数组定义:int v[3][3];

plaintext
1
2
3
4
5
6
遍历二维数组:
for(int i=0; i < sizeof(v1) / sizeof(v1[0]); i++){
for (int j=0; j < sizeof(v1[0]) / sizeof(v1[0][0]); j++){
cout << v1[i][j] << endL;
}
}

数组元素的移除

​ 前景:C++内置并未提供对数组元素进行增加(插入)、移除的功能,需要手动实现(vector容器提供)。

​ 实现:

​ 1.通过new操作符,申请新数组的内存空间,并复制数据到新数组

​ 2.通过delete删除旧数组的空间占用

​ 3.将旧数组指针,指向新数组地址

​ 本质:是将需要的元素复制到了一个新的数组

指针

​ 概述:特定类型数据在内存中的存储地址,即内存地址。指针只是一个逻辑概念,其实际应用是:指针变量

​ 指针变量:即变量p存储了一个变量的地址。并且指针变量本身也有自己的存储地址

​ 本质:协助程序员完成内存的直接操纵

​ 指针也可以像数组那样使用索引,p[0] = *(p+0)

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
定义方法一:先声明,后赋值
变量类型 *指针变量名; //声明
指针变量名 = 内存地址值; //赋值
不推荐,尽量处初始化为空
例如:
int num = 10;
int *p;
p = &num; //&在这里是取地址
cout << p; //输出num变量地址
cout << *p; //输出num变量值
定义方法二:声明和赋值同步
变量类型 *指针变量名 = 内存地址值;

*符号有两种含义:
声明时:*p,表示变量p,是指针变量(存的是内存地址)
使用时:*p,表示取指针p执行内存区域的数据。
&符号表示取变量内存地址,是一个取内存地址的单目操作符。


野指针:被声明但未初始化(赋值)的指针。这个指针会指向随机的内存空间,可能导致未知问题。(声明即分配内存原则)
为避免野指针,应及时初始化,或将指针置为空指针更为安全。
int *p = NULL;
int *p = nullptr;
NULL是C++内置的宏,表示"空、什么都没有"的含义,其本质是0
nuLLptr是C++11标准引入的关键字,表示指针为空

指针运算

plaintext
1
2
3
4
5
指针进行加减运算的结果,和指针指向内存区域的数据类型有关,以加法为例: 
char类型指针+1,地址+1(字节)
int类型指针+1,地址+4(字节)
doubLe类型指针+1,地址+8(字节)
指针+n或-n,即内存地址 +n*类型大小 或 -n*类型大小

指针悬挂

​ 概述:指针指向区域已经被回收(deLete),这种问题称之为指针悬挂。

​ 表现:指针悬挂之后,所指向的值是随机不确定的。容易造成内存安全问题。

​ 解决方法:

​ 不要轻易进行指针之间相互赋值

​ deLete回收空间前,确保此空间100%不再被使用

常量指针

​ 被const修饰的指针就叫常量指针。

​ 共分为已下3中类型的:

​ ①指向const的指针:表示指向区域的数据,是不变的,但可以更换指向。

​ 语法:const 数据类型 *指针; 或者 数据类型 const *指针;

​ 只需确保const在*前面即可。

​ 该类型的也就是只能指向常量也就是const

​ ②const指针:表示指针本身不可更改,但指向的数据可以更改。

​ 语法: 数据类型 * const 指针 = 地址; //必须初始化地址,因为地址不可修改

​ ③指向const的const指针:指针和指向区域的值,都不可更改。

​ 语法:const 数据类型 * const 指针 = 地址; //必须初始化地址,因为地址不可修改

​ 使用场景:

​ ①需要常量的同时也需要动态内存分配的场景

​ ②因为只有使用指针,方可动态分配内存

动态内存分配

​ 概述:即由程序员手动的进行内存空间的分配、内存空间的释放等内存管理操作。

​ 前景:

​ C++代码中,变量、数组等对象的创建,是由C++自动分配内存的,称之为(自动)静态内存分配。

​ (自动)静态内存管理,是不会进行内存空间的自动清理的。 (无垃圾回收机制)。

​ 我们需要手动的管理内存,即手动分配,用完清理。

new

​ 概述:new运算符用于申请并分配内存空间,并提供指向该空间的指针(内存地址)

​ 基本语法:

​ new type //申请普通变量空间

​ new type[n] //申请数组空间

delete

​ 概述:delete运算符用于释放内存,仅可用于new运算符申请的内存区域

​ 基本语法:

​ delete 指针 //删除普通变量空间

​ delete[] 指针 //删除数组空间

注意:常量是不需要删除的,main函数执行完会自动清理。

结构体

​ 概述:结构体(struct) ,是一种用户自定义复合数据类型,可以包含不同类型的不同成员。

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
语法如下:
//声明结构体
struct 结构体类型
{
成员1类型 成员1名称;
....
成员N类型 成员N名称;
};

struct 结构体类型 结构体变量名称; //定义结构体变量(struct关键字可省略)
结构体变量名称.成员名称 //访问结构体成员变量
结构体变量名称 = {赋值1,赋值2....}

默认值
可以在定义的时候直接赋值,代表的意思就是如果不手动赋值,就会默认使用这个值

结构体数组

​ 作用:将自定义的结构体放入到数组中方便维护

​ 创建:struct 结构体名 数组名[元素个数] = { {},{},…{} }

​ 建议使用new进行动态赋值

结构体指针

​ 作用:通过指针访问结构体中的成员

​ 利用操作符 -> 可以通过结构体指针访问结构体属性

plaintext
1
2
3
4
5
6
7
8
9
10
应用一:引入已经存在的结构体变量地址
struct Student *p = &stu; //直接将存在的结构体变量的地址赋值给指针,这种方法没法回收空间
struct Student *p = new Student{"1",2,3}; //这种能回收空间

示例:
//1、创建学生结构体变量
students = {”张三”,18,100 };
//2、通过指针指向结构体变量
student *p = &students;
printf("%students",p->name)

结构体嵌套结构体

​ 作用:结构体中的成员可以是另一个结构体

​ 示例:

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
例如:每个老师辅导一个学员, 一个老师的结构体中,记录一个学生的结构体
//学生结构体定义
struct student
{
string name; //姓名
int age; //年龄
int score; //分数
};

//定义老师结构体
struct teacher{
int id; //教师编号
string name; //教师姓名
int age; //年龄
struct student stu; //辅导的学生
};
//创建老师
teacher t;
t.id = 10000;
t.name =”老王”;
t.age = 50;
t.stu.name =”小王”;
t.stu.age = 20;
t.stu.score = 60;

结构体作为参数

​ 作用:将结构体作为参数向函数中传递

​ 传递方式有两种:

​ ①值传递(不会影响原本的值)

​ ②地址传递(会跟着形参的改变而改变) #定义的时候传入struct student *p,使用的时候传入&s1。用->访问内部属性

​ 注意:使用地址传递,可以通过const防止内部属性得到修改。

函数

​ 概述:是一个提前封装好的、可重复使用的、完成特定功能的独立代码单元。

​ 定义同java一样

函数传参

​ 值传递、地址传递、引用传递

值传递

​ 值传递会复制值,无法对实参本身产生影响

地址传递

​ 地址传递会复制地址,对实参本身可以产生影响,但指针写法麻烦

​ 比如指针,数组等

​ 传入数组一般建议附带数组长度的传入,因为C++不会检查数组的内存边界。

plaintext
1
2
3
4
5
6
7
例如交换两个变量的值
void switch_num(int *a, int *b){
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
引用传递

​ 作用:函数传参时,可以利用引用的技术让形参修饰实参

​ 优点:可以简化指针修改实参

​ 像普通变量那样操作,但对实参本身可以产生影响

​ 使用&表示传递引用对象

plaintext
1
2
3
4
5
6
7
例如交换两个变量的值
void switch_num(int &a, int &b){
int tmp;
tmp = a;
a = b;
b = tmp;
}

返回指针

​ 定义要用指针类型接收,return返回一个指针。

​ 但是要注意函数中的局部变量会在函数执行完自动清理。

​ 需要返回数组时,使用指针接收也是一样的。但不能返回一个局部创建的静态数组。用new动态分配的可以返回,或者全局的静态数组。

函数默认参数

​ 在C++中,函数的形参列表中的形参是可以有默认值的。

​ 语法:返回值类型 函数名(参数 = 默认值) {}

​ 注意事项:

​ 如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值

​ 如果函数声明(即不带函数体的函数声明)有默认参数,函数实现就不能有默认参数。

​ 函数的声明和实现只能有一个有默认参数

函数占位参数

​ C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

​ 语法:返回值类型 函数名(数据类型){}

​ 即形参对应位置只填数据类型,占位参数也可以有默认值。

函数重载

​ 作用:函数名可以相同,提高复用性

​ 函数重载满足条件:

​ ●同一个作用域下

​ ●函数名称相同

​ ●函数参数类型不同或者个数不同或者顺序不同

​ 注意:函数的返回值不可以作为函数重载的条件

当引用做为重载条件时:

​ 如int &a和const int &a。这是可以作为重载的,使用时如果传入变量会调用第一个,使用常量会调用第二个。

函数重载碰到默认参数时:

​ 相当于出现了二义性,因此一定要避免这种情况。

static

​ static表示静态(将内容存入静态内存区域),可以修饰变量和函数

​ 当static修饰函数内部的变量时,可以延长其生命周期到整个程序运行周期。

​ 简单来说就是只需要初始化一次,就给本类共享

进阶

内存分区模型

​ C++程序在执行时,将内存大方向划分为4个区域。

​ 程序运行前的两个区域:

​ 代码区:

​ 存放函数体的二进制代码,由操作系统进行管理的。

​ 存放CPU执行的机器指令

​ 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

​ 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

​ 全局区:

​ 存放全局变量和静态变量以及常量

​ 全局区还包含了常量区,字符串常量和其他常量(const修饰的)也存放在此.

​ 该区域的数据在程序结束后由操作系统释放.

​ 程序运行后的两个区域:

​ 栈区:

​ 由编译器自动分配释放,存放函数的参数值,局部变量等

​ 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

​ 堆区:

​ 由程序员分配和释放,若程序员不释放程序结束时由操作系统回收

​ 在C++中主要利用new在堆区开辟内存

new

​ 作用:C++中利用new操作符在堆区开辟数据。堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete

​ 语法:new 数据类型

​ 利用new创建的数据,会返回该数据对应的类型的指针。

plaintext
1
2
int* p = new int(10);		//表示创建一个变量并为其赋值10,并返回该变量的指针
delete[] arr; //释放数组

引用

​ 概述:对已存在的变量的别名,对引用的各种操作等同于操作被引用变量本身。

​ 语法:数据类型& 引用名 = 被引用变量;

plaintext
1
2
int &b = a;	  //表示给a起了别名b,对b操作也作用在a身上
上述代码本质就是int* const b = &a;

​ 本质:引用的本质在c++内部实现是一个指针常量(即指针指向不可修改,指针指向的值可以修改).

​ 注意事项:

​ 1.引用对象创建后,不可更改(更改指向到其它内存区域)

​ 2.因不可更改,所以引用必须初始化

​ 3.因必须初始化,所以引用不可为空(不可修改)

​ 4.正常的引用需要引一块合法的内存空间,也就是右边的值是一块合法的内存空间。

​ 场景:用于函数形参的声明(引用传参)和返回值使用

引用作函数的返回值

​ 作用:相当于函数赋值的形式给他起了别名

​ 注意:不要返回局部变量引用(局部变量会做一次保留,但也不能返回局部)

​ 用法:以引用会返回的函数的调用可以作为等号左边的值

plaintext
1
2
3
4
5
6
7
int& test(){
static int a = 10;
return a;
}
int &b = test(); //现在a的别名就是b了。

test() = 1000; //现在a的值变为了1000

常量引用

​ 作用:常量引用主要用来修饰形参,防止误操作

​ 场景:在函数形参列表中,可以加const修饰形参,防止形参改变实参。

​ test(const int& a){}

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
语法:
class 类名{
访问权限:
成员变量;
成员方法;
};

int main(){
类名 对象名;
对象属性赋值;
return 0;
}

访问权限

​ public:公共权限,类内外都可以访问

​ protected:保护权限,类内可以访问,类外不能,子类可以访问父类的保护内容

​ private:私有权限,类内可以访问,类外不能,子类不可以访问父类私有的

​ 一般成员属性会做私有化设置,并给出get和set方法

struct和class区别

​ 在C++中struct和class唯一的区别就在于默认的访问权限不同

​ 区别:

​ ●struct 默认权限为公共

​ ●class 默认权限为私有

对象

对象的初始化和清理

​ 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

​ 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

​ 构造函数语法:类名(){}

​ 1.构造函数,没有返回值也不写void

​ 2.函数名称与类名相同

​ 3.构造函数可以有参数,因此可以发生重载

​ 4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

​ 析构函数语法:~类名(){}

​ 1.析构函数,没有返回值也不写void

​ 2.函数名称与类名相同,在名称前加上符号~

​ 3.析构函数不可以有参数,因此不可以发生重载

​ 4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

​ 注意:构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构

构造函数

​ 两种分类方式:

​ 按参数分为:有参构造和无参构造

​ 按类型分为:普通构造和拷贝构造(即把自身类型的对象作为参数传入,往往是const修饰)

​ 三种调用方式:

​ 括号法

plaintext
1
2
3
4
即创建对象时直接通过括号进行选择构造函数的调用。创建对象时,调用默认构造函数时,尽量省掉(),因为会与函数的声明冲突。
Person p1;
Person p2(10);
Person p3(p2);

​ 显示法

plaintext
1
2
3
4
5
Person p1;
Person p2 = Person(10);
Person p3 = Person(p2);
Person(10); //这是一个匿名对象,使用完直接回收
/不要利用拷贝构造函数初始化匿名对象,即不能Person(p2); 编译器会默认认为是创建p2对象

​ 隐式转换法

plaintext
1
Person p1 = 10;			//相当于Person p2 = Person(10);

拷贝构造函数的使用时机

​ 拷贝构造函数的对象参数往往用const修饰,const Person& p

​ ●使用一个已经创建完毕的对象来初始化一个新对象

​ ●值传递的方式给函数参数传值(即拷贝一份不对原有进行修改)

​ ●以值方式返回局部对象(即拷贝一份不对原有进行修改)

构造函数调用规则

默认情况下,C++编译器至少给一个类添加3个函数

​ 1.默认构造函数(无参,函数体为空)

​ 2.默认析构函数(无参,函数体为空)

​ 3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

​ ●如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造

​ ●如果用户定义拷贝构造函数,C++不会再提供其他构造函数

深拷贝和浅拷贝

​ 浅拷贝:简单的赋值拷贝操作。

​ 一般编译器提供的拷贝构造函数是浅拷贝。

​ 浅拷贝很容易造成堆区内存重复释放。

​ 深拷贝:在堆区重新申请空间,进行拷贝操作

​ 自己实现一个拷贝函数解决浅拷贝的问题

​ 要重新开辟一个堆空间存储拷贝的东西。

初始化列表

​ 作用:C++提供了初始化列表语法,用来初始化属性

​ 语法:构造函数(): 属性1(值1),属性2(值2) … {}

​ 前言:以前的初始化都是利用构造函数形参传入,进行赋值。

plaintext
1
2
3
Person(int a,int b):m_A(a),m_B(b){		//也可以直接赋具体值

}

类对象作为类成员

​ C++类中的成员可以是另一个类的对象,我们称该成员为对象成员

plaintext
1
2
3
4
5
6
7
8

class A {}
class B
A a;
}
B类中有对象A作为成员,A为对象成员
执行顺序:
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身。而析构的顺序恰恰相反。

静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

​ 静态成员分为:

​ 静态成员变量

​ 特点1:所有对象共享同一份数据

​ 特点2:在编译阶段分配内存

​ 特点3:类内声明,类外初始化(一定要注意)

​ 访问方式:共享之后不属于某个对象,可以通过对象进行访问,也可以通过类名进行访问(如类名::属性名)

​ 访问权限同样适用

​ 静态成员函数

​ 特点1:所有对象共享同一个函数

​ 特点2:静态成员函数只能访问静态成员变量(因为无法区分到底是哪个对象的)

​ 访问方式:可以通过对象进行访问,也可以通过类名进行访问(如类名::方法名)

​ 访问权限同样适用

对象模型和this指针

成员变量和成员函数分开存储

​ 在C++中,类内的成员变量和成员函数分开存储

​ 只有非静态成员变量才属于类的对象上

​ C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置。

this指针

​ 在C+ +中成员变量和成员函数是分开存储的

​ 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码

​ 那么问题是:这一块代码是如何区分那个对象调用自己的呢?

​ C+ +通过提供特殊的对象指针, this指针,解决上述问题。

​ 概述:this指针指向被调用的成员函数所属的对象

​ this指针是隐含每一个非静态成员函数内的一种指针

​ this指针的用途:

​ 当形参和成员变量同名时,可用this指针来区分(如this->age = age;)

​ 在类的非静态成员函数中返回对象本身(便于链式编程),可使用return *this,同时函数类型要用引用返回(如Person&,以引用返回是为了确保能对原本的一直修改,而不是对浅拷贝出来的修改)

空指针访问成员函数

​ C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

​ 如果用到this指针,需要加以判断保证代码的健壮性。(因为this在此时是空肯定会报错)

const修饰成员函数

​ 常函数:

​ ●成员函数后加const后我们称为这个函数为常函数

​ ●常函数内不可以修改成员属性

​ ●成员属性声明时加关键字mutable后,在常函数中依然可以修改

​ 常对象:

​ ●声明对象前加const称该对象为常对象

​ ●常对象只能调用常函数

​ ●常对象的属性也不能修改,除非加关键字mutable

plaintext
1
2
3
void showPerson() const{

}

友元

​ 作用:就是让一个函数或者类访问另一个类中私有成员

​ 友元的关键字为friend,在类内实现语句

​ 友元的三种实现:

​ ●全局函数做友元

​ ●类做友元

​ ●成员函数做友元

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
全局函数做友元
class Person{
friend void visitFriend(Person *p);
};
void visitFriend(Person *p){
等等
}
此时在类外,这个函数就能访问到Person的私有东西了。

类做友元
class Person{
friend class Person1;
};

成员函数做友元
// 类内定义的函数就叫成员函数,类外需要先在类内声明函数,然后通过作用域的语法在类外定义成员函数如void Peron::visitFriend(){}
class Person{
friend void Person1::visitFriend();
};

运算符重载

​ 概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

​ 语法:自定义一个名为(operator运算符) 的函数,定义你要进行的操作,

加号运算符重载

​ 作用:实现两个自定义数据类型相加的运算

​ 前景:默认情况下想要实现两个自定义类型的某些属性相加,那我们需要自定义一个函数去实现。现在可以直接使用运算符重载实现。

​ 方法一:通过成员函数重载

plaintext
1
2
3
4
5
6
7
8
9
//即在类内定义名称为operator+函数
Person operator+(Person &p){
Person temp;
temp.m_A = this->m_A+p.m_A;
temp.m_B = this->m_B+p.m_B;
return temp;
}
//然后在使用
Person p3 = p1 + p2; //本质是Person p3 = p1.operator+(p2);

​ 方法二:通过全局函数重载

plaintext
1
2
3
4
5
6
7
8
9
//即在类外全局区域定义名称为operator+函数
Person operator+(Person &p1, Person &p2){
Person temp;
temp.m_A = p1.m_A+p2.m_A;
temp.m_B = p1.m_B+p2.m_B;
return temp;
}
//然后在使用
Person p3 = p1 + p2; //Person p3 = operator+(p1,p2);

注意:

​ 运算符重载进行operator定义时,也可以函数重载去应对不同情况

​ 对于内置的数据类型的表达式的的运算符是不可能改变的

​ 不要滥用运算符重载

左移运算符重载

​ 作用:重载左移运算符配合友元可以实现输出自定义数据类型

plaintext
1
2
3
4
5
6
不能使用成员函数重载,因为无法达到cout << 对象; 的效果
方法一:利用全局函数重载
ostream& operator<<(ostream &cout, Person &p){
cout<<p.name<<p.age;
return cout; //返回cout是为了能够链式编程
}

递增运算符重载

​ 作用:通过重载递增运算符,实现自己的整型数据

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
对前置的++进行重载
类名& oprator++(){ //引用接收是能够重复对自己进行操作
类名的对象.age++;
return *this; //将自身返回
}

对后置的++进行重载
类名& oprator++(int){ //加int占位参数,表示这是后置的++重载
类 类名的对象 = *this; //先记录递增之前的值
对象的属性++; //递增
return temp; //将记录的结果返回
}

赋值运算符重载

​ C++编译器至少给一个类添加4个函数

​ 1.默认构造函数(无参,函数体为空)

​ 2.默认析构函数(无参,函数体为空)

​ 3.默认拷贝构造函数,对属性进行值拷贝

​ 4.赋值运算符operator=,对属性进行值拷贝(浅拷贝)

​ 如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
//利用成员函数重载
Person& operator=(Person &p){
//编译器是提供浅拷贝
//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL){
delete m_Age;
m_Age = NULL;
}
//深拷贝
m_Age = new int(*p.m_Age);
return *this; //返回自身,方便链式编程
}

关系运算符重载

plaintext
1
2
3
4
5
6
7
8
9
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
//利用成员函数重载
bool operator==(Person &p){
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age){
return true;
}else{
return false;
}
}

函数调用运算符重载

​ 函数调用运算符()也可以重载

​ 由于重载后使用的方式非常像函数的调用,因此称为仿函数

​ 仿函数没有固定写法,非常灵活

plaintext
1
2
3
4
5
6
7
8
9
10
利用成员函数重载
//重载函数调用运算符
void operator() (string test){
cout << test << end1 ;
}
//调用
对应类的对象("Hello");

//匿名函数对象调用
类名()("Hello") //类名()就是使用构造函数创建了一个匿名对象

继承

​ 语法:class 子类 : 继承方式 父类

​ 继承方式:公共继承、保护继承、私有继承

简单来说就是:

​ 父类中所有非静态成员属性都会被子类继承下去,3种继承方式都只能继承下来,但是访问不到的。

​ 若为公共继承,则父类是什么权限的属性,子类同样也是。

​ 若为保护继承,则继承下来的父类属性都变为了保护权限。

​ 若为私有继承,则继承下来的父类属性都变为了私有权限。

继承中构造和析构顺序

​ 子类继承父类后,当创建子类对象,也会调用父类的构造函数

​ 总结:继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

继承中同名成员处理方式

​ 访问子类同名成员,直接访问即可

​ 访问父类同名成员,需要加作用域(在属性前加 父类::)

​ 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,要想访问到父类需要添加作用域

多继承语法

​ C++允许一个类继承多个类

​ 语法:class 子类 : 继承方式 父类1,继承方式 父类2…

​ 多继承可能会引发父类中有同名成员出现,需要加作用域区分

​ C++实际开发中不建议用多继承

菱形继承

​ 菱形继承概念:

​ 两个子类继承同一个父类

​ 又有某个类同时继承者两个子类

​ 这种继承被称为菱形继承,或者钻石继承

​ 问题:这种继承会造成属性重复继承,造成资源浪费

​ 解决:利用虚继承,在继承方式前加virtual。本质上是用了虚指针,找到了唯一变量

多态

多态分为两类

​ ●静态多态函数重载和运算符重载属于静态多态,复用函数名

​ ●动态多态:派生类和虚函数(即在函数定义前加virtual)实现运行时多态

静态多态和动态多态区别:

​ ●静态多态的函数地址早绑定 - 即编译阶段确定函数地址

​ ●动态多态的函数地址晚绑定 - 即运行阶段确定函数地址

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在父类的函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。

动态多态满足条件
有继承关系
子类重写父类的虚函数(子类无需再带virtual)
动态多态使用
父类的指针或者引用指向子类对象
如Animal& a = cat;

多态的本质:是虚函数内部有了一个虚函数指针,指针指向虚函数表,表中记录着.....。
多态的优点
●代码组织结构清晰
●可读性强
●利于前期和后期的扩展以及维护

纯虚函数和抽象类

​ 在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数

​ 纯虚函数语法: virtual 返回值类型 函数名(参数列表) = 0 ;

​ 只要类中有了纯虚函数,这个类也称为抽象类。

​ 抽象类特点:

​ ●无法实例化对象

​ ●子类必须重写抽象类中的纯虚函数,否则也属于抽象类

虚析构和纯虚析构

​ 问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时(也就是父类的析构函数调用时)无法调用到子类的析构代码

​ 解决方式:将父类中的析构函数改为虚析构或者纯虚析构

​ 虚析构和纯虚析构共性:

​ ●可以解决父类指针释放子类对象

​ ●都需要有具体的函数实现

​ 虚析构和纯虚析构区别:

​ ●如果是纯虚析构,该类属于抽象类,无法实例化对象

plaintext
1
2
3
4
5
6
7
8
9
10
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0; //在类内声明析构
类名::~类名(){} //这个是在类外定义

总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类

文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化

C++中对文件操作需要包含头文件< fstream >

文件类型分为两种:

​ 1.文本文件:文件以文本的ASCII码形式存储在计算机中

​ 2.二进制文件:文件以文本的二_进制形式存储在计算机中,用户-般不能直接读懂它们

操作文件的三大类:

  1. ofstream:写操作
  2. ifstream:读操作
  3. fstream:读写操作

文本文件

写文件
plaintext
1
2
3
4
5
6
7
8
9
10
11
写文件步骤如下:
1.包含头文件
#include <fstream>
2.创建流对象
ofstream ofs;
3.打开文件
ofs.open("文件路径",打开方式);
4.写数据
ofs<< "写入的数据";
5.关闭文件
ofs.close();
plaintext
1
2
3
4
5
6
7
8
文件打开方式
ios:in:为读文件而打开文件
ios::out:为写文件而打开文件
ios::ate:初始位置直接移动到文件尾
ios::app:追加方式写文件
ios::trunc:如果文件存在先删除,再创建
ios::binary:二进制方式
注意:文件打开方式可以多个配合使用,中间使用|操作符隔开
读文件
plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
读文件步骤如下:
1.包含头文件
#incude <fstream>
2.创建流对象
ifstream ifs;
3.打开文件并判断文件是否打开成功
ifs.open("文件路径" ,打开方式);
可以使用is_open函数看是否打开成功
4.读数据
四种方式读。
5.关闭文件
ifs.close();

第一种方式读:
char buf[1024] ={0};
while(ifs >> buf){
cout << buf << end1 ;
}
第二种方式读:
char buf[1024] ={0};
while( ifs.getline( buf,sizeof(buf) ) ){
cout << buf << end1 ;
}
第三种方式读:
string buf;
while(getline( ifs,buf) ){ //这个getline是全局的getline不是那个ifs的方法
cout << buf << end1 ;
}
第四种方式读:
char c;
while( (c = ifs.get() ) !=EOF ){ //添加判断条件,看看是否读到尾
cout << c;
}

二进制文件

​ 以二进制的方式对文件进行读写操作

​ 打开方式要指定为ios::binary

写文件
plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
二进制方式写文件主要利用流对象调用成员函数write
函数原型: ostream& write(const char* buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

写文件步骤如下:
1.包含头文件
#include <fstream>
2.创建流对象
ofstream ofs("文件",打开方式);
3.写数据
Person p = {"张三", 18}
ofs.write( (const char *)&p, sizeof(p));
4.关闭文件
ofs.close();
读文件
plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
二进制方式读文件主要利用流对象调用成员函数read
函数原型: istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

读文件步骤:
1.包含头文件
#incude <fstream>
2.创建流对象
ifstream ifs;
3.打开文件并判断文件是否打开成功
ifs.open("文件路径" ,打开方式);
可以使用is_open函数看是否打开成功
4.读数据
Person p;
ifs.read( (char *)&p, sizeof(p));
5.关闭文件
ifs.close();

模板

​ 前言:c++中另一种编程思想叫泛型编程,主要就是利用模板技术

​ 模板机制:函数模板和类模板

​ 模板的特点

​ ●模板不可以直接使用,它只是一个框架

​ ●模板的通用并不是万能的

函数模板

plaintext
1
2
3
4
5
6
7
8
9
10
函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
语法:
①在函数声明上一行先声明模板,告诉编译器T是通用类型不要报错。template<typename T>
②再进行函数声明或定义,如void mySwap(T &a, T &b){}
③使用。mySwap(a,b)或mySwap<int>(a,b)
解释:
template:声明创建模板
typename:表面其后面的符号是一种数据类型,可以用class代替
T:通用的数据类型,名称可以替换,通常为大写字母

​ 注意事项:

​ ●自动类型推导,必须推导出一致的数据类型T,才可以使用

​ ●模板必须要确定出T的数据类型,才可以使用

普通函数与函数模板区别

​ ●普通函数调用时可以发生自动类型转换(隐式类型转换)

​ ●函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换

​ ●如果利用显示指定类型的方式,可以发生隐式类型转换

普通函数与函数模板的调用规则如下

​ 1.如果函数模板和普通函数都可以实现,优先调用普通函数

​ 2.可以通过空模板参数列表来强制调用函数模板,如函数<>(a,b)

​ 3.函数模板也可以发生重载

​ 4.如果函数模板可以产生更好的匹配,优先调用函数模板

模板的局限性

​ 前景:有时特定的数据类型并不能直接通过泛型表示,需要加以修改

​ 方法一:对模板函数内的运算符进行重载(太麻烦)

​ 方法二:利用具体化Person的版本实现代码,具体化优先调用

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
template<> bool myCompare (Person &p1, Person &p2){
if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age){
return true;
}else{
return false;
}
}
总结:
●利用具体化的模板,可以解决自定义类型的通用化
●学习模板并不是为了写模板,而是在STL能够运用系统提供的模板


类模板

​ 作用:建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表。

plaintext
1
2
3
4
5
6
7
8
语法:
①先声明模板template<typename T,class R>
②然后定义类,其中函数定义时的形参用T和R去替代
解释:
template:声明创建模板
typename:表面其后面的符号是一种数据类型,可以用class代替
T:通用的数据类型,名称可以替换,通常为大写字母

类模板与函数模板区别

​ 1.类模板没有自动类型推导的使用方式,类模板只能显示指定类型,即加上<>表明类型

​ 2.类模板在模板参数列表中可以有默认参数,如template <class T = int>

类模板中成员函数创建时机

​ 类模板中成员函数和普通类中成员函数创建时机是有区别的:

​ ●普通类中的成员函数一开始就可以创建

​ ●类模板中的成员函数在调用时才创建

类模板对象做函数参数

​ 类模板实例化出的对象,向函数传参的方式

​ 1.指定传入的类型:直接显示对象的数据类型(用的最多)

plaintext
1
2
对象初始化:Person<string,int>p("sun",10);
在函数形参中:Person<string,int>&p

​ 2.参数模板化:将对象中的参数变为模板进行传递

​ 3.整个类模板化:将这个对象类型,模板化进行传递

类模板与继承

当类模板碰到继承时,需要注意一下几点:

​ ●当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型

plaintext
1
class Son : public Base<int>{};

​ ●如果不指定,编译器无法给子类分配内存

​ ●如果想灵活指定出父类中T的类型,子类也需变为类模板

类模板成员函数类外实现

​ 前景:一般情况下类内都只是函数声明,实现是在类外

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
//构造函数类外实现
template<class Tl,class T2>
Person<T1,T2>::Person(T1 name, T2 age){ //普通函数的类外实现不用加<T1,T2>
this->m_Name = name;
this->m_Age = age;
}

//成员函数类外实现
template<class Tl,class T2>
void Person<T1,T2>::showPerson(T1 name, T2 age){
cout<<"你好";
}
类模板分文件编写
plaintext
1
2
3
4
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
●解决方式1:直接包含.cpp源文件(但不建议)
●解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp, hpp是约定的名称,并不是强制
类模板和友元
plaintext
1
2
全局函数类内实现:直接在类内声明友元即可
全局函数类外实现:需要提前让编译器知道全局函数的存在(可以把函数放到上方,还需要把类和类模板声明到上方)

STL

​ 概念

​ STL(Standard Template Library,标准模板库)

​ STL从广义上分为:容器(container),算法(algorithm),迭代器(iterator)

​ 容器和算法之间通过迭代器进行无缝连接

​ STL几乎所有的代码都采用了模板类或者模板函数

六大组件

​ STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。

​ 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。

plaintext
1
2
3
4
5
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组、链表、树、栈、队列、集合、映射表等
这些容器分为序列式容器和关联式容器
序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置
关联式容器:二叉树结构体,各元素之间没有严格上的物理上的顺序关系

​ 算法:常用的各种算法,如sort、find、copy、for_each等

plaintext
1
2
3
4
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)
算法分为:质变算法和非质变算法
质变算法:是指运算过程中会更改区间内元素的内容。例如拷贝、查找、删除等等
非质变算法:是指在运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等

​ 迭代器:扮演了容器与算法之间的胶合剂

plaintext
1
2
3
4
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的迭代器。
迭代器的使用非常类似于指针,初学阶段我们可以先理解迭代器为指针。
常用的容器中迭代器种类为双向迭代器,和随机访问迭代器。

​ 仿函数:行为类似函数,可作为算法的某种策略

​ 适配器:一种用来修饰容器或者仿函数或迭代器结构的东西。

​ 空间配置器:负责空间的配置与管理。

常用容器

vector

​ 概述:向量,以顺序存储的线性表。vector为可变长数组动态数组,定义的vector数组可以随时添加数值和删除元素。

​ 头文件:#include<vector>

​ 一维初始化:

plaintext
1
2
3
4
5
6
vector<数据类型> a; //定义了一个名为a的一维数组,数组可以存储int/double/结构体等类型数据

//指定长度和初始值的初始化:
vector<int> v(n);// 定义一个长度为n的数组,初始值默认为0,下标范围[0, n - 1]
vector<int> v(n, 1);// v[0] 到 v[n - 1]所有的元素初始值均为1
//注意:指定数组长度之后(指定长度后的数组就相当于正常的数组了)

​ 二维初始化:

plaintext
1
2
3
4
5
6
7
//初始化二维均可变长数组
vector<vector<int>> array(M, vector<int>(N));//定义一个行为M,列为N的二维数组
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
cin >> array[i][j];
}
}

​ 添加元素到尾部:vector.push_back(值);

​ 获取元素数量:即长度,vector.size();

​ 删除最后一个数据:vector.pop_back()

​ 是否为空,为空返回真,反之返回假:vector.empty()

​ 返回首元素的地址:begin()

​ 返回最后一个元素的后一个位置的地址:end()

​ 将数据插到索引处,原本的要往后移:insert(vector.begin() + 索引,值) //索引是从0开始

​ 将索引处的数据删除掉:erase(vector.begin() + 索引)

​ 排序:

plaintext
1
2
3
4
vector<int> a(n + 1);
sort(a.begin() + 1, a.end()); // 对[1, n]区间进行从小到大排序
end()返回的是最后一个元素的后一个位置的地址,不是最后一个元素的地址,所有STL容器均是如此
begin()返回首元素的迭代器(通俗来说就是地址)

练习1

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
题目描述:一个数如果恰好等于它的各个因子(该数本身除外)之和,如6=3+2+1,那么称该数为“完数”;若因子之和大于该数,则称其为“盈数”。求出2到60之间的所有“完数”和“盈数”。
#include<iostream>
#include<vector>
#include<cstdio>
using namespace std;

int Sum(int i) {
int sum = 0;
//首先先判断每一个数他的因子都有哪些,其次再考虑他们因子之和等于还是大于本身
//因子也就是能被数本身整除的数,从1到n-1
for (int j = 1; j < i; j++)
{
if (i%j == 0)
{
sum = sum + j;
}
}
return sum;
}
int main() {
vector<int> evec; //完数
vector<int> gvec; //盈数
for (int i = 2; i <= 60; i++) //从2一直到60,依次判断是完数还是盈数
{
if (Sum(i) == i) //若是完数,则添加到完数的数组
{
evec.push_back(i);
}
else if (Sum(i) > i) { ////若是盈数,则添加到盈数的数组
gvec.push_back(i);
}
}
for (int i = 0; i < evec.size(); i++) //输出完数
{
printf(" %d", evec[i]);
}
return 0;
}

string

deque

stack

​ 特点:先进后出,栈顶top进出

​ 头文件:#include<stack>

​ 定义:stack<数据类型> 栈名;

​ 方法:

​ ①size():返回栈元素个数

​ ②push():加入元素到栈中

​ ③pop():弹出栈顶元素

​ ④top():获取栈顶元素的内容

​ ⑤empty():判空

queue

​ 特点:先进先出,对头出队,队尾入队

​ 场景:公平的等待,广度优先遍历

​ 头文件:#include<queue>

​ 定义:queue<数据类型> 队列名;

​ 方法:

​ ①push:入队

​ ②pop:出队

​ ③empty:判断是否为空

​ ④front:队首元素

list

set

Map

​ 头文件:#include<map>

​ 初始化:map<数据类型, 数据类型> mp; //数据类型如int,string,double

​ 特性:

plaintext
1
2
3
map会按照键的顺序从小到大自动排序,键的类型必须可以比较大小
map:内部用红黑树实现,具有自动排序(按键从小到大)功能。
unordered_map:内部用哈希表实现,内部元素无序杂乱。

​ 添加元素:

plaintext
1
2
mp.insert(pair<string,string>("fruit","水果"));
mp.insert({"hahaha","wawawa"});

​ 遍历访问:

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//方式一:迭代器访问
map<string,string>::iterator it;
for(it = mp.begin(); it != mp.end(); it++) {
// 键 值
// it是结构体指针访问所以要用 -> 访问
cout << it->first << " " << it->second << "\n";
//*it是结构体变量 访问要用 . 访问
//cout<<(*it).first<<" "<<(*it).second;
}

//方式二,智能指针访问
for(auto i : mp)
cout << i.first << " " << i.second << endl;//键,值

//方式二,对指定单个元素访问
map<char,int>::iterator it = mp.find('a');
cout << it -> first << " " << it->second << "\n";


//正向遍历
map<int,int> mp;
mp[1] = 2;
mp[2] = 3;
mp[3] = 4;
auto it = mp.begin();
while(it != mp.end()) {
cout << it->first << " " << it->second << "\n";
it ++;
}

方法:

plaintext
1
2
3
4
5
6
7
8
9
mp.erase(key) //根据映射的键删除键和值
mp.size() //返回映射的对数
mp.insert() //插入元素,插入时要构造键值对
mp.empty() //如果map为空,返回true,否则返回false
mp.count(key) //查看元素是否存在,因为map中键是唯一的,所以存在返回1,不存在返回0
mp.begin():返回指向map第一个元素的迭代器(地址)
mp.end() //返回指向map尾部的迭代器(最后一个元素的下一个地址)
mp.find(key) //返回键为key的映射的迭代器
注意:用find函数来定位数据出现位置,它返回一个迭代器。当数据存在时,返回数据所在位置的迭代器,数据不存在时,返回mp.end()

递归

​ 核心:如何把问题为n的规模用n-1的规模去表示

plaintext
1
2
3
4
5
6
如:传统3根柱子n个盘子的汉诺塔问题
假设上面的n-1个小的盘子你能够从A柱移到任何柱子,那递归步骤如下:
①先将n-1个小的移动到B柱子,共F(n-1)规模次
②然后将A柱唯一一个大的移到C柱,1次
③最后再将B柱的n-1个盘子移到C柱子完成。F(n-1)规模次
即2*F(n-1) + 1;