aoe1231

看得完吗

C++入门

1、C++初识

1.1、第一个C++程序

编写一个C++程序总共分为4个步骤:

  1. 创建项目;
  2. 创建文件;
  3. 编写代码;
  4. 运行程序。

1.2、注释

单行注释:// 描述信息

多行注释:/* 描述信息 */

1.3、变量

变量存在的意义:方便我们管理内存。

变量创建的语法:数据类型 变量名 = 变量初始值;  int a = 10;

1.4、常量

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

C++定义常量的两种方式:

  • #define 宏常量:#define 常量名 常量值。通常在文件上方定义,表示一个常量;
  • const 修饰的变量:const 数据类型 常量名 = 常量值。通常在变量定义前加关键字const,修饰该变量为常量,不可修改。
#include "pch.h"
#include <iostream>

using namespace std;

// 单行注释
/*
	多行注释
*/

// 定义宏常量
#define DAY 7

int main(){
	cout << "一周总共有" << DAY << "天" << endl;

	// const 修饰的变量
	const int a = 10;
	cout << "a = " << a << endl;

	system("pause");

	return 0;
}

1.5、关键字

作用:关键字是C++中预先保留的单词(标识符)。在给变量或常量起名的时候,不要用关键字。

C++关键字如下:

asm do if return typedef
auto double inline short typeid
bool dynamic_cast int signed typename
break else long sizeof union
case enum mutable static unsigned
catch explict namespace static_cast using
char export new struct virtual
class extern operator switch void
const false private template volatile
const_cast float protected this wchar_t

1.6、标识符命名规则

作用:C++规定给标识符(变量、常量)命名时,有一套自己的规则。

  • 标识符不能是关键字;
  • 标识符只能由字母、数字、下划线组成;
  • 第一个字符必须为字母或下划线;
  • 标识符中字母区分大小写。

建议:给标识符命名时,争取做到见名知义的效果,方便自己和他人的阅读。

2、数据类型

C++规定在创建一个变量或者常量时,必须要指定出相应的数据类型,否则无法给变量分配内存。

2.1、整型

作用:整型变量表示的是整型的数据。

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)

2.2、sizeof 关键字

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

语法:sizeof(数据类型/变量)

示例:

#include "pch.h"
#include <iostream>

using namespace std;

int main() {
	cout << "short 类型所占用内存空间为:" << sizeof(short) << endl;
	cout << "int 类型所占用内存空间为:" << sizeof(int) << endl;
	cout << "long 类型所占用内存空间为:" << sizeof(long) << endl;
	cout << "long long 类型所占用内存空间为:" << sizeof(long long) << endl;

	system("pause");

	return 0;
}

结果:
short 类型所占用内存空间为:2
int 类型所占用内存空间为:4
long 类型所占用内存空间为:4
long long 类型所占用内存空间为:8

2.3、实型(浮点型)

作用:用于表示小数。

浮点型变量分为2种:

  • 单精度 float;
  • 双精度 double。

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

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

示例:

#include <iostream>

using namespace std;

int main() {
	// 默认情况下,输出一个小数会显示6位有效数字,多的不显示
	float f1 = 3.1415926535f; // 不加f默认是双精度,会自动转换成单精度
	double d1 = 3.1415926535;

	cout << f1 << endl;
	cout << d1 << endl;

	// 统计float和double占用内存空间
	cout << "float占用内存空间为:" << sizeof(float) << endl;
	cout << "double占用的内存空间为:" << sizeof(double) << endl;

	// 科学计数法
	float f2 = 3e2; // 3 * 10 ^ 2
	cout << "f2=" << f2 << endl;
	float f3 = 3e-2; // 3 * 10 ^ -2
	cout << "f3=" << f3 << endl;

	system("pause");

	return 0;
}

结果:
3.14159
3.14159
float占用内存空间为:4
double占用的内存空间为:8
f2=300
f3=0.03

2.4、字符型

作用:字符型变量用户显示单个字符。

语法:char ch = 'a';

注意:

  • 在显示字符型变量时,用单引号将字符括起来,不要用双引号;
  • 单引号内只能由一个字符,不可以是字符串。

C和C++种字符型变量只占用1个字节。

字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元。

示例:

#include <iostream>

using namespace std;

int main() {
	char ch = 'a';
	cout << ch << endl;
	cout << "char字符型变量所占用内存空间:" << sizeof(char) << endl;

	// 常见错误
	// ch = "abcde"; // 错误,不可以用双引号
	// ch = 'abcde'; // 错误,不可以多个字符

	// 字符型变量对应ASCII编码.A-97,a-65
	cout << (int)ch << endl;

	system("pause");

	return 0;
}

结果:
a
char字符型变量所占用内存空间:1
97

ASCII码大致由以下两部分组成:

  • ASCII非打印控制字符:ASCII表上的数字0-31分配给了控制字符,用于控制像打印机等一些外围设备;
  • ASCII打印字符:数字32-126分配给了能在键盘上找到的字符,当查看或打印文档时就会出现。

2.5、转义字符

作用:用于表示一些不能显示出来的ASCII字符。现阶段常用的转义字符有:\n \\ \t

#include <iostream>

using namespace std;

// 转义字符
int main() {

	// 换行符 \n
	cout << "hello world\n";
	
	// 反斜杠
	cout << "\\" << endl;

	// 水平制表符
	cout << "aaaaaa\thelloworld" << endl;
	cout << "aaaaaaaa\thelloworld" << endl;
	cout << "aaaaaaaaaaaaaa\thelloworld" << endl;

	system("pause");

	return 0;
}

结果:
hello world
\
aaaaaa  helloworld
aaaaaaaa        helloworld
aaaaaaaaaaaaaa  helloworld

2.6、字符串型

作用:用于表示一串字符。

两种风格:

C风格字符串:char 变量名[] = "hello world;注意,C风格的字符串要用双引号括起来。

C++风格字符串:string 变量名 = "hello world;

演示:

#include <iostream>
#include <string> // 用C++风格字符串的时候,要包含这个头文件

using namespace std;

int main() {
	// 1、C风格字符串
	// 注意事项:加[];等号后面要用""包含字符串
	char str[] = "hello world";
	cout << str << endl;

	// 2、C++风格字符串
	// 注意事项:包含一个头文件 <string>
	string str2 = "hello world";
	cout << str2 << endl;

	system("pause");

	return 0;
}

结果:
hello world
hello world

2.7、布尔类型

作用:布尔数据类型代表真或假的值。

bool类型只有两个值:

  • true(真,本质是1);
  • false(假,本质是0)。

bool 类型占1个字节大小。

示例:

#include <iostream>

using namespace std;

int main6() {
	// 1、创建一个 bool 数据类型
	bool flag = true; // true 代表真
	cout << flag << endl;

	// 2、查看 bool 数据类型所占用空间
	cout << "bool 类型占用内存空间为:" << flag << endl;

	system("pause");

	return 0;
}

结果:
1
bool 类型占用内存空间为:1

2.8、数据的输入

作用:用于从键盘获取数据。关键字:cin。

语法:cin >> 变量

示例:

#include <iostream>
#include <string>

using namespace std;

int main() {
	// 1、整型
	int a = 0;
	cout << "请给整型变量a赋值:" << endl;
	cin >> a;
	cout << "整型变量a=" << a << endl;

	// 2、浮点型
	float f = 3.14f;
	cout << "请给浮点型变量f赋值:" << endl;
	cin >> f;
	cout << "浮点型变量f=" << f << endl;

	// 3、字符型
	char c = 'a';
	cout << "请给字符型变量c赋值:" << endl;
	cin >> c;
	cout << "字符型变量c=" << c << endl;

	// 4、字符串型
	string s = "hello";
	cout << "请给字符串型变量s赋值:" << endl;
	cin >> s;
	cout << "字符串型变量s=" << s << endl;

	// 5、布尔类型
	bool b = false;
	cout << "请给布尔型变量b赋值:" << endl;
	cin >> b; // bool 类型的值只要非0就为真
	cout << "布尔型变量b=" << b << endl;


	system("pause");

	return 0;
}

结果:
请给整型变量a赋值:
1
整型变量a=1
请给浮点型变量f赋值:
12.3
浮点型变量f=12.3
请给字符型变量c赋值:
t
字符型变量c=t
请给字符串型变量s赋值:
hello
字符串型变量s=hello
请给布尔型变量b赋值:
12
布尔型变量b=1

3、运算符

作用:用于执行代码的运算。

运算符类型 作用
算数运算符 用于处理四则运算
赋值运算符 用于将表达式的值赋给变量
比较运算符 用于表达式的比较,并返回一个真值或假值
逻辑运算符 用于根据表达式的值返回真值或假值

3.1、算数运算符

作用:用于处理四则运算。

#include <iostream>
#include <string>

using namespace std;

int main() {
	// 加减乘除
	int a1 = 10;
	int b1 = 3;

	cout << a1 + b1 << endl;
	cout << a1 - b1 << endl;
	cout << a1 * b1 << endl;
	cout << a1 / b1 << endl; // 两个整数相除结果还是整数,将小数部分去除

	// 两个小数相除
	double d1 = 0.5;
	double d2 = 0.25;
	cout << d1 / d2 << endl; // 运算的结果也可以是小数
    
    // 两个小数不可以做取模运算

	system("pause");

	return 0;
}

结果:
13
7
30
3
2
#include <iostream>
#include <string>

using namespace std;

int main() {
	// 1、前置递增
	int a = 10;
	++a; // 让变量+1
	cout << "a=" << a << endl;

	// 2、后置递增
	int b = 1;
	b++; // 让变量+1
	cout << "b=" << b << endl;

	// 3、前置和后置的区别
	// 前置递增 先让变量+1,然后进行表达式运算
	int a2 = 10;
	int b2 = ++a2 * 10;
	cout << "a2=" << a2 << endl;
	cout << "b2=" << b2 << endl;
	// 后置递增 先进行表达式计算,然后让变量+1
	int a3 = 10;
	int b3 = a3++ * 10;
	cout << "a3=" << a3 << endl;
	cout << "b3=" << b3 << endl;

	system("pause");

	return 0;
}

结果:
a=11
b=2
a2=11
b2=110
a3=11
b3=100

3.2、赋值运算符

作用:用于将表达式的值赋给变量。

包含以下几个符号:=  +=  -=  *=  /=  %=

3.3、比较运算符

作用:用于表达式的比较,并返回一个真值或假值。

包含以下符号:==  !=  <  >  <=  >=

3.4、逻辑运算符

作用:用于根据表达式的值返回真值或假值。

包含以下符号:!  &&  ||

4、程序流程结构

C/C++支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构。

  • 顺序结构:程序按顺序执行,不发生跳转;
  • 选择结构:依据条件是否满足,有选择地执行相应功能;
  • 循环结构:依据条件是否满足,循环多次执行某段代码。

4.1、选择结构

4.1.1、if 语句

作用:执行满足条件地语句。

嵌套if语句:在if语句中,可以嵌套使用if语句,达到更精确的条件判断。

4.1.2、三目运算符

作用:通过三目运算符实现简单的判断。

语法:表达式1 ? 表达式2 : 表达式3

4.1.3、switch语句

作用:执行多条件分支语句。

语法:

switch(表达式){
    case 结果1: 执行语句;break;
    case 结果1: 执行语句;break;
    ...
    default: 执行语句;break;
}

if和switch区别:switch判断时只能是整型或者字符型,不可以是一个区间,但是switch结构清晰,执行效率高。

4.2、循环结构

4.2.1、while循环语句

作用:满足循环条件,执行循环语句。

语法:while(循环条件){循环语句}

解释:只要循环条件的结构为真,就执行循环语句。

4.2.2、do...while 循环语句

作用:满足循环条件,执行循环语句。

语法:do{循环语句} while{循环语句}

注意:与while的区别在于do.while会先执行一次循环语句,再判断循环条件。

4.2.3、for循环语句

作用:满足循环条件,执行循环语句。

语法:for(起始表达式;条件表达式;末尾循环体){循环语句;}

4.2.4、嵌套循环

作用:在循环体中再嵌套一层循环,解决一些实际问题。

4.3、跳转语句

4.3.1、break语句

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

break使用的时机:

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

4.3.2、continue语句

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

4.3.3、goto语句

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

语法:goto 标记;

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

xxxxx
xxxxx
goto FLAG
xxxxx
FLAG: // 跳转到这里
xxxxx

5、数组

5.1、一维数组

5.1.1、概述

所谓数组,就是一个集合,里面存放了相同类型的数据元素。

特点:

  • 数组中的每个数据元素都是相同的数据类型;
  • 数组是由连续的内存位置组成的。
#include <iostream>
#include <string>

using namespace std;

int main() {
	// 定义数组的时候,必须有初始长度
	// 定义方式1
	// 数据类型 数组名[数组长度];
	int arr[5];
	arr[0] = 10;
	arr[1] = 20;
	arr[2] = 30;
	arr[3] = 40;
	arr[4] = 50;
	// 访问数组元素
	cout << arr[0] << endl;
	cout << arr[1] << endl;
	cout << arr[2] << endl;
	cout << arr[3] << endl;
	cout << arr[4] << endl;

	// 定义方式2
	// 数据类型 数组名[数组长度] = {值1, 值2, ...}
	// 如果初始化数据的时候,没有全部填写完,会用0来填补剩余数据
	int arr2[5] = { 10, 20, 30 }; 
	// 访问数组元素
	cout << arr2[0] << endl;
	cout << arr2[1] << endl;
	cout << arr2[2] << endl;
	cout << arr2[3] << endl;
	cout << arr2[4] << endl;

	// 定义方式3
	// 数据类型 数组名[] = {值1, 值2, ...}
	int arr3[] = { 10, 20, 30, 40, 50 }; // 会自动推测数组长度
	for (int i = 0; i < 5; i++) {
		cout << arr3[i] << endl;
	}

	system("pause");

	return 0;
}

结果:
10
20
30
40
50
10
20
30
0
0
10
20
30
40
50

5.1.2、一维数组数组名

一维数组名称的用途:

  • 可以统计整个数组在内存中的长度;
  • 可以获取数组在内存中的首地址。
#include <iostream>
#include <string>

using namespace std;

int main() {
	// 数组名用途
	// 1、可以获取整个数组的长度
	int arr[5] = { 1, 2, 3, 4, 5 };
	cout << "整个数组占用内存空间为:" << sizeof(arr) << endl;
	cout << "每个元素占用内存空间为:" << sizeof(arr[0]) << endl;
	cout << "数组中元素个数为:" << sizeof(arr) / sizeof(arr[0]) << endl;

	// 2、可以通过数组名称查看数组首地址
	cout << "数组首地址为:" << arr << endl;
	cout << "数组首地址(转为int后)为:" << (int)arr << endl;
	cout << "数组中第一个元素地址为:" <<  (int) & arr[0] << endl;

	// 数组名是常量,不可以进行赋值操作
	// arr = 10; // 错误

	system("pause");

	return 0;
}

结果:
整个数组占用内存空间为:20
每个元素占用内存空间为:4
数组中元素个数为:5
数组首地址为:00000086B399F518
数组首地址(转为int后)为:-1281755880
数组中第一个元素地址为:-1281755880

5.2、二维数组

5.2.1、定义方式

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

1、数据类型 数组名[行数][列数]
2、数据类型 数组名[行数][列数] = {{数据00,数据01}, {数据10, 数据11}};
3、数据类型 数组名[行数][列数] = {数据1, 数据2, 数据3, 数据4};
4、数据类型 数组名[][列数] = {数据1, 数据2, 数据3, 数据4};

建议:以上4种定义方式,利用第二种更加直观,提高代码的可读性。

#include <iostream>
#include <string>

using namespace std;

int main() {
	// 二维数组的定义方式
	// 1、数据类型 数组名[行数][列数]
	int arr[2][3];
	arr[0][0] = 1;
	arr[0][1] = 2;
	arr[0][2] = 3;
	arr[1][0] = 4;
	arr[1][1] = 5;
	arr[1][2] = 6;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			cout << arr[i][j] << " ";
		}
		cout << endl;
	}

	// 2、数据类型 数组名[行数][列数] = { {数据00,数据01}, {数据10, 数据11} };
	int arr2[2][3] = {
		{1, 2, 3},
		{4, 5, 6}
	};
	cout << endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			cout << arr2[i][j] << " ";
		}
		cout << endl;
	}

	// 3、数据类型 数组名[行数][列数] = { 数据1, 数据2, 数据3, 数据4 };
	int arr3[2][3] = { 1, 2, 3, 4, 5, 6 }; // 可以自动区分出来
	cout << endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			cout << arr3[i][j] << " ";
		}
		cout << endl;
	}

	// 4、数据类型 数组名[][列数] = { 数据1, 数据2, 数据3, 数据4 };
	int arr4[][3] = { 1, 2, 3, 4, 5, 6 }; // 可以自动区分出来
	cout << endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			cout << arr4[i][j] << " ";
		}
		cout << endl;
	}


	system("pause");

	return 0;
}

结果:
1 2 3
4 5 6

1 2 3
4 5 6

1 2 3
4 5 6

1 2 3
4 5 6

5.2.2、数组名

二维数组名的作用:

  • 查看二维数组所占内存空间;
  • 获取二维数组首地址。
#include <iostream>
#include <string>

using namespace std;

int main() {
	// 查看占用内存空间大小
	int arr[2][3] = {
		{1, 2, 3},
		{4, 5, 6}
	};
	cout << "二维数组占用内存空间为:" << sizeof(arr) << endl;
	cout << "二维数组第一行占用内存为:" << sizeof(arr[0]) << endl;
	cout << "二维数组第一个元素占用内存为:" << sizeof(arr[0][0]) << endl;

	// 查看二维数组的首地址
	cout << "二维数组首地址为:" << (int)arr << endl;
	cout << "二维数组第一行首地址为:" << (int)arr[0] << endl;
	cout << "二维数组第二行首地址为:" << (int)arr[1] << endl;

	cout << "二维数组第一个元素首地址:" << (int)&arr[0][0] << endl;
	cout << "二维数组第二个元素首地址:" << (int)&arr[0][1] << endl;

	system("pause");

	return 0;
}

结果:
二维数组占用内存空间为:24
二维数组第一行占用内存为:12
二维数组第一个元素占用内存为:4
二维数组首地址为:193985080
二维数组第一行首地址为:193985080
二维数组第二行首地址为:193985092
二维数组第一个元素首地址:193985080
二维数组第二个元素首地址:193985084

6、函数

6.1、概述

作用:将以端经常使用的代码封装起来,减少重复代码。

一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能。

6.2、函数的定义

函数的定义一般主要有5个步骤:

  • 返回值类型;
  • 函数名;
  • 参数列表;
  • 函数体语句;
  • return 表达式。
返回值类型 函数名(参数列表){
    函数体;
    return 表达式;
}

6.3、函数的调用

功能:使用定义好的函数。

语法:函数名(参数)

6.4、值传递

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

6.5、函数的常见样式

  • 无参无返;
  • 有参无返;
  • 无参有返;
  • 有参有返。

6.6、函数的声明

作用:告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。提前告诉编译器函数的存在,可以利用函数的声明。

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

6.7、函数的分文件编写

作用:让代码结构更加清晰。

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

  • 创建后缀名为.h的头文件;
  • 创建后缀名为.cpp的源文件;
  • 在头文件中写函数的声明;
  • 在源文件中写函数的定义。

7、指针

7.1、指针的基本概念

指针的作用:可以通过指针间接访问内存。

  • 内存编号是从0开始记录的,一般用十六进制数字表示;
  • 可以利用指针变量保存地址。

7.2、指针变量的定义和使用

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

#include <iostream>
#include <string>

using namespace std;

int main() {
	// 1、指针的定义
	int a = 10; // 定义整型变量
	// 指针定义的语法:数据类型 * 指针变量
	int* p;
	// 让指针记录变量a的地址
	p = &a;
	cout << "a的地址为:" << &a << endl;
	cout << "指针p为:" << p << endl;

	// 2、使用指针
	// 可以通过解引用的方式来找到指针指向的内存
	// 指针前加*来代表解引用,找到指针指向的内存中的数据
	*p = 1000;
	cout << "a=" << a << endl;
	cout << "*p=" << *p << endl;

	system("pause");

	return 0;
}

结果:
a的地址为:0000008B54B4F864
指针p为:0000008B54B4F864
a=1000
*p=1000

7.3、指针所占内存空间

指针在32位操作系统下占4个字节,在64位系统下占8个字节。

#include <iostream>
#include <string>

using namespace std;

int main() {
	// 指针所占内存空间
	int a = 10;
	int* p = &a;

	cout << "sizeof(int*) = " << sizeof(int*) << endl;
	cout << "sizeof(float*) = " << sizeof(float*) << endl;
	cout << "sizeof(double*) = " << sizeof(double*) << endl;
	cout << "sizeof(char*) = " << sizeof(char*) << endl;

	system("pause");

	return 0;
}

结果:
sizeof(int*) = 8
sizeof(float*) = 8
sizeof(double*) = 8
sizeof(char*) = 8

7.4、空指针和野指针

7.4.1、空指针

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

用途:初始化指针变量。

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

#include <iostream>
#include <string>

using namespace std;

int main() {
	// 指针变量p指向内存地址编号为0的空间
	// 空指针用于给指针变量进行初始化
	int* p = NULL;

	// 内存编号0~255为系统占用内存,不允许用户访问
	// 访问空指针报错
	// *p = 100;

	system("pause");

	return 0;
}

7.4.2、野指针

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

#include <iostream>
#include <string>

using namespace std;

int main18() {
	// 野指针
	// 在程序中,尽量避免野指针
	int* p = (int*)0x10100;

	system("pause");

	return 0;
}

7.5、const 修饰指针

const 修饰指针有3种情况:

  • const 修饰指针:常量指针;
  • const 修饰常量:指针常量;
  • const既修饰指针,又修饰常量。
#include <iostream>
#include <string>

using namespace std;

int main19() {
	int a = 10;

	// 常量指针:指针的指向可以修改,但是指针指向的值不可以修改
	const int* p = &a;

	// 指针常量:指针的指向不可以修改,但是指针指向的值可以修改
	int* const p = &a;

	// 既是常量指针又是指针常量:指针和其指向的值都不可以修改
	const int* const p = &a;

	system("pause");

	return 0;
}

7.6、指针和数组

作用:利用指针访问数组中的元素。

#include <iostream>
#include <string>

using namespace std;

int main() {
	// 指针和数组
	// 利用指针访问数组中的元素
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	cout << "第一个元素为:" << arr[0] << endl;

	int* p = arr; // arr就是数组首地址
	cout << "利用指针访问第一个元素:" << *p << endl;

	p++; // 让指针向后偏移4个字节
	cout << "利用指针访问第二个元素:" << *p << endl;

	cout << "利用指针遍历数组" << endl;
	int* p2 = arr;
	for (int i = 0; i < 10; i++) {
		cout << *p2 << " ";
		p2++;
	}
	cout << endl;

	system("pause");

	return 0;
}

结果:
第一个元素为:1
利用指针访问第一个元素:1
利用指针访问第二个元素:2
利用指针遍历数组
1 2 3 4 5 6 7 8 9 10

7.7、指针和函数

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

示例:

#include <iostream>
#include <string>

using namespace std;

// 值传递
void swap01(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
}

void swap02(int* pa, int* pb) {
	int temp = *pa;
	*pa = *pb;
	*pb = temp;
}

int main() {
	int a = 10;
	int b = 20;

	// 值传递
	swap01(a, b);
	cout << "a=" << a << ", b=" << b << endl;

	// 地址传递
	swap02(&a, &b);
	cout << "a=" << a << ", b=" << b << endl;

	system("pause");

	return 0;
}

结果:
a=10, b=20
a=20, b=10

8、结构体

8.1、结构体基本概念

结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。

8.2、结构体定义和使用

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

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

struct 结构体名 变量名
struct 结构体名 变量名 = {成员值1, 成员值2, ...};
定义结构体时顺便创建变量
#include <iostream>
#include <string>

using namespace std;

// 结构体定义
struct Student {
	// 成员列表
	string name; // 姓名
	int age; // 年龄
	int score; // 分数
}stu3; // stu3 为顺便创建的结构体变量

int main21() {
	// c++ 中 strcut 关键字可以省略
	Student stu;

	struct Student stu1;
	stu1.name = "李四";
	stu1.age = 19;
	stu1.score = 90;
	cout << "姓名:" << stu1.name << ", 年龄:" << stu1.age << ", 分数:" << stu1.score << endl;

	struct Student stu2 = { "张三", 18, 85 };

	stu3.name = "王五";
	stu3.age = 20;
	stu3.score = 75;

	system("pause");

	return 0;
}

8.3、结构体数组

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

语法:struct 结构体名 数组名[数组长度] = {{结构体1}, {结构体2}, ...};

#include <iostream>
#include <string>

using namespace std;

struct Stu {
	string name;
	int age;
};

int main() {
	// 创建结构体数组
	struct Stu stuArray[3] = {
		{"张三", 18},
		{"李四", 20},
		{"王五", 21}
	};

	// 修改结构体内容
	stuArray[0].name = "张三三";
	
	// 遍历数组
	for (int i = 0; i < 3; i++) {
		cout << "姓名:" << stuArray[i].name << ", 年龄:" << stuArray[i].age << endl;
	}

	system("pause");

	return 0;
}

结果:
姓名:张三三, 年龄:18
姓名:李四, 年龄:20
姓名:王五, 年龄:21

8.4、结构体指针

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

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

#include <iostream>
#include <string>

using namespace std;

// 结构体定义
struct stu {
	string name;
	int age;
};

int main() {
	// 创建学生结构体变量
	struct stu s = { "张三", 18 };

	// 通过指针指向结构体变量
	stu* p = &s;

	// 通过指针访问结构体变量中的数据
	p->name = "张三三";
	cout << "姓名:" << p->name << ", 年龄:" << p->age << endl;

	return 0;
}

结果:
姓名:张三三, 年龄:18

8.5、结构体嵌套结构体

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

示例:

#include <iostream>
#include <string>

using namespace std;

// 结构体定义
struct student {
	string name;
	int age;
	int score;
};

struct teacher {
	int id;
	string name;
	int age;
	struct student stu; // 辅导的学生,需要在前面定义结构体
};

int main() {
	// 创建老师
	teacher t;
	t.id = 10000;
	t.name = "老王";
	t.age = 50;
	// 辅导的学生属性
	t.stu.name = "小王";
	t.stu.age = 20;
	t.stu.score = 60;

	// 输出
	cout << "老师姓名:" << t.name << ", 老师编号:" << t.id << ", 老师年龄:" << "老师辅导的学生姓名:" << t.stu.name << ", 学生年龄:" << t.stu.age << ", 学生分数:" << t.stu.score << endl;

	return 0;
}

结果:
老师姓名:老王, 老师编号:10000, 老师年龄:老师辅导的学生姓名:小王, 学生年龄:20, 学生分数:60

8.6、结构体作为函数参数

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

传递方式有2种:

  • 值传递;
  • 地址传递。

示例:

#include <iostream>
#include <string>

using namespace std;

// 结构体定义
struct student {
	string name;
	int age;
	int score;
};

struct teacher {
	int id;
	string name;
	int age;
	struct student stu; // 辅导的学生,需要在前面定义结构体
};

// 打印学生信息的函数
// 值传递:改变s的属性值不会影响实际参数的值
void pringStudent1(struct student s) {
	s.age = 100;
	cout << "学生姓名:" << s.name << ", 学生年龄:" << s.age << ", 学生分数:" << s.score << endl;
}

// 地址传递:改变s的属性值会影响实际参数的值
void printStudent2(struct student* s) {
	s->age = 100;
	cout << "学生姓名:" << s->name << ", 学生年龄:" << s->age << ", 学生分数:" << s->score << endl;
}

int main() {
	// 创建老师
	teacher t;
	t.id = 10000;
	t.name = "老王";
	t.age = 50;
	// 辅导的学生属性
	t.stu.name = "小王";
	t.stu.age = 20;
	t.stu.score = 60;

	// 输出
	pringStudent1(t.stu);
	cout << "老师姓名:" << t.name << ",老师编号:" << t.id << ",老师年龄:" << t.age << ",老师辅导的学生姓名:" << t.stu.name << ",学生年龄:" << t.stu.age << ",学生分数:" << t.stu.score << endl;
	printStudent2(&t.stu);
	cout << "老师姓名:" << t.name << ",老师编号:" << t.id << ",老师年龄:" << t.age << ",老师辅导的学生姓名:" << t.stu.name << ",学生年龄:" << t.stu.age << ",学生分数:" << t.stu.score << endl;

	return 0;
}

结果:
学生姓名:小王, 学生年龄:100, 学生分数:60
老师姓名:老王,老师编号:10000,老师年龄:50,老师辅导的学生姓名:小王,学生年龄:20,学生分数:60
学生姓名:小王, 学生年龄:100, 学生分数:60
老师姓名:老王,老师编号:10000,老师年龄:50,老师辅导的学生姓名:小王,学生年龄:100,学生分数:60

8.7、结构体中 const 使用场景

作用:用 const 来防止误操作。

示例:

#include <iostream>
#include <string>

using namespace std;

// 结构体定义
struct Student {
	string name;
	int age;
};

// 将函数中的形参改为指针,可以避免复制新的副本出来,可以减少内存空间
void printStudent(const struct Student* s) {
	// 形参加了const之后,一旦有修改的操作就会报错,可以防止我们的误操作
	// s->age = 150;
	cout << "(函数中)姓名:" << s->name << ", 年龄:" << s->age << endl;
}

int main() {
	struct Student s = { "张三", 15 };

	// 通过函数打印结构体变量信息
	printStudent(&s);
	cout << "(main中)姓名:" << s.name << ", 年龄:" << s.age << endl;

	return 0;
}

结果:
(函数中)姓名:张三, 年龄:15
(main中)姓名:张三, 年龄:15

9、内存分区模型

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

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

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

9.1、程序运行前

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

代码区:

  • 存放CPU执行的机器指令;
  • 代码区是共享的,共享的目的是对于频繁执行的程序,只需要在内存有一份代码即可;
  • 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令。

全局区:

  • 全局变量和静态变量存放在此;
  • 全局区还包含了常量区,字符串常量和其他常量也存放在此;
  • 该区域的数据在程序结束后由操作系统释放。

总结:

C++中在程序运行前分为全局区和代码区;

  • 代码区特点是共享和只读;
  • 全局区中存放全局变量、静态变量、常量;
  • 常量区中存放 const 修饰的全局常量和字符串常量。

9.2、程序运行后

栈区:由编译器自动分配和释放,存放函数的参数值、局部变量等;

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

#include <iostream>
#include <string>

using namespace std;

// 栈区数据的注意事项:不要返回局部变量的地址
// 栈区的数据由编译器管理开辟和释放
int* func(int param) { // 形参数据也会放在栈区
	int a = 10; // 局部变量,存放在栈区,栈区数据在函数执行完后自动释放
	return &a; // 不要返回局部变量的地址
}

int main() {
	// 接收func()函数的返回值
	int* p = func(0);

	cout << *p << endl; // 第一次可以打印正确的数字,是因为编译器做了保留
	cout << *p << endl; // 第二次这个数据就不再保留了

	system("pause");

	return 0;
}

堆区:

  • 由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收;
  • 在C++中主要利用new在堆区开辟内存。
#include <iostream>
#include <string>

using namespace std;

// 结构体定义
struct stu {
	string name;
	int age;
};

int* func() {
	// 利用new关键字,可以将数据开辟到堆区
	// 指针本质也是局部变量,放在栈上,指针指向的内存放在堆区
	int* p = new int(10);
	return p;
}

int main() {
	int* p = func();

	cout << *p << endl;
	cout << *p << endl;
	cout << *p << endl;
	cout << *p << endl;

	delete(p);

	system("pause");

	return 0;
}

总结:

  • 堆区数据由程序员管理开辟和释放;
  • 堆区数据利用new关键字进行开辟内存。

9.3、new 操作符

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

语法:new 数据类型

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

#include <iostream>
#include <string>

using namespace std;

int main() {
	int* arr = new int[2];

	delete[] arr; // 释放数组的时候,要加[]才可以

	return 0;
}

12.5、函数调用运算符重载

  • 函数调用运算符 () 也可以重载;
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数;
  • 仿函数没有固定写法,非常灵活。

示例:

#include <iostream>
#include <string>

using namespace std;

// 函数调用运算符重载
// 打印输出类
// 仿函数非常灵活,没有固定的写法
class MyPrint {
public:
	// 重载函数调用运算符 ()
	void operator()(string test) {
		cout << test << endl;
	}
private:

};

void test54_1() {
	MyPrint myPrint;

	// 由于使用起来非常类似函数调用的形式,因此称为仿函数
	myPrint("hello world");
	// 匿名函数对象: MyPrint()
	MyPrint()("hello world");
}

int main() {
	test54_1();

	system("pause");

	return 0;
}

结果:
hello world
hello world

12.6、继承

继承是面向对象三大特性之一。

继承的好处:可以减少重复的代码。

12.6.1、继承的基本语法

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

子类也称派生类,父类也称为基类。

派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员。从基类继承过来的表现其共性,而新增的成员体现了其个性。

12.6.2、继承方式

继承方式一共有三种:

  • 公共继承;
  • 保护继承;
  • 私有继承。
#include <iostream>

using namespace std;

// 继承方式
class Base1 {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

// 公共继承
class Son1 : public Base1 {
	void func() {
		m_A = 10; // 父类中的公共权限成员,到子类中依然是公共权限
		m_B = 10; // 父类中的保护权限成员,到子类中依然是保护权限
		// m_C = 10; // 父类中的私有权限成员,子类访问不到
	}
};

// 保护继承
class Son2 : protected Base1 {
	void func() {
		m_A = 10; // 父类中的公共权限成员,到子类中是保护权限
		m_B = 10; // 父类中的保护权限成员,到子类中是保护权限
		// m_C = 10; // 父类中的私有权限成员,子类访问不到
	}
};

// 私有继承
class Son3 : private Base1 {
	void func() {
		m_A = 10; // 父类中的公共权限成员,到子类中是私有权限
		m_B = 10; // 父类中的保护权限成员,到子类中是私有权限
		// m_C = 10; // 父类中的私有权限成员,子类访问不到
	}
};

int main() {

	system("pause");

	return 0;
}

12.6.3、继承中的对象模型

#include <iostream>

using namespace std;

// 继承中的对象模型
class Base {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

// 在父类中的所有非静态成员属性都会被子类继承下去
// 父类中私有成员属性,是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了
class Son : public Base {
public:
	int m_D;
};

void test56_1() {
	cout << "size of Son:" << sizeof(Son) << endl;
}

// 利用开发人员命令提示工具查看对象模型
// 跳转盘符
// 跳转文件路径 cd 具体路径下
// 查看命名
// c1 /d1 reportSingleClassLayout 类名 文件名
int main() {
	test56_1();

	system("pause");

	return 0;
}

12.6.4、继承中构造和析构顺序

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

示例:

#include <iostream>

using namespace std;

// 继承中的构造和析构的顺序
class Base57 {
public:
	Base57() {
		cout << "Base57构造函数" << endl;
	}

	~Base57() {
		cout << "Base57析构函数" << endl;
	}
};

class Son57 : public Base57 {
public:
	Son57() {
		cout << "Son构造函数" << endl;
	}

	~Son57() {
		cout << "Son析构函数" << endl;
	}
};

void test57_1() {
	// 继承中的构造和析构顺序如下:
	// 先构造父类,再构造子类
	// 析构的顺序于构造相反
	Base57 b;
	cout << endl;
	Son57 s;
}

int main() {
	test57_1();

	system("pause");

	return 0;
}

结果:
Base57构造函数

Base57构造函数
Son构造函数
Son析构函数
Base57析构函数
Base57析构函数

12.6.5、继承同名成员处理方式

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

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

示例:

#include <iostream>

using namespace std;

// 继承中同名成员处理
class Base58 {
public:
	int m_A;

	Base58() {
		m_A = 100;
	}

	void func() {
		cout << "Base58 func()" << endl;
	}
};

class Son58 : public Base58 {
public:
	int m_A;

	Son58() {
		m_A = 200;
	}

	void func() {
		cout << "Son58 func()" << endl;
	}
};

// 同名成员属性处理
void test58_1() {
	Son58 s;
	// 如果通过子类对象,访问到父类中同名成员,需要加作用域
	cout << "Base 下 m_A = " << s.Base58::m_A << endl;
	cout << "Son 下 m_A = " << s.m_A << endl;
}

void test58_2() {
	Son58 s;

	// 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
	s.func();

	// 如果通过子类对象,访问到父类中同名成员,需要加作用域
	s.Base58::func();
}

int main() {
	test58_1();
	test58_2();

	system("pause");

	return 0;
}

结果:
Base 下 m_A = 100
Son 下 m_A = 200
Son58 func()
Base58 func()

总结:

  • 子类对象可以直接访问到子类中同名成员;
  • 子类对象加作用域可以访问到父类同名成员;
  • 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数。

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

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

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

  • 访问子类同名成员,直接访问即可;
  • 访问父类同名成员,需要加作用域。
#include <iostream>

using namespace std;

// 继承中的同名静态成员处理方式
class Base59 {
public:
	static int m_A;

	static void func() {
		cout << "Base static void func()" << endl;
	}
};
int Base59::m_A = 100;

class Son59 : public Base59 {
public:
	static int m_A;

	static void func() {
		cout << "Son static void func()" << endl;
	}
};
int Son59::m_A = 200;

// 同名静态成员变量
void test59_1() {
	Son59 son;

	// 通过对象访问
	cout << "Base 下 m_A = " << son.Base59::m_A << endl;
	cout << "Son 下 m_A = " << son.m_A << endl;

	// 通过类名访问
	cout << "通过类名访问:" << endl;
	// 第一个::代表通过类名方式访问 第二个::代表父类作用域下
	cout << "Base 下 m_A = " << Son59::Base59::m_A << endl;
	cout << "Son 下 m_A = " << Son59::m_A << endl;
}

// 同名静态成员函数
void test59_2() {
	Son59 son;
	
	// 通过对象访问
	son.func();
	son.Base59::func();
	son.func();

	// 通过类名访问
	Son59::Base59::func();
	Son59::func();
}

int main() {
	test59_1();
	cout << endl;
	test59_2();

	system("pause");

	return 0;
}

结果:
Base 下 m_A = 100
Son 下 m_A = 200
通过类名访问:
Base 下 m_A = 100
Son 下 m_A = 200

Son static void func()
Base static void func()
Son static void func()
Base static void func()
Son static void func()

12.6.7、多继承语法

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

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

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

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

12.6.8、菱形继承

菱形继承概念:

  • 两个派生类继承同一个基类;
  • 又有某个类型同时继承这两个派生类;
  • 这种继承称为菱形继承,或称钻石继承。

菱形继承问题:

  • 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性;
  • 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份即可。
#include <iostream>

using namespace std;

// 动物类
class Animal {
public:
	int m_Age;
};

// 羊类
// 利用虚继承,可以解决菱形继承的问题
class Sheep : virtual public Animal {

};

// 驼类
class Tuo : virtual public Animal {

};

// 羊驼类
class SheepTuo : public Sheep, public Tuo {

};

void test60_1() {
	SheepTuo st;

	st.Sheep::m_Age = 18;
	st.Tuo::m_Age = 28;
	// 当菱形继承,两个父类拥有相同数据,需要加以作用域区分
	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;

	// 这份数据我们知道,只要有一份就可以,菱形继承导致数据有两份,资源浪费

}

int main() {
	test60_1();

	system("pause");

	return 0;
}

结果:
st.Sheep::m_Age = 28
st.Tuo::m_Age = 28

总结:

  • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义;
  • 利用虚继承可以解决菱形继承问题。

12.7、多态

12.7.1、多态的基本概念

多态是C++面向对象三大特性之一。

多态分为两类:

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

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

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址;
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址。
#include <iostream>

using namespace std;

class Animal61 {
public:
	// Speak 函数就是虚函数
	// 函数前面加上 virtual 关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了
	virtual void speak() {
		cout << "动物在说话" << endl;
	}
};

// 猫类
class Cat61 : public Animal61 {
public:
	// 重写:函数返回值类型、函数名、参数列表完全相同
	void speak() {
		cout << "小猫在说话" << endl;
	}
};

// 狗
class Dog61 : public Animal61 {
public:
	// 重写:函数返回值类型、函数名、参数列表完全相同
	void speak() {
		cout << "小狗在说话" << endl;
	}
};

// 执行说话的函数
// 地址早绑定,在编译阶段确定函数地址
// 如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定
void doSpeak(Animal61& animal) {
	animal.speak();
}

// 动态多态满足条件
// 1、有继承关系;
// 2、子类重写父类的虚函数
void test61_1() {
	// 父类的指针或者引用指向子类对象
	Cat61 cat;
	doSpeak(cat);
	Dog61 dog;
	doSpeak(dog);
}

int main() {
	test61_1();

	system("pause");

	return 0;
}

结果:
小猫在说话
小狗在说话

12.7.2、纯虚函数和抽象类

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

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

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

抽象类特点:

  • 无法实例化对象;
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。

12.7.5、虚析构和纯虚析构

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

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

虚析构和纯虚析构共性:

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

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

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

虚析构语法:virtual ~类名(){}

纯虚析构语法:virtual ~类名() = 0;

总结:

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

13、文件操作

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

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

文件类型分为2种:

  • 文本文件:文件以文本的ASCII码形式存储在计算机种;
  • 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们。

操作文件的三大类:

  • ofstream:写操作;
  • ifstream:读操作;
  • fstream:读写操作。

13.1、文本文件

13.1.1、写文件

写文件步骤如下:

// 包含头文件
#include <fstream>
// 创建流对象
ofstream ofs;
// 打开文件
ofs.open("文件路径", 打开方式);
// 写数据
ofs << "写入的数据";
// 关闭文件
ofs.close();

文件打开方式:

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

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

示例:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

// 文本文件写文件
void test62_1() {
	// 创建流对象
	ofstream ofs;
	// 打开文件
	ofs.open("test.txt", ios::out);
	// 写数据
	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;
	// 关闭文件
	ofs.close();
}

int main() {
	test62_1();

	system("pause");

	return 0;
}

总结:

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

13.1.2、读文件

读文件与写文件步骤相似,但是读取方式相对于比较多。

读文件步骤如下:

// 包含头文件
#include <fstream>
// 创建流对象
ifstream ifs;
// 打开文件并判断文件是否打开成功
ifs.open("文件路径", 打开方式);
// 读数据
4种方式读取
// 关闭文件
ifs.close();

示例:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

// 文本文件写文件
void test62_1() {
	// 创建流对象
	ofstream ofs;
	// 打开文件
	ofs.open("test.txt", ios::out);
	// 写数据
	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;
	// 关闭文件
	ofs.close();
}

// 文本文件读文件
void test62_2() {
	// 创建流对象
	ifstream ifs;
	// 打开文件并判断文件是否打开成功
	ifs.open("test.txt", ios::in);
	if (!ifs.is_open()) {
		cout << "文件打开失败" << endl;
		return;
	}

	// 读数据
	// 方式1
	//char buf[1024] = { 0 };
	//while (ifs >> buf) {
	//	cout << buf << endl;
	//}

	// 方式2
	//char buf[1024] = { 0 };
	//while (ifs.getline(buf, sizeof(buf))) {
	//	cout << buf << endl;
	//}

	// 方式3
	//string buf;
	//while (getline(ifs, buf)) {
	//	cout << buf << endl;
	//}

	// 方式4
	char c;
	while ((c = ifs.get()) != EOF) {
		cout << c;
	}


	// 关闭文件
	ifs.close();
}

int main() {
	//test62_1();
	test62_2();

	system("pause");

	return 0;
}

13.2、二进制文件

以二进制的方式对文件进行读写操作。打开方式要指定为 ios::binary 。

13.2.1、写文件

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

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

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

示例:

#include <iostream>
#include <fstream>

using namespace std;

// 二进制文件 写文件
class Person63 {
public:
	char m_Name[64];
	int m_Age;
};

void test63_1() {
	ofstream ofs;
	ofs.open("test.txt", ios::out | ios::binary);

	Person63 p = { "张三", 18 };
	ofs.write((const char*)&p, sizeof(Person63));

	ofs.close();
}

int main() {
	test63_1();

	system("pause");

	return 0;
}

13.2.2、读文件

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

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

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

示例:

#include <iostream>
#include <fstream>

using namespace std;

class Person64 {
public:
	char m_Name[64];
	int m_Age;
};

// 二进制文件 读文件
void test64_1() {
	ifstream ifs;

	ifs.open("test.txt", ios::binary | ios::in);
	if (!ifs.is_open()) {
		cout << "文件打开失败" << endl;
	}

	Person64 p;
	ifs.read((char*)&p, sizeof(Person64));

	cout << p.m_Name << ", " << p.m_Age << endl;

	ifs.close();
}

int main() {
	test64_1();

	system("pause");

	return 0;
}

14、模板

14.1、模板的概念

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

14.2、函数模板

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

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

14.2.1、函数模板语法

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

语法:

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

解释:
template - 声明创建模板
typename - 表明其后面的符号是一种数据类型,可以用class代替
T - 通用的数据类型,名称可以替换,通常为大写字母
#include <iostream>

using namespace std;

// 函数模板
// 交换两个整型的函数
void swapInt(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}

// 交换两个浮点型函数
void swapDouble(double& a, double& b) {
	int temp = a;
	a = b;
	b = temp;
}

// 函数模板:声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
template<typename T>
// 或者 template<class T>
void swap66(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

void test66() {
	int a = 10, b = 20;

	// 两种方式使用函数模板
	// 1、自动类型推导
	swap66(a, b);
	// 2、显示指定类型
	swap66<int>(a, b);
}

int main() {
	test66();

	system("pause");

	return 0;
}

总结:

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

14.2.2、函数模板注意事项

注意事项:

  • 自动类型推导,必须推导出一致的数据类型T才可以使用;
  • 模板必须要确定出T的数据类型,才可以使用。

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

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

  • 普通函数调用时可以发生自动类型转换(隐式类型转换);
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换;
  • 如果利用显式指定类型的方式,可以发生隐式类型转换。

总结:建议使用显式指定类型的方式,调用函数模板,因为可以自己确定通用类型T。

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

调用规则如下:

  • 如果函数模板和普通函数都可以实现,优先调用普通函数;
  • 可以通过空模板参数列表来强制调用函数模板;
  • 函数模板也可以发生重载;
  • 如果函数模板可以产生更好的匹配,优先调用函数模板。
#include <iostream>

using namespace std;

void myPrint(int a, int b) {
	cout << "调用的普通函数" << endl;
}

template<class T>
void myPrint(T a, T b) {
	cout << "调用的模板(a,b)" << endl;
}

// 3、函数模板可以发生函数重载
template<class T>
void myPrint(T a, T b, T c) {
	cout << "调用的模板(a,b,c)" << endl;
}


// 普通函数与函数模板的调用规则
void test67_1() {
	int a = 10;
	int b = 20;

	// 1、如果函数模板和普通函数都可以调用,优先调用普通函数
	myPrint(a, b);

	// 2、可以通过空模板参数列表,强制调用函数模板
	myPrint<>(a, b);

	// 4、如果函数模板可以产生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	myPrint(c1, c2);
}

int main() {
	test67_1();

	system("pause");

	return 0;
}

结果:
调用的普通函数
调用的模板(a,b)
调用的模板(a,b)

14.2.5、模板的局限性

局限性:模板的通用性并不是万能的。

例如:

template<class T>
void f(T a, T b) {
    a = b;
}

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

再例如:

template<class T>
void f(T a, T b) {
    if (a > b) {
        ...
    }
}

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

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

template<class T>
bool myCompare(T& a, T& b) {
    if (a == b) {
        return true;
    } else {
        return false;
    }
}

// 利用具体化的Person的版本实现
template<> bool myCompare(Person& a, Person& b) {
    ...
}

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化;
  • 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板。

14.3、类模板

14.3.1、类模板语法

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

语法:template<typename T>

解释:

  • template - 声明创建模板;
  • typename - 表明气候的符号是一种数据类型,可以用class代替;
  • T - 通用的数据类型,名称可以替换,通常为大写字母。

示例:

#include <iostream>
#include <string>

using namespace std;

// 类模板
template<class NameType, class AgeType>
class Person68 {
public:
	Person68(NameType name, AgeType age) {
		this->m_Name = name;
		this->m_Age = age;
	}

	void showPerson() {
		cout << "name=" << m_Name << ", age=" << m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

void test68_1() {
	Person68<string, int> p1("孙悟空", 999);
	p1.showPerson();
}

int main() {
	test68_1();

	system("pause");

	return 0;
}

总结:类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板。

14.3.2、类模板与函数模板的区别

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

  • 类模板没有自动类型推导的使用方式;
  • 类模板在模板参数列表中可以有默认参数。

示例:

#include <iostream>

using namespace std;

// 类模板与函数模板区别
// 可以指定默认参数
template<class NameType, class AgeType = int>
class Person69 {
public:
	Person69(NameType name, AgeType age) {
		this->name = name;
		this->age = age;
	};

	NameType name;
	AgeType age;
};

// 1、类模板没有自动类型推导使用方式
void test69_1() {
	// Person69 p("孙悟空", 1000); // 错误,无法自动类型推导
	Person69<string, int> p("孙悟空", 1000); // 正确,只能用显式指定类型
}

// 2、类模板在模板参数列表中可以有默认参数
void test69_2() {
	Person69<string> p("猪八戒", 999);
}

int main() {
	test69_1();
	test69_2();

	system("pause");

	return 0;
}

总结:

  • 类模板使用只能显式指定类型方式;
  • 类模板中的模板参数列表可以有默认参数。

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

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

  • 普通类中的成员函数一开始就可以创建;
  • 类模板中的成员函数在调用时才创建。
#include <iostream>

using namespace std;

// 类模板中成员函数的创建时机
class Person70_1 {
public:
	void showPerson1() {
		cout << "showPerson1()" << endl;
	}
};

class Person70_2 {
public:
	void showPerson2() {
		cout << "showPerson2()" << endl;
	}
};

template<class T>
class MyClass70 {
public:
	T obj;

	// 类模板中的成员函数
	void func1() {
		obj.showPerson1();
	}

	void func2() {
		obj.showPerson2();
	}
};

void test70_1() {
	MyClass70<Person70_1> mc1;
	mc1.func1();
	mc1.func2(); // 报错,因为 Person70_1 没有 func2() 函数
}

int main() {
	test70_1();

	system("pause");

	return 0;
}

14.3.4、类模板对象做函数参数

一共有三种传入方式:

  • 指定传入的类型 - 直接显示对象的数据类型;
  • 参数模板化 - 将对象中的参数变为模板进行传递;
  • 整个类模板化 - 将这个对象类型模板化进行传递。

示例:

#include <iostream>

using namespace std;

// 类模板对象做函数参数
template<class T1, class T2>
class Person71 {
public:

	Person71(T1 name, T2 age) {
		this->name = name;
		this->age = age;
	}

	void showPerson() {
		cout << "姓名:" << name << ", 年龄:" << age << endl;
	}

	T1 name;
	T2 age;
};

//指定传入的类型 - 直接显示对象的数据类型;
void printPerson1(Person71<string, int>& p) {
	p.showPerson();
}

void test71_1() {
	Person71<string, int> p("孙悟空", 100);
	printPerson1(p);
}

//参数模板化 - 将对象中的参数变为模板进行传递;
template<class T1, class T2>
void printPerson2(Person71<T1, T2>& p) {
	p.showPerson();
	cout << "T1 的类型为:" << typeid(T1).name() << endl;
	cout << "T2 的类型为:" << typeid(T2).name() << endl;
}
void test71_2() {
	Person71<string, int> p("猪八戒", 90);
	printPerson2(p);
}

//整个类模板化 - 将这个对象类型模板化进行传递。
template<class T>
void printPerson3(T& p) {
	p.showPerson();
	cout << "T 的数据类型为:" << typeid(T).name() << endl;
}
void test71_3() {
	Person71<string, int> p("唐僧", 30);
	printPerson3(p);
}

int main() {
	test71_1();
	test71_2();
	test71_3();

	system("pause");

	return 0;
}

结果:
姓名:孙悟空, 年龄:100
姓名:猪八戒, 年龄:90
T1 的类型为:class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
T2 的类型为:int
姓名:唐僧, 年龄:30
T 的数据类型为:class Person71<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int>

14.3.5、类模板与继承

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

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型;
  • 如果不指定,编译器无法给子类分配内存;
  • 如果想灵活指定出父类中T的类型,子类也需要变为类模板。
#include <iostream>

using namespace std;

// 类模板与继承
template<class T>
class Base72 {
public:
	T m;
};

// 必须要知道父类中的T类型,才能继承给子类
class Son72_1 : public Base72<int> {
public:

};

// 如果想灵活指定父类中T类型,子类也需要变为类模板
template<class T1, class T2>
class Son72_2 : public Base72<T2> {
public:
	T1 obj;
};

void test72_1() {
	Son72_1 s1;

	Son72_2<int, char> s2;
}

int main() {
	test72_1();

	system("pause");

	return 0;
}

总结:如果父类是类模板,子类需要指定出父类中T的数据类型。

14.3.6、类模板成员函数类外实现

#include <iostream>
#include <string>

using namespace std;

// 类模板中成员函数类外实现
template<class T1, class T2>
class Person73 {
public:
	Person73(T1 name, T2 age);

	void showPerson();

	T1 name;
	T2 age;
};

// 构造函数类外实现
template<class T1, class T2>
Person73<T1, T2>::Person73(T1 name, T2 age) {
	this->name = name;
	this->age = age;
}

// 普通成员函数类外实现
template<class T1, class T2>
void Person73<T1, T2>::showPerson() {
	cout << "姓名:" << name << ", 年龄" << age << endl;
}

void test73_1() {
	Person73<string, int> p("Tom", 10);
	p.showPerson();
}

int main() {
	test73_1();

	system("pause");

	return 0;
}

结果:
姓名:Tom, 年龄10

总结:类模板中成员函数类外实现时,需要加上模板参数列表。

14.3.7、类模板分文件编写

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

解决:

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

示例:

#include <iostream>
#include <string>
#include "person74.h"

// 1、第1种解决方式:直接包含源文件
//#include "person74.cpp"
// 2、第二种解决方式:将.h 和 .cpp 中的内容写到一起,将后缀名改为.hpp文件
//#include "person74.hpp"

using namespace std;

void test74_1() {
	Person74<string, int> p("Jerry", 14);
	p.showPerson();
}

int main() {
	test74_1();

	system("pause");

	return 0;
}

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

14.3.8、类模板与友元

  • 全局函数类内实现:直接在类内声明友元即可;
  • 全局函数类外实现:需要提前让编译器知道全局函数的存在。
#include <iostream>
#include <string>

using namespace std;

// ------ 全局函数类外实现
template<class T1, class T2>
class Person75;

template<class T1, class T2>
void printPersonOut(Person75<T1, T2>& p) {
	cout << "姓名:" << p.name << ", 年龄:" << p.age << endl;
}
// ----------------------------------------------

template<class T1, class T2>
class Person75 {
	// 全局函数类内实现
	friend void printPersonIn(Person75<T1, T2>& p) {
		cout << "姓名:" << p.name << ", 年龄:" << p.age << endl;
	}

	// 全局函数类外实现
	friend void printPersonOut<>(Person75<T1, T2>& p);
    // 类外实现写法2:
	//template<class T1, class T2>
	//friend void printPersonOut(Person75<T1, T2>& p);

public:
	Person75(T1 name, T2 age) {
		this->name = name;
		this->age = age;
	}

private:
	T1 name;
	T2 age;
};

void test75_1() {
	// 1、全局函数类内实现
	Person75<string, int> p("Tom", 1);
	printPersonIn(p);
	printPersonOut(p);
}


int main() {
	test75_1();

	system("pause");

	return 0;
}

结果:
姓名:Tom, 年龄:1
姓名:Tom, 年龄:1

15、STL初识

15.1、STL的诞生

长久以来,软件界一直希望建立一种可重复利用的东西,C++的面向对象和泛型编程思想,目的就是复用性的提升。大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作。为了建立数据结构和算法的一套标准,诞生了STL。

15.2、STL基本概念

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

15.3、STL六大组件

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

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

15.4、STL 中容器、算法、迭代器

STL 容器就是将运用最广泛的一些数据结构实现出来。常用的数据结构:数组、链表、树、栈、队列、集合、映射表等。

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

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

算法:有限的步骤,解决逻辑或数学上的问题。

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

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

迭代器:容器和算法之间的粘合剂。提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。每个容器都有自己专属的迭代器。迭代器使用非常类似于指针。

迭代器种类:

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

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

15.5、容器算法迭代器初识

STL 中最常用的容器为 Vector,可以理解为数组。

15.5.1、vector 存放内置数据类型

容器:vector

算法:for_each

迭代器:vector<int>::iterator

示例:

#include <iostream>
#include <vector>
#include <algorithm> // 标准算法头文件

using namespace std;

void myPrint(int val) {
	cout << val << endl;
}

// vector 容器存放内置数据类型
void test76_1() {
	vector<int> v;
	// 插入数据
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);

	// 通过迭代器访问容器中的数据
	vector<int>::iterator itBegin = v.begin(); // 起始迭代器,指向容器中第一个元素
	vector<int>::iterator itEnd = v.end(); // 结束迭代器,指向容器中最后一个元素的下一个位置

	// 第一种遍历方式
	//while (itBegin != itEnd) {
	//	cout << *itBegin << endl;
	//	itBegin++;
	//}

	// 第二种遍历方式
	//for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
	//	cout << *it << endl;
	//}

	// 第三种遍历方式 利用 STL 中的遍历算法
	for_each(itBegin, itEnd, myPrint);
}

int main() {
	test76_1();

	system("pause");

	return 0;
}

结果:
10
20
30
40
50

15.5.2、Vector 容器嵌套容器

#include <iostream>
#include <vector>

using namespace std;

// 容器嵌套容器
void test77_1() {
	vector<vector<int>> v;

	// 创建小容器
	vector<int> v1;
	vector<int> v2;
	vector<int> v3;
	vector<int> v4;
	vector<int> v5;

	// 向小容器中添加数据
	for (int i = 0; i < 4; i++) {
		v1.push_back(i + 1);
		v2.push_back(i + 2);
		v3.push_back(i + 3);
		v4.push_back(i + 4);
	}
	
	// 将小容器插入到大容器中
	v.push_back(v1);
	v.push_back(v2);
	v.push_back(v3);
	v.push_back(v4);
	v.push_back(v5);

	// 通过大容器,吧所有数据遍历以便
	for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) {
		// (*it) —— 容器 vector<int>
		for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) {
			cout << *vit << " ";
		}
		cout << endl;
	}
}

int main() {
	test77_1();

	system("pause");

	return 0;
}

结果:
1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7

16、STL 常用容器

16.1、string 容器

16.1.1、string 基本概念

本质:string 是 C++ 风格的字符串,而string本质上是一个类。

string 和 char* 的区别:

  • char* 是一个指针;
  • string 是一个类,类内部封装了 char*,管理这个字符串,是一个 char* 型的容器。

特点:string 类内部封装了很多成员方法。例如:查找find、拷贝copy、删除delete、替换replace、插入insert。

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

16.1.2、string 构造函数

构造函数原型:

// 创建一个空的字符串 例如:string str;
string();
// 使用字符串s初始化
string(const char* s);
// 使用一个string对象初始化另一个string对象
string(const string& str);
// 使用n个字符c初始化
string(int n, char c);
#include <iostream>

using namespace std;

// string 的构造函数
void test1_1() {
	string s1; // 默认构造

	const char* str = "hello world";
	string s2(str);
	cout << "s2=" << s2 << endl;

	string s3(s2);
	cout << "s3=" << s3 << endl;

	string s4(10, 'a');
	cout << "s4=" << s4 << endl;
}

int main() {
	test1_1();

	system("pause");

	return 0;
}

结果:
s2=hello world
s3=hello world
s4=aaaaaaaaaa

总结:string的多种构造方式没有可比性,灵活使用即可。

16.1.3、string 的赋值操作

功能描述:给string字符串进行赋值。

赋值的函数原型:

// char* 类型字符串,赋值给当前的字符串
string& operator=(const char* s);
// 把字符串s赋给当前的字符串
string& operator=(const string& s);
// 字符赋值给当前的字符串
string& operator=(char c);
// 把字符串s赋给当前的字符串
string& assign(const char* s);
// 把字符串s的前n个字符赋给当前的字符串
string& assign(const char* s, int n);
// 把字符s赋给当前字符串
string& assign(const string& s);
// 用n个字符c赋给当前字符串
string& assign(int n, char c);

16.1.4、string 的拼接

#include <iostream>
#include <string>

using namespace std;

// 字符串拼接
void test2_1() {
	string str1 = "我";

	str1 += "爱玩游戏:";
	cout << "str1 = " << str1 << endl;

	string str2 = " LOL DNF ";
	str1 += str2;
	cout << "str1 = " << str1 << endl;

	string str3 = "I";
	str3.append(" love ");
	cout << "str3 = " << str3 << endl;

	str3.append("game abcde", 4);
	cout << "str3 = " << str3 << endl;

	str3.append(str2, 0, 4); // 从 0 开始截取 4 个字符
	cout << "str3 = " << str3 << endl;

	str3.append(str2);
	cout << "str3 = " << str3 << endl;
}

int main() {
	test2_1();
	
	system("pause");

	return 0;
}

结果:
str1 = 我爱玩游戏:
str1 = 我爱玩游戏: LOL DNF
str3 = I love
str3 = I love game
str3 = I love game LOL
str3 = I love game LOL LOL DNF

16.1.5、string 的查找和替换

#include <iostream>
#include <string>

using namespace std;

// 查找
void test3_1() {
	string str1 = "abcdefg";

	// find() 是从左往右查找
	int pos = str1.find("de"); // 不存在返回-1
	cout << "pos = " << pos << endl;

	// rfind() 是从右往左查找
	pos = str1.rfind("de");
	cout << "pos = " << pos << endl;
}

// 替换
void test3_2() {
	string str1 = "abcdefg";

	// 从 1号位置起3个字符,替换为 1111
	str1.replace(1, 3, "1111");
	cout << "str1 = " << str1 << endl;
}

int main() {
	test3_1();
	cout << endl;
	test3_2();

	system("pause");

	return 0;
}

结果:
pos = 3
pos = 3

str1 = a1111efg

16.1.6、string 的比较

#include <iostream>
#include <string>

using namespace std;

// 字符串比较
void test4_1() {
	string str1 = "hello";
	string str2 = "hello";

	if (str1.compare(str2) == 0) {
		cout << "str1 等于 str2 " << endl;
	} else if (str1.compare(str2) > 0) {
		cout << "str1 大于 str2" << endl;
	} else if (str1.compare(str2) < 0) {
		cout << "str1 小于 str2" << endl;
	}
}

int main() {
	test4_1();

	system("pause");

	return 0;
}

结果:
str1 等于 str2

总结:字符串对比主要是用于比较两个字符串是否相等,判断谁大谁小的意义并不是很大。

16.1.7、string 的字符存取

#include <iostream>
#include <string>

using namespace std;

// string 字符存取
void test5_1() {
	string str = "hello";
	cout << "str = " << str << endl;

	// 通过 [] 访问单个字符
	for (int i = 0; i < str.size(); i++) {
		cout << str[i] << " ";
	}
	cout << endl;

	// 通过 at() 函数访问单个字符
	for (int i = 0; i < str.size(); i++) {
		cout << str.at(i) << " ";
	}
	cout << endl;

	// 修改单个字符
	str[0] = 'x';
	cout << "str = " << str << endl;

	str.at(1) = 'x';
	cout << "str = " << str << endl;
}

int main() {
	test5_1();

	system("pause");

	return 0;
}

结果:
str = hello
h e l l o
h e l l o
str = xello
str = xxllo

16.1.8、string 的插入和删除

#include <iostream>
#include <string>

using namespace std;

// string 的插入删除
void test6_1() {
	string str = "hello";

	// 插入
	str.insert(1, "111");
	cout << "str = " << str << endl;

	// 删除
	str.erase(1, 3);
	cout << "str = " << str << endl;
}

int main() {
	test6_1();

	system("pause");

	return 0;
}

结果:
str = h111ello
str = hello

16.1.9、string 子串

#include <iostream>
#include <string>

using namespace std;

// string 子串
void test7_1() {
	string str = "abcdefg";

	string subStr = str.substr(1, 3);
	cout << "subStr = " << subStr << endl;
}

// 实用操作
void test7_2() {
	string email = "hello@sina.com";

	// 从邮件地址中获取用户名信息
	int pos = email.find("@");

	string username = email.substr(0, pos);
	cout << username << endl;
}

int main() {
	test7_1();
	test7_2();

	system("pause");

	return 0;
}

结果:
subStr = bcd
hello

16.2、Vector 容器

16.2.1、vector 基本概念

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

vector 与普通数组区别:数组是静态空间,而vector可以动态扩展。

动态扩展:并不是在原空间之后接新空间,而是找更大的空间,然后将原始数据拷贝至新空间,释放原空间。vector容器的迭代器是支持随机访问的迭代器。

16.2.2、vector 构造函数

功能描述:创建 vector 容器。

函数原型:

// 采用模板实现类实现,默认构造函数
vector<T> v;
// 将 v[begin(), end()] 区间中的元素拷贝给本身
vector(v.begin(), v.end());
// 构造函数将n个elem拷贝给本身
vector(n, elem);
// 拷贝构造函数
vector(const vector& vec);

示例:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

void printVector(vector<int>& v) {
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// vector 容器构造
void test8_1() {
	vector<int> v1; // 默认构造 无参
	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
	}
	printVector(v1);

	// 通过区间方式进行构造
	vector<int> v2(v1.begin(), v1.end());
	printVector(v2);

	// n个elem方式构造
	vector<int> v3(10, 100);
	printVector(v3);

	// 拷贝构造
	vector<int> v4(v3);
	printVector(v4);
}

// 实用操作
void test8_2() {
}

int main() {
	test8_1();
	test8_2();

	system("pause");

	return 0;
}

结果:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
100 100 100 100 100 100 100 100 100 100
100 100 100 100 100 100 100 100 100 100

16.2.3、vector 赋值操作

功能描述:给 vector 容器进行赋值。

函数原型:

// 重载等号操作符
vector& operator=(const vector& vec);
// 将 [beg, end] 区间中的数据拷贝赋值给本身
assign(beg, end);
// 将n个elem拷贝赋值给本身
assign(n, elem);

示例:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

void printVector9(vector<int>& v) {
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// vector 容器构造
void test9_1() {
	vector<int> v1;
	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
	}
	printVector9(v1);

	// 赋值
	vector<int> v2;
	v2 = v1;
	printVector9(v2);

	// assign
	vector<int> v3;
	v3.assign(v1.begin(), v1.end());
	printVector9(v3);

	// n 个 elem 赋值
	vector<int> v4;
	v4.assign(10, 100);
	printVector9(v4);
}

// 实用操作
void test9_2() {
}

int main() {
	test9_1();
	test9_2();

	system("pause");

	return 0;
}

结果:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
100 100 100 100 100 100 100 100 100 100

总结:vector 赋值方式比较简单,使用 operator=,或者assign都可以。

16.2.4、vector 容量和大小

功能描述:对vector容器的容量和大小操作。

函数原型:

// 判断容器是否为空
empty();
// 容器的容量
capacity();
// 返回容器中元素的个数
size();
// 重新指定容器的长度为num,若容器变长,则以默认值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
resize(int num);
// 重新指定容器的长度为num,若容器变长,则以elem值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
resize(int num, elem);

示例:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

void printVector10(vector<int>& v) {
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// vector 容量和大小
void test10_1() {
	vector<int> v1;
	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
	}
	printVector10(v1);

	// 判断是否为空
	if (v1.empty()) {
		cout << "v1 为空" << endl;
	} else {
		cout << "v1 不为空, 容量为" << v1.capacity() << ", 大小为" << v1.size() << endl;
	}

	// 重新指定大小
	v1.resize(15); // 比原来长的默认用0填充
	printVector10(v1);
	v1.resize(20); // 利用重载版本,可以指定默认填充值
	printVector10(v1);

	v1.resize(5);
	printVector10(v1); // 比原来短,超出的删除

}

// 实用操作
void test10_2() {
}

int main() {
	test10_1();
	test10_2();

	system("pause");

	return 0;
}

结果:
0 1 2 3 4 5 6 7 8 9
v1 不为空, 容量为13, 大小为10
0 1 2 3 4 5 6 7 8 9 0 0 0 0 0
0 1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 0 0 0 0
0 1 2 3 4

16.2.5、vector 的插入和删除

功能:对vector容器进行插入、删除操作。

函数原型:

// 尾部插入元素ele
push_back(ele);
// 删除最后一个元素
pop_back();
// 迭代器指向位置pos插入元素ele
insert(const_iterator pos, ele);
// 迭代器指向位置pos插入cout个元素ele
insert(const_iterator pos, int count, ele);
// 删除迭代器指向的元素
erase(const_iterator pos);
// 删除迭代器从start到end之间的元素
erase(cosnt_iterator start, const_iterator end);
// 删除容器中所有元素
clear();

示例:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

void printVector11(vector<int>& v) {
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// vector 插入和删除
void test11_1() {
	vector<int> v1;
	// 尾插
	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);
	v1.push_back(40);
	v1.push_back(50);
	
	// 遍历
	printVector11(v1);

	// 尾删
	v1.pop_back();
	printVector11(v1);

	// 插入 第一个参数是迭代器
	v1.insert(v1.begin(), 100);
	printVector11(v1);
	v1.insert(v1.begin(), 2, 1000);
	printVector11(v1);

	// 删除 参数也是迭代器
	v1.erase(v1.begin());
	printVector11(v1);

	// 等于 v1.clear();
	v1.erase(v1.begin(), v1.end());
	printVector11(v1);
}

// 实用操作
void test11_2() {
}

int main() {
	test11_1();
	test11_2();

	system("pause");

	return 0;
}

结果:
10 20 30 40 50
10 20 30 40
100 10 20 30 40
1000 1000 100 10 20 30 40
1000 100 10 20 30 40

16.2.6、vector 数据存取

功能:对vector中的数据的存取操作。

函数原型:

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

示例:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

void printVector12(vector<int>& v) {
	//for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
	//	cout << *it << " ";
	//}
	//cout << endl;
	// 利用[]访问
	//for (int i = 0; i < v.size(); i++) {
	//	cout << v[i] << " ";
	//}
	//cout << endl;
	// 利用 at() 访问
	for (int i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;
}

// vector 数据存取
void test12_1() {
	vector<int> v1;
	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
	}
	printVector12(v1);

	// 获取第一个元素
	cout << "第一个元素为:" << v1.front() << endl;
	// 获取最后一个元素
	cout << "最后一个元素为:" << v1.back() << endl;
	
}

// 实用操作
void test12_2() {
}

int main() {
	test12_1();
	test12_2();

	system("pause");

	return 0;
}

结果:
0 1 2 3 4 5 6 7 8 9
第一个元素为:0
最后一个元素为:9

16.2.7、vector 互换容器

功能描述:实现两个容器内元素进行互换。

函数原型:

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

示例:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

void printVector13(vector<int>& v) {
	for (int i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;
}

// vector 容器互换
void test13_1() {
	vector<int> v1;
	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
	}
	printVector13(v1);

	vector<int> v2;
	for (int i = 10; i > 0; i--) {
		v2.push_back(i);
	}
	printVector13(v2);

	cout << "交换....." << endl;
	v1.swap(v2);
	printVector13(v1);
	printVector13(v2);
}

// 实用操作:巧用 swap() 可以收缩内存空间
void test13_2() {
	vector<int> v;
	for (int i = 0; i < 10000; i++) {
		v.push_back(i);
	}

	cout << "v的容量为:" << v.capacity() << endl;
	cout << "v的大小为:" << v.size() << endl;
	cout << "resize() ..." << endl;
	v.resize(10);
	cout << "v的容量为:" << v.capacity() << endl;
	cout << "v的大小为:" << v.size() << endl;
	// 巧用 swap() 收缩内存
	cout << "swap() ..." << endl;
	vector<int>(v).swap(v);
	cout << "v的容量为:" << v.capacity() << endl;
	cout << "v的大小为:" << v.size() << endl;

}

int main() {
	test13_1();
	cout << endl;
	test13_2();

	system("pause");

	return 0;
}

结果:
0 1 2 3 4 5 6 7 8 9
10 9 8 7 6 5 4 3 2 1
交换.....
10 9 8 7 6 5 4 3 2 1
0 1 2 3 4 5 6 7 8 9

v的容量为:12138
v的大小为:10000
resize() ...
v的容量为:12138
v的大小为:10
swap() ...
v的容量为:10
v的大小为:10

16.2.8、vector 预留空间

功能描述:减少 vector 在动态扩展容量时的扩展次数。

函数原型:

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

示例:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

void printVector14(vector<int>& v) {
	for (int i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;
}

// vector 预留位置
void test14_1() {
	vector<int> v;

	// 利用 reserve 预留空间,不写这行最后num为24
	v.reserve(10000);

	int num = 0; // 统计开辟次数
	int* p = NULL;
	for (int i = 0; i < 10000; i++) {
		v.push_back(i);

		if (p != &v[0]) {
			p = &v[0];
			num++;
		}
	}
	cout << num << endl;
}

// 实用操作
void test14_2() {

}

int main() {
	test14_1();
	cout << endl;
	test14_2();

	system("pause");

	return 0;
}

结果:
1

16.3、deque 容器

16.3.1、deque 容器基本概念

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

deque 与 vector 区别:

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

16.3.2、deque 构造函数

函数原型:

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

using namespace std;

void printDeque15(const deque<int>& d) {
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// deque 构造函数
void test15_1() {
	deque<int> d1;
	for (int i = 0; i < 10; i++) {
		d1.push_back(i);
	}
	printDeque15(d1);

	deque<int> d2(d1.begin(), d1.end());
	printDeque15(d2);

	deque<int> d3(10, 100);
	printDeque15(d3);

	deque<int> d4(d3);
	printDeque15(d4);
}

// 实用操作
void test15_2() {

}

int main() {
	test15_1();
	cout << endl;
	test15_2();

	system("pause");

	return 0;
}

结果:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
100 100 100 100 100 100 100 100 100 100
100 100 100 100 100 100 100 100 100 100

16.3.3、deque 赋值操作

函数原型:

// 重载等号操作符
deque& operator=(const deque& deq);
// 将 [beg, end] 区间中的数据拷贝赋值给本身
assign(beg, end);
// 将n个elem拷贝赋值给本身
assign(n, elem);

示例:

#include <iostream>
#include <string>
#include <deque>

using namespace std;

void printDeque16(const deque<int>& d) {
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// deque 构造函数
void test16_1() {
	deque<int> d1;
	for (int i = 0; i < 10; i++) {
		d1.push_back(i);
	}
	printDeque16(d1);

	// = 赋值
	deque<int> d2;
	d2 = d1;
	printDeque16(d2);

	// assign 赋值
	deque<int> d3;
	d3.assign(d1.begin(), d1.end());
	printDeque16(d3);

	deque<int> d4;
	d4.assign(10, 100);
	printDeque16(d4);
}

// 实用操作
void test16_2() {

}

int main() {
	test16_1();
	cout << endl;
	test16_2();

	system("pause");

	return 0;
}

结果:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
100 100 100 100 100 100 100 100 100 100

总结:deque赋值操作也与vector相同,需熟练掌握。

16.3.4、deque 大小操作

函数原型:

// 判断容器是否为空
deque.empty();
// 返回容器中元素的个数
deque.size();
// 重新指定容器的长度为num,若容器变长,则以默认值填充新位置;若容器变短,则末尾超出容器长度的元素被删除
deque.resize(num);
// 重新指定容器的长度为num,若容器变长,则以elem值填充新位置;若容器变短,则末尾超出容器长度的元素被删除
deque.resize(num, elem);

示例:

#include <iostream>
#include <string>
#include <deque>

using namespace std;

void printDeque17(const deque<int>& d) {
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// deque 大小操作
void test17_1() {
	deque<int> d1;
	for (int i = 0; i < 10; i++) {
		d1.push_back(i);
	}
	printDeque17(d1);

	if (d1.empty()) {
		cout << "d1 为空" << endl;
	} else {
		cout << "d1 不为空" << ", 大小为:" << d1.size() << endl;
	}

	// 重新指定大小
	d1.resize(15);
	printDeque17(d1);
	d1.resize(20, 1);
	printDeque17(d1);
	d1.resize(5);
	printDeque17(d1);
}

// 实用操作
void test17_2() {

}

int main() {
	test17_1();
	cout << endl;
	test17_2();

	system("pause");

	return 0;
}

结果:
0 1 2 3 4 5 6 7 8 9
d1 不为空, 大小为:10
0 1 2 3 4 5 6 7 8 9 0 0 0 0 0
0 1 2 3 4 5 6 7 8 9 0 0 0 0 0 1 1 1 1 1
0 1 2 3 4

16.3.5、deque 插入和删除

功能:向 deque 容器中插入和删除数据。

函数原型:

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

// 指定位置操作
// 在pos位置插入一个elem元素的拷贝,返回新数据的位置
insert(pop, elem);
// 在pos位置插入n个elem数据,无返回值
insert(pos, n, elem);
// 在pos位置插入 [beg, end] 区间的数据,无返回值
insert(pos, beg, end);
// 清空容器的所有数据
clear();
// 删除 [beg, end] 区间的数据,返回下一个数据的位置
erase(beg, end);
// 删除 pos 位置的数据,返回下一个数据的位置
erase(pos);
#include <iostream>
#include <string>
#include <deque>

using namespace std;

void printDeque18(const deque<int>& d) {
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// deque 插入和删除
void test18_1() {
	deque<int> d1;

	// 尾插
	d1.push_back(10);
	d1.push_back(20);
	printDeque18(d1);

	// 头插
	d1.push_front(100);
	d1.push_front(200);
	printDeque18(d1);

	// 尾删
	d1.pop_back();
	printDeque18(d1);

	// 头删
	d1.pop_front();
	printDeque18(d1);

	// 指定位置插入
	d1.insert(d1.begin(), 1000);
	printDeque18(d1);
	d1.insert(d1.begin(), 2, 10000);
	printDeque18(d1);

	// 按照区间进行插入
	deque<int> d2;
	d2.push_back(1);
	d2.push_back(2);
	d2.push_back(3);
	d1.insert(d1.begin(), d2.begin(), d2.end());
	printDeque18(d1);

	// 删除
	deque<int>::iterator it= d1.begin();
	it++;
	d1.erase(it);
	printDeque18(d1);

	// 按照区间方式删除
	d1.erase(d1.begin(), d1.end()); // 即 d1.clear();
	printDeque18(d1);
}

// 实用操作
void test18_2() {

}

int main() {
	test18_1();
	cout << endl;
	test18_2();

	system("pause");

	return 0;
}

结果:
10 20
200 100 10 20
200 100 10
100 10
1000 100 10
10000 10000 1000 100 10
1 2 3 10000 10000 1000 100 10
1 3 10000 10000 1000 100 10

16.3.6、deque 数据存取

函数原型:

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

总结:

  • 除了用迭代器获取deque容器中元素,[] 和 at() 也可以;
  • front 返回容器中第一个元素;
  • back 返回容器中最后一个元素。

16.3.7、deque 排序

功能描述:利用算法实现 deque 容器进行排序。

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

#include <iostream>
#include <string>
#include <deque>
#include <algorithm>

using namespace std;

void printDeque19(const deque<int>& d) {
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// deque 排序
void test19_1() {
	deque<int> d1;
	d1.push_back(10);
	d1.push_back(20);
	d1.push_back(30);
	d1.push_front(100);
	d1.push_front(200);
	d1.push_front(300);
	cout << "排序前:";
	printDeque19(d1);
	cout << "排序后(默认从小到大):";
	sort(d1.begin(), d1.end());
	printDeque19(d1);
}

// string 的赋值操作
void test19_2() {
}

int main() {
	test19_1();
	cout << endl;
	test19_2();

	system("pause");

	return 0;
}

结果:
排序前:300 200 100 10 20 30
排序后(默认从小到大):10 20 30 100 200 300

16.4、stack 容器

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

栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为。

16.4.1、stack 常用接口

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

赋值操作:
// 重载 = 操作符
stack& operator=(const stack& stk);

数据存取:
// 向栈顶添加元素
push(elem);
// 从栈顶移除第一个元素
pop();
// 返回栈顶元素
top();

大小操作:
// 判断堆栈是否为空
empty();
// 返回栈的大小
size();

示例:

#include <iostream>
#include <string>
#include <stack>

using namespace std;

// 栈 容器
void test21_1() {
	stack<int> s;

	// 入栈
	s.push(10);
	s.push(20);
	s.push(30);
	s.push(40);

	// 出栈
	while (!s.empty()) {
		cout << "栈顶元素为:" << s.top() << endl;
		// 出栈
		s.pop();
	}

	cout << "栈的大小:" << s.size() << endl;
}

// 
void test21_2() {
}

int main() {
	test21_1();
	cout << endl;
	test21_2();

	system("pause");

	return 0;
}

结果:
栈顶元素为:40
栈顶元素为:30
栈顶元素为:20
栈顶元素为:10
栈的大小:0

16.4、queue 容器

16.4.1、queue 基本概念

概念:Queue 是一种先进先出(First In First Out,FIFO)的数据结构。

16.4.2、queue 常用接口

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

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

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

// 大小操作
// 判断堆栈是否为空
empty();

// 返回栈的大小
size();

16.5、list 容器

16.5.1、list 基本概念

功能:将数据进行链式存储。

链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的。

链表的组成:链表由一系列结点组成。

结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

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

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

list的优点:

  • 采用动态存储分配,不会造成内存浪费和溢出;
  • 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素。

list的缺点:

  • 链表灵活,但是空间(指针域)和时间(遍历)额外耗费较大。

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

总结:STL 中 list 和 vector 是两个最常被使用的容器,各有优缺点。

16.5.2、list 构造函数

函数原型:

// list 采用模板类实现,对象的默认构造形式
list<T> lst;
// 构造函数将 [beg, end] 区间中的元素拷贝给本身
list(beg, end);
// 构造函数将n个elem拷贝给本身
list(n, elem);
// 拷贝构造函数
list(const list& lst);

16.5.3、list 赋值和交换

函数原型:

// 将 [beg, end) 区间中的数据拷贝赋值给本身
assign(beg, end);
// 将n个elem拷贝赋值给本身
assign(n, elem);
// 重载等号操作符
list& operator=(const list& lst);
// 将lst与本身的元素交换
swap(lst);

16.5.4、list 大小操作

函数原型:

// 返回容器中元素的个数
size();
// 判断容器是否为空
empty();
// 重新指定容器的长度为num,若容器变长,则以默认值填充新位置;若容器变短,则末尾超出容器长度的元素被删除
resize(num);
// 重新指定容器的长度为num,若容器变长,则以elem值填充新位置;若容器变短,则末尾超出容器长度的元素被删除
resize(num, elem);

16.5.5、list 插入和删除

函数原型:

// 在容器尾部加入一个元素
push_back(elem);
// 删除容器中最后一个元素
pop_back();
// 在容器开头插入一个元素
push_front(elem);
// 从容器开头移除第一个元素
pop_front();
// 在 pos 位置插入elem元素的拷贝,返回新数据的位置
insert(pos, elem);
// 在pos位置插入n个elem数据,无返回值
insert(pos, n, elem);
// 在 pos 位置插入 [beg, end) 区间的数据,无返回值
insert(pos, beg, end);
// 移除容器的所有数据
clear();
// 删除[beg, end) 区间的数据,返回下一个数据的位置
erase(beg, end);
// 删除 pos 位置的数据,返回下一个数据的位置
erase(pos);
// 删除容器中所有与elem值匹配的元素
remove(elem);

16.5.6、list 数据存取

函数原型:

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

list 不可以用 [] 和 at() 访问 list 容器中的元素。因为list本质是链表,不是哦那个连续线性空间存储数据,迭代器也是不支持随机访问的。

函数原型:

// 反转链表
reverse();
// 链表排序
sort();

注意:所有不支持随机访问迭代器的容器,都不可以用标准算法,容器内部会提供对应一些算法。

// 不支持
sort(lst.begin(), lst.end());

// 使用内部排序算法,默认升序
lst.sort();
// 使用内部排序算法,默认降序
bool myCompare(int v1, int v2) { // 返回true,则v1在v2前面
    return v1 > v2; // 
}
lst.sort(myCompare);

16.6、set / multiset 容器

16.6.1、set 基本概念

set:所有元素都会在插入时自动被排序。

本质:set / multiset 属于关联式容器,底层结构是用二叉树实现。

set 和 multiset 区别:set不允许容器中有重复的元素;multiset允许容器中有重复的元素。

16.6.2、set 构造和赋值

函数原型:

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

// 赋值
// 重载 = 操作符
set& operator=(const set& st);

16.6.3、set 大小和交换

函数原型:

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

16.6.4、set 插入和删除

函数原型:

// 在容器中插入元素
insert(elem);
// 清除所有元素
clear();
// 删除 pos 迭代器所指的元素,返回下一个元素的迭代器
erase(pos);
// 删除区间 [beg, end)的所有元素,返回下一个元素的迭代器
erase(beg, end);
// 删除容器中值为elem的元素
erase(elem);

16.6.5、set 查找和统计

函数原型:

// 查找 key 是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
find(key);
// 统计key的元素个数
count(key);

16.6.6、set 和 multiset 区别

区别:

  • set不可以插入重复数据,而multiset可以;
  • set插入数据的同时会返回插入结果,表示插入是否成功;
  • multiset不会检测数据,因此可以插入重复数据。

16.6.7、pair 对组创建

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

两种创建方式:

pair<type, type> p(value1, value2);
pair<type, type> p = make_pair(value1, value2);

16.6.8、set 容器排序

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

#include <iostream>
#include <string>
#include <set>

using namespace std;

class MyCompare {
public:
	bool operator()(int v1, int v2) const {
		return v1 > v2;
	}
};

template<class T1, class T2>
void printSet22(set<T1, T2>& s) {
	for (typename set<T1, T2>::iterator it = s.begin(); it != s.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// set 存放自定义数据类型
class Person22 {
public:
	Person22(string name, int age) {
		this->name = name;
		this->age = age;
	}

	string name;
	int age;
};
class Person22Comparator {
public:
	bool operator()(const Person22& p1, const Person22& p2) const {
		return p1.age > p2.age; // 降序
	}
};

// set 存放内置数据类型
void test22_1() {
	set<int> s1;
	s1.insert(10);
	s1.insert(40);
	s1.insert(20);
	s1.insert(60);
	s1.insert(30);
	printSet22<int>(s1);

	// 指定排序规则为从大到小
	set<int, MyCompare> s2;
	s2.insert(10);
	s2.insert(40);
	s2.insert(20);
	s2.insert(60);
	s2.insert(30);
	printSet22<int, MyCompare>(s2);
}
void test22_2() {
	set<Person22, Person22Comparator> s;

	// 创建Person22对象
	Person22 p1("刘备", 24);
	Person22 p2("关羽", 28);
	Person22 p3("张飞", 25);

	s.insert(p1);
	s.insert(p2);
	s.insert(p3);
	for (set<Person22, Person22Comparator>::iterator it = s.begin(); it != s.end(); it++) {
		cout << "姓名:" << it->name << ", 年龄:" << it->age << endl;
	}
}

int main() {
	test22_1();
	cout << endl;
	test22_2();

	system("pause");

	return 0;
}

结果:
10 20 30 40 60
60 40 30 20 10

姓名:关羽, 年龄:28
姓名:张飞, 年龄:25
姓名:刘备, 年龄:24

总结:利用仿函数可以指定set容器的排序规则。

16.7、map / multimap 容器

16.7.1、map 基本概念

简介:map中所有元素都是pair,pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值),所有元素都会根据元素的键值自动排序。

本质:map / multimap 属于关联式容器,底层结构是用二叉树实现。

优点:可以根据key值快速找到value值。

map和multimap区别:map不允许容器中有重复key值元素,multimap允许容器中有重复的key值元素。

16.7.2、map 构造和赋值

函数原型:

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

// 赋值
// 重载 = 操作符
map& operator=(const map& mp);

示例:

#include <iostream>
#include <string>
#include <map>

using namespace std;

void printMap23(map<int, int>& m) {
	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
		cout << it->first << "=" << it->second << endl;
	}
	cout << endl;
}

// map
void test23_1() {
	// 默认构造
	map<int, int> m;
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));
	m.insert(pair<int, int>(4, 40));
	printMap23(m);

	// 拷贝构造
	map<int, int> m2(m);
	printMap23(m2);

	// 赋值
	map<int, int> m3;
	m3 = m2;
	printMap23(m3);
}
void test23_2() {
}

int main() {
	test23_1();
	cout << endl;
	test23_2();

	system("pause");

	return 0;
}

结果:
1=10
2=20
3=30
4=40

1=10
2=20
3=30
4=40

1=10
2=20
3=30
4=40

总结:map中所有元素都是成对出现,插入数据时候要使用对组。

16.7.3、map 大小和交换

功能:统计map容器大小以及交换map容器。

函数原型:

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

示例:

#include <iostream>
#include <string>
#include <map>

using namespace std;

void printMap24(map<int, int>& m) {
	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
		cout << it->first << "=" << it->second << endl;
	}
	cout << endl;
}

// map 大小
void test24_1() {
	map<int, int> m;
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));
	if (m.empty()) {
		cout << "m为空" << endl;
	} else {
		cout << "m不为空,大小为:" << m.size() << endl;
	}
}

// map 交换
void test24_2() {
	map<int, int> m;
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));
	map<int, int> m2;
	m2.insert(pair<int, int>(4, 40));
	m2.insert(pair<int, int>(5, 50));
	m2.insert(pair<int, int>(6, 60));

	cout << "交换前..." << endl;
	printMap24(m);
	printMap24(m2);
	cout << "交换后..." << endl;
	m.swap(m2);
	printMap24(m);
	printMap24(m2);
}

int main() {
	test24_1();
	cout << endl;
	test24_2();

	system("pause");

	return 0;
}

结果:
m不为空,大小为:3

交换前...
1=10
2=20
3=30

4=40
5=50
6=60

交换后...
4=40
5=50
6=60

1=10
2=20
3=30

16.7.4、map 插入和删除

功能:map容器进行插入数据和删除数据。

函数原型:

// 在容器中插入元素
insert(elem);
// 清除所有元素
clear();
// 删除pos迭代器所指的元素,返回下一个元素的迭代器
erase(pos);
// 删除区间 [beg, end) 的所有元素,返回下一个元素的迭代器
erase(beg, end);
// 删除容器中值为key的元素
erase(key);

示例:

#include <iostream>
#include <string>
#include <map>

using namespace std;

void printMap25(map<int, int>& m) {
	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
		cout << it->first << "=" << it->second << endl;
	}
	cout << endl;
}

// map 
void test25_1() {
	map<int, int> m;

	// 插入
	// 方式1
	m.insert(pair<int, int>(1, 10));

	// 方式2
	m.insert(make_pair(2, 20));

	// 方式3
	m.insert(map<int, int>::value_type(3, 30));

	// 方式4 - 不建议,用途是可以利用key访问到value
	m[4] = 40;

	cout << m[5] << endl;

	printMap25(m);

	// 删除
	m.erase(m.begin());
	printMap25(m);

	m.erase(3); // 按照key删除
	printMap25(m);

	m.erase(m.begin(), m.end()); // 等价于 m.clear()
	printMap25(m);
}

// map 
void test25_2() {
}

int main() {
	test25_1();
	cout << endl;
	test25_2();

	system("pause");

	return 0;
}

结果:
0
1=10
2=20
3=30
4=40
5=0

2=20
3=30
4=40
5=0

2=20
4=40
5=0

16.7.5、map 查找和统计

功能:对map容器进行查找数据以及统计数据。

函数原型:

// 查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
find(key);
// 统计key的元素个数
count(key);

示例:

#include <iostream>
#include <string>
#include <map>

using namespace std;

void printMap26(map<int, int>& m) {
	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
		cout << it->first << "=" << it->second << endl;
	}
	cout << endl;
}

// map 查找和统计
void test26_1() {
	map<int, int> m;
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));

	// 查找
	map<int, int>::iterator pos = m.find(3);
	if (pos != m.end()) {
		cout << "查到了元素" << pos->first << "=" << pos->second << endl;
	} else {
		cout << "未找到元素" << endl;
	}

	// 统计
	int num = m.count(3); // 对于map,num只能为0或1
	cout << "num=" << num << endl;
}

// map 
void test26_2() {
}

int main() {
	test26_1();
	cout << endl;
	test26_2();

	system("pause");

	return 0;
}

结果:
查到了元素3=30
num=1

16.7.6、map 容器排序

map容器默认排序规则为:按照key值进行从小到大排序,利用仿函数,可以改变排序规则。对于自定义数据类型,map必须要指定排序规则,同set容器。

例如:map<int, int, MyCompare> m;

17、STL 函数对象

17.1、函数对象

17.1.1、函数对象概念

概念:重载函数调用操作符的类,其对象常称为函数对象。函数对象使用重载的()时,行为类似函数调用,也叫仿函数。

本质:函数对象(仿函数)是一个类,不是一个函数。

17.1.2、函数对象使用

特点:

  • 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值;
  • 函数对象超出普通函数的概念,函数对象可以有自己的状态;
  • 函数对象可以作为参数传递。
#include <iostream>
#include <string>

using namespace std;

// 函数对象(仿函数)
class MyAdd {
public:
	int operator()(int v1, int v2) {
		return v1 + v2;
	}
};

class MyPrint {
public:
	MyPrint() {
		this->count = 0;
	}

	void operator()(string str) {
		cout << str << endl;
		this->count++;
	}

	int count; // 内部自己状态
};

// 函数对象可以作为参数传递
void doPrint(MyPrint& mp, string str) {
	mp(str);
}

void test27_1() {
	MyAdd myAdd;
	cout << myAdd(10, 10) << endl;

	MyPrint myPrint;
	myPrint("hello world");

	cout << "myPrint 调用次数为:" << myPrint.count << endl;

	// 函数对象可以作为参数传递
	doPrint(myPrint, "hello c++");
}

// map 
void test27_2() {
}

int main() {
	test27_1();
	cout << endl;
	test27_2();

	system("pause");

	return 0;
}

结果:
20
hello world
myPrint 调用次数为:1
hello c++

17.2、谓词

17.2.1、谓词概念

概念:

  • 返回bool类型的仿函数称为谓词;
  • 如果 operator() 接受一个参数,那么叫做一元谓词;
  • 如果 operator() 接受两个参数,那么叫做二元谓词。

17.2.2、一元谓词

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

// 一元谓词
class GreaterFive {
public:
	bool operator()(int val) {
		if (val > 5) {
			return true;
		}
		return false;
	}
};

void test28_1() {
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}

	// 查找容器中有没有大于5的数字
	vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
	if (it == v.end()) {
		cout << "未找到" << endl;
	} else {
		cout << "找到了大于5的数字为:" << *it << endl;
	}
}

// 
void test28_2() {
}

int main() {
	test28_1();
	cout << endl;
	test28_2();

	system("pause");

	return 0;
}

结果:
找到了大于5的数字为:6

17.2.2、二元谓词

示例:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

// 二元谓词
class MyCompare29 {
public:
	bool operator()(int v1, int v2) const {
		return v1 > v2;
	}
};

void test29_1() {
	vector<int> v;
	v.push_back(10);
	v.push_back(40);
	v.push_back(80);
	v.push_back(50);
	v.push_back(20);

	// 排序
	sort(v.begin(), v.end());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;

	// 使用函数对象,改变算法策略,变为排序规则为从大到小
	sort(v.begin(), v.end(), MyCompare29());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

// 
void test29_2() {
}

int main() {
	test29_1();
	cout << endl;
	test29_2();

	system("pause");

	return 0;
}

结果:
10 20 40 50 80
80 50 40 20 10

17.3、内建函数对象

17.3.1、内建函数对象意义

分类:算数仿函数、关系仿函数、逻辑仿函数。

用法:

  • 这些仿函数所产生的对象、用法和一般函数完全相同;
  • 使用内建函数对象,需要引入头文件#include<functional>

17.3.2、算数仿函数

功能:实现四则运算,其中negate是一元运算,其他都是二元运算。

仿函数原型:

// 加法仿函数
template<class T> T plus<T>
// 减法仿函数
template<class T> T minus<T>
// 乘法仿函数
template<class T> T multiplies<T>
// 除法仿函数
template<class T> T devides<T>
// 取模仿函数
template<class T> T modulus<T>
// 取反仿函数
template<class T> T negate<T>

17.3.3、关系仿函数

仿函数原型:

// 等于
template<class T> bool equal_to<T>
// 不等于
template<class T> bool not_equal_to<T>
// 大于
template<class T> bool greater<T>
// 大于等于
template<class T> bool greater_equal<T>
// 小于
template<class T> bool less<T>
// 小于等于
template<class T> bool less_equal<T>

17.3.4、逻辑仿函数

函数原型:

// 逻辑与
template<class T> bool logical_and<T>
// 逻辑或
template<class T> bool logical_or<T>
// 逻辑非
template<class T> bool logical_not<T>

17.3.5、示例

示例:

#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <algorithm>

using namespace std;

// 算数仿函数
void test30_1() {
	// 取反
	negate<int> negate;
	cout << negate(50) << endl;

	// 加法
	plus<int> plus;
	cout << plus(1, 1) << endl;
}

// 关系仿函数
class MyCompare {
public:
	bool operator()(int v1, int v2) const {
		return v1 > v2;
	}
};
void test30_2() {
	vector<int> v;
	v.push_back(10);
	v.push_back(60);
	v.push_back(40);
	v.push_back(20);
	v.push_back(90);
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;

	// 降序
	// sort(v.begin(), v.end(), MyCompare());
	sort(v.begin(), v.end(), greater<int>()); // 内建的函数对象
}

// 逻辑仿函数
void test30_3() {
	vector<bool> v;
	v.push_back(false);
	v.push_back(true);
	v.push_back(false);
	v.push_back(true);
	for (vector<bool>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;

	// 利用逻辑非,将容器v搬运到容器v2中,并执行取反操作
	vector<bool> v2;
	v2.resize(v.size()); // 指定相同的空间大小
	transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());
	for (vector<bool>::iterator it = v2.begin(); it != v2.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

int main() {
	test30_1();
	cout << endl;
	test30_2();
	cout << endl;
	test30_3();
	cout << endl;

	system("pause");

	return 0;
}

结果:
-50
2

10 60 40 20 90

0 1 0 1
1 0 1 0

18、STL 常用算法

算法主要是由头文件<algorithm> <functional> <numeric>组成:

  • <algorithm>是所有STL头文件中最大的一个,范围涉及到比较、交换、查找、遍历操作、复制、修改等等;
  • <numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数;
  • <functional>定义了一些模板类,用以声明函数对象。

18.1、常用遍历算法

// 遍历容器
for_each();
// 搬运容器元素到另一个容器中
transform();

18.1.1、for_each

函数原型:

for_each(iterator beg, iterator end, _func);
// 遍历算法 遍历容器元素
// beg 开始迭代器
// end 结束迭代器
// _func 函数或者函数对象

示例:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

// 普通函数
void print01(int val) {
	cout << val << " ";
}

// 仿函数
class print02 {
public:
	void operator()(int val) {
		cout << val << " ";
	}
};

void test31_1() {
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}

	for_each(v.begin(), v.end(), print01);
	cout << endl;

	for_each(v.begin(), v.end(), print02());
	cout << endl;
}

// 
void test31_2() {
}

int main() {
	test31_1();
	cout << endl;
	test31_2();

	system("pause");

	return 0;
}

结果:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9

18.1.2、transform

函数原型:transform(iterator beg1, iterator end1, iterator beg2, _func);

  • beg1:源容器开始迭代器;
  • end1:源容器结束迭代器;
  • beg2:目标容器开始迭代器;
  • _func:函数或者函数对象。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

// transform
class Transform {
public:
	int operator()(int v) {
		return v;
	}
};
class MyPrint {
public:
	void operator()(int v) {
		v += 100;
		cout << v << " ";
	}
};

void test32_1() {
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}

	vector<int> vTarget; // 目标容器
	vTarget.resize(v.size()); // 搬运的目标容器需要提前开辟好空间
	transform(v.begin(), v.end(), vTarget.begin(), Transform());

	for_each(vTarget.begin(), vTarget.end(), MyPrint());
	cout << endl;
}

// 
void test32_2() {
}

int main() {
	test32_1();
	cout << endl;
	test32_2();
	cout << endl;

	system("pause");

	return 0;
}

结果:
100 101 102 103 104 105 106 107 108 109

18.2、常用查找算法

算法简介:

// 查找元素
find();
// 按条件查找元素
find_if();
// 查找相邻重复元素
adjacent_find();
// 二分查找法
binary_search();
// 统计元素个数
count();
// 按条件统计元素个数
count_if();

18.2.1、find

查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()。

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

class Person33 {
public:
	Person33(string name, int age) {
		this->name = name;
		this->age = age;
	}

	// 重载 == 号,让底层find()知道如何对比person数据类型
	bool operator==(const Person33& p) {
		if (this->name == p.name && this->age == p.age) {
			return true;
		} else {
			return false;
		}
	}

	string name;
	int age;
};

void test33_1() {
	// 查找内置数据类型
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}
	// 查找容器中是否有5这个元素
	vector<int>::iterator it = find(v.begin(), v.end(), 5);
	if (it == v.end()) {
		cout << "没有找到" << endl;
	} else {
		cout << "找到:" << *it << endl;
	}
	
	// 查找自定义数据类型
	vector<Person33> v2;
	Person33 p1("aaa", 10);
	Person33 p2("bbb", 20);
	Person33 p3("ccc", 30);
	Person33 p4("ddd", 40);
	v2.push_back(p1);
	v2.push_back(p2);
	v2.push_back(p3);
	v2.push_back(p4);

	Person33 pp("bbb", 20);
	vector<Person33>::iterator it2 = find(v2.begin(), v2.end(), pp);
	if (it2 == v2.end()) {
		cout << "没有找到" << endl;
	} else {
		cout << "找到:姓名=" << it2->name << ", 年龄=" << it2->age << endl;
	}
}

// 
void test33_2() {
}

int main() {
	test33_1();
	cout << endl;
	test33_2();
	cout << endl;

	system("pause");

	return 0;
}

结果:
找到:5
找到:姓名=bbb, 年龄=20

总结:利用find() 可以在容器中找到指定的元素,返回值是迭代器。

18.2.2、find_if

函数原型:find_if(iterator beg, iterator end, _Pred);

按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置;

beg:开始迭代器;end:结束迭代器;_Pred:函数或者谓词(返回bool类型的仿函数)。

示例:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

class GreaterFive {
public:
	bool operator()(int val) {
		return val > 5;
	}
};

class Person34 {
public:
	Person34(string name, int age) {
		this->name = name;
		this->age = age;
	}

	// 重载 == 号,让底层find()知道如何对比person数据类型
	bool operator==(const Person34& p) {
		if (this->name == p.name && this->age == p.age) {
			return true;
		} else {
			return false;
		}
	}

	string name;
	int age;
};

class Greater20 {
public:
	bool operator()(Person34& p) {
		return p.age > 20;
	}
};

// 查找内置数据类型
void test34_1() {
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}

	vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
	if (it == v.end()) {
		cout << "没有找到" << endl;
	} else {
		cout << "找到大于5的数字为:" << *it << endl;
	}

}

// 查找自定义数据类型
void test34_2() {
	vector<Person34> v;

	// 创建数据
	Person34 p1("aaa", 10);
	Person34 p2("bbb", 20);
	Person34 p3("ccc", 30);
	Person34 p4("ddd", 40);
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);

	// 找年龄大于20的人
	vector<Person34>::iterator it = find_if(v.begin(), v.end(), Greater20());
	if (it == v.end()) {
		cout << "没有找到" << endl;
	} else {
		cout << "找到姓名:" << it->name << ", 年龄:" << it->age << endl;
	}
}

int main() {
	test34_1();
	cout << endl;
	test34_2();
	cout << endl;

	system("pause");

	return 0;
}

结果:
找到大于5的数字为:6

找到姓名:ccc, 年龄:30

18.2.3、adjacent_find

功能:查找相邻重复元素。

函数原型:adjacent_find(iterator beg, iterator end);

查找相邻重复元素,返回相邻元素的第一个位置的迭代器;

beg:开始迭代器;end:结束迭代器。

示例:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

// adjacent_find
void test35_1() {
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}

	v.push_back(0);
	v.push_back(2);
	v.push_back(0);
	v.push_back(3);
	v.push_back(3);
	v.push_back(0);
	v.push_back(1);
	v.push_back(4);

	vector<int>::iterator pos = adjacent_find(v.begin(), v.end());
	if (pos == v.end()) {
		cout << "未找到相邻重复元素" << endl;
	} else {
		cout << "找到相邻重复元素:" << *pos << endl;
	}
}

// 
void test35_2() {
}

int main() {
	test35_1();
	cout << endl;
	test35_2();
	cout << endl;

	system("pause");

	return 0;
}

结果:
找到相邻重复元素:3

18.2.4、binary_search

函数原型:bool binary_search(iterator beg, iterator end, value);

  • 查找指定的元素,查到返回true,否则返回false;
  • 注意:在无序序列中不可用;
  • beg:开始迭代器;end:结束迭代器;value:查找的元素。

示例:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

// binary_search
void test36_1() {
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}

	// 查找容器中是否有9元素(必须是有序的序列)
	bool ret = binary_search(v.begin(), v.end(), 9);
	if (ret) {
		cout << "找到了元素" << endl;
	} else {
		cout << "未找到元素" << endl;
	}
}

// 
void test36_2() {
}

int main() {
	test36_1();
	cout << endl;
	test36_2();
	cout << endl;

	system("pause");

	return 0;
}

结果:
找到了元素

18.2.5、count

函数原型:count(iterator beg, iterator end, value);

  • 统计元素出现次数;
  • beg:开始迭代器;end:结束迭代器;value:统计的元素。

示例:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

class Person37 {
public:
	Person37(string name, int age) {
		this->name = name;
		this->age = age;
	}

	// 重载 == 号,让底层find()知道如何对比person数据类型
	bool operator==(const Person37& p) {
		if (this->age == p.age) {
			return true;
		} else {
			return false;
		}
	}

	string name;
	int age;
};

void test37_1() {
	// 统计内置数据类型
	vector<int> v;
	v.push_back(10);
	v.push_back(40);
	v.push_back(30);
	v.push_back(40);
	v.push_back(20);
	v.push_back(40);
	// 统计次数
	int num = count(v.begin(), v.end(), 40);
	cout << "值为40的元素个数为" << endl;

	// 统计自定义数据类型
	vector<Person37> v2;
	Person37 p1("刘备", 35);
	v2.push_back(p1);
	Person37 p2("关羽", 35);
	v2.push_back(p2);
	Person37 p3("张飞", 35);
	v2.push_back(p3);
	Person37 p4("赵云", 35);
	v2.push_back(p4);
	Person37 p5("曹操", 40);
	v2.push_back(p5);
	Person37 p("诸葛亮", 35);
	num = count(v2.begin(), v2.end(), p);
	cout << "和诸葛亮同岁的人员个数为:" << num << endl;
}

// 
void test37_2() {
}

int main() {
	test37_1();
	cout << endl;
	test37_2();
	cout << endl;

	system("pause");

	return 0;
}

结果:
值为40的元素个数为
和诸葛亮同岁的人员个数为:4

18.2.6、count_if

函数原型:count_if(iterator beg, iterator end, _Pred);

  • 按条件统计元素出现次数;
  • beg:开始迭代器;end:结束迭代器;_Pred:谓词。

示例:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

class Greater38 {
public:
	bool operator()(int val) const {
		return val > 20;
	}
};

class Person38 {
public:
	Person38(string name, int age) {
		this->name = name;
		this->age = age;
	}

	// 重载 == 号,让底层find()知道如何对比person数据类型
	bool operator==(const Person38& p) {
		if (this->age == p.age) {
			return true;
		} else {
			return false;
		}
	}

	string name;
	int age;
};

class AgeGreater38 {
public:
	bool operator()(const Person38& p) const {
		return p.age > 20;
	}
};

void test38_1() {
	// 统计内置数据类型
	vector<int> v;
	v.push_back(10);
	v.push_back(40);
	v.push_back(30);
	v.push_back(40);
	v.push_back(20);
	v.push_back(40);
	// 统计次数
	int num = count_if(v.begin(), v.end(), Greater38());
	cout << "值大于20的元素个数为" << num << endl;

	// 统计自定义数据类型
	vector<Person38> v2;
	Person38 p1("刘备", 35);
	v2.push_back(p1);
	Person38 p2("关羽", 20);
	v2.push_back(p2);
	Person38 p3("张飞", 18);
	v2.push_back(p3);
	Person38 p4("赵云", 35);
	v2.push_back(p4);
	Person38 p5("曹操", 40);
	v2.push_back(p5);
	num = count_if(v2.begin(), v2.end(), AgeGreater38());
	cout << "大于20岁的人员个数为:" << num << endl;
}

// 
void test38_2() {
}

int main() {
	test38_1();
	cout << endl;
	test38_2();
	cout << endl;

	system("pause");

	return 0;
}

结果:
值大于20的元素个数为4
大于20岁的人员个数为:3

18.3、常用排序算法

// 对容器内元素进行排序
sort();
// 洗牌 指定范围内的元素随机调整次序
random_shuffle();
// 容器元素合并,并存储到另一容器中
merge();
// 反转指定范围的元素
reverse();

18.3.1、sort

函数原型:sort(iterator beg, iterator end, _Pred);

  • 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置;
  • beg:开始迭代器;end:结束迭代器;_Pred:谓词。

18.3.2、random_shuffle

函数原型:random_shuffle(iterator beg, iterator end);

  • 指定范围内的元素随机调整次序;
  • beg:开始迭代器;end:结束迭代器。

总结:random_shuffle 洗牌算法比较实用,使用时记得加随机数种子。

18.3.3、merge

功能:两个容器元素合并,并存储到另一容器中。

函数原型:merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

  • 容器元素合并,并存储到另一容器中(需要提前给新的容器进行空间分配(resize()));
  • 注意:两个容器必须是有序的;
  • beg1:容器1开始迭代器;end1:容器1结束迭代器;beg2:容器2开始迭代器;end2:容器2结束迭代器;dest:目标容器开始迭代器。

示例:

18.3.4、reverse

功能:将容器内元素进行反转。

函数原型:reverse(iterator beg, iterator end);

  • 反转指定范围的元素;
  • beg:开始迭代器;end:结束迭代器。

18.4、常用拷贝和替换算法

算法简介:

// 容器内指定范围的元素拷贝到另一容器中
copy();
// 将容器内指定范围的就元素修改为新元素
replace();
// 容器内指定范围满足条件的元素替换为新元素
replace_if();
// 互换两个容器的元素
swap();

18.4.1、copy

功能:容器内指定范围的元素拷贝到另一容器中。

函数原型:copy(iterator beg, iterator end, iterator dest);

  • 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置。
  • beg:开始迭代器;end:结束迭代器;dest:目标起始迭代器。

总结:利用copy算法在拷贝时,目标容器记得提前开辟空间。

18.4.2、replace

功能:将容器内指定范围的旧元素修改为新元素。

函数原型:replace(iterator beg, iterator end, oldvalue, newvalue);

  • 将区间内旧元素替换成新元素;
  • beg:开始迭代器;end:结束迭代器;oldvalue:旧元素;newvalue:新元素。

总结:replace 会替换区间内所有满足条件(=oldvalue)的元素。

18.4.3、replace_if

功能:将区间内满足条件的元素,替换成指定元素。

函数原型:replace_if(iterator beg, iterator end, _pred, newvalue);

  • 按条件替换元素,满足条件的替换成指定元素;
  • beg:开始迭代器;end:结束迭代器;_pred:谓词;newvalue:替换的新元素。

总结:replace_if 按条件查找,可以利用仿函数灵活筛选满足的条件。

18.4.4、swap

功能:互换两个容器的元素。

函数原型:swap(container c1, container c2);

  • 互换两个容器的元素(同种类型);
  • c1容器1;c2容器2.

总结:swap 交换容器时,注意交换的容器要同种类型。

18.5、常用算数生成算法

注意:算数生成算法属于小型算法,使用时包含的头文件为 #include <numeric>

算法简介:

// 计算容器元素累计总和
accumulate();
// 向容器中添加元素
fill();

18.5.1、accumulate

功能:计算区间内容器元素累计总和。

函数原型:accumulate(iterator beg, iterator end, value);

  • 计算容器元素累计总和;
  • beg:开始迭代器;end:结束迭代器;value:起始值。

总结:accumulate 使用时头文件注意是 numeric,这个算法很实用。

18.5.2、fill

功能:向容器中填充指定的元素。

函数原型:fill(iterator beg, iterator end, value);

  • 向容器中填充元素;
  • beg:开始迭代器;end:结束迭代器;value:填充的值。

总结:利用fill可以将容器区间内怨怒是填充为指定的值。

18.6、常用集合算法

算法简介:

// 求两个容器的交集
set_intersetction();
// 求两个容器的并集
set_union();
// 求两个容器的差集
set_difference();

18.6.1、set_intersection

功能:求两个容器的交集。

函数原型:set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

  • 求两个集合的交集;
  • 注意:两个集合必须是有序序列;
  • beg1、end1:容器1开始、结束迭代器;beg2、end2:容器2开始、结束迭代器;dest:目标容器开始迭代器(需要提前开辟空间(resize()))。
  • 返回值是交集中最后一个元素的位置。

18.6.2、set_union

函数原型:set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

  • 求两个集合的并集;
  • 注意:两个集合必须是有序序列;
  • beg1、end1:容器1开始、结束迭代器;beg2、end2:容器2开始、结束迭代器;dest:目标容器开始迭代器(需要提前开辟空间(resize()))。
  • 返回值是并集中最后一个元素的位置。

18.6.3、set_difference

函数原型:set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

  • 求两个集合的差集;
  • 注意:两个集合必须是有序序列;
  • beg1、end1:容器1开始、结束迭代器;beg2、end2:容器2开始、结束迭代器;dest:目标容器开始迭代器(需要提前开辟空间(resize()))。求的是 容器1的差集。
  • 返回值是差集中最后一个元素的位置。

差集:不是交集的部分。

 

posted on 2024-03-15 08:39  啊噢1231  阅读(25)  评论(0编辑  收藏  举报

导航

回到顶部