1、C++初识
1.1、第一个C++程序
编写一个C++程序总共分为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的差集。
- 返回值是差集中最后一个元素的位置。
差集:不是交集的部分。
标签:容器,end,入门,int,cout,C++,include,函数 From: https://www.cnblogs.com/aoe1231/p/18066668