c++知识点与进阶

因为本人在学校只学了一点C,想继续深入学习的C++,这是我根据黑马C++视频一点一点整理的学习笔记,前面几集有省略写,自用复习,应该有不少毛病,欢迎捉虫,后面应该还会完善

黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili

目录

C++

基础知识

程序流程语句

选择结构

循环结构

跳转结构

数组(难点)

概述

一维数组

二维数组

函数

指针

结构体

通讯录管理系统

内存分区模型

引用

函数提高

类和对象

封装

对象的初始化和清理

对象的初始化和清理

友元

运算符重载

继承

多态

文件操作

文本文件

二进制文件

职工管理系统

模板

模板的概念

函数模板

类模板

STL初识

STL的诞生

STL基本概念

STL六大组件

STL中容器、算法、迭代器

容器算法迭代器初识

STL常用容器

String容器

vector容器

deque容器

案例-评委打分

stack容器

queue容器

list容器

排序案例

set/multist容器

map和multimap容器

案例-员工分组

函数对象

函数对象

谓词

内建函数对象

STL算法

常用遍历算法

常用查找算法

常用排序算法

常用拷贝和替换算法

常用算术生成算法

常用集合算法


C++

基础知识

常量

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

C++定义常量的两个方式

  1. #define (#define 常量名 常量值)

    宏变量(通常在文件上方定义,表示一个常量)

  2. const 修饰的变量 constant 数据类型 常量名 = 常量值

    通常在变量定义前加关键字const,修饰该变量为常量,不可修改

浮点型

单精度:float f1 = 3.14f;

双精度:double d1 = 3.14;

默认情况下 输出提高小数,会显示出6位有效数字

科学计数法:

float f2 = 3e2; //3*10^2        
 ​
 float f3 = 3e-2;    //3*0.1^2

字符型

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

a-97 A-65 B-66

char ch = 'a'; //注意只能单引号一个字符,只占1字符

转义符

换行符 \n

反斜杠 \\

 cout    <<  "\\\\\" <<  endl;

水平制表符 \t 作用:可以整齐输出数据

字符串

1.c风格字符串

注意事项 char 字符串名 [],等号后面要用双引号包含字符串

char str[] = "HELLO WORLD";
 cout << str << endl;

2.C++风格字符串

一定要包含#include< string>这个头文件

 string str2 = "HELLO WORLD";
 cout << str2 << endl;

布尔类型

只有真假两种,占1字符,非零即1

 bool flag = false;
 cout << "请给布尔类型flag赋值" << endl;
 cin >> flag;
 cout << "布尔类型flag=" << flag << endl;
 ​
 键盘输入:100    //0
 输出:布尔类型flag=1   //0

算术运算符

 //前置递增,先让变量+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;

 

三目运算符

创建三个变量,将a和b作比较,将变量大的值赋值给变量c

 int a = 10;
 int b = 20;
 int c = 0;
 ​
 //将a和b作比较,将变量大的值赋值给变量c
 c=(a>b ? a:b)
 cout << "c=" << c<< endl;   c=20
 ​
 //在C++中三目运算符返回的是变量,可以继续赋值
 (a<b ? a:b) = 100;
 cout << "a=" << a << endl;  a=100
 cout << "b=" << b << endl;  b=20

if和switch的区别

Switch

缺点,判断时候只能是整型或者字符型,不可以是一个区间

优点,结构清晰,执行效率高

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

程序流程语句

选择结构

if

循环结构

用while写猜数字程序(随机数)

 
#include<iostream>
 using namespace std; 
 //time系统时间头文件包含
 #include <ctime>
 ​
 int main1()
 {   
     //添加随机数种子,作用利用当前系统时间生成随机数,防止每次随机数都一样
     srand((unsigned int)time(NULL));
 ​
     //1、系统生成随机数
     int num = rand() % 100 + 1; //rand() % 100 + 1;生成 0+1~99+1随机数
 ​
     //2、玩家进行猜测
     int val = 0;
     
     //3、判断玩家的猜测
     while (val != num) {    //while (1)
         cin >> val;
         if (val > num) {
             cout << "大了" << endl;
         }
         else if(val < num) {
             cout << "小了" << endl;
         }
         else {
             cout << "猜对了" << endl;
         }
     }
 ​
     system("pause");
         
     return 0;
 }

do..while

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

 
//while语句
     int num1 = 0;
     while (num1 < 10) {
         cout << num1 << endl;
         num1++;
     }
 ​
 //do..while语句
     int num = 0;
     do {
         cout << num << endl;
         num++;
     } while (num < 10);

for循环语句

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

嵌套循环

 
//打印10*10的矩阵
 int main() {
     for (int i = 0; i <= 10; i++) {
         for (int j = 0; j <= 10; j++) {
             cout << "* ";
         }
         cout << endl;
     }
 ​
     return 0;
 }

注意endl表示换行;

乘法口诀程序

 #include<iostream>
 using namespace std;
 ​
 int main() {
     int num;
     for (int i = 1; i < 10; i++) {
         for (int j = 1; j <= i; j++) {
             num = i * j;
             cout  << j << "*" << i << "=" << num << "\t";   
         }
         cout << endl;
     }
     return 0;
 }

注意:列数*行数=计算结果 列数<=当前行数

\t 水平制表符,让输出结果更美观

跳转结构

break语句

作用:用于跳出选择结构和循环结构

break使用时机:

  • 在switch条件语句中,作用是终止case并跳出switch、

  • 在循环语句中,作用是跳出当前的循环语句,最常用

  • 在嵌套循环中,跳出最近的内层循环语句

continue语句

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

goto语句

作用:可以无条件跳转语句,不要乱用,以免造成程序混乱

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

数组(难点)

概述

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

特点1:数组中的每个数据元素都是相同的数据类型

特点2:数组是由连续的内存位置组成的

一维数组

三种定义方式

  1. 数据类型 数组名[数组长度];

  2. 数据类型 数组名[数组长度] = {值1,值2 ... };

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

注意:1.数组下标是从0开始的

2.如果在初始化数据时候,没有全部填写完,会用0来填补剩余数据

3.定义数组时候必须有初始长度

 //利用循环 输出数组中的元素
 int arr[5] = {10,20,30,40,50};
 ​
 for(int i =0; i<5; i++){
     cout << arr[i] << endl;
 }

一维数组组名

一维数组名称的用途:

1、可以统计整个数组在内存中的长度

 sizeof(arr) / sizeof(arr[0])  
 //数组中元素个数=整个数组占用内存空间 / 每个元素占用内存空间

2、可以获取数组在内存中的首地址

 (int)arr    //数组首地址
 (int)&arr[0]    //数组中第一个元素地址为
 (int)&arr[1]    //数组中第二个元素地址为

注意:数组名是常量,不可以进行赋值操作

一维数组应用案例难点

元素逆置

 #include <iostream>
 using namespace std;
 ​
 int main() {
     int arr[5];
     cout << "请输入五个数字" << endl;
     for (int i = 0; i < 5; i++) {
         cin >> arr[i];
     }
 ​
     //实现逆置
     //记录起始下标位置
     //记录结束下标位置
     //起始下标与结束下标的元素互换
     //起始位置++,结束位置--
     //循环执行,直至起始位置>=结束位置
     int start = 0;
     int end = sizeof(arr) / sizeof(arr[0]) - 1;
 ​
     while (start < end) {
         //实现元素互换
         int temp = arr[start];
         arr[start] = arr[end];
         arr[end] = temp;
 ​
         //下表更新
         start++;
         end--;
     }
 ​
     //打印逆置后的数组
     cout << "数组元素逆置后:" << endl;
     for (int i = 0; i < 5; i++) {
         cout << arr[i] << " ";
     }
 ​
     return 0;
 }

注意:本题主要是运用了起始,结尾两个指针

冒泡排序

重点:总共排序轮数为 = 元素个数 - 1

次数 = 元素个数 - 当前轮数 - 1

 
#include <iostream>
 using namespace std;
 ​
 //将数组升序排序
 int main() {
     int arr[9] = { 4,2,8,0,5,7,1,3,9 };
     cout << "排序前" << endl;
     for (int i = 0; i < 9; i++) {
         cout << arr[i] << " ";
     }
     cout << endl;
 ​
     //开始冒泡排序
     //总共排序轮数为 元素个数-1
     for (int i = 0; i < 9 - 1; i++) {
         //内层循环对比    次数=元素个数-当前轮数-1
         for (int j = 0; j < 9 - i - 1; j++) {
             //如果第一个数大于第二个数则交换位置
             if (arr[j] > arr[j + 1]) {
                 int temp = arr[j];
                 arr[j] = arr[j + 1];
                 arr[j + 1] = temp;
             }
         }
     }
 ​
     //排序后结果
     cout << "排序后" << endl;
     for (int i = 0; i < 9; i++) {
         cout << arr[i] << " ";
     }
     cout << endl;
     return 0;
 }

二维数组

二维数组定义方式

  1. 数据类型 数组名[ 行数 ] [ 列数 ];

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

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

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

二维数组数组名

  • 查看二维数组所占内存空间

sizeof(arr)                       //二维数组占用内存空间
   sizeof(arr[0])                    //二维数组第一行占用内存空间
   sizeof(arr[0][0])             // 二维数组第一个元素占用内存空间
   
   sizeof(arr) / sizeof(arr[0])      //二维数组行数
   sizeof(arr[0]) / sizeof(arr[0][0])    //二维数组列数
  • 获取二维数组首地址

 (int)arr    //二维数组首地址
 (int)&arr[0]    //数组中第一行首地址为
 (int)&arr[0][0] //数组中第一个元素元素地址为
 //以上三个地址相同

二维数组应用案例难点

函数

概述

作用:将一段经常使用的代码封装起来,减少重复代码

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

函数的定义

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

1、返回值类型:一个函数可以返回一个值,在函数定义中 int

2、函数名:给函数起个名称 add

3、参数列表:使用该函数时,传入的数据 (int num1 , int num2)

4、函数体语句:花括号内的代码,函数内需要执行的语句 int sum = num1 +num2

5、return表达式:和返回值类型挂钩,函数执行完后,函数相应的数据 return sum;

语法

返回值类型 函数名 (参数列表)

{

函数体语句

return 语句

}

函数的调用

功能:使用定义好的函数

其中a b是实参,num1,num2是形参没有实际的值

 int add(int num1 , int  num2){
     int sum = num1 +num2;
     return  sum;
 }
 int main(){
     int a = 10;
     int b = 20;
     int c = add(a,b);   
     cout << "c=" << c << endl;
 }
 //输出结果c=30

值传递

就是函数调用时实参将数值传入给形参

值传递时,如果形参发生改变,并不会影响到实参

所以两数交换后输出,要在函数体语句中写,而不是主函数中写。

注意:return返回值不需要的时候,可以不写return

函数的常见样式

常见样式有4种:

1、无参无返

2、有参无返

3、无参有返

4、有参有返

 //1、无参无返
 void test01(){
     //void a = 10;      //无类型不可以创建变量,原因无法分配内存
     cout << "test01" << endl;
 }
 ​
 //2、有参无返
 void test02(int a){
     cout << "test02 a=" << a << endl;
 }
 ​
 //3、无参有返
 int test03(){
     cout << "test03" << endl;
     return 1000;
 }
 ​
 //4、有参有返
 int test04(int a){
     cout << "test04 a=" << a << endl;
     return a;
 }
 int main(){
     //1、无参无返函数调用
     test01();
     
     //2、有参无返函数调用
     test02(100);
     
     //3、无参有返函数调用
     int num1 = test03();
     cout << "num1=" << num << endl;
     
     //4、有参有返函数调用
     int num2 = test04(10000);
     cout << "num2=" << num << endl;
 }

函数的声明

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

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

 //声明定义,两数比较选大的
 int max(int a, int b) {
     return a > b ? a : b;
 }
 ​
 int main() {
     int a = 10;
     int b = 20;
 ​
     cout << max(a, b) << endl;
     return 0;
 }

函数的分文件编写

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

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

1、创建后缀名为.h的头文件

2、创建后缀名为.cpp的源文件

3、在头文件中写函数的声明

4、在源文件中写函数的定义

指针

指针的基本概念

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

  • 内存编号是从0开始记录的,一般用十六进制数字表示

  • 可以利用指针变量保存地址

指针变量的定义和使用

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

解引用:指针前加一个*,来找到指针指向的内存

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

//2、使用指针
//可以通过解引用的方式来找到指针指向的内存
//解引用:指针前加一个*,例: *p
*p = 1000; 
cout << "a=" << a << endl;
cout << "*p=" << *p << endl;

指针所占内存内存空间

指针也是一种数据类型,其占用的内存空间:

在32位的操作系统下:占用4个字符,64位下:占用8个字符;

     int a = 10;
     
     //int* p;
     //p = &a;
     int * p = &a;
     
     cout << *p << endl;
     cout << sizeof(p) << endl;              //sizeof(int *)
     cout << sizeof(char *) << endl;
     cout << sizeof(float *) << endl;
     cout << sizeof(double *) << endl;
 ​
     return 0;

空指针

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

用途:初始化指针变量

注意:空指针指向的内存不可访问,0~255的内存编号是系统占用的,因此不可以访问

 //空指针
 int * p = NULL;

野指针

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

在程序中,尽量避免写野指针

 int * p = (int *)0x1100;        //语法没错误,但是报错没有权限访问

const修饰指针

有三种情况:

1、const修饰指针——常量指针:指针的指向可以修改,但是指针指向的值不可以改(常量的指针)

 int a =10;
 int b = 10;
 const int * p = &a;
 ​
 *p=20;                      //错误,指针指向的值不可以改
 p = &b;                     //正确,指针的指向可以修改

2、const修饰常量——指针常量:指针的指向不可以修改,但是指针指向的值可以改(指针的常量)

 int a =10;
 int b = 10;
 int * const p = &a;
 ​
 *p=20;                      //正确,指针指向的值可以改  
 p = &b;                     //错误,指针的指向不可以修改

3、const即修饰指针,又修饰常量:指针的指向,指针指向的值都不可以修改

 int a =10;
 int b = 10;
 const int * const p = &a;
 ​
 *p=20;                      //错误,指针指向的值不可以改 
 p = &b;                     //错误,指针的指向不可以修改

指针和数组

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

 int arr[10] = {1,2,3,4,5,6,7,8,9,10}
 int * p = arr;              //arr就是数组的首地址
 p++;                        //指针向后偏移四个字节
 ​
 int main() {
     int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
     cout << "利用指针遍历数组" << endl;
 ​
     int * p = arr;
     for (int i = 0; i < 10; i++) {
         cout << *p << endl;
         p++;
     }
     return 0;
 }

指针和函数

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

地址传递

 void swap02(int * p1,int * p2) {
     int temp = *p1;
     *p1 = *p2;
     *p2 = temp;
 }
 ​
 int main() {
     int a = 10;
     int b = 20;
     //地址传递
     //如果是地址传递,可以修饰实参
     swap02(&a, &b);
     cout << "a=" << a << endl;          //a=20
     cout << "b=" << b << endl;          //b=10
 ​
     return 0;
 }

指针数组和函数的案例 难点

描述:封装一个函数,利用冒泡排序,实现对整型数组的升序排列

 #include <iostream>
 using namespace std;
 ​
 //冒泡函数排序,参数1    数组的首地址,参数2      数组长度
 void bubbleSort(int * arr,int len) {
     for (int i = 0; i < len - 1; i++) {
         for (int j = 0; j < len - i - 1; j++) {
             //如果j>j+1的值 交换数字,指针偏移
             if (arr[j] > arr[j + 1]) {
                 int temp = arr[j];
                 arr[j] = arr[j + 1];
                 arr[j + 1] = temp;
             }
         }
     }
 }
 ​
 //打印数组
 void printArray(int* arr, int len) {
     for (int i = 0; i < len; i++) {
         cout << arr[i] << endl;
     }
 }
 ​
 int main() {
     //1、先创建数组
     int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
     //数组长度
     int len = sizeof(arr) / sizeof(arr[0]);
 ​
     //2、创建函数,实现冒泡排序
     bubbleSort(arr, len);
 ​
     //3、打印排序后的数组
     printArray(arr, len);
     return 0;
 }

结构体

结构体基本概念

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

结构体定义和使用

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

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

  • struct结构体名 变量名

  • struct结构体名 变量名 = {成员1值 , 成员2值...}

  • 定义结构体时顺便创建变量 //用的少,上两个多

 #include <iostream>
 using namespace std;
 #include <string>
 ​
 //1、创建学生数据类型:学生包括(姓名,年龄,分数)
 //自定义数据类型,一些类型集合组成的一个类型
 struct Student {
     //成员列表
     //姓名
     string name;
     //年龄
     int age;
     //分数
     int score;
 }s3;    //顺便创建结构体变量
 ​
 //2、通过学生类型创建具体学生
 int main() {
     //1、struct Student s1
     //struct可以省略
     Student s1;
     //给s1属性赋值,通过,访问结构体变量中的属性
     s1.name = "张三";
     s1.age = 18;
     s1.score = 100;
     cout << "姓名:" << s1.name << "  年龄:" << s1.age << "  分数:" << s1.score << endl;
 ​
     //2、struct Student s2={...}
     struct Student s2 = { "李四",19,80 };
     cout << "姓名:" << s2.name << "  年龄:" << s2.age << "  分数:" << s2.score << endl;
 ​
     //3、在定义结构体时顺便创建结构体变量struct Student s2={...}
     s3.name = "王五";
     s3.age = 20;
     s3.score = 60;
     cout << "姓名:" << s3.name << "  年龄:" << s3.age << "  分数:" << s3.score << endl;
 ​
     return 0;
 }

结构体数组

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

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

 //2、创建结构体数组
     struct Student stuArray[3] = {
         { "张三", 18, 100 },
         { "李四", 28, 80 },
         { "王五", 38, 66 }
     };
 ​
     //3、给结构体数组中的元素赋值
     stuArray[2].name = "赵六";
     stuArray[2].age = 80;
     stuArray[2].score = 60;
 ​
     //遍历结构体数组
     for (int i = 0; i < 3; i++) {
         cout << "姓名:" << stuArray[i].name 
              << "  年龄:" << stuArray[i].age 
              << "  分数:" << stuArray[i].score << endl;
 ​
     }

结构体指针

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

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

//创建学生结构体变量
	Student s = { "张三", 18, 100 };

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

	//通过指针访问结构体变量中的数据
	cout << "姓名:" << p->name << "  年龄:" << p->age << "  分数:" << p->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;
     return 0;
 }

结构体做函数参数

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

传递方式两种:1、值传递 2、地址传递

总结:如果不想修改主函数中的数据,用值传递,反之用地址传递

 #include <iostream>
 using namespace std;
 #include <string>
 ​
 struct Student {
     string name;
     int age;
     int score;
 };
 ​
 //打印学生信息函数
 //1、值传递
 void printStudent1(Student s) {
     s.age = 100;
     cout << "子函数1中  姓名:" << s.name << "  年龄:" << s.age << "  分数:" << s.score << endl;
 }
 ​
 //2、地址传递
 void printStudent2(Student * p) {
     p->age = 200;
     cout << "子函数1中  姓名:" << p->name << "  年龄:" << p->age << "  分数:" << p->score << endl;
 }
 int main() {
     Student s = { "张三", 18, 100 };
     //printStudent1(s);
     printStudent2(&s);
     cout << "mian函数中  姓名:" << s.name << "  年龄:" << s.age << "  分数:" << s.score << endl;
 ​
 }

结构体中const使用场景

作用:用const来防止误操作

#include <iostream>
using namespace std;
#include <string>

struct Student {
	string name;
	int age;
	int score;
};

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

int main() {
	Student s = { "张三", 18, 100 };
	printStudent(&s);

	cout <<  "main函数中年龄为:" << s.age << endl;
	return 0;
}

结构体案例

描述

学校正在做毕设项目,每名老师带领5个学生,总共3名老师,需求如下:

设计学生和老师的结构体,其中老师的结构体中,有老师姓名和一个存放5名学生的数组作为成员

学生的成员有姓名,考试分数,创建数组存放3名老师,通过每个老师及所带的学生赋值

最终打印出老师数据以及老师索塞的学生的数据\

 #include <iostream>
 using namespace std;
 #include <string>
 #include <ctime>
 ​
 struct student {
     string sName;
     int score;
 };
 struct teacher {
     string tName;
     struct student s[5];
 };
 ​
 //给老师和学生赋值的函数
 void allocateSpace(struct teacher t[], int len) {
     string nameSeed = "ABCDE";
     //给老师赋值
     for (int i = 0; i < len; i++) {
         t[i].tName = "teacher_";
         t[i].tName += nameSeed[i];          //字符串的拼接
 ​
         //通过循环给每名老师所带的学生赋值
         for (int j = 0; j < 5; j++) {
             t[i].s[j].sName = "student_";
             t[i].s[j].sName += nameSeed[j];
 ​
             int random = rand() % 61 + 40;      //0~59+40=0~99 -> 0~60+40=0~100
             t[i].s[j].score = random;
         }
     }
 }
 ​
 void printTeacher(teacher t[],int len) {
     for (int i = 0; i < len; i++) {
         cout << "老师名" << t[i].tName << endl;
         for (int j = 0; j < 5; j++) {
             cout << "\t学生姓名" << t[i].s[j].sName << " 分数" << t[i].s[j].score << endl;
         }
     }
 }
 ​
 int main() {
     //随机数种子,作用利用当前系统时间生成随机数,防止每次随机数都一样
     srand((unsigned int)time(NULL));
 ​
     teacher t[3];
 ​
     //通过函数给三个老师的信息赋值,并给老师带的学生信息赋值
     int len = sizeof(t) / sizeof(t[0]);
     allocateSpace(t, len);
 ​
     //打印
     printTeacher(t,len);
     return 0;
 }

结构体案例2

描述:设计一个英雄的结构体,包括成员姓名,年龄,性别;创建结构体数组,数组中存放5名英雄。

通过冒泡排序的算法,将数组中的英雄按照年龄进行升序排序,最终稿打印排序后的结果

 #include <iostream>
 using namespace std;
 #include <string>
 ​
 struct hero {
     string name;
     int age;
     string sex;
 };
 struct heroS {
     hero h[5];
 };
 ​
 //地址传递
 void bubbleSort(hero h[], int len) {
     for (int i = 0; i < len - 1; i++) {
         for (int j = 0; j < len - i - 1; j++) {
             if (h[j].age > h[j + 1].age) {
                 hero temp = h[j];
                 h[j] = h[j + 1];
                 h[j + 1] = temp;
             }
         }
     }
 }
 ​
 void printHero(hero h[], int len) {
     for (int i = 0; i < len; i++) {
         cout << "姓名:" << h[i].name << " 年龄:" << h[i].age << " 性别:" << h[i].sex << endl;
     }
 }
 int main() {
     hero h[5] = {
         {"刘备",23,"男"},
         {"关羽",22,"男"},
         {"张飞",20,"男"},
         {"赵云",21,"男"},
         {"貂蝉",19,"女"}
     };
 ​
     int len = sizeof(h) / sizeof(h[0]);
     /*for (int i = 0; i < len; i++) {
         cout << "姓名:" << h[i].name << " 年龄:" << h[i].age << " 性别:" << h[i].sex << endl;
     }*/
     bubbleSort(h, len);
 ​
     printHero(h, len);
     return 0;
 }

通讯录管理系统

添加联系人

步骤

  • 设计联系人结构体

  • 设计通信录结构体 (要记录人数最多1k)

  • main函数中创建通讯录

  • 封装添加联系人函数

  • 测试添加联系人功能

显示联系人

步骤

  • 封装显示联系人函数

  • 测试显示联系人功能

删除联系人

描述:按照姓名进行删除联系人

步骤

  • 封装检测联系人是否存在

  • 封装显示联系人函数

  • 测试显示联系人功能

封装检测联系人是否存在

思路:删除联系人之前需要先判断联系人是否存在,如果存在删除没不存在要提示用户没有此联系人,因此我们需要把检测联系人是否存在封装成一个函数中,如果存在,返回联系人在通讯录中的位置,不存在返回-1

封装删除联系人函数

根据用户输入的联系人判断改通讯录中是否有此人

查找到进行删除,并提示删除成功

查不到提升查无此人

清空通讯录

思路:将通讯录所有联系人信息清除掉,只要将通讯录记录的联系人数量变为0,做逻辑上清空。

本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓

内存分区模型

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

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

  • 全局区:存放全局变量和静态变量以及常量

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

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

分区意义:

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

1.1 程序运行前

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

代码区

存放CPU执行的机器指令

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

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

全局区

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

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

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

 #include <iostream>
 using namespace std;
 ​
 //全局变量:不在任何函数声明
 int g_a = 10;
 int g_b = 10;
 ​
 //const修饰的全局变量,全局常量
 const int c_g_a = 10;
 const int c_g_b = 10;
 ​
 int main() {
 ​
     创建普通局部变量 
     int a = 10;
     int b = 10;
     cout << "局部变量a的地址为:" << (int)&a << endl;
     cout << "局部变量b的地址为:" << (int)&b << endl;
 ​
     cout << "全局变量g_a的地址为:" << (int)&g_a << endl;
     cout << "全局变量g_b的地址为:" << (int)&g_b << endl;
 ​
     //静态变量  在普通变量前加static
     static int s_a = 10;
     static int s_b = 10;
     cout << "静态变量s_a的地址为:" << (int)&s_a << endl;
     cout << "静态变量s_b的地址为:" << (int)&s_b << endl;
 ​
     //常量
     //字符串常量
     cout << "字符串常量的地址为:" << (int)&"hello world" << endl;
 ​
     //const修饰的变量
     //const修饰的全局变量
     cout << "全局常量c_g_a的地址为:" << (int)&c_g_a << endl;
     cout << "全局常量c_g_b的地址为:" << (int)&c_g_b << endl;
 ​
     const修饰的局部变量
     int c_l_a = 10;         //c-const   g-global全局  l-local局部
     int c_l_b = 10;         
     cout << "局部常量c_l_a的地址为:" << (int)&c_l_a << endl;
     cout << "局部常量c_l_b的地址为:" << (int)&c_l_b << endl;
 ​
 ​
     return 0;
 }

总结:

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

  • 代码区特点是共享和只读

  • 全局区中存放全局变量,静态变量,常量

  • 常量区中存放const修饰的全局常量和字符串变量

1.2 程序运行后

栈区

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

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

堆区

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

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

 #include <iostream>
 using namespace std;
 ​
 int* func() {
     /*int a = 10;
     return &a;*/
 ​
     //利用new关键字,可以将数据开辟到堆区
     //指针 本质也是局部变量,放在栈上,指针保存的数据是放在堆区
     int * p = new int(10);
     return p;
 }
 ​
 int main() {
 ​
     //在堆区开辟数据
     int *p = func();
     cout << *p << endl;
     cout << *p << endl;
     cout << *p << endl;
 ​
     return 0;
 }

总结:

  • 堆区数据由程序员管理开辟和释放

  • 堆区数据利用new关键字进行开辟内存

1.3new操作符

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

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

语法:new 数据类型

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

 #include <iostream>
 using namespace std;
 ​
 //1、在堆区创建整型数据
 int* func() {   
     //new 返回的是该数据类型的指针
     int * p = new int(10);
     return p;
 }
 ​
 void test01() {
     int * p = func();
     cout << *p << endl;
     cout << *p << endl;
     //堆区数据由程序员管理开辟,释放
     //利用delete释放
     delete p;
     //cout << *p << endl;       //报错,内存已经释放,在访问就是非法操作
 }
 ​
 //2、在堆区利用new开辟数组
 void test02() {
     //创建10个整型数据的数组,在堆区
     int * arr = new int[10];    //数组10个元素
     for (int i = 0; i < 10; i++) {
         arr[i] = i + 100;       //给十个数赋值    100~109
     }
     for (int i = 0; i < 10; i++) {
         cout << arr[i] << endl;     //arr[i]=*(arr+i)
     }
     //释放数组,要加[]不然就会只释放了首地址
     delete[] arr;
 }
 ​
 int main() {
     //test01();
     test02();
     return 0;
 }

引用

作用:给变量起别名

语法数据类型 &别名 = 原名

注意:

  • 引用必须初始化

  • 引用在初始化后,不可以改变

引用做函数参数

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

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

 //3、引用传递
 void mySwap03(int& a, int& b) {     //这里不是取址符,是引用
     int temp = a;
     a = b;
     b = temp;
 }
 ​
 int main6() {
     int a = 10;
     int b = 20;
     mySwap03(a, b);         //引用传递实参改变,(类似指针常量)
     
     cout << "a=" << a << endl;
     cout << "b=" << b << endl;
 }

引用做函数返回值

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

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

用法:函数调用作为左值(可以被引用的对象叫做左值)

 #include <iostream>
 using namespace std;
 ​
 //引用做函数的返回值
 //1、不要返回局部变量的引用
 int & test01() {
     int a = 10;     //  局部变量存放在四区中的 栈区
     return a;
 }
 ​
 //2、函数的调用可以作为左值
 int& test02() {
     static int a = 10;      //  静态变量存放在四区中的 全局区,这上面的数据在程序结束后系统释放
     return a;
 }
 ​
 int main() {
     //int& ref = test01();
     int& ref = test02();    
     cout << "ref=" << ref << endl;      //第一次结果正确,编译器做了保留
     cout << "ref=" << ref << endl;      //第二次结果错误,因为a的内存已经释放
 ​
     test02() = 1000;                    //返回的a的引用被赋值,ref是a的别名
     cout << "ref=" << ref << endl;      //因此如果函数的返回值是引用,这个函数调用可以作为左值
     cout << "ref=" << ref << endl;
     return 0;
 }

引用的本质

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

 //发现是引用,转换为 int * const ref = &a;
 void func(int& ref){
     ref = 100;      //ref是引用,转换为*ref = 100
 }
 int main(){
     int a = 10;
     
     //自动转换为int * const ref = &a;指针常量是指针指向不可改,也说明为什么引用不可更改
     int & ref = a;
     ref = 20;       //内存发现ref是引用,自动帮我们转换为*ref = 20;
     
     cout << "a:" << a << endl;
     cout << "ref:" << ref << endl;
     
     func(a);
     return 0;
 }

常量引用

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

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

 #include <iostream>
 using namespace std;
 ​
 void showValue(const int & val) {
     //val = 1000;               //报错,加上const后变为只读,不可修改
     cout << "val:" << val << endl;
 }
 ​
 int main() {
     //常量引用场景:修饰形参,防止误操作
     //int a = 10;
     //int& ref = a;         //必须引用一块合法内存空间 int & ref = 10 不被允许
     //const int& ref = 10;  //加上const之后,编译器将代码修改 int temp = 10; int& ref = temp;
     //ref = 20;             //报错,加上const后变为只读,不可修改
 ​
     int a = 100;
     showValue(a);
     cout << "a:" << a << endl;
 ​
     return 0;
 }

函数提高

函数默认参数

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

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

 #include <iostream>
 using namespace std;
 ​
 //函数默认参数
 //如果我们自己传入数据,就用自己的数据,如果没有,那么默认值
 //语法:`返回值类型 函数名 (参数 = 默认值){}`
 int func(int a, int b = 20, int c =30) {
     return a + b + c;
 }
 ​
 //注意:
 //1、如果某个位置已经有了默认参数,那么从这个位置往后从左到右都必须有默认值,如下面c也必须要有默认值
 //int func2(int a, int b = 20, int c) {
 //  return a + b + c;
 //}
 ​
 //2、如果函数声明有默认参数,函数实现就不能有默认参数
 //int func2(int a=10, int b=10);
 //int func2(int a=10, int b=10) {
 //  return a + b;
 //}
 ​
 int main() {
     cout << func(10,30) << endl;        //  结果70
     //cout << func2(10,10) << endl;     
     return 0;
 }

函数占位参数

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

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

在现阶段函数占位参数讯在意义不大,但是后面的课程中会用到技术

函数重载

函数重载概述

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

函数重载满足条件:

  • 用一个作用域下

  • 函数名称相同

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

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

 #include <iostream>
 using namespace std;
 ​
 //函数重载
 //函数名可以相同,提高复用性
 ​
 //函数重载满足条件
 //1、同一作用域下
 //2、函数名称相同
 //3、函数参数类型不同或者个数不同或者顺序不同
 void func() {
     cout << "func的调用" << endl;
 }
 void func(int a) {
     cout << "func(int a)的调用" << endl;
 }
 void func(double a) {
     cout << "func(double a)的调用" << endl;
 }
 void func(int a,double b) {
     cout << "func(int a,double b)的调用" << endl;
 }
 void func(double a, int b) {
     cout << "func(double a, int b)的调用" << endl;
 }
 //注意函数的返回值不可以作为函数重载的条件
 //int func(double a, int b) {
 //  cout << "func(double a, int b)的调用" << endl;
 //}
 int main() {
     //func();       //调用第一个
     //func(10);     //调用第二个
     //func(3.14);       //调用第三个
     func(10,3.14);      //调用第四个
     return 0;
 }

函数重载注意事项:

  • 引用作为函数重载条件

  • 函数重载碰到函数默认参数

     #include <iostream>
     using namespace std;
     ​
     //1、引用作为重载的条件
     void func(int& a) {     //int & a = 10;不合法
         cout << "func(int &a)调用" << endl;
     }
     void func(const int& a) {       //const int &a = 10;合法,常量引用
         cout << "func(const int &a)调用" << endl;
     }
     ​
     //2、函数重载碰到默认参数
     void func2(int a,int b ) {          //void func2(int a,int b = 10)出现二义性报错,尽量避免这类错误
         cout << "func2(int a,int b)的调用" << endl;
     }
     void func2(int a) {
         cout << "func2(int a)的调用" << endl;
     }
     int main() {
         //int a = 10;
         //func(a);      //调用第一个因为const只读不写
     ​
         //func(10);     //调用第二个
     ​
         func2(10);  //调用第三个
         return 0;
     }

类和对象

C++面向对象的三大特性:封装、继承、多态

C++认为万事万物皆为对象,对象上有其属性和行为

人可以作为对象,属性有姓名,年龄,身高等等,行为有走,跑,跳,吃饭等等

具有相同性质的对象,我们可以抽象为类。人属于人类,车属于车类

封装

封装的意义

封装是C++面向对象三大特性之一

意义:

  • 将属性和行为作为一个整体,表现生活的事物

  • 将属性和行为加以权限控制

封装的意义一:

在设计类的时候,属性和行为写在一起,表现事物

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

 #include <iostream>
 using namespace std;
 ​
 //设计一个圆类:求圆的周长
 //圆周率
 const double PI = 3.14;
 ​
 //class代表设计一个类,类后面紧跟着的是类名
 class Circle {
     //访问权限
     //公共权限
 public:
 ​
     //属性
     //半径
     int r;
 ​
     //行为
     //获取圆的周长
     double calculateZC() {
         return 2 * PI * r;
     }
 };
 ​
 int mian() {
     //通过圆类 创建具体的圆(对象)
     //实例化   (通过一个类 创建一个对象的过程)
     Circle cl;
     //给圆对象的属性进行赋值
     cl.r = 10;
 ​
     //2 * PI * r = 62.8
     cout << "圆的周长" << cl.calculateZC() << endl;
 ​
     return 0;
 }

封装的意义二

类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有三种:

1.public 公共权限 成员 类内可以访问 类外可以访问

2.protected 保护权限 成员 类内可以访问 类外不可以访问 子类也可以访问父类中保护的内容

3.private 私有权限 成员 类内可以访问 类外不可以访问 子类不可以访问父类中私有的内容

 class Person{
 public:
     string name;
 protected:
     string car;
 private:
     int password;
 private:
     void func(){
         name = "张三";
         car = "奔驰";
         password = "123";
     }
 }
 ​
 int main(){
     //实例化具体对象
     Person p1;
     
     p1.name = "李四";
     p1.car = "奔驰";      //报错,保护权限内容,在类外访问不到
     p1.password = "123"  //报错,私有权限内容,在类外访问不到
     
     p1.func();
     return 0;
 }

struct和class区别

在C++中struct和class都能创建类,唯一区别就在于:默认的访问权限不同

区别:

  • struct默认权限为公共

  • class 默认权限为私有

成员属性设置为私有

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

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

对象的初始化和清理

  • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全

  • C++中面向对象来源于生活,每个对象也都会有初始化设置以及对象销毁前的清理数据的设置

构造函数和析构函数

对象的初始化和清理也是两个非常重要的安全问题

一个对象或者变量没有初始状态,对其使用后果是未知

同样的使用玩一个对象或变量,没有及时清理,也会造成一定的安全问题

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

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

编译器提供的构造函数和析构函数是空实现

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

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

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

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

2.函数名称与类名相同

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

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

析构函数~类名(){}

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

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

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

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

 #include <iostream>
 using namespace std;
 ​
 //对象的初始化和清理
 ​
 class Person1 {
 public:
     //1、构造函数 进行初始化操作
     //没有返回值也不写void
     //函数名称与类名相同
     //构造函数可以有参数,因此可以发生重载
     //程序在销毁对象时候自动调用构造,无需手动调用,而且只会调用一次
     Person1() {
         cout << "Person构造函数的调用" << endl;
     }
 ​
     //2.析构函数 进行清理操作
     //没有返回值也不写void
     //函数名称与类名相同 前面加符号~
     //构造函数不可以有参数,因此不可以发生重载
     //程序在销毁对象时候自动调用析构,无需手动调用,而且只会调用一次
     ~Person1() {
         cout << "Person析构函数的调用" << endl;
     }
 };
 ​
 //构造析构都是必须有的实现,如果我们自己不提供,编译器会提供一下空实现的构造和析构
 void test01() {
     Person1 p;      //局部变量,创建在栈上,test01执行完后就会释放这个对象,析构在销毁前自动调用
 }
 ​
 int main() {
     //test01();
     Person1 p;      //在main函数里不会被释放,按下pause任意键后会被释放,但是同时窗口关闭可能看不到
 ​
     system("pause");
     return 0;
 }

构造函数的分类和调用

两种分类方式:

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

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

三种调用方式:

括号法,显示法,隐式转换法

 #include <iostream>
 using namespace std;
 ​
 //分类
 //  按照参数分类:无参构造(默认构造) 有参构造
 //  按照类型分类:普通构造 拷贝构造
 class Person2 {
 public:
     //构造函数
     Person2() {
         cout << "Person无参构造函数的调用" << endl;
     }
     Person2(int a) {
         age = a;
         cout << "Person有参构造函数的调用" << endl;
     }
 ​
     //拷贝构造函数
     Person2(const Person2 &p) {     //拷贝要const防止误改,并且要引用方式传入
         //将传入的人身上所有属性,拷贝到我身上
         age = p.age;
         cout << "Person拷贝构造函数的调用" << endl;
     }
 ​
     ~Person2() {
         cout << "Person析构函数的调用" << endl;
     }
 ​
     int age;
 };
 ​
 //调用
 void test02() {
     //1、括号法
     //Person2 p1;   //默认构造函数调用方法
     //Person2 p2(10);   //调用有参构造函数
     //Person2 p3(p2);   //调用拷贝构造函数
 ​
     //!!注意:调用默认函数时候不要用小括号
     //因为下面这个函数,编译器会认为是一个函数的声明,返回类型是Person,函数名p1,不会认为在创建对象
     //Person p1();
 ​
     //2、显示法
     Person2 p1;
     Person2 p2 = Person2(10);   //有参构造
     Person2 p3 = Person2(p2);   //拷贝构造
     //Person2(10);      //匿名对象有参构造,当前行执行后,系统会立即回收掉匿名对象
     //cout << "aaa" << endl;        //会在析构函数调用后打印,说明是在匿名对象销毁掉后在打印
     
     //!!注意:不要利用拷贝构造函数初始化匿名对象
     //Person2(p3);      //报错重定义,编译器会认为是对象声明等价于 Person2 p3;是在定义p3对象,与上面重名
 ​
     //3、隐式转换法
     Person2 p4 = 10;        //相当于,写了 Person p4 = Person(10);是有参构造的调用
     Person2 p5 = p4;            //拷贝构造
 }
 ​
 int main() {
     test02();
     system("pause");
     return 0;
 }

构造函数调用时机

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

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

  • 值传递的方式给函数参数传值

  • 以值方式返回局部对象

 #include <iostream>
 using namespace std;
 ​
 class Person3 {
 public:
     Person3() {
         cout << "Person默认构造函数调用" << endl;
     }
     Person3(int age) {
         cout << "Person有参构造函数的调用" << endl;
         m_Age = age;
     }
 ​
     Person3(const Person3& p) {
         cout << "Person拷贝构造函数的调用" << endl;
         m_Age = p.m_Age;
     }
 ​
     ~Person3() {
         cout << "Person析构函数调用" << endl;
     }
 ​
     int m_Age;
 };
 ​
 //1、使用一个已经创建完毕的对象来初始化一个新对象
 void test03() {
     Person3 p1(20);
     Person3 p2(p1);
 ​
     cout << "p2的年龄为:" << p2.m_Age << endl;
 }
 ​
 //2、值传递的方式给函数参数传值
 //值传递相当于 Person3 p = p      拷贝构造函数的隐式写法
 void doWork(Person3 p) {
     p.m_Age = 1000;         //值传递不会改变实参数
 }
 void test04() {
     Person3 p = 20;     //有参构造函数
     doWork(p);          //这里p不等于doWork(Person3 p)中的p,是拷贝Person3 p = 20;的p
     cout << p.m_Age << endl;    //打印为20
 }
 ​
 //3、值方式返回局部对象
 Person3 doWork2() {
     Person3 p1;         //默认构造函数,局部变量,会被释放
     cout << (int*) & p1 << endl;
     return p1;          //这里返回的p1不是上一行的p1,而是克隆体
 }
 void test05() {
     Person3 p = doWork2();
     cout << (int*)&p << endl;       //p地址转换成一个整形的指针,就是&p
 }
 ​
 int main() {
     //test03();
     //test04();
     test05();
     system("pause");
     return 0;
 }

构造函数调用规则

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

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

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

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

构造函数调用规则如下:

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

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

     #include <iostream>
     using namespace std;
     ​
     //1.默认构造函数(无参,函数体为空)
     //2.默认析构函数(无参,函数体为空)
     //3.默认拷贝构造函数,对属性进行值拷贝
     ​
     class Person4 {
     public:
         /*Person4() {
             cout << "默认构造函数调用" << endl;
         }*/
         /*Person4(int age) {
             cout << "有参构造函数调用" << endl;
             m_Age = age;
         }*/
         Person4(const Person4& p) {
             cout << "拷贝构造函数调用" << endl;
             m_Age = p.m_Age;
         }
         ~Person4() {
             cout << "析构构造函数调用" << endl;
         }
     ​
         int m_Age;
     };
     ​
     //void test4() {
     //  Person4 p;
     //  p.m_Age = 18;           //虽然没有了拷贝构造函数,但是系统会自动写m_Age = p.m_Age;并隐藏
     //
     //  Person4 p2(p);
     //  cout << "p2年龄:" << p2.m_Age << endl;
     //}
     ​
     //将默认构造注释,如果我们写了有参构造函数,编译器就不再提供给默认构造,但是依然构造拷贝构造
     //void test5() {
     //  Person4 p(28);
     //
     //  Person4 p2(p);
     //  cout << "p2年龄:" << p2.m_Age << endl;
     //}
     ​
     //如果我们写了拷贝构造函数,编译器就不再提供给其他构造
     void test6() {
         //Person4 p;        //报错,当只有拷贝构造函数时,其他构造函数都不会自动写
     }
     ​
     int main() {
         //test4();
         //test5();
         test6();
         return 0;
     }

对象的初始化和清理

深拷贝和浅拷贝

深浅拷贝时面试经典问题,常见第一个坑

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

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

 #include <iostream>
 using namespace std;
 ​
 class Person {
 public:
     Person() {
         cout << "默认构造函数调用" << endl;
     }
     Person(int age,int height) {
         m_Age = age;
         m_Height = new int(height);     //new将数据开辟到堆区
         cout << "有参构造函数调用" << endl;
     }
 ​
     //自己实现一个拷贝构造函数。来解决浅拷贝问题
     Person(const Person& p) {
         cout << "拷贝构造函数调用" << endl;
         m_Age = p.m_Age;
         //m_Height = p.m_Height编译器默认实现就是这行代码
         //深拷贝操作
 ​
         m_Height = new int(*p.m_Height);        //重新开辟数据堆区
     }
 ​
     ~Person() {
         //p1,p2都会调用析构函数,其中堆区的数据在第一次析构函数调用时就被释放,第二次调用析构函数再去删去m_Heght的堆区是没有数据的从所以报错
         // 浅拷贝的问题需要深拷贝来解决
         //析构代码,会将堆区开辟数据做释放操作
         if (m_Height != NULL) {
             delete m_Height;
             m_Height = NULL;        //防止野指针出现
         }
         cout << "析构函数调用" << endl;
     }
     int m_Age;
     int * m_Height;
 };
 ​
 void test01() {
     Person p1(18,160);
     cout << "p1年龄为:" << p1.m_Age << "  身高为:" << *p1.m_Height << endl;
 ​
     Person p2(p1);
     cout << "p2年龄为:" << p1.m_Age << "  身高为:" << *p2.m_Height << endl;
 }
 ​
 int main() {
     test01();
     return 0;
 }

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

初始化列表

作用:用来初始化属性

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

 //初始化列表初始化属性
     Person2(int a,int b,int c) :m_A(a), m_B(b), m_C(c) {
     }
 ​
 void test(){
     Person2 p(20,10,30);
 }

类对象作为类成员

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

 class A{}
 class B{
     A a;
 }

B类中有对象A作为成员,A为对象成员

那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?

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

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

析构顺序与构造相反

静态成员

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

静态成员分为:

  • 静态成员变量

    所有对象共享同一份数据

    在编译阶段分配内存

    类内声明,类外初始化

  • 静态成员函数

    所有对象共享同一个函数

    静态成员函数只能访问静态成员变量

 #include <iostream>
 using namespace std;
 ​
 ​
 class Person3 {
 public:
     //1.所有对象都共享同一份数据
     //2.在编译阶段分配内存
     //3.类内声明
     static int m_A;
 ​
     //静态成员变量也是有访问权限的
 private:
     static int m_B;
 };
 ​
 //4.类外初始化
 int Person3 :: m_A = 100;
 int Person3 :: m_B = 200;
 ​
 void test3() {
     Person3 p;
     cout << p.m_A << endl;  //100
 ​
     Person3 p2;
     p2.m_A = 200;
     cout << p.m_A << endl;  //200因为共享
 }
 ​
 void test32() {
     //静态成员变量 不属于某个对象上,所有对象都共享同一份数据
     //因此静态成员变量有两种访问方式
 ​
     //1.通过对象进行访问
     /*Person3 p;
     cout << p.m_A << endl;*/
 ​
     //2.通过类名进行访问
     cout << Person3::m_A << endl;
     //cout << Person3::m_B << endl;     //报错,因为是私有出了作用域类外是访问不到的
 }
 ​
 int main() {
     //test3();
     test32();
     return 0;
 }

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

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

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

面试:空对象占用内存空间为:1

 #include <iostream>
 using namespace std;
 ​
 class Person1{
     int m_A;    //非静态成员变量
     static int m_B; //  静态成员变量,不属于类的对象上
     void func(){}   //非静态成员函数,不属于类的对象上
     static void func2() {}; //  静态成员函数,不属于类的对象上
 };
 ​
 int Person1::m_B = 0;
 ​
 void test11() {
     Person1 p;
 ​
     //空对象占用内存空间为:1
     //因为C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
     //每个空对象也应该有个独一无二的内存地址
     cout << "size of p=" << sizeof(p) << endl;
 }
 void test12() {
     Person1 p;
     //
     cout << "size of p=" << sizeof(p) << endl;
 ​
 }
 int main() {
     //test11();
     test12();
     return 0;
 }

this指针的用途

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

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

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

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

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

用途:

  • 当形参和实参同名时可以使用this来区分

  • 在类的非静态成员函数中返回对象本身,可返回return *this

 #include <iostream>
 using namespace std;
 ​
 class Person2 {
 public:
     Person2(int age) {
         //this指向 被调用的成员函数 所属的对象,指向p1
         this->age = age;
     }
      Person2& PersonAddAge(Person2& p) {        //用引用的方式返回  //如果没有&是以值的方式返回,会拷贝构造一个新的数据而不是p2本体
         this->age += p.age;
 ​
         //this指向p2指针,而*this指向的就是p2这个对象本体
         return *this;
     }
 ​
     int age;    //如果没有this指针,成员变量与形参名称冲突
 };
 //1.解决名称冲突
 void test01() {
     Person2 p1(18);
     cout << "p1的年龄为" << p1.age << endl;
 }
 ​
 //2.返回对象本身
 void test22() {
     Person2 p1(10);
     Person2 p2(10);
 ​
     //链式编程思想
     p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);  //因为p2.PersonAddAge(p1)返回的就是p2本体,实现了(10+10)+10+10叠加
     cout << "p2的年龄为" << p2.age << endl;
 ​
 }
 int main() {
     //test01();
     test22();
     return 0;
 }

空指针访问成员函数

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

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

 #include <iostream>
 using namespace std;
 ​
 class Person3 {
 public:
     void showClassName() {
         cout << "this is Person class" << endl;
     }
     void showPersonAge() {
         if (this == NULL) {
             return;         //如果为空直接扔掉
         }
         cout << "age=" << m_Age << endl;    //都会默认加上this->m_Age,因为传入的this指针为空
     }
     int m_Age;
 };
 ​
 void test31() {
     Person3* p = NULL;
     p->showClassName();     //可以运行
     p->showPersonAge();
 }
 ​
 int main() {
     test31();
     return 0;
 }

const修饰成员函数

常函数

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

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

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

常对象

  • 声明对象前加const称改对象为常对象

  • 常对象只能调用常函数

 #include <iostream>
 using namespace std;
 ​
 class Person4 {
 public:
 ​
     //this指针的本质 是指针常量 指针的指向是不可以修改的
     //const Person4 * const this
     //在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以改
     void showPerson() const{
         this->m_B = 100;
         //this->m_A = 100;  加了const指向的值也不能改了
         //this = NULL;  //指针的指向是不可以修改的
     }
     void func() {   }
     int m_A;
     mutable int m_B;    //特殊变量,即使在常函数中,也可以改变这个值,在前面加上mutable
 };
 ​
 void test41() {
     Person4 p;
     p.showPerson();
 }
 ​
 ​
 //常对象
 void test42() {
     const Person4 p;    //在对象前面加const,变为常对象
     //p.m_A = 100;  报错
     p.m_B = 100;    //m_B是特殊值,在常对象下也可以修改
 ​
     //常对象只能调用常函数
     p.showPerson();
     //p.func();     报错,如果这个能调用,因为没有普通成员函数可以修改成员变量,就侧面的说明可以在func函数中更改变量,这是不允许的
 }
 ​
 int main() {
     test41();
     return 0;
 }

友元

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

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

友元关键字:friend

三种实现:

  • 全局函数做友元

  • 类做友元

  • 成员函数做友元

全局函数做友元

 #include <iostream>
 using namespace std;
 ​
 ​
 //建筑物类
 class Building {
     //goodBoy全局函数是Building好盆友,可以访问私有属性
 friend void goodBoy(Building* building);
 ​
 public:
     Building() {
         m_SittingRoom = "客厅";
         m_BedRoom = "卧室";
     }
 public:
     string m_SittingRoom;
 ​
 private:
     string m_BedRoom;
 };
 ​
 //全局函数
 void goodBoy(Building* building) {
     cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl;
     cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl;     //报错,私有属性在类外不能访问
 }
 ​
 void test51() {
     Building building;
     goodBoy(&building);
 }
 ​
 int main() {
     test51();
     return 0;
 }

友元类

 #include <iostream>
 using namespace std;
 ​
 class Building5;
 class GoodGay {
 public:
     GoodGay();
     void visit();       //参观函数 访问building中的属性
     Building5 * building5;
 ​
 };
 ​
 class Building5 {
     //goodBoy全局函数是Building好盆友,可以访问私有属性
     friend class GoodGay;
 public:
     Building5();
 public:
     string m_SittingRoom;
 private:
     string m_BedRoom;
 };
 ​
 //类外写成员函数
 Building5::Building5() {
     m_SittingRoom = "客厅";
     m_BedRoom = "卧室";
 };
 ​
 GoodGay::GoodGay() {
     //创建一个建筑物对象
     building5 = new Building5();        //building是指针在GoodGay完成后,会被释放,这里开辟堆区存放数据,new什么数据类型,返回什么数据类型
 }
 ​
 void GoodGay::visit() {
     cout << "好基友正在访问:" << building5->m_SittingRoom << endl;
     cout << "好基友正在访问:" << building5->m_BedRoom << endl;
 }
 ​
 void test61() {
     GoodGay gg;
     gg.visit();
 }
 ​
 int main() {
     test61();
     return 0;
 }

成员函数做友元

 #include <iostream>
 using namespace std;
 ​
 class Building7;
 class GoodGay7 {
 public:
     GoodGay7();
     void visit71();     //让visit71函数可以访问Building中的私有成员
     void visit72();     //让visit72函数不可以访问Building中的私有成员
 ​
     Building7* building7;
 };
 ​
 class Building7 {
     //告诉GoodGay作为本类的好朋友,可以访问私有成员
     friend void GoodGay7::visit71();
 public:
     Building7();
 public:
     string m_SittingRoom;
 private:
     string m_BedRoom;
 };
 ​
 //类外实现成员函数
 GoodGay7::GoodGay7() {
     building7 = new Building7();
 }
 ​
 void GoodGay7::visit71() {
     cout << "visit函数正在访问" << building7->m_SittingRoom << endl;
     cout << "visit函数正在访问" << building7->m_BedRoom << endl;
 }
 ​
 void GoodGay7::visit72() {
     cout << "visit函数正在访问" << building7->m_SittingRoom << endl;
     //cout << "visit函数正在访问" << building7->m_BedRoom << endl;
 ​
 }
 ​
 Building7::Building7() {
     m_SittingRoom = "客厅";
     m_BedRoom = "卧室";
 }
 ​
 void test7() {
     GoodGay7 gg;
     gg.visit71();
     gg.visit72();
 }
 ​
 int main() {
     test7();
     return 0;
 }

运算符重载

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

加号运算符重载

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

 #include <iostream>
 using namespace std;
 ​
 //加号运算符重载
 ​
 class Person {
 public:
     1、成员函数重载加号  
     //Person operator+(Person& p) {
     //  Person temp;
     //  temp.m_A = this->m_A + p.m_A;
     //  temp.m_B = this->m_B + p.m_B;
     //  return temp;
     //}
 ​
     int m_A;
     int m_B;
 };
 ​
     //2、全局函数重载加号
 Person operator+(Person& p1, Person& p2) {
     Person temp;
     temp.m_A = p1.m_A + p2.m_A;
     temp.m_B = p1.m_B + p2.m_B;
     return temp;
     }
 ​
     //函数重载版本
 Person operator+(Person& p1, int num) {
     Person temp;
     temp.m_A = p1.m_A + num;
     temp.m_B = p1.m_B + num;
     return temp;
 }
 void test11() { 
     Person p1;
     p1.m_A = 10;
     p1.m_B = 10;
     Person p2;
     p2.m_A = 10;
     p2.m_B = 10;
 ​
     // 成员函数重载本质:Person p3=p1.operator+(p2)
     //全局函数重载本质:Person p3=operator(p1,p2)
 ​
     Person p3= p1 + p2;     //报错
 ​
     //运算符重载也可以发生函数重载
     Person p4 = p1 +100;    //是person类型和int类型相加
 ​
     cout << "p3.m_A=" << p3.m_A << endl;
     cout << "p3.m_B=" << p3.m_B << endl;
 ​
     cout << "p4.m_A=" << p4.m_A << endl;
     cout << "p4.m_B=" << p4.m_B << endl;
 }
 ​
 int main() {
     test11();
     return 0;
 }

总结

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

2.不要滥用运算符重载(名加法写减法)

左移运算符重载

 #include <iostream>
 using namespace std;
 ​
 class Person2 {
     friend ostream& operator<<(ostream& cout, Person2& p);
 ​
 public:
     Person2(int a, int b) {
         m_A = a;
         m_B = b;
     }
 private:
     //利用成员函数重载左移运算符     p.operator<<(cout)  简化版本p<<cout
     //因此不会利用成员函数重载左移运算符,因为无法实现cout在左侧
     /*void operator<< (cout) {  
 ​
     }*/
     int m_A;
     int m_B;
 };
 ​
 //只能利用全局函数重载左移运算符       //本质 operator<<(cout ,p)    简化版本cout << p
 ostream& operator<<(ostream &cout,Person2 &p) {     //ostream输出流且全局只能有一个,用引用的方法
     cout << "m_A=" << p.m_A << "  m_B=" << p.m_B << endl;
     return cout;
 }       
 ​
 void test2() {
     Person2 p(10,10);
     /*p.m_A = 10;
     p.m_B = 10;*/
 ​
     cout << p << " hello world" << endl;
 }
 ​
 int main() {
     test2();
     return 0;
 }

递增运算符重载

作用:实现自己的整型数据

 #include <iostream>
 using namespace std;
 ​
 //重载递增运算符
 ​
 //自定义整型
 class MyInteger {
     friend  ostream& operator<<(ostream & cout, MyInteger myint);
 public:
     MyInteger() {
         m_Num = 0;
     }
     //重载前置++运算符,返回引用是为了一直对一个数据进行递增
     MyInteger& operator++() {
         m_Num++;
 ​
         //将自身返回
         return *this;
     }
 ​
     //重载后置++运算符
     //int代表的是占位参数,用于区分前置和后置递增,并且只认int,double等不认
     //局部变量temp执行完会被释放,不能返回接着引用,是非法的,也可以用前面的开辟堆区返回*temp的方式
     MyInteger operator++(int) {
         //先记录结果
         MyInteger temp = *this;
         //后递增
         m_Num++;
         //最后将记录的结果返回
         return temp;
     }
 private:
     int m_Num;
 };
 ​
 //重载运算符<<
 ostream& operator<<(ostream & cout, MyInteger myint) {
     cout << myint.m_Num;
     return cout;
 }
 ​
 void test3() {
     MyInteger myint;
     
     cout << ++(++myint) << endl;        //前置重载时如果不用引用,结果是2,1,第一次返回的是新的变量
     cout << myint << endl;
 }
 ​
 void test32() {
     MyInteger myint;
     cout << myint++ << endl;
     cout << myint << endl;
 ​
 }
 ​
 int main() {
     //test3();
     test32();
     int a = 0;
     cout << ++(++a) << endl;
     cout << a << endl;
     return 0;
 }

赋值运算符重载

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

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

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

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

  4. 赋值运算符operator,做赋值操作时也会出现深浅拷贝问题

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

 #include <iostream>
 using namespace std;
 ​
 class Person4 {
 public:
     Person4(int age) {
         m_Age = new int(age);
     }
     ~Person4() {
         if (m_Age != NULL) {
             delete m_Age;
             m_Age = NULL;           //堆区内存重复释放,浅拷贝崩溃
         }
     }
 ​
     //重载 赋值运算符
     Person4& operator=(Person4& p) {
         //编译器是提供浅拷贝
         //m_Age = p.m_Age;
 ​
         //应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
         if (m_Age != NULL) {
             delete m_Age;
             m_Age = NULL;
         }
 ​
         //深拷贝
         m_Age = new int(*p.m_Age);
 ​
         //返回对象本身
         return *this;
     }
 ​
     int *m_Age;
 };
 ​
 void test4() {
     Person4 p1(18);
     Person4 p2(20);
     Person4 p3(30);
 ​
     p3 = p2 = p1;
 ​
     cout << "p1的年龄为:" << *p1.m_Age << endl;
     cout << "p2的年龄为:" << *p2.m_Age << endl;
     cout << "p3的年龄为:" << *p3.m_Age << endl;
 }
 ​
 int main() {
     test4();
     return 0;
 }

关系运算符重载

作用:可以两个自定义类型对象进行对比操作

 #include <iostream>
 using namespace std;
 ​
 class Person5 {
 public:
     Person5(string name, int age) {
         m_Age = age;
         m_Name = name;
     }
 ​
     //重载==号
     bool operator==(Person5& p) {
         if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
             return true;
         }
         else
         return false;
     }
     bool operator!=(Person5& p) {
         if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
             return false;
         }
         else
             return true;
     }
     string m_Name;
     int m_Age;
 };
 ​
 void test5() {
     Person5 p1("Tom", 18);
     Person5 p2("Tom", 18);
 ​
     if (p1 == p2) {
         cout << "p1和p2相等" << endl;
     }
     else {
         cout << "p1和p2不相等" << endl;
     }
     if (p1 != p2) {
         cout << "p1和p2不相等" << endl;
     }
     else {
         cout << "p1和p2相等" << endl;
     }
 }
 ​
 int main() {
     test5();
 ​
     return 0;
 }

函数调用运算符重载

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

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

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

 #include <iostream>
 using namespace std;
 ​
 //打印输出类
 class MyPrint {
 public:
     //重载函数调用运算符
     void operator()(string test) {
         cout << test << endl;
     }
 };
 ​
 void MyPrint02(string test) {
     cout << test << endl;
 }
 ​
 //仿函数非常灵活,没有固定写法,加法类
 class MyAdd {
 public:
     int operator()(int num1, int num2) {
         return num1 + num2;
     }
 };
 ​
 void test6() {
     MyPrint myPrint;
     myPrint("hello world");         //由于使用起来非常像函数,因此也被称为仿函数
     MyPrint02("hello world");
 }
 ​
 void test62() {
     MyAdd myadd;
     int ret = myadd(100, 100);
     cout << "ret=" << ret << endl;
 ​
     //匿名函数对象,第二个括号是使用重载
     cout << MyAdd()(100, 100) << endl;
 }
 ​
 int main() {
     //test6();
     test62();
     return 0;
 }

继承

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

定义类时,下级别成员除了拥有上级别的共性,也有自己的特性(如:布偶猫,加菲猫)

这个时候我们就可以考虑利用继承的技术,减少重复代码

继承的基本语法

 #include <iostream>
 using namespace std;
 ​
 //普通实现页面
 ​
 //!!继承好处:减少重复代码
 //!语法:class子类 :继承方式 父类
 //! 子类也成为派生类
 //! 父类也成为基类
 ​
 //简称实现页面
 //公共页面
 class BasePage {
 public:
     void header() {
         cout << "首页、公开课、登录、注册...(公共头部)" << endl;
     }
     void footer() {
         cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
     }
     void left() {
         cout << "Java、Python、C++/...(公共分类列表)" << endl;
     }
 };
 //Java页面
 class Java :public BasePage {
 public:
     void content() {
         cout << "Java学科视频" << endl;
     }
 };
 ​
 class Python :public BasePage {
 public:
     void content() {
         cout << "Python学科视频" << endl;
     }
 };
 ​
 class CPP :public BasePage {
 public:
     void content() {
         cout << "C++学科视频" << endl;
     }
 };
 ​
 void test1() {
     cout << "Java下载视频页面如下:" << endl;
     Java ja;
     ja.header();    
     ja.content();
     ja.left();
     ja.footer();
     cout << "-----------------------------" << endl;
 ​
     cout << "Python下载视频页面如下:" << endl;
     Python py;
     py.header();
     py.content();
     py.left();
     py.footer();
     cout << "-----------------------------" << endl;
 ​
     cout << "C++下载视频页面如下:" << endl;
     CPP cpp;
     cpp.header();
     cpp.content();
     cpp.left();
     cpp.footer();
 }
 ​
 int main() {
     test1();
     return 0;
 }

继承方式

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

三种方式:1、公共继承public 2、保护继承protected 3、私有继承private

子类不论那种继承方式,都不能访问父类私有权限成员,子类权限收缩

例如:当子类以保护方式继承父类,则父类公共,保护成员,到子类都变为保护成员

当子类以私有方式继承父类,则父类公共,保护成员,到子类都变为私有成员

但是都不能访问到父类私有权限成员

继承的对象模型

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

 #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;
 };
 ​
 //利用开发人员命令提示工具查看对象模型
 //跳转盘符  E:
 //跳转文件路径    cd 具体路径下
 //查看命名
 //cl /d1 reportSingleClassLayoutSon类名
 ​
 void test2() {
     //父类中所有非静态成员属性都会被子类继承下去
     //父类中私有成员属性,是被编译器隐藏了,因此是访问不到,但是确实被继承下去了
     cout << "size of Son =" << sizeof(Son) << endl;
 }
 ​
 int main() {
     test2();
     return 0;
 }

继承中构造和析构顺序

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

问题:父类和子类的构造和析构顺序谁先谁后?

先构造父类,再构造子类,析构的顺序与构造相反

 #include <iostream>
 using namespace std;
 ​
 class Base {
 public:
     Base() {
         cout << "Base构造函数!" << endl;
     }
     ~Base() {
         cout << "Base析构函数!" << endl;
     }
     
 };
 ​
 class Son :public Base{
 public:
     Son() {
         cout << "Son构造函数!" << endl;
     }
     ~Son() {
         cout << "Son析构函数!" << endl;
     }
 };
 ​
 void test3() {
     //Base b;
 ​
     //继承中的构造和析构顺序如下:
     //先构造父类,再构造子类,析构的顺序与构造相反
     Son s;
 }
 ​
 int main() {
     test3();
     return 0;
 }

继承同名成员处理方式

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

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

  • 访问父类同名成员,需要加作用域

 #include <iostream>
 using namespace std;
 class Base4{
 public:
     Base4() {
         m_A = 100;
     }
     void func() {
         cout << "Base - func()调用" << endl;
     }
     void func(int a) {
         cout << "Base - func(int a)调用" << endl;
     }
     int m_A;
 };
 ​
 class Son4 :public Base4{
 public:
     Son4() {
         m_A = 200;
     }
     void func() {
         cout << "Son - func()调用" << endl;
     }
 ​
     int m_A;
 };
 ​
 void test4() {
     Son4 s;
     cout << "Son下的m_A = " << s.m_A << endl;
     //如果通过子类对象 访问到父类中同名成员,需要加作用域
     cout << "Base下的m_A = " << s.Base4::m_A << endl;
 }
 ​
 //同名成员函数处理
 void test42() {
     Son4 s;
     s.func();       //直接调用 调用的是子类中的同名成员
     
     //如何调用父类中的同名成员函数
     s.Base4::func();
 ​
     //如果子类出现了和父类同名成员函数,子类的同名成员函数会隐藏掉父类中所有同名成员函数
     //如果想要访问父类中被隐藏的同名成员函数,要加作用域
     s.Base4::func(100);
 }
 ​
 int main() {
     //test4();
     test42();
     return 0;
 }

总结:

  1. 子类对象可以直接访问到子类同名成员

  2. 子类对象加作用域可以访问到父类同名成员

  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

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

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

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

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

  • 访问父类同名成员,需要加作用域

   #include <iostream>
   using namespace std;
   
   class Base5 {
   public:
     static int m_A;
     static void func() {
         cout << "Base - func()调用" << endl;
     }
     static void func(int a) {
         cout << "Base - func(int a)调用" << endl;
     }
   };
   int Base5::m_A = 100;
   
   class Son5 :public Base5 {
   public:
     static int m_A;
     static void func() {
         cout << "Son- func()调用" << endl;
     }
   };
   int Son5::m_A = 200;
   
   void test5() {
     Son5 s;
   
     //通过对象访问
     cout << "通过对象访问" << endl;
     cout << "Son下的m_A = " << s.m_A << endl;
     cout << "Base下的m_A = " << s.Base5::m_A << endl;
   
     //通过类名访问
     cout << "通过类名访问" << endl;
     cout << "Son下的m_A = " << Son5::m_A << endl;
     //第一个::代表通过类名方式访问,第二个::代表访问父类作用域下
     cout << "Base下的m_A = " << Son5::Base5::m_A << endl;
   
   }
   
   //同名成员函数处理
   void test52() {
     Son5 s;
     //通过对象访问
     cout << "通过对象访问" << endl;
     s.func();   
     s.Base5::func();
   
     //通过类名访问
     cout << "通过类名访问" << endl;
     Son5::func();
     Son5::Base5::func();
   
     //如果子类和父类拥有同名成员函数,子类的同名成员函数会隐藏掉父类中所有同名成员函数
     Son5::Base5::func(100);
   }
   
   int main() {
     //test5();
     test52();
     return 0;
   }

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

多继承语法

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

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

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

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

 #include <iostream>
 using namespace std;
 ​
 class Base6 {
 public:
     Base6() {
         m_A = 100;
     }
     int m_A;
 };
 ​
 class Base62 {
 public:
     Base62() {
         m_A = 200;
     }
     int m_A;
 };
 ​
 class Son6 : public Base6, public Base62 {
 public:
     Son6() {
         m_C = 300;
         m_D = 400;
     }
     int m_C;
     int m_D;
 };
 ​
 void test6() {
     Son6 s;
     //C,D两个字节(8),两个父类各一个字节(4)   8+4+4=16
     cout << "sizeof Son =" << sizeof(s) << endl;
     
     //当父类中出现了同名成员,需要加作用域
     cout << "base下的m_A = " << s.Base6::m_A << endl;
     cout << "base2下的m_A = " << s.Base62::m_A << endl;
 }
 ​
 int main() {
     test6();
     return 0;
 }

菱形继承

概念:

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

  2. 又有某个类同时继承这两个派生类

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

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性

  2. 羊驼继承自动物的数据继承了两份,但是我们只需要一份就可以了

 #include <iostream>
 using namespace std;
 ​
 class Animal{
 public:
     int m_Age;
 };
 ​
 //利用虚继承来解决菱形继承问题
 //继承之前 加上关键字 virtual 变成虚继承
 //Animal类变成虚基类
 class Sheep :virtual public Animal{};
 class Tuo :virtual public Animal{};
 class SheepTuo : public Sheep, public Tuo {
 ​
 };
 ​
 void test7() {
     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;
     cout << "st.m_Age =" << st.m_Age << endl;
 ​
     //只要一份数据,菱形继承导致数据有两份,资源浪费
 }
 int main() {
     test7();
 ​
     return 0;
 }

总结:利用虚继承来解决菱形继承问题,继承之前 加上关键字 virtual 变成虚继承 只要一份数据,菱形继承导致数据有两份,资源浪费

多态

基本概念

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

多态分为两类:

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

  • 动态多态:派生类和虚函数实现运行时的多态

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

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

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

 #include <iostream>
 using namespace std;
 ​
 class Animal {
 public:
     //虚函数
     virtual void speak() {
         cout << "动物在说话" << endl;
     }
 };
 ​
 class Cat :public Animal {
 public:
     //重写:函数返回值类型 函数名 参数列表 完全相同virtual不影响
     void speak() {
         cout << "小猫在说话" << endl;
     }
 };
 ​
 class Dog :public Animal {
 public:
     void speak() {
         cout << "小狗在说话" << endl;
     }
 };
 ​
 //执行说话的函数
 //地制早绑定 在编译阶段确定函数地址
 //如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
 //让Animal.speak变成虚函数
 ​
 //动态多态满足条件
 //1.有继承关系
 //2.子类重写父类的虚函数
 ​
 //动态多态使用
 //父类的指针或者引用来指向子类的对象Animal& animal = cat
 void doSpeak(Animal& animal) {      //Animal& animal = cat
     animal.speak();
 }
 ​
 void test1() {
     Cat cat;
     Dog dog;
     doSpeak(cat);
     doSpeak(dog);
 }
 ​
 int main() {
     test1();
     return 0;
 }

总结:动态多态满足条件 1.有继承关系 2.子类重写父类的虚函数

动态多态使用 ​ 父类的指针或者引用来指向子类的对象Animal& animal = cat

重写:函数返回值类型 函数名 参数列表 完全相同virtual不影响

 

多态案例—计算器类

描述:分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态优点:

  • 代码组织结构清晰

  • 可读性强

  • 利用前期和后期的扩展以及维护

 #include <iostream>
 using namespace std;
 ​
 //利用普通写法和多态技术实现计算器
 //普通写法
 class Calulator {
 public:
     int getResult(string oper) {
         if (oper == "+") {
             return m_Num1 + m_Num2;
         }
         else if (oper == "-") {
             return m_Num1 - m_Num2;
         }
         else if (oper == "*") {
             return m_Num1 * m_Num2;
         }
         //如果想扩展新的功能,需要修改源代码
         //在真实开发中,提倡开闭原则
         //对扩展进行开放,对修改进行关闭
     }
     int m_Num1; //操作数1
     int m_Num2;
 };
 ​
 void test21() {
     Calulator c;
     c.m_Num1 = 10;
     c.m_Num2 = 10;
     cout << c.m_Num1 << " + " << c.m_Num2 << "= " << c.getResult("+") << endl;
     cout << c.m_Num1 << " - " << c.m_Num2 << "= " << c.getResult("-") << endl;
     cout << c.m_Num1 << " * " << c.m_Num2 << "= " << c.getResult("*") << endl;
 }
 ​
 ​
 //利用多态实现计算器
 //多态优点
 // 1、组织结构清晰
 // 2、可读性强
 // 3、对于前期和后期扩展以及维护
 //实现计算器的抽象类
 class AbstractCalculator {
 public:
     virtual int getResult() {
         return 0;
     }
     int m_Num1;
     int m_Num2;
 };
 ​
 //加法计算器
 class AddCalculator :public AbstractCalculator {
 public:
     int getResult() {
         return m_Num1 + m_Num2;
     }
 };
 ​
 //减法计算器
 class SubCalculator :public AbstractCalculator {
 public:
     int getResult() {
         return m_Num1 - m_Num2;
     }
 };
 ​
 //乘法计算器
 class MulCalculator :public AbstractCalculator {
 public:
     int getResult() {
         return m_Num1 * m_Num2;
     }
 };
 void test22() {
     //父类指针或者引用指向子类对象
 ​
     //加法运算
     AbstractCalculator* abc = new AddCalculator;
     abc->m_Num1 = 10;
     abc->m_Num2 = 10;
 ​
     cout << abc->m_Num1 << " + " << abc->m_Num2 << "= " << abc->getResult() << endl;
     //用完后记得销毁
     delete abc;
 ​
     //减法计算
     abc = new SubCalculator;
     abc->m_Num1 = 100;
     abc->m_Num2 = 100;
 ​
     cout << abc->m_Num1 << " - " << abc->m_Num2 << "= " << abc->getResult() << endl;
     delete abc;
 ​
     //乘法计算
     abc = new MulCalculator;
     abc->m_Num1 = 100;
     abc->m_Num2 = 100;
 ​
     cout << abc->m_Num1 << " * " << abc->m_Num2 << "= " << abc->getResult() << endl;
     delete abc;
 }
 int main() {
     test21();
     return 0;
 }

纯虚函数和抽象类

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

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

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

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

特点:

  • 无法实例化对象

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

#include <iostream>
using namespace std;

//纯虚函数和抽象类
class Base {
public:
	//必须虚函数基础上等于零才合法,为纯虚函数
	//当类中有了一个纯虚函数,称为抽象类
	//抽象类特点:
	//1、无法实例化对象
	//2、抽象类的子类 必须要重写父类中的纯虚函数 否则也属于抽象类
	virtual void func() = 0;
};

class Son :public Base {
public:
	virtual void func(){
		cout << "func函数调用" << endl;
	};

};

void test3() {
	//Base b;	//抽象类无法实现实例化对象
	Son s;		//抽象类的子类 必须要重写父类中的纯虚函数 否则也属于抽象类也不能实现实例化对象
	Base* base = new Son;
	base->func();
}


int main() {
	test3();
	return 0;
}

多态案例—制作饮品

描述:制作饮品大致流程:煮水-冲泡-倒入杯中-加入辅料

 #include <iostream>
 using namespace std;
 ​
 class AbstractDrinking {
 public:
     //煮水
     virtual void Boil() = 0;
 ​
     //冲泡
     virtual void Brew() = 0;
 ​
     //倒入杯中
     virtual void PourInCup() = 0;
 ​
     //加入辅料
     virtual void PutSomething() = 0;
 ​
     void makeDrink() {
         Boil();
         Brew();
         PourInCup();
         PutSomething();
     }
 };
 ​
 class Coffee :public AbstractDrinking {
 public:
     virtual void Boil() {
         cout << "煮农夫山泉" << endl;
     };
 ​
     virtual void Brew() {
         cout << "冲泡咖啡" << endl;
     };
 ​
     virtual void PourInCup() {
         cout << "倒入杯中" << endl;
     };
 ​
     virtual void PutSomething() {
         cout << "加入牛奶和糖" << endl;
     };
 };
 ​
 class Tea :public AbstractDrinking {
 public:
     virtual void Boil() {
         cout << "煮矿泉水" << endl;
     };
 ​
     virtual void Brew() {
         cout << "冲泡茶叶" << endl;
     };
 ​
     virtual void PourInCup() {
         cout << "倒入杯中" << endl;
     };
 ​
     virtual void PutSomething() {
         cout << "加入枸杞" << endl;
     };
 };
 ​
 void doWork(AbstractDrinking *abs) {    //AbstractDrinking *abs = new Cofee
     abs->makeDrink();
     delete abs;     //记得释放
 }
 ​
 void test4() {
     doWork(new Coffee);
     cout << "-------------------" << endl;
     doWork(new Tea);
 }
 ​
 int main() {
     test4();
     return 0;
 }

虚析构和纯虚析构

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

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

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

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

  • 都需要具体的函数实现

区别:

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

语法:

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

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

 

#include <iostream>

using namespace std;

class Animal5 {
public:
	Animal5() {
		cout << "Animal的构造函数调用" << endl;

	}
	利用虚析构可以解决,父类指针释放子类对象时不干净的问题
	//virtual ~Animal5() {
	//	cout << "Animal的虚析构函数调用" << endl;
	//}

	//纯虚析构也能解决该问题与虚析构二选一,且都要代码实现
	//有了纯虚析构后,这个类也属于抽象类,无法实现实例化对象
	virtual ~Animal5() = 0;		//纯虚析构声明

	//纯虚函数
	virtual void speak5() = 0;
};

//纯虚析构代码实现
Animal5 :: ~Animal5() {
	cout << "Animal的纯虚析构函数调用" << endl;
}

class Cat5 :public Animal5 {
public:
	Cat5(string name) {
		cout << "cat的构造函数调用" << endl;
		m_Name = new string(name);
	}
	virtual void speak5() {
		cout << *m_Name <<"小猫在说话" << endl;
	}
	~Cat5() {
		if (m_Name != NULL) {
			cout << "cat的析构函数调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}
	string* m_Name;
};

void test5() {
	Animal5* animal = new Cat5("Tom");
	animal->speak5();
	//父类指针在析构时候 不会调用子类中析构函数 导致子类如果有堆区属性 会出现内存泄露
	delete animal;
}

int main() {
	test5();
	return 0;
}

总结:

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

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

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

多态案例—电脑组装

描述:电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)

将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

#include <iostream>
using namespace std;

class CPU {
public:
	virtual void calculate() = 0;
};

//显卡
class VideoCard {
public:
	virtual void display() = 0;
};


//内存条
class Memory{
public:
	virtual void storage() = 0;
};

//组装电脑
class Computer {
public:
	Computer(CPU* cpu, VideoCard* vc, Memory* mem) {
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}

	//提供工作函数
	void work() {
		//让零件工作起来,调用接口
		m_cpu->calculate();
		m_vc->display();
		m_mem->storage();
	}

	//提供析构函数 释放3个零件
	~Computer() {
		if (m_cpu != NULL) {
			delete m_cpu;
			m_cpu = NULL;
		}
		if (m_vc != NULL) {
			delete m_vc;
			m_vc = NULL;
		}
		if (m_mem != NULL) {
			delete m_mem;
			m_mem = NULL;
		}
	}

private:
	CPU* m_cpu;
	VideoCard* m_vc;
	Memory* m_mem;
};

//具体厂商
class IntelCPU :public CPU {
	void calculate() {
		cout << "Intel的CPU开始计算" << endl;
	}
};

class IntelVideoCard :public VideoCard{
	void display() {
		cout << "Intel的显卡开始显示" << endl;
	}
};

class IntelMemory :public Memory {
	void storage() {
		cout << "Intel的内存条开始存储" << endl;
	}
};


//Lenovo厂商
class LenovoCpu :public CPU {
	void calculate() {
		cout << "Lenovo的CPU开始计算" << endl;
	}
};

class LenovoVideoCard :public VideoCard {
	void display() {
		cout << "Lenovo的显卡开始显示" << endl;
	}
};

class LenovoMemory :public Memory {
	void storage() {
		cout << "Lenovo的内存条开始存储" << endl;
	}
};

void test6() {
	//第一台电脑零件
	CPU* intelCpu = new IntelCPU;
	VideoCard* intelCard = new IntelVideoCard;
	Memory* itelMem = new IntelMemory;

	cout << "第一台电脑开始工作" << endl;
	//第一台电脑
	Computer* computer1 = new Computer(intelCpu, intelCard, itelMem);
	computer1->work();
	delete computer1;

	cout << "---------------------" << endl;
	cout << "第二台电脑开始工作" << endl;
	//第二台电脑
	Computer* computer2 = new Computer(new LenovoCpu,new LenovoVideoCard,new LenovoMemory);
	computer2->work();
	delete computer2;

	cout << "---------------------" << endl;
	cout << "第三台电脑开始工作" << endl;
	//第三台电脑
	Computer* computer3 = new Computer(new IntelCPU, new LenovoVideoCard, new LenovoMemory);
	computer3->work();
	delete computer3;
}

int main() {
	test6();
	return 0;
}

文件操作

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

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

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

文件类型分为两种:

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

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

操作文件的三大类:

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

文本文件

写文件

步骤:1.包含头文件 #include <fstream>

2.创建流对象 ofstream ofs;

3.打开文件 ofs.open("文件路径",打开方式);

4.写数据 ofs << "写入的数据"

5.关闭文件 ofs.close();

文件打开方式:

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

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

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

 #include <iostream>
 #include <fstream>
 using namespace std;
 ​
 void test7() {
     //创建流对象
     ofstream ofs;
 ​
     //指定打开方式
     ofs.open("test.txt",ios::out);
 ​
     //写内容
     ofs << "姓名:张三" << endl;
     ofs << "性别:男" << endl;
     ofs << "年龄:18" << endl;
 ​
     //关闭文件
     ofs.close();
 }
 ​
 int main() {
     test7();
     return 0;
 }

总结:

  • 文件操作必须包含头文件

  • 读文件可以利用ofstream,或者fstream类

  • 打开文件时候需要指定操作文件的路径,以及打开方式

  • 利用<<可以向文件中写数据

  • 操作完毕,要关闭文件

读文件

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

步骤:1.包含头文件 #include <fstream>

2.创建流对象 ifstream ifs;

3.打开文件并判断文件是否打开成功 ifs.open("文件路径",打开方式);

4.读数据 四种方式读取

5.关闭文件 ifs.close();

 #include <iostream>
 #include <fstream>
 #include <string>;
 using namespace std;
 ​
 ​
 void test8() {
     ifstream ifs;
     ifs.open("test.txt", ios::in);
     //判断是否打开成功
     if (!ifs.is_open()) {
         cout << "文件打开失败" << endl;
         return;
     }
 ​
     //读文件
     第一种
     //char buf[1024] = { 0 };
     //while (ifs >> buf) {
     //  cout << buf << endl;
     //}
 ​
     第二种
     //char buf[1024] = { 0 };
     //while (ifs.getline(buf,sizeof(buf))){
     //  cout << buf << endl;
     //}
 ​
     第三种
     //string buf;
     全局
     //while (getline(ifs,buf)) {
     //  cout << buf << endl;
     //}
 ​
     //第四种不推荐
     char c;
     while ((c = ifs.get()) != EOF) {    //EOF=end of file读取到最后
         cout << c;
     }
 ​
     ifs.close();
 }
 ​
 int main() {
     test8();
     return 0;
 }

总结:

  • 读文件可以利用ifstream ,或者fstream类

  • 利用is_open函数可以判断文件是否成功打开

  • ifs.close()关闭

二进制文件

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

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

写文件

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

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

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

 #include <iostream>
 #include <fstream>
 using namespace std;
 ​
 class Person {
 public:
     //再写字符串时最好不要用string,其本质是一个类,会把类写进去
     /*string m_Name;*/
     char m_Name[64];
     int m_Age;
 };
 ​
 void test9() {
     ofstream ofs;
     ofs.open("person.txt", ios::out | ios::binary);
     Person p = {"张三" ,18};
     ofs.write((const char *)&p,sizeof(Person));
     ofs.close();
 }
 ​
 int main() {
     test9();
     return 0;
 }

总结:

  • 文件输出流对象,可以通过write函数,以二进制方式写数据

读文件

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

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

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

 #include <iostream>
 #include <fstream>
 using namespace std;
 ​
 class Person {
 public:
     char m_Name[64];
     int m_Age;
 };
 ​
 void test10() {
     ifstream ifs;
     ifs.open("person.txt", ios::in | ios::binary);
     if (!ifs.is_open()) {
         cout << "文件打开失败" << endl;
         return;
     }
     Person p;
     //数据地址,并强转化为char型  所占内存空间
     ifs.read((char*)&p, sizeof(Person));
     cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
     ifs.close();
 }
 ​
 int main() {
     test10();
     return 0;
 }

职工管理系统

公司中职工分为三类:普通员工,经理,老板,显示信息是,需要显示职工编号,职工姓名,职工岗位,以及职责

普通员工职责:完成经理交给的任务

经理职责:完成老板交给的任务,并下发任务给员工

老板职责:管理公司所有事务

创建管理类:

内容:

  • 与用户的沟通菜单页面

  • 对职工增删改查的操作

  • 与文件的读写交互

菜单功能:

描述:与用户的沟通页面

添加成员函数:

在管理类WorkerManager.h中添加成员函数void Show_Menu()

菜单功能实现:

在管理类WorkerManager.cpp中实现Show_Menu()函数

测试菜单功能:

在职工管理系统.cpp中测试菜单功能

退出功能:

提供退出接口

在main函数中提供分支选择,提供每个功能接口

实现退出功能

在WorkerManager.h提供退出系统的成员函数void exitSystem();

在WorkerManager.cpp中提供具体的功能实现

测试功能

在mian函数分支0选项中,调用退出程序的接口

创建职工:

抽象类和普通员工类:

职工分类为:普通员工、经理、老板

将三种职工抽象到一个类(worker)中,利用多态管理不同职工种类

职工的属性为:职工编号,职工姓名,职工所在部门编号

职工的行为:岗位职责信息描述,获取岗位名称

创建职工抽象类:

在头文件文件夹下面创建worker.h

创建普通员工类:

普通员工类继承职工抽象类,并重写父类中纯虚函数

在头文件和源文件的文件夹下分别创建employee.h和employee.cpp文件

添加职工:

功能描述:批量添加职工,并保存到文件中

功能分析:

用户在批量创建时,可能会创建不同种类的职工

如果想将所有不同种类的员工都放入到一个数组中,可以将所有员工的指针维护到一个数组里

如果想在程序中维护职工不定长度的数组,可以将数组创建到堆区,并利用Worker**的指针维护(二级指针)

文件交互-写文件

描述:对文字读写

在上一个添加功能中,我们只是将所有的数据添加到内存中,一旦程序结束就无法保存了

因此文件管理类中需要一个与文件进行交互的功能,对于文件进行读写操作

设定文件路径

首先我们将文件路径,在wokerManager.h中添加宏常量,并且包含头文件fstream,文件流

成员函数声明

在workerManager.h中类里添加成员函数void save()

文件交互-读文件

描述:将文件中的内容读取到程序中

虽然我们实现了添加职工后保存到文件的操作,但是每次开始运行程序,并没有将文件中数据读取到程序中

而我们的程序功能中还有清空文件的需求

因此构造函数初始化数据的清空分为三种

1.第一次使用,文件未创建

2.文件存在,但是数据被用户清空

3.文件存在,并且保存职工的所有数据

文件未创建

在workerManager.h中添加新的成员属性m_FilelsEmpty标志文件是否为空

修改WorkerManager.cpp中构造函数代码

文件存在且数据为空

在wokerManager.cpp中构造函数追加代码

文件存在且保存职工数据

获取记录的职工人数

在workerManager.h添加成员函数int get_EmpNum();

初始化数组

根据职工的数据以及职工数据,初始化workerManager**m_EmpArray指针

在WorkerManager.h中添加成员函数void init_Emp();

本阶段主要针对C++泛型编程和STL技术做详细讲解,探讨C++更深层的使用

模板

模板的概念

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

模板的特点:

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

  2. 模板的通用并不是万能的

函数模板

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

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

函数模板语法

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

语法:

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

解释

template ---- 声明创建模板

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

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

 //函数模板
 template<typename T>        //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
 void mySwap(T& a, T& b) {
     T temp = a;
     a = b;
     b = temp;
 }
 ​
 void test1() {
     int a = 10;
     int b = 20;
 ​
     //swapInt(a, b);
     
     //利用函数模板交换
     //两种方式使用函数模板
     //1、自动类型推导
     // mySwap(a, b);
     
     //2、显示指定类型
     mySwap<int>(a, b);
 ​
     cout << "a=" << a << endl;
     cout << "b=" << b << endl;
 }

总结:

  1. 函数模板利用关键字template

  2. 使用函数模板有两种方式:自动类型推导,显示指定类型

  3. 模板的目的是为了提高复用性,将类型参数化

函数模板注意事项

注意事项:

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

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

 #include <iostream>
 using namespace std;
 ​
 //函数模板注意事项
 ​
 template<class T>       //template可以替换成class
 void mySwap(T& a, T& b) {
     T temp = a;
     a = b;
     b = temp;
 }
 //1、自动类型推导,必须推导出一直的数据类型T,才可以使用
 void test21() {
     int a = 10;
     int b = 20;
     char c = 'c';
     mySwap(a, b);
     //mySwap(a, c); //错误,推导不出一致的T类型
 ​
     cout << "a=" << a << endl;
     cout << "b=" << b << endl;
 }
 //2、模板必须要确定出T的数据类型,才可以使用
 template<class T>
 void func() {
     cout << "func调用" << endl;
 }
 void test22() {
     //func();   //错误
     func<int>();
 }
 ​
 int main() {
     test22();
     return 0;
 }

函数模板案例

描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序

  • 排序规则从大到小,排序算法为选择排序

  • 分别利用char数组int数组进行测试

#include <iostream>
using namespace std;

//实现通用 对数组进行排序的函数

//交换函数模板
template<class T>
void mySwap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

//排序算法
template<class T>
void mySort(T arr[], int len) {
	for (int i = 0; i < len; i++) {
		int max = i;
		for (int j = i + 1; j < len; j++) {
			//认定的最大值比遍历出的数值要小,说明j下标的元素才是真正的最大值
			if (arr[max] < arr[j]) {
				max = j;	//	更新最大值下标
			}
		}
		if (max != i) {
			//交换max和i元素
			mySwap(arr[max], arr[i]);
		}
	}
}

//打印数组模板
template<class T>
void printArray(T arr[], int len) {
	for (int i = 0; i < len; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
}

void test3() {
	//测试char数组
	char charArr[] = "badcfe";
	int num = sizeof(charArr) / sizeof(char);
	mySort(charArr, num);
	printArray(charArr, num);
}

void test32() {
	//测试数组
	int intArr[] = { 7,5,4,2,9,1,3,8,6 };
	int num = sizeof(intArr) / sizeof(int);
	mySort(intArr, num);
	printArray(intArr, num);
}

int main() {
	test3();
	test32();
	return 0;
}

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

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

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

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

 #include <iostream>
 using namespace std;
 ​
 //普通函数
 int myAdd01(int a, int b) {
     return a + b;
 }
 ​
 //函数模板
 template<class T>
 T myAdd02(T a, T b) {
     return a + b;
 }
 ​
 void test4() {
     int a = 10;
     int b = 20;
     char c = 'c';           //a=97 c=99
     cout << myAdd01(a, c) << endl;
 ​
     //自动类型推导
     //cout << myAdd02(a, c) << endl;        报错, 不会发生隐式转换
 ​
     //显示指定类型
     cout << myAdd02<int> << endl;
 ​
 }
 ​
 int main() {
     test4();
 ​
     return 0;
 }

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

规则如下:

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

  2. 可以通过空模板参数列表来强调函数模板

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

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

#include <iostream>
using namespace std;

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

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

}

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

void test5() {
	int a = 10;
	int b = 20;
	//myPrint(a, b);		//优先普通

	//通过空模板参数列表来强调函数模板
	//myPrint<>(a, b);

	//函数模板也可以发生重载
	//myPrint(a, b, 100);

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

int main() {
	test5();

	return 0;
}

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

模板的局限性

模板的通用性不是万能的,有些特定数据类型,需要用具体化方式做特殊实现

例如:

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

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

#include <iostream>
using namespace std;

class Person {
public:
	//姓名
	Person(string name, int age) {
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	string m_Age;
};

//对比两个数据是否相等函数
template<class T>
bool myCompare(T& a, T& b) {
	return (a == b) ? true : false;
}

//利用具体化Person的版本实现代码,具体化优先调用
template<> bool myCompare(Person& p1, Person& p2) {
	if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) {
		return true;
	}
	else {
		return false;
	}
}

void test6() {
	int a = 10;
	int b = 20;

	bool ret = myCompare(a, b);
	if (ret) {
		cout << "a == b" << endl;
	}
	else {
		cout << "a != b" << endl;
	}
}

void test62() {
	Person p1("Tom", 10);
	Person p2("Tom", 10);
	bool ret = myCompare(p1, p2);
	if (ret) {
		cout << "p1 == p2" << endl;
	}
	else {
		cout << "p1 != p2" << endl;
	}
}

int main() {
	test6();
	test62();
	return 0;
}

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化

  • 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板

类模板

类模板语法

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

 template<typename T>
 类

与函数模板一样

 #include <iostream>
 using namespace std;
 ​
 //
 template<class NameType,class AgeType>
 class Person7 {
 public:
     //姓名
     Person7(NameType name, AgeType age) {
         this->m_Name = name;
         this->m_Age = age;
     }
     void showPerson() {
         cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
     }
     NameType m_Name;
     AgeType m_Age;
 };
 ​
 void test7() {
     Person7<string, int>p1("孙悟空", 999);
     p1.showPerson();
 }
 int main() {
     test7();
     return 0;
 }

类模板和函数模板的区别

主要区别:

  1. 类模板没有自动类型推导的使用方式

  2. 类模板在模板参数列表中可以有默认参数

 #include <iostream>
 using namespace std;
 ​
 template<class NameType, class AgeType = int>
 class Person8 {
 public:
     //姓名
     Person8(NameType name, AgeType age) {
         this->m_Name = name;
         this->m_Age = age;
     }
     void showPerson() {
         cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
     }
     NameType m_Name;
     AgeType m_Age;
 };
 ​
 //类模板没有自动类型推导的使用方式
 void test81() {
     //Person8 p("孙悟空", 999);        //错误,无法自动类型推导
     Person8<string, int>p1("孙悟空", 999);     //正确,只能显示指定类型
     p1.showPerson();
 }
 ​
 //类模板在模板参数列表中可以有默认参数
 void test82() {
     Person8<string>p2("猪八戒", 990);      //正确,只能显示指定类型
     p2.showPerson();
 ​
 }
 ​
 int main() {
     test81();
     test82();
     return 0;
 }

总结:

  • 类模板使用只能用显示指定类型方式

  • 类模板中的模板参数可以有默认参数

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

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

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

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

#include <iostream>
using namespace std;

class Person9 {
public:
	
	void showPerson1() {
		cout << "Person1 show" << endl;
	}	
};

class Person92{
public:

	void showPerson2() {
		cout << "Person2 show" << endl;
	}
};

template<class T>
class MyClass {
public:
	T obj;
	//类模板中成员函数
	void func1() {
		obj.showPerson1();
	}
	void func2() {
		obj.showPerson2();
	}
};

void test9() {
	MyClass<Person9>m;
	m.func1();
	//m.func2();	//出错,说明函数调用才会去创建成员函数
}
int main() {
	test9();
	return 0;
}

总结:类模板中的成员函数斌不是一开始就创建的,在调用时才去创建

类模板对象做函数参数

目标:

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

一共三种传入方式:

  1. 指定传入的类型 ——直接显示对象的数据类型

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

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

 #include <iostream>
 using namespace std;
 ​
 template<class T1,class T2>
 class Person10 {
 public:
     Person10(T1 name, T2 age) {
         this->m_Name = name;
         this->m_Age = age;
     }
     void showPerson() {
         cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
     }
     T1 m_Name;
     T2 m_Age;
 };
 ​
 //1、指定传入类型
 void printPerson1(Person10<string,int>&p) {
     p.showPerson();
 }
 void test10() {
     Person10<string, int>p("孙悟空", 999);
     printPerson1(p);
 }
 ​
 ​
 //2、参数模板化
 template<class T1, class T2>
 void printPerson2(Person10<T1, T2>& p) {
     p.showPerson();
     cout << "T1的类型为:" << typeid(T1).name() << endl;     //string的全名
     cout << "T2的类型为:" << typeid(T2).name() << endl;
 }
 void test102() {
     Person10<string, int>p("猪八戒", 888);
     printPerson2(p);
 }
 ​
 //3、整个类模板化
 template<class T>
 void printPerson3(T & p) {
     p.showPerson();
     cout << "T的类型为:" << typeid(T).name() << endl;       //Person string int
 }
 void test103() {
     Person10<string, int>p("唐僧", 40);
     printPerson3(p);
 }
 ​
 int main() {
     test10();
     test102();
     test103();
     return 0;
 }

类模板与继承

注意以下:

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

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

  3. 如果想灵活指定出父类T的类型,子类也需要变为类模板

#include <iostream>
using namespace std;

template<class T>
class Base {
	T m;
};

//class Son :public Base {};		//错误,必须要知道父类T中的数据类型

class Son : public Base<int> {};

void test11() {
	Son s1;
}

//如果想灵活指定出父类T的类型,子类也需要变为类模板
template<class T1,class T2>
class Son2 :public Base<T2> {
public:
	Son2() {
		cout << "T1的类型为:"<< typeid(T1).name() << endl;			//int
		cout << "T2的类型为:"<< typeid(T2).name() << endl;			//char
	}
	T1 obj;
};
void test112() {
	Son2<int,char> s2;
}

int main() {
	test11();
	test112();

	return 0;
}

类模板成员函数类外实现

#include <iostream>
using namespace std;

template<class T1, class T2>
class Person12 {
public:
	Person12(T1 name, T2 age);
	/*{
		this->m_Name = name;
		this->m_Age = age;
	}*/
	void showPerson();
	/*{
		cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
	}*/
	T1 m_Name;
	T2 m_Age;
};

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

//成员函数类外实现
template<class T1, class T2>
void Person12<T1, T2>::showPerson() {
	cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}

void test12() {
	Person12<string, int>p("Tom", 20);
	p.showPerson();
}


int main() {
	test12();
	return 0;
}

类模板拆分文件编写

掌握类模板成员函数分文件编写产生的问题以及解决方式

问题:

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

解决:

  • 解决方式1:直接包含.cpp源文件

  • 解决方式2:将声明和实现写道同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制(主流)

类模板与友元

掌握类模板配合友元函数的类内和类外实现

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

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

 #include <iostream>
 using namespace std;
 ​
 //通过全局函数 打印Person信息
 ​
 //2、提前让编译器知道Person类
 template<class T1, class T2>
 class Person14;
 ​
 template<class T1, class T2>
 void printPerson2(Person14<T1, T2> p) {
     cout << "类外实现—姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
 }
 ​
 template<class T1,class T2>
 class Person14 {
 ​
     //全局函数 类内实现
     friend void printPerson(Person14<T1, T2> p) {
         cout << "类内实现—姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
     }
 ​
     //全局函数 类外实现 普通函数声明变成函数模板的函数声明
     //加空模板参数列表
     //如果全局函数是类外实现,需要编译器提前知道整个函数的存在
     //这个函数要提前
     friend void printPerson2<>(Person14<T1, T2> p);
 ​
 public:
     Person14(T1 name, T2 age) {
         this->m_Name = name;
         this->m_Age = age;
     }
 private:
     T1 m_Name;
     T2 m_Age;
 };
 ​
 //1、全局函数 类内实现
 void test14() {
     Person14<string, int>p("Tom", 20);
 ​
     printPerson(p);
 }
 ​
 //2、全局函数在类外实现
 void test142() {
     Person14<string, int>p("Jerry", 20);
 ​
     printPerson2(p);
 }
 ​
 int main() {
     test14();
     return 0;
 }

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

类模板案例

描述:实现一个通用的数组类,要求如下:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储

  • 将数组中的数据存储到堆区

  • 构造函数中可以传入数组的容量

  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题

  • 提供尾插法和尾删法对数组中的数据进行增加和删除

  • 可以通过下标的方式访问数组中的元素

  • 可以获取数组中当前元素个数和数组的元素

  • 可以获取数组中当前元素个数和数组的容量

STL初识

STL的诞生

  • 长久以来,软件界一直希望建立一种可重复利用的东西

  • C++的面向对象泛型编程思想,目的就是复用性的提升

  • 大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作

  • 为了建立数据结构和算法的一套标准,诞生了STL

STL基本概念

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

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

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

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

STL六大组件

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

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

  2. 算法:各种常用算法,如sort、find、copy、for_each等

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

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

  5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西

  6. 空间适配器:负责空间的配置与管理

STL中容器、算法、迭代器

容器:置物之所也

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

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

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

序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置

关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系

算法:问题之解法也

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

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

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

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

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

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

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

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

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

容器算法迭代器初识

了解STL容器、算法、迭代器概念之后,我们利用代码感受STL的魅力

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

vector存放内置数据类型

容器:vector

算法:for_each

迭代器:vector<int>::iterator

 #include<iostream>
 using namespace std;
 #include <vector>
 #include <algorithm>    //标准算法头文件
 ​
 void myPrint(int val) {
     cout << val << endl;
 }
 ​
 void test1() {
     //创建一个vector容器,数组
     vector<int> v;
 ​
     //向容器中插入数据
     v.push_back(10);
     v.push_back(20);
     v.push_back(30);
     v.push_back(40);
 ​
     //通过迭代器访问容器中的数据
     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(v.begin(), v.end(), myPrint);
 ​
 }
 ​
 int main() {
     test1();
     return 0;
 }

vector存放自定义数据类型

vector中存放自定义数据类型,并打印输出

 #include<iostream>
 using namespace std;
 #include <vector>
 #include <algorithm>    //标准算法头文件
 ​
 class Person {
 public:
     Person(string name, int age) {
         this->m_Name = name;
         this->m_Age = age;
     }
     string m_Name;
     int m_Age;
 };
 ​
 void test2() {
     vector<Person>v;
     Person p1("aaa", 10);
     Person p2("bbb", 20);
     Person p3("ccc", 30);
     Person p4("ddd", 40);
     Person p5("eee", 50);
 ​
     //向容器中添加数据
     v.push_back(p1);
     v.push_back(p2);
     v.push_back(p3);
     v.push_back(p4);
     v.push_back(p5);
 ​
     //遍历容器中的数据
     for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
         //cout << "姓名:" << (*it).m_Name << " 年龄:" << (*it).m_Age << endl;
         cout << "姓名:" << it->m_Name << " 年龄:" << it->m_Age << endl;
     }
 }
 ​
 //存放自定义数据类型 指针
 void test22() {
     vector<Person*>v;
     Person p1("aaa", 10);
     Person p2("bbb", 20);
     Person p3("ccc", 30);
     Person p4("ddd", 40);
     Person p5("eee", 50);
 ​
     //向容器中添加数据
     v.push_back(&p1);
     v.push_back(&p2);
     v.push_back(&p3);
     v.push_back(&p4);
     v.push_back(&p5);
 ​
     //遍历容器中的数据
     for (vector<Person *>::iterator it = v.begin(); it != v.end(); it++) {
         //it代表指向person指针
         cout << "姓名:" << (*it)->m_Name << " 年龄:" << (*it)->m_Age << endl;
     }
 }
 int main() {
     test2();
     test22();
     return 0;
 }

vector容器嵌套容器

容器中嵌套人容器,我们将所有所有数据进行遍历输出

 #include<iostream>
 using namespace std;
 #include <vector>
 #include <algorithm>    //标准算法头文件
 ​
 void test3() {
     vector<vector<int>>v;
 ​
     //创建小容器
     vector<int>v1;
     vector<int>v2;
     vector<int>v3;
     vector<int>v4;
 ​
     //向小容器中添加数据
     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);
 ​
     //通过大容器,把所有数据遍历一遍
     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() {
     test3();
     return 0;
 }

STL常用容器

String容器

string基本概念

本质:

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

string和char*区别

  • char*是一个指针

  • string是一个类,类内部封装了char*

    管理整个字符串,是一个char*型的容器

特点:

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

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

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

string构造函数

构造函数原型:

  • string(); //创建一个空符号串,例如:string str;

    string(const char* s); //使用字符串s初始化

  • string(const string& str); //使用一个string对象初始化另外一个string对象

  • string(int n,char c); //使用n个字符c初始化

#include<iostream>
using namespace std;

//string的构造函数
//-string(); 							//创建一个空符号串,例如:string str;
//string(const char* s); 				//使用字符串s初始化
//- string(const string & str);        //使用一个string对象初始化另外一个string对象
//- string(int n, char c);              //使用n个字符c初始化

void test1() {
	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();
	return 0;
}

string赋值操作

给string字符串进行赋值

赋值函数原型:

  • string& operator=(const char* s); //char*类型字符串 赋值给当前的字符串

  • string& operator=(const char &s); //把字符串s赋给当前的字符串

  • string& operator=(char c); //字符赋值给当前的字符串

  • string& assign=(const char* s); //把字符串s赋给当前的字符串

  • string& assign=(const char* s,int n); //把字符串s的前几个字符赋给当前的字符串

  • string& assign=(const sring &s); //把字符串s赋给当前的字符串

  • string& assign=(int n,char c); //用n个字符c赋给当前字符串

 #include<iostream>
 using namespace std;
 //-string& operator=(const char* s);                        //char*类型字符串 赋值给当前的字符串
 //-string& operator=(const char& s);                          //把字符串s赋给当前的字符串
 //-string& operator=(char c);                                          //字符赋值给当前的字符串
 //-string& assign = (const char* s);                               //把字符串s赋给当前的字符串
 //-string& assign = (const char* s, int n);                 //把字符串s的前几个字符赋给当前的字符串
 //-string& assign = (const sring & s);                           //把字符串s赋给当前的字符串
 //-string& assign = (int n, char c);                                //用n个字符c赋给当前字符串
 ​
 void test2() {
     string str1;
     str1 = "hello world";
     cout << "str1=" << str1 << endl;
 ​
     string str2;
     str2 = str1;
     cout << "str2=" << str2 << endl;
 ​
     string str3;
     str3 = 'a';
     cout << "str3=" << str3 << endl;
 ​
     string str4;
     str4.assign("hello C++");
     cout << "str4=" << str4 << endl;
 ​
     string str5;
     str5.assign("hello C++", 5);
     cout << "str5=" << str5 << endl;
 ​
     string str6;
     str6 = str5;
     cout << "str6=" << str6 << endl;
 ​
     string str7;
     str7.assign(10, 'w');
     cout << "str7=" << str7 << endl;
 ​
 }
 ​
 int main() {
     test2();
     return 0;
 }

string字符串拼接

实现在字符串末尾拼接字符串

  • string& operator+=(const char* str); //重载+=操作符

  • string& operator+=(const char c); //重载+=操作符

  • string& operator+=(const string& str); //重载+=操作符

  • string& append(const char *s); //把字符串s连接到当前字符串结尾

  • string& append(const char *s,int n); //把字符串s的前n个字符连接到当前字符串结尾

  • string& append(const string &s); //同operator+=(const string& str)

  • string& append(const string &s,int pos,int n); //字符串s中从pos开始的n个字符连接到字符串结尾

#include<iostream>
using namespace std;
//- string& operator+=(const char* str); 						    //重载+=操作符
//- string& operator+=(const char c);                               //重载+=操作符
//- string& operator+=(const string & str);                         //重载+=操作符
//- string& append(const char* s);                                  //把字符串s连接到当前字符串结尾
//- string& append(const char* s,int n);                           //把字符串s的前n个字符连接到当前字符串结尾
//- string& append(const string & s);                               //同operator+=(const string& str)
//- string& append(const string & s, int pos, int n);               //字符串s中从pos开始的n个字符连接到字符串结尾


void test3() {
	string str1= "我";

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

	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);
	//str3.append(str2,0,3);	//只截取到LOL
	str3.append(str2,4,3);	//只截取到DNF
	//参数2从哪个位置开始截取,参数3截取个数
	cout << "str3=" << str3 << endl;
}

int main() {
	test3();
	return 0;
}

string查找和替换

查找:查找指定字符串是否存在

替换:在指定的位置替换字符串

函数原型

  • int find(const string & str, int pos=0) const; //查找str第一次出现位置,从pos开始查找

  • int find(const char* s,int pos=0) const; //查找s第一次出现位置,从pos开始查找

  • int find(const char* s,int pos,int n) const; //从pos位置查找s的前n个字符第一次位置

  • int find(const char c,int pos=0) const; //查找字符c第一次出现位置

  • int rfind(const string& str,int pos=npos) const; //查找str最后一次位置,从pos开始寻找

  • int rfind(const char* s,int pos = npos) const; //查找s最后一次出现位置,从pos开始查找

  • int rfind(const char* s,int pos,int n) const; //从pos位置查找s的前n个字符最后一次位置

  • int rfind(const char* s,int pos = 0) const; //查找字符c最后一次出现位置

  • string& replace(int pos,int n,const string& str); //替换从pos开始n个字符为字符串str

  • string& replace(int pos,int n,const char* s); //替换从pos开始的n个字符为字符串

 #include<iostream>
 using namespace std;
 ​
 //查找
 void test41() {
     string str1 = "abcdefgde";
     int pos = str1.find("de");
     if (pos == -1) {
         cout << "未找到字符串" << endl;
     }
     else {
         cout << "找到字符串,pos=" << pos << endl;        //3从0开始算,d是第四个数,没有查找到返回-1
     }
 ​
     //rfind和find的区别
     //rfind查找方向从右往左,find从左往右,但是输出都是从左往右的从0序号
     pos = str1.rfind("de");
     cout << "找到字符串,pos=" << pos << endl;
 }
 ​
 //替换
 void test42() {
     string str1 = "abcdefg";
     str1.replace(1, 3, "1111");
     cout << str1 << endl;
 }
 int main() {
     test41();
     test42();
     return 0;
 }

string字符串比较

字符串之间的比较

比较方式:

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

=返回 0 , >返回 1,<返回 -1

函数原型

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

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

 #include<iostream>
 using namespace std;
 ​
 void test5() {
     string str1 = "aello";
     string str2 = "hello";
 ​
     if (str1.compare(str2) == 0) {
         cout << "str1等于str2" << endl;
     }
     else if (str1.compare(str2) > 0) {
         cout << "str1大于str2" << endl;
     }
     else {
         cout << "str1小于str2" << endl;
     }
 }
 ​
 int main() {
     test5();
     return 0;
 }

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

string字符存取

string中单个字符存取方式有两种

  • char& operator[](int n); //通过[]方法取字符

  • char& at(int n); //通过at方法获取字符

 #include<iostream>
 using namespace std;
 ​
 void test6() {
     string str = "hello";
     //cout << "str=" << str << endl;
 ​
     //1、通过[]访问单个字符
     for (int i = 0; i < str.size(); i++) {
         cout << str[i] << " ";
     }
     cout << endl;
 ​
     //2、通过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() {
     test6();
     return 0;
 }

总结:string字符串中单个字符存取有两种方式,利用[]或at

string插入和删除

对string字符串进行插入和删除字符操作

函数原型:

  • string& insert(int pos,const char* s); //插入字符串

  • string& insert(int pos,const string& str); //插入字符串

  • string& insert(int pos,int n,char c); //在指定位置插入n个字符c

  • string& erase(int pos,int n=npos); //删除从Pos开始的n个字符

#include<iostream>
using namespace std;

void test7() {
	string str = "hello";

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

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

int main() {
	test7();
	return 0;
}

string子串获取

从字符串中获取想要的子串

函数原型

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

 #include<iostream>
 using namespace std;
 ​
 void test8() {
     string str = "abcdef";
     string subStr = str.substr(1, 3);
     cout << "subStr = " << subStr << endl;
 }
 ​
 //使用操作
 void test82() {
     string email = "zhangsan@sina.com";
 ​
     //从邮件地址中 获取 用户名信息
     //string name = email.substr(0, 8);
 ​
     int pos = email.find("@");      //8
     string name = email.substr(0, pos);
     cout << "name = " << name << endl;
 ​
 }
 int main() {
     test8();
     test82();
     return 0;
 }

vector容器

vector基本概念

功能:

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

vector与普通数组区别:

  • 不同之处在于数组是静态空间,而vector可以动态扩展

动态扩展

  • 并不是在原空间之后续接新空间,而是找更大的内存空间,然后将元数据拷贝到新空间,释放原空间

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

vector构造函数

  • 创建vector容器

函数原型

  • vector<T> v; //采用模板实现类实现,默认构造函数

  • vector(v.begin(),v.end()); //将v[begin(),end()]区间中的圆度拷贝给本身

  • vector(n,elem); //构造函数将n个elem拷贝给本身

  • vector(const vector &vec); //拷贝构造函数

 #include <iostream>
 using namespace std;
 #include<vector>
 ​
 void printVector(vector<int>&v) {
     for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test1() {
     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方式构造  参数1:个数  参数2:赋值
     vector<int>v3(10,100);
     printVector(v3);
 ​
     //!!用的多,拷贝构造
     vector<int>v4(v3);
     printVector(v4);
 }
 ​
 int main() {
     test1();
     return 0;
 }

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

vector赋值操作

给vector容器进行赋值

函数原型

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

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

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

 #include <iostream>
 using namespace std;
 #include<vector>
 ​
 void printVector2(vector<int>& v) {
     for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test2() {
     vector<int>v1;
     for (int i = 0; i < 10; i++) {
         v1.push_back(i);
     }
     printVector2(v1);
 ​
     //赋值 operator=
     vector <int>v2;
     v2 = v1;
     printVector2(v2);
 ​
     //assign
     vector<int>v3;
     v3.assign(v1.begin(), v1.end());
     printVector2(v3);
 ​
     //n个elem方式赋值
     vector<int>v4;
     v4.assign(10, 100);
     printVector2(v4);
 ​
 ​
 }
 int main() {
     test2();
     return 0;
 }

vector容量和大小

对vector容器的容量和大小操作

函数原型:

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

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

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

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

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

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

    //如果容器变短,则末尾超出容器常速的元素被删除

#include <iostream>
using namespace std;
#include<vector>

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

void  test3() {
	vector<int>v1;
	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
	}
	printVector3(v1);

	if (v1.empty()) {		//为真 代表容器为空
		cout << "v1为空" << endl;
	}
	else {
		cout << "v1不为空" << endl;
		cout << "v1的容量为:" << v1.capacity() << endl;
		cout << "v1的大小为:" << v1.size() << endl;
	}

	//重新指定大小
	v1.resize(15,100);	//利用重载版本,可以指定默认填充值,参数2
	printVector3(v1);		//如果重新指定的比原长了,默认用0填充新的位置

	v1.resize(5);
	printVector3(v1);     //如果重新指定的比原短了,超出部分元素会删除

}

int main() {
	test3();
	return 0;
}

vector插入和删除

对vector容器进行插入,删除操作

函数原型

  • push_back(ele); //尾部插入元素ele

  • pop_back(); //删除最后一个元素

  • insert(const_iterator pos,ele); //迭代器指向位置pos插入元素ele

  • insert(const_iterator pos,int count,ele); //迭代器指向位置pos插入count个元素ele

  • erase(const_iterator pos); //删除迭代器指向的元素

  • erase(const_iterator start,const_iterator end); //删除迭代器从start到end之间的元素

  • clear(); //删除容器中所有元素

 #include <iostream>
 using namespace std;
 #include<vector>
 ​
 //- push_back(ele);                                   //尾部插入元素ele
 //- pop_back();                                       //删除最后一个元素
 //- insert(const_iterator pos, ele);                  //迭代器指向位置pos插入元素ele
 //- insert(const_iterator pos, int count, ele);       //迭代器指向位置pos插入count个元素ele
 //- erase(const_iterator pos);                       //删除迭代器指向的元素
 //- erase(const_iterator start, const_iterator end); //删除迭代器从start到end之间的元素
 //- clear();                                         //删除容器中所有元素
 ​
 void printVector4(vector<int>& v) {
     for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test4() {
     vector<int>v1;
     //尾插
     v1.push_back(10);
     v1.push_back(20);
     v1.push_back(30);
     v1.push_back(40);
     v1.push_back(50);
 ​
     //遍历
     printVector4(v1);
 ​
     //尾删
     v1.pop_back();
     printVector4(v1);
 ​
     //插入  
     v1.insert(v1.begin(), 100);
     printVector4(v1);
 ​
     v1.insert(v1.begin(),2, 100);
     printVector4(v1);
 ​
     //删除  参数也是迭代器
     v1.erase(v1.begin());
     printVector4(v1);
 ​
     //清空
     //v1.erase(v1.begin(), v1.end());
     v1.clear();
     printVector4(v1);
 }
 ​
 int main() {
     test4();
     return 0;
 }

vector数据存取

对vector中的数据的存取操作

函数原型:

  • at(int idx); //返回索引idx所指的数据

  • operator[]; //返回索引idx所指的数据

  • front(); //返回容器中第一个数据元素

  • back(); //返回容器中最后一个数据元素

#include <iostream>
using namespace std;
#include<vector>

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

	//利用[]方式访问数组中元素
	for (int i = 0; i < v1.size(); i++) {
		cout << v1[i] << " ";
	}
	cout << endl;

	//利用at方式访问元素
	for (int i = 0; i < v1.size(); i++) {
		cout << v1.at(i) << " ";
	}
	cout << endl;

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

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

}

int main() {
	test5();
	return 0;
}

总结:

  • 除了用迭代器获取vector容器中元素,[]和at也可以

  • front返回容器第一个元素

  • back返回容器最后一个元素

vector互换容器

实现两个容器内元素进行互换

函数原型

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

#include <iostream>
using namespace std;
#include<vector>

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

void test6() {
	vector<int>v1;
	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
	}
	cout << "交换前:" << endl;
	printVector6(v1);

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

	cout << "交换后:" << endl;
	v1.swap(v2);
	printVector6(v1);
	printVector6(v2);
}

//实际运用
//巧用swap可以收缩内存空间
void test62() {
	vector<int>v;
	for (int i = 0; i < 100000; i++) {
		v.push_back(i);
	}
	cout << "v的容量为:" << v.capacity() << endl;
	cout << "v的大小为:" << v.size() << endl;

	v.resize(3);		//重新指定大小
	cout << "v的容量为:" << v.capacity() << endl;
	cout << "v的大小为:" << v.size() << endl;

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

int main() {
	test6();
	test62();
	return 0;
}

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

vector预留空间

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

函数原型:

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

 #include <iostream>
 using namespace std;
 #include<vector>
 ​
 //预留空间
 void test7() {
     vector<int>v;
 ​
     //利用reserve预留空间
     v.reserve(1000000);
 ​
     int num = 0;    //冲击开辟次数
     int* p = NULL;
     for (int i = 0; i < 100000; i++) {
         v.push_back(i);
 ​
         if (p != &v[0]) {
             p = &v[0];
             num++;
         }
     }
     cout << "num=" << num << endl;      //1
 }
 ​
 int main() {
     test7();
     return 0;
 }

deque容器

deque容器基本概念

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

deque与vector区别:(deque相当于双端数组,vector相当于动态数组,类似中间插入的队列和栈)

  • vector对于头部的插入删除效率低,数据量越大,效率越低

  • deque相对而言,对头部的插入删除速度回比vector快

  • vector访问元素时的速度会比deque快,这和两者内部实现有关

 

  • deque容器的迭代器也是支持随机访问的

deque构造函数

功能描述:

  • deque容器构造

函数原型:

  • deque<T>deqT; //默认构造形式

  • deque(beg,end); //构造函数[beg,end]区间中的元素拷贝给本身

  • deque(n,elem); //构造函数将n个elem拷贝给本身

  • deque(const deque &deq); //拷贝构造函数

 #include<iostream>
 using namespace std;
 #include<deque>
 ​
 void printDeque(const deque<int>& d) {
     for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
         //*it = 100; 容器中数据不可修改
         cout << *it << endl;
     }
     cout << endl;
 }
 ​
 void test1() {
     deque<int>d1;
     for (int i = 0; i < 10; i++) {
         d1.push_back(i);
     }
     printDeque(d1);
 ​
     //构造函数[beg,end]区间中的元素拷贝给本身
     deque<int>d2(d1.begin(), d1.end());
     printDeque(d2);
 ​
     //构造函数将n个elem拷贝给本身
     deque<int>d3(10, 100);
     printDeque(d3);
 ​
     //拷贝构造函数
     deque<int>d4(d3);
     printDeque(d4);
 ​
 }
 ​
 int main() {
     test1();
 }

deque赋值操作

函数原型

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

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

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

 #include<iostream>
 using namespace std;
 #include<deque>
 ​
 void printDeque2(const deque<int>& d) {
     for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
         //*it = 100; 容器中数据不可修改
         cout << *it <<" ";
     }
     cout << endl;
 }
 ​
 void test2() {
     deque<int>d1;
     for (int i = 0; i < 10; i++) {
         d1.push_back(i);
     }
     printDeque2(d1);
 ​
     //operator=赋值
     deque<int>d2;
     d2 = d1;
     printDeque2(d2);
 ​
     //assign赋值
     deque<int>d3;
     d3.assign(d1.begin(), d1.end());
     printDeque2(d3);
 ​
     deque<int>d4;
     d4.assign(10, 100);
     printDeque2(d4);
 }
 int main() {
     test2();
     return 0;
 }

deque大小操作

函数原型:

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

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

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

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

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

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

 #include<iostream>
 using namespace std;
 #include<deque>
 ​
 void printDeque3(const deque<int>& d) {
     for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
         //*it = 100; 容器中数据不可修改
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test3() {
     deque<int>d1;
     for (int i = 0; i < 10; i++) {
         d1.push_back(i);
     }
     printDeque3(d1);
 ​
     if (d1.empty()) {
         cout << "d1为空" << endl;
     }
     else {
         cout << "d1不为空" << endl;
         cout << "d1的大小为:"<< d1.size() << endl;
         //deque容器没有容量概念,无限大
     }
 ​
     //重新指定大小
     //d1.resize(15);
     d1.resize(15,1);
     printDeque3(d1);
 ​
     d1.resize(5);
     printDeque3(d1);
 }
 ​
 int main() {
     test3();
     return 0;
 }

deque插入和删除

函数原型:

两端插入操作

  • push_back(elem); //尾部插入元素elem

  • push_front(elem); //头部插入元素elem

  • pop_back(); //删除最后一个元素

  • pop_front(); //删除第一个元素

指定位置操作:

  • insert(pos,elem); //迭代器指向位置pos插入元素elem元素的拷贝,返回型数据的位置

  • insert(pos,n,elem); //迭代器指向位置pos插入n个元素elem,无返回值

  • insert(pos,beg,end); //迭代器指向位置pos插入[beg,end]区间的数据,无返回值

  • erase(pos); //删除迭代器指向的元素,返回下一个数据的位置

  • erase(beg,end); //删除迭代器从beg到end之间的元素,返回下一个数据的位置

  • clear(); //删除容器中所有元素

#include<iostream>
using namespace std;
#include<deque>

void printDeque4(const deque<int>& d) {
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
		//*it = 100; 容器中数据不可修改
		cout << *it << " ";
	}
	cout << endl;
}

void test4() {
	deque<int>d1;
	for (int i = 0; i < 10; i++) {
		d1.push_back(i);
	}
	printDeque4(d1);

	//尾插
	d1.push_back(10); 
	d1.push_back(20); 
	printDeque4(d1);
	//头插
	d1.push_front(100);
	d1.push_front(200);
	printDeque4(d1);

	//尾删
	d1.pop_back();
	printDeque4(d1);
	d1.pop_front();
	printDeque4(d1);
}

void test42() {
	deque<int>d1;
	d1.push_back(10);
	d1.push_front(20);
	d1.push_back(100);
	d1.push_front(200);
	printDeque4(d1);

	//插入
	d1.insert(d1.begin(), 1000);
	printDeque4(d1);

	//插入n个元素elem,无返回值
	d1.insert(d1.begin(),2, 10000);
	printDeque4(d1);

	//按区间进行插入
	deque<int>d2;
	d2.push_back(1);
	d2.push_back(2);
	d2.push_back(3);

	//插入[beg,end]区间的数据,无返回值
	d1.insert(d1.begin(), d2.begin(), d2.end());
	printDeque4(d1);
}

void test43() {
	deque<int>d1;
	d1.push_back(10);
	d1.push_front(20);
	d1.push_back(100);
	d1.push_front(200);
	//200 20 10 100

	//删除
	//迭代器往后偏移
	deque<int>::iterator it = d1.begin();
	it++;
	d1.erase(it);       //200 10 100
	printDeque4(d1);

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

int main() {
	//test4();
	//test42();
	test43();
	return 0;
}

deque数据存取

函数原型:

  • at(int idx); //返回索引idx所指的数据

  • operator[]; //返回索引idx所指的数据

  • front(); //返回容器中第一个数据元素

  • back(); //返回容器中最后一个数据元素

 #include<iostream>
 using namespace std;
 #include<deque>
 ​
 void printDeque5(const deque<int>& d) {
     for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
         //*it = 100; 容器中数据不可修改
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test5() {
     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);
     for (int i = 0; i < d1.size(); i++) {
         cout << d1[i] << endl;
     }
     cout << endl;
 ​
     //通过at方式访问元素
     for (int i = 0; i < d1.size(); i++) {
         cout << d1.at(i) << " ";
     }
     cout << endl;
 ​
     cout << "第一个元素:" << d1.front() << endl;
     cout << "最后一个元素:" << d1.back() << endl;
 }
 ​
 int main() {
     test5();
     return 0;
 }

deque排序

函数原型:

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

 #include<iostream>
 using namespace std;
 #include<deque>
 #include<algorithm>
 ​
 void printDeque6(const deque<int>& d) {
     for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
         //*it = 100; 容器中数据不可修改
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test6() {
     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);
     printDeque6(d1);
 ​
     //排序默认从小到大,升序
     //对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其进行排序
     //例如vector
     sort(d1.begin(), d1.end());
     cout << "排序后" << endl;
     printDeque6(d1);
 }
 ​
 int main() {
     test6();
     return 0;
 }

案例-评委打分

有5名选手,选手ABCDE,10个评委分别对每一个选手打分,去除最高分

实现步骤

  1. 创建5名选手,放到vector中

  2. 遍历vector容器,取出来每一个选手,执行for循环,可以把10个评分打分存到deque容器中

  3. sort算法对deque容器中分数排序,去除最高分和最低分

  4. deque容器遍历一遍,累加总分

  5. 获取平均分

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<deque>
 #include<algorithm>
 #include<ctime>
 //有5名选手,选手ABCDE,10个评委分别对每一个选手打分,去除最高分
 //1. 创建5名选手,放到vector中
 //2. 遍历vector容器,取出来每一个选手,执行for循环,可以把10个评分打分存到deque容器中
 //3. sort算法对deque容器中分数排序,去除最高分和最低分
 //4. deque容器遍历一遍,累加总分
 //5. 获取平均分
 ​
 ​
 class Person {
 public:
     Person(string name, int score) {
         this->m_Name = name;
         this->m_Score = score;
     }
     string m_Name;
     int m_Score;
 };
 ​
 void createPerson(vector<Person>& v) {
     string nameSeed = "ABCDE";
     for (int i= 0; i < 5; i++) {
         string name = "选手";
         name += nameSeed[i];
 ​
         int score = 0;
         Person p(name, score);
 ​
         //将创建的person对象 放入到容器中
         v.push_back(p);
     }
 }
 ​
 //打分
 void setScore(vector<Person>&v) {
     for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
         //将评委的分数,放入到deque容器中
         deque<int>d;
         for (int i = 0; i < 10; i++) {
             int score = rand() % 41 + 60;        //60~100
             d.push_back(score);
         }
 ​
         //每个评委的打分
         /*cout << "选手:" << it->m_Name << "\t打分:";
         for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++) {
             cout << *dit << " ";
         }
         cout << endl;*/
 ​
         //排序
         sort(d.begin(), d.end());
             //去掉最高分和最低分
             d.pop_back();
             d.pop_front();
 ​
             //取平均分
             int sum = 0;
             for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++) {
                 sum += *dit;          //累加每个评委的分数
             }
             int avg = sum / d.size();
 ​
             //将平均分赋值给选手
             it->m_Score = avg;
     }
 }
 ​
 //显示分数
 void showSorce(vector<Person>&v) {
     for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
         cout << "姓名:" << it->m_Name << "\t平均分:" << it->m_Score << endl;
     }
 }
 ​
 int main() {
     //随机数种子
     srand((unsigned int)time(NULL));
     //1、创建5名选手
     vector<Person>v;
     createPerson(v);
     //测试
     /*for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
         cout << "姓名:" << (*it).m_Name << "\t分数:"<< (*it).m_Score << endl;
     }*/
 ​
     //2、给5名选手打分
     setScore(v);
 ​
     //3、显示最后得分
     showSorce(v);
 ​
     return 0;
 }

stack容器

stack基本概念

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

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

(只有栈顶元素可以被外界访问到,当要看第二个元素时,需要把第一个元素拿出,这时候数据发生变化,不符合遍历条件)

栈可以判断容器为空嘛?

可以,empty来判断

栈可以返回元素个数嘛?

可以,size

stack常用接口

描述:栈容器常用的对外接口

构造函数:

  • stack<T> stk; //stack采用模板类实现,stack对象的默认构造形式

  • stack(const stack &stk); //拷贝构造函数

赋值操作:

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

数据存取:

  • push(elem); //向栈顶添加元素

  • pop(); //从栈顶移除一个元素

  • top(); //返回栈顶元素

大小操作:

  • empty(); //判断堆栈是否为空

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

 #include<iostream>
 using namespace std;
 #include<stack>
 ​
 void test() {
     //特点:先进后出
     stack<int>s;
 ​
     //入栈
     s.push(10);
     s.push(20);
     s.push(30);
     s.push(40);
 ​
     cout << "栈的大小" << s.size() << endl;
 ​
     //只要栈不为空,查看栈顶,并且执行出栈操作
     while (!s.empty()) {
         //查看栈顶元素
         cout << "栈顶元素" << s.top() << endl;
 ​
         //出栈
         s.pop();
     }
     cout << "栈的大小"<< s.size() << endl;
 }
 ​
 int main() {
     test();
     return 0;
 }

queue容器

queue容器基本概念

概念:Queue是一种先进先出的数据结构(队列),它有两个出口

队列容器允许一端新增元素,另一端移除元素

队列只有队头和队尾可以被外界使用,因此队列不允许有遍历行为

queue常用接口

描述:队列容器常用的对外接口

构造函数:

  • queue<T> que; //queue采用模板类实现,queue对象的默认构造形式

  • queue(const queue &que); //拷贝构造函数

赋值操作:

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

数据存取:

  • push(elem); //向栈顶添加元素

  • pop(); //从栈顶移除一个元素

  • top(); //返回栈顶元素

大小操作:

  • empty(); //判断堆栈是否为空

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

#include<iostream>
using namespace std;
#include<queue>

class Person {
public:
	Person(string name, int age) {
		this->m_Name=name;
		this->m_Age=age;
	}
	string m_Name;
	int m_Age;
};

void test2() {
	//特点:先进先出
	queue<Person>q;

	//准备数据
	Person p1("唐僧", 30);
	Person p2("孙悟空", 1000);
	Person p3("猪八戒", 900);
	Person p4("沙僧", 800);

	//入队
	q.push(p1);
	q.push(p2);
	q.push(p3);
	q.push(p4);

	cout << "队列的大小" << q.size() << endl;

	//判断只要队列不为空,查看对头,查看队尾
	while (!q.empty()) {
		//查看对头
		cout << "队头元素——姓名:" << q.front().m_Name << "年龄:" << q.front ().m_Age << endl;
		cout << "队尾元素——姓名:" << q.back().m_Name << "年龄:" << q.back().m_Age << endl;

		//出队
		q.pop();
	}

	cout << "队列的大小" << q.size() << endl;
}

int main() {
	test2();
	return 0;
}

list容器

list基本概念

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

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

链表的组成:链表由一系列节点组成

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

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

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

优点:

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

  • 可以对任意位置进行快速插入或删除元素,修改指针即可,不需要移动大量元素

缺点:

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

  • 容器遍历速度,没有数组快

  • 占用空间比数组大

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

list构造函数

函数原型:

  • list<T> lst; //默认构造形式

  • list(beg,end); //构造函数[beg,end]区间中的元素拷贝给本身

  • list(n,elem); //构造函数将n个elem拷贝给本身

  • list(const list &lst); //拷贝构造函数

 #include<iostream>
 using namespace std;
 #include<list>
 ​
 void printList(const list<int>& L) {
     for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test3() {
     list<int>L1;
 ​
     L1.push_back(10);
     L1.push_back(20);
     L1.push_back(30);
     L1.push_back(40);
 ​
     printList(L1);
 ​
     //区间方式构造
     list<int>L2(L1.begin(), L1.end());
     printList(L2);
 ​
     //拷贝构造
     list<int>L3(L2);
     printList(L3);
 ​
     //n个elem
     list<int>L4(10, 10000);
     printList(L4);
 }
 ​
 int main() {
     test3();
     return 0;
 }

list赋值和交换

函数原型:

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

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

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

  • swap(lst); //将list与本身的元素互换

 #include<iostream>
 using namespace std;
 #include<list>
 ​
 void printList4(const list<int>& L) {
     for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test4() {
     list<int>L1;
 ​
     L1.push_back(10);
     L1.push_back(20);
     L1.push_back(30);
     L1.push_back(40);
 ​
     printList4(L1);
 ​
     list<int>L2;
     L2 = L1;
     printList4(L2);
 ​
     list<int>L3;
     L3.assign(L2.begin(), L2.end());
     printList4(L3);
 ​
     list<int>L4;
     L4.assign(10, 100);
     printList4(L4);
 }
 ​
 void test42() {
     list<int>L1;
 ​
     L1.push_back(10);
     L1.push_back(20);
     L1.push_back(30);
     L1.push_back(40);
 ​
     list<int>L2;
     L2.assign(10, 100);
 ​
     cout << "交换前:" << endl;
     printList4(L1);
     printList4(L2);
 ​
     L1.swap(L2);
     cout << "交换后:" << endl;
     printList4(L1);
     printList4(L2);
 }
 ​
 ​
 int main() {
     test4();
     test42();
     return 0;
 }

list大小操作

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

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

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

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

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

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

 #include<iostream>
 using namespace std;
 #include<list>
 ​
 void printList5(const list<int>& L) {
     for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test5() {
     list<int>L1;
 ​
     L1.push_back(10);
     L1.push_back(20);
     L1.push_back(30);
     L1.push_back(40);
 ​
     printList5(L1);
 ​
     if (L1.empty()) {
         cout << "容器为空" << endl;
     }
     else {
         cout << "容器不为空" << endl;
         cout << "L1元素个数:" << L1.size() << endl;
     }
 ​
     //重新指定大小
     L1.resize(10,10000);
     printList5(L1);
 ​
     L1.resize(2);
     printList5(L1);
 }
 ​
 int main() {
     test5();
     return 0;
 }

list插入和删除

  • push_back(elem); //尾部插入元素elem

  • push_front(elem); //头部插入元素elem

  • pop_back(); //删除最后一个元素

  • pop_front(); //删除第一个元素

  • insert(pos,elem); //迭代器指向位置pos插入元素elem元素的拷贝,返回型数据的位置

  • insert(pos,n,elem); //迭代器指向位置pos插入n个元素elem,无返回值

  • insert(pos,beg,end); //迭代器指向位置pos插入[beg,end]区间的数据,无返回值

  • erase(pos); //删除迭代器指向的元素,返回下一个数据的位置

  • erase(beg,end); //删除迭代器从beg到end之间的元素,返回下一个数据的位置

  • remove(elem); //删除容器中所有与elem匹配的元素

  • clear(); //删除容器中所有元素

 #include<iostream>
 using namespace std;
 #include<list>
 ​
 void printList6(const list<int>& L) {
     for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test6() {
     list<int>L1;
 ​
     //尾插
     L1.push_back(10);
     L1.push_back(20);
     L1.push_back(30);
     L1.push_back(40);
 ​
     //头插
     L1.push_front(100);
     L1.push_front(200);
     L1.push_front(300);
     L1.push_front(400);
 ​
     printList6(L1);
 ​
     //尾删
     L1.pop_back();
     printList6(L1);
 ​
     //头删
     L1.pop_front();
     printList6(L1);
 ​
     //insert插入
     list<int>::iterator it = L1.begin();
     L1.insert(++it, 1000);
     //L1.insert(L1.begin(), 1000);
     printList6(L1);
 ​
     //删除
     it = L1.begin();
     L1.erase(++it);
     printList6(L1);
 ​
     //删除特定元素
     L1.push_back(10000);
     L1.push_back(10000);
     L1.push_back(10000);
     printList6(L1);
     L1.remove(10000);
     printList6(L1);
 ​
     L1.clear();
     printList6(L1);
 }
 ​
 int main() {
     test6();
     return 0;
 }
 ​

list数据存取

函数原型

  • front(); //返回容器中第一个数据元素

  • back(); //返回容器中最后一个数据元素

list存储不是连续的空间,因此不能使用at和[]存取,迭代器也不支持随机访问

 #include<iostream>
 using namespace std;
 #include<list>
 ​
 void printList7(const list<int>& L) {
     for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test7() {
     list<int>L1;
 ​
     //尾插
     L1.push_back(10);
     L1.push_back(20);
     L1.push_back(30);
     L1.push_back(40);
 ​
     //L[0]不可以用[]访问list容器中的元素
     //L1.at不可以用at访问list容器中的元素
     //原因,list本身是个链表,不是连续空间储存数据,迭代器也不支持随机访问
 ​
     cout << "第一个元素:" << L1.front() << endl;
     cout << "最后一个元素:" << L1.back() << endl;
 ​
     //验证迭代器不支持随机访问
     list<int>::iterator it = L1.begin();
     //it = it + 1;  报错,但是it++,it--支持,+具体数字是跳跃式访问
     it++;
     it--;
 }
 ​
 int main() {
     test7();
     return 0;
 }

list反转和排序

函数原型:

  • reverse() //链表反转

  • sort() //链表排序

 #include<iostream>
 using namespace std;
 #include<list>
 ​
 void printList8(const list<int>& L) {
     for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 bool myCompare(int v1,int v2) {
     //降序 第一个数> 第二个数
     return v1 > v2;
 }
 ​
 void test8() {
     list<int>L1;
 ​
     //尾插
     L1.push_back(20);
     L1.push_back(30);
     L1.push_back(10);
     L1.push_back(40);
     printList8(L1);
 ​
     L1.reverse();
     cout << "反转后:" << endl;
     printList8(L1);
 ​
     //所有不支持随机访问迭代器的容器,不可以用标准算法
     //不支持随机访问迭代器的容器,内部会提供对应一些算法
     //sort(L1.begin(), L1.end());
 ​
     L1.sort();  //默认从小到大,升序
     cout << "排序后:" << endl;
     printList8(L1);
 ​
     L1.sort(myCompare);  //降序 对于sort提供两个版本重载,可以提供一个函数或者仿函数来改变默认排序规则
     printList8(L1);
 ​
 }
 ​
 int main() {
     test8();
     return 0;
 }

排序案例

描述:将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高

排序规则:按照年龄进行排序,如果年龄相同按照升高进行降序

 #include<iostream>
 using namespace std;
 #include<list>
 ​
 //将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高
 //排序规则:按照年龄进行排序,如果年龄相同按照身高进行降序
 ​
 class Person9 {
 public:
     Person9(string name, int age, int height) {
         this->m_Name = name;
         this->m_Age = age;
         this->m_Height = height;
     }
     string m_Name;
     int m_Age;
     int m_Height;
 };
 ​
 //指定排序规则
 bool myComparePerson(Person9& q1, Person9& q2) {
     //按照身高 降序
     if(q1.m_Age == q2.m_Age){
         return q1.m_Height > q2.m_Height;
     }
     else
     {
         return q1.m_Age < q2.m_Age;
     }
 }
 ​
 void test9() {
     list<Person9>L;
     Person9 q1("张三", 20, 185);
     Person9 q2("李四", 30, 175);
     Person9 q3("王五", 30, 165);
     Person9 q4("谢六", 40, 155);
 ​
     L.push_back(q1);
     L.push_back(q2);
     L.push_back(q3);
     L.push_back(q4);
 ​
     for (list<Person9>::const_iterator it = L.begin(); it != L.end(); it++) {
         cout << "姓名: " << it->m_Name << "\t年龄:" << (*it).m_Age << "\t身高:" << (*it).m_Height << "cm" << endl;
         //用*和指针都行,it就是指针
     }
 ​
     //排序后
     cout << "-----------------------" << endl;
     cout << "排序后:" << endl;
 ​
     L.sort(myComparePerson);
     
     for (list<Person9>::const_iterator it = L.begin(); it != L.end(); it++) {
         cout << "姓名: " << it->m_Name << "\t年龄:" << (*it).m_Age << "\t身高:" << (*it).m_Height << "cm" << endl;
     }
 }
 ​
 int main() {
     test9();
     return 0;
 }

set/multist容器

set基本概念

  • 也叫集合容器,关联式容器,所有元素都会在插入时自动被排序

本质

  • set/multist属于关联式容器,底层结构是用二叉树实现

set和multist区别

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

  • multist允许容器中有重复的元素

set构造和赋值

构造:

  • set<T> st; //默认构造函数

  • set(const set &st); //拷贝构造函数

插入数据 只有insert方式

赋值:

  • set& operator=(const set &st); //重载等号操作

 #include<iostream>
 using namespace std;
 #include <set>
 ​
 void printSet(const set<int>& s) {
     for (set<int>::const_iterator it = s.begin(); it != s.end(); it++) {
         //*it = 100; 容器中数据不可修改
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test() {
     set<int>s1;
 ​
     //插入数据 只有insert方式
     //s1.push_back(10);
     s1.insert(10);
     s1.insert(20);
     s1.insert(30);
     s1.insert(40);
     s1.insert(30);
 ​
     //遍历容器
     //特点,所有元素插入时候自动被排序
     //set容器不允许插入重复值
     printSet(s1);
 ​
     //拷贝构造
     set<int>s2(s1);
     printSet(s1);
 ​
     //赋值
     set<int>s3;
     s3 = s2;
     printSet(s1);
 ​
 }
 ​
 int main() {
     test();
     return 0;
 }

set大小和交换

函数原型

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

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

  • swap(); //交换两个集合容器

#include<iostream>
using namespace std;
#include <set>

void printSet2(const set<int>& s) {
	for (set<int>::const_iterator it = s.begin(); it != s.end(); it++) {
		//*it = 100; 容器中数据不可修改
		cout << *it << " ";
	}
	cout << endl;
}

void test2() {
	set<int>s1;

	s1.insert(10);
	s1.insert(20);
	s1.insert(30);
	s1.insert(40);

	if (s1.empty()) {
		cout << "容器为空" << endl;
	}
	else {
		cout << "set元素大小:" << s1.size() << endl;
	}
	cout << "交换前:" << endl;
	printSet2(s1);
	
	set<int>s2;
	s2.insert(100);
	s2.insert(200);
	s2.insert(300);
	s2.insert(400);
	printSet2(s2);

	s2.swap(s1);
	cout << "交换后:" << endl;
	printSet2(s1);
	printSet2(s2);

}

int main() {
	test2();
	return 0;
}

set插入和删除

函数原型

  • insert(elem); //迭代器插入元素

  • erase(pos); //删除迭代器指向的元素,返回下一个数据的位置

  • erase(beg,end); //删除迭代器从beg到end之间的元素,返回下一个数据的位置

  • erase(elem); //删除迭代器所有elem元素

  • clear(); //删除容器中所有元素

 #include<iostream>
 using namespace std;
 #include <set>
 ​
 void printSet3(const set<int>& s) {
     for (set<int>::const_iterator it = s.begin(); it != s.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test3() {
     set<int>s1;
     s1.insert(50);
     s1.insert(10);
     s1.insert(20);
     s1.insert(30);
     s1.insert(40);  
     printSet3(s1);
 ​
     //删除
     s1.erase(s1.begin());
     printSet3(s1);
 ​
     //重载删除
     s1.erase(20);
     printSet3(s1);
 ​
     //清空
     s1.erase(s1.begin(), s1.end());
     printSet3(s1);
     
     s1.clear();
     printSet3(s1);
 ​
 }
 ​
 int main() {
     test3();
     return 0;
 }

set查找和统计

函数原型

  • find(key); //查看key是否存在,返回该键的元素的迭代器,若不存在,返回set.end();

  • count(key); //统计key的元素个数

 #include<iostream>
 using namespace std;
 #include <set>
 ​
 void printSet4(const set<int>& s) {
     for (set<int>::const_iterator it = s.begin(); it != s.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 void test4() {
     set<int>s1;
     s1.insert(10);
     s1.insert(20);
     s1.insert(30);
     s1.insert(40);
     printSet4(s1);
 ​
     //返回的是迭代器,所以要迭代器接收
     set<int>::iterator pos= s1.find(10);
     if (pos != s1.end()) {
         cout << "找到元素" << *pos << endl;
     }
     else {
         cout << "未找到元素" << endl;
 ​
     }
 }
 ​
 //统计
 void test42() { 
     set<int>s1;
     s1.insert(10);
     s1.insert(20);
     s1.insert(30);
     s1.insert(40);
     int num = s1.count(10);
     //对于set而言,结果要么0要么1
     cout << "num=" << num << endl;
 }
 ​
 int main() {
     test4();
     test42();
     return 0;
 }

set和multist区别

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

  • set插入数据的同时会返回插入结果,表示插入是否成功

  • multist不会监测数据,因此可以插入重复数据

#include<iostream>
using namespace std;
#include <set>

void test5() {
	set<int>s1;
	pair<set<int>::iterator,bool> ret =s1.insert(10);
	
	if (ret.second) {
		cout << "第一次插入成功" << endl;
	}
	else {
		cout << "第一次插入失败" << endl;
	}

	ret = s1.insert(10);
	if (ret.second) {
		cout << "第二次插入成功" << endl;
	}
	else {
		cout << "第二次插入失败" << endl;
	}

	multiset<int>ms;
	ms.insert(10);
	ms.insert(10);

	for (multiset<int>::iterator it = ms.begin(); it != ms.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

int main() {
	test5();
	return 0;
}

pair对组创建

描述:

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

两种创建方式

  • pair<type,type> p (value1,value2);

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

#include<iostream>
using namespace std;

void test6() {
	//第一种方式
	pair<string, int>p("Tom", 20);
	cout << "姓名:" << p.first << " 年龄:" << p.second << endl;

	//第二种方式
	pair<string, int>p2 = make_pair("Jerry", 30);
	cout << "姓名:" << p2.first << " 年龄:" << p2.second << endl;

}

int main() {
	test6();
	return 0;
}

set容器排序

  • set容器默认排序规则从小到大,掌握如何改变排序规则

主要技术点:

  • 利用仿函数,可以改变排序规则

示例一:set存放内置数据类型,利用仿函数指定set容器的排序规则

 #include<iostream>
 using namespace std;
 #include <set>
 ​
 class myCompare7 {
 public:
     bool operator()(int v1, int v2)const{
         return v1 > v2;
     }
 };
 ​
 void test7() {
     set<int>s1;
     s1.insert(10);
     s1.insert(40);
     s1.insert(20);
     s1.insert(30);
     s1.insert(50);
 ​
     for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 ​
     //指定排序规则升序
     set<int, myCompare7>s2;
     s2.insert(10);
     s2.insert(40);
     s2.insert(20);
     s2.insert(30);
     s2.insert(50);
 ​
     for (set<int, myCompare7>::iterator it = s2.begin(); it != s2.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 int main() {
     test7();
     return 0;
 }

示例二set存放自定义数据类型

 #include<iostream>
 using namespace std;
 #include <set>
 ​
 class Person8 {
 public:
     Person8(string name, int age) {
         this->m_Name = name;
         this->m_Age = age;
     }
     string m_Name;
     int m_Age;
 };
 ​
 class comparePerson {
 public:
     bool operator()(const Person8& p1, const Person8& p2)const {
         //按照年龄 降序
         return p1.m_Age > p2.m_Age;
 ​
     }
 };
 ​
 void test8() {
     //自定义数据类型 都会指定排序规则
     set<Person8, comparePerson>s;
 ​
     //创建Person对象
     Person8 q1("张三", 20);
     Person8 q2("李四", 30);
     Person8 q3("王五", 33);
 ​
     s.insert(q1);
     s.insert(q2);
     s.insert(q3);
 ​
     for (set<Person8>::iterator it = s.begin(); it != s.end(); it++) {
         cout << "姓名: " << it->m_Name << "\t年龄:" << (*it).m_Age <<  endl;
     }
 }
 ​
 int main() {
     test8();
     return 0;
 }

map和multimap容器

map基本概念

简介:

  • map中所有元素都是pair

  • pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)

  • 所有元素都会根据元素的键值自动排序

本质:

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

优点:

  • 可以根据key值快速找到value值

map和multimap区别

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

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

map构造和赋值

 #include<iostream>
 using namespace std;
 #include<map>
 ​
 void printMap(map<int, int>& m) {
     for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
         cout << "key=" << (*it).first << "\tvalue=" << (*it).second << endl;
     }
     cout << endl;
 }
 void test() {
     map<int, int>m;
 ​
     //map每个元素都是对组
     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));
 ​
     printMap(m);
 ​
     //拷贝构造
     map<int, int>m2(m);
     printMap(m2);
 ​
     //赋值
     map<int, int>m3;
     m3 = m2;
     printMap(m3);
 }
 int main() {
     test();
     return 0;
 }

map大小和交换

函数原型

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

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

  • swap(); //交换两个集合容器

 #include<iostream>
 using namespace std;
 #include<map>
 ​
 void printMap2(map<int, int>& m) {
     for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
         cout << "key=" << (*it).first << "\tvalue=" << (*it).second << endl;
     }
     cout << endl;
 }
 void test2() {
     map<int, int>m;
 ​
     //map每个元素都是对组
     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));
 ​
     printMap2(m);
 ​
     if (m.empty()) {
         cout << "为空" << endl;
     }
     else {
         cout << "不为空" << endl;
         cout << "大小为:" << m.size() << endl;
     }
 }
 ​
 //交换
 void test22() {
     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));
 ​
     map<int, int>m2;
     m2.insert(pair<int, int>(5, 100));
     m2.insert(pair<int, int>(6, 200));
     m2.insert(pair<int, int>(7, 300));
     m2.insert(pair<int, int>(8, 400));
 ​
     cout << "交换前" << endl;
     printMap2(m);
     printMap2(m2);
 ​
     m2.swap(m);
     cout << "交换后" << endl;
     printMap2(m);
     printMap2(m2);
 }
 ​
 int main() {
     test2();
     test22();
     return 0;
 }

map插入和删除

函数原型

  • insert(elem); //迭代器插入元素

  • erase(pos); //删除迭代器指向的元素,返回下一个数据的位置

  • erase(beg,end); //删除迭代器从beg到end之间的元素,返回下一个数据的位置

  • erase(key); //删除迭代器所有key元素

  • clear(); //删除容器中所有元素

 #include<iostream>
 using namespace std;
 #include<map>
 ​
 void printMap3(map<int, int>& m) {
     for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
         cout << "key=" << (*it).first << "\tvalue=" << (*it).second << endl;
     }
     cout << endl;
 }
 void test3() {
     map<int, int>m;
     
     //插入第一种
     m.insert(pair<int, int>(1, 10));
     
     //插入第二种
     m.insert(make_pair(2, 20));
 ​
     //插入第三种
     m.insert(map<int, int>::value_type(3, 30));
 ​
     //第四种
     m[4] = 40;
     //[]不建议插入,用途,可以利用key访问到value
     cout << m[5] << endl;
     cout << m[4] << endl;
 ​
     printMap3(m);
 ​
     //删除
     m.erase(m.begin());
     printMap3(m);
 ​
     m.erase(3);    //按照key来删
     printMap3(m);
 ​
     //m.erase(m.begin(), m.end());
     m.clear();
     printMap3(m);
 ​
 }
 ​
 int main() {
     test3();
     return 0;
 }

map查找和统计

函数原型

  • find(key); //查看key是否存在,返回该键的元素的迭代器,若不存在,返回set.end();

  • count(key); //统计key的元素个数

 #include<iostream>
 using namespace std;
 #include<map>
 ​
 void printMap4(map<int, int>& m) {
     for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
         cout << "key=" << (*it).first << "\tvalue=" << (*it).second << endl;
     }
     cout << endl;
 }
 ​
 void test4() {
     //查找
     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>(3, 40));
 ​
     m.find(3);
     map<int, int>::iterator pos = m.find(3);
 ​
     if (pos != m.end()) {
         cout << "找到元素key= " << (*pos).first << " value=" << (*pos).second << endl;
     }
     else {
         cout << "未找到元素" << endl;
     }
 ​
     //统计
     //map不允许插入重复key,对于count统计而言,结果要么0,要么1
     //multimap可以大于1
     int num = m.count(3);
     cout << "num=" << num << endl;
 }
 int main() {
     test4();
     return 0;
 }

map容器排序

默认按照key从小到大

主要技术点:

  • 利用仿函数,可以改变排序规则

 #include<iostream>
 using namespace std;
 #include<map>
 ​
 class myCompare5{
 public:
     bool operator()(int v1, int v2)const {
         return v1 > v2;
     }
 };
 ​
 void test5() {
     map<int, int>m;
     m.insert(pair<int, int>(3, 10));
     m.insert(pair<int, int>(1, 20));
     m.insert(pair<int, int>(4, 30));
     m.insert(pair<int, int>(2, 40));
     m.insert(pair<int, int>(5, 50));
 ​
     for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
         cout << "key=" << (*it).first << "\tvalue=" << (*it).second << endl;
     }
     cout << endl;
 ​
     //指定排序规则升序
     map<int, int,myCompare5>m2;
     m2.insert(pair<int, int>(3, 10));
     m2.insert(pair<int, int>(1, 20));
     m2.insert(pair<int, int>(4, 30));
     m2.insert(pair<int, int>(2, 40));
     m2.insert(pair<int, int>(5, 50));
 ​
     for (map<int, int, myCompare5>::iterator itm = m2.begin(); itm != m2.end(); itm++) {
         cout << "key=" << (*itm).first << "\tvalue=" << (*itm).second << endl;
     }
     cout << endl;
 }
 ​
 int main() {
     test5();
     return 0;
 }

案例-员工分组

描述:

 #include<iostream>
 using namespace std;
 #include<map>
 #include<vector>
 ​
 //定义0-2代表
 #define CEHUA  0
 #define MEISHU 1
 #define YANFA  2
 ​
 class Person {
 public:
     /*Person(string name,int wage) {
         this->m_Name;
         this->m_Wage;
     }*/
     string m_Name;
     int m_Wage;
 };
 ​
 void createPerson(vector<Person>& v) {
     string nameSeed = "ABCDEFGHIJ";
     for (int i = 0; i < 10; i++) {
         Person p;
         p.m_Name = "员工";
         p.m_Name += nameSeed[i];
 ​
         p.m_Wage = rand() % 10001 + 10000;   //1w~2w
         int wage = 0;
         
         //将创建的person对象 放入到容器中
         v.push_back(p);
     }
 }
 ​
 //分组
 void setGroup(vector<Person>&v,multimap<int,Person>&m) {
     for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
         //产生随机部门编号
         int id = rand() % 3;    //0,1,2
 ​
         //将员工插入到分组
         //key代表部门编号,value代表员工
         m.insert(make_pair(id, *it));
         
     }
 }
 ​
 void showGroup(multimap<int, Person>& m) {
     //0号部门 A B C      1号部门 D E F
     cout << "策划部门" << endl;
     multimap<int, Person>::iterator pos = m.find(CEHUA);
     int count = m.count(CEHUA);   //统计具体人数
     int index = 0;      //索引值
     for (; pos != m.end() && index < count; pos++, index++) {
         cout << "姓名:" << pos->second.m_Name << "\t工资:" << pos->second.m_Wage << endl;
     }
 ​
     cout << "---------------" << endl;
     cout << "美术部门" << endl;
     pos = m.find(MEISHU);
     count = m.count(MEISHU);   //统计具体人数
     index = 0;
     for (; pos != m.end() && index < count; pos++, index++) {
         cout << "姓名:" << pos->second.m_Name << "\t工资:" << pos->second.m_Wage << endl;
     }
 ​
     cout << "---------------" << endl;
     cout << "研发部门" << endl;
     pos = m.find(YANFA);
     count = m.count(YANFA);   //统计具体人数
     index = 0;
     for (; pos != m.end() && index < count; pos++, index++) {
         cout << "姓名:" << pos->second.m_Name << "\t工资:" << pos->second.m_Wage << endl;
     }
 }
 ​
 int main() {
     //随机数种子
     srand((unsigned int)time(NULL));
 ​
     //1、创建员工
     vector<Person>v;
     createPerson(v);
     //测试
     /*for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
         cout << "姓名:" << (*it).m_Name << "\t工资:"<< (*it).m_Wage << endl;
     }*/
 ​
     //2、员工分组
     multimap<int, Person>m;
     setGroup(v,m);
 ​
     //3、分组显示员工
     showGroup(m);
     return 0;
 }

函数对象

函数对象

函数对象概念

概念:

  • 重载函数调用操作符的类,其对象常称为函数对象

  • 函数对象使用重载的()时,行为类似函数调用,也叫仿函数

本质:

函数对象(仿函数)是一个,不是一个函数

函数对象使用

特点:

  • 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值

  • 函数对象超出普通函数的概念,函数对象可以有自己的状态

  • 函数对象可以作为参数传递

 #include<iostream>
 using namespace std;
 ​
 class MyAdd {
 public:
     int operator()(int v1, int v2){
         return v1 + v2;
     }
 };
 ​
 //1、函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
 void test() {
     MyAdd myAdd;
     cout << myAdd(10,10) << endl;
 }
 ​
 //2、函数对象超出普通函数的概念,函数对象可以有自己的状态
 class MyPrint {
 public:
     MyPrint() {
         this->count = 0;
     }
     void operator()(string test) {
         cout << test << endl;
         this->count++;
     }
     int count;   //内部自己的状态
 };
 ​
 void test12() {
     MyPrint myPrint;
     myPrint("hello world");
     myPrint("hello world");
     myPrint("hello world");
     cout << "myPrint的调用次数" << myPrint.count << endl;
 }
 ​
 //3、函数对象可以作为参数传递
 void doPrint(MyPrint &mp,string test) {
     mp(test);
 }
 void test13() {
     MyPrint myPrint;
     doPrint(myPrint, "Hello c++");
 }
 int main() {
     test();
     test12();
     test13();
     return 0;
 }

谓词

谓词概念

predicate

概念:

  • 返回bool类型的仿函数称为谓词

  • 如果operator()接受一个参数,那么叫做一元谓词

  • 如果operator()接受两个参数,那么叫做二元谓词

一元谓词

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 //一元谓词
 class GreaterFive {
 public:
     bool operator()(int val) {
         return val > 5;
     }
 };
 void test2() {
     vector<int>v;
     for (int i = 0; i < 10; i++) {
         v.push_back(i);
     }
 ​
     //查找容器中 没有没大于5的数字
     //GreaterFive()匿名函数对象,不想给类取名可以这样,或者直接放类名
     vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
     if (it == v.end()) {
         cout << "未找到" << endl;
     }
     else {
         cout << "找到了大于5的数为:" << *it << endl;
 ​
     }
 }
 int main() {
     test2();
     return 0;
 }

二次谓词

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 class MyCompare {
 public:
     bool operator()(int val1, int val2) {
         return val1 > val2;
     }
 };
 void test3() {
     vector<int>v;
     v.push_back(10);
     v.push_back(40);
     v.push_back(20);
     v.push_back(30);
     v.push_back(50);
 ​
     sort(v.begin(), v.end());
     for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
         cout << *it << endl;
     }
     cout << endl;
 ​
     //使用函数对象,改变算法策略,变为排序规则为从大到小
     sort(v.begin(), v.end(), MyCompare());
     cout << "---------------------" << endl;
     for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
         cout << *it << endl;
     }
     cout << endl;
 }
 ​
 int main() {
     test3();
     return 0;
 }

内建函数对象

内建函数对象意义

概念

  • STL内建了一些函数对象

分类

  • 算术仿函数

  • 关系仿函数

  • 逻辑仿函数

用法

  • 这些仿函数所产生的对象,用法和一般函数完全相同

  • 使用内建函数对象,需要引入头文件include <functional>

算术仿函数

功能描述

  • 实现四则运算

  • 其中negate是一元运算,其他都是二元运算

仿函数原型

  • template<class T> T puls<T> //加法仿函数

  • template<class T> T minus<T> //减法仿函数

  • template<class T> T multiplies<T> //乘法法仿函数

  • template<class T> T divides<T> //除法仿函数

  • template<class T> T modulus<T> //取模仿函数

  • template<class T> T negate<T> //取反仿函数

 #include<iostream>
 using namespace std;
 #include <functional>   //内建函数对象头文件
 ​
 //negate  一元仿函数 取反仿函数
 void test4() {
     negate<int>n;
     cout << n(50) << endl;
 }
 ​
 //plus 二元仿函数 加法
 void test42() {
     plus<int>p;
     cout << p(10, 20) << endl;
 }
 ​
 int main() {
     test4();
     test42();
     return 0;
 }

关系仿函数

描述:实现关系对比

仿函数原理:

  • template<class T> bool equal_to<T> //等于

  • template<class T> bool not_equal<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> //小于等于

 #include<iostream>
 using namespace std;
 #include <vector>
 #include<algorithm>
 #include <functional>   //内建函数对象头文件
 ​
 class myCompare{
 public:
     bool operator()(int v1, int v2)const {
         return v1 > v2;
     }
 };
 ​
 void test5() {
     vector<int>v;
     v.push_back(10);
     v.push_back(20);
     v.push_back(30);
     v.push_back(40);
     v.push_back(50);
 ​
     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>());
     for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
         cout << *it << " ";
     }
     cout << endl;
 }
 ​
 int main() {
     test5();
     return 0;
 }

逻辑仿函数

函数原型

  • template<class T> bool logical_and<T> //逻辑与

  • template<class T> bool logical_or<T> //逻辑或

  • template<class T> bool logical_not<T> //逻辑非

 #include<iostream>
 using namespace std;
 #include <vector>
 #include<algorithm>
 #include <functional>   //内建函数对象头文件
 ​
 void test6() {
     vector<bool>v;
     v.push_back(true);
     v.push_back(false);
     v.push_back(true);
     v.push_back(false);
 ​
     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() {
     test6();
     return 0;
 }

STL算法

概述

  • 算法主要是由头文件<algorithm> <functional> <numeric> 组成

  • <algorithm> 是所有STL头文件中最大的的一个,范围涉及到比较、交换、查找、遍历操作、复制、修改等等

  • <numeric> 体积很小,只包括几个在序列上面进行简单数学运算的模板函数

  • <functional>定义了一些模板类,用以声明函数对象

常用遍历算法

学习目标:

  • 掌握常用的遍历算法

算法简介:

  • for_each //遍历容器

  • transform //搬运容器到另一个容器

for_each

描述:实现遍历容器,是最常用的算法,要熟练

函数原型:

  • for_each(iterator beg,iterator end,_func);

    //遍历算法 遍历容器元素

    //beg开始迭代器

    //end结束迭代器

    //-func函数或者函数对象

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 void print1(int val) {
     cout << val << " ";
 }
 ​
 //仿函数
 class print12 {
 public:
     void operator()(int val) {
         cout << val << endl;
     }
 };
 void test1() {
     vector<int>v;
     for (int i = 0; i < 10; i++) {
         v.push_back(i);
     }
 ​
     //普通函数放函数名
     for_each(v.begin(), v.end(), print1);
     cout << endl;
 ​
     //仿函数放进函数对象
     for_each(v.begin(), v.end(), print12());
     cout << endl;
 }
 ​
 int main() {
     test1();
     return 0;
 }

transform

功能描述:搬运容器到另一个容器中

函数原型

  • transform(iterator beg1,iterator end1,iterator beg2,_func);

    //beg1 源容器开始迭代器

    //end1 源容器结束迭代器

    //beg2 目标容器开始迭代器

    //_func 函数或者函数对象

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 class Transform {
 public:
     int operator()(int v) {
         return v+100;
     }
 };
 ​
 class MyPrint {
 public:
     void operator()(int val) {
         cout << val << " ";
     }
 };
 void test21() {
     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;
 }
 ​
 int main() {
     test21();
     return 0;
 }

总结:搬运的目标必须要提前开辟空间,否则无法正常搬运

常用查找算法

算法简介

  • find //查找元素

  • find_if //按条件查找元素

  • adjacent_find //查找相邻重复元素

  • binary_search //二分查找法

  • count //统计元素个数

  • count_if //按条件统计个数

find

功能描述

  • 查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()

函数原型

  • find(iterator beg,iterator )

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 //查找 内置数据类型
 void test31() {
     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;
     }
 }
 ​
 class Person {
 public:
     Person(string name, int age) {
         this->m_Name = name;
         this->m_Age = age;
     }
 ​
     //重载== 底层find知道如何对比person数据类型
     bool operator==(const Person & p) {
         if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
             return true;
         }
         else {
             return false;
         }
     }
     string m_Name;
     int m_Age;
 };
 ​
 //自定义数据类型
 void test32() {
     vector<Person>v;
     Person p1("aaa", 10);
     Person p2("bbb", 20);
     Person p3("ccc", 30);
     Person p4("ddd", 40);
 ​
     v.push_back(p1);
     v.push_back(p2);
     v.push_back(p3);
     v.push_back(p4);
 ​
     Person pp("bbb", 20);
 ​
     vector<Person>::iterator it = find(v.begin(), v.end(), pp);
     if (it == v.end()) {
         cout << "没有找到" << endl;
     }
     else {
         cout << "找到元素 姓名:" <<it->m_Name <<"年龄:" <<it->m_Age << endl;
     }
 }
 ​
 int main() {
     test31();
     test32();
     return 0;
 }

find_if

描述:按条件查找元素

函数原型:

  • find_if(iterator beg,iterator end,_Pred);

    //按值查找元素,找到返回值指定位置迭代器,找不到返回结束迭代器位置

    //beg开始迭代器

    //end结束迭代器

    //_Pred函数或者谓词(返回bool类型的仿函数)

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 class GreaterFive {
 public:
     bool operator()(int val) {
         return val > 5;
     }
 };
 //查找内置数据类型
 void test4() {
     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;
     }
 }
 ​
 //查找内置数据类型
 class Person4 {
 public:
     public:
     Person4(string name, int age) {
         this->m_Name = name;
         this->m_Age = age;
     }
     string m_Name;
     int m_Age;
 };
 ​
 class Greater20 {
 public:
     bool operator()(Person4 &p) {
         return p.m_Age > 20;
     }
 };
 ​
 void test42() {
     vector<Person4>v;
 ​
     Person4 p1("aaa", 10);
     Person4 p2("bbb", 20);
     Person4 p3("ccc", 30);
     Person4 p4("ddd", 40);
     Person4 p5("eee", 50);
 ​
     v.push_back(p1);
     v.push_back(p2);
     v.push_back(p3);
     v.push_back(p4);
     v.push_back(p5);
 ​
     //找年龄大于20的人
     vector<Person4>::iterator it = find_if(v.begin(), v.end(), Greater20());
     if (it == v.end()) {
         cout << "没有找到" << endl;
     }
     else {
         for (; it != v.end(); it++) {
             cout << "找到元素 姓名:" << it->m_Name << "年龄:" << it->m_Age << endl;
 ​
         }
     }
 }
 int main() {
     test4();
     test42();
     return 0;
 }

adjacent_find

查找相邻重复元素(用的少)

函数原型:

  • adjacent_find(iterator beg,iterator end,_Pred);

    //查找相邻重复元素,返回相邻元素的第一个位置的迭代器

    //beg开始迭代器

    //end结束迭代器

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

void test5() {
	vector<int>v;
	v.push_back(0);
	v.push_back(2);
	v.push_back(0);
	v.push_back(3);
	v.push_back(1);
	v.push_back(4);
	v.push_back(3);
	v.push_back(3);

	vector<int>::iterator pos = adjacent_find(v.begin(), v.end());
	if (pos == v.end()) {
		cout << "没有相邻且重复元素" << endl;
	}
	else {
		cout << "找到相邻且重复元素,数字为:" << *pos << endl;
	}
}

int main() {
	test5();
	return 0;
}

binary_search

查找指定元素是否存在(用指数的方式查,所以速度很快)

函数原型:

  • bool binary_search(iterator beg,iterator end,value);

    //查找指定的元素,查找返回true,否则false

    //注意:在无序序列中不可用

    //beg开始迭代器

    //end结束迭代器

    //value查找到元素

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 void test6() {
     vector<int>v;
     for (int i = 0; i < 10; i++) {
         v.push_back(i);
     }
     //v.push_back(2);     如果是无需序列,结果未知
     //查找容器中是否有9元素
     //注意,容器必须有序的序列
     bool ret = binary_search(v.begin(), v.end(),9);
 ​
     if (ret) {
         cout << "找到元素" << endl;
     }
     else {
         cout << "未找到元素:" << endl;
     }
 }
 ​
 int main() {
     test6();
     return 0;
 }

总结:二分查找法效率很高,值得注意的是查找的容器中元素必须是有序序列

count

统计元素个数

函数原型:

  • count(iterator beg,iterator end,value);

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 //统计内置数据类型
 void test7() {
     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的元素个数为:" << num << endl;
 }
 ​
 //统计自定义数据类型
 class Person7 {
 public:
 public:
     Person7(string name, int age) {
         this->m_Name = name;
         this->m_Age = age;
     }
     //重载== 底层find知道如何对比person数据类型
     bool operator==(const Person7& p) {
         if (this->m_Age == p.m_Age) {
             return true;
         }
         else {
             return false;
         }
     }
     string m_Name;
     int m_Age;
 };
 ​
 void test72() {
     vector<Person7>v;
 ​
     Person7 p1("aaa", 50);
     Person7 p2("bbb", 20);
     Person7 p3("ccc", 30);
     Person7 p4("ddd", 20);
     Person7 p5("eee", 30);
 ​
     v.push_back(p1);
     v.push_back(p2);
     v.push_back(p3);
     v.push_back(p4);
     v.push_back(p5);
     Person7 p("fff", 50);
     int num = count(v.begin(), v.end(), p);
 ​
     cout << "和p同岁数的人数有" << num << endl;
 }
 ​
 int main() {
     test7();
     test72();
     return 0;
 }

count_if

按照条件统计

函数原型:

  • count_if(iterator beg,iterator end,_Pred);

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 //统计内置数据类型
 class Greater208 {
 public:
     bool operator()(int val) {
         return val > 20;
     }
 };
 void test8() {
     vector<int>v;
     v.push_back(10);
     v.push_back(40);
     v.push_back(30);
     v.push_back(20);
     v.push_back(40);
     v.push_back(20);
 ​
     int num = count_if(v.begin(), v.end(), Greater208());
 ​
     cout << "大于20的元素个数为:" << num << endl;
 }
 ​
 //统计自定义数据类型
 class Person8 {
 public:
     Person8(string name, int age) {
         this->m_Name = name;
         this->m_Age = age;
     }
     string m_Name;
     int m_Age;
 };
 ​
 class AgeGreater20 {
 public:
     bool operator()(const Person8 & p) {
         return p.m_Age > 20;
     }
 };
 ​
 void test82() {
     vector<Person8>v;
 ​
     Person8 p1("aaa", 50);
     Person8 p2("bbb", 20);
     Person8 p3("ccc", 30);
     Person8 p4("ddd", 20);
     Person8 p5("eee", 30);
 ​
     v.push_back(p1);
     v.push_back(p2);
     v.push_back(p3);
     v.push_back(p4);
     v.push_back(p5);
 ​
     //找年龄大于20的人数
     int num = count_if(v.begin(), v.end(), AgeGreater20());
     if (num == 0) {
         cout << "没有年龄大于20的人" << endl;
     }
     else {
         cout << "年龄大于20的人数有:" << num << endl;
     }
 }
 ​
 int main() {
     test8();
     test82();
     return 0;
 }

常用排序算法

算法简介

  • sort //对容器内元素

  • random_shuffle //洗牌 指定范围内的元素随机调整次序

  • merge //容器元素合并,并存储到另一个容器中

  • reverse //反转指定范围的元素

sort

最常用的排序算法

函数原型

  • sort(iterator beg,iterator end,_Pred);

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 void myPrint(int val) {
     cout << val << endl;
 }
 void test1() {
     vector<int>v;
 ​
     v.push_back(10);
     v.push_back(30);
     v.push_back(50);
     v.push_back(20);
     v.push_back(40);
 ​
     //利用sort排序
     sort(v.begin(), v.end());
     for_each(v.begin(), v.end(), myPrint);
     cout << endl;
 ​
 ​
     //改变为降序
     sort(v.begin(), v.end(), greater<int>());
     for_each(v.begin(), v.end(), myPrint);
     cout << endl;
 }
 ​
 int main() {
     test1();
     return 0;
 }

random_shuffle

描述:洗牌:指定范围内元素随机调整次序

函数原型:

  • random_shuffle(iterator beg,iterator end);

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

void myPrint2(int val) {
	cout << val << " ";
}
void test2() {
	srand((unsigned int)time(NULL));
	vector<int>v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}
	//利用洗牌 算法 打乱排序
	random_shuffle(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint2);
	cout << endl;
}

int main() {
	test2();
	return 0;
}

merge

描述:两个容器元素合并,并储存到另一个容器中

函数原型:

  • merge(iterator beg1,iterator end1,iterator beg2,iterator end2 ,iterator dest);

    //注意两个容器必须是有序

    //并且顺序要一致的

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

void myPrint3(int val) {
	cout << val << " ";
}

void test3() {
	vector<int> v1;
	vector<int> v2;

	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
		v2.push_back(i+1);
	}

	//目标容器
	vector<int>vTarget;

	//提前给目标容器分配空间
	vTarget.resize(v1.size() + v2.size());
	merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());

	for_each(vTarget.begin(), vTarget.end(), myPrint3);
	cout << endl;
}


int main() {
	test3();
	return 0;
}

reverse

将容器进行反转

函数原型

  • reverse(iterator beg,iterator end);

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

void myPrint4(int val) {
	cout << val << " ";
}
void test4() {
	vector<int> v;

	v.push_back(10);
	v.push_back(30);
	v.push_back(50);
	v.push_back(20);
	v.push_back(40);

	cout << "反转前:" << endl;
	for_each(v.begin(), v.end(), myPrint4);
	cout << endl;

	cout << "反转后:" << endl;
	reverse(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint4);
}

int main() {
	test4();
	return 0;
}

常用拷贝和替换算法

算法简介:

  • copy //容器内指定范围的元素拷贝到另一容器中

  • replace //将容器内指定范围的旧元素修改为新元素

  • replace_if //容器内指定范围满足条件喜欢成新元素

  • swap //互换两个容器元素

copy

容器内指定范围元素拷贝到另一个容器中

函数原型

  • copy(iterator beg,iterator end,iterator dest);

    //dest目标容器

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

void myPrint5(int val) {
	cout << val << " ";
}
void test5() {
	vector<int>v1;
	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
	}

	vector<int>v2;
	v2.resize(v1.size());
	copy(v1.begin(), v1.end(), v2.begin());

	for_each(v2.begin(), v2.end(), myPrint5);
	cout << endl;
}

int main() {
	test5();
	return 0;
}

replace

将容器内指定范围的旧元素改为新元素

函数原型:

  • replace(iterator beg,iterator end,oldvalue,newvalue);

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

void myPrint6(int val) {
	cout << val << " ";
}
void test6() {
	vector<int>v;
	v.push_back(10);
	v.push_back(30);
	v.push_back(50);
	v.push_back(20);
	v.push_back(40);
	v.push_back(10);
	v.push_back(20);
	
	cout << "替换前" << endl;
	for_each(v.begin(), v.end(), myPrint6);
	cout << endl;

	cout << "替换后" << endl;
	replace(v.begin(), v.end(), 20, 2000);
	for_each(v.begin(), v.end(), myPrint6);
	cout << endl;

}

int main() {
	test6();
	return 0;
}

replace_if

将区间内满足条件的元素,替换成指定元素

函数原型:

  • replace_if(iterator beg,iterator end,_pred,newvalue);

    //_pred 谓词

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 class myPrint7 {
 public:
     void operator()(int val) {
         cout << val << " ";
     }
 };
 ​
 class Greater30 {
 public:
     bool operator()(int val) {
         return val >= 30 && val < 60;
     }
 };
 void test7() {
     vector<int>v;
     v.push_back(10);
     v.push_back(20);
     v.push_back(30);
     v.push_back(40);
     v.push_back(50);
     v.push_back(60);
 ​
     cout << "替换前" << endl;
     for_each(v.begin(), v.end(), myPrint7());
     cout << endl;
 ​
     //将大于等于30 小于60 替换为3000
     //如果是自定义函数,要重载=
     cout << "替换后" << endl;
     replace_if(v.begin(), v.end(), Greater30(), 3000);
     for_each(v.begin(), v.end(), myPrint7());
     cout << endl;
 }
 ​
 int main() {
     test7();
     return 0;
 }

swap

互换两个容器的元素,注意交换的容器要同种类型

函数元素:

  • swap(container c1,container c2);

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 class myPrint8 {
 public:
     void operator()(int val) {
         cout << val << " ";
     }
 };
 void test8() {
     vector<int>v1;
     for (int i = 0; i < 10; i++) {
         v1.push_back(i);
     }
 ​
     vector<int>v2;
     for (int i = 100; i < 110; i++) {
         v2.push_back(i);
     }
 ​
     cout << "互换前" << endl;
     for_each(v1.begin(), v1.end(), myPrint8());
     cout << endl;
     for_each(v2.begin(), v2.end(), myPrint8());
     cout << endl;
 ​
     swap(v1, v2);
     cout << "互换后" << endl;
     for_each(v1.begin(), v1.end(), myPrint8());
     cout << endl;
     for_each(v2.begin(), v2.end(), myPrint8());
     cout << endl;
 }
 ​
 int main() {
     test8();
     return 0;
 }

常用算术生成算法

注意:算术生成属于小型算法,使用时包含的头文件为#include <numeric>

算法简介:

  • accumulate //计算容器元素总和

  • fill //向容器中添加元素

accumulate

计算区间内 容器元素累计总和

注意:使用时注意头文件#include<numeric>

函数原型:

  • accumulate(iterator beg,iterator end,vlaue)

 #include<iostream>
 using namespace std;
 #include<vector>
 ​
 #include<numeric>
 ​
 void test9() {
     vector<int>v1;
     for (int i = 0; i <= 100; i++) {
         v1.push_back(i);
     }
 ​
     //参数3是 起始的累加值  不需要就写0
     int total=accumulate(v1.begin(), v1.end(), 0);
     cout << "total=" << total << endl;
 }
 ​
 int main() {
     test9();
     return 0;
 }

fill

向容器中填充指定的元素

函数原型:

  • fill(iterator beg,iterator end,vlaue)

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<numeric>

class myPrint10 {
public:
	void operator()(int val) {
		cout << val << " ";
	}
};
void test10() {
	vector<int>v;
	v.resize(10);

	//后期重新填充
	fill(v.begin(), v.end(), 100);
	for_each(v.begin(), v.end(), myPrint10());
}
int main() {
	test10();
	return 0;
}

常用集合算法

算法简介:

  • set_intersection //求两个容器的交集

  • set_union //求两个容器的并集

  • set_difference //求两个容器的差集

set_intersection

求两个容器交集

函数原型:

  • set_intersection (iterator beg1,iterator end1,iterator beg2,iterator end2 ,iterator dest);

注意:

  • 求交集的两个集合必须是有序序列

  • 目标容器开辟空间需要从两个容器中取最小值

  • set_intersection 返回值既是交集中最后一个交集位置

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 class myPrint11 {
 public:
     void operator()(int val) {
         cout << val << " ";
     }
 };
 void test11() {
     vector<int>v1;
     vector<int>v2;
     for (int i = 0; i < 10; i++) {
         v1.push_back(i);
     }
     for (int i = 5; i < 15; i++) {
         v2.push_back(i);
     }
 ​
     //目标容器
     vector<int>vTarget;
     //开辟空间
     vTarget.resize(min(v1.size() , v2.size()));
     //获取交集
     vector<int>::iterator itEnd=set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
     for_each(vTarget.begin(), itEnd, myPrint11());
     cout << endl;
 }
 ​
 int main() {
     test11();
     return 0;
 }

set_union

求两个集合的并集

函数原型:

  • set_union (iterator beg1,iterator end1,iterator beg2,iterator end2 ,iterator dest);

注意:

  • 两个集合必须是两个有序序列

  • 目标容器开辟空间需要两个容器相加

  • set_union返回值既是交集中最后一个交集位置

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 class myPrint12 {
 public:
     void operator()(int val) {
         cout << val << " ";
     }
 };
 void test12() {
     vector<int>v1;
     vector<int>v2;
     for (int i = 0; i < 10; i++) {
         v1.push_back(i);
     }
     for (int i = 5; i < 15; i++) {
         v2.push_back(i);
     }
 ​
     //目标容器
     vector<int>vTarget;
     //开辟空间
     vTarget.resize(v1.size()+ v2.size());
     //获取并集
     vector<int>::iterator itEnd = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
     for_each(vTarget.begin(), itEnd, myPrint12());
     cout << endl;
 }
 ​
 int main() {
     test12();
     return 0;
 }

set_difference

求两个集合的差集

函数原型:

  • set_difference (iterator beg1,iterator end1,iterator beg2,iterator end2 ,iterator dest);

 #include<iostream>
 using namespace std;
 #include<vector>
 #include<algorithm>
 ​
 class myPrint13 {
 public:
     void operator()(int val) {
         cout << val << " ";
     }
 };
 void test131() {
     vector<int>v1;
     vector<int>v2;
     for (int i = 0; i < 10; i++) {
         v1.push_back(i);
         v2.push_back(i+5);
     }
 ​
     //目标容器
     vector<int>vTarget;
     //开辟空间
     vTarget.resize(max(v1.size(),v2.size()));
     //取交集
     cout << "v1和v2的差集为:" << endl;
     vector<int>::iterator itEnd = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
     for_each(vTarget.begin(), itEnd, myPrint13());
     cout << endl;
 ​
     cout << "v2和v1的差集为:" << endl;
     itEnd = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), vTarget.begin());
     for_each(vTarget.begin(), itEnd, myPrint13());
     cout << endl;
 }
 ​
 int main() {
     test131();
     return 0;
 }

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值