黑马教程深入学习C++(持续更新中)

黑马教程深入学习C++

阶段 内容 目标 案例
第一阶段 C++基础语法入门 对C++有初步了解,能够有基础的编程能力 通讯录管理系统
第二阶段 C++核心编程 介绍C++面向对象编程,为大型项目做铺垫 职工管理系统
第三阶段 C++提高编程 介绍C++泛型编程思想,以及STL的基本使用 演讲比赛系统
  • 综合大案例:机房预约系统

C++基础入门

1 C++初识

1.1 编写代码

#include

using std::cout;
using std::endl;

int main() 
{
	cout << "Hello,World!" << endl;
	system("pause");
	return 0;
}

1.2 注释

作用:在代码中加一些说明和解释,方便自己或其他程序员阅读代码

两种格式;

  1. 单行注释://描述信息
  2. 多行注释:/* 描述信息 */
  • 提示:编译器在编译代码时,会忽略注释的内容

1.3 变量和常量

作用:给一段指定的内存空间取名,方便操作这段代码

语法:数据类型 变量名 = 初始值

示例:

#include

using std::cout;
using std::cin;
using std::endl;

int main()
{
	int a = 10;
	
	cout << "a = " << endl;
	
	return 0;
}

1.4 常量

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

C++定义常量两种方式

  1. #define 宏变量:#define 常量名 常量值
    • 通常在文件上方定义,表示一个常量
  2. const修饰的变量:const 数据类型 常量名 = 常量值
    • 通常在变量定义前加关键字const,修饰该变量是常量,不可修改

示例:

#include

using std::cout;
using std::endl;

#define day 7

int main()
{
	const int month = 12;
	cout << "一年总共有" << month << "月" << endl;
	cout << "这周总共有" << day << "天" << endl;

	return 0;
}

1.5 关键字

作用:关键字是C++中预先保留的单词(标识符)

  • 在定义变量或者常量时候,不要使用关键字

1.6 标识符命名规则

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

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

2 数据类型

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

2.1 整型

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

数据类型 占用空间
short(短整型) 2字节
int(整型) 4字节
long(长整型) Windows下为4字节,Linux为4字节(32位),8字节(64位)
long long(长长整型) 8字节

2.2 sizeof关键字

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

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

示例:

#include

using std::cout;
using std::endl;

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

	return 0;
}

2.3实型(浮点型)

作用:用于表示小数

浮点型变量分为两种:

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

区别:表示的有效数字范围不同。

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

示例:

#include

using std::cout;
using std::endl;

int main()
{
   float f1 = 3.1415926f;
   cout <<"f1="<< f1 << endl;
   double d1 = 3.1415926;
   cout <<"d1="<< d1 << endl;

   cout <<"float 占用内存空间为:" <#include

using std::cout;
using std::endl;

int main()
{
	char ch = 'a';
	cout << ch << endl;
	cout << "char字符型变量所占:" << sizeof(char) << endl;
	//字符型变量对应ASCII编码
	cout << (int)ch << endl;


	return 0;
}

2.5 转义字符

作用:用于表示一些不能显示出来的ASCII编码

常用的转义字符有:\n \\ \t

2.6字符串型

作用:用于表示一串字符

两种风格

  1. C风格字符串:char 变量名[] = "字符串值"
  2. C++风格字符串:string str = "hello world"

示例:

#include
#include

using std::cout;
using std::string;
using std::endl;

int main()
{
	//C风格字符串
	char str1[] = "sfsdgsd";
	cout << str1<

2.7 布尔类型

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

bool类型只有两个值:

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

bool类型占一个字节大小

示例:

#include
#include

using std::cout;
using std::string;
using std::endl;

int main()
{
	bool flag = true;
	cout << flag << endl;

	flag = false;
	cout << flag << endl;

	cout << "size of bool = " << sizeof(bool) << endl;

	return 0;
}

2.8 数据的输入

作用:用于从键盘获取数据

关键字:cin

语法:cin>>变量

3 运算符

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

3.1 算术运算符

作用:用于处理四则运算

运算符 示例
%(取模运算) 10%3 1
++(前置递增) a=2;b=++a; a=3;b=3;
++(后置递增) a=2;b=a++; a=3;b=2;
–(前置递减) a=2;b=–a; a=1;b=1;
–(后置递减) a=2;b=a–; a=1;b=2;

3.2 赋值运算符

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

运算符 示例 结果
+= a=0;a+=2; a=2;
*= a=2;a*=2; a=4;
%= a=3;a%2; a=1;

3.3 比较运算符

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

运算符 示例 结果
== 4==3 0
!= 4!=3 1
>= 4>=3 1

3.4 逻辑运算符

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

运算符 示例 结果
a=0;a+=2; 如果a为假,则a为真;如果a为真,则a为假
&& a&&b 如果a和b都为真,则结果为真;则结果为真,否则为假
|| a||b 如果a和b有一个为真,则结果为真;两者都为假,则为假

4 程序流程结构

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

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

4.1 选择结构

4.1.1 if语句

作用:执行满足条件的语句

三种形式:

  • 单行格式if语句
  • 多行格式if语句
  • 多条件的if语句

示例:

#include
#include

using std::cout;
using std::cin;
using std::string;
using std::endl;

int main()
{
	cout << "请输入你的考试分数:" << endl;
	int score;
	cin >> score;

	if (score>=700)
	{
		cout << "恭喜你考上了清华北大!" << endl;
	}
	else if (score >= 600&& score < 700)
	{
		cout << "恭喜你考上了重大!" << endl;
	}
	else if (score >= 500 && score < 600)
	{
		cout << "恭喜你考上了重师!" << endl;
	}
	else if (score >= 400 && score < 500)
	{
		cout << "恭喜你考上了川美!" << endl;
	}
	else if (score >= 300 && score < 400)
	{
		cout << "恭喜你考不起了!" << endl;
	}
	else
	{
		cout << "恭喜你可以重开了!" << endl;
	}
	return 0;
}

判断三只小猪的体重

#include
#include

using std::cout;
using std::cin;
using std::string;
using std::endl;

int main()
{
	int num1, num2, num3;
	cout << "请输入三只小猪的体重:" << endl;
	cin >> num1 >> num2 >> num3;

	if (num1 > num2)//A比B重
	{
		if (num1 > num3)//A比C重
		{
			cout << "小猪A最重!" << endl;
		}
		else//C比A重
		{
			cout << "小猪C最重!" << endl;
		}
	}
	else //B比A重
	{
		if (num2 > num3)//B比C重	
		{
			cout << "小猪B最重!" << endl;
		}
		else
		{
			cout << "小猪C最重!" << endl;
		}
	}
	return 0;
}
4.1.2 三目运算符

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

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

  • 如果表达式1为真,则执行表达式2

  • 如果表达式1为假,则执行表达式3

示例:

#include
#include

using std::cout;
using std::cin;
using std::string;
using std::endl;

int main()
{
	int a=30,b=20,c=0;

	c = a > b ? a : b;
	cout << "c=" << c << endl;

	(a > b ? a : b) == 0;
	cout << "修改后的a=" << a << endl;
	
	return 0;
}
4.1.3 switch语句

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

#include
#include

using std::cout;
using std::cin;
using std::string;
using std::endl;

int main()
{
	int score;
	cout << "请输入电影的分数:" ;
	cin >> score;
	switch (score)
	{
	case 10:cout << "满分电影" << endl; break;
	case 9:cout << "经典电影" << endl; break;
	case 8:cout << "经典电影" << endl; break;
	case 7:cout << "一般电影" << endl; break;
	case 6:cout << "一般电影" << endl; break;
	case 5:cout << "垃圾电影" << endl; break;
	case 4:cout << "垃圾电影" << endl; break;
	case 3:cout << "垃圾电影" << endl; break;
	case 2:cout << "垃圾电影" << endl; break;
	case 1:cout << "垃圾电影" << endl; break;
	case 0:cout << "垃圾电影" << endl; break;
	default:break;
	}
	return 0;
}

4.2 循环结构

4.2.1 while循环

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

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

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

示例:

#include
#include

using std::cout;
using std::cin;
using std::string;
using std::endl;

int main()
{
	int number=0;
	cout << "输出0~9:" << endl;
	while (number<10)
	{
		cout << number << endl;
		number++;
	}
	
	return 0;
}
4.2.2 do…while循环语句

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

语法:do{ 循环语句 } while(循环语句);

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

#include
#include

using std::cout;
using std::cin;
using std::string;
using std::endl;

int main()
{
	int number=0;
	do
	{
		cout << number << endl;
		number++;
	} while (number<10);
	
	return 0;
}

水仙花数

#include
#include

using std::cout;
using std::cin;
using std::string;
using std::endl;

int main()
{
	int number=100;
	do
	{
		int a = 0;
		int b = 0;
		int c = 0;

		a = number % 10;
		b = number / 10 % 10;
		c = number / 100;

		if (a*a*a+b*b*b+c*c*c==number)
		{
			cout << number << endl;
		}
		number++;
	} while (number>=100&&number<1000);
	
	return 0;
}
4.2.3 for循环语句

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

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

4.2.4 嵌套循环

作用:在循环体中再嵌套一层循环,解决一些实际问题,比如打印金字塔。

九九乘法表:

#include
#include

using std::cout;
using std::cin;
using std::string;
using std::endl;

int main()
{
	for (int i = 1; i <=9; i++)
	{
		for (int j = 1; j <=i; j++)
		{
			cout << j << "*" << i << "=" << i * j<<" ";
		}
		cout << endl;

	}
	return 0;
}

4.3跳转语句

4.3.1 break语句

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

使用时机:

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

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

4.3.3 goto语句

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

语法:goto 标记;

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

示例:

#include  
using std::cout;
using std::endl;

int main() {

	cout << "1" << endl;

	goto FLAG;

	cout << "2" << endl;
	cout << "3" << endl;
	cout << "4" << endl;

FLAG:
	cout << "5" << endl;
}

5 数组

5.1 概述

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

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

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

5.2 一维数组

5.2.1一维数组定义方式

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

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

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

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

  • 总结1:数组名的命名规范与变量名命名规范一致,不要和变量重名
  • 总结2:数组中下标是从0开始索引
5.2.2一维数组的数组名

一位数组的名称和用途:

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

案例:数组元素逆置

#include  
using std::cout;
using std::endl;

int main() 
{
	int arr[5] = {1,3,4,2,5};
	cout << "数组元素是:" << endl;
	for (int i = 0; i < 5; i++)
	{
		cout << arr[i]<
5.2.3 冒泡排序

作用:最常用的排序算法,对数组内元素进行排序

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个。

  2. 对每一个相邻元素做同样的工作,执行完毕后,找到第一个最大值。

  3. 重复以上的步骤,每次比较次数-1,直到不需要比较。

    示例:将数组 {4,2,8,0,5,7,1,3,9}进行升序排序

#include  
using std::cout;
using std::endl;

int main() 
{
	int arr[9] = { 4,2,8,0,5,7,1,3,9 };
	cout << "排序前的:";
	for (int i = 0; i < 9-1; i++)
	{
		cout < arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
	cout << "排序后的:";
	for (int i = 0; i < 9 - 1; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

5.3 二维数组

二维数组就是在一维数组的基础上多加一个维度。

5.3.1 二维数组定义方式

四种方式:

  1. 数据类型 数组名[行数][列数]
  2. 数据类型 数组名[行数][列数]={{数据1,数据2},{数据3,数据4}};
  3. 数据类型 数组名[行数][列数]={数据1,数据2,数据3,数据4}
  4. 数据类型 数组名[][列数]={数据1,数据2,数据3,数据4}
  • 第二种更加直观,提高代码的可读性。
5.3.2 二维数组数组名
  • 查看二维数组所占内存空间
  • 获取二维数组首地址
5.3.3 二维数组的应用案例

考试成绩统计:

#include  
using std::cout;
using std::endl;

int main() 
{
	int score[3][3] = {100,100,100,90,50,100,60,70,80};
	int sum[3] = { 0 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			sum[i] += score[i][j];
		}
		cout << "第"<

6 函数

6.1 概述

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

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

6.2 函数的定义

函数的定义:

  1. 返回值类型
  2. 函数名
  3. 参数表列
  4. 函数体语句
  5. return 表达式

语法:

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

案例:实现加法函数

#include  
using std::cout;
using std::endl;
using std::cin;

int add(int n1,int n2) 
{
	return n1 + n2;
}

int main() 
{
	int n1, n2,sum;
	cout << "请输入两个加数:" << endl;
	cin >> n1 >> n2;
	sum = add(n1, n2);
	cout<<"和为:" << sum<

6.3 函数的调用

功能:使用定义好的函数

语法:函数名(参数)

6.4 值传递

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

示例:

#include  
using std::cout;
using std::endl;
using std::cin;

void swap(int n1,int n2) 
{
	cout << "交换前的数字为:" << n1 << " " << n2 << endl;

	int temp = n1;
	n1 = n2;
	n2 = temp;

	cout << "交换前的数字为:" << n1 << " " << n2 << endl;
	
	return;
}

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

	cout << "a=" << a<#include  
using std::cout;
using std::endl;
using std::cin;

void test01()
{
	cout <<"This is test01"<#include  
using std::cout;
using std::endl;
using std::cin;

int max(int a, int b);


int main() 
{
	int a = 10;
	int b = 20;
	cout< n2 ? n1 : n2;
}

6.7 函数的分文件编写

作用:让代码结构清晰

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

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

示例:

  • 头文件:
#pragma once

#include 

using std::cout;
using std::endl;

void swap(int a, int b);
  • 源文件
#include"函数的分文件编写.h"

void swap(int a,int b) 
{
	int temp = a;
	a = b;
	b = temp;

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
}


int main()
{
	int a = 10;
	int b = 20;
	swap(a,b);
	return 0;
}

7 指针

7.1 指针的基本概念

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

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

7.2 指针变量的定义和使用

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

#include  
using std::cout;
using std::endl;
using std::cin;

int main() 
{
	int a = 10;
	//指针定义的语法:数据类型 *指针变量名;

	int* p;
	//让指针记录变量a的地址
	p = &a; 

	cout << "a的地址为:" << &a << endl;
	cout << "指针p为:" << p << endl;
	//使用指针
	//可以通过解引用的方式来找到指针指向的内存
	//指针前加*表示解引用,找到指针指向的内存中的数据

	*p = 1000;

	cout << "a=" << a << endl;
	cout << "*p=" << *p << endl;
}

7.3 指针所占内存空间

提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?

  • 在32位操作系统下,指针占4个字节空间大小,不管是什么数据类型
  • 在64位操作系统下,指针占8个字节空间大小

7.4 空指针和野指针

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

用途:初始化指针变量

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

示例1:空指针

#include  
using std::cout;
using std::endl;
using std::cin;

int main() 
{
	int* p = NULL;
	cout << *p << endl;//会报错,空指针指向的内存是不可以访问的
}

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

示例2:野指针

#include  
using std::cout;
using std::endl;
using std::cin;

int main() 
{
	int* p = (int *)0x1100;
	//在程序中,尽量避免出现野指针
	cout << *p << endl;
}

7.5 const修饰指针

const修饰指针有三种情况:

  1. const修饰指针 —常量指针
  2. const修饰常量—指针常量
  3. const既修饰指针,又修饰常量

示例:

#include  
using std::cout;
using std::endl;
using std::cin;

int main() 
{
	
	int a = 10;
	int b = 20;
	//1、const修饰指针 常量指针
	const int* p1 = &a;
	//指针指向的值不可以改,指针的指向可以改
	//*p = 20; //错误
	p1 = &b;//正确

	//2、const修饰常量 指针常量
	int* const p2 = &a;
	*p2 = 100;
	//p2 = &b; //错误
	//指针的指向不可以改,指针指向的值可以改
	
	//3、const修饰指针和常量 
	const int * const p3 = &a;
	//指针的指向 和指针指向的值 都不可以改
	//*p3 = 100; //错误
	//p3 = &b; //错误 
}

7.6 指针和数组

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

示例:

#include  
using std::cout;
using std::endl;
using std::cin;

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;
	}
}

7.7 指针和函数

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

#include  
using std::cout;
using std::endl;
using std::cin;

void swap1(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;
}

void swap2(int *p1,int *p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

int main() 
{
	int a = 10;
	int b = 20;
	swap1(a,b);// 值传递
	cout << "a= " << a << endl;
	cout << "b= " << b << endl;
	swap2(&a,&b);// 地址传递
	cout << "a= " << a << endl;
	cout << "b= " << b << endl;
}

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

7.8 指针、数组、函数

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

例如数组:int arr[10]={4,3,6,9,1,2,10,8,7,5};

示例:

#include  
using std::cout;
using std::endl;
using std::cin;

void bubbleSort(int * arr ,int len)
{
	for (int i = 0; i < len; i++)
	{
		for (int j = 0; j < len-1-i; j++)
		{
			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)
{
	cout << "排序后的数组为:" << endl;
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << endl;
	}
}
int main() 
{
	int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
	int len = sizeof(arr) / sizeof(arr[0]);
	bubbleSort(arr, len);
	printArray(arr, len);
}

8 结构体

8.1 结构体的概念

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

8.2 结构体定义和使用

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

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

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

示例:

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

struct Student
{
	string name;
	int age;
	int score;
}s3;
int main() 
{
	// 2.1 struct Student s1;创建变量时关键字可以省略
	Student s1;	
	s1.name = "张三";
	s1.age = 19;
	s1.score = 100;
	cout <<"学生姓名为:" << s1.name << " 年龄为: " << s1.age <<" 分数为:" << s1.score << endl;
	// 2.2 struct Student s2={....};
	Student s2 {"李四",20,90 };
	cout << "学生姓名为:" << s2.name << " 年龄为: " << s2.age << " 分数为:" << s2.score << endl;
	// 2.3 定义结构体时顺便创建结构体变量
	s3.name = "王五";
	s3.age = 19;
	s3.score = 95;
	cout << "学生姓名为:" << s3.name << " 年龄为: " << s3.age << " 分数为:" << s3.score << endl;
}
  • 总结1:定义结构体的关键字struct,不可省略
  • 总结2:创建结构体变量时,关键字struct可以省略
  • 总结3:结构体变量利用操作符"."访问成员

8.3 结构体数组

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

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

示例:

	#include  
	using std::cout;
	using std::endl;
	using std::cin;
	using std::string;

	struct Student
	{
		string name;
		int age;
		int score;
	};
	int main() 
	{
		Student stuArray[3] =
		{
			{"张三",18,100},
			{"李四",19,90},
			{"王五",20,95}
		};
	
		stuArray[2].name = "赵六";
		stuArray[2].age = 25;
		stuArray[2].score = 85;

		for (int i = 0; i < 3; i++)
		{
			cout << "学生:" << stuArray[i].name << ",年龄为:" << stuArray[i].age << ",成绩为:" << stuArray[i].score << endl;
		}
	}

8.4 结构体指针

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

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

示例:

	#include  
	using std::cout;
	using std::endl;
	using std::cin;
	using std::string;

	struct Student
	{
		string name;
		int age;
		int score;
	};
	int main() 
	{
		Student s = {"张三",19,100};

		Student* p = &s;
		cout << "学生:" << p->name << ",年龄:" << p->age << ",分数:"<score<
  • 总结:结构体指针可以通过->操作符 来访问结构体中的成员

8.5 结构体嵌套结构体

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

例如:每个老师辅导一个成员,一个老师的结构体中,记录一个学生的结构体

示例:

	#include  
	using std::cout;
	using std::endl;
	using std::cin;
	using std::string;

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

	struct teacher
	{
		int id;
		string name;
		int age;
		struct student stu;
	};
	int main() 
	{
		teacher t;
		t.id = 121235;
		t.name = "老王";
		t.age = 45;
		t.stu.name = "张三";
		t.stu.age = 18;
		t.stu.score = 100;

		cout << "老师姓名:" << t.name << " 老师工号:" << t.id << " 老师年龄:" << t.age << endl 
			 << "学生姓名:" << t.stu.name << " 学生年龄:" << t.stu.age << " 学生成绩:" << t.stu.score << endl;
	}
  • 总结:在结构体中可以定义另一个结构体作为成员,用来解决实际问题

8.6 结构体做函数参数

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

传值方式有两种:

  • 值传递
  • 地址传递

示例:

	#include  
	using std::cout;
	using std::endl;
	using std::cin;
	using std::string;

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

	void printStudent1(student s)
	{
		s.name = "李四";
		cout << "子函数打印学生:" <name = "王五";
		cout << "子函数打印学生:" << p->name << endl;
		cout << "年龄:" << p->age << endl;
		cout << "成绩:" << p->score << endl;
	}


	int main() 
	{ 
		student s;
		s.name = "张三";
		s.age = 19;
		s.score = 95;
		printStudent1(s);
		printStudent2(&s);
		cout << "main函数中打印学生:" << s.name << ",年龄:" << s.age << ",成绩:" << s.score << endl;
	}
  • 总结:如果不想修改主函数中的数据,用值传递,反之用地址传递。

8.7 结构体const使用场景

作用:用const来防止误操作

示例:

	#include  
	using std::cout;
	using std::endl;
	using std::cin;
	using std::string;

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

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

	int main() 
	{ 
		student s{"张三",15,95};
		printStudent(&s);
		cout << "main函数中打印学生:" << s.name << ",年龄:" << s.age << ",成绩:" << s.score << endl;
	}

8.8 结构体的案例

8.8.1 案例1

案例描述:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bGzcicV3-1679388470128)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1668065891487.png)]

	#include  
	using std::cout;
	using std::endl;
	using std::cin;
	using std::string;

	struct student
	{
		string sName;
		int score;
	};

	struct teacher
	{
		string tName;
		student stu[5];
	};

	void allocateSpace(teacher tch[] , int len)
	{
		string nameSeed = "ABCDE";
		for (int i = 0; i < len; i++)
		{
			tch[i].tName = "teacher_" ;
			tch[i].tName += nameSeed[i];
			for (int j = 0; j < 5; j++)
			{
				tch[i].stu[j].sName = "student_" ;
				tch[i].stu[j].sName += +nameSeed[j];
				tch[i].stu[j].score = 60;
			}
		}
	}
	void printInfo(teacher tch[],int len)
	{
		for (int i = 0; i < len; i++)
		{
			cout <<"老师:" << tch[i].tName << endl;
			for (int j = 0; j < 5; j++)
			{
				cout <<"学生:" << tch[i].stu[j].sName << " 成绩:" << tch[i].stu[j].score << endl;
			}
			cout << endl;
		}
	}

	int main() 
	{ 
		teacher tch[3];
		int len = sizeof(tch) / sizeof(tch[0]);
		allocateSpace(tch, len);
		printInfo(tch,len);
	}
8.8.2 案例

案例描述:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gBSNoqFi-1679388470129)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1668066163673.png)]

	#include  
	using std::cout;
	using std::endl;
	using std::cin;
	using std::string;

	struct Hero
	{
		string name;
		int age;
		string sex;
	};

	void bubbleSort(Hero he[],int len)
	{
		for (int i = 0; i < len - 1; i++)
		{
			for (int j = 0; j < len - 1 - i; j++)
			{
				if (he[j].age > he[j + 1].age)
				{
					Hero temp = he[j];
					he[j] = he[j + 1];
					he[j + 1] = temp;
				}
			}
		}
	}

	void printArray(Hero he[],int len)
	{
		for (int i = 0; i < len; i++)
		{
			cout <<"英雄:" << he[i].name <<" 年龄:" << he[i].age <<" 性别:" << he[i].sex << endl;
		}
	
	}

	int main() 
	{ 
		Hero he[5]
		{
			{"刘备",23,"男"},
			{"关羽",22,"男"},
			{"张飞",20,"男"},
			{"赵云",21,"男"},
			{"貂蝉",19,"女"}
		};
		int len = sizeof(he) / sizeof(he[0]);
		bubbleSort(he,len);
		printArray(he, len);
	}

通讯录管理系统

1、系统需求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ujKDVe5f-1679388470129)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1668131537806.png)]

2、菜单功能

功能描述: 用户选择功能的界面

  1. 菜单界面效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TUUigz5E-1679388470130)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1668132773899.png)]

  1. 步骤:
  • 封装函数显示该界面,如:void showMenu();
  • 在main函数中调用封装好的函数

代码:

//菜单界面
void showMenu()
{
	string be= "*************************" ;
	string behind= "***** " ;
	string end= " *****" ;
	string str[6] = {"添加","显示","删除","查找","修改","清空"};
	cout << be << endl;
	for (int i = 0; i < 6; i++)
	{
		cout <

3、退出功能

功能描述:退出通讯录系统

思路:根据用户的不同选择,进入不同的功能,可以选择switch分支结构,将整个架构搭建起来

当用户选择0时,执行退出,选择其他先不要操作,也不会退出程序

代码:

int main()
{
	int select = 0;
	while (true)
	{
		showMenu();

		cin >> select;

		switch (select)
		{
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		case 5:
			break;
		case 6:
			break;
		case 0://退出通讯录
			cout << "欢迎下次使用!" << endl;
			system("pause");
			return 0;
			break;
			
		default:
			cout << "没有该选项,请重新输入:" << endl;
		}
	}

4、添加联系人

功能描述:

实现添加联系人功能,联系人上限为1000人,联系人信息包括(姓名、性别、年龄、联系电话、家庭住址)

添加联系人的实现步骤:

  • 设计联系人结构体
  • 设置通讯录结构体
  • main函数中创建通讯录
  • 封装添加联系人函数
  • 测试添加联系人功能

4.1 设计联系人结构体

联系人信息包括:姓名、性别、年龄、联系电话、家庭住址

设计如下:

struct Person
{
	string m_Name;//姓名
	string m_Sex;//性别
	int m_Age;//年龄
	string m_Phone;//电话
	string m_Adress;//地址
};

4.2 设计通讯录结构体

设计的时候可以在通讯录结构体中,维护一个容量为1000的存放联系人的数组,并记录当前通讯录中联系人数量

设计如下:

#define MAX 1000//最大人数

struct Adressbooks
{
	Person personArray[MAX];//通讯录中保存的联系人数组
	int m_Size;//通讯录中人员个数
};

4.3 main中创建通讯录

添加联系人函数封装好后,在main函数中创建一个通讯录变量,这个就是我们需要一直维护的通讯录

	// 创建通讯录结构体变量
	Addressbooks abs;
	//初始化通讯录中当前人员个数
	abs.m_Size = 0;

4.4 封装添加联系人函数

思路:添加联系人之前首先判断通讯录是否已满,如果满了就不再添加,未满则将新联系人信息逐个加入到通讯录

添加联系人代码:

void addPerson(Addressbooks* abs)
{
	//判断通讯录是否已满,如果满了就不再添加
	if (abs->m_Size>MAX)
	{
		cout << "通讯录已满,无法添加!" << endl;
		return;
	}
	else
	{
		//添加具体的联系人

		//姓名
		cout << "请输入姓名:" << endl;
		string name;
		cin >> name;
		abs->personArray[abs->m_Size].m_Name = name;

		//性别
		cout << "请输入性别:" << endl;
		string sex;
		cin >> sex;
		abs->personArray[abs->m_Size].m_Sex = sex;

		//年龄
		cout << "请输入年龄:" << endl;
		int age = 0;
		cin >> age;
		abs->personArray[abs->m_Size].m_Age = age;

		//电话
		cout << "请输入电话:" << endl;
		string phone;
		cin >> phone;
		abs->personArray[abs->m_Size].m_Phone = phone;

		//住址 
		cout << "请输入地址:" << endl;
		string address;
		cin >> address;
		abs->personArray[abs->m_Size].m_Addr = address;

		abs->m_Size++;
		cout << "添加成功!" << endl;
		system("pause");//请按任意键继续
		system("cls");//清屏操作
	}
}

5、显示联系人

功能描述:显示通讯录中已有的联系人信息

显示联系人实现步骤:

  • 封装显示联系人函数
  • 测试显示联系人功能

5.1 封装显示联系人函数

思路:判断如果当前通讯录中没有信息,就提示为空,人数大于0,显示通讯录中人员信息

显示联系人代码:

//2、显示联系人
void showPerson(Addressbooks* abs)
{
	if (abs->m_Size==0)
	{
		cout << "当前记录为空" << endl;
	}
	else
	{
		for (int i = 0; i < abs->m_Size; i++)
		{
			cout << "姓名为:" << abs->personArray[i].m_Name << "\t";
			cout << "性别为:" << abs->personArray[i].m_Sex << "\t";
			cout << "年龄为:" << abs->personArray[i].m_Age << "\t";
			cout << "联系电话为:" << abs->personArray[i].m_Phone << "\t";
			cout << "家庭住址为:" << abs->personArray[i].m_Addr<< endl;
		}
		system("pause");
		system("cls");
	}
}

6、删除联系人

功能描述:按照姓名指定删除指定联系人

删除联系人实现步骤:

  • 封装检测联系人是否存在
  • 封装删除联系人函数
  • 测试删除联系人功能

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

设计思路:

删除联系人前,我们需要先判断用户输入的联系人是否存在,如果存在删除,不存在提示用户没有要删除的联系人

因此把检测联系人是否存在封装成一个函数中,如果存在,返回联系人在通讯录中的位置,不存在则返回-1

代码:

//检测联系人是否存在,如果存在,返回联系人所在数组中的具体位置,不存在返回-1
int isExist(Addressbooks* abs ,string name)
{
	for (int i = 0; i < abs->m_Size; i++)
	{
		//找到用户输入的姓名了
		if (abs->personArray[i].m_Name==name)
		{
			return i;//找到就返回坐标
		}
	}
	return -1;//如果没找到就返回-1;
}

6.2 封装删除联系人函数

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

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

查不到则提示查无此人

//3、删除指定联系人
void deletePerson(Addressbooks* abs)
{
	cout << "请输入你要删除的联系人的名字:" << endl;

	string name;
	cin >> name;
	//ret==-1表示未查到
	int ret = isExist(abs,name);

	if (ret!=-1)
	{
		for (int i = ret; i < abs->m_Size; i++)
		{
			abs->personArray[i] = abs->personArray[i + 1];
		}
		abs->m_Size--;
	}
	else
	{
		cout << "查无此人!" << endl;
	}
	system("pause");
	system("cls");
}

7、查找联系人

功能描述:按照姓名查看指定联系人信息

查找联系人步骤:

  • 封装查找联系人函数
  • 测试查找指定联系人

7.1 封装查找联系人函数

实现思路:判断用户指定的联系人是否存在,如果存在显示信息,不存在则提示查无此人

查找联系人代码:

//4、查找指定联系人
void findPerson(Addressbooks* abs)
{
	cout << "请输入需要查找人的名字:" << endl;
	string name;
	cin >> name;
	//ret==-1表示未查到
	int ret = isExist(abs, name);

	if (ret != -1)
	{
		cout << "姓名:" << abs->personArray[ret].m_Name << "\t";
		cout << "性别:" << abs->personArray[ret].m_Sex<< "\t";
		cout << "年龄:" << abs->personArray[ret].m_Age << "\t";
		cout << "电话:" << abs->personArray[ret].m_Phone << "\t";
		cout << "地址:" << abs->personArray[ret].m_Addr << endl;
	}
	else
	{ 
		cout << "查无此人!" << endl;
	}
	system("pause");
	system("cls");
}

8、修改联系人

功能描述:

按照姓名重新修改指定联系人

修改联系人步骤:

  • 封装修改联系人函数
  • 测试修改联系人功能

8.1 封装修改联系人函数

实现思路:查找用户输入的联系人,如果查找成功进行修改操作,查找失败提示查无此人

修改联系人代码:

//5、修改联系人信息
void modifyPerson(Addressbooks* abs)
{
	cout << "请输入您要修改的联系人:" << endl;
	string name;
	cin >> name;

	int ret = isExist(abs,name);

	if (ret!=-1)
	{
		//姓名
		cout << "请输入姓名:" << endl;
		string name;
		cin >> name;
		abs->personArray[abs->m_Size].m_Name = name;

		//性别
		cout << "请输入性别:" << endl;
		string sex;
		cin >> sex;
		abs->personArray[abs->m_Size].m_Sex = sex;

		//年龄
		cout << "请输入年龄:" << endl;
		int age = 0;
		cin >> age;
		abs->personArray[abs->m_Size].m_Age = age;

		//电话
		cout << "请输入电话:" << endl;
		string phone;
		cin >> phone;
		abs->personArray[abs->m_Size].m_Phone = phone;

		//住址 
		cout << "请输入地址:" << endl;
		string address;
		cin >> address;
		abs->personArray[abs->m_Size].m_Addr = address;
	}
	else
	{
		cout << "查无此人!" << endl;
	}
	system("pause");
	system("cls");
}

9、清空联系人

功能描述:清空通讯录中所有信息

清空联系人实现步骤:

  • 封装清空联系人函数
  • 测试清空联系人功能

9.1 封装清空联系人函数

实现思路:将通讯录所有联系人信息清除掉,只要将通讯录中记录的联系人数量置为0,做逻辑清空即可

清空联系人代码:

//6、清空联系人信息
void cleanPerson(Addressbooks* abs)
{
	abs->m_Size = 0;
	cout << "通讯录已经清空!" << endl;
	system("pause");
	system("cls");
}

C++核心编程

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

1 内存分区模型

C++程序在执行时,将内存大致分成了4个区域:

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

内存四区的意义:

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

1.1 程序运行前

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

代码区:

存放CPU执行的机器指令

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

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

全局区:

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

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

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

1.2 程序运行后

栈区:

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

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

示例:

	#include  
	using std::cout;
	using std::endl;
	using std::cin;
	using std::string;

	int* func(int b)//形参也会放在栈区
	{
		b = 100;
		int a = 10;
		return &a;
	}

    int main()
    {
		int* p = func(1);
		cout << *p << endl;//第一次能打印正确的数字,是因为编译器做了保留
		cout << *p << endl;//第二次这个数据就不再保留了
        return 0;
    }

堆区:

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

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

示例:

	#include  
	using std::cout;
	using std::endl;
	using std::cin;
	using std::string;

	int* func()
	{
		int* a = new int(10);
		return a;
	}

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

		cout << *p << endl;
		cout << *p << endl;
        return 0;
    }

1.3 new操作符

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

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

语法:new 数据类型

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

示例1:

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

int* func()
{
	int* a = new int(10);
	return a;
}

void test0()
{
	int *arr=new int[10];
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;
	}
	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	delete[] arr;//释放数组的时候,要加[]才可以
}
int main()
{
	int* p = func();

	cout << *p << endl;
	cout << *p << endl;
	delete p; 
	//cout << *p << endl;// 内存已经被释放,再次访问就是非法操作,会报错
	return 0;
}

2 引用

2.1 引用的基本使用

作用:给变量起别名

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

示例:

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

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

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

	b = 100;

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	system("pause");

	return 0;
}

2.2 引用注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

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

	//int& c;//错误,引用必须初始化
	int& c = a;//一旦初始化后就不可以更改

	c = b;//赋值操作,不是更改引用

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	cout << "c=" << c << endl;

	system("pause");

	return 0;
}

2.3 引用做函数参数

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

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

示例:

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

//值传递
void mySwap01(int a,int b)
{
    int temp = a;
    a = b;
    b = temp;
}
//地址传递
void mySwap02(int *a,int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
//引用传递
void mySwap03(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

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

    mySwap01(a,b);
    cout << "a=" <#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

//返回局部变量引用//不要
int& test01()
{
	int a = 10;//局部变量存放在四区中的 栈区
	return a;
}
//返回静态变量引用
int& test02()
{
	static int a = 20;//静态变量,存放在全局区,全局区上的数据在程序结束后系统释放
	return a;
}

int main()
{
	//不能返回局部变量引用
	int& ret1 = test01();
	cout << "ret1=" <#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

//发现是引用,转换为 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);
}

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

2.6 常量引用

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

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

示例:

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

void showValue(const int &val)
{
	//val = 1000; 
	cout << "val=" << val << endl;
}

int main()
{
	//int a = 10;
	//int& ref = 10;//错误,引用必须引一块合法的内存空间
	//加上const后,编译器将代码修改 int temp = 10;const int & ref = temp; 
	//const int& ref = 10;
	//ref = 20;加入const后变为只读,不可修改

	int a = 100;
	showValue(a); 
	cout << "a=" << a << endl;
}

3 函数提高

3.1 函数默认参数

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

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

示例:

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

//如果我们自己传入数据,就用自己的数据,如果没有,那么用默认值
int func(int a,int b=10,int c=10)
{
	return a + b + c;
}

//注意事项:
// 1、如果某个位置已经有了默认参数值,那么从这个位置往后,从左到右都必须有默认值
//int func2(int a=10,int b,int c,int d)
//{
//	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<

3.2 函数占位参数

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

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

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

示例:

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

//函数占位参数,占位参数也可以有默认值
//void func(int a,int =10)
//{
//	cout << "This is func" << endl;
//}
//int main()
//{
//	func(10);
//}
void func(int a,int)
{
	cout << "This is func" << endl;
}
int main()
{
	func(10,20);//参数必须填补
}

3.3 函数重载

3.3.1 函数重载概述

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

函数重载满足条件:

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

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

示例:

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
void func()
{
	cout << "func的调用" << endl;
}
void func(int a)
{
	cout << "func的调用" << endl;
}
//错误,函数的返回值不能作为函数重载的条件
//int func()
//{
//	cout << "func的调用" << endl;
//}
int main()
{
	func();
	func(10);
}
3.3.2 函数重载注意事项
  • 引用作为重载条件
  • 函数重载碰到函数默认参数

示例:

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

//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=10)
{
	cout << "func2(int a,int b)调用" << endl;
}
void func2(int a)
{
	cout << "func2(int a)调用" << endl;
}

int main()
{
	int a = 10;
	func(a);//调用第一个

	func(10);//调用第二个

	//func2(10);//当函数重载碰到默认参数,出现二义性,报错,尽量避免这种情况
}

4 类和对象

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

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

例如:

  • 人可以作为对象,属性有姓名、年龄、身高、体重…行为有走、跑、跳、吃饭、唱歌…
  • 车也可以作为对象,属性有轮盘、方向盘、车灯…行为有载人、放音乐、开空调…
  • 具有相同性质的对象,我们也可以抽象为,人属于人类,车属于车类

4.1 封装

4.1.1 封装的意义

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

封装的意义:

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

封装意义一:

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

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

示例1:设计一个圆类,求圆的周长

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

const double PI = 3.14;
//圆求周长的公式:2*PI*半径
class Circle
{
	//访问权限
public:
	//属性
	//半径
	int m_r;
	//行为
	double calculateZC()
	{
		return 2*PI*m_r;
	}
};

int main()
{
	//通过圆类 创建具体的圆(对象)
	//实例化	(通过一个类 创建一个对象的过程)
	Circle c1;
	//给圆对象 的属性赋值
	c1.m_r = 10;  

	cout << "圆的周长为:" << c1.calculateZC() << endl;
}

示例2:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Student
{
public:

	string m_name;
	string m_id;

	void setName(string name)
	{
		m_name = name;
	}
	void setId(string id)
	{
		m_id = id;
	}
	void showStu()
	{
		cout << "学生姓名:" << m_name << " 学号:" << m_id << endl;
	}
};

int main()
{
	Student stu;
	stu.setName("张三");
	stu.setId("123456789");
	stu.showStu();
}

封装意义二:

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

访问权限有三种:

  1. public 公共权限 类内可以访问 类外可以访问
  2. protected 保护权限 类内可以访问 类外不可以访问 儿子可以访问父亲中的保护内容
  3. private 私有权限 类内可以访问 类外不可以访问 儿子不可以访问父亲中的私有内容

示例:

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	string m_Name;
protected:
	string m_Car;
private:
	int m_Password;

public:
	void func()
	{
		m_Name = "张三";
		m_Car = "拖拉机";
		m_Password = 123456;
	}
};
int main()
{
	Person p1;
	p1.m_Name = "李四";
	//p1.m_Car = "奔驰";//保护权限内容,在类外访问不到
	//p1.m_Password = 123456;//私有权限内容,在类外访问不到
}
4.1.2 structclass的区别

在C++中structclass惟一的区别就是在于默认的访问权限不同

区别:

  • struct默认权限为公认
  • class默认权限为私有
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

class C1
{
	int m_A;//默认为私有
};
struct C2
{
	int m_A;//默认为公有
};

int main()
{
	C1 c1;
	C2 c2;

	//c1.m_A = 100;//错误,默认为私有
	c2.m_A = 100;
}
4.1.3 成员属性设置为私有

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

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

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	void setName(string name)
	{
		m_Name = name;
	}
	string getName()
	{
		return m_Name;
	}
	void setAge(int age)
	{
		if (age < 0 || age>150)
			cout << "你这个老妖精!" << endl;
		m_Age = age;
	}
	int getAge()
	{
		m_Age = 10;
		return m_Age;
	}

	void setLover(string lover)
	{
		m_Lover = lover;
	}

private:
	string m_Name;
	int m_Age;
	string m_Lover;

};

int main()
{
	Person p;
	p.setName("张三");
	
	cout << "姓名为:" << p.getName() << endl<<"年龄为:" << p.getAge() << endl;
}
练习案例1:设计立方体类

设计立方体类(Cube)

求出立方体的面积和体积

分别用全局函数和成员函数判断两个立方体是否相等

#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Cube
{
public:
	void setL(int l)
	{
		m_L = l;
	}
	int getL()
	{
		return m_L;
	}

	void setW(int w)
	{
		m_W = w;
	}
	int getW()
	{
		return m_W;
	}

	void setH(int h)
	{
		m_H = h;
	}
	int getH()
	{
		return m_H;
	}

	//行为:获取面积和体积
	int caculateS()
	{
		return 2*m_L * m_W + 2*m_L*m_H+2*m_W*m_H;
	}
	int caculateV()
	{
		return m_L * m_W * m_H;
	}
	//成员函数判断
	bool isSameByClass(Cube &c)
	{
		if (m_L == c.getL() && m_W == c.getW() && m_H == c.getH())
		{
			return true;
		}
		return false;
	}
	//属性:长、宽、高
private:
	int m_L;
	int m_W;
	int m_H;
};

bool isSame(Cube &c1,Cube &c2)
{
	if (c1.getL()==c2.getL() && c1.getW() == c2.getW()&& c1.getH() == c2.getH())
	{
		return true;
	}
	return false;
	
}
int main()
{

	Cube c1;
	c1.setL(10);
	c1.setW(10);
	c1.setH(10);

	
	Cube c2;
	c2.setL(10);
	c2.setW(10);
	c2.setH(10);
	//利用全局函数判断
	bool ret = isSame(c1, c2);
	if (ret)
	{
		cout << "c1,c2相同!" << endl;
	}
	else 
	{
		cout << "c1,c2不相同!" << endl;
	}
	//利用成员函数判断
	ret = c1.isSameByClass(c2);
	if (ret!=0)
	{
		cout << "c1,c2相同!" << endl;
	}
	else
	{
		cout << "c1,c2不相同!" << endl;
	}	
}
练习案例2:点和圆的关系

设计一个圆形类(Circle),和一个点类(Point),计算点和圆的关系

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Circle
{
public:
	void setX(double x)
	{
		m_X = x;
	}
	void setY(double y)
	{
		m_Y = y;
	}

	int getX()
	{
		return m_X;
	}
	int getY()
	{
		return m_Y;
	}

	void setRadius(double radius)
	{
		m_Radius=radius;
	}
	int getRadius()
	{
		return m_Radius;
	}
	
private:
	//圆心位置
	double m_X;//横坐标
	double m_Y;//纵坐标
	double m_Radius;//半径
};
class Point
{
public:
	void setX(double x)
	{
		m_X = x;
	}
	void setY(double y)
	{
		m_Y = y;
	}

	int getX()
	{
		return m_X;
	}
	int getY()
	{
		return m_Y;
	}
private:
	//位置
	double m_X;//横坐标
	double m_Y;//纵坐标
};
double caculate(Circle &c,Point &p)
{
	double radius;
	radius = sqrt(pow(c.getX() - p.getX(),2) + pow(c.getY() - p.getY(),2));
	return radius;
}

int main()
{
	Circle c;
	c.setX(1);
	c.setY(2);
	c.setRadius(2);

	Point p;
	p.setX(1);
	p.setY(3);

	if (c.getRadius()>caculate(c, p))
	{
		cout << "点在圆内!" << endl;
	}
	else if(c.getRadius() == caculate(c, p))
	{
		cout << "点在圆上!" << endl;
	}
	else
	{
		cout << "点在圆外!" << endl;
	}
}

4.2 对象的初始化和清理

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

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

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

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

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

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

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

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

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

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

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,前面需要加符号~
  3. 析构函数没有有参数,因此不可以发生重载
  4. 程序在调用对象时候会自动调用析构,无须手动调用而且只会调用一次
#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	Person() 
	{
		cout << "调用构造函数" << endl;
	}
	~Person() 
	{
		cout << "调用析构函数" << endl;
	}

};

void test01() 
{
	Person p;
}
int main()
{
	test01();
}
4.2.2 构造函数的分类和调用

两种分类方式:

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

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

三种调用方式:

​ 括号法

​ 显示法

​ 隐式转换法

示例:

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	//构造函数
	Person() 
	{
		cout << "调用默认构造函数" << endl;
	}
	Person(int age) 
	{
		m_age = age;
		cout << "调用有参构造函数" << endl;
	}
	//拷贝构造函数
	Person(const Person &p)
	{
		//将传入人的所有属性,拷贝在我身上
		m_age = p.m_age;
		cout << "调用拷贝构造函数" << endl;
	}

	~Person() 
	{
		cout << "调用析构函数" << endl;
	}
	int m_age;
};
void test01()
{
	//1、括号法,比较简单直接
	Person p1;//默认构造函数
	Person p2(10);//有参构造函数
	Person p3(p2);//拷贝构造函数
	//注意事项
	//调用默认构造函数时,不要加()
	//因为编译器会认为是一个函数的声明,不会认为在创建对象

	//2、显示法
	Person p1;
	Person p2 = Person(10);
	Person p3 = Person(p2);

	Person(10);//匿名对象 特点:当前行执行结束后,系统会回收掉匿名对象

	//注意事项2
	//不要利用拷贝构造函数 初始化匿名对象 编译器会认为 Person(p3)==Person p3;对象声明
	Person(p3);
	//3、隐式转换法
	Person p4 = 10;//相当于写了Person p4 =Person(10);有参构造
	Person p5 = p4;//拷贝构造

}

int main()
{
	test01();
}
4.2.3 拷贝构造函数的调用时机

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

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传递
  • 以值方式返回对象

示例:

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	//构造函数
	Person() 
	{
		cout << "调用默认构造函数" << endl;
	}

	Person(int age)
	{
		cout << "调用有参构造函数" << endl;
		m_Age = age;
	}
	Person(const Person &p)
	{
		cout << "调用拷贝构造函数" << endl;
		m_Age = p.m_Age;
	}

	~Person() 
	{
		cout << "调用析构函数" << endl;
	}
	int m_Age;
};

//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
	Person p1(20);
	Person p2(p1);

	cout << "P2的年龄为:" << p2.m_Age << endl;
}
//2、值传递的方式给函数参数传值
void doWork(Person p)
{
}
void test02()
{
	Person p;
	doWork(p);
}
//3、值方式返回局部对象
Person doWork2()
{
	Person p1;
	return p1;//不会返回局部对象,外部调用会创建一个新的对象给外部
}
void test03()
{
	Person p2 = doWork2();
}
int main()
{
	test01();
	test02();
	test03();
}
4.2.4 构造函数的调用规则

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

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

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

深浅拷贝是面试经典问题,也是常见的一个坑

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

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

示例:

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	//无参(默认)构造函数
	Person() 
	{
		cout << "调用默认构造函数" << endl;
	}
	//有参构造函数
	Person(int age ,int height)
	{
		cout << "调用有参构造函数" << endl;
		m_Age = age;
		m_Height = new int(height);
	}

	Person(const Person& p)
	{
		cout << "调用拷贝构造函数" << endl;
		//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区的问题
		m_Age = p.m_Age;
		m_Height = new int(*p.m_Height);
	}

	~Person() 
	{
		//析构代码,将堆区开辟数据做释放操作
		cout << "调用析构函数" << endl;
		if (m_Height!= NULL)
		{
			delete m_Height;
			m_Height = NULL;//防止野指针
		}
	}

	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的年龄为:" << p2.m_Age << " 身高为:" << *p2.m_Height << endl;
}
int main()
{
	test01();
}
4.2.6 初始化列表

作用:

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

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

示例:

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	//1、传统初始化操作
	/*Person(int a, int b, int c)
	{
		m_A = a;
		m_B = b;
		m_C = c;
	}*/
	//2、初始化列表初始化属性
	//Person() :m_A(10), m_B(20),m_C(30)
	//{
	//}
	//3、初始化列表初始化属性
	Person(int a,int b,int c) :m_A(a), m_B(b),m_C(c)
	{
	}

	int m_A;
	int m_B;
	int m_C;
};
void test01()
{
	/*Person p(10,20,30);*/
	//Person p;
	Person p(1,2,3);
	cout << "m_A=" << p.m_A << endl;
	cout << "m_B=" << p.m_B << endl;
	cout << "m_C=" << p.m_C << endl;
}

int main()
{
	test01();
}
4.2.7 类对象作为类成员

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

例如:

class A{}
class B
{
	A a;
}

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

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;
//手机类
class Phone
{
public:
	Phone(string pName)
	{
		cout << "Phone的构造函数" << endl;
		m_PName = pName;
	}
	~Phone() 
	{
		cout << "Phone的析构函数" << endl;
	}
	string m_PName;
};
//人类	
class Person
{
public:
	Person(string name,string pName):m_Name(name),m_Phone(pName)
	{	
		cout << "Person的构造函数" << endl;
	}
	~Person()
	{
		cout << "Person的析构函数" << endl;
	}
	string m_Name;
	Phone m_Phone;
};
//当其他类对象作为本类成员,构造时先构造类对象,再构造自身,析构的顺序与构造相反

void test01()
{
	Person p("张三","华为");

	cout << p.m_Name << "拿着" << p.m_Phone.m_PName << endl;
}

int main()
{
	test01();
}
4.2.8 静态成员

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

静态成员分为:

  • 静态成员变量

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

    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量

示例1:静态成员变量

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;
class Person
{
public:
	static int m_A;//类内声明
	//静态成员有访问权限
private:
	static int m_B;//类内声明
};

int Person::m_A = 100;//类外初始化
int Person::m_B = 300;//类外初始化

void test01()
{
	Person p;
	//100
	cout<

示例2:静态成员函数

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	//静态成员函数
	static void func()
	{
		m_A = 100;//静态成员函数可以访问 静态成员变量  
		//m_B = 200;//静态成员函数 不可以访问 非静态成员变量,无法区分到底是哪个对象的m_B
		cout << "static void func的调用" << endl;
	}
	static int m_A;//静态成员变量
	int m_B;//非静态成员变量 

	//静态成员函数也是有访问权限的
private:
	static void func2()
	{
		cout << "static void func2" << endl;
	}

};
int Person::m_A = 100;
//两种访问方法
void test01()
{
	//1、通过对象访问
	Person p;
	p.func();
	cout << p.m_A << endl;
	//2、通过类名访问
	Person::func();
	//Person::func2();类外访问不到私有静态成员函数
}

int main()
{
	test01();
}

4.3 C++对象模型和this指针

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

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

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

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	int m_A;//非静态成员变量 属于类对象
	static int m_B;//静态成员变量 不属于类对象上

	void func() {}//非静态成员函数 不属于类对象上
	static void func2() {}//静态成员函数 不属于类对象上
};

void test01()
{
	Person p;
	//空对象占用内存空间为:1
	//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象站内存的位置
	//每个空对象也应该有一个独一无二的内存地址
	cout << "size of p= " << sizeof(p) << endl;
}
void test02()
{
	Person p;
	cout << "size of p= " << sizeof(p) << endl;
}
int main()
{
	//test01();
	test02();
}
4.3.2 this指针概念

通过4.3.1 我们知道C++成员变量和成员函数是分开存储的

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

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

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

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

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

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this
#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	Person(int age)
	{
		//this指针指向 被调用的成员函数 所属地对象
		this->age=age;
	}
	int age;//非静态成员变量 属于类对象

	Person& PersonAddAge(Person& p)//引用指向本体
	{
		this->age += p.age;
		//this指向p2的指针,而*this指向的就是p2这个对象本体
		return *this;
	}

};
//1 解决名称冲突
void test01()
{
	Person p1(18);
	cout << "年龄为:" << p1.age << endl;
}

//2 返回对象本身用*this
void test02()
{
	Person p1(10);
	Person p2(10);
	//链式编程思想
	p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p2);

	cout << "年龄为:" << p2.age<
4.3.3 空指针访问成员函数

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

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

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	void showClassName()
	{
		cout << "This is Person class" << endl;
	}
	void showPersonAge()
	{
		//报错原因是因为传入的指针是为NULL
		if (this == NULL)
		{
			return;//程序不会崩
		}
		cout << "age=" <m_Age<< endl;//默认m_Age前面有个this->
	}

	int m_Age;

};

void test01()
{
	Person* p=NULL;
	p->showClassName();
	p->showPersonAge();
}

int main()
{
	test01();

}
4.3.4 const修饰成员函数

常函数:

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

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;
//常函数
class Person
{
public:
	//this指针的本质	是指针常量	指针的指向是不可以修改的
	//const Person * const this;
	//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
	void showPerson() const
	{
		this->m_B = 100;
		//this->m_A = 100;
		//this =NULL;错误,this指针不可以修改指针的指向
	}
	void func()
	{
		m_A = 100;
	}
	int m_A;
	mutable int m_B;//特殊变量,即使在常函数中,也可以修改这个值
};


void test01()
{
	Person p;
	p.showPerson();
}
//常对象
void test02()
{
	const Person p;//在对象前加const,变为常对象
	//p.m_A = 100;
	p.m_B = 100;//m_B是特殊值,在常对象下也可以修改
	//p.func();//错误,常对象 不可以调用普通的成员函数,因为普通成员可以修改属性
	//常函数
}

int main()
{
	test01();
	test02();
}

4.4 友元

生活中你的家有客厅(Public),有你的卧室(Private)

客户所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去

但是,你也可以允许你的好闺蜜好基友进去

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

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

友元的关键字为friend

友元的三实现

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元
4.4.1 全局函数做友元
#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

//建筑类
class Building
{
	//goodGay全局函数是Building好朋友,可以访问Building中的私有成员
	friend void goodGay(Building& building);

public:
	Building()
	{
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	
	}
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室

};
void goodGay(Building &building)
{
	cout <<"好基友全局函数 正在访问:"<< building.m_SittingRoom << endl;
	cout <<"好基友全局函数 正在访问:"<< building.m_BedRoom << endl;
}
void test01()
{
	Building b;
	goodGay(b);
}

int main()
{
	test01();
}
4.4.2 类做友元
#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

//类做友元
class Building;
class GoodGay
{
public:
	GoodGay();
	void visit();//参观函数 访问Building中的属性

	Building * building;

};
class Building
{
	//告诉编译器goodGay类是Building类的好朋友,可以访问到Building中私有内容
	friend class GoodGay;
public:
	Building();
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};
//类外写成员函数
Building::Building()
{
	m_SittingRoom = "梦厅";
	m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
	//创建一个建筑物对象
	building = new Building;
}
void GoodGay::visit()
{
	cout << "好基友正在访问:" << building->m_SittingRoom<m_BedRoom<
4.4.3 成员函数做友元
#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Building;
class GoodGay
{
public:
	GoodGay();
	void visit();//让visit函数可以访问Building中私有成员
	void visit2();//让visit2函数不可以访问Building中私有成员

	Building * building;

};
class Building
{
	//告诉编译器 GoodGay类下得到visit成员函数作为本类的好朋友,可以访问私有成员
	friend void GoodGay::visit();
public:
	Building();
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};
//类外写成员函数
Building::Building()
{
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
	//创建一个建筑物对象
	building = new Building;
}
void GoodGay::visit()
{
	cout << "visit函数正在访问:" << building->m_SittingRoom<m_BedRoom<m_SittingRoom<m_BedRoom<

4.5 运算符重载

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

4.5.1 加号运算符重载

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NBa9mvce-1679388470131)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1668861869901.png)]

#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	//成员函数重载+号
	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;
};
//全局函数重载+号
//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 test01()
{
	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 + 20;//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()
{
	test01();
}
  • 总结1:对于内置的数据类型的表达式的运算符是不可能改变的
  • 总结2:不要滥用运算符重载
4.5.2 左移运算符重载

作用:可以输出自定义数据类型

#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::ostream;

class Person
{
	friend ostream& operator<<(ostream& cout, Person& p);//本质 operator<<(cout,p) 简化cout<

  • 总结:重载左移运算符配合友元可以实现输出自定义数据类型
4.5.3 递增运算符重载

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

示例1:加加运算符重载

#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::ostream;

//自定义整型
class MyInteger
{
	friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
	MyInteger()
	{
		m_Num = 0;
	}
	//重置前置++运算符 返回引用是为了一直对一个数据进行递增操作
	MyInteger& operator++()
	{
		//先进行++运算
		m_Num++;
		//再将自身进行返回
		return *this;
	}
	//重置后置++运算符	int占位参数,区分前置或后置递增
	MyInteger operator++(int)
	{ 
		//先 记录当时结果
		MyInteger temp = *this;
		//后 递增
		m_Num++;//本身的值加1
		//最后将记录结果返回
		return temp;//返回以前的值

	}
private:
	int m_Num;
};
//重载<<运算符
ostream & operator<<(ostream &cout,MyInteger myint)
{
	cout << myint.m_Num;
	return cout;
}
void test01()
{
	MyInteger myint;
	cout << ++myint << endl;
};
void test02()
{
	MyInteger myint;
	cout << myint++ << endl;
};

int main()
{
	test01();
	test02();
}

示例2:减减运算符重载

#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::ostream;

//自定义整型
class MyInteger 
{
	friend ostream& operator<<(ostream& cout, MyInteger myInt);
public:
	MyInteger()
	{
		m_Num = 1;
	}
private:
	int m_Num;
public:
	//重置前置--运算符
	MyInteger & operator--()
	{
		m_Num--;
		return *this;
	}
	//重置后置--运算符
	MyInteger operator--(int)
	{
		MyInteger temp=*this;
		m_Num--;
		return temp;
	}
};

//全局函数重载输出流操作符

ostream & operator<<(ostream &cout,MyInteger myInt)//有疑问,,,,
{
	cout << myInt.m_Num;
	return cout;
}
void test01()
{
	MyInteger myint;

	cout << myint--<
4.5.4 赋值运算符重载

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ELabyGT6-1679388470131)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1668928110646.png)]

示例:

#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	Person(int age)
	{
		m_Age = new int(age);
	}
	int *m_Age;
	~Person()
	{
		if (m_Age!=NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}
	//重载 赋值运算符
	Person & operator=(Person &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;
	}
};
void test01()
{
	Person p1(18);
	Person p2(20);
	Person 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()
{
	test01();
}
4.5.5 关系运算符重载

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

示例:

#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

class Person
{
public:
	Person(string name,int age)
	{
		m_Name = name;
		m_Age = age;
	}
	string m_Name;
	int m_Age;
	bool operator==(Person& p)
	{
		if (this->m_Name==p.m_Name&&this->m_Age==p.m_Age)
		{
			return true;
		}
		return false;	
	}
	bool operator!=(Person& p)
	{
		if (this->m_Name==p.m_Name&&this->m_Age==p.m_Age)
		{
			return false;
		}
		return true;	
	}
};

void test01()
{
	Person p1("张三", 18);
	Person p2("李四", 20);

	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()
{
	test01();
}
4.5.6 函数调用运算符重载
  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活

示例:

#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;

//打印输出类
class MyPrint
{
public:
	//重载函数调用运算符
	void operator()(string test)
	{
		cout << test << endl;
	}

};

void MyPrint02(string test)
{
	cout << test << endl;
}

void test01()
{
	MyPrint myPrint;

	myPrint("hello world");

	MyPrint02("hello world");
}
//仿函数非常灵活,没有固定的写法
//加法类
class MyAdd
{
public:
	int operator()(int num1,int num2)
	{
		return num1 + num2;
	}
};
void test02()
{
	MyAdd myadd;
	int ret=myadd(100,100);
	cout << "ret=" << ret << endl;
	//匿名函数对象
	cout << MyAdd()(100,100) << endl;
}
int main()
{
	test01(); 
	test02(); 
}

4.6 继承

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

有些类与类之间存在特殊的关系,例如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s1ixuuqa-1679388470132)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1668993815844.png)]

定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。

利用继承的技术,减少重复代码

4.6.1 继承的基本语法

普通实现:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;

//普通实现页面

//Java页面

class Java
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册···(公开头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图···(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++···(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "Java学科视频" << endl;
	}
};

//Python页面

class Python
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册···(公开头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图···(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++···(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};

//C++页面

class CPP
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册···(公开头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图···(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++···(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
	cout << "Java下载视频页面如下:" << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "---------------------------------------------" << endl;
	cout << "Python下载视频页面如下:" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "---------------------------------------------" << endl;
	cout << "C++下载视频页面如下:" << endl;
	CPP cpp;
	cpp.header();
	cpp.footer();
	cpp.left();
	cpp.content();

}

int main()
{
	test01();
}

继承实现:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;

//继承实现页面

//公共页面类
class BasePage
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册···(公开头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图···(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++···(公共分类列表)" << endl;
	}
};

//继承的好处:减少重复代码
//语法:class 子类	: 继承方式	父类
//子类:派生类
//父类:基类

//Java页面
class Java:public BasePage
{
public:
	void content()
	{
		cout << "Java学科视频" << endl;
	}

};

//Python页面
class Python:public BasePage
{
public:
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};

//C++页面
class CPP:public BasePage
{
public:
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
	cout << "Java下载视频页面如下:" << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "---------------------------------------------" << endl;
	cout << "Python下载视频页面如下:" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "---------------------------------------------" << endl;
	cout << "C++下载视频页面如下:" << endl;
	CPP cpp;
	cpp.header();
	cpp.footer();
	cpp.left();
	cpp.content();

}
int main()
{
	test01();
}

总结:

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

class A:public B;

A类称为子类 或派生类

B类称为父类 或基类

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

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

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

4.6.2 继承方式

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

继承方式一共有三种:

  • 公共继承
  • 保护继承
  • 私有继承

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NEuO0vff-1679388470132)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669037530324.png)]

示例:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

//继承方式

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

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

void test01()
{
	Son1 s1;
	s1.m_A = 100;
	//s1.m_B = 100;//到Son1中m_B是保护权限 类外访问不到
}
void test02()
{
	Son2 s2;
	//s2.m_A = 100;//到Son2中m_A是保护权限 类外访问不到
	//s1.m_B = 100;//到Son2中m_B是保护权限 类外访问不到
}
void test03()
{
	Son3 s3;
	//s2.m_A = 100;//到Son2中m_A是私有权限 类外访问不到
	//s1.m_B = 100;//到Son2中m_B是私有权限 类外访问不到
}

int main()
{
	test01();
	test02();
	test03();

}
4.6.3 继承中的对象模型

**问题:**从父类继承过来的成员,哪些属于子类成员

示例:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

//继承方式

//公共继承
class Base
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son :public Base
{
public:
	int m_D;
};
void test01()
{
	//16
	//父类中所有非静态成员属性都会被子类继承下去
	//父类中私有成员属性 是被编译器给隐藏了,因此访问不到,但是确实被继承下去了
	cout << "size of Son = " << sizeof(Son) << endl;
}

int main()
{
	test01();
}
4.6.4 继承中的构造和析构顺序

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

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

示例:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

//继承方式

//公共继承
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 test01()
{
	//Base b;
	//继承中的构造和析构顺序如下:
	//先构造父类,再构造子类,析构的顺序与构造的顺序相反
	Son s;
}

int main()
{
	test01();
}
  • 总结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
4.6.5 继承同名成员处理方式

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

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

示例:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

//继承方式

//公共继承
class Base
{
public:
	Base()
	{
		m_A = 100;
	}
	void func()
	{
		cout << "Base - func()调用" << endl;
	}
	void func(int a)
	{
		cout << "Son - func(int a)调用" << endl;
	}
	int m_A;

};

class Son :public Base
{
public:
	Son()
	{
		m_A = 200;
	}
	void func()
	{
		cout << "Son - func()调用" << endl;
	}

	int m_A;
};
//同名成员属性处理
void test01()
{
	Son s;
	cout << "Son 下 m_A = " << s.m_A << endl;
	//如果通过子类成员 访问到父类中同名成员,需要加作用域
	cout << "Base 下 m_A = " << s.Base::m_A << endl;
}
//同名成员函数处理
void test02()
{
	Son s;
	s.func();//直接调用 调用是子类中的同名成员
	s.Base::func();

	//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
	// 如果想访问到父类中被隐藏的同名成员函数,需要加作用域
	s.Base::func(100);
}

int main()
{
	//test01();
	test02();
}
  • 总结:
    1. 子类对象可以直接访问到子类中同名成员
    2. 子类对象加作用域可以访问到父类同名成员
    3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
4.6.6 继承同名静态成员处理方式

问题:

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

  • 访问子类同名对象 直接访问即可
  • 访问父类同名成员 需要加作用域
#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;


class Base
{
public:
	static int m_A;
	static void func()
	{
		cout << "Base - static void func()" << endl;
	}
	static void func(int a)
	{
		cout << "Base - static void func(int a)" << endl;
	}
};
int Base::m_A = 100;

class Son :public Base
{
public:
	static int m_A;
	static void func()
	{
		cout << "Son - static void func()" << endl;
	}
	//static void func(int a)
	//{
	//	cout << "Son - static void func(int a)" << endl;
	//}
};
int Son::m_A = 200;

//同名静态成员属性
void test01()
{
	//1、通过对象访问
	cout << "通过对象访问:" << endl;
	Son s;
	cout << "Son 下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;

	//2、通过类名访问
	cout << "通过类名访问:" << endl;
	cout << "Son 下 m_A = " << Son::m_A << endl;
	cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
//同名静态成员函数
void test02()
{
	//1、通过对象访问
	cout << "通过对象访问" << endl;
	Son s;
	s.func();
	s.Base::func();

	//2、通过类名访问
	cout << "通过类名访问" << endl;
	Son::func();
	Son::Base::func();

	//子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
	//如果想访问父类中被隐藏同名函数,需要加作用域
	Son::Base::func(100);
}
int main()
{
	//test01();
	test02();
}
  • 总结:同名静态成员处理方式和静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)
4.6.7 多继承语法

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

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

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

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

示例:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

//多继承语法
class Base1
{
public:
	Base1()
	{
		m_A = 100;
	}
	int m_A = 100;
};
class Base2
{
public:
	Base2()
	{
		m_A = 200;
	}
	int m_A;
};

//子类 需要继承Base1和Base2
//语法:class 子类 : 继承方式 父类1 ,继承方式 父类2...
class Son :public Base1, public Base2
{
public:
	Son()
	{
		m_C = 300;
		m_D = 400;
	}
	int m_C;
	int m_D;
};

void test01()
{
	Son s;
	cout << "sizeof Son = " << sizeof(s) << endl;
	//当父类中出现同名成员,需要加作用域区分
	cout << "m_A = " << s.Base1::m_A << endl;
	cout << "m_A = " << s.Base2::m_A << endl;
}
int main()
{
	test01();
}
  • 总结:多继承中如果父类中出现了同名情况,子类使用时候要加作用域
4.6.7 菱形继承

菱形继承的概念:

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

​ 又有一个类同时继承这两个派生类

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KfVehJH0-1679388470133)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669104967157.png)]

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性
  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以
#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

//动物类
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 test01()
{
	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()
{
	test01();
}

总结:

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

4.7 多态

4.7.1 多态的基本概念

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

多态分为两类

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

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

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

案例:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

//动物类
class Animal
{
public:
	//虚函数
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
public:
	//重写 函数返回值类型	函数名	参数列表	完全相同
	virtual void speak()//virtual 可以不写
	{
		cout << "小猫在说话" << endl;
	}
};
//狗类
class Dog :public Animal
{
public:
	virtual void speak()//virtual 可以不写
	{
		cout << "小狗在说话" << endl;
	}
};

//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定

//动态多态满足条件
//1、有继承关系
//2、子类要重写父类的虚函数

//动态多态使用
//父类的指针或者引用 执行子类对象

void doSpeak(Animal &animal)//父类引用接受子类对象
{
	animal.speak();
}

void test01()
{
	Cat cat;
	doSpeak(cat);

	Dog dog;
	doSpeak(dog);
}
int main()
{
	test01();
}

总结:

多态满足条件

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

多态使用条件

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

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

原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omKywtFA-1679388470133)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669195302158.png)]

4.7.2 多态案例——计算器类

案例描述:

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

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的拓展以及维护

示例:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

//分别利用普通写法和多态技术实现计算器

//普通写法
class Calculator
{
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;
		}
		else
		{
			return m_Num1 / m_Num2;
		}
		//如果想拓展新的功能,需要修改代码
		//在真实开发中 提倡 开闭原则
		//开闭原则:对拓展进行开发,对修改进行关闭
	}
	int m_Num1;//操作数1
	int m_Num2;//操作数2
};

void test01()
{
	//创建计算器对象
	Calculator c;
	c.m_Num1 = 20;
	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;
	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 MultiCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 * m_Num2;
	}
};
//除法计算器类
class DivCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 / m_Num2;
	}
};

void test02() 
{
	//多态使用条件
	//父类指针或者引用指向子类对象
	
	//加法运算
	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 MultiCalculator;//堆区释放,但是指向父类的指针类型没变,所以父类指针指向子类的对象
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	//用完后记得销毁
	delete abc;
	//除法运算
	abc = new DivCalculator;//堆区释放,但是指向父类的指针类型没变,所以父类指针指向子类的对象
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;
	cout << abc->m_Num1 << " / " << abc->m_Num2 << " = " << abc->getResult() << endl;
	//用完后记得销毁
	delete abc;

}
int main()
{
	//test01();
	test02();
}
  • 总结:C++开发提倡多态设计程序架构,因为多态优点很多
4.7.3 纯虚函数和抽象类

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

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

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

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

抽象类特点:

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

示例:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

class Base
{
public:
	//纯虚函数
	//只要有一个纯虚函数,这个类称为抽象类
	//抽象类特点:
	//1、无法实例化对象
	//2、抽象类的子类	必须要重写父类中的纯虚函数,否则也属于抽象类
	virtual void func() = 0;
};
class Son :public Base
{
public:
	virtual void func() 
	{
		cout << "func函数调用" << endl;
	};
};

void test01()
{
	//Base b;//抽象类无法实例化对象
	//new Base://抽象类无法实例化对象

	//Son s;//子类必须重写父类中的纯虚函数,否则无法实例化对象
	Base * base = new Son;
	base ->func();
}
int main()
{
	test01();
}
4.7.4 多态案例二——制作饮品

案例描述:

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

利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

    #include  
    #include  
    using std::cout;
    using std::endl;
    using std::cin;
    using std::string;
    using std::setw;

    //饮品类
    class AbstractDrinking
    {
    public:
        //煮水
        virtual void Boil() = 0;
        //冲泡
        virtual void Brew() = 0;
        //倒入杯中
        virtual void PourInCup() = 0;
        //加入辅料
        virtual void PutSomething() = 0;
        //制作饮品
        void MakeDrinking()
        {
            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 *abc)
    {
        abc->MakeDrinking();
        delete abc;
    }
    void test01()
    {
        cout << "煮咖啡" << endl;
        doWork(new Coffee);
        cout << "-------------"<
4.7.5 虚析构和纯虚析构

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

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

虚析构和纯虚析构共性:

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

虚析构和纯虚析构区别:

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

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

类名::~类名(){}

示例:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

class Animal
{
public:
	Animal()
	{
		cout << "Animal的构造函数" << endl;
	}
	//利用虚析构可以解决 父类指针释放子类对象时不干净的问题
	//virtual ~Animal()
	//{
	//	cout << "Animal的虚析构函数" << endl;
	//}
	
	//纯虚析构的声明
	//有了纯虚析构 之后,这个类也属于抽象类,无法实例化对象
	virtual ~Animal() = 0;
	//纯虚函数
	virtual void speak() = 0;
};
//纯虚析构的实现
Animal ::~Animal()
{
	cout<< "Animal的纯虚析构函数" << endl;
}

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


void test01()
{
	Animal* animal = new Cat("Tmo");
	animal->speak();
	//父类指针在析构时候 不会调用子类中析构,导致子类如果有堆区属性,出现内存泄漏
	delete animal;
}
int main()
{
	test01();
}

总结:

  1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  3. 拥有纯虚析构函数的类也属于抽象类
4.7.6 多态案例三——电脑组装

案例描述:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F2A74J0I-1679388470134)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669355733563.png)]

自己写的:

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

//抽象的CPU类
class Cpu
{
public:
	virtual ~Cpu()
	{
		cout << "Cpu的虚析构函数" << endl;
	}
	virtual void Caculate() = 0;
};
//抽象的显卡类
class Graphics
{
public:
	virtual ~Graphics()
	{
		cout << "显卡的虚析构函数" << endl;
	}
	virtual void Show() = 0;
};
//抽象的内存条类
class Ram
{
public:
	virtual ~Ram()
	{
		cout << "内存条的虚析构函数" << endl;
	}
	virtual void Storage() = 0;
};
class Computer
{
public:
	virtual ~Computer()
	{
		cout << "电脑的虚析构函数" << endl;
	}
	virtual void doWork() = 0;
};
class Computer_Intel : public Cpu, public Graphics, public Ram,public Computer
{
public:
	void Caculate()
	{
		cout << "Intel厂商提供的CPU计算" << endl;
	}
	void Show()
	{
		cout << "Intel厂商提供的显卡显示" << endl;
	}
	void Storage()
	{
		cout << "Intel厂商提供的内存条存储" << endl;
	}
	void doWork()
	{
		Caculate();
		Show();
		Storage();
	}
};
class Computer_Lenovo : public Cpu, public Graphics, public Ram, public Computer
{
public:
	void Caculate()
	{
		cout << "Lenovo厂商提供的CPU计算" << endl;
	}
	void Show()
	{
		cout << "Lenovo厂商提供的显卡显示" << endl;
	}
	void Storage()
	{
		cout << "Lenovo厂商提供的内存条存储" << endl;
	}
	void doWork()
	{
		Caculate();
		Show();
		Storage();
	}
};
class Computer_Apple : public Cpu, public Graphics, public Ram, public Computer
{
public:
	void Caculate()
	{
		cout << "Apple厂商提供的CPU计算" << endl;
	}
	void Show()
	{
		cout << "Apple厂商提供的显卡显示" << endl;
	}
	void Storage()
	{
		cout << "Apple厂商提供的内存条存储" << endl;
	}
	void doWork()
	{
		Caculate();
		Show();
		Storage();
	}
};

void test01()
{
	cout << "电脑的配置参数:" << endl;
	Computer* c = new Computer_Intel;
	c->doWork();
	delete c;
	cout << "--------------------" << endl;
	c = new Computer_Lenovo;
	c->doWork();
	delete c;
	cout << "--------------------" << endl;
	c = new Computer_Apple;
	c->doWork();
	delete c;
}
int main()
{
	test01();
}

示例

#include  
#include  
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::setw;

//抽象的CPU类
class CPU
{
public:
	virtual ~CPU()
	{
		cout << "Cpu的虚析构函数" << endl;
	}
	virtual void calculate() = 0;
};
//抽象的显卡类
class Graphics
{
public:
	virtual ~Graphics()
	{
		cout << "显卡的虚析构函数" << endl;
	}
	virtual void show() = 0;
};
//抽象的内存条类
class Ram
{
public:
	virtual ~Ram()
	{
		cout << "内存条的虚析构函数" << endl;
	}
	virtual void storage() = 0;
};

class Computer
{
public:
	Computer(CPU* cpu,Graphics* gp,Ram* ram)
	{
		m_cpu =  cpu;
		m_gp = gp;
		m_ram = ram;
	}
	//提供析构函数 释放3个电脑零件
	~Computer()
	{
		if (m_cpu!=NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}
		if (m_gp!=NULL)
		{
			delete m_gp;
			m_gp = NULL;
		}
		if (m_ram!=NULL)
		{
			delete m_ram;
			m_ram = NULL;
		}
	}
	void doWork()
	{
		m_cpu->calculate();
		m_gp->show();
		m_ram->storage();
	}
private:
	CPU* m_cpu;//CPU零件指针
	Graphics* m_gp;//显卡零件指针
	Ram* m_ram;//内存条零件指针
};

class IntelCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Intel的CPU开始计算了!" << endl;
	}
};
class IntelGraphics :public Graphics
{
public:
	virtual void show()
	{
		cout << "Intel的显卡开始显示了!" << endl;
	}
};
class IntelRam :public Ram
{
public:
	virtual void storage()
	{
		cout << "Intel的内存条开始存储了!" << endl;
	}
};

class LenovoCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Lenovo的CPU开始计算了!" << endl;
	}
};
class LenovoGraphics :public Graphics
{
public:
	virtual void show()
	{
		cout << "Lenovo的显卡开始显示了!" << endl;
	}
};
class LenovoRam :public Ram
{
public:
	virtual void storage()
	{
		cout << "Lenovo的内存条开始存储了!" << endl;
	}
};

class AppleCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Apple的CPU开始计算了!" << endl;
	}
};
class AppleGraphics :public Graphics
{
public:
	virtual void show()
	{
		cout << "Apple的显卡开始显示了!" << endl;
	}
};
class AppleRam :public Ram
{
public:
	virtual void storage()
	{
		cout << "Apple的内存条开始存储了!" << endl;
	}
};

void test01()
{
	//第一台电脑零件
	cout << "第一台电脑开始工作:" << endl;
	CPU* intelCpu = new IntelCPU;
	Graphics* intelGraphics = new IntelGraphics;
	Ram* intelram = new IntelRam;
	Computer* computer1 = new Computer(intelCpu,intelGraphics, intelram);
	computer1->doWork();
	delete computer1;
	//第二台电脑零件
	cout << "----------------------------" << endl;
	cout << "第二台电脑开始工作:" << endl;
	CPU* lenovoCpu = new LenovoCPU;
	Graphics* lenovoGraphics = new LenovoGraphics;
	Ram* lenovoram = new LenovoRam;
	Computer* computer2 = new Computer(lenovoCpu, lenovoGraphics, lenovoram);
	computer2->doWork();
	delete computer2;
	//第三台电脑零件
	cout << "----------------------------" << endl;
	cout << "第三台电脑开始工作:" << endl;
	CPU* appleCpu = new AppleCPU;
	Graphics* appleGraphics = new AppleGraphics;
	Ram* appleram = new AppleRam;
	Computer* computer3 = new Computer(appleCpu, appleGraphics, appleram);
	computer3->doWork();
	delete computer3;
}

int main()
{
	test01();
}

5 文件操作

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

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

C++中对文件操作需要包含头文件====

文件类型分为两种:

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

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

操作文件的三大类:

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

5.1 文本文件

5.1.1 写文件

写文件步骤如下:

  1. 包含头文件

    #include

  2. 创建流对象

    ofstream ofs;

  3. 打开文件

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

  4. 写数据

    ofs<<"写入的数据";

  5. 关闭文件

    ofs.close();

文件打开方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-85oipxk2-1679388470134)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669385953313.png)]

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

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

#include  
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::ofstream;
using std::ios;

void test01()
{
	//1、包含头文件 fstream

	//2、创建流对象
	ofstream ofs;

	//3、指定打开方式
	ofs.open("test.txt",ios::out);
	//4、写内容
	ofs << "姓名:张三"<

总结:

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

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

读文件步骤如下:

  1. 包含头文件

    #include

  2. 创建流对象

    ifstream ifs;

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

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

  4. 读数据

    四种方式读取==

  5. 关闭文件

    ifs.close();

示例:

#include  
#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::ifstream;
using std::ios;

void test01()
{
	//1、包含头文件 fstream

	//2、创建流对象
	ifstream ifs;

	//3、打开文件 并且判断是否打开成功
	ifs.open("test.txt",ios::in);
	bool ret = ifs.is_open();
	if (ret)
		cout << "文件打开成功!" << endl;
	else
	{
		cout << "文件打开失败!" << endl;
		return;
	}
		
	//4、写内容 (四种)
	// 第一种
	/*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 (std::getline(ifs,buf))
	{
		cout << buf << endl;
	}
	//第四种(不推荐用)太慢了
	//char c;

	//while ((c=ifs.get())!=EOF)//EOF end of file
	//{
	//	cout << c;
	//}
	
	//5、关闭文件
	ifs.close();
}

int main()
{
	test01();
}

总结:

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

5.2 二进制文件

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

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

5.2.1 写文件

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

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

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

示例:

#include  
#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::ofstream;
using std::ios;

class Person
{
public:

	char m_Name[64];
	int m_Age;
};
void test01()
{
	//构造函数
	ofstream ofs("person.txt", ios::out | ios::binary);

	//ofs.open("person.txt",ios::out|ios::binary );

	Person person = { "张三", 25 };//相当于结构体,权限是公开的
	//写文件
	ofs.write((const char *)&person,sizeof(Person));
	ofs.close();
}

int main()
{
	test01();
}

总结:

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

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

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

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

示例:

#include  
#include 
#include 
using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::ofstream;
using std::ifstream;
using std::ios;

class Person
{
public:

	char m_Name[64];
	int m_Age;
};
void test01()
{
	//1、包含头文件
	
	//3、打开文件	判断文件是否打开成功
	//4、包含头文件
	//5、包含头文件
	
	//2、创建流对象//构造函数
	ifstream ifs("person.txt", ios::in | ios::binary);
	if (!ifs.is_open())
	{
		cout << "文件打开失败!" << endl;
		return;
	}
	//读文件
	Person person;
	ifs.read((char *)&person,sizeof(Person));

	cout << "姓名:" << person.m_Name <<" 年龄为:" << person.m_Age << endl;

	ifs.close();
}

int main()
{
	test01();
}

总结:

  • 文件输入流对象 可以通过read函数,以二进制方式读数据

职工管理系统

1、管理系统需求

职工管理系统可以用来管理公司内所有员工的信息

主要利用C++来实现一个基于多态的职工管理系统

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

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

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

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

管理系统需要实现的功能如下:

  • 退出管理程序:退出当前管理系统
  • 增加职工信息:实现批量添加职工功能,将信息录入到文件中,职工信息为:职工编号、姓名、部门编号
  • 显示职工信息:显示公司内部所有职工的信息
  • 删除离职员工:按照编号删除指定的员工
  • 修改职工信息:按照编号修改职工个人信息
  • 查找员工信息:按照职工的编号或者职工的姓名进行查找相关的人员信息
  • 按照编号排序:按照职工编号,进行排序,排序规则由用户指定
  • 清空所有文档:清空文件中记录的所有职工信息(清空前需要再次确认,防止误删)

2、创建管理类

​ 管理类负责的内容如下:

  • 与用户的沟通菜单界面
  • 对职工增删改查的操作
  • 与文件的读写交互

2.1 创建文件

在头文件和源文件夹下分别创建workManager.hworkManager.cpp文件

2.2 头文件实现

workManager.h中设计管理类

代码如下:

#pragma once //防止头文件重复包含
#include 	//包含输入输出流头文件

class workManager
{
public:

	//构造函数
	workManager();
	//析构函数
	~workManager();
};

2.3 源文件实现

workManager.cpp中将构造和析构函数空实现补全

代码如下:

#include "workManager.h"

workManager::workManager()
{

}
workManager::~workManager()
{
	
}

3、菜单功能

功能描述:与用户的沟通界面

3.1 添加成员函数

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GgUMOEtp-1679388470135)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669633286226.png)]

3.2 菜单功能的实现

在管理类workManager.cpp中添加成员函数Show_Menu()函数

void workManager::ShowMenu()
{
	cout << "********************************************" << endl;
	cout << "*********	欢迎使用职工管理系统!	*********" << endl;
	cout << "*************	0.退出管理程序  *************" << endl;
	cout << "*************	1.增加职工信息  *************" << endl;
	cout << "*************	2.增加职工信息  *************" << endl;
	cout << "*************	3.退出管理程序  *************" << endl;
	cout << "*************	4.退出管理程序  *************" << endl;
	cout << "*************	5.退出管理程序  *************" << endl;
	cout << "*************	6.退出管理程序  *************" << endl;
	cout << "*************	7.退出管理程序  *************" << endl;
	cout << "********************************************" << endl;
	cout << endl;
}

4、退出功能

4.1 提供功能接口

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

代码:

#include 
#include "workManager.h"
using std::cout;
using std::endl;
using std::cin;

int main()
{
	//实例化管理者对象
	workManager wm;
	int choice = 0;
	while (true)
	{
		//调用展示菜单成员函数
		wm.ShowMenu();
		
		cout << "请输入您的选择:" << endl;
		cin >> choice;

		switch (choice)
		{
		case 0: //退出系统
			break;
		case 1: //添加职工
			break;
		case 2: //显示职工
			break;
		case 3: //删除职工
			break;
		case 4: //修改职工
			break;
		case 5: //查找职工
			break;
		case 6: //排序职工
			break;
		case 7: //退出系统
			break;
		default:
			system("cls");
			break;
		}
	}	
	return 0;
}

4.2 实现退出功能

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

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

void workerManager::ExitSystem()
{
	cout << "欢迎下次使用!" << endl;
	system("pause");
	exit(0);
}

5、创建职工类

5.1 创建职工抽象类

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

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

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

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

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

代码如下:

#pragma once
#include 
using std::string;

//职工抽象基类
class Worker
{
public:
	//显示个人信息
	virtual void showInfo() = 0;
	//获取岗位名称
	virtual string getDeptName() = 0;

	int m_Id;//职工编号
	string m_Name;//职工姓名
	int m_DeptId;//职工所在部门的名称编号

	virtual ~Worker() = 0;
};

5.2 创建普通员工类

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

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

employee.h中代码如下:

#pragma once
#include 
#include "worker.h"

//员工类

class Employee:public Worker
{
	//构造函数
	Employee(int id, string name, int dId);

	//显示个人信息
	virtual void showInfo() = 0;

	//获取职工岗位名称
	virtual string getDeptName() = 0;
};

employee.h中代码如下:

#include"employee.h"

Employee::Employee(int id, string name, int dId)//构造函数
{
	this->m_Id = id;
	this->m_Name = name;
	this->m_DeptId = dId;
}

void Employee::showInfo()//显示个人信息
{
	cout << "职工编号:" << this->m_Id 
		<< "\t职工姓名:" << this->m_Name 
		<< "\t岗位:" << this->getDeptName()
		<<"\t岗位职责:完成经理交给的任务" << endl;
}

string Employee::getDeptName()//获取岗位名称
{
	return string("员工");
}

5.3 创建经理类

经理类继承职工抽象类,并重写父类中纯虚函数

在头文件中和源文件的文件夹下分别创建manager.hmanager.cpp文件

manager.h中代码如下:

#pragma once
#include 
#include "worker.h"

using std::cout;
using std::endl;
//经理类

class  Manager:public Worker
{
public:
	//构造函数
	Manager(int id, string name, int dId);

	//显示个人信息
	virtual void showInfo();

	//获取职工岗位名称
	virtual string getDeptName();
};

manager.cpp中代码如下:

#include "manager.h"

Manager::Manager(int id, string name, int dId)
{
	this->m_Id = id;
	this->m_Name = name;
	this->m_DeptId = dId;
}

void Manager::showInfo()
{
	cout << "职工编号:" << this->m_Id
		<< "\t职工姓名:" << this->m_Name
		<< "\t岗位:" << this->getDeptName()
		<< "\t岗位职责:完成老板交给的任务,并下发任务给员工" << endl;
}

string Manager::getDeptName()
{
	return string("经理");
}

5.4 创建老板类

老板类继承职工抽象类,并重写父类中纯虚函数

在头文件中和源文件的文件夹下分别创建boss.hboss.cpp文件

boss.h中代码如下:

#pragma once
#include 
#include "worker.h"

using std::cout;
using std::endl;
//老板类

class Boss :public Worker
{
public:
	//构造函数
	Boss(int id, string name, int dId);

	//显示个人信息
	virtual void showInfo();

	//获取职工岗位名称
	virtual string getDeptName();
};

boss.cpp中代码如下:

#include "boss.h"

Boss::Boss(int id, string name, int dId)//构造函数
{
	this->m_Id = id;
	this->m_Name = name;
	this->m_DeptId = dId;
}

void Boss::showInfo()//显示个人信息
{
	cout << "职工编号:" << this->m_Id
		<< "\t职工姓名:" << this->m_Name
		<< "\t岗位:" << this->getDeptName()
		<< "\t岗位职责:管理公司所有事务" << endl;
}

string Boss::getDeptName()//获取岗位名称
{
	return string("总裁");
}

6、添加职工

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

6.1、功能分析

分析:

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C0LFOIqo-1679388470136)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669792838812.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dSgAb0Vt-1679388470136)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669792907426.png)]

6.2 功能实现

WorkerManager.h头文件中添加成员属性

代码:

	int m_EmpNum;//记录文件中的人数个数

	Worker** m_EmpArray;//员工数组的指针

WorkManager构造函数中初始化属性

WorkerManager::WorkerManager()
{
	//初始化属性
	this->m_EmpNum = 0;
	this->m_EmpArray = NULL;
}

WorkerManager.h头文件中添加成员函数

	void Add_Emp();//增加职工

WorkerManager.cpp头文件中添加成员函数

void WorkerManager::Add_Emp()
{
	cout << "请输入添加职工数量:" << endl;

	int addNum = 0;//保存用户的输入数量
	cin >> addNum;

	if (addNum>0)
	{
		//添加
		//计算添加新空间大小
		int newSize = this->m_EmpNum + addNum;//新空间人数 = 原来记录人数+新增人数

		//开辟新空间
		Worker** newSpace = new Worker * [newSize];

		//将原来空间下数据,拷贝到新空间下
		if (this->m_EmpArray!=NULL)
		{
			for (int i = 0; i < this->m_EmpNum; i++)
			{
				newSpace[i] = this->m_EmpArray[i];
			}
		}
		//批量添加新数据
		for (int i = 0; i < addNum; i++)
		{
			int id;//职工编号
			string name;//职工姓名
			int dSelect;//部门选择

			cout << "请输入第 " << i + 1 << " 个新职工编号:" << endl;
			cin >> id;

			cout << "请输入第 " << i + 1 << " 个新职工姓名:" << endl;
			cin >> name;

			cout << "请选择该职工岗位:" << endl;
			cout << "1、普通职工" << endl;
			cout << "2、经理" << endl;
			cout << "3、老板" << endl;
			cin >> dSelect;

			Worker* worker = NULL;
			switch (dSelect)
			{
			case 1:
				worker = new Employee(id,name,1);
			case 2:
				worker = new Employee(id, name, 2);
			case 3:
				worker = new Employee(id, name, 3);
			default:
				break;
			}
			//将创建职工职责 ,保存到数组中
			newSpace[this->m_EmpNum + i] = worker;
		}
		//释放原有空间
		delete[] this->m_EmpArray;

		//更改新空间的指向
		this->m_EmpArray = newSpace;

		//更新新的职工人数
		this->m_EmpNum = newSize;

		//成功添加后  保存到文件中

		//提示添加成功
		cout << "成功添加" << addNum << "名新职工!" << endl;

	}
	else
	{
		cout << "输入数据有误!" << endl;
	}
	system("pause");
	system("cls");
}

7、文件交互—写文件

功能描述:对文件进行读写

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

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

7.1 设定文件路径

首先我们将文件路劲,在workerManager.h中添加宏变量,并且包含头文件fstream

#include 

#define	FILENAME "empFile.txt"

7.2 成员函数声明

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

//保存文件
void save();

7.3 保存文件功能实现

void WorkerManager::save()
{
	ofstream ofs;
	ofs.open(FILENAME, ios::out);

	for (int i = 0; i < this->m_EmpNum; i++)
	{
		ofs << this->m_EmpArray[i]->m_Id << " "
			<< this->m_EmpArray[i]->m_Name << " "
			<< this->m_EmpArray[i]->m_DeptId << endl;
	}
	ofs.close();
}

8、文件交互—读文件

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

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

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

因此构造函数初始化数据的情况分为三种情况

  1. 第一次使用,文件未创建
  2. 文件存在,但是数据被用户清空
  3. 文化存在,并且保存职工的所有数据

8.1 文件未创建

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

//标志文件是否为空
bool m_FilesEmpty;

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

WorkerManager::WorkerManager()
{
	ifstream ifs;
	ifs.open(FILENAME, ios::in);

	//文件不存在情况

	if (!ifs.is_open())
	{
		cout << "文件不存在" << endl;//测试输出

		this->m_EmpNum = 0;//初始化人数
		this->m_EmpArray = NULL;//初始化数组
		this->m_FilesEmpty = true;//初始化文件为空标志
	
		ifs.close();//关闭文件
		return;
	}
}

删除文件后,测试文件不存在时初始化数据功能

8.2 文件存在且数据为空

WorkerManager.cpp中构造函数追加代码

char ch;
	ifs >> ch;
	if(ifs.eof())
	{
		//文件为空
		cout << "文件为空!" << endl;
		//初始化记录人数
		this->m_EmpNum = 0;
		//初始化数组指针
		this->m_EmpArray = NULL;
		//初始化文件是否为空
		this->m_FilesEmpty = true;
		ifs.close();
		return;
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUieQx4w-1679388470137)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669952920818.png)]

将文件创建后清空文件内容,并测试该情况下初始化功能

我们发现文件不存在或者为空清空m_FilesEmpty判断文件是否为空的标志都为真,何时未假?

//更新职工不为空标志
		this->m_FilesEmpty = false;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kUiPZ1KC-1679388470137)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669952991424.png)]

8.3 文件存在且保存职工数据

8.3.1 获取记录的职工人数

WorkerManager.h中添加成员函数int get_EmpNum();

int get_EmpNum();//统计人数

WorkerManager.cpp中实现

int WorkerManager::get_EmpNum()
{
	ifstream ifs;
	ifs.open(FILENAME,ios::in);

	int id;
	string name;
	int dId;

	int num = 0;

	while (ifs>>id&&ifs>>name&&ifs>>dId)
	{
		num++;//记录人数
	}
	ifs.close();

	return num;
}

WorkerManager.cpp构造函数中继续追求代码:

	//3、文件存在,并且记录数据
	int num = this->get_EmpNum();
	cout << "职工人数为:" << num << endl;
	this->m_EmpNum = num;

手动添加一些职工信息,测试获取职工数量函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3egATDL-1679388470137)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1669956679518.png)]

8.3.2 初始化数组

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

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

//初始化员工
void init_Emp();

WorkerManager.cpp中实现

void WorkerManager::init_Emp()
{
	ifstream ifs;
	ifs.open(FILENAME,ios::in);

	int id;
	string name;
	int dId;

	int index = 0;
	while (ifs>>id&&ifs>>name&&ifs>>dId)
	{
		Worker* worker = NULL;
		if (dId==1)//普通员工
		{
			worker = new Employee(id,name,dId);
		}
		else if (dId == 2)//经理
		{
			worker = new Manager(id,name,dId);
		}
		else if (dId == 3)//总裁
		{
			worker = new Boss(id,name,dId);
		}
		this->m_EmpArray[index] = worker;
		index++;
	}

}

WorkerManager.cpp构造函数中追加代码

	//开辟空间
	this->m_EmpArray = new Worker * [this->m_EmpNum];
	//将文件中的数据,存到数组中
	this->init_Emp();

9、显示职工

功能描述:显示当前职工信息

9.1 显示职工函数声明

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

//显示职工
void Show_Emp();

9.2 显示职工函数实现

workerManager.cpp中实现成员函数void Show_Emp();

void WorkerManager::Show_Emp()
{
	if (this->m_FilesEmpty)
	{
		cout << "文件不存在或记录为空!" << endl;
	}
	else
	{
		for (int i = 0; i < m_EmpNum; i++)
		{
			//利用多态调用程序接口
			this->m_EmpArray[i]->showInfo();
		}
	}

	system("pause");
	system("cls");
}

10、删除职工

功能描述:按照职工编号进行删除职工操作

10.1 删除职工函数声明

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

//删除职工
void Del_Emp();

10.2 职工是否存在函数声明

很多功能都需要用到根据职工是否存在来进行操作如:删除职工、修改职工、查找职工

因此添加该公告函数,以便后序调用

WorkerManager.h中添加成员函数int IsExit(int id);

int WorkerManager::IsExist(int id)
{
	int index = -1;
	for (int i = 0; i < this->m_EmpNum; i++)
	{
		if (this->m_EmpArray[i]->m_Id == id)
		{
			//找到职工
			index = i;
			break;
		}
	}
	return index;
}

10.3 职工是否存在函数实现

WorkerManager.cpp中实现成员函数void Del_Emp();

void WorkerManager::Del_Emp()
{
	if (this->m_FilesEmpty)
	{
		cout << "文件不存在或记录为空!"<> id;

		int index=this->IsExist(id);

		if (index != -1)//说明职工存在,并且要删除掉index位置上的职工
		{
			for (int i = index; i < this->m_EmpNum-1; i++)
			{
				//数据前移
				this->m_EmpArray[i] = this->m_EmpArray[i + 1];
			}
			this->m_EmpNum--;//更新数组中记录人员个数
			this->save();//将数据同步更新到文件中

			cout << "删除成功!" << endl;
		}
		else
		{
			cout << "删除失败,未找到该职工" << endl;
		}
	}
	system("pause");
	system("cls");
}

11、修改职工

功能描述:能够按照职工的编号对职工信息进行修改并保存

11.1 修改职工函数声明

WorkerManager.h中实现成员函数void Mod_Emp();

//修改职工
void Mod_Emp();

11.2 修改职工函数实现

WorkerManager.cpp中实现成员函数void Mod_Emp();

void WorkerManager::Mod_Emp()
{
	if (this->m_FilesEmpty)
	{
		cout << "文件不存在或记录为空!" << endl;
	}
	else
	{
		cout << "请输入修改职工的编号:" << endl;
		int id;
		cin >> id;

		int ret = this->IsExist(id);
		if (ret != -1)
		{
			//查找到编号的职工

			delete this->m_EmpArray[ret];

			int newId = 0;
			string newName;
			int dSelect = 0;

			cout << "查到:" << id << "号职工,请输入新职工号:" << endl;
			cin >> newId;

			cout << "请输入新姓名:" << endl;
			cin >> newName;

			cout << "请输入岗位:" << endl;
			cout << "1、普通员工" << endl;
			cout << "2、经理" << endl;
			cout << "3、老板" << endl;
			cin >> dSelect;

			Worker* worker = NULL;

			switch (dSelect)
			{
			case 1:
				worker = new Employee(newId,newName,dSelect);
				break;
			case 2:
				worker = new Manager(newId,newName,dSelect);
				break;
			case 3:
				worker = new Boss(newId,newName,dSelect);
				break;
			default:
				break;
			}
			//更新数据 到数组中
			this->m_EmpArray[ret] = worker;
			cout << "修改成功!" << endl;

			//保存到文件中
			this->save();
		}
		else
		{
			cout << "修改失败,查无此人!" << endl;
		}
	}
	system("pause");
	system("cls");
}

12、查找职工

功能描述:提供两种查找职工方式:一种按照职工编号,一种按照职工姓名

12.1 查找职工函数声明

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

//查找员工
void Find_Emp();

12.2 查找职工函数实现

workerManager.cpp中实现成员函数void Find_Emp();

void WorkerManager::Find_Emp()
{
	if (this->m_FilesEmpty)
	{
		cout << "文件不存在或记录为空!" << endl;
	}
	else
	{
		cout << "请输入查找的方式:" << endl;
		cout << "1、按职工编号查找" << endl;
		cout << "2、按姓名查找" << endl;

		int select = 0;
		cin >> select;

		if (select==1)//按职工号查找
		{
			int id;
			cout << "请输入查找的职工编号:" << endl;
			cin >> id;

			int ret = IsExist(id);
			if (ret != -1)
			{
				cout << "查找成功!该职工信息如下:" << endl;
				this->m_EmpArray[ret]->showInfo();
			}
			else
			{
				cout << "查找失败!" << endl;
			}
		}
		else if (select==2)//按照姓名查找
		{
			string name;
			cout << "请输入查找的姓名:" << endl;
			cin >> name;

			//加入判断是否查到的标志
			bool flag = false;//默认未找到职工

			for (int i = 0; i < this->m_EmpNum; i++)
			{
				if (this->m_EmpArray[i]->m_Name == name)
				{
					cout << "查找成功,职工编号为:" 
						<< this->m_EmpArray[i]->m_Id
						<<"号职工信息如下:"<m_EmpArray[i]->showInfo();
				}
			}
			if (flag==false)
			{
				cout << "查找失败,查无此人!" << endl;
			}
		}
		else
		{
			cout << "输入选项错误!" << endl;
		}
	}

	system("pause");
	system("cls");
}

13 排序

功能描述:按照职工编号进行排序,排序的顺序由用户指定

13.1 排序函数声明

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

//排序职工
void Sort_Emp();

13.2 排序函数实现

WorkerManager.cpp中实现成员函数void Sort_Emp();

void WorkerManager::Sort_Emp()
{
	if (this->m_FilesEmpty)
	{
		cout << "文件不存在或记录为空!" << endl;
		system("pause");
		system("cls");
	}
	else
	{
		cout << "请选择排序方式:" << endl;
		cout << "1、按职工号进行升序" << endl;
		cout << "2、按职工号进行降序" << endl;

		int select = 0;
		cin >> select;

		for (int i = 0; i < m_EmpNum; i++)
		{
			int minOrmax = i;//声明最小值 或 最大值下标
			for (int j = i + 1; j < m_EmpNum; j++)
			{
				if (select == 1)//升序
				{
					if (m_EmpArray[minOrmax]->m_Id > m_EmpArray[j]->m_Id)
					{
						minOrmax = j;
					}
				}
				else//降序
				{
					if (m_EmpArray[minOrmax]->m_Id < m_EmpArray[j]->m_Id)
					{
						minOrmax = j;
					}
				}
			}

			//判断一开始认定 最小值或最大值 是不是 计算的最小值或最大值,如果不是 交换数据
			if (i != minOrmax)
			{
				Worker* temp = m_EmpArray[i];
				m_EmpArray[i] = m_EmpArray[minOrmax];
				m_EmpArray[minOrmax] = temp;
			}
		}
		cout << "排序成功,排序后结果为:" << endl;
		this->save();
		this->Show_Emp();
	}
}

14、清空文件

功能描述:将文件中记录数据清空

14.1 清空函数声明

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

//清空文件
void Clean_File();

14.2 清空函数实现

WorkerManager.cpp中实现成员函数void Clean_File();

void WorkerManager::Clean_File()
{
	cout << "确认清空?" << endl;
	cout << "1、确认" << endl;
	cout << "2、返回" << endl;

	int select = 0;
	cin >> select;

	if (select == 1)
	{
		//打开模式 ios::trunc 如果存在删除文件并重新创建
		ofstream ofs(FILENAME,ios::trunc);
		ofs.close();

		if (this->m_EmpArray != NULL)
		{
			for (int i = 0; i < this->m_EmpNum; i++)
			{
				if (this->m_EmpArray[i] != NULL)
				{
					delete this->m_EmpArray[i];
				}
			}
			this->m_EmpNum = 0;
			delete[] this->m_EmpArray;
			this->m_EmpArray = NULL;
			this->m_FilesEmpty = true;
		}
		cout << "清空成功!" << endl;
	}

	system("pause");
	system("cls");
}

C++提高编程

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

1 模板

1.1 模板的概念

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

模板的特点:

  • 模板不可以直接使用,它只是一个框架
  • 模板的通用并不是万能的

1.2 函数模板

  • C++另外一种编程思想称为泛型编程,主要利用的技术就是模板
  • C++提供两种模板极致:函数模板类模板
1.2.1 函数模板语法

函数模板的作用:

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

语法:

template
函数声明或定义

解释:

template — 声明创建模板

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

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

示例:

#include

using std::cout;
using std::endl;
//函数模板

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

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

//函数模板
template //声明一个模板,钙素编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型

void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}
void test01()
{

	int a = 10;
	int b = 20;

	//swapInt(a, b);
	//利用函数模板交换
	//两种方式使用函数模板
	//1、自动类型推导
	mySwap(a, b);
	//2、显示指定类型
	mySwap(a, b);

	cout << "a= " << a << " b= " << b << endl;

}
int main()
{
	test01();
}

总结:

  • 函数模板利用关键字template
  • 使用函数模板有两种凡是:自动类型推导、显示指定类型
  • 模板的目的是为了提供复用性,将类型参数化
1.2.2 函数模板注意事项

注意事项:

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

示例:

#include

using std::cout;
using std::endl;
//函数模板注意事项

template//typename可以替换成class
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

//1、自动类型推导,必须推导出一致的数据类型T才可以使用
void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';
	mySwap(a, b);//正确!
	//mySwap(a, c);//错误!推导不出一致的数据类型T

	cout << "a= " << a << " b= " << b << endl;
}
//2、模板必须要确定出T的数据类型,才可以使用
template
void func()
{
	cout << "func()函数调用" << endl;
}
void test02()
{
	func();
}

int main()
{
	test01();
	test02();
}

总结:

  • 使用模板时必须确定出通用数据类型T,并且能够推导出一致的类型
1.2.3 函数模板

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别利用char数组int数组进行测试

示例:

#include

using std::cout;
using std::endl;
using std::cin;

//实现通用 对数组进行排序的函数
//规则 从大到小
//算法 选择
//测试 char数组、int数组、

//交换函数模板
template
void mySwap(T&a,T&b)
{
	T temp = a;
	a = b;
	b = temp;
}
//排序算法
template
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
void printArray(T arr[],int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

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

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

	int num = sizeof(intArr) / sizeof(int);

	mySort(intArr, num);
	printArray(intArr, num);
}
int main()
{
	test01();
	test02();
}
1.2.4 普通函数与函数模板区别:

区别:

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

示例:

#include

using std::cout;
using std::endl;
using std::cin;

//1、普通函数调用可以发生隐式类型转换
//2、函数模板 用自动类型推导,不可以发生隐式类型转换
//3、

//普通函数
int myAdd01(int a,int b)
{
	return a + b;
}

//函数模板
template
T myAdd02(T a, T b)
{
	return a + b;
}

void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';//a-97,c-99

	cout<(a, c) << endl;

}
int main()
{
	test01();
}

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

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

调用规则如下:

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

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

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

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

示例:

#include

using std::cout;
using std::endl;
using std::cin;

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

void myPrint(int a,int b)
{
	cout << "调用普通函数" << endl;
}
template
void myPrint(T a, T b)
{
	cout << "调用函数模板" << endl;
}
template
void myPrint(T a, T b,T c)
{
	cout << "调用函数重载模板" << endl;
}

void test01()
{
	int a = 10;
	int b = 20;
	int c = 30;
	myPrint(a, b);//如果函数模板和普通函数都可以实现,优先调用普通函数
	myPrint<>(a, b);//可以通过空模板参数列表来强制调用函数模板
	myPrint<>(a, b, c);//函数模板也可以发生重载

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

}
int main()
{
	test01();
}

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

1.2.6 模板的局限性

局限性:

  • 模板的通用性并不是万能的

例如:

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

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

再例如:

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

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

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

示例:

#include

using std::cout;
using std::endl;
using std::cin;
using std::string;

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

class Person
{
public:
	Person(string name,int age)
	{
		this->m_name = name;
		this->m_age = age;
	}

	string m_name;
	int m_age;

};
//对比两个数据是否相等函数
template
bool myCompare(T& a, T& b)
{
	if (a == b)
	{
		return true;
	}
	else
	{
		return 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 test01()
{
	int a = 10;
	int b = 20;

	bool ret = myCompare(a, b);
	if (ret)
	{
		cout << "a==b" << endl;
	}
	else
	{
		cout << "a!=b" << endl;
	}
}
void test02()
{
	Person p1("Tom",12);
	Person p2("Tom",12);

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


int main()
{
	//test01();
	test02();
}

总结:

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

1.3 类模板

1.3.1 类模板语法

类模板作用:

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

语法:

template

解释:

template — 声明创建模板

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

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

示例:

#include

using std::cout;
using std::endl;
using std::cin;
using std::string;

//类模板
template 
class Person
{
public:
	Person(NameType name,AgeType age)
	{
		this->m_name = name;
		this->m_age = age; 
	}
	NameType m_name;
	AgeType m_age;
	void showPerson()
	{
		cout << "姓名:" << this->m_name << " 年龄为:" << this->m_age << endl;
	}

};

void test01()
{
	Personp1("孙悟空", 999);
	p1.showPerson();
}

int main()
{
	test01();
}

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

1.3.2 类模板与函数模板区别

区别主要有两点:

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

示例:

#include

using std::cout;
using std::endl;
using std::cin;
using std::string;

//类模板
template 
class Person
{
public:
	Person(NameType name,AgeType age)
	{
		this->m_name = name;
		this->m_age = age; 
	}
	NameType m_name;
	AgeType m_age;
	void showPerson()
	{
		cout << "姓名:" << this->m_name << " 年龄为:" << this->m_age << endl;
	}

};

void test01()
{
	//Person p("孙悟空",999); 错误,无法用自动类型推导
	Personp1("孙悟空", 999);//正确,只能用显示指定类型

	p1.showPerson();
}

int main()
{
	test01();
}

总结:

  • 类模板使用只能用显示指定类型方式
  • 类模板中的模板参数列表可以有默认参数
1.3.3 类模板中成员函数创建时机

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

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

示例:

#include

using std::cout;
using std::endl;
using std::cin;
using std::string;

//类模板中成员函数创建时机
//类模板中成员函数在调用时才去创建

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

template
class MyClass
{
public:
	T obj;

	//类模板中的成员函数
	void func1()
	{
		obj.showPerson1();
	}
	void func2()
	{
		obj.showPerson2();
	}
};
void test01()
{
	MyClassm;
	m.func1();
	//m.func2();//编译会出错,说明函数调用才会去创建成员函数

}
int main()
{
	test01();
}

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

1.3.4 类模板对象做函数参数

学习目标:

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

一共有三种传入方式:

  1. 指定传入的类型 — 直接显示对象的数据类型
  2. 参数模板化 — 将对象中的参数变为模板进行传递
  3. 整个类模板化 — 将这个对象类型 模板化进行传递

示例:

#include

using std::cout;
using std::endl;
using std::cin;
using std::string;

//类模板对象做函数的参数
template
class Person
{
public:
	Person(T1 name, T2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	T1 m_Name;
	T2 m_Age;

	void showPerson()
	{
		cout << "姓名:" << this->m_Name << " 年龄:" <m_Age<< endl;
	}
};

//1、指定传入类型(最常用的方法)
void printPerson1(Person&p)
{
	p.showPerson();
}

void test01()
{
	Personp("孙悟空", 100);
	printPerson1(p);
}
//2、参数模板化
template
void printPerson2(Person&p)
{
	p.showPerson();
	//利用typeid(T).name()方法查看T的数据类型
	cout << "T1的类型为:" << typeid(T1).name()<p("猪八戒", 200);
	printPerson2(p);
}

//3、整个类模板化
template
void printPerson3(T &p)
{
	p.showPerson();
	cout << "T的类型为:" << typeid(T).name() << endl;
}

void test03()
{
	Personp("唐僧",30);
	printPerson3(p);

}

int main()
{
	test01();
	test02();
	test03();
}

总结:

  • 通过类模板创建的对象,可以有三种方式向函数中进行传参
  • 使用比较广泛是第一种:指定传入类型
1.3.5 类模板与继承

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

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

using std::cout;
using std::endl;
using std::cin;
using std::string;

//类模板与继承
template
class Base
{
	T m;
};

//class Son :public Base//错误,必须知道父类中的T类型,才能继承给子类
class Son :public Base
{
};

void test01()
{
	Son s1;
}

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

};
void test02()
{
	Son2s2;
}

int main()
{
	test01();
	test02();
}

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

1.3.6 类模板成员函数类外实现

学习目标:能够掌握类模板中的成员函数类外实现

示例:

#include

using std::cout;
using std::endl;
using std::cin;
using std::string;

//类模板函数成员类外实现
template
class Person
{
public:
	Person(T1 name,T2 age);
	/*{
		this->m_Name = name;
		this->m_Age = age;
	}*/
	void showPerson();
	/*{
		cout << "姓名:" << this->m_Name << " 年龄: " << this->m_Age << endl;
	}*/
	T1 m_Name;
	T2 m_Age;
};
//构造函数的类外实现
template
Person::Person(T1 name, T2 age)
{
	this->m_Name = name;
	this->m_Age = age;
}
//成员函数的类外实现
template
void Person::showPerson()
{
	cout << "姓名:" << this->m_Name << " 年龄: " << this->m_Age << endl;
}
void test01()
{
	PersonP("Tom",20);
	P.showPerson();
}

int main()
{
	test01();
}

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

1.3.7 类模板分文件编写

学习目标:

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

问题:

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

解决:

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

示例:

person.hpp中代码

#include

using std::cout;
using std::endl;
using std::cin;
using std::string;

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

//将类模板分文件编写问题以及解决
template
class Person
{
public:
	Person(T1 name,T2 age);
	/*{
		this->m_Name = name;
		this->m_Age = age;
	}*/
	void showPerson();
	/*{
		cout << "姓名:" << this->m_Name << " 年龄: " << this->m_Age << endl;
	}*/
	T1 m_Name;
	T2 m_Age;
};
//构造函数的类外实现
template
Person::Person(T1 name, T2 age)
{
	this->m_Name = name;
	this->m_Age = age;
}
//成员函数的类外实现
template
void Person::showPerson()
{
	cout << "姓名:" << this->m_Name << " 年龄: " << this->m_Age << endl;
}

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

#include

using std::cout;
using std::endl;
using std::cin;
using std::string;

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

void test01()
{
	PersonP("Tom",20);
	P.showPerson();
}

int main()
{
	test01();
}

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

1.3.8 类模板和友元

学习目标:

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

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

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

示例:

#include

using std::cout;
using std::endl;
using std::cin;
using std::string;

//通过全局函数 打印Person信息

//提前让编译器知道模板类的存在
template
class Person;

//全局函数在类外实现
template
void printPerson2(Person p)
{
	cout << "类外实现 --- 姓名:" << p.m_Name << " 年龄: " << p.m_Age << endl;
}

template
class Person
{
	//全局函数 类内实现
	friend void printPerson(Person p)
	{
		cout << "姓名:" << p.m_Name << " 年龄: " << p.m_Age << endl;
	}
	//全局函数 类外实现
	//加空模板参数列表
	//如果全局函数 是类外实现,需要让编译器提前知道这个函数的存在	
	friend void printPerson2<>(Person p);

public:
	Person(T1 name,T2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
private:
	T1 m_Name;
	T2 m_Age;
};


//全局函数在类内实现
void test01()
{
	PersonP("Tom",20);
	printPerson(P);
}
void test02()
{
	PersonP("Jarry",22);
	printPerson2(P);
}

int main()
{
	test01();
	test02();
}

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

1.3.9 类模板案例

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

  • 可以对内置数据类型以及自定义数据类型的数据进行存储
  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DlV3aT1L-1679388470140)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1677336522355.png)]

示例:

myArray.hpp中的代码

#pragma once
#include
using namespace std;

template
class MyArray
{
public:
	//有参构造 参数 容量
	MyArray(int capacity) 
	{
		//cout << "MyArray的有参构造调用" << endl;
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->pAddress = new T[this->m_Capacity];
	}
	//拷贝构造函数
	MyArray(const MyArray& arr)
	{
		//cout << "MyArray的拷贝构造调用" << endl;
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		//深拷贝
		this->pAddress = new T[arr.m_Capacity];
		
		//将arr中的数据都拷贝过来
		for (int i = 0; i < this->m_Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	//operator= 防止浅拷贝问题 a = b =  c
	MyArray& operator=(const MyArray& arr)
	{
		//cout << "MyArray的operator=调用" << endl;
		//先判断原来堆区是否有数据,如果有数据先释放
		if (this->pAddress != NULL)
		{
			delete[]this->pAddress;
			this->pAddress = NULL;
		}
		//深拷贝
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->pAddress = new T[arr.m_Capacity];
		for (int i = 0; i < this->m_Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;
	}

	//尾插法
	void Push_Back(const T & val)
	{
		//判断容量是否等于大小
		if (this->m_Capacity==this->m_Size)
		{
			return;
		}
		this->pAddress[this->m_Size] = val;//在数组末尾插入数据
		this->m_Size++;//更新数组大小
	}

	//尾删法
	void Pop_Back()
	{
		//让用户访问不到最后一个元素,即为尾删
		if (this->m_Size == 0)
		{
			return;
		}
		this->m_Size--;
	}

	//通过数组中的下标来访问数组中的元素	arr[0]
	T& operator[](int index)
	{
		return this->pAddress[index];
	}

	//返回数组的容量
	int getCapacity()
	{
		return this->m_Capacity;
	}

	//返回数组的大小
	int getSize()
	{
		return this->m_Size;
	}


	//析构函数
	~MyArray()
	{
		//cout << "MyArray的析构函数调用" << endl;
		if (this->pAddress != NULL)
		{
			delete[]this->pAddress;
			this->pAddress = NULL;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}
	}

private:
	T* pAddress;//指针指向堆区开辟的真实数组

	int m_Capacity;//数组容量
	int m_Size;//数组大小
};

类模板案例.cpp

// 类模板案例-数组类封装.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。

#include 
#include"MyArray.hpp"

void printIntArray(MyArray &arr)
{
	for (int i = 0; i < arr.getSize(); i++)
	{
		cout << arr[i] << endl;
	}
}

void test01()
{
    MyArrayarr1(5);
	for (int i = 0; i < 5; i++)
	{
		//利用尾插法想数组中插入数据
		arr1.Push_Back(i);
	}
	cout << "arr1的打印输出为:" << endl;
	printIntArray(arr1);
	cout << "arr1的容量为:" <arr2(arr1);
	cout << "arr2的打印输出为:" << endl;
	printIntArray(arr2);

	arr2.Pop_Back();
	cout << "arr2尾删后:" << endl;
	cout << "arr2的容量为:" << arr2.getCapacity() << endl;
	cout << "arr2大小为:" << arr2.getSize() << endl;
}

//测试自定义数据类型
class Person
{
public:
	Person() {};
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

void printPersonArray(MyArray & arr)
{
	for (int i = 0; i < arr.getSize(); i++)
	{
		cout <<"姓名:" << arr[i].m_Name <<",年龄:" << arr[i].m_Age << endl;
	}
}

void test02()
{
	MyArrayarr(10);
	Person p1("孙悟空",999);
	Person p2("韩信",299);
	Person p3("牛魔",799);
	Person p4("猪八戒",899);
	Person p5("梦琪",599);

	//将数据插入到数组中
	arr.Push_Back(p1);
	arr.Push_Back(p2);
	arr.Push_Back(p3);
	arr.Push_Back(p4);
	arr.Push_Back(p5);

	//打印数组
	printPersonArray(arr);
	//输出容量
	cout << "arr的容量为:" << arr.getCapacity() << endl;
	//输出大小
	cout << "arr的大小为:" << arr.getSize() << endl;
}

int main()
{
    test01();
    test02();
}

2 STL初识别

2.1 STL的诞生

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

2.2 STL基本概念

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

2.3 STL六大组件

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

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

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

容器:置物之所也

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

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

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

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

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

算法:问题之解法

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

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

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

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

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

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

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

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

迭代器的种类:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xcbj6yZR-1679388470141)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1677637450118.png)]

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

2.5 容器算法迭代器初识

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

2.5.1 Vector存放内置数据类型

容器:vector

算法:for_each

迭代器:vector::iterator

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;
using std::for_each;

//vector容器存放内置数据类型

void myPrint(int val)
{
	cout << val << endl;
}
void test01()
{
	//创建一个vector容器,数组
	vector v;
	
	//向容器中插入数据
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);

	//通过迭代器访问容器中的数据
	vector::iterator itBegin = v.begin();//起始迭代器 指向容器中第一个元素
	vector::iterator itEnd = v.end();//结束迭代器 指向容器中最后一个元素的下一个位置
	
	//第一种遍历方式
	while (itBegin != itEnd)
	{
		cout << *itBegin << endl;
		itBegin++;
	}
	//第二种遍历方式
	for (vector::iterator it = v.begin(); it!=v.end(); it++)
	{
		cout << *it << endl;
	}
	//第三种遍历方式  利用STL提供遍历算法
	for_each(v.begin(), v.end(), myPrint);
}

int main()
{
	test01();
	return 0;
}
2.5.2 Vector存放自定义数据类型

学习目标:Vector中存放自定义数据类型,并打印输出

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;
using std::for_each;

//vector容器存放自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

void test01()
{
	vectorv;

	Person p1("aaa", 11);
	Person p2("bbb", 12);
	Person p3("ccc", 13);
	Person p4("ddd", 14);
	Person p5("eee", 15);

	//向容器中添加数据
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);

	//遍历容器中的数据 vector::iterator=auto(自动类型推导)
	// 为什么不直接写*it.name,因为编译器会认为:这是在访问it对象的成员name
	// 然后再将它解引用,(*it).m_Name是解引用指针it,得到地址指向的对象,
	// 然后访问其成员name
	//这是运算符优先级的知识,至于->和(*).的写法没有任何区别,功能相同,方法不同而已
	for (auto it=v.begin(); it !=v.end(); it++)
	{
		//cout << "姓名:" << (*it).m_Name << ",年龄:" << (*it).m_Age << endl;
		cout << "姓名:" << it->m_Name << ",年龄:" << it->m_Age << endl;
	}
}

//存放自定义数据类型 指针
void test02()
{
	vectorv;
	Person p1("aaa", 11);
	Person p2("bbb", 12);
	Person p3("ccc", 13);
	Person p4("ddd", 14);
	Person p5("eee", 15);

	//向容器中添加数据
	v.push_back(&p1);
	v.push_back(&p2);
	v.push_back(&p3);
	v.push_back(&p4);
	v.push_back(&p5);

	for (auto it = v.begin(); it != v.end(); it++)
	{
		Person* p = (*it); 
		//cout << "姓名:" << (*it).m_Name << ",年龄:" << (*it).m_Age << endl;
		cout << "姓名:" << p->m_Name << ",年龄:" << p->m_Age << endl;
	}
}


int main()
{
	test01();
	test02();
	return 0;
}
2.5.3 Vector容器嵌套容器

学习目标:容器中嵌套容器,我们将所有数据进行遍历输出

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;
using std::for_each;

//容器嵌套容器
void test01()
{
	vector>v;

	//创建小容器
	vectorv1;
	vectorv2;
	vectorv3;
	vectorv4;

	//向小容器中添加数据
	for (int i = 0; i < 4; i++)
	{
		v1.push_back(i + 1);//1
		v2.push_back(i + 2);//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>::iterator it = v.begin(); it != v.end(); it++)
	{
		//(*it) ---- 容器 vector
		for (vector::iterator vit = (*it).begin(); vit != (*it).end(); vit++)
		{
			cout << (*vit)<<" ";
		}
		cout << endl;
	}
}

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

3 STL- 常用容器

3.1 string容器

3.1.1 string基本概念

本质:

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

string和char*区别:

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

特点

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

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

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

3.1.2 string构造函数

构造函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//string的构造函数
void test01()
{
	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()
{
	test01();
	return 0;
}

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

3.1.3 string赋值操作

功能描述:

  • 给string字符串进行赋值

赋值的函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//string的构造函数
void test01()
{
	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.assign(str5);
	cout << "str6 = " << str6 << endl;

	string str7;
	str7.assign(10,'w');
	cout << "str7 = " << str7 << endl;
}

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

总结:

string的赋值方式有很多种,operator=这种方式是比较实用的

3.1.4 string字符串拼接

功能描述:

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

函数原型:

  • string& operator+=(const char* str); //重载+=操作符
  • string& operator+=(const char c); //重载+=操作符
  • string& operator+=(const string &s); //重载+=操作符
  • 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  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//string的构造函数
void test01()
{
	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 abcds", 4); 
	cout << "str3 = " << str3 << endl;

	str3.append(str2, 0, 3);
	//str3.append(str2);
	cout << "str3 = " << str3 << endl;
}

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

总结:字符串拼接版本很多,记几种即可

3.1.5 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 = 0)const; //从pos位置查找s的前n个字符第一次位置

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

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

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

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

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

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

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//字符串的查找和替换

//1、查找
void test01()
{
	string str1 = "abcdefgde";
	int pos = str1.find("de");
	if (pos==-1)
	{
		cout << "未找到字符串" << endl;
	}
	else 
	{
		cout << "找到字符串,pos = " << pos+1 << endl;
	}

	//rfind 和find区别
	//rfind从右往左查找		find从左到右查找
	pos = str1.rfind("de");
	cout << "pos = " << pos << endl;
}

//2、替换

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

int main()
{
	test01();
	test02();
	return 0;
}

总结:

  • find查找是从左到右,rfind查找从右到左
  • find找到字符串后返回查找的第一个字符位置,找不到返回-1
  • replace在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串
3.1.6 string字符串比较

功能描述

  • 字符串之间的比较

比较方式

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

= 返回 0

> 返回 1

< 返回 -1

函数原型

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

示例

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//字符串比较

void test01()
{
	string str1 = "xello";
	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()
{
	test01();
	return 0;
}

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

3.1.7 string字符串存取

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

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//string 字符存取

void test01()
{
	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';
	//xello
	cout << "str = " << str << endl;

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

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

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

3.1.8 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  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//字符串 插入和删除

void test01()
{
	string str = "hello";
	//插入
	str.insert(1, "111");
	//h111ello
	cout << "str = " << str << endl;

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

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

总结:插入和删除的起始下标都从0开始

3.1.9 string子串

功能描述:

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

函数原型

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

示例

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//string求子串

void test01()
{
	string str = "abcdef";

	string subStr = str.substr(1,3);

	cout << "subStr = " << subStr << endl;
}

//实用操作
void test02()
{
	string email = "[email protected]";
	//从邮件地址中 获取 用户名信息
	int pos = email.find("@");//8
	string userName = email.substr(0, pos);
	cout << userName << endl;
}

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

总结:灵活的运用求子串功能,可以在实际开发中获取有效的信息

3.2 vector容器

3.2.1 vector基本概念

功能

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

vector与普通数组区别

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

动态扩展

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LgvjGflk-1679388470142)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1678114810024.png)]

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

功能描述

  • 创建vector容器

函数原型

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

示例

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

void printVector(vector&v)
{
	for (vector::iterator it = v.begin(); it !=v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
//vector容器构造
void test01()
{
	vectorv1;//默认构造  无参构造
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}
	printVector(v1);

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

	//n个elem方式构造
	vectorv3(10, 100);//前面是个数,后面是赋值
	printVector(v3);

	//拷贝构造
	vectorv4(v3);
	printVector(v4);
}

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

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

3.2.3 vector赋值操作

功能描述

  • 给vector容器进行赋值

函数原型

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

示例

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

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

//vector赋值
void test01()
{
	vectorv1;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);

	}
	printVector(v1);

	//赋值 operator=
	vectorv2;
	v2 = v1;
	printVector(v2);

	//assign
	vectorv3;
	v3.assign(v1.begin(),v1.end());
	printVector(v3);

	//n个elem 方式赋值
	vectorv4;
	v4.assign(10, 100);
	printVector(v4);
}

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

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

3.2.4 vector容量和大小

功能描述

  • 对vector容器的大小和容量进行操作

函数原型

  • empty(); //判断容器是否为空
  • capacity(); //容器的容量
  • size(); //返回容器中元素的个数
  • resize(int num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

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

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

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//vector容量和大小操作

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


void test01()
{
	vectorv1;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}
	printVector(v1);

	if (v1.empty())
	{
		cout << "v1为空" << endl;
	}
	else
	{
		cout << "v1不为空" << endl;
		cout << "v1容量为:" <

总结:

  • 判断是否为空----empty
  • 返回元素个数----size
  • 返回容器容量----capacity
  • 重新指定大小----resize
3.2.5 vector插入和删除

功能描述

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

函数原型

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

示例

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//vector插入和删除操作

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


void test01()
{
	vectorv1;
	//尾插
	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);
	v1.push_back(40);
	v1.push_back(50);

	//遍历
	printVector(v1);

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

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

	v1.insert(v1.begin(), 2, 1000);
	printVector(v1);

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

	//清空(两种)
	v1.erase(v1.begin(), v1.end());
	//v1.clear();
	printVector(v1);
}

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

总结:

  • 尾插----push_back
  • 尾删----pop_back
  • 插入----insert(位置迭代器)
  • 删除----erase(位置迭代器)
  • 清空----clear
3.2.6 vector数据存取

功能描述

  • 对vector中的数据的存取操作

函数原型

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//vector容器  数据存取

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

void test01()
{
	vectorv1;
	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 << "第一个元素为:" << v1.front() << endl;
	//最后一个元素
	cout << "最后一个元素为:" << v1.back() << endl;
}

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

总结:

  • 除了用迭代器获取vector容器中元素,[]和at也可以
  • front返回容器第一个元素
  • back返回容器最后一个元素
3.2.7 vector互换容器

功能描述:

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

函数原型:

  • swap(vec); //将vec与本身的元素交换
#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//vector容器  数据存取

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

void test01()
{
	vectorv1;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}
	printVector(v1);

	vectorv2;
	for (int i = 10; i >0; i--)
	{
		v2.push_back(i);
	}
	printVector(v2);

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


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

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

	//利用swap收缩内存
	vector(v).swap(v); //创建匿名对象的声明,用完自动释放
	cout << "v的容量为:" << v.capacity() << endl;
	cout << "v的大小为:" << v.size() << endl;
}

int main()
{
	test01();
    test02();
	return 0;
}

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

3.2.8 vector预留空间

功能描述:

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

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;

//vector容器 预留空间

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

void test01()
{
	vectorv;
	//利用reserve预留空间
	v.reserve(100000);
	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;
}

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

总结:如果数据量较大,可以一开始利用reserve预留空间

3.3 deque容器

3.3.1 deque容器基本概念

功能

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

dequevector区别:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dpQNZinN-1679388470143)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1678285428410.png)]

deque内部工作原理:

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xh4uS9Uh-1679388470143)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1678286650549.png)]

  • deque容器的迭代器也是支持随机访问的
3.3.2 deque构造函数

功能描述:

  • deque容器构造

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::deque;

void printDeque(deque&d)
{
	for (deque::iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//deque容器
void test01()
{
	dequed1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	printDeque(d1);

	dequed2(d1.begin(), d1.end());
	printDeque(d2);

	dequed3(10,100);
	printDeque(d3);

	dequed4(d3);
	printDeque(d4);
}
int main()
{
	test01();
	return 0;
}

总结:deque容器和vector容器的构造方式几乎一样,灵活使用即可

3.3.3 deque赋值操作

功能:

  • deque容器赋值

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::deque;

void printDeque(deque&d)
{
	for (deque::iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//deque容器 赋值操作
void test01()
{
	dequed1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	printDeque(d1);
	// operator= 赋值
	dequed2;
	d2 = d1;
	printDeque(d2);

	//assign赋值
	dequed3;
	d3.assign(d1.begin(),d1.end());
	printDeque(d3);

	dequed4;
	d4.assign(10, 100);
	printDeque(d4);
}
int main()
{
	test01();
	return 0;
}

总结:deque容器和vector容器的赋值操作相同,需熟练掌握

3.3.4 deque大小操作

功能描述:

  • deque容器的大小进行操作

函数原型:

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

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

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

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

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

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::deque;

void printDeque(deque&d)
{
	for (deque::iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//deque容器 大小操作
void test01()
{
	dequed1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	printDeque(d1);
	if (d1.empty())
	{
		cout << "d1为空" << endl;
	}
	else
	{
		cout << "d1不为空" << endl;
		cout << "d1的大小为:" << d1.size() << endl;
		//deque容器没有容量概念
	}

	//重新指定大小
	//d1.resize(15);
	d1.resize(15,1);
	printDeque(d1);

	d1.resize(5);
	printDeque(d1);
}
int main()
{
	test01();
	return 0;
}

总结:

  • deque没有容量的概念
  • 判断是否为空 ----empty
  • 返回元素个数 ----size
  • 重新指定个数 ----resize
3.3.5 deque 插入和删除

功能描述:

  • deque容器中插入和删除数据

函数原型:

两端插入操作:

  • push_back(elem); //在容器尾部添加一个数据
  • push_front(elem); //在容器头部添加一个数据
  • pop_back(); //在容器尾部添加一个数据
  • pop_front(); //在容器尾部添加一个数据

指定位置操作:

  • insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置。
  • insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值。
  • insert(pos,beg,end); //在pos位置插入[beg,end]区间的数据,无返回值。
  • clear(); //清空容器的所有数据。
  • erase(beg,end); //删除[beg,end]区间的数据,返回下一个数据的位置。
  • erase(pos); //删除pos位置的数据,返回下一个数据的位置。
#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::deque;

//deque插入和删除

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

//两端操作
void test01()
{
	dequed1;

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

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

	printDeque(d1);

	//尾删法
	d1.pop_back();
	printDeque(d1);
	
	//头删法
	d1.pop_front();
	printDeque(d1);
}

void test02()
{
	dequed1;
	d1.push_back(10);
	d1.push_back(20);
	d1.push_front(100);
	d1.push_front(200);

	printDeque(d1);

	//insert插入
	d1.insert(d1.begin(),1000);
	printDeque(d1);

	d1.insert(d1.begin(), 2, 10000);
	printDeque(d1);

	//按照区间进行插入
	dequed2;
	d1.push_back(10);
	d1.push_back(20);
	d1.push_back(30);
	d1.insert(d1.begin(), d2.begin(), d2.end());
	printDeque(d1);
}

void test03()
{
	dequed1;
	d1.push_back(10);
	d1.push_back(20);
	d1.push_front(100);
	d1.push_front(200);

	//删除
	deque::iterator it = d1.begin();
	it++;
	d1.erase(it);
	printDeque(d1);

	//按照区间的方式删除
	d1.erase(d1.begin(),d1.end());//效果等于d1.clear();
	printDeque(d1);



}

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

总结:

  • 插入和删除提供的位置是迭代器!
  • 尾插 — push_back
  • 尾删 — pop_back
  • 头插 — push_front
  • 头删 — pop_front
3.3.6 deque 数据存取

功能描述:

  • deque中的数据的存取操作

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::deque;

//deque容器数据存取

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

//两端操作
void test01()
{
	dequed1;

	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] << " ";
	}
	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()
{
	test01();
	return 0;
}

总结:

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

功能描述:

  • 利用算法实现对deque容器进行排序

算法:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::deque;

//deque容器排序

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

//两端操作
void test01()
{
	dequed1;

	d1.push_back(10);
	d1.push_back(20);
	d1.push_back(30);
	d1.push_front(100);
	d1.push_front(200);
	d1.push_front(300);

	printDeque(d1);

	//排序	默认排序规则 从小到大 升序
	//对于支持随便访问的迭代器的容器,都可以利用sort算法直接对其进行排序
	//vector容器也可以利用 sort进行排序
	sort(d1.begin(),d1.end());
	cout << "排序后:" << endl;
	printDeque(d1);
}

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

总结:sort算法非常实用,使用时要包含头文件algorithm即可

3.4 案例-评委打分

3.4.1 案例描述

有五名选手:选手ABCDE,10个评委分别对每一名选手打分,去除最高分,去除最低分,取平均分

3.4.2 实现步骤
  1. 创建五名选手,放在vector中
  2. 遍历vector容器,取出每一个选手,执行for循环,可以把10个评分打分存到deque容器中
  3. sort算法对deque容器中分数排序,去除最高和最低分
  4. deque容器遍历一遍,累加总分
  5. 获取平均分

示例代码:

#include  
#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;
using std::deque;
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&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& v)
{
	for (vector::iterator it = v.begin(); it != v.end(); it++)
	{
		//将评委的分数 放到deque容器
		dequed;
		for (int i = 0; i < 10; i++)
		{
			int score = rand() % 41 + 60;//60~100
			d.push_back(score);
		}

		//排序
		sort(d.begin(), d.end());

		//去除最高分和最低分
		d.pop_back();
		d.pop_front();

		//取平均分
		int sum = 0;
		for (deque::iterator dit=d.begin(); dit !=d.end(); dit++)
		{
			sum += *dit; //累加每个评委的分数
		}

		int average = sum / d.size();

		//将平均分赋给选手身上
		it->m_Score=average;
	}
}

//显示分数
void showScore(vector& v)
{
	for (vector::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << "姓名:" << (*it).m_Name << " 平均分::" << (*it).m_Score << endl;
	}
}
int main()
{
	//1、创建5名选手
	vectorv;//存放选手的容器
	createPerson(v);
	//2、给5名选手打分
	setScore(v);
	//3、显示最后得分
	showScore(v);

	return 0;
}

3.5 stack容器

3.5.1 stack 基本概念

概念:stack是一种先进后出的数据结构,它只有一个出口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8bRujuac-1679388470144)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1678785050678.png)]

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

栈中进入数据称为 — 入栈push

栈中弹出数据称为 — 出栈pop

3.5.2 stack 常用接口

功能描述:栈容器常用的对外接口

构造函数:

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

赋值操作:

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

数据存取:

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

大小操作:

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

示例:

#include  
#include  
#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;
using std::deque;
using std::stack;

//栈stack容器

void test01()
{
	//特点:符合先进后出数据结构
	stacks;

	//入栈
	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()
{
	test01();
	system("pause");
}

总结:

  • 入栈 — push
  • 出栈 — pop
  • 返回栈顶 — top
  • 判断栈是否为空 — empty
  • 返回栈的大小 — size

3.6 queue容器

3.6.1 queue 容器

概念:Queue是一种先进先出的数据结构,它有两个出口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rYtZpFy5-1679388470144)(C:\Users\qfc\AppData\Roaming\Typora\typora-user-images\1678795988535.png)]

队列容器允许从一端新增元素,从另一端移除元素

队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为

队列中进数据称为 — 入队 push

队列中出数据称为 — 出队 pop

3.6.2 queue 常用接口

功能描述:堆容器常用的对外接口

构造函数:

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

赋值操作:

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

数据存取:

  • push(elem); //向栈顶添加元素
  • pop(); //向栈顶移除第一个元素
  • back(); //返回最后一个元素
  • front(); //返回第一个元素

大小操作:

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

示例:

#include  
#include  
#include  
#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::vector;
using std::deque;
using std::stack;
using std::queue;

//队列queue容器

void test01()
{
	//特点:符合先进先出数据结构
	queueq;

	//入队
	q.push(10);
	q.push(20);
	q.push(30);
	q.push(40);

	cout << "堆的大小:" << q.size() << endl;
	//只要队列不为空,查看队头和队尾,并且执行出队操作
	while (!q.empty())
	{
		//查看队头元素
		cout << "队头元素是:" << q.front() << endl;
		//查看队尾元素
		cout << "队尾元素是:" << q.back() << endl;

		//出栈
		q.pop();
	}
	cout << "队列的大小:" << q.size() << endl;
}

int main()
{
	test01();
	system("pause");
}

总结:

  • 入栈 — push
  • 出栈 — pop
  • 返回队头 — front
  • 返回队尾 — back
  • 判断栈是否为空 — empty
  • 返回栈的大小 — size

3.7 list容器

3.7.1 list 基本概念

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

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

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

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

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

截图

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

list的优点:

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

list的缺点:

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

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

总结:

STLListvector是最常被使用的容器,各有优缺点

3.7.2 list 构造函数

功能描述:创建list容器

函数原型

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::list;

void printList(const list& L)
{
	for (list::const_iterator it = L.begin(); it !=L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//list容器构造函数

void test01()
{
	//创建list容器
	listL1;//默认构造

	//添加数据
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	//遍历容器
	printList(L1);

	//区间方式构造
	listL2(L1.begin(), L1.end());
	printList(L2);

	//拷贝构造
	listL3(L2);
	printList(L3);

	//n个elem
	listL4(10, 100);
	printList(L4);
}

int main()
{
	test01();
	system("pause");
}

总结:list构造方式同其他几个STL常用容器,熟练掌握即可

3.7.3 list 赋值和交换

功能描述:

  • 给list容器进行赋值,以及交换list容器

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::list;

void printList(const list& L)
{
	for (list::const_iterator it = L.begin(); it !=L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//list容器赋值和交换

//赋值
void test01()
{
	//创建list容器
	listL1;//默认构造

	//添加数据
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	//遍历容器
	printList(L1);

	listL2;
	L2 = L1;//operator=赋值
	printList(L2);

	listL3;
	L3.assign(L2.begin(), L2.end());
	printList(L3);

	listL4;
	L4.assign(10,100);
	printList(L4);
}

//交换
void test02()
{
	listL1;//默认构造

//添加数据
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	listL2;
	L2.assign(10, 1000);

	cout << "交换前:" << endl;
	printList(L1);
	printList(L2);

	L1.swap(L2);
	cout << "交换后:" << endl;
	printList(L1);
	printList(L2);
}

int main()
{
	test01();
	test02();
	system("pause");
}

总结:list交换和赋值操作能够灵活运用即可

3.7.4 list 大小操作

功能描述:

  • 对list容器的大小进行操作

函数原型:

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

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

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

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

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

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::list;

void printList(const list& L)
{
	for (list::const_iterator it = L.begin(); it !=L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//list容器大小操作

//赋值
void test01()
{
	//创建list容器
	listL1;//默认构造

	//添加数据
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	if (L1.empty())
	{
		cout << "L1为空" << endl;
	}
	else
	{
		cout << "L1不为空" << endl;
		cout << "L1的元素个数为:" <

总结:

  • 判断是否为空 — empty
  • 返回元素个数 — size
  • 重新指定个数 — resize
3.7.5 list 插入和删除

功能描述

  • 对list容器进行插入、删除操作

函数原型

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

示例

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::list;

//list插入和删除操作

void printList(list& L)
{
	for (list::iterator it = L.begin(); it != L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}


void test01()
{
	listL;
	
	//尾插
	L.push_back(10);
	L.push_back(20);
	L.push_back(30);

	//头插
	L.push_front(100);
	L.push_front(200);
	L.push_front(300);

	printList(L);

	//尾删
	L.pop_back();
	printList(L);

	//头删
	L.pop_front();
	printList(L);

	//insert插入
	list::iterator it = L.begin();
	L.insert(++it, 1000);
	printList(L);

	//删除
	it = L.begin();
	L.erase(++it);
	printList(L);

	//移除
	L.push_back(10000);
	L.push_back(10000);
	L.push_back(10000);
	L.push_back(10000);
	printList(L);
	L.remove(10000);
	printList(L);

	//清空
	L.clear();
	printList(L);
}

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

总结:

  • 尾插----push_back
  • 尾删----pop_back
  • 头插----pop_front
  • 头删----pop_front
  • 插入----insert(位置迭代器)
  • 删除----erase(位置迭代器)
  • 移除----remove
  • 清空----clear
3.7.6 list 数据存取

功能描述

  • 对list中的数据的存取

函数原型

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::list;

//list数据存取操作

void printList(list& L)
{
	for (list::iterator it = L.begin(); it != L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}


void test01()
{
	listL;
	
	L.push_back(10);
	L.push_back(20);
	L.push_back(30);
	L.push_back(40);

	//L[0] 不可以用[]访问list容器中的元素
	//L.at(0) 不可以用at方式访问list容器中的元素
	//原因是list本质链表,不是用连续空间存储数据,迭代器也是不支持随机访问的
	cout << "第一个元素是:" << L.front() << endl;
	cout << "最后一个元素是:" << L.back() << endl;

	//验证迭代器是不支持随机访问的
	list::iterator it = L.begin();
	it++; 
	it--;

	//it = it+1;//不支持随机访问
}

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

总结:

  • list容器中不可以通过[]或者at方式访问数据
  • front返回容器第一个元素
  • back返回容器最后一个元素
3.7.7 list 反转和排序

功能描述:

  • 将容器中的元素反转,以及将容器中的数据进行排序

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::list;

//list反转和排序操作

void printList(list& L)
{
	for (list::iterator it = L.begin(); it != L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}


void test01()
{
	listL;
	
	L.push_back(10);
	L.push_back(20);
	L.push_back(30);
	L.push_back(40);

	printList(L);
	//反转
	L.reverse();
	printList(L);

	//排序
	//所有不支持随机访问迭代器的容器,不可以用标准算法
	//不支持随机访问迭代器的容器,内部会提供对应的一些算法
	//sort(L.begin(),L.end());

	L.sort();//默认排序规则 从小到大 升序
	printList(L);
}

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

总结:

  • 反转 — reserve
  • 排序 — sort(成员函数)
3.7.8 排序案例

案例描述:将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高

排序规则:按照年龄进行升序,如果年龄相同按照身高进行降序

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::list;

//list容器 排序案例 对于自定义数据类型 做排序
//按照年龄进行升序,如果年龄相同按照身高进行降序

class Person
{
public:
	Person(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 comparePerson(Person& p1, Person& p2)
{
	//按照年龄 升序
	if (p1.m_Age==p2.m_Age)
	{
		return p1.m_Height < p2.m_Height;
	}
	else
	{
		return p1.m_Age < p2.m_Age;
	}
}

void test01()
{
	listL;
	
	Person p1("刘备", 35, 175);
	Person p2("曹操", 45, 180);
	Person p3("孙权", 40, 170);
	Person p4("赵云", 25, 190);
	Person p5("张飞", 35, 185);
	Person p6("关羽", 35, 200);

	L.push_back(p1);
	L.push_back(p2);
	L.push_back(p3);
	L.push_back(p4);
	L.push_back(p5);
	L.push_back(p6);
	
	for (list::iterator it = L.begin(); it != L.end(); it++)
	{
		cout << "姓名:" << (*it).m_Name << " 年龄:" << (*it).m_Age << " 身高:" << (*it).m_Height << endl;
	}

	//排序
	cout << "--------------------" << endl;
	cout << "排序后:" << endl;

	L.sort(comparePerson);
	for (list::iterator it = L.begin(); it != L.end(); it++)
	{
		cout << "姓名:" << (*it).m_Name << " 年龄:" << (*it).m_Age << " 身高:" << (*it).m_Height << endl;
	}

}

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

总结:

  • 对于自定义数据类型,必须要指定排序规则,否则编译器不知道如何进行排序
  • 高级排序只是在排序规则上再进行一次逻辑规则制定,并不复杂

3.8 set/multiset 容器

3.8.1 set 基本概念

简介:

  • 所有元素都会在插入时自动被排序

本质:

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

set和multiset区别:

  • set不允许容器中有重复元素
  • multiset允许容器中有重复元素
3.8.2 set 构造和赋值

功能描述:创建set容器以及赋值

构造:

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

赋值:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;

void printSet(set& s)
{
	for (set::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//set容器构造和赋值
void test01()
{
	sets1;

	//插入数据 只有insert方式
	s1.insert(10);
	s1.insert(20);
	s1.insert(30);
	s1.insert(40);
	s1.insert(30);

	//遍历容器
	//set容器特点:所有元素插入时候自动被排序
	//set容器不允许插入重复值
	printSet(s1);

	//拷贝构造
	sets2(s1);
	printSet(s2);

	//赋值
	sets3;
	s3 = s2;
	printSet(s3);
}

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

总结:

  • set容器插入数据时用insert
  • set插入数据的数据会自动排序
3.8.3 set大小和交换

功能描述:

  • 统计set容器大小以及交换set容器

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;

void printSet(set& s)
{
	for (set::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//set容器大小和交换

//大小
void test01()
{
	sets1;

	s1.insert(10);
	s1.insert(20);
	s1.insert(30);
	s1.insert(40);

	printSet(s1);

	//判断是否为空
	if (s1.empty())
	{
		cout << "s1为空" << endl;
	}
	else
	{
		cout << "s1不为空" << endl;
		cout << "s1的大小为" <s1;

	s1.insert(10);
	s1.insert(20);
	s1.insert(30);
	s1.insert(40);

	sets2;

	s2.insert(1000);
	s2.insert(2000);
	s2.insert(4000);
	s2.insert(3000);

	cout << "交换前的:" << endl;
	printSet(s1);
	printSet(s2);

	s2.swap(s1);
	cout << "交换后的:" << endl;
	printSet(s1);
	printSet(s2);
}

int main()
{
	test01();
	test02();
	return 0;
}

总结:

  • 统计大小 — size
  • 判断是否为空 — empty
  • 交换容器 — swap
3.8.4 set插入和删除

功能描述

  • 对set容器进行插入数据、删除数据操作

函数原型

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

示例

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;

//插入和删除操作

void printSet(set& s)
{
	for (set::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}


void test01()
{
	sets1;

	//插入
	s1.insert(10);
	s1.insert(30);
	s1.insert(20);
	s1.insert(40);
	printSet(s1);

	//删除
	s1.erase(s1.begin());
	printSet(s1);

	//删除重载版本
	s1.erase(30);
	printSet(s1);

	//清空
	s1.erase(s1.begin(), s1.end());
	//s1.clear();
	printSet(s1);
}

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

总结:

  • 插入----insert(位置迭代器)
  • 删除----erase(位置迭代器)
  • 清空----clear
3.8.5 set查找和统计

功能描述:

  • 对set容器进行查找数据以及统计数据

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;

//set查找和统计

void printSet(set& s)
{
	for (set::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test01()
{
	sets1;

	//插入
	s1.insert(10);
	s1.insert(30);
	s1.insert(20);
	s1.insert(40);
	printSet(s1);

	set::iterator pos = s1.find(20);
	if (pos != s1.end())
	{
		cout << "找到元素:" << *pos << endl;
		//统计30的个数 结果为1或0
		cout << "num= " << s1.count(30) << endl;
	}
	else
	{
		cout << "未找到元素:" << endl;
	}
}

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

总结:

  • 查找 — find (返回的是迭代器)
  • 统计 — count (对于set,结果为0或1)
3.8.6 set和multiset区别

学习目标:

  • 掌握set和multiset的区别

区别:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;
using std::multiset;
using std::pair;

//set查找和统计

void printSet(set& s)
{
	for (set::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test01()
{
	sets1;
    //插入
	pair::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;
	}

	multisetms;
	//允许插入重复数据
	ms.insert(10);
	ms.insert(10);
	ms.insert(10);
	ms.insert(10);

	for (multiset::iterator it = ms.begin(); it != ms.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

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

总结:

  • 如果不允许插入重复数据可以利用set
  • 如果需要插入重复数据利用multiset
3.8.7 pair对组的创建

功能描述:

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

两种创建方式:

  • pair p(value1,value2);
  • pair p=make_pair(value1,value2);

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;
using std::pair;
using std::make_pair;

//pair对组的创建

void test01()
{
	//第一种方式
	pairp("Tom", 15);
	cout << "姓名:" << p.first << ",年龄:" << p.second << endl;

	//第二种方式
	pairp2 = make_pair("Jerry", 30);
	cout << "姓名:" << p2.first << ",年龄:" << p2.second << endl;
}

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

总结:

两种方式都可以创建对组,记住一种即可

3.8.8 set容器排序

学习目标:

  • set 容器默认排序规则为从小到大,掌握如何改变规则

主要技术点:

  • 利用仿函数,可以改变排序规则

示例一 set存放内置数据规则

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;
using std::pair;
using std::make_pair;

//set容器排序
class MyCompare
{
public:
	bool operator()(int v1,int v2)const//这里如果运行报错,在仿函数bool那一行加一个const限定
	{
		return v1 > v2;
	}
};

void test01()
{
	sets1;
	s1.insert(10);
	s1.insert(50);
	s1.insert(20);
	s1.insert(40);
	s1.insert(30);

	for (set::iterator it = s1.begin(); it != s1.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;

	//指定排序规则为从大到小
	sets2;
	s2.insert(10);
	s2.insert(30);
	s2.insert(20);
	s2.insert(40);
	s2.insert(50);

	for (set::iterator it = s2.begin(); it != s2.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

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

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

示例二 set存放自定义数据类型

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;
using std::pair;
using std::make_pair;

//set容器排序,存放自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	string m_Name;
	int m_Age;
};

class comparePerson
{
public:
	bool operator()(const Person& p1, const Person& p2)const
	{
		//按照年龄降序
		return p1.m_Age > p2.m_Age;
	}
};

void test01()
{
	//自定义数据类型 都会指定排序
	sets;
	Person p1("刘备", 24);
	Person p2("关羽", 25);
	Person p3("张飞", 26);
	Person p4("赵云", 20);

	s.insert(p1);
	s.insert(p2);
	s.insert(p3);
	s.insert(p4);

	for (set::iterator it = s.begin(); it != s.end(); it++)
	{
		cout <<"姓名:" << it->m_Name <<" 年龄:" <m_Age<

总结:

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

3.9 map/multimap容器

3.9.1 map基本概念

简介:

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

本质:

  • map/multimap属于关联性容器,底层结构用二叉树实现

优点:

  • 可以根据可以key值快速找到value值

map/multimap的区别:

  • map不允许容器中有重复key值元素
  • multimap允许容器中有重复key值元素
3.9.2 map构造和赋值

功能描述:

  • 对map容器进行构造和赋值操作

函数原型:

构造:

  • map mp; //map默认构造函数
  • map //拷贝构造函数

赋值:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::pair;

//map容器 构造和赋值
void printMap(map& m)
{
	for (map::iterator it = m.begin(); it != m.end(); it++)
	{
		//cout <<"key=" << (*it).first << " value="<<(*it).second;两种方式都可以
		cout <<"key=" << it->first << " value="<second< m;
	m.insert(pair(1, 10));
	m.insert(pair(3, 30));
	m.insert(pair(2, 20));
	m.insert(pair(4, 40));


	//遍历容器
	//map容器特点:所有元素插入时候自动被排序
	//map容器不允许插入有重复的key值
	printMap(m);

	//拷贝构造
	mapm2(m);
	printMap(m2);

	//赋值
	map m3;
	m3 = m2;
	printMap(m3);
}

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

总结:

  • map中所有元素都是成对出现的,插入数据是要使用对组
3.9.3 map大小和交换

功能描述:

  • 统计map容器大小以及交换map容器

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::pair;

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

//map容器大小和交换

//大小
void test01()
{
	mapm;

	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);

	//判断是否为空
	if (m.empty())
	{
		cout << "m为空" << endl;
	}
	else
	{
		cout << "m不为空" << endl;
		cout << "m的大小为" << m.size() << endl;
	}
}

//交换
void test02()
{
	mapm;

	m.insert(pair< int, int>(1, 10));
	m.insert(pair< int, int>(2, 20));
	m.insert(pair< int, int>(3, 30));

	mapm2;

	m2.insert(pair< int, int>(4, 40));
	m2.insert(pair< int, int>(5, 50));
	m2.insert(pair< int, int>(6, 60));

	cout << "交换前的:" << endl;
	printMap(m);
	printMap(m2);

	m2.swap(m);
	cout << "交换后的:" << endl;
	printMap(m);
	printMap(m2);
}

int main()
{
	test01();
	test02();
	return 0;
}

总结:

  • 统计大小 — size
  • 判断是否为空 — empty
  • 交换容器 — swap
3.9.4 map插入和删除

功能描述

  • 对map容器进行插入数据、删除数据操作

函数原型

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

示例

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::pair;
using std::make_pair;

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

//map容器插入和删除

//大小
void test01()
{
	mapm;

	//第一种
	m.insert(pair< int, int>(1, 10));

	//第二种
	m.insert(make_pair(2, 20));

	//第三种
	m.insert(map::value_type(3, 30));

	//第四种
	m[4] = 40;//不建议插入,用途:可以利用key访问value

	cout << m[5] << endl;
	printMap(m);

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

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

	m.erase(m.begin(), m.end());
	//m.clear();
	printMap(m);
}

//交换
void test02()
{
	mapm;

	m.insert(pair< int, int>(1, 10));
	m.insert(pair< int, int>(2, 20));
	m.insert(pair< int, int>(3, 30));

	mapm2;

	m2.insert(pair< int, int>(4, 40));
	m2.insert(pair< int, int>(5, 50));
	m2.insert(pair< int, int>(6, 60));

	cout << "交换前的:" << endl;
	printMap(m);
	printMap(m2);

	m2.swap(m);
	cout << "交换后的:" << endl;
	printMap(m);
	printMap(m2);
}

int main()
{
	test01();
	test02();
	return 0;
}

总结:

  • 插入----insert(位置迭代器)
  • 删除----erase(位置迭代器)
  • 清空----clear
3.9.5 map查找和统计

功能描述:

  • 对map容器进行查找数据以及统计数据

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::pair;
using std::multimap;

//map查找和统计

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

void test01()
{
	mapm;

	//插入
	m.insert(pair(1, 10));
	m.insert(pair(2, 20));
	m.insert(pair(3, 30));
	m.insert(pair(4, 40));
	printMap(m);

	map::iterator pos = m.find(3);
	if (pos != m.end())
	{
		cout << "找到元素 key=" << pos->first <<" value="<second << endl;
		//map不允许插入重复key元素,count的结果而言 要么是0 要么是1
		//multimap的count统计可能大于1
		cout << "num= " << m.count(3) << endl;
	}
	else
	{
		cout << "未找到元素:" << endl;
	}
}

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

总结:

  • 查找 — find (返回的是迭代器)
  • 统计 — count (对于map,结果为0或1)
3.9.6 map容器排序

学习目标:

  • map容器默认排序规则为按照key值进行从小到大排序,掌握如何改变规则

主要技术点:

  • 利用仿函数,可以改变排序规则

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::pair;
using std::make_pair;

//map容器排序

class MyCompare
{
public:
	bool operator()(int v1, int v2)const//这里如果运行报错,在仿函数bool那一行加一个const限定
	{
		return v1 > v2;
	}
};

void test01()
{
	//指定排序规则为从大到小
	mapm;
	m.insert(make_pair(1, 10));
	m.insert(make_pair(2, 20));
	m.insert(make_pair(3, 30));
	m.insert(make_pair(4, 40));
	m.insert(make_pair(5, 50));

	for (map::iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "key=" << it->first << " value=" << it->second << endl;
	}
	cout << endl;
}

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

总结:

  • 利用仿函数可以指定set容器的排序规则
  • 对于自定义数据类型,map必须指定排序规则才可以插入数据

3.10 案例-员工分组

3.10.1 案例描述
  • 公司今天招聘了10个员工(ABCDEFGHI) ,10名员工进入公司之后,需要指派员工在那个部门工作
  • 员工信息有:姓名、工资组成; 部门分为:策划、美术研发
  • 随机给10名员工分配部门和工资
  • 通过multimap进行信息的插入 key(部门编号) value(员工)
  • 分部门显示员工信息
3.10.2 实现步骤
  1. 创建10名员工,放到vector中
  2. 遍历vector容器
  3. 分组后,将员工部门编导作为key,具体员工作为value,放入到multimap容器中
  4. 分部门显示员工信息

案例代码:

#include  
#include  
#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::multimap;
using std::vector;
using std::pair;
using std::make_pair;

#define CEHUA 0
#define MEISHU 1
#define YANFA 2

class Worker
{
public:
	string m_Name;
	int m_Income;
};
void createWorker(vector& v)
{
	string nameSeed = "ABCDEFGHIJ";
	for (int i = 0; i < 10; i++)
	{
		Worker worker;
		worker.m_Name = "员工";
		worker.m_Name += nameSeed[i];

		worker.m_Income = rand() % 10000 + 10000;//10000~19999
		//将员工放入到容器中
		v.push_back(worker);
	}
}
void setGroup(vector& v,multimap& m)
{
	for (vector::iterator it = v.begin(); it !=v.end(); it++)
	{
		//产生随机部门编号
		int depId = rand() % 3;//0 1 2 

		//将员工插入到分组中
		//key部门编号,value具体员工
		m.insert(make_pair(depId, *it));
	}
}
void showWorkerByGroup(multimap& m)
{
	//0 A B C 1 D E 2 F G 
	cout << "策划部门:" << endl;
	multimap::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 << " 工资:" << pos->second.m_Income << 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 << " 工资:" << pos->second.m_Income << 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 << " 工资:" << pos->second.m_Income << endl;
	}	
}

void test01()
{
	//创建员工
	vectorvWorker;
	createWorker(vWorker);

	//员工分组
	multimapmWorker;
	setGroup(vWorker, mWorker);

	//分组显示员工
	showWorkerByGroup(mWorker);
}

int main()
{
	srand((unsigned int)time(NULL));
	test01();
	return 0;
}

4 STL - 函数对象

4.1 函数对象

4.1.1 函数对象概念
}
cout << endl;

}

void test01()
{
sets1;
//插入
pair ret = s1.insert(10);
if (ret.second)
{
cout << “第一次插入成功” << endl;
}
else
{
cout << “第一次插入失败” << endl;
}
ret = s1.insert(10);
if (ret.second)
{
cout << “第二插入成功” << endl;
}
else
{
cout << “第二次插入失败” << endl;
}

multisetms;
//允许插入重复数据
ms.insert(10);
ms.insert(10);
ms.insert(10);
ms.insert(10);

for (multiset::iterator it = ms.begin(); it != ms.end(); it++)
{
	cout << *it << " ";
}
cout << endl;

}

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


总结:

+ 如果不允许插入重复数据可以利用set
+ 如果需要插入重复数据利用`multiset`

##### 3.8.7 pair对组的创建

功能描述:

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

两种创建方式:

+ `pair p(value1,value2);`
+ `pair p=make_pair(value1,value2);`

示例:

```c++
#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;
using std::pair;
using std::make_pair;

//pair对组的创建

void test01()
{
	//第一种方式
	pairp("Tom", 15);
	cout << "姓名:" << p.first << ",年龄:" << p.second << endl;

	//第二种方式
	pairp2 = make_pair("Jerry", 30);
	cout << "姓名:" << p2.first << ",年龄:" << p2.second << endl;
}

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

总结:

两种方式都可以创建对组,记住一种即可

3.8.8 set容器排序

学习目标:

  • set 容器默认排序规则为从小到大,掌握如何改变规则

主要技术点:

  • 利用仿函数,可以改变排序规则

示例一 set存放内置数据规则

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;
using std::pair;
using std::make_pair;

//set容器排序
class MyCompare
{
public:
	bool operator()(int v1,int v2)const//这里如果运行报错,在仿函数bool那一行加一个const限定
	{
		return v1 > v2;
	}
};

void test01()
{
	sets1;
	s1.insert(10);
	s1.insert(50);
	s1.insert(20);
	s1.insert(40);
	s1.insert(30);

	for (set::iterator it = s1.begin(); it != s1.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;

	//指定排序规则为从大到小
	sets2;
	s2.insert(10);
	s2.insert(30);
	s2.insert(20);
	s2.insert(40);
	s2.insert(50);

	for (set::iterator it = s2.begin(); it != s2.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

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

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

示例二 set存放自定义数据类型

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::set;
using std::pair;
using std::make_pair;

//set容器排序,存放自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	string m_Name;
	int m_Age;
};

class comparePerson
{
public:
	bool operator()(const Person& p1, const Person& p2)const
	{
		//按照年龄降序
		return p1.m_Age > p2.m_Age;
	}
};

void test01()
{
	//自定义数据类型 都会指定排序
	sets;
	Person p1("刘备", 24);
	Person p2("关羽", 25);
	Person p3("张飞", 26);
	Person p4("赵云", 20);

	s.insert(p1);
	s.insert(p2);
	s.insert(p3);
	s.insert(p4);

	for (set::iterator it = s.begin(); it != s.end(); it++)
	{
		cout <<"姓名:" << it->m_Name <<" 年龄:" <m_Age<

总结:

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

3.9 map/multimap容器

3.9.1 map基本概念

简介:

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

本质:

  • map/multimap属于关联性容器,底层结构用二叉树实现

优点:

  • 可以根据可以key值快速找到value值

map/multimap的区别:

  • map不允许容器中有重复key值元素
  • multimap允许容器中有重复key值元素
3.9.2 map构造和赋值

功能描述:

  • 对map容器进行构造和赋值操作

函数原型:

构造:

  • map mp; //map默认构造函数
  • map //拷贝构造函数

赋值:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::pair;

//map容器 构造和赋值
void printMap(map& m)
{
	for (map::iterator it = m.begin(); it != m.end(); it++)
	{
		//cout <<"key=" << (*it).first << " value="<<(*it).second;两种方式都可以
		cout <<"key=" << it->first << " value="<second< m;
	m.insert(pair(1, 10));
	m.insert(pair(3, 30));
	m.insert(pair(2, 20));
	m.insert(pair(4, 40));


	//遍历容器
	//map容器特点:所有元素插入时候自动被排序
	//map容器不允许插入有重复的key值
	printMap(m);

	//拷贝构造
	mapm2(m);
	printMap(m2);

	//赋值
	map m3;
	m3 = m2;
	printMap(m3);
}

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

总结:

  • map中所有元素都是成对出现的,插入数据是要使用对组
3.9.3 map大小和交换

功能描述:

  • 统计map容器大小以及交换map容器

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::pair;

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

//map容器大小和交换

//大小
void test01()
{
	mapm;

	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);

	//判断是否为空
	if (m.empty())
	{
		cout << "m为空" << endl;
	}
	else
	{
		cout << "m不为空" << endl;
		cout << "m的大小为" << m.size() << endl;
	}
}

//交换
void test02()
{
	mapm;

	m.insert(pair< int, int>(1, 10));
	m.insert(pair< int, int>(2, 20));
	m.insert(pair< int, int>(3, 30));

	mapm2;

	m2.insert(pair< int, int>(4, 40));
	m2.insert(pair< int, int>(5, 50));
	m2.insert(pair< int, int>(6, 60));

	cout << "交换前的:" << endl;
	printMap(m);
	printMap(m2);

	m2.swap(m);
	cout << "交换后的:" << endl;
	printMap(m);
	printMap(m2);
}

int main()
{
	test01();
	test02();
	return 0;
}

总结:

  • 统计大小 — size
  • 判断是否为空 — empty
  • 交换容器 — swap
3.9.4 map插入和删除

功能描述

  • 对map容器进行插入数据、删除数据操作

函数原型

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

示例

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::pair;
using std::make_pair;

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

//map容器插入和删除

//大小
void test01()
{
	mapm;

	//第一种
	m.insert(pair< int, int>(1, 10));

	//第二种
	m.insert(make_pair(2, 20));

	//第三种
	m.insert(map::value_type(3, 30));

	//第四种
	m[4] = 40;//不建议插入,用途:可以利用key访问value

	cout << m[5] << endl;
	printMap(m);

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

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

	m.erase(m.begin(), m.end());
	//m.clear();
	printMap(m);
}

//交换
void test02()
{
	mapm;

	m.insert(pair< int, int>(1, 10));
	m.insert(pair< int, int>(2, 20));
	m.insert(pair< int, int>(3, 30));

	mapm2;

	m2.insert(pair< int, int>(4, 40));
	m2.insert(pair< int, int>(5, 50));
	m2.insert(pair< int, int>(6, 60));

	cout << "交换前的:" << endl;
	printMap(m);
	printMap(m2);

	m2.swap(m);
	cout << "交换后的:" << endl;
	printMap(m);
	printMap(m2);
}

int main()
{
	test01();
	test02();
	return 0;
}

总结:

  • 插入----insert(位置迭代器)
  • 删除----erase(位置迭代器)
  • 清空----clear
3.9.5 map查找和统计

功能描述:

  • 对map容器进行查找数据以及统计数据

函数原型:

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

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::pair;
using std::multimap;

//map查找和统计

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

void test01()
{
	mapm;

	//插入
	m.insert(pair(1, 10));
	m.insert(pair(2, 20));
	m.insert(pair(3, 30));
	m.insert(pair(4, 40));
	printMap(m);

	map::iterator pos = m.find(3);
	if (pos != m.end())
	{
		cout << "找到元素 key=" << pos->first <<" value="<second << endl;
		//map不允许插入重复key元素,count的结果而言 要么是0 要么是1
		//multimap的count统计可能大于1
		cout << "num= " << m.count(3) << endl;
	}
	else
	{
		cout << "未找到元素:" << endl;
	}
}

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

总结:

  • 查找 — find (返回的是迭代器)
  • 统计 — count (对于map,结果为0或1)
3.9.6 map容器排序

学习目标:

  • map容器默认排序规则为按照key值进行从小到大排序,掌握如何改变规则

主要技术点:

  • 利用仿函数,可以改变排序规则

示例:

#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::pair;
using std::make_pair;

//map容器排序

class MyCompare
{
public:
	bool operator()(int v1, int v2)const//这里如果运行报错,在仿函数bool那一行加一个const限定
	{
		return v1 > v2;
	}
};

void test01()
{
	//指定排序规则为从大到小
	mapm;
	m.insert(make_pair(1, 10));
	m.insert(make_pair(2, 20));
	m.insert(make_pair(3, 30));
	m.insert(make_pair(4, 40));
	m.insert(make_pair(5, 50));

	for (map::iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "key=" << it->first << " value=" << it->second << endl;
	}
	cout << endl;
}

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

总结:

  • 利用仿函数可以指定set容器的排序规则
  • 对于自定义数据类型,map必须指定排序规则才可以插入数据

3.10 案例-员工分组

3.10.1 案例描述
  • 公司今天招聘了10个员工(ABCDEFGHI) ,10名员工进入公司之后,需要指派员工在那个部门工作
  • 员工信息有:姓名、工资组成; 部门分为:策划、美术研发
  • 随机给10名员工分配部门和工资
  • 通过multimap进行信息的插入 key(部门编号) value(员工)
  • 分部门显示员工信息
3.10.2 实现步骤
  1. 创建10名员工,放到vector中
  2. 遍历vector容器
  3. 分组后,将员工部门编导作为key,具体员工作为value,放入到multimap容器中
  4. 分部门显示员工信息

案例代码:

#include  
#include  
#include  
#include  
#include  
#include //标准算法头文件 

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::map;
using std::multimap;
using std::vector;
using std::pair;
using std::make_pair;

#define CEHUA 0
#define MEISHU 1
#define YANFA 2

class Worker
{
public:
	string m_Name;
	int m_Income;
};
void createWorker(vector& v)
{
	string nameSeed = "ABCDEFGHIJ";
	for (int i = 0; i < 10; i++)
	{
		Worker worker;
		worker.m_Name = "员工";
		worker.m_Name += nameSeed[i];

		worker.m_Income = rand() % 10000 + 10000;//10000~19999
		//将员工放入到容器中
		v.push_back(worker);
	}
}
void setGroup(vector& v,multimap& m)
{
	for (vector::iterator it = v.begin(); it !=v.end(); it++)
	{
		//产生随机部门编号
		int depId = rand() % 3;//0 1 2 

		//将员工插入到分组中
		//key部门编号,value具体员工
		m.insert(make_pair(depId, *it));
	}
}
void showWorkerByGroup(multimap& m)
{
	//0 A B C 1 D E 2 F G 
	cout << "策划部门:" << endl;
	multimap::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 << " 工资:" << pos->second.m_Income << 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 << " 工资:" << pos->second.m_Income << 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 << " 工资:" << pos->second.m_Income << endl;
	}	
}

void test01()
{
	//创建员工
	vectorvWorker;
	createWorker(vWorker);

	//员工分组
	multimapmWorker;
	setGroup(vWorker, mWorker);

	//分组显示员工
	showWorkerByGroup(mWorker);
}

int main()
{
	srand((unsigned int)time(NULL));
	test01();
	return 0;
}

4 STL - 函数对象

4.1 函数对象

4.1.1 函数对象概念

你可能感兴趣的:(黑马自主学习,c++,学习,算法)