C++基础查漏补缺

目录

快速回顾一下, 简单的就不记了.

1.常量

作用:用于记录程序中不可更改的数据

C++定义常量两种方式

  1. #define 宏常量: #define 常量名 常量值

    • 通常在文件上方定义,表示一个常量
  2. const修饰的变量 const 数据类型 常量名 = 常量值

    • 通常在变量定义前加关键字const,修饰该变量为常量,不可修改
 1//1、宏常量
 2#define day 7
 3
 4int main() {
 5
 6	cout << "一周里总共有 " << day << " 天" << endl;
 7	//day = 8;  //报错,宏常量不可以修改
 8
 9	//2、const修饰变量
10	const int month = 12;
11	cout << "一年里总共有 " << month << " 个月份" << endl;
12	//month = 24; //报错,常量是不可以修改的
13	
14	
15	system("pause");
16
17	return 0;
18}

2.数据类型

整型

C++中能够表示整型的类型有以下几种方式,区别在于所占内存空间不同

数据类型 占用空间 取值范围
short(短整型) 2字节 (-2^15 ~ 2^15-1)
int(整型) 4字节 (-2^31 ~ 2^31-1)
long(长整形) Windows为4字节,Linux为4字节(32位),8字节(64位) (-2^31 ~ 2^31-1)
long long(长长整形) 8字节 (-2^63 ~ 2^63-1)

sizeof关键字: 利用sizeof关键字可以统计数据类型所占内存大小

sizeof( 数据类型 / 变量)

浮点型

浮点型变量分为两种:

  1. 单精度float
  2. 双精度double

两者的区别在于表示的有效数字范围不同。

数据类型 占用空间 有效数字范围
float 4字节 7位有效数字
double 8字节 15~16位有效数字

字符型

字符型变量用于显示单个字符

char ch = 'a';

注意1:在显示字符型变量时,用单引号将字符括起来,不要用双引号

注意2:单引号内只能有一个字符,不可以是字符串

  • C和C++中字符型变量只占用1个字节。
  • 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元

字符串

两种风格

  1. C风格字符串char 变量名[] = "字符串值"

注意:C风格的字符串要用双引号括起来

  1. C++风格字符串string 变量名 = "字符串值"

注意:C++风格字符串,需要加入头文件==#include<string>==

布尔值

bool类型只有两个值:

  • true — 真(本质是1)
  • false — 假(本质是0)

bool类型占==1个字节==大小

数据的输入: cin >> 变量

3.运算符

算术运算符

运算符 术语 示例 结果
+ 正号 +3 3
- 负号 -3 -3
+ 10 + 5 15
- 10 - 5 5
* 10 * 5 50
/ 10 / 5 2
% 取模(取余) 10 % 3 1
++ 前置递增 a=2; b=++a; a=3; b=3;
++ 后置递增 a=2; b=a++; a=3; b=2;
前置递减 a=2; b=–a; a=1; b=1;
后置递减 a=2; b=a–; a=1; b=2;

赋值运算符

运算符 术语 示例 结果
= 赋值 a=2; b=3; a=2; b=3;
+= 加等于 a=0; a+=2; a=2;
-= 减等于 a=5; a-=3; a=2;
*= 乘等于 a=2; a*=2; a=4;
/= 除等于 a=4; a/=2; a=2;
%= 模等于 a=3; a%2; a=1;

比较运算符

运算符 术语 示例 结果
== 相等于 4 == 3 0
!= 不等于 4 != 3 1
< 小于 4 < 3 0
> 大于 4 > 3 1
<= 小于等于 4 <= 3 0
>= 大于等于 4 >= 1 1

逻辑运算符

运算符 术语 示例 结果
! !a 如果a为假,则!a为真; 如果a为真,则!a为假。
&& a && b 如果a和b都为真,则结果为真,否则为假。
|| a || b 如果a和b有一个为真,则结果为真,二者都为假时,结果为假。

4.程序流程结构

if

三元运算符

switch

语法:

 1switch(表达式)
 2
 3{
 4
 5	case 结果1:执行语句;break;
 6
 7	case 结果2:执行语句;break;
 8
 9	...
10
11	default:执行语句;break;
12
13}
14

示例

 1int main() {
 2
 3	//请给电影评分 
 4	//10 ~ 9   经典   
 5	// 8 ~ 7   非常好
 6	// 6 ~ 5   一般
 7	// 5分以下 烂片
 8
 9	int score = 0;
10	cout << "请给电影打分" << endl;
11	cin >> score;
12
13	switch (score)
14	{
15	case 10:
16	case 9:
17		cout << "经典" << endl;
18		break;
19	case 8:
20		cout << "非常好" << endl;
21		break;
22	case 7:
23	case 6:
24		cout << "一般" << endl;
25		break;
26	default:
27		cout << "烂片" << endl;
28		break;
29	}
30
31	system("pause");
32
33	return 0;
34}

注意1:switch语句中表达式类型只能是整型或者字符型

注意2:case里如果没有break,那么程序会一直向下执行

总结:与if语句比,对于多条件判断时,switch的结构清晰,执行效率高,缺点是switch不可以判断区间

while

do…while

for

跳转语句

break

作用: 用于跳出==选择结构==或者==循环结构==

break使用的时机:

  • 出现在switch条件语句中,作用是终止case并跳出switch
  • 出现在循环语句中,作用是跳出当前的循环语句
  • 出现在嵌套循环中,跳出最近的内层循环语句

continue

**作用:**在==循环语句==中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环

goto

**作用:**可以无条件跳转语句

语法: goto 标记;

**解释:**如果标记的名称存在,执行到goto语句时,会跳转到标记的位置

示例:

 1int main() {
 2
 3	cout << "1" << endl;
 4
 5	goto FLAG;
 6
 7	cout << "2" << endl;
 8	cout << "3" << endl;
 9	cout << "4" << endl;
10
11	FLAG:
12
13	cout << "5" << endl;
14	
15	system("pause");
16
17	return 0;
18}

注意:在程序中不建议使用goto语句,以免造成程序流程混乱

5.数组

一维数组

一维数组定义的三种方式:

  1. 数据类型 数组名[ 数组长度 ];
  2. 数据类型 数组名[ 数组长度 ] = { 值1,值2 ...}; (如果{}内不足10个数据,剩余数据用0补全)
  3. 数据类型 数组名[ ] = { 值1,值2 ...};

一维数组名称的用途

  1. 可以统计整个数组在内存中的长度
  2. 可以获取数组在内存中的首地址

示例:

 1int main() {
 2
 3	//数组名用途
 4	//1、可以获取整个数组占用内存空间大小
 5	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 6
 7	cout << "整个数组所占内存空间为: " << sizeof(arr) << endl;
 8	cout << "每个元素所占内存空间为: " << sizeof(arr[0]) << endl;
 9	cout << "数组的元素个数为: " << sizeof(arr) / sizeof(arr[0]) << endl;
10
11	//2、可以通过数组名获取到数组首地址
12	cout << "数组首地址为: " << (int)arr << endl;
13	cout << "数组中第一个元素地址为: " << (int)&arr[0] << endl;
14	cout << "数组中第二个元素地址为: " << (int)&arr[1] << endl;
15
16	//arr = 100; 错误,数组名是常量,因此不可以赋值
17
18
19	system("pause");
20
21	return 0;
22}

注意:数组名是常量,不可以赋值

总结1:直接打印数组名,可以查看数组所占内存的首地址

总结2:对数组名进行sizeof,可以获取整个数组占内存空间的大小

二维数组

二维数组定义的四种方式:

  1. 数据类型 数组名[ 行数 ][ 列数 ];
  2. 数据类型 数组名[ 行数 ][ 列数 ] = { {数据1,数据2 } ,{数据3,数据4 } };
  3. 数据类型 数组名[ 行数 ][ 列数 ] = { 数据1,数据2,数据3,数据4};
  4. 数据类型 数组名[ ][ 列数 ] = { 数据1,数据2,数据3,数据4};

二维数组数组名:

  • 查看二维数组所占内存空间
  • 获取二维数组首地址

示例:

 1int main() {
 2
 3	//二维数组数组名
 4	int arr[2][3] =
 5	{
 6		{1,2,3},
 7		{4,5,6}
 8	};
 9
10	cout << "二维数组大小: " << sizeof(arr) << endl;
11	cout << "二维数组一行大小: " << sizeof(arr[0]) << endl;
12	cout << "二维数组元素大小: " << sizeof(arr[0][0]) << endl;
13
14	cout << "二维数组行数: " << sizeof(arr) / sizeof(arr[0]) << endl;
15	cout << "二维数组列数: " << sizeof(arr[0]) / sizeof(arr[0][0]) << endl;
16
17	//地址
18	cout << "二维数组首地址:" << arr << endl;
19	cout << "二维数组第一行地址:" << arr[0] << endl;
20	cout << "二维数组第二行地址:" << arr[1] << endl;
21
22	cout << "二维数组第一个元素地址:" << &arr[0][0] << endl;
23	cout << "二维数组第二个元素地址:" << &arr[0][1] << endl;
24
25	system("pause");
26
27	return 0;
28}

总结1:二维数组名就是这个数组的首地址

总结2:对二维数组名进行sizeof时,可以获取整个二维数组占用的内存空间大小

6.函数

函数的定义

语法:

1返回值类型 函数名 (参数列表)
2{
3
4       函数体语句
5
6       return表达式
7
8}

函数的调用

值传递

  • 所谓值传递,就是函数调用时实参将数值传入给形参
  • 值传递时,如果形参发生,并不会影响实参

函数的常见样式

函数的声明

作用: 告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。

  • 函数的声明可以多次,但是函数的定义只能有一次

函数的分文件编写

函数分文件编写一般有4个步骤

  1. 创建后缀名为.h的头文件
  2. 创建后缀名为.cpp的源文件
  3. 在头文件中写函数的声明
  4. 在源文件中写函数的定义

示例:

1//swap.h文件
2#include<iostream>
3using namespace std;
4
5//实现两个数字交换的函数声明
6void swap(int a, int b);
7
 1//swap.cpp文件
 2#include "swap.h"
 3
 4void swap(int a, int b)
 5{
 6	int temp = a;
 7	a = b;
 8	b = temp;
 9
10	cout << "a = " << a << endl;
11	cout << "b = " << b << endl;
12}
 1//main函数文件
 2#include "swap.h"
 3int main() {
 4
 5	int a = 100;
 6	int b = 200;
 7	swap(a, b);
 8
 9	system("pause");
10
11	return 0;
12}
13

7.指针

指针变量定义语法: 数据类型 * 变量名;

示例:

 1int main() {
 2
 3	//1、指针的定义
 4	int a = 10; //定义整型变量a
 5	
 6	//指针定义语法: 数据类型 * 变量名 ;
 7	int * p;
 8
 9	//指针变量赋值
10	p = &a; //指针指向变量a的地址
11	cout << &a << endl; //打印数据a的地址
12	cout << p << endl;  //打印指针变量p
13
14	//2、指针的使用
15	//通过*操作指针变量指向的内存
16	cout << "*p = " << *p << endl;
17
18	system("pause");
19
20	return 0;
21}

指针变量和普通变量的区别

  • 普通变量存放的是数据,指针变量存放的是地址
  • 指针变量可以通过" * “操作符,操作指针变量指向的内存空间,这个过程称为解引用

总结1: 我们可以通过 & 符号 获取变量的地址

总结2:利用指针可以记录地址

总结3:对指针变量解引用,可以操作指针指向的内存

指针所占内存空间

所有指针类型在32位操作系统下是4个字节

空指针和野指针

空指针:指针变量指向内存中编号为0的空间

**注意:**空指针指向的内存是不可以访问的

示例1:空指针

 1int main() {
 2
 3	//指针变量p指向内存地址编号为0的空间
 4	int * p = NULL;
 5
 6	//访问空指针报错 
 7	//内存编号0 ~255为系统占用内存,不允许用户访问
 8	cout << *p << endl;
 9
10	system("pause");
11
12	return 0;
13}

野指针:指针变量指向非法的内存空间

示例2:野指针

 1int main() {
 2
 3	//指针变量p指向内存地址编号为0x1100的空间
 4	int * p = (int *)0x1100;
 5
 6	//访问野指针报错 
 7	cout << *p << endl;
 8
 9	system("pause");
10
11	return 0;
12}

总结:空指针和野指针都不是我们申请的空间,因此不要访问。

const修饰指针

const修饰指针有三种情况

  1. const修饰指针 — 常量指针 const int * p1 = &a;
  2. const修饰常量 — 指针常量 int * const p2 = &a;
  3. const即修饰指针,又修饰常量 const int * const p3 = &a;

**示例: **

 1int main() {
 2
 3	int a = 10;
 4	int b = 10;
 5
 6	//const修饰的是指针,指针指向可以改,指针指向的值不可以更改
 7	const int * p1 = &a; 
 8	p1 = &b; //正确
 9	//*p1 = 100;  报错
10	
11
12	//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
13	int * const p2 = &a;
14	//p2 = &b; //错误
15	*p2 = 100; //正确
16
17    //const既修饰指针又修饰常量
18	const int * const p3 = &a;
19	//p3 = &b; //错误
20	//*p3 = 100; //错误
21
22	system("pause");
23
24	return 0;
25}

技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量

指针和数组

当数组名传入到函数作为参数时,被退化为指向首元素的指针

示例:

 1int main() {
 2
 3	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
 4
 5	int * p = arr;  //指向数组的指针
 6
 7	cout << "第一个元素: " << arr[0] << endl;
 8	cout << "指针访问第一个元素: " << *p << endl;
 9
10	for (int i = 0; i < 10; i++)
11	{
12		//利用指针遍历数组
13		cout << *p << endl;
14		p++;
15	}
16
17	system("pause");
18
19	return 0;
20}

指针和函数

利用指针作函数参数,可以修改实参的值

1//地址传递
2void swap2(int * p1, int *p2)
3{
4	int temp = *p1;
5	*p1 = *p2;
6	*p2 = temp;
7}

如果不想修改实参,就用值传递,如果想修改实参,就用地址传递

8.结构体

结构体定义和使用

语法:struct 结构体名 { 结构体成员列表 };

通过结构体创建变量的方式有三种:

  • struct 结构体名 变量名
  • struct 结构体名 变量名 = { 成员1值 , 成员2值…}
  • 定义结构体时顺便创建变量

示例:

 1//结构体定义
 2struct student
 3{
 4	//成员列表
 5	string name;  //姓名
 6	int age;      //年龄
 7	int score;    //分数
 8}stu3; //结构体变量创建方式3 
 9
10
11int main() {
12
13	//结构体变量创建方式1
14	struct student stu1; //struct 关键字可以省略
15
16	stu1.name = "张三";
17	stu1.age = 18;
18	stu1.score = 100;
19	
20	cout << "姓名:" << stu1.name << " 年龄:" << stu1.age  << " 分数:" << stu1.score << endl;
21
22	//结构体变量创建方式2
23	struct student stu2 = { "李四",19,60 };
24
25	cout << "姓名:" << stu2.name << " 年龄:" << stu2.age  << " 分数:" << stu2.score << endl;
26
27
28	stu3.name = "王五";
29	stu3.age = 18;
30	stu3.score = 80;
31	
32
33	cout << "姓名:" << stu3.name << " 年龄:" << stu3.age  << " 分数:" << stu3.score << endl;
34
35	system("pause");
36
37	return 0;
38}

总结1:定义结构体时的关键字是struct,不可省略

总结2:创建结构体变量时,关键字struct可以省略

总结3:结构体变量利用操作符 ‘’.'' 访问成员

结构体数组

struct 结构体名 数组名[元素个数] = { {} , {} , ... {} }

1struct student arr[3]=
2	{
3		{"张三",18,80 },
4		{"李四",19,60 },
5		{"王五",20,70 }
6	};
7

结构体指针

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

 1int main() {
 2	
 3	struct student stu = { "张三",18,100, };
 4	
 5	struct student * p = &stu;
 6	
 7	p->score = 80; //指针通过 -> 操作符可以访问成员
 8
 9	cout << "姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl;
10	
11	system("pause");
12
13	return 0;
14}

结构体嵌套结构体

结构体做函数参数

传递方式有两种:

  • 值传递
  • 地址传递
 1//值传递
 2void printStudent(student stu )
 3{
 4	stu.age = 28;
 5	cout << "子函数中 姓名:" << stu.name << " 年龄: " << stu.age  << " 分数:" << stu.score << endl;
 6}
 7
 8//地址传递
 9void printStudent2(student *stu)
10{
11	stu->age = 28;
12	cout << "子函数中 姓名:" << stu->name << " 年龄: " << stu->age  << " 分数:" << stu->score << endl;
13}

结构体中 const使用场景

用const来防止误操作

1//const使用场景
2void printStudent(const student *stu) //加const防止函数体中的误操作
3{
4	//stu->age = 100; //操作失败,因为加了const修饰
5	cout << "姓名:" << stu->name << " 年龄:" << stu->age << " 分数:" << stu->score << endl;
6
7}

9.内存分区模型

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

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程

程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域

代码区:

​ 存放 CPU 执行的机器指令

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

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

全局区:

​ 全局变量和静态变量存放在此.

​ 全局区还包含了常量区, 字符串常量和其他常量也存放在此.

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

程序运行后

栈区:

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

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

堆区:

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

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

new操作符

C++中利用==new==操作符在堆区开辟数据

​ 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 ==delete==

​ 语法: new 数据类型

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

示例1: 基本语法

 1int* func()
 2{
 3	int* a = new int(10);
 4	return a;
 5}
 6
 7int main() {
 8
 9	int *p = func();
10
11	cout << *p << endl;
12	cout << *p << endl;
13
14	//利用delete释放堆区数据
15	delete p;
16
17	//cout << *p << endl; //报错,释放的空间不可访问
18
19	system("pause");
20
21	return 0;
22}

示例2:开辟数组

 1//堆区开辟数组
 2int main() {
 3
 4	int* arr = new int[10];
 5
 6	for (int i = 0; i < 10; i++)
 7	{
 8		arr[i] = i + 100;
 9	}
10
11	for (int i = 0; i < 10; i++)
12	{
13		cout << arr[i] << endl;
14	}
15	//释放数组 delete 后加 []
16	delete[] arr;
17
18	system("pause");
19
20	return 0;
21}
22

10.引用

引用的基本使用

给变量起别名

数据类型 &别名 = 原名

引用注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变
 1int main() {
 2
 3	int a = 10;
 4	int b = 20;
 5	//int &c; //错误,引用必须初始化
 6	int &c = a; //一旦初始化后,就不可以更改
 7	c = b; //这是赋值操作,不是更改引用
 8
 9	cout << "a = " << a << endl;
10	cout << "b = " << b << endl;
11	cout << "c = " << c << endl;
12
13	system("pause");
14
15	return 0;
16}

引用做函数参数

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

**优点:**可以简化指针修改实参, 通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单

 1//1. 值传递
 2void mySwap01(int a, int b) {
 3	int temp = a;
 4	a = b;
 5	b = temp;
 6}
 7
 8//2. 地址传递
 9void mySwap02(int* a, int* b) {
10	int temp = *a;
11	*a = *b;
12	*b = temp;
13}
14
15//3. 引用传递
16void mySwap03(int& a, int& b) {
17	int temp = a;
18	a = b;
19	b = temp;
20}

引用做函数返回值

引用是可以作为函数的返回值存在的

注意:不要返回局部变量引用

用法:函数调用作为左值

 1//返回局部变量引用
 2int& test01() {
 3	int a = 10; //局部变量
 4	return a;
 5}
 6
 7//返回静态变量引用
 8int& test02() {
 9	static int a = 20;
10	return a;
11}
12
13int main() {
14
15	//不能返回局部变量的引用
16	int& ref = test01();
17	cout << "ref = " << ref << endl;
18	cout << "ref = " << ref << endl;
19
20	//如果函数做左值,那么必须返回引用
21	int& ref2 = test02();
22	cout << "ref2 = " << ref2 << endl;
23	cout << "ref2 = " << ref2 << endl;
24
25	test02() = 1000;
26
27	cout << "ref2 = " << ref2 << endl;
28	cout << "ref2 = " << ref2 << endl;
29
30	system("pause");
31
32	return 0;
33}

引用的本质

引用的本质在c++内部实现是一个指针常量.

C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了

 1//发现是引用,转换为 int* const ref = &a;
 2void func(int& ref){
 3	ref = 100; // ref是引用,转换为*ref = 100
 4}
 5int main(){
 6	int a = 10;
 7    
 8    //自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
 9	int& ref = a; 
10	ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;
11    
12	cout << "a:" << a << endl;
13	cout << "ref:" << ref << endl;
14    
15	func(a);
16	return 0;
17}

常量引用

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

 1//引用使用的场景,通常用来修饰形参
 2void showValue(const int& v) {
 3	//v += 10;
 4	cout << v << endl;
 5}
 6
 7int main() {
 8
 9	//int& ref = 10;  引用本身需要一个合法的内存空间,因此这行错误
10	//加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;
11	const int& ref = 10;
12
13	//ref = 100;  //加入const后不可以修改变量
14	cout << ref << endl;
15
16	//函数中利用常量引用防止误操作修改实参
17	int a = 10;
18	showValue(a);
19
20	system("pause");
21
22	return 0;
23}

11.函数提高

函数默认参数

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

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

 1int func(int a, int b = 10, int c = 10) {
 2	return a + b + c;
 3}
 4
 5//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
 6//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
 7int func2(int a = 10, int b = 10);
 8int func2(int a, int b) {
 9	return a + b;
10}

函数占位参数

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

返回值类型 函数名 (数据类型){}

 1//函数占位参数 ,占位参数也可以有默认参数
 2void func(int a, int) {
 3	cout << "this is func" << endl;
 4}
 5
 6int main() {
 7
 8	func(10,10); //占位参数必须填补
 9
10	system("pause");
11
12	return 0;
13}

12.函数重载

函数重载概述

函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同 或者 个数不同 或者 顺序不同

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

函数重载注意事项

  • 引用作为重载条件
  • 函数重载碰到函数默认参数

示例:

 1//函数重载注意事项
 2//1、引用作为重载条件
 3
 4void func(int &a)
 5{
 6	cout << "func (int &a) 调用 " << endl;
 7}
 8
 9void func(const int &a)
10{
11	cout << "func (const int &a) 调用 " << endl;
12}
13
14
15//2、函数重载碰到函数默认参数
16
17void func2(int a, int b = 10)
18{
19	cout << "func2(int a, int b = 10) 调用" << endl;
20}
21
22void func2(int a)
23{
24	cout << "func2(int a) 调用" << endl;
25}
26
27int main() {
28	
29	int a = 10;
30	func(a); //调用无const
31	func(10);//调用有const
32
33
34	//func2(10); //碰到默认参数产生歧义,需要避免
35
36	system("pause");
37
38	return 0;
39}

13.类和对象

封装

封装的意义:

  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制

class 类名{ 访问权限: 属性 / 行为 };

访问权限有三种:

  1. public 公共权限
  2. protected 保护权限
  3. private 私有权限

示例:

 1//三种权限
 2//公共权限  public     类内可以访问  类外可以访问
 3//保护权限  protected  类内可以访问  类外不可以访问
 4//私有权限  private    类内可以访问  类外不可以访问
 5
 6class Person
 7{
 8	//姓名  公共权限
 9public:
10	string m_Name;
11
12	//汽车  保护权限
13protected:
14	string m_Car;
15
16	//银行卡密码  私有权限
17private:
18	int m_Password;
19
20public:
21	void func()
22	{
23		m_Name = "张三";
24		m_Car = "拖拉机";
25		m_Password = 123456;
26	}
27};
28
29int main() {
30
31	Person p;
32	p.m_Name = "李四";
33	//p.m_Car = "奔驰";  //保护权限类外访问不到
34	//p.m_Password = 123; //私有权限类外访问不到
35
36	system("pause");
37
38	return 0;
39}

struct和class区别

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

区别:

  • struct 默认权限为公共
  • class 默认权限为私有
 1class C1
 2{
 3	int  m_A; //默认是私有权限
 4};
 5
 6struct C2
 7{
 8	int m_A;  //默认是公共权限
 9};
10
11int main() {
12
13	C1 c1;
14	c1.m_A = 10; //错误,访问权限是私有
15
16	C2 c2;
17	c2.m_A = 10; //正确,访问权限是公共
18
19	system("pause");
20
21	return 0;
22}

成员属性设置为私有

**优点1:**将所有成员属性设置为私有,可以自己控制读写权限

**优点2:**对于写权限,我们可以检测数据的有效性

对象的初始化和清理

I 构造函数和析构函数

c++利用了构造函数析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。

对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供, 编译器提供的构造函数和析构函数是空实现。

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

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

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

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

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号 ~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

II 构造函数的分类及调用

两种分类方式:

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

​ 按类型分为: 普通构造和拷贝构造

三种调用方式:

​ 括号法

​ 显示法

​ 隐式转换法

 1class Person {
 2public:
 3	//无参(默认)构造函数
 4	Person() {
 5		cout << "无参构造函数!" << endl;
 6	}
 7	//有参构造函数
 8	Person(int a) {
 9		age = a;
10		cout << "有参构造函数!" << endl;
11	}
12	//拷贝构造函数
13	Person(const Person& p) {
14		age = p.age;
15		cout << "拷贝构造函数!" << endl;
16	}
17	//析构函数
18	~Person() {
19		cout << "析构函数!" << endl;
20	}
21public:
22	int age;
23};
24
25//2、构造函数的调用
26//调用无参构造函数
27void test01() {
28	Person p; //调用无参构造函数
29}
30
31//调用有参的构造函数
32void test02() {
33
34	//2.1  括号法,常用
35	Person p1(10);
36	//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
37	//Person p2();
38
39	//2.2 显式法
40	Person p2 = Person(10); 
41	Person p3 = Person(p2);
42	//Person(10)单独写就是匿名对象  当前行结束之后,马上析构
43
44	//2.3 隐式转换法
45	Person p4 = 10; // Person p4 = Person(10); 
46	Person p5 = p4; // Person p5 = Person(p4); 
47
48	//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
49	//Person p5(p4);
50}
51
52int main() {
53
54	test01();
55	//test02();
56
57	system("pause");
58
59	return 0;
60}

III 拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象
 1class Person {
 2public:
 3	Person() {
 4		cout << "无参构造函数!" << endl;
 5		mAge = 0;
 6	}
 7	Person(int age) {
 8		cout << "有参构造函数!" << endl;
 9		mAge = age;
10	}
11	Person(const Person& p) {
12		cout << "拷贝构造函数!" << endl;
13		mAge = p.mAge;
14	}
15	//析构函数在释放内存之前调用
16	~Person() {
17		cout << "析构函数!" << endl;
18	}
19public:
20	int mAge;
21};
22
23//1. 使用一个已经创建完毕的对象来初始化一个新对象
24void test01() {
25
26	Person man(100); //p对象已经创建完毕
27	Person newman(man); //调用拷贝构造函数
28	Person newman2 = man; //拷贝构造
29
30	//Person newman3;
31	//newman3 = man; //不是调用拷贝构造函数,赋值操作
32}
33
34//2. 值传递的方式给函数参数传值
35//相当于Person p1 = p;
36void doWork(Person p1) {}
37void test02() {
38	Person p; //无参构造函数
39	doWork(p);
40}
41
42//3. 以值方式返回局部对象
43Person doWork2()
44{
45	Person p1;
46	cout << (int *)&p1 << endl;
47	return p1;
48}
49
50void test03()
51{
52	Person p = doWork2();
53	cout << (int *)&p << endl;
54}
55
56
57int main() {
58
59	//test01();
60	//test02();
61	test03();
62
63	system("pause");
64
65	return 0;
66}

IV 构造函数调用规则

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

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

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

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

构造函数调用规则如下:

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

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

 1class Person {
 2public:
 3	//无参(默认)构造函数
 4	Person() {
 5		cout << "无参构造函数!" << endl;
 6	}
 7	//有参构造函数
 8	Person(int a) {
 9		age = a;
10		cout << "有参构造函数!" << endl;
11	}
12	//拷贝构造函数
13	Person(const Person& p) {
14		age = p.age;
15		cout << "拷贝构造函数!" << endl;
16	}
17	//析构函数
18	~Person() {
19		cout << "析构函数!" << endl;
20	}
21public:
22	int age;
23};
24
25void test01()
26{
27	Person p1(18);
28	//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
29	Person p2(p1);
30
31	cout << "p2的年龄为: " << p2.age << endl;
32}
33
34void test02()
35{
36	//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
37	Person p1; //此时如果用户自己没有提供默认构造,会出错
38	Person p2(10); //用户提供的有参
39	Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供
40
41	//如果用户提供拷贝构造,编译器不会提供其他构造函数
42	Person p4; //此时如果用户自己没有提供默认构造,会出错
43	Person p5(10); //此时如果用户自己没有提供有参,会出错
44	Person p6(p5); //用户自己提供拷贝构造
45}
46
47int main() {
48
49	test01();
50
51	system("pause");
52
53	return 0;
54}

V 深拷贝与浅拷贝

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

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

如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

示例:

 1class Person {
 2public:
 3	//无参(默认)构造函数
 4	Person() {
 5		cout << "无参构造函数!" << endl;
 6	}
 7	//有参构造函数
 8	Person(int age ,int height) {
 9		
10		cout << "有参构造函数!" << endl;
11
12		m_age = age;
13		m_height = new int(height);
14		
15	}
16	//拷贝构造函数  
17	Person(const Person& p) {
18		cout << "拷贝构造函数!" << endl;
19		//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
20		m_age = p.m_age;
21		m_height = new int(*p.m_height);
22		
23	}
24
25	//析构函数
26	~Person() {
27		cout << "析构函数!" << endl;
28		if (m_height != NULL)
29		{
30			delete m_height;
31		}
32	}
33public:
34	int m_age;
35	int* m_height;
36};

VI 初始化列表

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

构造函数():属性1(值1),属性2(值2)... {}

1	////传统方式初始化
2	//Person(int a, int b, int c) {
3	//	m_A = a;
4	//	m_B = b;
5	//	m_C = c;
6	//}
7
8	//初始化列表方式初始化
9	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}

VII 类对象作为类成员

当类中成员是其他类对象时,我们称该成员为 对象成员

构造的顺序是 :先调用对象成员的构造,再调用本类构造

析构顺序与构造相反

VIII 静态成员

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

静态成员分为:

  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量

C++对象模型和this指针

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

 1class Person {
 2public:
 3	Person() {
 4		mA = 0;
 5	}
 6	//非静态成员变量占对象空间
 7	int mA;
 8	//静态成员变量不占对象空间
 9	static int mB; 
10	//函数也不占对象空间,所有函数共享一个函数实例
11	void func() {
12		cout << "mA:" << this->mA << endl;
13	}
14	//静态成员函数也不占对象空间
15	static void sfunc() {
16	}
17};

this指针概念

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

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

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

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

this指针不需要定义,直接使用即可

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this
 1Person(int age)
 2	{
 3		//1、当形参和成员变量同名时,可用this指针来区分
 4		this->age = age;
 5	}
 6
 7	Person& PersonAddPerson(Person p)
 8	{
 9		this->age += p.age;
10		//返回对象本身
11		return *this;
12	}

空指针访问成员函数

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

如果用到this指针,需要加以判断保证代码的健壮性

 1//空指针访问成员函数
 2class Person {
 3public:
 4
 5	void ShowClassName() {
 6		cout << "我是Person类!" << endl;
 7	}
 8
 9	void ShowPerson() {
10		if (this == NULL) {
11			return;
12		}
13		cout << mAge << endl;
14	}
15
16public:
17	int mAge;
18};
19
20void test01()
21{
22	Person * p = NULL;
23	p->ShowClassName(); //空指针,可以调用成员函数
24	p->ShowPerson();  //但是如果成员函数中用到了this指针,就不可以了
25}

const修饰成员函数

常函数:

  • 成员函数后加const后我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数

示例:

 1class Person {
 2public:
 3	Person() {
 4		m_A = 0;
 5		m_B = 0;
 6	}
 7
 8	//this指针的本质是一个指针常量,指针的指向不可修改
 9	//如果想让指针指向的值也不可以修改,需要声明常函数
10	void ShowPerson() const {
11		//const Type* const pointer;
12		//this = NULL; //不能修改指针的指向 Person* const this;
13		//this->mA = 100; //但是this指针指向的对象的数据是可以修改的
14
15		//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
16		this->m_B = 100;
17	}
18
19	void MyFunc() const {
20		//mA = 10000;
21	}
22
23public:
24	int m_A;
25	mutable int m_B; //可修改 可变的
26};
27
28
29//const修饰对象  常对象
30void test01() {
31
32	const Person person; //常量对象  
33	cout << person.m_A << endl;
34	//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
35	person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
36
37	//常对象访问成员函数
38	person.MyFunc(); //常对象不能调用const的函数
39
40}

友元

在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术

友元的目的就是让一个函数或者类 访问另一个类中私有成员

友元的关键字为 ==friend==

友元的三种实现

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

全局函数做友元

 1class Building
 2{
 3	//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
 4	friend void goodGay(Building * building);
 5
 6public:
 7
 8	Building()
 9	{
10		this->m_SittingRoom = "客厅";
11		this->m_BedRoom = "卧室";
12	}
13
14
15public:
16	string m_SittingRoom; //客厅
17
18private:
19	string m_BedRoom; //卧室
20};
21
22
23void goodGay(Building * building)
24{
25	cout << "好基友正在访问: " << building->m_SittingRoom << endl;
26	cout << "好基友正在访问: " << building->m_BedRoom << endl;
27}
28
29
30void test01()
31{
32	Building b;
33	goodGay(&b);
34}
35

类做友元

 1class Building;
 2class goodGay
 3{
 4public:
 5
 6	goodGay();
 7	void visit();
 8
 9private:
10	Building *building;
11};
12
13
14class Building
15{
16	//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
17	friend class goodGay;
18
19public:
20	Building();
21
22public:
23	string m_SittingRoom; //客厅
24private:
25	string m_BedRoom;//卧室
26};
27
28Building::Building()
29{
30	this->m_SittingRoom = "客厅";
31	this->m_BedRoom = "卧室";
32}
33
34goodGay::goodGay()
35{
36	building = new Building;
37}
38
39void goodGay::visit()
40{
41	cout << "好基友正在访问" << building->m_SittingRoom << endl;
42	cout << "好基友正在访问" << building->m_BedRoom << endl;
43}
44
45void test01()
46{
47	goodGay gg;
48	gg.visit();
49
50}

成员函数做友元

 1class Building;
 2class goodGay
 3{
 4public:
 5
 6	goodGay();
 7	void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
 8	void visit2(); 
 9
10private:
11	Building *building;
12};
13
14
15class Building
16{
17	//告诉编译器  goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
18	friend void goodGay::visit();
19
20public:
21	Building();
22
23public:
24	string m_SittingRoom; //客厅
25private:
26	string m_BedRoom;//卧室
27};
28
29Building::Building()
30{
31	this->m_SittingRoom = "客厅";
32	this->m_BedRoom = "卧室";
33}
34
35goodGay::goodGay()
36{
37	building = new Building;
38}
39
40void goodGay::visit()
41{
42	cout << "好基友正在访问" << building->m_SittingRoom << endl;
43	cout << "好基友正在访问" << building->m_BedRoom << endl;
44}
45
46void goodGay::visit2()
47{
48	cout << "好基友正在访问" << building->m_SittingRoom << endl;
49	//cout << "好基友正在访问" << building->m_BedRoom << endl;
50}
51
52void test01()
53{
54	goodGay  gg;
55	gg.visit();
56
57}

运算符重载

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

加号运算符重载

 1class Person {
 2public:
 3	Person() {};
 4	Person(int a, int b)
 5	{
 6		this->m_A = a;
 7		this->m_B = b;
 8	}
 9	//成员函数实现 + 号运算符重载
10	Person operator+(const Person& p) {
11		Person temp;
12		temp.m_A = this->m_A + p.m_A;
13		temp.m_B = this->m_B + p.m_B;
14		return temp;
15	}
16
17
18public:
19	int m_A;
20	int m_B;
21};
22
23//全局函数实现 + 号运算符重载
24//Person operator+(const Person& p1, const Person& p2) {
25//	Person temp(0, 0);
26//	temp.m_A = p1.m_A + p2.m_A;
27//	temp.m_B = p1.m_B + p2.m_B;
28//	return temp;
29//}
30
31//运算符重载 可以发生函数重载 
32Person operator+(const Person& p2, int val)  
33{
34	Person temp;
35	temp.m_A = p2.m_A + val;
36	temp.m_B = p2.m_B + val;
37	return temp;
38}
39
40void test() {
41
42	Person p1(10, 10);
43	Person p2(20, 20);
44
45	//成员函数方式
46	Person p3 = p2 + p1;  //相当于 p2.operaor+(p1)
47	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
48
49
50	Person p4 = p3 + 10; //相当于 operator+(p3,10)
51	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
52
53}

左移运算符重载

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

 1class Person {
 2	friend ostream& operator<<(ostream& out, Person& p);
 3
 4public:
 5
 6	Person(int a, int b)
 7	{
 8		this->m_A = a;
 9		this->m_B = b;
10	}
11
12	//成员函数 实现不了  p << cout 不是我们想要的效果
13	//void operator<<(Person& p){
14	//}
15
16private:
17	int m_A;
18	int m_B;
19};
20
21//全局函数实现左移重载
22//ostream对象只能有一个
23ostream& operator<<(ostream& out, Person& p) {
24	out << "a:" << p.m_A << " b:" << p.m_B;
25	return out;
26}
27
28void test() {
29
30	Person p1(10, 20);
31
32	cout << p1 << "hello world" << endl; //链式编程
33}
34

递增运算符重载

前置递增返回引用,后置递增返回值

 1class MyInteger {
 2
 3	friend ostream& operator<<(ostream& out, MyInteger myint);
 4
 5public:
 6	MyInteger() {
 7		m_Num = 0;
 8	}
 9	//前置++
10	MyInteger& operator++() {
11		//先++
12		m_Num++;
13		//再返回
14		return *this;
15	}
16
17	//后置++
18	MyInteger operator++(int) {
19		//先返回
20		MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
21		m_Num++;
22		return temp;
23	}
24
25private:
26	int m_Num;
27};
28
29
30ostream& operator<<(ostream& out, MyInteger myint) {
31	out << myint.m_Num;
32	return out;
33}
34
35
36//前置++ 先++ 再返回
37void test01() {
38	MyInteger myInt;
39	cout << ++myInt << endl;
40	cout << myInt << endl;
41}
42
43//后置++ 先返回 再++
44void test02() {
45
46	MyInteger myInt;
47	cout << myInt++ << endl;
48	cout << myInt << endl;
49}

赋值运算符重载

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

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 赋值运算符 operator=, 对属性进行值拷贝

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

 1class Person
 2{
 3public:
 4
 5	Person(int age)
 6	{
 7		//将年龄数据开辟到堆区
 8		m_Age = new int(age);
 9	}
10
11	//重载赋值运算符 
12	Person& operator=(Person &p)
13	{
14		if (m_Age != NULL)
15		{
16			delete m_Age;
17			m_Age = NULL;
18		}
19		//编译器提供的代码是浅拷贝
20		//m_Age = p.m_Age;
21
22		//提供深拷贝 解决浅拷贝的问题
23		m_Age = new int(*p.m_Age);
24
25		//返回自身
26		return *this;
27	}
28
29
30	~Person()
31	{
32		if (m_Age != NULL)
33		{
34			delete m_Age;
35			m_Age = NULL;
36		}
37	}
38
39	//年龄的指针
40	int *m_Age;
41
42};
43
44
45void test01()
46{
47	Person p1(18);
48
49	Person p2(20);
50
51	Person p3(30);
52
53	p3 = p2 = p1; //赋值操作
54
55	cout << "p1的年龄为:" << *p1.m_Age << endl;
56
57	cout << "p2的年龄为:" << *p2.m_Age << endl;
58
59	cout << "p3的年龄为:" << *p3.m_Age << endl;
60}

关系运算符重载

重载关系运算符,可以让两个自定义类型对象进行对比操作

 1class Person
 2{
 3public:
 4	Person(string name, int age)
 5	{
 6		this->m_Name = name;
 7		this->m_Age = age;
 8	};
 9
10	bool operator==(Person & p)
11	{
12		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
13		{
14			return true;
15		}
16		else
17		{
18			return false;
19		}
20	}
21
22	bool operator!=(Person & p)
23	{
24		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
25		{
26			return false;
27		}
28		else
29		{
30			return true;
31		}
32	}
33
34	string m_Name;
35	int m_Age;
36};

函数调用运算符重载

  • 函数调用运算符 () 也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
 1class MyPrint
 2{
 3public:
 4	void operator()(string text)
 5	{
 6		cout << text << endl;
 7	}
 8
 9};
10void test01()
11{
12	//重载的()操作符 也称为仿函数
13	MyPrint myFunc;
14	myFunc("hello world");
15}
16
17
18class MyAdd
19{
20public:
21	int operator()(int v1, int v2)
22	{
23		return v1 + v2;
24	}
25};
26
27void test02()
28{
29	MyAdd add;
30	int ret = add(10, 10);
31	cout << "ret = " << ret << endl;
32
33	//匿名对象调用  
34	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
35}

继承

继承的基本语法

class A : public B;

A 类称为子类 或 派生类

B 类称为父类 或 基类

派生类中的成员,包含两大部分

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

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

继承方式

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

继承方式一共有三种:

  • 公共继承
  • 保护继承
  • 私有继承
 1class Base1
 2{
 3public: 
 4	int m_A;
 5protected:
 6	int m_B;
 7private:
 8	int m_C;
 9};
10
11//公共继承
12class Son1 :public Base1
13{
14public:
15	void func()
16	{
17		m_A; //可访问 public权限
18		m_B; //可访问 protected权限
19		//m_C; //不可访问
20	}
21};
22
23void myClass()
24{
25	Son1 s1;
26	s1.m_A; //其他类只能访问到公共权限
27}
28
29//保护继承
30class Base2
31{
32public:
33	int m_A;
34protected:
35	int m_B;
36private:
37	int m_C;
38};
39class Son2:protected Base2
40{
41public:
42	void func()
43	{
44		m_A; //可访问 protected权限
45		m_B; //可访问 protected权限
46		//m_C; //不可访问
47	}
48};
49void myClass2()
50{
51	Son2 s;
52	//s.m_A; //不可访问
53}
54
55//私有继承
56class Base3
57{
58public:
59	int m_A;
60protected:
61	int m_B;
62private:
63	int m_C;
64};
65class Son3:private Base3
66{
67public:
68	void func()
69	{
70		m_A; //可访问 private权限
71		m_B; //可访问 private权限
72		//m_C; //不可访问
73	}
74};
75class GrandSon3 :public Son3
76{
77public:
78	void func()
79	{
80		//Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
81		//m_A;
82		//m_B;
83		//m_C;
84	}
85};

继承中的对象模型

从父类继承过来的成员,哪些属于子类对象中?

私有成员只是被隐藏了,但是还是会继承下去

继承中构造和析构顺序

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

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

继承同名成员处理方式

当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域
 1class Base {
 2public:
 3	Base()
 4	{
 5		m_A = 100;
 6	}
 7
 8	void func()
 9	{
10		cout << "Base - func()调用" << endl;
11	}
12
13	void func(int a)
14	{
15		cout << "Base - func(int a)调用" << endl;
16	}
17
18public:
19	int m_A;
20};
21
22
23class Son : public Base {
24public:
25	Son()
26	{
27		m_A = 200;
28	}
29
30	//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
31	//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
32	void func()
33	{
34		cout << "Son - func()调用" << endl;
35	}
36public:
37	int m_A;
38};
39
40void test01()
41{
42	Son s;
43
44	cout << "Son下的m_A = " << s.m_A << endl;
45	cout << "Base下的m_A = " << s.Base::m_A << endl;
46
47	s.func();
48	s.Base::func();
49	s.Base::func(10);
50
51}

继承同名静态成员处理方式

继承中同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)

 1class Base {
 2public:
 3	static void func()
 4	{
 5		cout << "Base - static void func()" << endl;
 6	}
 7	static void func(int a)
 8	{
 9		cout << "Base - static void func(int a)" << endl;
10	}
11
12	static int m_A;
13};
14
15int Base::m_A = 100;
16
17class Son : public Base {
18public:
19	static void func()
20	{
21		cout << "Son - static void func()" << endl;
22	}
23	static int m_A;
24};
25
26int Son::m_A = 200;
27
28//同名成员属性
29void test01()
30{
31	//通过对象访问
32	cout << "通过对象访问: " << endl;
33	Son s;
34	cout << "Son  下 m_A = " << s.m_A << endl;
35	cout << "Base 下 m_A = " << s.Base::m_A << endl;
36
37	//通过类名访问
38	cout << "通过类名访问: " << endl;
39	cout << "Son  下 m_A = " << Son::m_A << endl;
40	cout << "Base 下 m_A = " << Son::Base::m_A << endl;
41}
42
43//同名成员函数
44void test02()
45{
46	//通过对象访问
47	cout << "通过对象访问: " << endl;
48	Son s;
49	s.func();
50	s.Base::func();
51
52	cout << "通过类名访问: " << endl;
53	Son::func();
54	Son::Base::func();
55	//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
56	Son::Base::func(100);
57}

多继承语法

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

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

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

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

 1class Base1 {
 2public:
 3	Base1()
 4	{
 5		m_A = 100;
 6	}
 7public:
 8	int m_A;
 9};
10
11class Base2 {
12public:
13	Base2()
14	{
15		m_A = 200;  //开始是m_B 不会出问题,但是改为mA就会出现不明确
16	}
17public:
18	int m_A;
19};
20
21//语法:class 子类:继承方式 父类1 ,继承方式 父类2 
22class Son : public Base2, public Base1 
23{
24public:
25	Son()
26	{
27		m_C = 300;
28		m_D = 400;
29	}
30public:
31	int m_C;
32	int m_D;
33};
34
35
36//多继承容易产生成员同名的情况
37//通过使用类名作用域可以区分调用哪一个基类的成员
38void test01()
39{
40	Son s;
41	cout << "sizeof Son = " << sizeof(s) << endl;
42	cout << s.Base1::m_A << endl;
43	cout << s.Base2::m_A << endl;
44}

菱形继承

两个派生类继承同一个基类

又有某个类同时继承者两个派生类

  • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
  • 利用虚继承可以解决菱形继承问题
 1class Animal
 2{
 3public:
 4	int m_Age;
 5};
 6
 7//继承前加virtual关键字后,变为虚继承
 8//此时公共的父类Animal称为虚基类
 9class Sheep : virtual public Animal {};
10class Tuo   : virtual public Animal {};
11class SheepTuo : public Sheep, public Tuo {};
12
13void test01()
14{
15	SheepTuo st;
16	st.Sheep::m_Age = 100;
17	st.Tuo::m_Age = 200;
18
19	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
20	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
21	cout << "st.m_Age = " << st.m_Age << endl;
22}

多态

多态分为两类

  • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态: 派生类和虚函数实现运行时多态

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

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

多态满足条件

  • 有继承关系
  • 子类重写父类中的虚函数

多态使用条件

  • 父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写

 1class Animal
 2{
 3public:
 4	//Speak函数就是虚函数
 5	//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
 6	virtual void speak()
 7	{
 8		cout << "动物在说话" << endl;
 9	}
10};
11
12class Cat :public Animal
13{
14public:
15	void speak()
16	{
17		cout << "小猫在说话" << endl;
18	}
19};
20
21class Dog :public Animal
22{
23public:
24
25	void speak()
26	{
27		cout << "小狗在说话" << endl;
28	}
29
30};
31//我们希望传入什么对象,那么就调用什么对象的函数
32//如果函数地址在编译阶段就能确定,那么静态联编
33//如果函数地址在运行阶段才能确定,就是动态联编
34
35void DoSpeak(Animal & animal)
36{
37	animal.speak();
38}
39//
40//多态满足条件: 
41//1、有继承关系
42//2、子类重写父类中的虚函数
43//多态使用:
44//父类指针或引用指向子类对象
45
46void test01()
47{
48	Cat cat;
49	DoSpeak(cat);
50
51
52	Dog dog;
53	DoSpeak(dog);
54}

多态案例一-计算器类

 1//多态实现
 2//抽象计算器类
 3//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
 4class AbstractCalculator
 5{
 6public :
 7
 8	virtual int getResult()
 9	{
10		return 0;
11	}
12
13	int m_Num1;
14	int m_Num2;
15};
16
17//加法计算器
18class AddCalculator :public AbstractCalculator
19{
20public:
21	int getResult()
22	{
23		return m_Num1 + m_Num2;
24	}
25};
26
27//减法计算器
28class SubCalculator :public AbstractCalculator
29{
30public:
31	int getResult()
32	{
33		return m_Num1 - m_Num2;
34	}
35};
36
37//乘法计算器
38class MulCalculator :public AbstractCalculator
39{
40public:
41	int getResult()
42	{
43		return m_Num1 * m_Num2;
44	}
45};
46
47
48void test02()
49{
50	//创建加法计算器
51	AbstractCalculator *abc = new AddCalculator;
52	abc->m_Num1 = 10;
53	abc->m_Num2 = 10;
54	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
55	delete abc;  //用完了记得销毁
56
57	//创建减法计算器
58	abc = new SubCalculator;
59	abc->m_Num1 = 10;
60	abc->m_Num2 = 10;
61	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
62	delete abc;  
63
64	//创建乘法计算器
65	abc = new MulCalculator;
66	abc->m_Num1 = 10;
67	abc->m_Num2 = 10;
68	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
69	delete abc;
70}
71

纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

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

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
 1class Base
 2{
 3public:
 4	//纯虚函数
 5	//类中只要有一个纯虚函数就称为抽象类
 6	//抽象类无法实例化对象
 7	//子类必须重写父类中的纯虚函数,否则也属于抽象类
 8	virtual void func() = 0;
 9};
10
11class Son :public Base
12{
13public:
14	virtual void func() 
15	{
16		cout << "func调用" << endl;
17	};
18};
19
20void test01()
21{
22	Base * base = NULL;
23	//base = new Base; // 错误,抽象类无法实例化对象
24	base = new Son;
25	base->func();
26	delete base;//记得销毁
27}

多态案例二-制作饮品

 1//抽象制作饮品
 2class AbstractDrinking {
 3public:
 4	//烧水
 5	virtual void Boil() = 0;
 6	//冲泡
 7	virtual void Brew() = 0;
 8	//倒入杯中
 9	virtual void PourInCup() = 0;
10	//加入辅料
11	virtual void PutSomething() = 0;
12	//规定流程
13	void MakeDrink() {
14		Boil();
15		Brew();
16		PourInCup();
17		PutSomething();
18	}
19};
20
21//制作咖啡
22class Coffee : public AbstractDrinking {
23public:
24	//烧水
25	virtual void Boil() {
26		cout << "煮农夫山泉!" << endl;
27	}
28	//冲泡
29	virtual void Brew() {
30		cout << "冲泡咖啡!" << endl;
31	}
32	//倒入杯中
33	virtual void PourInCup() {
34		cout << "将咖啡倒入杯中!" << endl;
35	}
36	//加入辅料
37	virtual void PutSomething() {
38		cout << "加入牛奶!" << endl;
39	}
40};
41
42//制作茶水
43class Tea : public AbstractDrinking {
44public:
45	//烧水
46	virtual void Boil() {
47		cout << "煮自来水!" << endl;
48	}
49	//冲泡
50	virtual void Brew() {
51		cout << "冲泡茶叶!" << endl;
52	}
53	//倒入杯中
54	virtual void PourInCup() {
55		cout << "将茶水倒入杯中!" << endl;
56	}
57	//加入辅料
58	virtual void PutSomething() {
59		cout << "加入枸杞!" << endl;
60	}
61};
62
63//业务函数
64void DoWork(AbstractDrinking* drink) {
65	drink->MakeDrink();
66	delete drink;
67}
68
69void test01() {
70	DoWork(new Coffee);
71	cout << "--------------" << endl;
72	DoWork(new Tea);
73}

虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

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

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

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

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

类名::~类名(){}

​ 1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

​ 2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

​ 3. 拥有纯虚析构函数的类也属于抽象类

 1class Animal {
 2public:
 3
 4	Animal()
 5	{
 6		cout << "Animal 构造函数调用!" << endl;
 7	}
 8	virtual void Speak() = 0;
 9
10	//析构函数加上virtual关键字,变成虚析构函数
11	//virtual ~Animal()
12	//{
13	//	cout << "Animal虚析构函数调用!" << endl;
14	//}
15
16
17	virtual ~Animal() = 0;
18};
19
20Animal::~Animal()
21{
22	cout << "Animal 纯虚析构函数调用!" << endl;
23}
24
25//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。
26
27class Cat : public Animal {
28public:
29	Cat(string name)
30	{
31		cout << "Cat构造函数调用!" << endl;
32		m_Name = new string(name);
33	}
34	virtual void Speak()
35	{
36		cout << *m_Name <<  "小猫在说话!" << endl;
37	}
38	~Cat()
39	{
40		cout << "Cat析构函数调用!" << endl;
41		if (this->m_Name != NULL) {
42			delete m_Name;
43			m_Name = NULL;
44		}
45	}
46
47public:
48	string *m_Name;
49};
50
51void test01()
52{
53	Animal *animal = new Cat("Tom");
54	animal->Speak();
55
56	//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
57	//怎么解决?给基类增加一个虚析构函数
58	//虚析构函数就是用来解决通过父类指针释放子类对象
59	delete animal;
60}

多态案例三-电脑组装

14.文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放

通过文件可以将数据持久化

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

文件类型分为两种:

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

操作文件的三大类:

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

文本文件

写文件

写文件步骤如下:

  1. 包含头文件

    #include <fstream>

  2. 创建流对象

    ofstream ofs;

  3. 打开文件

    ofs.open(“文件路径”,打开方式);

  4. 写数据

    ofs « “写入的数据”;

  5. 关闭文件

    ofs.close();

文件打开方式:

打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在先删除,再创建
ios::binary 二进制方式

注意: 文件打开方式可以配合使用,利用|操作符

**例如:**用二进制方式写文件 ios::binary | ios:: out

示例:

 1#include <fstream>
 2
 3void test01()
 4{
 5	ofstream ofs;
 6	ofs.open("test.txt", ios::out);
 7
 8	ofs << "姓名:张三" << endl;
 9	ofs << "性别:男" << endl;
10	ofs << "年龄:18" << endl;
11
12	ofs.close();
13}
14
15int main() {
16
17	test01();
18
19	system("pause");
20
21	return 0;
22}

总结:

  • 文件操作必须包含头文件 fstream
  • 读文件可以利用 ofstream ,或者fstream类
  • 打开文件时候需要指定操作文件的路径,以及打开方式
  • 利用«可以向文件中写数据
  • 操作完毕,要关闭文件

读文件

读文件步骤如下:

  1. 包含头文件

    #include <fstream>

  2. 创建流对象

    ifstream ifs;

  3. 打开文件并判断文件是否打开成功

    ifs.open(“文件路径”,打开方式);

  4. 读数据

    四种方式读取

  5. 关闭文件

    ifs.close();示例:

     1#include <fstream>
     2#include <string>
     3void test01()
     4{
     5	ifstream ifs;
     6	ifs.open("test.txt", ios::in);
     7   
     8	if (!ifs.is_open())
     9	{
    10     cout << "文件打开失败" << endl;
    11     return;
    12	}
    13   
    14	//第一种方式
    15	//char buf[1024] = { 0 };
    16	//while (ifs >> buf)
    17	//{
    18	//	cout << buf << endl;
    19	//}
    20   
    21	//第二种
    22	//char buf[1024] = { 0 };
    23	//while (ifs.getline(buf,sizeof(buf)))
    24	//{
    25	//	cout << buf << endl;
    26	//}
    27   
    28	//第三种
    29	//string buf;
    30	//while (getline(ifs, buf))
    31	//{
    32	//	cout << buf << endl;
    33	//}
    34   
    35	char c;
    36	while ((c = ifs.get()) != EOF)
    37	{
    38     cout << c;
    39	}
    40   
    41	ifs.close();
    42   
    43   
    44}
    45   
    46int main() {
    47   
    48	test01();
    49   
    50	system("pause");
    51   
    52	return 0;
    53}
    

    总结:

    • 读文件可以利用 ifstream ,或者fstream类
    • 利用is_open函数可以判断文件是否打开成功
    • close 关闭文件

二进制文件

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

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

写文件

二进制方式写文件主要利用流对象调用成员函数write

函数原型 :ostream& write(const char * buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

 1#include <fstream>
 2#include <string>
 3
 4class Person
 5{
 6public:
 7	char m_Name[64];
 8	int m_Age;
 9};
10
11//二进制文件  写文件
12void test01()
13{
14	//1、包含头文件
15
16	//2、创建输出流对象
17	ofstream ofs("person.txt", ios::out | ios::binary);
18	
19	//3、打开文件
20	//ofs.open("person.txt", ios::out | ios::binary);
21
22	Person p = {"张三"  , 18};
23
24	//4、写文件
25	ofs.write((const char *)&p, sizeof(p));
26
27	//5、关闭文件
28	ofs.close();
29}

读文件

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

示例:

 1#include <fstream>
 2#include <string>
 3
 4class Person
 5{
 6public:
 7	char m_Name[64];
 8	int m_Age;
 9};
10
11void test01()
12{
13	ifstream ifs("person.txt", ios::in | ios::binary);
14	if (!ifs.is_open())
15	{
16		cout << "文件打开失败" << endl;
17	}
18
19	Person p;
20	ifs.read((char *)&p, sizeof(p));
21
22	cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
23}

15.模板

模板的概念

模板就是建立通用的模具,大大提高复用性

函数模板

  • C++另一种编程思想称为 泛型编程,主要利用的技术就是模板

  • C++提供两种模板机制:函数模板类模板

函数模板语法

建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。

语法:

1template<typename T>
2函数声明或定义

解释:

template — 声明创建模板

typename — 表面其后面的符号是一种数据类型,可以用class代替

T — 通用的数据类型,名称可以替换,通常为大写字母

示例:

 1//交换整型函数
 2void swapInt(int& a, int& b) {
 3	int temp = a;
 4	a = b;
 5	b = temp;
 6}
 7
 8//交换浮点型函数
 9void swapDouble(double& a, double& b) {
10	double temp = a;
11	a = b;
12	b = temp;
13}
14
15//利用模板提供通用的交换函数
16template<typename T>
17void mySwap(T& a, T& b)
18{
19	T temp = a;
20	a = b;
21	b = temp;
22}
23
24void test01()
25{
26	int a = 10;
27	int b = 20;
28	
29	//swapInt(a, b);
30
31	//利用模板实现交换
32	//1、自动类型推导
33	mySwap(a, b);
34
35	//2、显示指定类型
36	mySwap<int>(a, b);
37
38	cout << "a = " << a << endl;
39	cout << "b = " << b << endl;
40
41}

总结:

  • 函数模板利用关键字 template
  • 使用函数模板有两种方式:自动类型推导、显示指定类型
  • 模板的目的是为了提高复用性,将类型参数化

函数模板注意事项

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

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

 1//利用模板提供通用的交换函数
 2template<class T>
 3void mySwap(T& a, T& b)
 4{
 5	T temp = a;
 6	a = b;
 7	b = temp;
 8}
 9
10
11// 1、自动类型推导,必须推导出一致的数据类型T,才可以使用
12void test01()
13{
14	int a = 10;
15	int b = 20;
16	char c = 'c';
17
18	mySwap(a, b); // 正确,可以推导出一致的T
19	//mySwap(a, c); // 错误,推导不出一致的T类型
20}
21
22
23// 2、模板必须要确定出T的数据类型,才可以使用
24template<class T>
25void func()
26{
27	cout << "func 调用" << endl;
28}
29
30void test02()
31{
32	//func(); //错误,模板不能独立使用,必须确定出T的类型
33	func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
34}

函数模板案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别利用char数组int数组进行测试
 1//交换的函数模板
 2template<typename T>
 3void mySwap(T &a, T&b)
 4{
 5	T temp = a;
 6	a = b;
 7	b = temp;
 8}
 9
10
11template<class T> // 也可以替换成typename
12//利用选择排序,进行对数组从大到小的排序
13void mySort(T arr[], int len)
14{
15	for (int i = 0; i < len; i++)
16	{
17		int max = i; //最大数的下标
18		for (int j = i + 1; j < len; j++)
19		{
20			if (arr[max] < arr[j])
21			{
22				max = j;
23			}
24		}
25		if (max != i) //如果最大数的下标不是i,交换两者
26		{
27			mySwap(arr[max], arr[i]);
28		}
29	}
30}
31template<typename T>
32void printArray(T arr[], int len) {
33
34	for (int i = 0; i < len; i++) {
35		cout << arr[i] << " ";
36	}
37	cout << endl;
38}
39void test01()
40{
41	//测试char数组
42	char charArr[] = "bdcfeagh";
43	int num = sizeof(charArr) / sizeof(char);
44	mySort(charArr, num);
45	printArray(charArr, num);
46}
47
48void test02()
49{
50	//测试int数组
51	int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };
52	int num = sizeof(intArr) / sizeof(int);
53	mySort(intArr, num);
54	printArray(intArr, num);
55}

普通函数与函数模板的区别

普通函数与函数模板区别:

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换
 1//普通函数
 2int myAdd01(int a, int b)
 3{
 4	return a + b;
 5}
 6
 7//函数模板
 8template<class T>
 9T myAdd02(T a, T b)  
10{
11	return a + b;
12}
13
14//使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换
15void test01()
16{
17	int a = 10;
18	int b = 20;
19	char c = 'c';
20	
21	cout << myAdd01(a, c) << endl; //正确,将char类型的'c'隐式转换为int类型  'c' 对应 ASCII码 99
22
23	//myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换
24
25	myAdd02<int>(a, c); //正确,如果用显示指定类型,可以发生隐式类型转换
26}

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

调用规则如下:

  1. 如果函数模板和普通函数都可以实现,优先调用普通函数
  2. 可以通过空模板参数列表来强制调用函数模板
  3. 函数模板也可以发生重载
  4. 如果函数模板可以产生更好的匹配,优先调用函数模板

总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性

 1//普通函数与函数模板调用规则
 2void myPrint(int a, int b)
 3{
 4	cout << "调用的普通函数" << endl;
 5}
 6
 7template<typename T>
 8void myPrint(T a, T b) 
 9{ 
10	cout << "调用的模板" << endl;
11}
12
13template<typename T>
14void myPrint(T a, T b, T c) 
15{ 
16	cout << "调用重载的模板" << endl; 
17}
18
19void test01()
20{
21	//1、如果函数模板和普通函数都可以实现,优先调用普通函数
22	// 注意 如果告诉编译器  普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到
23	int a = 10;
24	int b = 20;
25	myPrint(a, b); //调用普通函数
26
27	//2、可以通过空模板参数列表来强制调用函数模板
28	myPrint<>(a, b); //调用函数模板
29
30	//3、函数模板也可以发生重载
31	int c = 30;
32	myPrint(a, b, c); //调用重载的函数模板
33
34	//4、 如果函数模板可以产生更好的匹配,优先调用函数模板
35	char c1 = 'a';
36	char c2 = 'b';
37	myPrint(c1, c2); //调用函数模板
38}

模板的局限性

例如:

1	template<class T>
2	void f(T a, T b)
3	{ 
4    	a = b;
5    }

在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了

再例如:

1	template<class T>
2	void f(T a, T b)
3	{ 
4    	if(a > b) { ... }
5    }

在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行

因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

 1//普通函数模板
 2template<class T>
 3bool myCompare(T& a, T& b)
 4{
 5	if (a == b)
 6	{
 7		return true;
 8	}
 9	else
10	{
11		return false;
12	}
13}
14
15
16//具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型
17//具体化优先于常规模板
18template<> bool myCompare(Person &p1, Person &p2)
19{
20	if ( p1.m_Name  == p2.m_Name && p1.m_Age == p2.m_Age)
21	{
22		return true;
23	}
24	else
25	{
26		return false;
27	}
28}
29
30void test01()
31{
32	int a = 10;
33	int b = 20;
34	//内置数据类型可以直接使用通用的函数模板
35	bool ret = myCompare(a, b);
36	if (ret)
37	{
38		cout << "a == b " << endl;
39	}
40	else
41	{
42		cout << "a != b " << endl;
43	}
44}
45
46void test02()
47{
48	Person p1("Tom", 10);
49	Person p2("Tom", 10);
50	//自定义数据类型,不会调用普通的函数模板
51	//可以创建具体化的Person数据类型的模板,用于特殊处理这个类型
52	bool ret = myCompare(p1, p2);
53	if (ret)
54	{
55		cout << "p1 == p2 " << endl;
56	}
57	else
58	{
59		cout << "p1 != p2 " << endl;
60	}
61}

类模板

类模板语法

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

语法:

1template<typename T>
2

解释:

template — 声明创建模板

typename — 表面其后面的符号是一种数据类型,可以用class代替

T — 通用的数据类型,名称可以替换,通常为大写字母

示例:

 1#include <string>
 2//类模板
 3template<class NameType, class AgeType> 
 4class Person
 5{
 6public:
 7	Person(NameType name, AgeType age)
 8	{
 9		this->mName = name;
10		this->mAge = age;
11	}
12	void showPerson()
13	{
14		cout << "name: " << this->mName << " age: " << this->mAge << endl;
15	}
16public:
17	NameType mName;
18	AgeType mAge;
19};
20
21void test01()
22{
23	// 指定NameType 为string类型,AgeType 为 int类型
24	Person<string, int>P1("孙悟空", 999);
25	P1.showPerson();
26}

类模板与函数模板区别

类模板与函数模板区别主要有两点:

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数
 1#include <string>
 2//类模板
 3template<class NameType, class AgeType = int> 
 4class Person
 5{
 6public:
 7	Person(NameType name, AgeType age)
 8	{
 9		this->mName = name;
10		this->mAge = age;
11	}
12	void showPerson()
13	{
14		cout << "name: " << this->mName << " age: " << this->mAge << endl;
15	}
16public:
17	NameType mName;
18	AgeType mAge;
19};
20
21//1、类模板没有自动类型推导的使用方式
22void test01()
23{
24	// Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导
25	Person <string ,int>p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板
26	p.showPerson();
27}
28
29//2、类模板在模板参数列表中可以有默认参数
30void test02()
31{
32	Person <string> p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数
33	p.showPerson();
34}

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

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

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建

类模板对象做函数参数

一共有三种传入方式:

  1. 指定传入的类型 — 直接显示对象的数据类型
  2. 参数模板化 — 将对象中的参数变为模板进行传递
  3. 整个类模板化 — 将这个对象类型 模板化进行传递
 1//1、指定传入的类型
 2void printPerson1(Person<string, int> &p) 
 3{
 4	p.showPerson();
 5}
 6void test01()
 7{
 8	Person <string, int >p("孙悟空", 100);
 9	printPerson1(p);
10}
11
12//2、参数模板化
13template <class T1, class T2>
14void printPerson2(Person<T1, T2>&p)
15{
16	p.showPerson();
17	cout << "T1的类型为: " << typeid(T1).name() << endl;
18	cout << "T2的类型为: " << typeid(T2).name() << endl;
19}
20void test02()
21{
22	Person <string, int >p("猪八戒", 90);
23	printPerson2(p);
24}
25
26//3、整个类模板化
27template<class T>
28void printPerson3(T & p)
29{
30	cout << "T的类型为: " << typeid(T).name() << endl;
31	p.showPerson();
32
33}
34void test03()
35{
36	Person <string, int >p("唐僧", 30);
37	printPerson3(p);
38}

类模板与继承

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

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需变为类模板
 1template<class T>
 2class Base
 3{
 4	T m;
 5};
 6
 7//class Son:public Base  //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
 8class Son :public Base<int> //必须指定一个类型
 9{
10};
11void test01()
12{
13	Son c;
14}
15
16//类模板继承类模板 ,可以用T2指定父类中的T类型
17template<class T1, class T2>
18class Son2 :public Base<T2>
19{
20public:
21	Son2()
22	{
23		cout << typeid(T1).name() << endl;
24		cout << typeid(T2).name() << endl;
25	}
26};
27
28void test02()
29{
30	Son2<int, char> child1;
31}

类模板成员函数 类外实现

 1#include <string>
 2
 3//类模板中成员函数类外实现
 4template<class T1, class T2>
 5class Person {
 6public:
 7	//成员函数类内声明
 8	Person(T1 name, T2 age);
 9	void showPerson();
10
11public:
12	T1 m_Name;
13	T2 m_Age;
14};
15
16//构造函数 类外实现
17template<class T1, class T2>
18Person<T1, T2>::Person(T1 name, T2 age) {
19	this->m_Name = name;
20	this->m_Age = age;
21}
22
23//成员函数 类外实现
24template<class T1, class T2>
25void Person<T1, T2>::showPerson() {
26	cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
27}
28
29void test01()
30{
31	Person<string, int> p("Tom", 20);
32	p.showPerson();
33}

类模板分文件编写

问题:

  • 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决:

  • 解决方式1:直接包含.cpp源文件
  • 解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp

person.hpp中代码:

 1#pragma once
 2#include <iostream>
 3using namespace std;
 4#include <string>
 5
 6template<class T1, class T2>
 7class Person {
 8public:
 9	Person(T1 name, T2 age);
10	void showPerson();
11public:
12	T1 m_Name;
13	T2 m_Age;
14};
15
16//构造函数 类外实现
17template<class T1, class T2>
18Person<T1, T2>::Person(T1 name, T2 age) {
19	this->m_Name = name;
20	this->m_Age = age;
21}
22
23//成员函数 类外实现
24template<class T1, class T2>
25void Person<T1, T2>::showPerson() {
26	cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
27}

类模板分文件编写.cpp中代码

 1#include<iostream>
 2using namespace std;
 3
 4//#include "person.h"
 5#include "person.cpp" //解决方式1,包含cpp源文件
 6
 7//解决方式2,将声明和实现写到一起,文件后缀名改为.hpp
 8#include "person.hpp"
 9void test01()
10{
11	Person<string, int> p("Tom", 10);
12	p.showPerson();
13}
14
15int main() {
16
17	test01();
18
19	system("pause");
20
21	return 0;
22}

类模板与友元

全局函数类内实现 - 直接在类内声明友元即可

全局函数类外实现 - 需要提前让编译器知道全局函数的存在

建议全局函数做类内实现,用法简单,而且编译器可以直接识别

 1#include <string>
 2
 3//2、全局函数配合友元  类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元
 4template<class T1, class T2> class Person;
 5
 6//如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到
 7//template<class T1, class T2> void printPerson2(Person<T1, T2> & p); 
 8
 9template<class T1, class T2>
10void printPerson2(Person<T1, T2> & p)
11{
12	cout << "类外实现 ---- 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
13}
14
15template<class T1, class T2>
16class Person
17{
18	//1、全局函数配合友元   类内实现
19	friend void printPerson(Person<T1, T2> & p)
20	{
21		cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
22	}
23
24
25	//全局函数配合友元  类外实现
26	friend void printPerson2<>(Person<T1, T2> & p);
27
28public:
29
30	Person(T1 name, T2 age)
31	{
32		this->m_Name = name;
33		this->m_Age = age;
34	}
35
36
37private:
38	T1 m_Name;
39	T2 m_Age;
40
41};
42
43//1、全局函数在类内实现
44void test01()
45{
46	Person <string, int >p("Tom", 20);
47	printPerson(p);
48}
49
50
51//2、全局函数在类外实现
52void test02()
53{
54	Person <string, int >p("Jerry", 30);
55	printPerson2(p);
56}

16.STL初识

STL基本概念

  • STL(Standard Template Library,标准模板库)
  • STL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator)
  • 容器算法之间通过迭代器进行无缝连接。
  • STL 几乎所有的代码都采用了模板类或者模板函数

STL六大组件

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

  1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
  2. 算法:各种常用的算法,如sort、find、copy、for_each等
  3. 迭代器:扮演了容器与算法之间的胶合剂。
  4. 仿函数:行为类似函数,可作为算法的某种策略。
  5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
  6. 空间配置器:负责空间的配置与管理。

STL中容器、算法、迭代器

**容器:**置物之所也

STL容器就是将运用最广泛的一些数据结构实现出来

常用的数据结构:数组, 链表,树, 栈, 队列, 集合, 映射表 等

这些容器分为序列式容器关联式容器两种:

序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。 ​ 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系

**算法:**问题之解法也

有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)

算法分为:质变算法非质变算法

质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等

非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等

**迭代器:**容器和算法之间粘合剂

提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。

每个容器都有自己专属的迭代器

迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针

迭代器种类:

种类 功能 支持运算
输入迭代器 对数据的只读访问 只读,支持++、==、!=
输出迭代器 对数据的只写访问 只写,支持++
前向迭代器 读写操作,并能向前推进迭代器 读写,支持++、==、!=
双向迭代器 读写操作,并能向前和向后操作 读写,支持++、–,
随机访问迭代器 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 读写,支持++、–、[n]、-n、<、<=、>、>=

常用的容器中迭代器种类为双向迭代器,和随机访问迭代器

容器算法迭代器初识

vector存放内置数据类型

 1#include <vector>
 2#include <algorithm>
 3
 4void MyPrint(int val)
 5{
 6	cout << val << endl;
 7}
 8
 9void test01() {
10
11	//创建vector容器对象,并且通过模板参数指定容器中存放的数据的类型
12	vector<int> v;
13	//向容器中放数据
14	v.push_back(10);
15	v.push_back(20);
16	v.push_back(30);
17	v.push_back(40);
18
19	//每一个容器都有自己的迭代器,迭代器是用来遍历容器中的元素
20	//v.begin()返回迭代器,这个迭代器指向容器中第一个数据
21	//v.end()返回迭代器,这个迭代器指向容器元素的最后一个元素的下一个位置
22	//vector<int>::iterator 拿到vector<int>这种容器的迭代器类型
23
24	vector<int>::iterator pBegin = v.begin();
25	vector<int>::iterator pEnd = v.end();
26
27	//第一种遍历方式:
28	while (pBegin != pEnd) {
29		cout << *pBegin << endl;
30		pBegin++;
31	}
32
33	
34	//第二种遍历方式:
35	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
36		cout << *it << endl;
37	}
38	cout << endl;
39
40	//第三种遍历方式:
41	//使用STL提供标准遍历算法  头文件 algorithm
42	for_each(v.begin(), v.end(), MyPrint);
43}

Vector存放自定义数据类型

 1//存放对象
 2void test01() {
 3
 4	vector<Person> v;
 5
 6	//创建数据
 7	Person p1("aaa", 10);
 8	Person p2("bbb", 20);
 9	Person p3("ccc", 30);
10	Person p4("ddd", 40);
11	Person p5("eee", 50);
12
13	v.push_back(p1);
14	v.push_back(p2);
15	v.push_back(p3);
16	v.push_back(p4);
17	v.push_back(p5);
18
19	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
20		cout << "Name:" << (*it).mName << " Age:" << (*it).mAge << endl;
21
22	}
23}
24
25
26//放对象指针
27void test02() {
28
29	vector<Person*> v;
30
31	//创建数据
32	Person p1("aaa", 10);
33	Person p2("bbb", 20);
34	Person p3("ccc", 30);
35	Person p4("ddd", 40);
36	Person p5("eee", 50);
37
38	v.push_back(&p1);
39	v.push_back(&p2);
40	v.push_back(&p3);
41	v.push_back(&p4);
42	v.push_back(&p5);
43
44	for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {
45		Person * p = (*it);
46		cout << "Name:" << p->mName << " Age:" << (*it)->mAge << endl;
47	}
48}

Vector容器嵌套容器

 1#include <vector>
 2
 3//容器嵌套容器
 4void test01() {
 5
 6	vector< vector<int> >  v;
 7
 8	vector<int> v1;
 9	vector<int> v2;
10	vector<int> v3;
11	vector<int> v4;
12
13	for (int i = 0; i < 4; i++) {
14		v1.push_back(i + 1);
15		v2.push_back(i + 2);
16		v3.push_back(i + 3);
17		v4.push_back(i + 4);
18	}
19
20	//将容器元素插入到vector v中
21	v.push_back(v1);
22	v.push_back(v2);
23	v.push_back(v3);
24	v.push_back(v4);
25
26
27	for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) {
28
29		for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) {
30			cout << *vit << " ";
31		}
32		cout << endl;
33	}
34
35}

17.STL- 常用容器

string容器

string基本概念

string 类内部封装了很多成员方法

例如:查找find,拷贝copy,删除delete 替换replace,插入insert

string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责

string构造函数

构造函数原型:

  • string(); //创建一个空的字符串 例如: string str; string(const char* s); //使用字符串s初始化
  • string(const string& str); //使用一个string对象初始化另一个string对象
  • string(int n, char c); //使用n个字符c初始化
 1#include <string>
 2//string构造
 3void test01()
 4{
 5	string s1; //创建空字符串,调用无参构造函数
 6	cout << "str1 = " << s1 << endl;
 7
 8	const char* str = "hello world";
 9	string s2(str); //把c_string转换成了string
10
11	cout << "str2 = " << s2 << endl;
12
13	string s3(s2); //调用拷贝构造函数
14	cout << "str3 = " << s3 << endl;
15
16	string s4(10, 'a');
17	cout << "str3 = " << s3 << endl;
18}

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赋给当前字符串
 1void test01()
 2{
 3	string str1;
 4	str1 = "hello world";
 5	cout << "str1 = " << str1 << endl;
 6
 7	string str2;
 8	str2 = str1;
 9	cout << "str2 = " << str2 << endl;
10
11	string str3;
12	str3 = 'a';
13	cout << "str3 = " << str3 << endl;
14
15	string str4;
16	str4.assign("hello c++");
17	cout << "str4 = " << str4 << endl;
18
19	string str5;
20	str5.assign("hello c++",5);
21	cout << "str5 = " << str5 << endl;
22
23
24	string str6;
25	str6.assign(str5);
26	cout << "str6 = " << str6 << endl;
27
28	string str7;
29	str7.assign(5, 'x');
30	cout << "str7 = " << str7 << endl;
31}
32

string字符串拼接

  • string& operator+=(const char* str); //重载+=操作符
  • string& operator+=(const char c); //重载+=操作符
  • string& operator+=(const string& str); //重载+=操作符
  • string& append(const char *s); //把字符串s连接到当前字符串结尾
  • string& append(const char *s, int n); //把字符串s的前n个字符连接到当前字符串结尾
  • string& append(const string &s); //同operator+=(const string& str)
  • string& append(const string &s, int pos, int n);//字符串s中从pos开始的n个字符连接到字符串结尾
 1void test01()
 2{
 3	string str1 = "我";
 4
 5	str1 += "爱玩游戏";
 6
 7	cout << "str1 = " << str1 << endl;
 8	
 9	str1 += ':';
10
11	cout << "str1 = " << str1 << endl;
12
13	string str2 = "LOL DNF";
14
15	str1 += str2;
16
17	cout << "str1 = " << str1 << endl;
18
19	string str3 = "I";
20	str3.append(" love ");
21	str3.append("game abcde", 4);
22	//str3.append(str2);
23	str3.append(str2, 4, 3); // 从下标4位置开始 ,截取3个字符,拼接到字符串末尾
24	cout << "str3 = " << str3 << endl;
25}

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

find找到字符串后返回查找的第一个字符位置,找不到返回-1

 1//查找和替换
 2void test01()
 3{
 4	//查找
 5	string str1 = "abcdefgde";
 6
 7	int pos = str1.find("de");
 8
 9	if (pos == -1)
10	{
11		cout << "未找到" << endl;
12	}
13	else
14	{
15		cout << "pos = " << pos << endl;
16	}
17	
18
19	pos = str1.rfind("de");
20
21	cout << "pos = " << pos << endl;
22
23}
24
25void test02()
26{
27	//替换
28	string str1 = "abcdefgde";
29	str1.replace(1, 3, "1111");
30
31	cout << "str1 = " << str1 << endl;
32}

string字符串比较

  • 字符串比较是按字符的ASCII码进行对比

= 返回 0

> 返回 1

< 返回 -1

  • int compare(const string &s) const; //与字符串s比较
  • int compare(const char *s) const; //与字符串s比较

string字符存取

  • char& operator[](int n); //通过[]方式取字符
  • char& at(int n); //通过at方法获取字符
 1void test01()
 2{
 3	string str = "hello world";
 4
 5	for (int i = 0; i < str.size(); i++)
 6	{
 7		cout << str[i] << " ";
 8	}
 9	cout << endl;
10
11	for (int i = 0; i < str.size(); i++)
12	{
13		cout << str.at(i) << " ";
14	}
15	cout << endl;
16
17
18	//字符修改
19	str[0] = 'x';
20	str.at(1) = 'x';
21	cout << str << endl;
22	
23}

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子串

string substr(int pos = 0, int n = npos) const; //返回由pos开始的n个字符组成的字符串

 1//子串
 2void test01()
 3{
 4
 5	string str = "abcdefg";
 6	string subStr = str.substr(1, 3);
 7	cout << "subStr = " << subStr << endl;
 8
 9	string email = "[email protected]";
10	int pos = email.find("@");
11	string username = email.substr(0, pos);
12	cout << "username: " << username << endl;
13
14}

vector容器

vector基本概念

vector数据结构和数组非常相似,也称为单端数组

vector可以动态扩展, 并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间

vector容器的迭代器是支持随机访问的迭代器

vector构造函数

  • vector<T> v; //采用模板实现类实现,默认构造函数
  • vector(v.begin(), v.end()); //将v[begin(), end())区间中的元素拷贝给本身。
  • vector(n, elem); //构造函数将n个elem拷贝给本身。
  • vector(const vector &vec); //拷贝构造函数。
 1#include <vector>
 2
 3void printVector(vector<int>& v) {
 4
 5	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
 6		cout << *it << " ";
 7	}
 8	cout << endl;
 9}
10
11void test01()
12{
13	vector<int> v1; //无参构造
14	for (int i = 0; i < 10; i++)
15	{
16		v1.push_back(i);
17	}
18	printVector(v1);
19
20	vector<int> v2(v1.begin(), v1.end());
21	printVector(v2);
22
23	vector<int> v3(10, 100);
24	printVector(v3);
25	
26	vector<int> v4(v3);
27	printVector(v4);
28}

vector赋值操作

  • vector& operator=(const vector &vec);//重载等号操作符

  • assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。

  • assign(n, elem); //将n个elem拷贝赋值给本身。

 1#include <vector>
 2
 3void printVector(vector<int>& v) {
 4
 5	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
 6		cout << *it << " ";
 7	}
 8	cout << endl;
 9}
10
11//赋值操作
12void test01()
13{
14	vector<int> v1; //无参构造
15	for (int i = 0; i < 10; i++)
16	{
17		v1.push_back(i);
18	}
19	printVector(v1);
20
21	vector<int>v2;
22	v2 = v1;
23	printVector(v2);
24
25	vector<int>v3;
26	v3.assign(v1.begin(), v1.end());
27	printVector(v3);
28
29	vector<int>v4;
30	v4.assign(10, 100);
31	printVector(v4);
32}

vector容量和大小

  • empty(); //判断容器是否为空

  • capacity(); //容器的容量

  • size(); //返回容器中元素的个数

  • resize(int num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

  • resize(int num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除

 1void test01()
 2{
 3	vector<int> v1;
 4	for (int i = 0; i < 10; i++)
 5	{
 6		v1.push_back(i);
 7	}
 8	printVector(v1);
 9	if (v1.empty())
10	{
11		cout << "v1为空" << endl;
12	}
13	else
14	{
15		cout << "v1不为空" << endl;
16		cout << "v1的容量 = " << v1.capacity() << endl;
17		cout << "v1的大小 = " << v1.size() << endl;
18	}
19
20	//resize 重新指定大小 ,若指定的更大,默认用0填充新位置,可以利用重载版本替换默认填充
21	v1.resize(15,10);
22	printVector(v1);
23
24	//resize 重新指定大小 ,若指定的更小,超出部分元素被删除
25	v1.resize(5);
26	printVector(v1);
27}

vector插入和删除

  • push_back(ele); //尾部插入元素ele
  • pop_back(); //删除最后一个元素
  • insert(const_iterator pos, ele); //迭代器指向位置pos插入元素ele
  • insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count个元素ele
  • erase(const_iterator pos); //删除迭代器指向的元素
  • erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
  • clear(); //删除容器中所有元素
 1void test01()
 2{
 3	vector<int> v1;
 4	//尾插
 5	v1.push_back(10);
 6	v1.push_back(20);
 7	v1.push_back(30);
 8	v1.push_back(40);
 9	v1.push_back(50);
10	printVector(v1);
11	//尾删
12	v1.pop_back();
13	printVector(v1);
14	//插入
15	v1.insert(v1.begin(), 100);
16	printVector(v1);
17
18	v1.insert(v1.begin(), 2, 1000);
19	printVector(v1);
20
21	//删除
22	v1.erase(v1.begin());
23	printVector(v1);
24
25	//清空
26	v1.erase(v1.begin(), v1.end());
27	v1.clear();
28	printVector(v1);
29}

vector数据存取

  • at(int idx); //返回索引idx所指的数据
  • operator[]; //返回索引idx所指的数据
  • front(); //返回容器中第一个数据元素
  • back(); //返回容器中最后一个数据元素

vector互换容器

swap(vec); // 将vec与本身的元素互换

swap可以使两个容器互换,可以达到实用的收缩内存效果

 1void test01()
 2{
 3	vector<int>v1;
 4	for (int i = 0; i < 10; i++)
 5	{
 6		v1.push_back(i);
 7	}
 8	printVector(v1);
 9
10	vector<int>v2;
11	for (int i = 10; i > 0; i--)
12	{
13		v2.push_back(i);
14	}
15	printVector(v2);
16
17	//互换容器
18	cout << "互换后" << endl;
19	v1.swap(v2);
20	printVector(v1);
21	printVector(v2);
22}
23
24void test02()
25{
26	vector<int> v;
27	for (int i = 0; i < 100000; i++) {
28		v.push_back(i);
29	}
30
31	cout << "v的容量为:" << v.capacity() << endl;
32	cout << "v的大小为:" << v.size() << endl;
33
34	v.resize(3);
35
36	cout << "v的容量为:" << v.capacity() << endl;
37	cout << "v的大小为:" << v.size() << endl;
38
39	//收缩内存
40	vector<int>(v).swap(v); //匿名对象
41
42	cout << "v的容量为:" << v.capacity() << endl;
43	cout << "v的大小为:" << v.size() << endl;
44}

vector预留空间

减少vector在动态扩展容量时的扩展次数

reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问。

 1void test01()
 2{
 3	vector<int> v;
 4
 5	//预留空间
 6	v.reserve(100000);
 7
 8	int num = 0;
 9	int* p = NULL;
10	for (int i = 0; i < 100000; i++) {
11		v.push_back(i);
12		if (p != &v[0]) {
13			p = &v[0];
14			num++;
15		}
16	}
17
18	cout << "num:" << num << endl;
19}

deque容器

deque容器基本概念

双端数组,可以对头端进行插入删除操作

deque与vector区别:

  • vector对于头部的插入删除效率低,数据量越大,效率越低
  • deque相对而言,对头部的插入删除速度回比vector快
  • vector访问元素时的速度会比deque快,这和两者内部实现有关

deque内部工作原理:

deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据

中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间

deque构造函数

  • deque<T> deqT; //默认构造形式
  • deque(beg, end); //构造函数将[beg, end)区间中的元素拷贝给本身。
  • deque(n, elem); //构造函数将n个elem拷贝给本身。
  • deque(const deque &deq); //拷贝构造函数

deque赋值操作

  • deque& operator=(const deque &deq); //重载等号操作符

  • assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。

  • assign(n, elem); //将n个elem拷贝赋值给本身。

deque大小操作

  • deque.empty(); //判断容器是否为空

  • deque.size(); //返回容器中元素的个数

  • deque.resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

  • deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

deque 插入和删除

两端插入操作:

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

指定位置操作:

  • insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置。

  • insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值。

  • insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值。

  • clear(); //清空容器的所有数据

  • erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置。

  • erase(pos); //删除pos位置的数据,返回下一个数据的位置。

 1void test01()
 2{
 3	deque<int> d;
 4	//尾插
 5	d.push_back(10);
 6	d.push_back(20);
 7	//头插
 8	d.push_front(100);
 9	d.push_front(200);
10
11	printDeque(d);
12
13	//尾删
14	d.pop_back();
15	//头删
16	d.pop_front();
17	printDeque(d);
18}
19
20//插入
21void test02()
22{
23	deque<int> d;
24	d.push_back(10);
25	d.push_back(20);
26	d.push_front(100);
27	d.push_front(200);
28	printDeque(d);
29
30	d.insert(d.begin(), 1000);
31	printDeque(d);
32
33	d.insert(d.begin(), 2,10000);
34	printDeque(d);
35
36	deque<int>d2;
37	d2.push_back(1);
38	d2.push_back(2);
39	d2.push_back(3);
40
41	d.insert(d.begin(), d2.begin(), d2.end());
42	printDeque(d);
43
44}
45
46//删除
47void test03()
48{
49	deque<int> d;
50	d.push_back(10);
51	d.push_back(20);
52	d.push_front(100);
53	d.push_front(200);
54	printDeque(d);
55
56	d.erase(d.begin());
57	printDeque(d);
58
59	d.erase(d.begin(), d.end());
60	d.clear();
61	printDeque(d);
62}

deque 数据存取

  • at(int idx); //返回索引idx所指的数据
  • operator[]; //返回索引idx所指的数据
  • front(); //返回容器中第一个数据元素
  • back(); //返回容器中最后一个数据元素

deque 排序

sort(iterator beg, iterator end) //对beg和end区间内元素进行排序

 1void test01()
 2{
 3
 4	deque<int> d;
 5	d.push_back(10);
 6	d.push_back(20);
 7	d.push_front(100);
 8	d.push_front(200);
 9
10	printDeque(d);
11	sort(d.begin(), d.end());
12	printDeque(d);
13
14}

stack容器

stack 基本概念

stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口

stack 常用接口

构造函数:

  • stack<T> stk; //stack采用模板类实现, stack对象的默认构造形式
  • stack(const stack &stk); //拷贝构造函数

赋值操作:

  • stack& operator=(const stack &stk); //重载等号操作符

数据存取:

  • push(elem); //向栈顶添加元素
  • pop(); //从栈顶移除第一个元素
  • top(); //返回栈顶元素

大小操作:

  • empty(); //判断堆栈是否为空
  • size(); //返回栈的大小
 1#include <stack>
 2
 3//栈容器常用接口
 4void test01()
 5{
 6	//创建栈容器 栈容器必须符合先进后出
 7	stack<int> s;
 8
 9	//向栈中添加元素,叫做 压栈 入栈
10	s.push(10);
11	s.push(20);
12	s.push(30);
13
14	while (!s.empty()) {
15		//输出栈顶元素
16		cout << "栈顶元素为: " << s.top() << endl;
17		//弹出栈顶元素
18		s.pop();
19	}
20	cout << "栈的大小为:" << s.size() << endl;
21
22}

queue 容器

queue 基本概念

Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口

queue 常用接口

构造函数:

  • queue<T> que; //queue采用模板类实现,queue对象的默认构造形式
  • queue(const queue &que); //拷贝构造函数

赋值操作:

  • queue& operator=(const queue &que); //重载等号操作符

数据存取:

  • push(elem); //往队尾添加元素
  • pop(); //从队头移除第一个元素
  • back(); //返回最后一个元素
  • front(); //返回第一个元素

大小操作:

  • empty(); //判断堆栈是否为空
  • size(); //返回栈的大小
 1void test01() {
 2
 3	//创建队列
 4	queue<Person> q;
 5
 6	//准备数据
 7	Person p1("唐僧", 30);
 8	Person p2("孙悟空", 1000);
 9	Person p3("猪八戒", 900);
10	Person p4("沙僧", 800);
11
12	//向队列中添加元素  入队操作
13	q.push(p1);
14	q.push(p2);
15	q.push(p3);
16	q.push(p4);
17
18	//队列不提供迭代器,更不支持随机访问	
19	while (!q.empty()) {
20		//输出队头元素
21		cout << "队头元素-- 姓名: " << q.front().m_Name 
22              << " 年龄: "<< q.front().m_Age << endl;
23        
24		cout << "队尾元素-- 姓名: " << q.back().m_Name  
25              << " 年龄: " << q.back().m_Age << endl;
26        
27		cout << endl;
28		//弹出队头元素
29		q.pop();
30	}
31
32	cout << "队列大小为:" << q.size() << endl;
33}

list容器

list基本概念

将数据进行链式存储

STL中的链表是一个双向循环链表

由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。

list构造函数

  • list<T> lst; //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 大小操作

  • size(); //返回容器中元素的个数

  • empty(); //判断容器是否为空

  • resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

  • resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

      ​					    //如果容器变短,则末尾超出容器长度的元素被删除。
    

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)区间的数据,无返回值。
  • clear();//移除容器的所有数据
  • erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
  • erase(pos);//删除pos位置的数据,返回下一个数据的位置。
  • remove(elem);//删除容器中所有与elem值匹配的元素。

list 数据存取

  • front(); //返回第一个元素。
  • back(); //返回最后一个元素。

list容器中不可以通过[]或者at方式访问数据

 1//数据存取
 2void test01()
 3{
 4	list<int>L1;
 5	L1.push_back(10);
 6	L1.push_back(20);
 7	L1.push_back(30);
 8	L1.push_back(40);
 9
10	
11	//cout << L1.at(0) << endl;//错误 不支持at访问数据
12	//cout << L1[0] << endl; //错误  不支持[]方式访问数据
13	cout << "第一个元素为: " << L1.front() << endl;
14	cout << "最后一个元素为: " << L1.back() << endl;
15
16	//list容器的迭代器是双向迭代器,不支持随机访问
17	list<int>::iterator it = L1.begin();
18	//it = it + 1;//错误,不可以跳跃访问,即使是+1
19    //区别it++
20}

list 反转和排序

  • reverse(); //反转链表
  • sort(); //链表排序
 1void printList(const list<int>& L) {
 2
 3	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
 4		cout << *it << " ";
 5	}
 6	cout << endl;
 7}
 8
 9bool myCompare(int val1 , int val2)
10{
11	return val1 > val2;
12}
13
14//反转和排序
15void test01()
16{
17	list<int> L;
18	L.push_back(90);
19	L.push_back(30);
20	L.push_back(20);
21	L.push_back(70);
22	printList(L);
23
24	//反转容器的元素
25	L.reverse();
26	printList(L);
27
28	//排序
29	L.sort(); //默认的排序规则 从小到大
30	printList(L);
31
32	L.sort(myCompare); //指定规则,从大到小
33	printList(L);
34}

set/ multiset 容器

set基本概念

  • 所有元素都会在插入时自动被排序
  • set/multiset属于关联式容器,底层结构是用二叉树实现。

set和multiset区别

  • set不允许容器中有重复的元素
  • multiset允许容器中有重复的元素

set构造和赋值

构造:

  • set<T> st; //默认构造函数:
  • set(const set &st); //拷贝构造函数

赋值:

  • set& operator=(const set &st); //重载等号操作符
 1void test01()
 2{
 3	set<int> s1;
 4
 5	s1.insert(10);
 6	s1.insert(30);
 7	s1.insert(20);
 8	s1.insert(40);
 9	printSet(s1);
10
11	//拷贝构造
12	set<int>s2(s1);
13	printSet(s2);
14
15	//赋值
16	set<int>s3;
17	s3 = s2;
18	printSet(s3);
19}

set大小和交换

  • size(); //返回容器中元素的数目
  • empty(); //判断容器是否为空
  • swap(st); //交换两个集合容器
 1void test01()
 2{
 3
 4	set<int> s1;
 5	
 6	s1.insert(10);
 7	s1.insert(30);
 8	s1.insert(20);
 9	s1.insert(40);
10
11	if (s1.empty())
12	{
13		cout << "s1为空" << endl;
14	}
15	else
16	{
17		cout << "s1不为空" << endl;
18		cout << "s1的大小为: " << s1.size() << endl;
19	}
20
21}
22
23//交换
24void test02()
25{
26	set<int> s1;
27
28	s1.insert(10);
29	s1.insert(30);
30	s1.insert(20);
31	s1.insert(40);
32
33	set<int> s2;
34
35	s2.insert(100);
36	s2.insert(300);
37	s2.insert(200);
38	s2.insert(400);
39
40	cout << "交换前" << endl;
41	printSet(s1);
42	printSet(s2);
43	cout << endl;
44
45	cout << "交换后" << endl;
46	s1.swap(s2);
47	printSet(s1);
48	printSet(s2);
49}

set插入和删除

  • insert(elem); //在容器中插入元素。
  • clear(); //清除所有元素
  • erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
  • erase(beg, end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
  • erase(elem); //删除容器中值为elem的元素。
 1void test01()
 2{
 3	set<int> s1;
 4	//插入
 5	s1.insert(10);
 6	s1.insert(30);
 7	s1.insert(20);
 8	s1.insert(40);
 9	printSet(s1);
10
11	//删除
12	s1.erase(s1.begin());
13	printSet(s1);
14
15	s1.erase(30);
16	printSet(s1);
17
18	//清空
19	//s1.erase(s1.begin(), s1.end());
20	s1.clear();
21	printSet(s1);
22}

set查找和统计

  • find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
  • count(key); //统计key的元素个数
 1void test01()
 2{
 3	set<int> s1;
 4	//插入
 5	s1.insert(10);
 6	s1.insert(30);
 7	s1.insert(20);
 8	s1.insert(40);
 9	
10	//查找
11	set<int>::iterator pos = s1.find(30);
12
13	if (pos != s1.end())
14	{
15		cout << "找到了元素 : " << *pos << endl;
16	}
17	else
18	{
19		cout << "未找到元素" << endl;
20	}
21
22	//统计
23	int num = s1.count(30);
24	cout << "num = " << num << endl;
25}

set和multiset区别

  • set不可以插入重复数据,而multiset可以
  • set插入数据的同时会返回插入结果,表示插入是否成功
  • multiset不会检测数据,因此可以插入重复数据
 1void test01()
 2{
 3	set<int> s;
 4	pair<set<int>::iterator, bool>  ret = s.insert(10);
 5	if (ret.second) {
 6		cout << "第一次插入成功!" << endl;
 7	}
 8	else {
 9		cout << "第一次插入失败!" << endl;
10	}
11
12	ret = s.insert(10);
13	if (ret.second) {
14		cout << "第二次插入成功!" << endl;
15	}
16	else {
17		cout << "第二次插入失败!" << endl;
18	}
19    
20	//multiset
21	multiset<int> ms;
22	ms.insert(10);
23	ms.insert(10);
24
25	for (multiset<int>::iterator it = ms.begin(); it != ms.end(); it++) {
26		cout << *it << " ";
27	}
28	cout << endl;
29}

pair对组创建

成对出现的数据,利用对组可以返回两个数据

  • pair<type, type> p ( value1, value2 );
  • pair<type, type> p = make_pair( value1, value2 );
1void test01()
2{
3	pair<string, int> p(string("Tom"), 20);
4	cout << "姓名: " <<  p.first << " 年龄: " << p.second << endl;
5
6	pair<string, int> p2 = make_pair("Jerry", 10);
7	cout << "姓名: " << p2.first << " 年龄: " << p2.second << endl;
8}

set容器排序

set容器默认排序规则为从小到大,利用仿函数,可以改变排序规则

对于自定义数据类型,set必须指定排序规则才可以插入数据

 1class MyCompare 
 2{
 3public:
 4	bool operator()(int v1, int v2) {
 5		return v1 > v2;
 6	}
 7};
 8void test01() 
 9{    
10	set<int> s1;
11	s1.insert(10);
12	s1.insert(40);
13	s1.insert(20);
14	s1.insert(30);
15	s1.insert(50);
16
17	//默认从小到大
18	for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
19		cout << *it << " ";
20	}
21	cout << endl;
22
23	//指定排序规则
24	set<int,MyCompare> s2;
25	s2.insert(10);
26	s2.insert(40);
27	s2.insert(20);
28	s2.insert(30);
29	s2.insert(50);
30
31	for (set<int, MyCompare>::iterator it = s2.begin(); it != s2.end(); it++) {
32		cout << *it << " ";
33	}
34	cout << endl;
35}
 1class Person
 2{
 3public:
 4	Person(string name, int age)
 5	{
 6		this->m_Name = name;
 7		this->m_Age = age;
 8	}
 9
10	string m_Name;
11	int m_Age;
12
13};
14class comparePerson
15{
16public:
17	bool operator()(const Person& p1, const Person &p2)
18	{
19		//按照年龄进行排序  降序
20		return p1.m_Age > p2.m_Age;
21	}
22};
23
24void test01()
25{
26	set<Person, comparePerson> s;
27
28	Person p1("刘备", 23);
29	Person p2("关羽", 27);
30	Person p3("张飞", 25);
31	Person p4("赵云", 21);
32
33	s.insert(p1);
34	s.insert(p2);
35	s.insert(p3);
36	s.insert(p4);
37
38	for (set<Person, comparePerson>::iterator it = s.begin(); it != s.end(); it++)
39	{
40		cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << endl;
41	}
42}

map/ multimap容器

map基本概念

  • map中所有元素都是pair
  • pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
  • 所有元素都会根据元素的键值自动排序
  • map/multimap属于关联式容器,底层结构是用二叉树实现。

map和multimap区别

  • map不允许容器中有重复key值元素
  • multimap允许容器中有重复key值元素

map构造和赋值

构造:

  • map<T1, T2> mp; //map默认构造函数:
  • map(const map &mp); //拷贝构造函数

赋值:

  • map& operator=(const map &mp); //重载等号操作符
 1void printMap(map<int,int>&m)
 2{
 3	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++)
 4	{
 5		cout << "key = " << it->first << " value = " << it->second << endl;
 6	}
 7	cout << endl;
 8}
 9
10void test01()
11{
12	map<int,int>m; //默认构造
13	m.insert(pair<int, int>(1, 10));
14	m.insert(pair<int, int>(2, 20));
15	m.insert(pair<int, int>(3, 30));
16	printMap(m);
17
18	map<int, int>m2(m); //拷贝构造
19	printMap(m2);
20
21	map<int, int>m3;
22	m3 = m2; //赋值
23	printMap(m3);
24}

map大小和交换

  • size(); //返回容器中元素的数目
  • empty(); //判断容器是否为空
  • swap(st); //交换两个集合容器

map插入和删除

  • insert(elem); //在容器中插入元素。
  • clear(); //清除所有元素
  • erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
  • erase(beg, end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
  • erase(key); //删除容器中值为key的元素。
 1void test01()
 2{
 3	//插入
 4	map<int, int> m;
 5	//第一种插入方式
 6	m.insert(pair<int, int>(1, 10));
 7	//第二种插入方式
 8	m.insert(make_pair(2, 20));
 9	//第三种插入方式
10	m.insert(map<int, int>::value_type(3, 30));
11	//第四种插入方式
12	m[4] = 40; 
13	printMap(m);
14
15	//删除
16	m.erase(m.begin());
17	printMap(m);
18
19	m.erase(3);
20	printMap(m);
21
22	//清空
23	m.erase(m.begin(),m.end());
24	m.clear();
25	printMap(m);
26}

map查找和统计

  • find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
  • count(key); //统计key的元素个数
 1void test01()
 2{
 3	map<int, int>m; 
 4	m.insert(pair<int, int>(1, 10));
 5	m.insert(pair<int, int>(2, 20));
 6	m.insert(pair<int, int>(3, 30));
 7
 8	//查找
 9	map<int, int>::iterator pos = m.find(3);
10
11	if (pos != m.end())
12	{
13		cout << "找到了元素 key = " << (*pos).first << " value = " << (*pos).second << endl;
14	}
15	else
16	{
17		cout << "未找到元素" << endl;
18	}
19
20	//统计
21	int num = m.count(3);
22	cout << "num = " << num << endl;
23}

map容器排序

map容器默认排序规则为 按照key值进行 从小到大排序,利用仿函数,可以改变排序规则

 1#include <map>
 2
 3class MyCompare {
 4public:
 5	bool operator()(int v1, int v2) {
 6		return v1 > v2;
 7	}
 8};
 9
10void test01() 
11{
12	//默认从小到大排序
13	//利用仿函数实现从大到小排序
14	map<int, int, MyCompare> m;
15
16	m.insert(make_pair(1, 10));
17	m.insert(make_pair(2, 20));
18	m.insert(make_pair(3, 30));
19	m.insert(make_pair(4, 40));
20	m.insert(make_pair(5, 50));
21
22	for (map<int, int, MyCompare>::iterator it = m.begin(); it != m.end(); it++) {
23		cout << "key:" << it->first << " value:" << it->second << endl;
24	}
25}

18.STL- 函数对象

函数对象

函数对象概念