C++初始

第一个C++程序

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

  • 创建项目
  • 创建文件
  • 编写代码
  • 运行程序
  1. 创建项目(hello world)
# include<iostream>
using namespace std;
int main() {
	cout << "hello world" << endl;
	system("pause");
	return 0;
}

注释

  1. 单行注释://描述信息
  2. 多行注释/描述信息/
    1. 通常放在一段代码的上方,对该代码做整体说明

变量

作用;方便我们管理内存空间 变量创建的语法: 数据类型 变量名= 变量初始值

常量

作用:用于记录程序中不可更改的数据 C++定义常量的两种方法:

  1. #define宏常量:#define 常量名 常量值
    1. 通常在文件上方定义,是一个常量,这个一般在主函数之外定义
  2. const修饰的变量,const 数据类型 变量名 = 常量值
    1. 通常在变量定义前加关键字const,修饰该变量为常量,不可修改
# include<iostream>
using namespace std;
//#define宏常量
#define Day 7
int main() {
	cout << "一周有" << Day << "天" << endl;
	int a = 10;
	//const修饰的变量
	const double pi = 3.14;
	cout << "圆周率为" << pi << endl;
	cout << "hello world " << "a = " << a << endl;
	system("pause");
	return 0;
}

关键字

不要用关键字给变量或者变量起名称

标识符命名规则

作用:C++规定给标识符(变量,常量)命名时有一套自己的规则,和其他编程的规则类似

数据类型

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

整型

作用:整型变量表示的是整数类型的数据 C++中可以表示整型有以下几种方式,区别在于所占内存空间不同

#include <iostream>
using namespace std;
int main() {
	//整型
	//1.短整型(2个字节)
	short num1 = 10;
	//2.整型(4个字节)
	int num2 = 10;
	//3.长整型
	long num3 = 10;
	//4.长长整型
	long long num4 = 10;
	cout << "num1=" << num1 << endl;
	cout << "num2=" << num2 << endl;
	cout << "num3=" << num3 << endl;
	cout << "num4=" << num4 << endl;
}

sizeof关键字

作用:利用这个关键字可以统计数据类型所占内存空间大小 语法:sizeof(数据类型/变量) 实例:

#include<iostream>
using namespace std;
int main() {
	short num1 = 10;
	//sizeof参数既可以传入num1也可以传入short
	cout << "num1占用的内存空间为" << sizeof(num1) << endl;
	cout << "short占用的内存空间为" << sizeof(short) << endl;
	system("pause");
	return 0;
}

实型(浮点型)

作用:用于表示小数 浮点型变量分为2种

  1. 单精度float
  2. 双精度double 二者的区别在于表示的有效数字范围不同。
#include <iostream>
using namespace std;
int main() {
	//单精度float
	float f1 = 1.f;//注意定义单精度时,多加一个f否则默认双精度
	//双精度double
	double f2 = 1.;
	cout << "f1=" << f1 << " f1占用的空间" << sizeof(f1) << endl;
	cout << "f2=" << f2 << " f2占用的空间" << sizeof(f2) << endl;
	//科学计数法
	float f3 = 3e2;//3*10^2;
	float f4 = 3e-2;
	cout << "f3= " << f3 << " f4=" << f4 << endl;
}

字符型

作用:字符型变量用于显示单个字符 语法:char ch = 'a';

  • 注意1:在显示字符型变量时,用单引号将字符括起来,不要用双引号
  • 注意2:单引号内只能由一个字符,不可是字符串
  • C和C++字符型变量只占用1个字节
  • 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元
#include<iostream>
using namespace std;
int main() {
	//字符变量创建方法
	char ch = 'a';
	cout << "ch=" << ch << endl;
	//字符变量所占内存大小
	cout << "ch所占内存大小" << sizeof(ch) << endl;
	//字符型变量常见错误
	// 不能使用双引号创建字符
	//char ch2 = "b";
	//单引号里面只能由一个字符
	//char ch3 = 'fsfs'
	//字符型变量对应的ASCII编码
    //a-97
    //A-65
	cout << (int)ch << endl;
}

转义字符

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

字符串型

作用:用于表示一串字符 两种风格

  1. c风格字符串:char 变量名[] = "字符串值" 实例
int main(){
    char str1[] = "hello world";
    cout << str1 << endl;
}

注意:C风格的字符串要用双引号括起来 2. C++风格的字符串:string 变量名 = "字符串值"

#include<iostream>
#include<string>//用C++风格的字符串时,需要包含这个头文件
using namespace std;
int main() {
	//C风格的字符串
	char str[] = "hello world";
	cout << str << endl;
	//C++风格字符串
	string str2 = "hello world2";
	cout << str2 << endl;
}

布尔类型bool

作用:布尔数据类型代表真或假的值 bool类型两个值:true和false,bool类型只占一个字节大小 实例:

#include<iostream>
using namespace std;
int main() {
	//创建bool数据类型
	bool flag = true;//true代表真
	cout << flag << endl;
	//查看bool类型所占空间
	cout << "bool类型所占的空间为:" << sizeof(flag) << endl;
    system("pause");
    return 0;
}

数据的输入

作用:用于从键盘获取数据 关键字:cin 语法cin>>变量 实例:

#include<iostream>
#include<string>
using namespace std;
int main() {
	//键盘输入语句
	//整型
	int a = 0;
	cout << "请给整型变量a赋值" << endl;
	cin >> a;
	cout << "整型变量a=" << a << endl;
	//浮点型
	float f = 3.14;
	cout << "请给浮点型变量f赋值:" << endl;
	cin >> f;
	cout << "浮点型变量f=" << f << endl;
	//字符型
	char ch = 'a';
	cout << "请给字符型变量ch赋值" << endl;
	cin >> ch;
	cout << "字符型变量ch=" << ch <<  endl;
	//字符串型
	string str = "hello";
	cout << "请给字符串str赋值" << endl;
	cin >> str;
	cout << "字符串型变量str=" << str <<endl;
	bool flag = false;
	cout << "请给布尔类型的flag赋值"  << endl;
	cin >> flag;
	cout << "布尔类型的变量flag=" << flag << endl;
	system("pause");
	return 0;
}

运算符

  1. 算数运算符
#include<iostream>
using namespace std;
int main() {
	//加减乘除
	int a1 = 10;
	int b1 = 3;
	cout << a1 + b1 << endl;
	cout << a1 - b1 << endl;
	cout << a1 * b1 << endl;
	cout << a1 / b1 << endl;//两个整数相除,结果仍然是整数,将小数部分去除
	system("pause");
	return 0;
}
  1. 递增操作
#include<iostream>
using namespace std;
int main() {
	//前置递增,先变量+1,后进行表达式运算
	int a2 = 10;
	int b2 = ++a2 * 10;
	cout << "a2=" << a2 << endl;
	cout << "b2 = " << b2 << endl;
	//后置递增,先进行表达式运算,后让变量+1
	int a3 = 10;
	int b3 = a3++ * 10;
	cout << "a3 =" << a3 << endl;
	cout << "b3 = " << b3 << endl;
	//输出
	/*a2=11
	b2 = 110
	a3 =11
	b3 = 100*/
}

赋值运算符

作用:用于将表达式的值赋给变量 赋值运算符包括以下几个符号

比较运算符

逻辑运算符

程序流程结构

C/C++支持三种程序运行结构:顺序,选择,循环

选择语句

  • 单行if语句
  • 多行if语句
  • 多条件的if语句
  1. 单行if语句
#include<iostream>
using namespace std;
int main() {
	//选择结构,单行if语句
	//用户输入分数,如果分数大于600,视为考上一本大学,在屏幕输出
	//用户输入分数
	int score = 0;
	cout << "请输入一个分数" << endl;
	cin >> score;
	//打印用户输入的分数
	cout << "您输入的分为:" << score << endl;
	//判断分数是否大于600
	if (score > 600) {//注意if后面千万不能加分号,不然{}里面的判断都是白写了,是否满足都会输出{}里面的结果
		cout << "恭喜您考上一本大学" << endl;
	}
}
  1. 多行if语句
#include<iostream>
using namespace std;
int main() {
	//选择结构,单行if语句
	//用户输入分数,如果分数大于600,视为考上一本大学,在屏幕输出
	//用户输入分数
	int score = 0;
	cout << "请输入一个分数" << endl;
	cin >> score;
	//打印用户输入的分数
	cout << "您输入的分为:" << score << endl;
	//判断分数是否大于600
	if (score > 600) {//注意if后面千万不能加分号,不然{}里面的判断都是白写了,是否满足都会输出{}里面的结果
		cout << "恭喜您考上一本大学" << endl;
	}
	else {
		cout << "很可惜,你没有考上一本大学" << endl;
	}
}
  1. 多条件的if语句
#include<iostream>
using namespace std;
int main() {
	//选择结构,单行if语句
	//用户输入分数,如果分数大于600,视为考上一本大学,在屏幕输出
	//用户输入分数
	int score = 0;
	cout << "请输入一个分数" << endl;
	cin >> score;
	//打印用户输入的分数
	cout << "您输入的分为:" << score << endl;
	//判断分数是否大于600
	if (score > 600) {//注意if后面千万不能加分号,不然{}里面的判断都是白写了,是否满足都会输出{}里面的结果
		cout << "恭喜您考上一本大学" << endl;
	}
	else if(score > 400){
		cout << "恭喜你考上二本大学" << endl;
	}
	else {
		cout << "很可惜,你没有考上本科" << endl;
	}
}
  1. 嵌套if语句 案例需求:
  • 提示用户输入一个高考分数,根据分数做如下判断
  • 分数如果大于600分视为考上一本,大于500视为考上二本,大于400视为考上三本,其余视为未考上本科
  • 在一本分数种,如果大于700分,考入北大,大于650考入清华,大于600考入人大.
#include<iostream>
using namespace std;
int main(){
    int score = 0;
    //输入
    cout << "请输入分数" << endl;
    cin >> score;
    if (score > 600){
        cout <<"恭喜你考上一本"<< endl;
        if (score > 700){
            cout << "恭喜你考上北大" << endl;
        }else if(score > 650){
            cout << "恭喜考上清华" <<endl;
        }else{
            cout << "恭喜考上人大" <<endl;
        }
    }else if(score > 500){
        cout << "恭喜考上二本" << endl;
    }else if(score > 400){
        cout << "恭喜考上三本" << endl;
    }
}

练习案例,三只小猪称体重,判断谁更重

#include<iostream>
using namespace std;
int main() {
	int num1 = 0;
	int num2 = 0;
	int num3 = 0;
	int temp = 0;
	//输入三只小猪的体重
	cout << "请输入第一只小猪的体重" << endl;
	cin >> num1;
	cout << "请输入第二只小猪的体重" << endl;
	cin >> num2;
	cout << "请输入第三只小猪的体重" << endl;
	cout << "三只小猪的体重分别为" << num1 << num2 << num3 << endl;
	cin >> num3;
	if (num1 > num2)
	{
		if (num1 > num3)
		{
			cout << "小猪1是最重的" << endl;
		}
		else
		{
			cout << "小猪3是最重的" << endl;
		}
	}
	else
	{
		if (num2> num3)
		{
			cout << "小猪2是最重的" << endl;
		}
		else
		{
			cout << "小猪3是最重的" << endl;
		}
	}
}

三目运算符

作用:通过三目运算符实现简单的判断 语法:表达式1 ? 表达式2 : 表达式3 解释 如果表达式1的值为真,执行表达式2,并返回表达式2的结果; 如果表达式1值为假,执行表达式3,并返回表达式3的结果 实例:

#include<iostream>
using namespace std;
int main() {
	int a = 10;
	int b = 20;
	int c = 0;
	c = a > b ? a : b;
	cout << "c=" << c << endl;
	//在c++中三目运算符返回的式变量,可以继续赋值
	(a > b ? a : b) = 200;
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;//注意a没有发生任何变化,b变成了200
}

switch语句

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

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

表达式,只能放一个整型或者字符型,不可以判断一个区间 优点是结构清晰,执行效率高

#include<iostream>
using namespace std;
int main() {
	/*
	switch语句
	给电影打分
	10-9经典
	8-7非常好
	6-5一般
	5以下烂片
	*/
	//提示用户输入
	cout << "请输入你的打分0-10" << endl;
	//用户开始打分
	int score = 0;
	cin >> score;
	cout << "你打的分为:" << score << endl;
	switch (score)
	{
	case 10:
		cout << "您认为式经典电影" << endl;
		break;
	case 9:
		cout << "您认为是经典电影" << endl;
		break;
	case 8:
	case 7:
		cout << "您认为电影非常好" << endl;
		break;
	case 6:
	case 5:
		cout << "您认为电影一般" << endl;
		break;
	default:
		cout << "您认为电影很烂" << endl;
		break;
	}
}

循环结构

while循环语句

作用:满足循环条件,执行循环语句 语法:while(循环条件){循环语句} 解释:只要循环条件的结果为真,就执行循环语句

#include<iostream>
using namespace std;
int main() {
	int num = 0;
	while (num<10)
	{
		cout << "num=" << num << endl;
		num++;
	}
}

案例练习:猜数字(0-100)之间

#include<iostream>
using namespace std;
int main() {
	//系统生成随机数
	int num = rand() % 100 + 1;//rand()%生成0~99随机数
	//玩家进行猜测
	int guess = 0;
	int count = 0;
	while (true)
	{
		count++;
		cout << "请输入你猜的数" << endl;
		cin >> guess;
		if (guess == num)
		{
			cout << "恭喜你,你猜对了,一共猜了" << count <<"次" << endl;
			break;
		}
		else
		{
			if (guess>num)
			{
				cout << "你猜的太大了" << endl;
			}
			else
			{
				cout << "你猜的太小了" << endl;
			}
		}
	}
	//判断玩家的猜测
	//猜对,退出游戏
	//猜错,提示过大过小,返回第二步

}

do...while语法

作用:满足循环条件,执行循环语句 语法:do{循环语句}while(循环条件); 注意:与while的区别在于do...while会先执行一次循环语句,在判断循环条件

#include<iostream>
using namespace std;
int main() {
	//do while 输出0-9
	int num = 0; 
	do
	{
		cout << "num=" << num << endl;
		num++;
	} while (num<10);
}

for循环语句

作用:满足循环条件,执行循环语句 语法:for(起始表达式;条件表达式;末尾循环体){循环语句;} 实例

int main(){
    for(int i = 0;i < 10;i++){
        cout << i << endl;
    }
}

案例:敲桌子,从1到100,如果数字个位含有7,或者数字十位含有7,或者该数字是7的倍数,我们打印敲桌子,其余数字直接打印输出

#include<iostream>
using namespace std;
int main() {
	for (int i = 1; i <= 100; i++) {
		if ((i%10==7)||(i%7==0)||(i/10==7))
		{
			cout << "敲桌子" << endl;
		}
		else
		{
			cout << i << endl;
		}
	}
}

嵌套循环

作用:循环之内再嵌套一层循环

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

案例:乘法口诀表

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

跳转语句

  1. break语句 作用:用于跳出选择结构或者循环结构 break使用的时机:
  • 出现switch条件语句中,作用是终止case并跳出switch
  • 出现在循环语句中,作用就是跳出当前循环语句
  • 出现在嵌套循环中,跳出最近的内层循环语句
  1. continue语句 作用:在循环中,跳过本次循环为执行的语句,进入下一次循环
  2. goto语句 作用:无条件跳转语句 语法:goto标记
#include<iostream>
using namespace std;
int main() {
	cout << "1,sfs" << endl;
	cout << "2,sfs" << endl;
	goto FLAG;
	cout << "3,sfs" << endl;
	cout << "4,sfs" << endl;
	cout << "5,sfs" << endl;
	FLAG:
	cout << "6,sfs" << endl;
}

数组

概述

所谓数组,就是一个集合,里面放了很多相同类型的数据元素 特点1:数组中每个数据元素都是相同的数据类型 特点2:数组是由连续内存位置组成的

一维数组

定义方式:

  • 数据类型 数组名[数组长度];
  • 数据类型 数组名[数组长度] = {值1,值2,...}
  • 数据类型 数组名[] = {值1,值2,...}
#include<iostream>
using namespace std;
int main() {
	/*定义方式:
* 数据类型 数组名[数组长度];
* 数据类型 数组名[数组长度] = {值1,值2,...}
* 数据类型 数组名[] = {值1,值2,...}
*/
	// 1. 数据类型 数组名[数组长度]
	int arr[5];
	arr[0] = 10;
	arr[1] = 20;
	arr[2] = 30;
	arr[3] = 40;
	arr[4] = 50;
	//访问数据元素
	cout << arr[0] << endl;
	//2.数据类型 数组名[数组长度] = {值1,值2,。。。}
	int arr2[5] = { 10,20,30,40,50 };//如果在初始化的时候没有全部填写完,会用0填补剩余数据
	for (int i = 0; i < 5; i++)
	{
		cout << arr[i] << endl;
	}
	// 3.数据类型 数组名[] = {值1,值2.。。};
	int arr3[] = { 3,3,25,652,24,62 };
}

一维数组的数组名

一维数组名的用途:

  1. 可以统计整个数组在内存中的长度:sizeof(arr)
  2. 可以获取数组在内存中的首地址:cout << endl 实例:
int arr3[] = { 3,3,25,652,24,62 };
//整个数组占用内存大小
cout << "arr3整个数组占用空间" << sizeof(arr3) << endl;
//这个数组有多少元素
cout << "arr3元素个数" << sizeof(arr3) / sizeof(arr3[0]) << endl;
//数组首地址
cout << "数组arr3首地址" << (int)arr << endl;
//数组中第一个元素的地址
cout << "数组中第一个元素地址为:" << (int)&arr[0] << endl;
//说明首地址和第一个元素地址相等
//第二个元素地址
cout << "数组中第二个元素地址为:" << (int)&arr[1] << endl;
//说明数组中每个元素紧挨着
//注意:数组名是常量,不可以进行赋值操作

案例:五只小猪称体重 在一个数组中记录了五只小猪的体重,如:int arr[5] = [300,350,200,400,250]找出并打印最重的小猪体重

#include<iostream>
using namespace std;
int main() {
	//在一个数组中记录了五只小猪的体重,如:int arr[5] = [300,350,200,400,250]找出并打印最重的小猪体重
	int arr[5] = { 300, 350, 200, 400, 250 };
	int max = 0;
	int num = 0;
	for (int i = 0; i < size(arr); i++)
	{
		if (arr[i] > max)
		{
			max = arr[i];
			num = i;
		}
	}
	cout << "最重的小猪是" << num << "号" << "他的体重是" << max << endl;
}

案例:数组元素逆置 请申请一个5元素的数组,并且将元素逆置

#include<iostream>
using namespace std;
int main() {
	int arr[5] = {300, 350, 200, 400, 250};
	int start = 0;//起始元素下标
	int temp = 0;
	int end = sizeof(arr) / sizeof(arr[0]) - 1;//结束位置下标
	cout << "原数组为" << endl;
	for (int i = 0; i < size(arr); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	for (int i = 0; i < size(arr); i++)
	{
		if (start == end)
		{
			break;
		}
		temp = arr[start];
		arr[start] = arr[end];
		arr[end] = temp;
		start++;
		end--;
	}
	cout << "调整后的数组为" << endl;
	for (int i = 0; i < size(arr); i++)
	{
		cout << arr[i] << " ";
	}
}

冒泡排序

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

  1. 比较相邻的元素,如果第一个比第二个大,就交换他们两个
  2. 对每一对相邻的元素做同样的工作,执行完毕后,找到第一个最大值
  3. 重复以上步骤,每次比较次数-1,直到不需要比较
#include<iostream>
using namespace std;
int main() {
	//利用冒泡排序实现升序排序
	int arr[9] = { 4,2,8,0,5,7,1,3,9 };
	int temp = 0;
	cout << "排序前" << endl;
	for (int i = 0; i < size(arr); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	for (int i = 0; i < size(arr)-1; i++)
	{
		for (int j = 0; j < size(arr)-i-1; j++)
		{
			if (arr[j]>arr[j+1])
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
	cout << "排序后" << endl;
	for (int i = 0; i < size(arr); i++)
	{
		cout << arr[i] << " ";
	}
}

二维数组

二维数组就是在一维数组上多加一个维度 二维数组定义的四种方式

  1. 数据类型 数组名[行数][列数];
  2. 数据类型 数组名[行数][列数] = {{数据1,数据2},{数据1,数据2}};
  3. 数据类型 数组名[行数][列数] = {数据1,数据2,数据3,数据4};
  4. 数据类型 数组名[][列数] = {数据1,数据2,数据3,数据4}; 推荐使用第二种,因为她更加直观,可以提高代码的可读性
#include<iostream>
using namespace std;
int main() {
	/*
		二维数组定义的四种方式
	1. 数据类型 数组名[行数][列数];
	2. 数据类型 数组名[行数][列数] = {{数据1,数据2},{数据1,数据2}};
	3. 数据类型 数组名[行数][列数] = {数据1,数据2,数据3,数据4};
	4. 数据类型 数组名[][列数] = {数据1,数据2,数据3,数据4};
*/
	int arr[2][3];
	int arr1[2][3] = { {3,3,3},{3,2,4} };
	int arr2[][3] = { {3,3,3},{3,2,4} };
	int arr3[2][3] = { 3,3,3,4,4,4 };
}

二维数组的别名

  • 查看二维数组所占内存空间
  • 获取二维数组首地址 实例:
#include<iostream>
using namespace std;
int main() {
	/*
	* 查看二维数组所占内存空间
	* 获取二维数组首地址
	*/
	int arr[2][3] =
	{
		{1,2,3},
		{4,5,6}
	};
	cout << "二维数组占用空间为" << sizeof(arr) << endl;
	cout << "二维数组第一行占用空间" << sizeof(arr[0]) << endl;
	cout << "二维数组第一个元素占用内存" << sizeof(arr[0][0]) << endl;;
	cout << "二维数组行数" << size(arr) << endl;
	cout << "二维数组列数" << size(arr[0]) << endl;
	//查看二维数组的首地址
	cout << "二维数组首地址" << (int)arr << endl;
	cout << "二维数组第一行首地址" << (int)arr[0] << endl;
	cout << "二维数组第一个元素首地址" << (int)&arr[0][0] << endl;
}

二维数组应用案例 考试成绩统计,分别输出三名同学总成绩

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

int main() {
	int score[3][3] =
	{
		{100,100,100},
		{90,50,100},
		{60,70,80}
	};
	string name[3] = { "张三","李四","王五" };
	cout << "二维数组为:" << endl;
	for (int i = 0; i < size(score); i++)
	{
		int sum = 0;//统计分数综合
		for (int j = 0; j < size(score[0]); j++)
		{
			sum += score[i][j];
			cout << score[i][j] << "\t";
		}
		cout <<name[i]<<"的" << "个人的总分为" << sum << endl;
	}
}

函数

概述:

作用:讲一段经常使用的代码封装起来,减少重复代码 一个较大的程序,一般分为若干个程序块,每个模块实现特定功能

函数的定义

函数的定义一般包括:

  1. 返回值类型
  2. 函数名
  3. 参数列表
  4. 函数体语句
  5. return表达式 语法: 返回值类型 函数名(参数列表) { 函数体语句 return 表达式 } 案例:实现一个加法函数,传入整型数据,计算结果,并返回。
int add(int num1, int num2) {
	int sum = num1 + num2;
	return sum;
}

函数的调用

#include<iostream>
using namespace std;
//num1,num2叫做形参
int add(int num1, int num2) {
	int sum = num1 + num2;
	return sum;
}
int main() {
	//函数调用
	//调用整型的加法函数

	int a = 10;
	int b = 20;
	//a,b叫做实参
	//调用函数时,实参传递给形参
	int c = add(a, b);
	cout << "c=" << c << endl;
	
}

值传递

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

函数的常见样式

常见的函数样式有4种

  1. 无参无返
  2. 有参无返
  3. 无参有返
  4. 有参有返
#include<iostream>
using namespace std;
/*
常见的函数样式有4种
1. 无参无返
2. 有参无返
3. 无参有返
4. 有参有返
*/
//无参无返
void test01() {
	cout << "this is test01" << endl;
}
//有参无返
void test02(int a) {
	cout << "this is test02\t" << a << endl;
}
//无参有返
int test03() {
	cout << "this is test03" << endl;
	return 1000;
}
//有参有返
int test04(int a) {
	cout << "this is test04 a=" << a << endl;
	return a;
}
int main() {
	test01();
	test02(3);
	int num1 = test03();
	cout << "num1=" << num1 << endl;
	int num2 = test04(2);
	cout << "num2=" << num2 << endl;
}

函数的声明

作用:告诉编译器名称及如何调用函数,函数的主体可以单独定义 函数的声明可以多次,但是函数的定义只可以有一次 函数声明举例:int max(int a,int b);函数声明的好处就是可以把自定义的函数写在main函数之后,相当于提前告诉编译器函数的存在

函数分文件的编写

作用:让代码结构更加清晰 函数分文件编写一般有4个步骤

  1. 创建后缀为.h的头文件
  2. 创建后缀为.cpp的源文件
  3. 在头文件种写函数的声明
  4. 在源文件种写函数的定义 例子: 主程序:
#include<iostream>
#include "swap.h"
using namespace std;

//函数分文件编写,实现两个数字进行交换的函数
//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);
}

头文件

#pragma once
#include<iostream>
using namespace std;
void swap(int a, int b);

源文件

#include "swap.h"

void swap(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
}

指针

指针的基本概念

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

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

指针变量的定义和使用

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

#include<iostream>
using namespace std;
int main() {
	//定义指针
	int a = 10;
	//指针定义的语法:数据类型 * 指针变量名
	int* p;//定义int类型的指针变量
	//现在让p保存a的地址
	p = &a;//指针记录的就是地址

	cout << "a的地址为" << &a << endl;
	cout << "p的值为" << p << endl;

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

输出:

a的地址为000000D6A315FBF4
p的值为000000D6A315FBF4
a=1000
p=000000D6A315FBF4

指针所占内存空间

提问:指针也是一种数据类型,那么这种数据类型所占空间是多少? 在32位操作系统下,指针占用4个字节,64位下占用8个字节 实例:

#include<iostream>
using namespace std;
int main() {
	//指针所占空间
	//在32位操作系统下,指针是占用4个字节空间大小,不管是什么数据类型
	int a = 10;
	int* p = &a;
	cout << "sizeof int * =" << sizeof(int*) << endl;
	cout << "sizeof int * =" << sizeof(float*) << endl;
	cout << "sizeof int * =" << sizeof(char*) << endl;
	cout << "sizeof int * =" << sizeof(double*) << endl;
}

空指针和野指针

  • 空指针:指针变量指向内存中编号为0的空间
  • 用途:初始化指针变量
  • 空指针指向的内存是不可以访问的 实例:int* p = NULL;
#include<iostream>
using namespace std;
int main() {
	//空指针,空指针用于对指针变量初始化
	int* p = NULL;
	//空指针是不可以访问的
	//0-255内存编号是系统占用内存,不可以用户访问
	*p = 100;
}

  • 野指针
  • 指针变量指向非法的内存空间 实例:
#include<iostream>
using namespace std;
int main() {
	//野指针
	//在程序中,尽量避免野指针
	int* p = (int*)0x1100;//没有申请的内存空间
	cout << *p << endl;
}

const修饰指针

const修饰指针有三种情况

  1. const修饰指针--常量指针(就是指向常量的指针)
    1. 形如:const int* p = &a;
    2. 特点:指针的指向可以修改,但是指针指向的值不可以修改
    3. 如:```cpp int a = 10; int b = 10; const int * p = &a; //判断下面那句话是对的? *p = 20;//错的,指针指向的值不可以改 p = &b;//对的,指针指向可以改
  2. const修饰常量--指针常量
    1. 形如:int* const p = &a;
    2. 特点:指针的指向不可以改,带式指针指向的值可以改
    3. 如:```cpp *p = 20;正确,指向的值可以改 p = &b;错误,指向不可以改
  3. const既修饰指针,又修饰常量
    1. const int* const p = &a;
    2. 特点:指针指向和指向的值都不可以改
    3. 如:```cpp *p = 20;//错误 p = &b;//错误

简单记忆:

  • 怎么记忆叫法 const叫常量,* 叫指针,那么int* const p = &a;就叫做指针常量,const int *p = &a;叫做常量指针
  • 怎么记忆修改 const紧跟的东西不可以修改,如int* const p = &a;那么指向就不可以修改,const int p = &a;const紧跟的是那么取*的操作就不能做了,也就是不能改变值
#include<iostream>
using namespace std;
int main() {
	//const修饰指针 常量指针
	int a = 10;
	int b = 20;
	//int* p = &a;
	const int* p = &a;
	//*p = 20;错误写法,因为常量指针是指向常量的指针,指向的值不可以修改
	p = &b;
	cout << "p=" << p << endl;
	//const修饰常量 指针常量
	int* const p2 = &a;
	//p2 = &b;错误,因为指针常量指针不可以改变指向
	*p2 = 1000;
	cout << "a=" << a << endl;
	//const既修饰指针又修饰常量
	const int* const p3 = &a;
	//p3 = &b;错误,指向不可改
	//*p3 = 1000;错误,指向的值也不可以改

}

指针和数组

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

#include<iostream>
using namespace std;
int main() {
	//指针和数组
	//利用指针访问数组中的元素
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	cout << "第一个元素:" << arr[0] << endl;
	int* p = arr;//arr就是数组首地址
	cout << p << endl;
	cout << "利用指针访问第一个元素" << *p << endl;
	p++;
	cout << "利用指针访问第二个元素" << *p << endl;
	//利用指针遍历这个数组
	int* p2 = arr;
	for (int i = 0; i < size(arr); i++)
	{
		cout << "数组第" << i+1 << "个元素:" << *p2 << endl;
		p2++;
	}
}

指针和函数

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

//实现两个数字进行交换
#include<iostream>
using namespace std;
void swap01(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
	cout << "swap01a=" << a << endl;
	cout << "swap02b=" << b << endl;
}
void swap02(int* p,int* p2){
	int temp = *p;
	*p = *p2;
	*p2 = temp;
}
int main() {
	int a = 10;
	int b = 20;
	//值传递,形参影响不了实参
	//swap01(a, b);
	//地址传递,地址传递,可以修饰实参
	swap02(&a, &b);
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
}

指针数组和函数

案例描述:封装一个函数,利用冒泡排序,实现对整型数组的升序排列 例如数组:int arr[10] = {4,3,6,9,1,2,10,8,7,5};

#include<iostream>
using namespace std;
//打印数组
void printArray(int* arr,int len) {//这边也可以写成int arr[]
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << "\t";
	}
}
//冒泡排序
void bubbleSort(int* arr, int len){
	for (int i = 0; i < len-1; i++)
	{
		for (int j = 0; j < len-i-1; j++)
		{
			if (arr[j]>arr[j+1])
			{	
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}
int main() {
	//先创建数组
	int arr[10] = {4,3,6,9,1,2,10,8,7,5};
	//创建函数,实现冒泡排序
	bubbleSort(arr, size(arr));
	//打印排序后的数组
	printArray(arr, size(arr));
}

结构体

结构体基本概念

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

结构体定义和使用

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

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

//创建学生数据类型:学生包括(姓名,年龄,分数)
struct Student
{
	//成员列表
	//姓名
	string name;
	//年龄
	int age;
	//分数
	int score;
}s3;//顺便创建一个结构体变量s3
//通过学生类型创建具体学生
//struct Student s1
//struct Student s2 = {...}
//在定义结构体时顺便创建结构体变量
int main() {
	struct Student s1;//结构体创建时struct关键字可以省略,但是定义的时候不可以省略
	//给s1属性赋值,通过.访问结构体变量中的属性
	s1.name = "张三";
	s1.age = 18;
	s1.score = 100;
	cout << "姓名:" << s1.name << " 年龄:" << s1.age << " 分数:" << s1.score << endl;
	//struct Student s2 = {...}
	struct Student s2 = { "李四",28,13 };
	cout << "姓名:" << s2.name << " 年龄:" << s2.age << " 分数:" << s2.score << endl;
	//定义结构体时顺便创建结构体变量
	s3.name = "王五";
	s3.age = 20;
	s3.score = 50;
	cout << "姓名:" << s3.name << " 年龄:" << s3.age << " 分数:" << s3.score << endl;
}

结构体数组

作用:将自定义的结构体放入到数组中方便维护 语法:struct 结构体名 数组名[元素个数] = {{},{},...{}} 实例:

#include<iostream>
#include<string>
using namespace std;
struct Student
{
	string name;
	int age;
	int score;		
};
int main() {
	//创建结构体数组
	Student stuArray[3] =
	{
		{"张三",28,100},
		{"李四",28,99},
		{"王五",43,33}
	};
	//给结构体数组中元素赋值,改写一个
	stuArray[2].name = "赵六";
	stuArray[2].age = 85;
	stuArray[2].score = 60;
	//遍历结构体数组
	for (int i = 0; i < 3; i++)
	{
		cout << "姓名:" << stuArray[i].name 
			<< " 年龄:" << stuArray[i].age 
			<< " 分数:" << stuArray[i].score<<endl;
	}
}

结构体指针

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

  • 利用操作符->可以通过结构体指针访问结构体的属性 实例:
#include<iostream>
#include<string>
using namespace std;
//结构体指针
struct student
{
	string name;
	int age;
	int score;
};
int main() {
	//创建学生的结构体变量
	student s = { "张三",18,100 };
	//通过指针指向结构体变量
	student* p = &s;
	//通过指针访问结构体变量中的数据'->'
	cout << "姓名" << p->name << " 年龄" << p->age << " 分数" << p->score << endl;
}

结构体嵌套结构体

作用:结构体的成员可以是另一个结构体 例如:每个老师辅导一个学员,一个老师的结构体中,记录一个学生的结构体 实例:

#include<iostream>
#include<string>
using namespace std;
//定义学生结构体
struct student
{
	string name;
	int age;
	int score;
};
//定义老师结构体
struct teacher
{
	int id;//教师编号
	string name;//教师姓名
	int age;//年龄
	struct student stu;
};
int main() {
	//结构体嵌套结构体
	//创建老师
	teacher t;
	t.id = 10000;
	t.name = "老王";
	t.age = 50;
	t.stu.age = 28;
	t.stu.name = "小王";
	t.stu.score = 48;
	cout << "老师姓名:" << t.name << " 老师编号" << t.id << " 老师年龄:" << t.age
		<< " 老师辅导的学生:" << t.stu.name << endl;
}

结构体做函数参数

作用:将结构体作为参数向函数中传递 传递方式有两种

  • 值传递
  • 地址传递 实例:
#include<iostream>
#include<string>
using namespace std;
struct student
{
	//姓名
	string name;
	int age;
	int score;
};
//值传递
void printStudent01(student stu)
{
	cout << "main函数中打印姓名:" << stu.name << " 年龄:" << stu.age << " 得分" << stu.score << endl;;

}
//地址传递
void printStudent02(student* stu)
{
	cout << "main函数中打印姓名:" << stu->name << " 年龄:" << stu->age << " 得分" << stu->score << endl;;

}
int main() 
{
	//结构体做函数参数
	//将学生传入到一个参数中,打印学生身上所有信息

	//创建一个结构体变量
	student stu;
	stu.name = "张三";
	stu.score = 48;
	stu.age = 29;
	//值传递
	printStudent01(stu);
	//地址传递
	printStudent02(&stu);
	//cout << "main函数中打印姓名:" << stu.name << " 年龄:" << stu.age << " 得分" << stu.score;
}

结构体中const使用场景

作用:用const来防止误操作 实例:

#include<iostream>
#include<string>
using namespace std;
//const的使用场景
struct student
{
	string name;
	int age;
	int score;
};
//将函数中的形参改为指针,可以减少内存空间,而且不会复制新的副本出来
//为了防止误操作(不小心在函数内部把属性给改了)就需要const
void printStudent(const student* s) {//加入const之后,一旦有修改的操作就会报错,可以防止我们误操作。
	//s->name = "3dfs";报错
	cout << "main函数中打印姓名:" << s->name<< " 年龄:" << s->age << " 得分" << s->score;
}
int main() {
	//创建结构体变量
	student s = { "张三",15,89 };
	//通过函数来打印结构体变量的信息
	printStudent(&s);
	system("pause");
	return 0;
}

结构体案例

  1. 案例1 案例描述: 学校正在做毕设项目,每名老师带领5个学生,共有3名老师,需求如下: 设计学生和老师的结构体,其中在老师的结构体中,有老师姓名和一个存放5名学生的数组作为成员 学生的成员有姓名、考试分数、创建数组存放3名老师,通过函数给每个老师及其所带的学生赋值,最终打印出老师数据以及老师所带的学生数据 实例:
#include<iostream>
#include<string>
#include<ctime>
using namespace std;
struct student
{
	string sName;
	int score;
};
//老师的结构体
struct teacher
{
	string tName;
	student sArray[5];
};
//给老师和学生赋值的函数
void allocateSpace(teacher tArray[], int len)//这里也可以写成teacher *tArray
{
	string nameSeed = "ABCDE";
	for (int i = 0; i < len; i++)
	{
		tArray[i].tName = "teacher_";
		tArray[i].tName += nameSeed[i];
		//通过循环给每名老师所带的学生赋值
		for (int j = 0; j < 5; j++)
		{
			tArray[i].sArray[j].sName = "student_";
			tArray[i].sArray[j].sName += nameSeed[j];
			int random = rand() % 61 + 40;//40-100
			tArray[i].sArray[j].score = random;
		}
	}
}
//打印信息的函数
void printInfo(teacher tArray[], int len) {
	for (int i = 0; i < len; i++)
	{
		cout << "老师的姓名:\t" << tArray[i].tName << endl;
		for (int j = 0; j < 5; j++)
		{
			cout << "学生姓名:\t" << tArray[i].sArray[j].sName <<
				"\t考试分数:\t" << tArray[i].sArray[j].score << endl;
		}
	}
}
int main() {
	//随机数种子
	srand((unsigned int)time(NULL));
	//创建3名老师的数组
	teacher tArray[3];
	//函数给3名老师赋值,并给老师所带的学生信息赋值
	allocateSpace(tArray, size(tArray));
	//打印所有老师以及所带的学生信息
	printInfo(tArray, size(tArray));
}

案例2

案例描述: 设计一个英雄的结构体:包括成员姓名,年龄,性别,创建结构体数组,数组中存放5名英雄 通过冒泡排序算法,将数组中的英雄按照年龄进行升序排序,最终打印排序后的结果

#include<iostream>
#include<string>
using namespace std;
struct hero
{
	string name;
	int age;
	string sex;
};
//起始状态打印
void printInfoBegin(hero* heroArray,int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << "成员姓名:\t" << heroArray[i].name << "\t年龄:\t" << heroArray[i].age
			<< "\t性别:" << heroArray[i].sex << endl;
	}
}
//按照年龄升序排列
void printInfoBubbleSort(hero* heroArray, int len)
{
	for (int i = 0; i < len-1; i++)
	{
		for (int j = 0; j < len-1-i; j++)
		{
			if (heroArray[j].age > heroArray[j+1].age)
			{
				hero temp = heroArray[j];
				heroArray[j] = heroArray[j + 1];
				heroArray[j + 1] = temp;
			}
		}
	}
}
int main() {
	//创建结构体数组
	hero heroArray[5] =
	{
		{"刘备",23,"男"},
		{"关羽",22,"男"},
		{"张飞",20,"男"},
		{"赵云",21,"男"},
		{"貂蝉",19,"女"}
	};
	//打印一下看看原始排列
	printInfoBegin(heroArray, size(heroArray));
	//排序后
	cout << "------------------------------------------------------" << endl;
	printInfoBubbleSort(heroArray, size(heroArray));
	printInfoBegin(heroArray, size(heroArray));
}

通讯录管理系统

系统需求

通讯录是一个可以记录亲人好友信息的工具 本教程主要利用C++来实现一个通讯录管理系统 系统中需要实现的功能如下:

  • 添加联系人:向通讯录添加联系人,信息包括(姓名、性别、年龄、联系电话、家庭住址)最多记录1000人
  • 显示联系人:显示通讯录中所有联系人信息
  • 删除联系人:按照姓名进行删除指定联系人
  • 查找联系人:按照姓名查看指定联系人信息
  • 修改联系人:按照姓名重新修改指定联系人
  • 清空联系人:清空通讯录中所有信息
  • 退出通讯录:退出当前使用的通讯录

菜单功能

#include<iostream>
#include<string>
using namespace std;
//封装函数显示该界面,如void showMenu()
//在main函数中调用封装好的函数
//1. 显示菜单
void showMenu()
{
	cout << "*************************" << endl;
	cout << "***** 1、添加联系人 *****" << endl;
	cout << "***** 2、显示联系人 *****" << endl;
	cout << "***** 3、删除联系人 *****" << endl;
	cout << "***** 4、查找联系人 *****" << endl;
	cout << "***** 5、修改联系人 *****" << endl;
	cout << "***** 6、清空联系人 *****" << endl;
	cout << "***** 7、退出通讯录 *****" << endl;
	cout << "*************************" << endl;
}
int main()
{
	//菜单调用
	showMenu();

}

退出功能

功能描述:退出通讯录系统 思路:根据用户不同的选择,进入不同的功能,可以选择switch分支结构,将整个架构进行搭建 当用户选择0时,执行退出,选择其他先不做操作,也不会退出程序。

添加联系人

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

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

显示联系人

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

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

删除联系人

功能描述:按照姓名进行删除联系人 删除联系人实现步骤:

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

查找联系人

功能描述:按照姓名查看指定联系人信息 查找联系人实现步骤

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

修改联系人

功能描述:按照姓名重新修改指定联系人 修改联系人实现步骤:

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

清空联系人

功能描述:清空通讯录中所有信息 清空联系人实现步骤

  • 封装清空联系人函数
  • 测试清空联系人
#include<iostream>
#include<string>
#define MAX 1000//最大数
using namespace std;
//封装函数显示该界面,如void showMenu()
//在main函数中调用封装好的函数
//1. 显示菜单
void showMenu()
{
	cout << "*************************" << endl;
	cout << "***** 1、添加联系人 *****" << endl;
	cout << "***** 2、显示联系人 *****" << endl;
	cout << "***** 3、删除联系人 *****" << endl;
	cout << "***** 4、查找联系人 *****" << endl;
	cout << "***** 5、修改联系人 *****" << endl;
	cout << "***** 6、清空联系人 *****" << endl;
	cout << "***** 7、退出通讯录 *****" << endl;
	cout << "*************************" << endl;
}
//设计联系人结构体
struct Person
{
	string mName;
	int mGender;//性别,1男,2女
	int mAge;
	string mPhone;
	string mAddr;
};
//通讯录结构体
struct Addressbooks
{
	Person personArray[MAX];//通讯录中保存的联系人数组
	int mSize;//当前记录的联系人个数
};
//1.添加联系人
void addPerson(Addressbooks* abs)
{
	//判断通讯录是否已经满了,如果满了就不再添加
	if (abs->mSize == MAX)
	{
		cout << "通讯录已经满了,无法添加" << endl;
		return;
	}
	else
	{
		//添加具体联系人
		//姓名
		string name;
		cout << "输入联系人姓名:" << endl;
		cin >> name;
		abs->personArray[abs->mSize].mName = name;
		//性别
		cout << "请输入性别,1代表男,2代表女" << endl;
		int sex = 0;
		while (true)
		{
			//如果输入的是1或者2,可以退出循环,因为输入的是正确值,否则重新输入
			cin >> sex;
			if (sex == 1 || sex == 2)
			{
				abs->personArray[abs->mSize].mGender = sex;
				break;
			}
			cout << "输入有误,请重新输入" << endl;
		}
		//年龄
		cout << "请输入年龄:" << endl;
		int age = 0;
		while (true)
		{
			//如果输入的年龄在1-200之间,可以退出循环,否则重新输入
			cin >> age;
			if (age>=1&&age<=200)
			{
				abs->personArray[abs->mSize].mAge = age;
				break;
			}
			cout << "输入有误,请重新输入" << endl;
		}
		//电话
		cout << "请输入联系电话:" << endl;
		string phone;
		cin >> phone;
		abs->personArray[abs->mSize].mPhone = phone;
		//家庭住址
		cout << "请输入家庭住址:" << endl;
		string address;
		cin >> address;
		abs->personArray[abs->mSize].mAddr = address;
		//更新通讯录人数
		abs->mSize++;
		cout << "添加成功!" << endl;
		system("pause");//请按任意键继续
		system("cls");//清屏
	}
}
//2.显示联系人
void showPerson(Addressbooks* abs)
{
	//判断通讯录中人数是否为0,如果为0,提示记录为空
	if (abs->mSize == 0)
	{
		cout << "记录为空" << endl;
	}
	else
	{
		for (int i = 0; i < abs->mSize; i++)
		{
			cout << "姓名:" << abs->personArray[i].mName << "\t"
				<< "性别:" << (abs->personArray[i].mGender == 1?"男" :"女") << "\t"
				<< "年龄:" << abs->personArray[i].mAge << "\t"
				<< "电话:" << abs->personArray[i].mPhone << "\t"
				<< "住址:" << abs->personArray[i].mAddr << endl;
		}
	}
	system("pause");
	system("cls");
}
//
//检测联系人是否存在,如果存在,返回具体位置,否则返回-1
int isExist(Addressbooks* abs, string name)
{
	for (int i = 0; i < abs->mSize; i++)
	{
		if (abs->personArray[i].mName == name)
		{
			return i;
		}
	}
	return -1;//如果遍历结束都没有找到返回-1.
}
//3.删除联系人函数
void deletePerson(Addressbooks* abs)
{
	cout << "请输入一下您要删除的联系人" << endl;
	string name;
	cin >> name;
	int ret = isExist(abs, name);
	if (ret==-1)
	{
		//未查到
		cout << "查无此人" << endl;
	}
	else
	{
		//查到了此人
		//将要删除的元素的后面元素向前移动,并且让通讯录中记录的人员个数做一个-1的操作
		for (int i = ret;i<abs->mSize;i++)
		{
			//数据迁移
			abs->personArray[i] = abs->personArray[i + 1];
		}
		abs->mSize--;//更新一下通讯录中的人员数。
		cout << "删除成功" << endl;
	}
	system("pause");
	system("cls");
}
//4.查找指定联系人信息
void findPerson(Addressbooks* abs)
{
	cout << "请输入您要查找的联系人" << endl;
	string name;
	cin >> name;
	//判断指定联系人是否存在通讯录中
	int res = isExist(abs, name);
	if (res!= -1)//找到了
	{
		cout << "姓名:" << abs->personArray[res].mName << "\t";
		cout << "性别:" << abs->personArray[res].mGender << "\t";
		cout << "年龄:" << abs->personArray[res].mAge << "\t";
		cout << "电话:" << abs->personArray[res].mPhone << "\t";
		cout << "住址:" << abs->personArray[res].mAddr << "\t";
	}
	else
	{
		cout << "未找到联系人" << endl;
	}
	system("pause");
	system("cls");
}
//5.修改指定联系人
void modifyPerson(Addressbooks* abs)
{
	//想修改哪一个的信息
	cout << "请输入你想修改的联系人的姓名:" << endl;
	string name;
	cin >> name;
	int res = isExist(abs,name);
	if (res != -1)
	{
		cout << "原始信息为:" << endl;
		cout << "姓名:" << abs->personArray[res].mName << "\t";
		cout << "性别:" << abs->personArray[res].mGender << "\t";
		cout << "年龄:" << abs->personArray[res].mAge << "\t";
		cout << "电话:" << abs->personArray[res].mPhone << "\t";
		cout << "住址:" << abs->personArray[res].mAddr << "\t" << endl;;
		cout << "------------------------------" << endl;
		//姓名
		string name;
		cout << "请输入姓名" << endl;
		cin >> name;
		abs->personArray[res].mName = name;
		//性别
		cout << "请输入性别" << endl;
		cout << "1---男" << endl;
		cout << "2---女" << endl;
		int sex = 0;
		while (true)
		{
			cin >> sex;
			if (sex == 1 || sex == 2)
			{
				//输入正确,则退出循环
				abs->personArray[res].mGender = sex;
				break;
			}
				cout << "输入有误,请重新输入" << endl;
		}
		//年龄
		cout << "请输入年龄:" << endl;
		int age = 0;
		cin >> age;
		abs->personArray[res].mAge = age;
		//电话
		cout << "请输入联系电话" << endl;
		string phone;
		cin >> phone;
		abs->personArray[res].mPhone = phone;
		//家庭住址
		cout << "请输入家庭住址" << endl;
		string address;
		cin >> address;
		abs->personArray[res].mAddr = address;
		cout << "修改成功" << endl;
	}
	else//未找到联系人
	{
		cout << "查无此人" << endl;
	}
	system("pause");
	system("cls");

}
//6.清空联系人
void cleanPerson(Addressbooks* abs)
{
	abs->mSize = 0;//将当前记录联系人数量置为0,做逻辑清空
	cout << "通讯录已经清空" << endl;
	system("pause");
	system("cls");
}
int main()
{
	//创建通讯录结构体变量
	Addressbooks abs;
	//初始化通讯录中当前人员个数
	abs.mSize = 0;
	int select = 0;
	while (true)
	{
		//菜单调用
		showMenu();
		cin >> select;
		switch (select)
		{
		case 1://添加联系人
			addPerson(&abs);
			break;
		case 2://显示联系人
			showPerson(&abs);
			break;
		case 3://删除联系人
			deletePerson(&abs);
			break;
		case 4://查找联系人
			findPerson(&abs);
			break;
		case 5://修改联系人
			modifyPerson(&abs);
			break;
		case 6://清空联系人
			cleanPerson(&abs);
			break;
		case 0://退出通讯录
			cout << "欢迎下次使用" << endl;
			system("pause");
			return 0;
			break;
		default:
			break;
		}
	}
}

C++核心编程

内存分区模型

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

  • 代码区:存放函数体的二进制代码,由操作系统进行管理
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值局部变量等
  • 堆区:由程序分配和释放,若程序员不释放程序结束时由操作系统回收 内存4区的意义: 不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
  1. 程序运行前 在程序编译后,生成exe可执行程序,未执行该程序前分为2个区域 代码区: 存放cpu执行的机器指令 代码区时共享的,共享的目的是对于频繁被执行的程序,之后在内存中有一份代码即可 代码区是只读的,使其只读的原因是防止程序意外地修改了他的指令 全局区: 全局变量和静态变量存放在此(全局变量不在main函数内定义,静态变量是在普通变量前加static) 全局区还包含了常量区:字符串常量和其他常量(const修饰的全局常量)也存放于此 该区域的数据在程序结束后由操作系统释放
  2. 程序运行后
    1. 栈区 由编译器自动分配释放,存放函数的参数值,局部变量,形参也会放在栈区 注意事项:不要返回局部变量的地址,因为栈区开辟的地址,由编译器自动释放。
    2. 堆区 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收 在C++中主要利用new在堆区开辟内存。
    int* func()
    {
     //利用new关键字,可以将数据开辟到堆区
     int* p = new int(10);//数据初始值10,返回的时一个地址编号。
     //指针p本质也是一个局部变量,放在栈上,但是指针指向的数据是放在堆区。也就是说堆区的地址编号用栈区的指针保存了
     return p;
    }
    int main()
    {
     //在堆区开辟数据
     int* p = func();
     cout<<*p <<endl;
    }
    

    new操作符

    C++中利用new操作符在堆区开辟数据 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete 语法:new 数据类型 利用new创建的数据,会返回该数据对应的类型的指针 基本语法:
#include<iostream>
using namespace std;
//new的基本用法
int* func()
{
	//在堆区创建整型数据
	//new返回是该数据类型的指针
	int* p = new int(10);
	return p;
}
void test01()
{
	int* p = func();
	cout << *p << endl;
	cout << *p << endl;
	cout << *p << endl;
	//堆区数据由程序员管理开辟,程序员管理释放
	//如果想释放堆区的数组,利用关键字delete
	delete p;
	//cout << *p << endl;//访问权限冲突,非法操作
}
//在堆区利用new开辟数组
void test02()
{
	//创建10整型数据的数组
	int* arr = new int[10];
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;//给10个元素赋值100-109
	}
	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	//释放堆区数组
	//释放数组的时候,要加[]才可以
	delete[] arr;
}
int main()
{
	//test01();
	test02();
}

引用

引用的基本类型

作用:给变量起别名 语法:数据类型 &别名 = 原名 实例:

int a = 10;
//创建引用
int &b = a;//别名可以和原名一样
b = 100;
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;

引用注意事项

  • 引用必须初始化 int &b;//错误的,必须要初始化。
  • 引用在初始化之后就不可以改变了 如果b初始化为a的别名之后,就不可以更改为c的别名了

引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参 优点:可以简化指针修改实参 实例:

#include<iostream>
using namespace std;
//交换函数
//值传递
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;
	cout << "a=" << *a << endl;
	cout << "b=" << *b << endl;
}
//引用传递
void mySwap03(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}
int main()
{
	int a = 10;
	int b = 20;
	//mySwap01(a,b);
	//mySwap02(&a, &b);
	mySwap03(a, b);//引用传递,形参会修饰实参
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}

引用做函数返回值

作用:引用时可以作为函数的返回值存在的 注意:不要返回局部变量引用

#include<iostream>
using namespace std;
int& test01()
{
	int a = 10;//局部变量保存在栈区
	return a;
}
int main()
{
	int& ref = test01();
	cout << "ref=" << ref << endl;//第一次结果正确,是因为编译器做了保留
	cout << "ref=" << ref << endl;//第二次结果错误,因为a的内存已经释放
}

用法:函数调用作为左值

#include<iostream>
using namespace std;
int& test01()
{
	int a = 10;//局部变量保存在栈区
	return a;
}
//函数的调用可以作为左值
int& test02()
{
	static int a = 10;//静态变量存在全局区,全局区数据在程序结束后系统释放
	return a;
}
int main()
{
	int& ref = test02();
	cout << "ref=" << ref << endl;//输出正确
	cout << "ref=" << ref << endl;//输出正确
	test02() = 1000;//函数调用可以作为左值,因为test02()返回的是a的一个引用,将引用赋值为1000
	cout << "ref=" << ref << endl;//输出正确
	cout << "ref=" << ref << endl;//输出正确
}

实例:

引用的本质

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

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

常量引用

作用:常量引用主要是来修饰形参,防止误操作 在函数形参列表钟,可以加const修饰形参,防止形参改变实参

#include<iostream>
using namespace std;
//打印数据函数
void showValue(const int& val)
{
	//val = 1000;
	cout << "val=" << val << endl;
}
int main()
{
	//常量引用
	//使用场景:用来修饰形参,防止误操作
	//int a = 10;
	//int& ref = 10;//引用必须引一快合法的内存空间,这里是错误的
	//const int& ref = 10;//加const之后,这又是对的,编译器将代码修改为
	//ref = 20//加入const之后变为只读,不可以修改
	//int temp = 10;
	//int& ref = temp;
	int a = 100;
	showValue(a);
	system("pause");
}

函数的提高

函数的默认参数

在c++钟,函数的形参里表中的形参可以有默认值
语法:返回值类型 函数名(参数=默认值){}
如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
声明和实现只能有一个有默认参数,另一个没有。如果是声明有默认参数,实现就没有,否则实现有默认参数,声明就没有。

#include<iostream>
using namespace std;
int func(int a, int b=20, int c=30)
{
	return a + b + c;
}
int main() {
	cout << func(10) << endl;

}

函数占位参数

c++钟函数的形参列表可以有占位参数,用来做占位,调用函数时必须填补该位置。 语法:返回值类型 函数名(数据类型){} 在现阶段的占位参数存在意义不大,但是后面的课程中会使用这个技术

#include<iostream>
using namespace std;
//占位参数
void func(int a,int)
{
	cout << "this is func" << endl;
}
int main() {
	func(10,10);
}
//占位参数也可以有默认参数void func(int a, int = 10)

函数重载

  1. 函数重载概述 作用:函数名可以相同,提高复用性 函数重载满足条件:
  • 同一个作用域下
  • 函数名相同
  • 函数参数类型不同或者个数不同或者顺序不同
  • 注意:函数的返回值不可以做为函数重载的条件
#include<iostream>
using namespace std;
//函数重载
//可以让函数名相同,提高复用性

//函数重载的满足条件
/*
* 同一个作用域下
* 函数名相同
* 函数参数类型不同或者个数不同或者顺序不同
* 注意:函数的返回值不可以做为函数重载的条件
*/
void func()
{
	cout << "func的调用" << endl;
}
void func(int a)
{
	cout << "func的调用!!!!" << endl;
}
void func(double a)
{
	cout << "double(func)的调用!!!!" << endl;
}
void func(int a,double b)
{
	cout << "int a,double b" << endl;
}
void func( double b, int a)
{
	cout << "double b, int a" << endl;
}
//注意函数的返回值不可以作为函数重载的条件
int func(int a, double b)
{
	cout << "int a,double b" << endl;
}
void func(int a, double b)
{
	cout << "int a,double b" << endl;
}
int main() {
	//func();
	//func(10.);
	func(10.,10);
	func(10, 10.);
}

函数重载的注意事项

  • 引用作为重载条件
#include<iostream>
using namespace std;
//函数重载的注意事项
//引用作为重载的条件
void func(int& a)//int &a = 10;不合法,引用必须引合法的内存空间
{
	cout << "func的调用" << endl;
}
//函数重载碰到默认参数
void func(const int& a)//const int &a = 10;
{
	cout << "func(const int &a)调用" << endl;
}
int main() {
	int a = 10;
	func(a);//如果是传入a会调入func(int& a)
	func(10);//如果传入10会调入func(const int& a)
}
  • 函数重载碰到默认参数
//函数重载碰到默认参数
#include<iostream>
using namespace std;
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()
{
	func2(10);//当函数重载碰到默认参数,出现二义性,因此尽量避免这种情况
}

类和对象

c面向对象三大特性:封装、继承、多态 c认为万事万物皆为对象,对象上有其属性和行为

封装

  1. 封装的意义
  2. 封装是c++面向对象三大特性之一
  3. 封装的意义:
    1. 将属性和行为作为一个整体,表现生活中的事物
    2. 将属性和行为加以权限控制
    3. 封装意义一:
      1. 在设计类的时候,属性和行为写在一起,表现事物
      2. 语法:class 类名(访问权限 属性/行为);
#include<iostream>
using namespace std;
//设计一个圆类,求圆的周长
const double PI = 3.14;
//class代表设计一个类,类后面紧跟着就是类名称
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;
}
#include<iostream>
using namespace std;
//设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
class Student
{
public:
	string m_name;//姓名
	int m_id;
	void setName(string name) {
		m_name = name;
	}
	void setId(int id) {
		m_id = id;
	}
	void showStudent()
	{
		cout << "姓名:" << m_name << "学号:" << m_id << endl;
	}
};
int main() {
	//创建一个具体学生
	Student s1;
	//s1.m_name = "张三";
	s1.setName("张三");
	//s1.m_id = 1;
	s1.setId(1);
	s1.showStudent();
	Student s2;
	//s2.m_name = "李四";
	s2.setName("李四");
	//s2.m_id = 2;
	s2.setId(2);
	s2.showStudent();
}
  1. 封装意义二: 类在设计时,可以把属性和行为放在不同的权限下,加以控制,访问权限有三种:
    1. public 公共权限 成员类内可以访问,类外可以访问
    2. protected 保护权限 成员类内可以访问,类外不可以访问 儿子可以访问
    3. private 私有权限 成员类内可以访问,类外不可以访问 儿子不可以访问
#include<iostream>
using namespace std;
//访问权限
//三种
/*
   1. public 公共权限	 成员类内可以访问,类外可以访问
   2. protected 保护权限 成员类内可以访问,类外不可以访问 儿子也可以访问父亲的保护内容。
   3. private 私有权限   成员类内可以访问,类外不可以访问 儿子不可以访问父亲的私有内容。
   */
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 = 111111;//私有权限,在类外访问不到
}
  1. struct和class的区别 在c++中struct和class的区别就在于默认的访问权限不同 区别:
  • struct默认权限为公共public
  • class默认权限为私有private
#include<iostream>
using namespace std;
class C1
{
	int m_A;//默认权限为私有private
};
struct C2
{
	int m_A;//默认权限为公共public
};
int main()
{
	C1 c1;
	//c1.m_A = 100;//报错,class默认为私有权限
	C2 c2;
	c2.m_A = 100;//正确,struct默认权限为共有
}
  1. 成员属性设置为私有
  • 优点1:将所有成员属性设置为私有,可以自己控制读写权限
  • 优点2:对于写权限,我们可以检测数据的有效性
#include<iostream>
#include<string>
using namespace std;
//成员属性设置为私有
//设计人类
class Person
{
public:
	//写姓名
	void setName(string name)
	{
		m_Name = name;
	}
	//获取姓名
	string getName()
	{
		return m_Name;
	}
	//获取年龄 可读可写,如果想修改(年龄的范围是0~150之间)
	int getAge()
	{
		m_Age = 0;//初始化为0
		return m_Age;
	}
	//设置年龄
	void setAge(int age)
	{
		if (age<0 || age>150)
		{
			m_Age = 0;
			cout << "您输入的年龄有误,请确保年龄在0-150之间";
			return;
		}
		m_Age = 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;
	cout << "年龄为:" << p.getAge() << endl;
	p.setLover("苍井空");
	p.setAge(120);
	cout << "年龄为:" << p.getAge() << endl;
}
  1. 案例1:设计立方体类 设计立方体类(Cube) 求出立方体类(Cube) 求出立方体的面积和体积 分别用全局函数和成员函数判断两个立方体是否相等
#include<iostream>
using namespace std;
//创建一个立方体的类
class Cube
{
	//行为
public:
	//设置长
	void setL(double l)
	{
		m_L = l;
	}
	//获取长
	double getL()
	{
		return m_L;
	}
	//设置宽
	void setW(double w)
	{
		m_W = w;
	}
	//获取宽
	double  getW()
	{
		return m_W;
	}
	//设置高
	void setH(double h)
	{
		m_H = h;
	}
	//获取高
	double getH()
	{
		return m_H;
	}
	//获取立方体面积
	double area()
	{
		return 2 * (m_H * m_L + m_H * m_W + m_L * m_W);
	}
	double getArea();
	//获取立方体体积
	double calculateV()
	{
		return m_L * m_H * m_W;
	}
	//利用成员函数来判断两个立方体是否相等
	bool isSameByClass(Cube &c)
	{
		if (m_L == c.getL() && m_H == c.getH() && m_W == c.getW())
		{
			return true;
		}
		else
		{
			return false;
		}
	}
private:
	//属性
	double m_L;//长
	double m_W;//宽
	double m_H;//高

};
//利用全局函数判断两个立方体是否相等
bool isSame(Cube& c1, Cube& c2)
{
	if (c1.getL() == c2.getL() && c1.getH() == c2.getH() && c1.getW() == c2.getW())
	{
		return true;
	}
	return false;
}
int main()
{
	//创建立方体对象
	Cube c1;
	c1.setL(10.);
	c1.setH(10.);
	c1.setW(10);
	cout << "c1的面积为:" << c1.area() << endl;
	cout << "c2的体积为:" << c1.calculateV() << endl;
	//创建第二个立方体
	Cube c2;
	c2.setH(10);
	c2.setL(10);
	c2.setW(10);
	//判断两个立方体是否相等
	bool ret = isSame(c1, c2);//利用全局函数判断
	bool ret1 = c1.isSameByClass(c2);//利用成员函数判断
	if (ret1)
	{
		cout << "c1和c2是相等的" << endl;
	}
	else
	{
		cout << "c1和c2是不想等的" << endl;
	}
}

封装案例2:点和圆的关系 设计一个圆形类(Circle)和一个点类(Point),计算点和圆的关系。

#include<iostream>
using namespace std;
//根据点到圆心的距离和半径比较判断点在圆的情况。
//点类
class Point
{
public:
	//设置x
	void setX(double x)
	{
		m_X = x;
	}
	//获取x
	double getX()
	{
		return m_X;
	}
	//设置y
	void setY(double y)
	{
		m_Y = y;
	}
	//获取y
	double getY()
	{
		return m_Y;
	}
private:
	int m_X;
	int m_Y;
};
//圆类
class Circle
{
public:
	//设置半径
	void setR(double r)
	{
		m_R = r;
	}
	//获取半径
	double getR()
	{
		return m_R;
	}
	//设置圆心
	void setCenter(Point center)
	{
		m_Center = center;
	}
	//获取圆心
	Point getCenter()
	{
		return m_Center;
	}

private:
	int m_R;//半径
	Point m_Center;//圆心
};
//判断点和圆的关系
void isInCircle(Circle& c, Point& p)
{
	//计算两点之间距离的平方
	double distance = (c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
		(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
	//计算半径的平方
	int rDistance = c.getR() * c.getR();
	//判断关系
	if (distance == rDistance)
	{
		cout << "点在圆上" << endl;
}
	else if (distance > rDistance)
	{
		cout << "点在圆外" << endl;
	}
	else
	{
		cout << "点在圆外" << endl;
	}
}
int main()
{
	//创建圆
	Circle c1;
	Point p1;
	p1.setX(0);
	p1.setY(0);
	c1.setR(10);
	c1.setCenter(p1);
	Point p2;
	p2.setX(10);
	p2.setY(10);
	isInCircle(c1, p2);
}

对象的初始化和清理

  1. 构造函数和析构函数 对象的初始化和清理也是两个非常重要的安全问题 一个对象或者变量没有初始状态,对其使用后果是未知 同样的使用完一个对象或者变量,没有及时清理,也会造成一定安全问题 C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。 对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。 编译器提供的构造和析构函数是空实现
  • 构造函数:主要作用在于创建对象为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
  1. 构造函数的语法:类名()
    1. 构造函数,没有返回值,也不写void
    2. 函数名称与类名称相同
    3. 构造函数可以有参数,因此可以发生重载
    4. 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
  2. 析构函数语法:类名()
    1. 析构函数,没有返回值也不写void
    2. 函数名称与类名称相同,在名称前加上符号~
    3. 析构函数不可以有参数,因此不可以发生重载
    4. 程序在对象销毁前自动调用析构,无需手动调用,而且只会调用一次。
#include<iostream>
using namespace std;
class Person
{
public:
	//构造函数的调用
	/*
	   1. 构造函数,没有返回值,也不写void
   2. 函数名称与类名称相同
   3. 构造函数可以有参数,因此可以发生重载
   4. 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
   */
	Person()
	{
		cout << "Person构造函数被调用" << endl;
	}
	//析构函数的调用
	/*
	   1. 析构函数,没有返回值也不写void
   2. 函数名称与类名称相同,在名称前加上符号~
   3. 析构函数不可以有参数,因此不可以发生重载
   4. 程序在对象销毁前自动调用析构,无需手动调用,而且只会调用一次。
   */
	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}
};
void test01()
{
	Person p;//局部变量,开辟在栈上,test01执行完毕后,释放这个对象
}
int main()
{
	//test01();
	Person p;
	system("pause");
}
  1. 构造函数的分类和调用
    1. 两种分类方式:
      1. 按参数分为:有参构造和无参构造
      2. 按类型分为:普通构造和拷贝构造
    2. 三种调用方法
      1. 括号法
      2. 显示法
      3. 隐式转换法 实例:
#include<iostream>
using namespace std;
//构造函数的分类和调用
//分类
class Person
{
public:
	//构造函数
	Person()
	{
		cout << "Person的无参构造函数调用" << endl;
	}
	Person(int a)
	{
		age = a;
		cout << "Person的有参构造函数调用" << endl;
	}
	//拷贝构造函数
	Person(const Person &p)//不能把本体改了
	{
		cout << "Person的拷贝构造函数调用" << endl;
		age = p.age;//将传入的人身上的所有属性,拷贝到我身上
	}
	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}
	int age;
};
//调用
void test01()
{
	//括号法调用
	Person p;//默认构造函数调用
	Person p2(10);//调用有参构造
	Person p3(p2);//调用拷贝构造函数
	cout << "p2的年龄:" << p2.age << endl;
	cout << "p3的年龄:" << p3.age << endl;
	//注意事项
	// 调用默认构造函数的时候,不要加(),不是Person p()
	// Person p()编译器会认为是一个函数声明,不会认为是创建对象
	//显示法
	Person p1;
	Person p2 = Person(10);//有参构造
	Person p3 = Person(p2);//拷贝构造
	
	Person(10);//匿名对象 特点:当前行执行结束后,系统会立即回收匿名对象
	//注意事项2
	//不要利用拷贝构造函数初始化匿名对象
	Person(p3);//重定义 编译器会认为 Person (p3) == Person p3;

	//隐式法调用
	Person p4 = 10;//相当于写了Person p4 = Person(10);有参构造
	Person p5 = p4;//相当于Person p5 = p4;拷贝构造
}
int main()
{
	test01();
}
  1. 拷贝构造函数调用时机 c++中拷贝构造函数调用时机有以下三种情况:
  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数传值(会调用拷贝构造函数)
  • 以值方式返回局部对象(也会调用拷贝构造函数)
#include<iostream>
using namespace std;
//拷贝构造函数的调用时机
//使用一个已经创建完毕的对象来初始化一个新对象
//值传递的方式给函数参数来传值
//值方式返回局部对象
class Person
{
public:
	Person()
	{
		cout << "Person默认构造函数被调用" << endl;
	}
	Person(int age)
	{
		cout << "Person的有参构造函数被调用" << endl;
		m_Age = age;
	}
	Person(const Person &p)
	{
		m_Age = p.m_Age;
		cout << "Person的拷贝构造函数被调用" << endl;
	}
	~Person()
	{
		cout << "Person的析构函数被调用" << endl;
	}
	int m_Age;
};
void test01()
{
	Person p1(10);
	Person p2(p1);
}
//值传递的方式给函数参数传值

void doWork(Person p)//拷贝
{

}
void test02()
{
	Person p;
	doWork(p);
}
int main()
{
	//test01();
	test02();
}
  1. 构造函数的调用规则 默认情况下,c++编译器至少给一个类添加3个函数
  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝构造函数,对属性进行值拷贝 构造函数调用规则如下:
  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数 实例:
#include<iostream>
using namespace std;
/*
默认情况下,c++编译器至少给一个类添加3个函数
* 默认构造函数(无参,函数体为空)
* 默认析构函数(无参,函数体为空)
* 默认拷贝构造函数,对属性进行值拷贝
*/

class Person
{
public:
	Person()
	{
		cout << "Person默认构造函数调用" << endl;
	}
	Person(int age)
	{
		m_Age = age;
		cout << "Person的有参构造函数调用" << endl;
	}
	/*Person(const Person& p)
	{
		m_Age = p.m_Age;
		cout << "Person的拷贝构造函数调用" << endl;
	}*/
	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}
	int m_Age;
};
void test01()
{
	Person p;
	p.m_Age = 18;
	Person p2(p);//拷贝构造函数
	cout << "p2的年龄为:" << p2.m_Age << endl;
}
int main()
{
	test01();
}
  1. 深拷贝和浅拷贝 深浅拷贝是面试经典问题,也是常见的一个坑 浅拷贝:简单的赋值拷贝操作 深拷贝:在堆区重新申请空间,进行拷贝操作 实例: 浅拷贝的问题:堆区内存的重复释放 深拷贝,重新在堆区找一个内存,存拷贝的东西
#include<iostream>
using namespace std;
class Person
{
public:
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}
	Person(int age, int height)
	{
		m_Height = new int(height);
		m_Age = age;
		cout << "Perosn的有参构造函数调用" << endl;
	}
	//自己实现的拷贝构造函数,解决浅拷贝带来的问题
	Person(const Person& p)
	{
		cout << "自己实现的拷贝构造函数" << endl;
		m_Age = p.m_Age;
		//m_Height = p.m_Height;编译器默认实现的就是这行代码
		//深拷贝操作
		m_Height = new int(*p.m_Height);
	}
	~Person()
	{
		cout << "Person的析构函数被调用" << endl;
		if (m_Height != NULL) {
			delete m_Height;
			m_Height = NULL;
		}
	}
	int m_Age;//年龄
	int* m_Height;//身高,堆区数据手动开辟也需要手动释放,需要在析构函数释放
};
void test01()
{
	Person p(18,160);
	cout << "p的年龄为:" << p.m_Age << endl;
	cout << "p的身高为:" << *p.m_Height << endl;
	Person p2(p);
	cout << "p2的年龄为:" << p2.m_Age << endl;
	cout << "p2的身高为:" << *p2.m_Height << endl;
}
int main()
{
	test01();
}
  1. 初始化列表
  2. 作用:c++提供了初始化列表的语法,用来初始化属性 语法:构造函数():属性1(值1),属性2(值2)。。。()
#include<iostream>
using namespace std;
//初始化列表
class Person
{
public:
	//传统的初始化操作
	//Person(int a, int b, int c)
	//{
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}
	//初始化列表初始化属性
	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(30,20,10);
	cout << "m_A:" << p.m_A << endl;
	cout << "m_B:" << p.m_B << endl;
	cout << "m_C:" << p.m_C << endl;
}
int main()
{
	test01();
}
  1. 类对象作为类成员 C++类中的成员可以是另一个类的对象,我们称该成员为对象成员 例如:
class A{}
class B
{
	A a;
}

B类中有对象A作为成员,A为对象成员 那么当创建B对象时,A和B的构造和析构的顺序时谁先谁后?

#include<iostream>
using namespace std;
//类对象作为类成员
class Phone
{
public:
	string m_PName;
	Phone(string mName)
	{
		m_PName = mName;
		cout << "Phone的构造函数被调用" << endl;
	}
	~Phone()
	{
		cout << "Phone的析构函数被调用" << endl;
	}
};
class Person
{
public:
	//姓名
	string m_Name;
	//手机
	Phone m_Phone;
	//Phone m_Phone = pName,隐式法等于Phone m_Phone(pName)
	Person(string name, string pName):m_Name(name),m_Phone(pName)
	{
		cout << "Person的构造函数被调用" << endl;
	}
	~Person()
	{
		cout << "Person的析构函数被调用" << endl;
	}
};
void test01()
{
	Person p("张三", "苹果");
	cout << p.m_Name << "拿着:" << p.m_Phone.m_PName <<"手机"<< endl;
}
int main()
{
	test01();
}
/*
	输出结果,说明Phone的构造函数先被调用,Person的析构函数先被调用
	Phone的构造函数被调用
	Person的构造函数被调用
	张三拿着:苹果手机
	Person的析构函数被调用
	Phone的析构函数被调用
*/
  1. 静态成员 静态成员就是在成员变量和成员函数前加上static,称为静态成员 静态成员分为:
  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量
#include<iostream>
using namespace std;
class Person
{
public:
	/*
	  * 所有对象共享同一份数据
	  * 在编译阶段分配内存
	  * 类内声明,类外初始化
  */
	static int m_A;
	//静态成员变量也是有访问权限的
private:
	static int m_B;
};
int Person::m_B = 200;//会报错,因为m_B是私有作用域下的。类外访问不到私有的成员变量
int Person::m_A = 100;//Person作用域下
void test01()
{
	Person p;
	cout << p.m_A << endl;
	Person p2;
	p2.m_A = 200;
	cout << p.m_A << endl;//输出结果是200,说明static是所有对象共享的数据
}
void test02()
{
	//静态成员变量 不属于某个对象上,所有对象都共享同一份数据
	//因此静态成员变量有两种访问方式
	//1.通过对象进行访问
	Person p;
	cout << p.m_A << endl;
	//2.通过类名进行访问
	cout << Person::m_A << endl;


}
int main()
{
	//test01();
	test02();
}
#include<iostream>
using namespace std;
//静态成员函数
//所有对象都共享同一个函数
//静态成员函数只能访问静态成员变量
class Person
{
public:
	//静态成员函数
	static void func()
	{
		m_A = 100;//静态成员函数可以访问静态成员变量
		//m_B = 200;//报错,静态成员变量不可以访问非静态成员变量
		cout << "static void func调用" << endl;
	}
	static int m_A;
	int m_B;
	//静态成员函数也是有访问权限的
private:
	static void func2()
	{
		cout << "static func2调用" << endl;
	}
};
int Person::m_A = 0;
void test01()
{
	//通过对象访问
	Person p;
	p.func();
	//通过类名访问
	Person::func();
	Person::func2();//报错,类外访问不到私有的静态成员函数。
}
int main()
{
	test01();
}

c++对象模型和this指针

  1. 成员变量和成员函数分开存储 在c++中,类内的成员变量和成员函数分开存储只有非静态成员变量才属于类的对象上
#include<iostream>
using namespace std;
//成员变量和成员函数分开存储
class Person
{

	int m_A;//非静态成员变量,输出结果4
	static int m_B;//静态成员变量 不属于类对象,所以输出结果4
	void func() {}//非静态成员函数,不属于类对象上
	static void func2() {}//静态成员函数,也不属于类对象上
};
int Person::m_B = 0;
void test01()
{
	Person p;
	//空对象占用内存空间为1个字节
	//c++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置。
	cout << "size of p=" << sizeof(p) << endl;
}
int main()
{
	test01();
}
  1. this指针 通过前节,我们知道c中成员函数是分开存储的 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用一块代码 那没问题是:这一块代码是如何区分那个对象调用自己的? c通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象 this指针是隐含每一个非静态成员函数的一种指针 this指针不需要点工艺,直接使用即可 this指针的用途
  • 当形参和成员变量同名时,可用this指针区分
  • 在类的非静态成员函数中返回对象本身,可以使用return *this
  1. 空指针访问成员函数 c++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针 如果用到this指针,需要加以判断保证代码的健壮性 实例:
#include<iostream>
using namespace std;
//空指针调用成员函数
class Person
{
public:
	void showClassName()
	{
		cout << "this is Person class" << endl;
	}
	void showPersonAge()
	{
		//报错原因是因为传入的指针式NULL,一般加上这一句
		if (this == NULL)
		{
			return;
		}
		cout << "age = " << m_Age << endl;
	}
	int m_Age;
};
void test01()
{
	Person* p = NULL;
	p->showClassName();//这个是正常调用
	p->showPersonAge();//这个会崩溃,因为
}
int main()
{
	test01();
}
  1. const修饰成员函数 常函数:
  • 成员函数后加const后我们称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明同时加关键字mutable后,在常函数中依然可以修改 常对象:
  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
#include<iostream>
using namespace std;
//常函数
class Person
{
public:
	void showPerson()const//在成员函数后加const,修饰的是this指向,让指针指向的值也不可以修改
	{
		//this指针的本质时指针常量,指针的指向时不可以修改的
		//函数声明后加const相当于const Person * const this;这时候指针指向的值也不可以下修改了
		m_A = 100;//函数声明时,后面加了const意味着这是一个常函数,常函数不能修改属性
		m_B = 200;
	}
	void test02()
	{

	}
	int  m_A;
	mutable int m_B;//特殊变量,即使在常函数中,这个属性仍然可以修改
};
//常对象
void test02()
{
	const Person p;//在对象前加const,变为常对象
	p.m_A = 100;
	p.m_B = 100;//m_B是特殊的值,在常对象下也可以修改
	//常对象只可以调用常函数
	p.showPerson();
	//p.test02();//无法调用,因为test02不是常函数,常对象无法调用非常函数。
}

友元

生活中你的家有客厅(public),有你的卧室(Private) 客厅中所有客人都可以进入,但是卧室是私有的,也就是说只有你能进入 但是,你也可以允许你的好朋友进入。在程序中,有些私有属性也想让类外特殊的一些函数或者类进行访问, 就需要用到友元的技术 友元的目的就是让一个函数或者类访问另一个类中私有成员 友元的关键字为friend 友元的三种实现

  • 全局函数做友元
#include<iostream>
using namespace std;
class Building
{
	//goodGay全局函数是Building的好朋友,可以访问building类的私有成员
	friend void googGay(Building* building);
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
public:
	Building()
	{

		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	}
};
//全局函数
void googGay(Building* building)
{
	cout << "好基友的全局函数 正在访问" << building->m_SittingRoom << endl;
	cout << "好基友的全局函数 正在访问" << building->m_BedRoom << endl;
}
void test01()
{
	Building building;
	googGay(&building);
}
int main()
{
	test01();
}
  • 类做友元
#include<iostream>
using namespace std;
//类做友元
class Building;
class GoodGay
{
public:
	GoodGay();
public:
	void visit();//参观函数,访问building中的属性
	Building* building;
};
class Building
{
	friend class GoodGay;//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 << endl;
	cout << "好基友类正在访问" << building->m_BedRoom << endl;
}
void test01()
{
	GoodGay gg;
	gg.visit();
}
int main()
{
	test01();
}
  • 成员函数做友元
#include<iostream>
using namespace std;
class Building;
class GoodGay
{
public:
	GoodGay();
	void visit();//让visit函数可以访问Buidlding中私有成员
	void visit2();//visit2函数不可以访问Building中私有成员
	Building* building;
};
class Building
{
	friend void GoodGay::visit();//告诉编译器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 << "visit正在访问" << building->m_SittingRoom << endl;
	cout << "visit正在访问" << building->m_BedRoom << endl;
}
void GoodGay::visit2()
{
	cout << "visit2正在访问" << building->m_SittingRoom << endl;
	//cout << "visit正在访问" << building.m_BedRoom << endl;
}
void test01()
{
	GoodGay gg;
	gg.visit();
	gg.visit2();
}
int main()
{
	test01();
}

运算符重载

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

  1. 加号运算符重载 作用:实现两个自定义数据类型相加的运算
#include<iostream>
using namespace std;
//加号运算符重载
//成员函数重载+号
class Person
{
public:
	int m_A;
	int m_B;
public:
	Person()
	{
		m_A = 10;
		m_B = 20;
	}
	//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;
	//}
};
//全局函数重载+号
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;
	Person p2;
	Person p3 = p1 + p2;
	cout << "p3.m_A:" << p3.m_A << endl;
	cout << "p3.m_B:" << p3.m_B << endl;
	//运算符重载,也可以发生函数重载
	Person p4 = p1 + 10;
	cout << "p4.m_B:" << p4.m_B << endl;
}

int main()
{
	test01();
}
  • 总结:对于内置的数据类型的表达式的运算符是不可能改变的
  • 总结:不要滥用运算符重载
  1. 左移运算符重载
#include<iostream>
using namespace std;
class Person
{
	friend ostream& operator<< (ostream& cout, Person& p);//本质opeartor<<(cout,p)简化cout<<p
public:
	Person(int a, int b)
	{
		m_A = a;
		m_B = b;
	}
private:
	int m_A;
	int m_B;
	//利用成员函数重载左移运算符,通常不会利用成员函数重载左移运算符,因为无法让p在右边
};
//只能用全局函数重载左移运算符
ostream& operator<< (ostream & cout, Person & p)//本质opeartor<<(cout,p)简化cout<<p
{
	cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
	return cout;
}
void test01()
{
	Person p(10,20);

	cout << p << endl;
}
int main()
{
	test01();
}

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

#include<iostream>
using namespace std;
//重载递增运算符
//自定义整型
class MyInteger
{
	friend ostream& operator<< (ostream& cout, MyInteger inter);
public:
	MyInteger()
	{
		m_Num = 0;
	}
	//重载前置++运算符
	MyInteger& operator++()
	{
		m_Num++;
		return *this;
	}
	//重载后置++运算符
	//void operator++(int) int 代表占位参数,可以用于区分前置和后置递增
	MyInteger operator++(int)
	{
		//先记录一下当时结果
		MyInteger temp = *this;
		//后递增
		m_Num++;
		//最后将记录的结果返回
		return temp;
	}
	/*
	注意,后置递增返回的是值,而前置递增返回的是引用,原因如下
	前置递增返回引用:如果返回值的话++(++a)这种代码就只会执行一次++因为第二次的++对象已经变了
	后置递增返回值:因为后置递增temp是临时变量,不能返回临时变量的引用*/
private:
	int m_Num;
};
//只能使用全局函数重载左移运算符
ostream& operator<< (ostream& cout, MyInteger inter)
{
	cout << "MyInter.inter:" << inter.m_Num << endl;
	return cout;
}
void test01()
{
	MyInteger myint;
	cout << myint << endl;
	//cout << ++myint << endl;
	cout << myint++ << endl;
	cout << myint << endl;
}
int main() 
{
	test01();
}
  1. 递减运算符重载
#include<iostream>
using namespace std;
class MyImpuse
{
	friend ostream& operator<<(ostream& cout, MyImpuse impuse);
public:
	MyImpuse()
	{
		m_Int = 10;
	}
private:
	int m_Int;
public:
	//前置--运算符的重载
	MyImpuse& operator--()
	{
		m_Int--;
		return *this;
	}
	//后置--运算符的重载
	MyImpuse operator--(int)
	{
		MyImpuse temp = *this;
		m_Int--;
		return temp;
	}
};
//左移运算符的重载
ostream& operator<<(ostream& cout, MyImpuse impuse)
{
	cout << "impuse.m_Int:" << impuse.m_Int << endl;
	return cout;
}
void test01()
{
	MyImpuse p;
	cout << p << endl;
	cout << --p << endl;
	cout << --(--p) << endl;
	cout << p-- << endl;
	cout << p << endl;
}
int main()
{
	test01();
}
  1. 赋值运算符重载 c++编译器至少给一个类添加4个函数
  2. 默认构造函数无参,函数体为空
  3. 默认析构函数无参,函数体为空
  4. 默认拷贝构造函数,对属性进行值拷贝
  5. 赋值运算符operator=对属性进行值拷贝 如果类中有属性指向堆区,做赋值操作时,也会出现深浅拷贝问题
#include<iostream>
using namespace std;
//赋值运算符重载
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);
	cout << "p1的年龄为:" << *p1.m_Age << endl;
	Person p2(20);
	p2 = p1;//赋值操作
	cout << "p2的年龄为:" << *p2.m_Age << endl;
	Person p3(30);
	p3 = p2 = p1;
	cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main()
{
	test01();
}
  1. 关系运算符重载 作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
    实例:
#include<iostream>
using namespace std;
//重载关系运算符
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 (p.m_Age == this->m_Age&&p.m_Name == this->m_Name)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
};
void test01()
{
	Person p1("Tom", 18);
	Person p2("李四", 18);
	if (p1 == p2)
	{
		cout << "p1和p2相等" << endl;
	}
	else
	{
		cout << "p1和p2不相等" << endl;
	}
}
int main()
{
	test01();
}
  1. 函数调用运算符重载
  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此成为仿函数
  • 仿函数没有固定写法,非常灵活 实例:
#include<iostream>
using namespace std;
//函数调用运算符重载
//打印输出类
class MyPrint
{
public:
	//重载函数调用运算符
	void operator()(string test)
	{
		cout << test << endl;
	}
};
void test01()
{
	MyPrint myPrint;
	myPrint("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() {
	test02();
}

继承

继承是面向对象三大特性之一
有些类与类之间存在特殊的关系,例如下图中: 我们发现,定义这些类型时,下级别的成员除了拥有上级别的共性,还有自己的特性
这个时候我们就可以考虑利用继承的技术,减少重复代码

继承的基本语法

例如我们看到的很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同,接下来
我们分别利用普通写法和继承写法来实现网页中的内容,看一下继承存在的意义以及好处。

#include<iostream>
using namespace std;
//普通实现页面
//Java页面
//class Java
//{
//public:
//	void header()
//	{
//		cout << "首页 公开课 登录 注册(公共头部)" << endl; 
//	}
//	void footer()
//	{
//		cout << "帮助中心 交流合作 站内地图(公共底部)" << endl;
//	}
//	void left()
//	{
//		cout << "java python c++(公共分类列表)" << endl;
//	}
//	void content()
//	{
//		cout << "java 学科视频" << endl;
//	}
//
//};
//继承方式实现

//公共页面类
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 << "python下载视频页面如下:" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();	cout << "c++下载视频页面如下:" << endl;
	Cpp c;
	c.header();
	c.footer();
	c.left();
	c.content();
}
int main()
{
	test01();
}

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

继承方式

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

  • 公共继承
  • 保护继承
  • 私有继承
#include<iostream>
using namespace std;
//继承方式

//公共继承
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;//父类中私有权限成员,到子类中访问不到
	}
};
void test01()
{
	Son1 s1;
	s1.m_A = 100;
	//s1.m_B = 100;//保护权限访问不到
}
//保护继承
class Base2
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son2 :protected Base2
{
public:
	void func()
	{
		m_A = 100;//父类中公共成员,到子类中变为保护权限
		m_B = 100;
		//m_C = 100;//父类中私有成员,子类访问不到
	}
};
void test02()
{
	Son2 s1;
	s1.m_A = 100;//保护继承,所以m_A变成保护权限,因此类外访问不到
	s1.m_B = 200;//保护继承,m_B 变成保护权限,类外访问不到
}
class Base3
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son3 :private Base3
{
public:
	void func()
	{
		m_A = 100;//父类中公共成员,到子类中变为私有成员
		m_B = 100;//父类中保护成员,到子类中变为私有成员
		m_C = 100;//父类中私有成员,子类访问不到
	}
};
void test03()
{
	Son3 s1;
	s1.m_A = 100;//私有成员,类外访问不到,下同
	s1.m_B = 100;//
	s1.m_B = 100;//
}
class GrandSon3 :public Son3
{
public:
	void func()
	{
		m_A = 1000;//报错,这是Son3中私有成员,无论GrandSon如何继承,都访问不到
	}
};
int main()
{
	test03;
}

继承中的对象模型

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

#include<iostream>
using namespace std;
//继承中的对象模型
class Base
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son :public Base
{
public:
	int m_D;
};
void test01()
{
	cout << "size of Son=" << sizeof(Son) << endl;
	//输出结果是size of Son=16,意思其实是无论父类是什么属性都会被子类继承下去
	//但是父类中私有成员属性是被编译器隐藏了,因此是访问不到,但是确实是继承下去了。
}
int main()
{
	test01();
}
//利用开发人员命令提示工具查看对象模型
//跳转盘符F:
//跳转到文件路径cd具体路径下
//查看命名
//  c1/d1 reportSingleClassLayout类名 "文件名.cpp"
/*
源.cpp

class Son       size(16):
		+---
 0      | +--- (base class Base)
 0      | | m_A
 4      | | m_B
 8      | | m_C
		| +---
12      | m_D
		+---
*/

结论:父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到

继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用子类的构造函数
问题:父类和子类的构造和析构顺序
实例:

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

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

继承同名成员处理方式

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

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
#include<iostream>
using namespace std;
//继承中同名成员处理
class Base
{
public:
	Base()
	{
		m_A = 100;
	}
	void func()
	{
		cout << "Base - func()调用" << 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;
	/*
	Son m_A=200
	Base m_A=100
	*/
}
//同名函数处理
void test02()
{
	Son s;
	s.func();
	s.Base::func();
	//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
	/*
	Son - func()调用
	Base - func()调用
	*/
}
int main()
{
	//test01();
	test02();
}

总结:

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

继承中静态成员处理方式

问题:继承中同名的静态成员在子类对象上如何进行访问? 静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域 实例:
#include<iostream>
using namespace std;
//继承中的同名静态成员处理方式
class Base
{
public:
	static int m_A;
	static void func()
	{
		cout << "Base - static void func()" << endl;
	}
};
int Base::m_A = 1200;
class Son :public Base
{
public:
	static int m_A;
	static void func()
	{
		cout << "Son - static void func()" << endl;
	}
};
int Son::m_A = 200;

//同名静态成员属性
void test01()
{
	//通过对象访问数据
	cout << "通过对象方式访问:" << endl;
	Son s;
	cout << "Son m_A=" << s.m_A << endl;
	cout << "Base m_A=" << s.Base::m_A << endl;
	//通过类名访问数据
	cout << "通过类名访问:" << endl;
	cout << "Son m_A=" << Son::m_A << endl;
	//第一个::代表通过类名的方式访问 第二个::代表访问父类作用域下
	cout << "Base m_A=" << Son::Base::m_A << endl;
}
//同名静态函数
void test02()
{
	//通过对象方式访问
	Son s;
	s.func();
	s.Base::func();
	//通过类名方式访问
	Son::func();
	Son::Base::func();
	//子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
	//如果想访问父类中被隐藏同名成员,需要加作用域
}
int main()
{
	//test01();
	test02();
}

多继承语法

c允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C
实际开发中不建议使用多继承

#include<iostream>
using namespace std;
//多继承语法
class Base1
{
public:
	Base1()
	{
		m_A = 100;
	}
	int m_A;
};
class Base2
{
public:
	Base2()
	{
		m_A = 200;
	}
	int m_A;
};
//子类  需要继承Base1和Base2
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.m_A << endl;
	//当父类出现同名成员,需要加作用域区分
	cout << "Base1::m_A = " << s.Base1::m_A << endl;
	cout << "Base2::m_B = " << s.Base2::m_A << endl;
}
int main() 
{
	test01();
}

总结:多继承中如果父类中出现了同名情况,子类使用时要加作用域。

菱形继承

菱形继承概念
两个派生类继承同一个基类
又有某个类同时继承自两个派生类
这种继承称为菱形继承,或者钻石继承
典型菱形继承案例:
菱形继承问题

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
#include<iostream>
using namespace std;
//动物类
class Animal {
public:
	int m_Age;
};
//利用虚继承可以解决菱形继承的问题
//继承之前加上关键字virtual变成虚继承
//Animal类称为虚基类
//羊类
class Sheep :virtual public Animal {};
//驼类
class Tuo :virtual public Animal {};
//羊驼类
class SheepTuo :public Sheep, public Tuo {};
void test01()
{
	SheepTuo st;
	st.Sheep::m_Age = 18;
	st.Tuo::m_Age = 29;
	//当菱形继承时,两个父类拥有相同的数据,需要加以作用域区分
	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.Tuo::m_Age << endl;
	/*
	st.Sheep::m_Age=29
	st.Tuo::m_Age=29
	st.m_Age=29
	*/
	//这份数据我们只要一份就可以了,菱形继承导致数据有两份,资源浪费。
}
int main()
{
	test01();
}
/*
class SheepTuo  size(8):
		+---
 0      | +--- (base class Sheep)
 0      | | +--- (base class Animal)
 0      | | | m_Age
		| | +---
		| +---
 4      | +--- (base class Tuo)
 4      | | +--- (base class Animal)
 4      | | | m_Age
		| | +---
		| +---
		+---
*/
//使用虚继承之后
/*

class SheepTuo  size(12):
		+---
 0      | +--- (base class Sheep)
 0      | | {vbptr}
		| +---
 4      | +--- (base class Tuo)
 4      | | {vbptr}
		| +---
		+---
		+--- (virtual base Animal)
 8      | m_Age
		+---

SheepTuo::$vbtable@Sheep@:
 0      | 0
 1      | 8 (SheepTuod(Sheep+0)Animal)

SheepTuo::$vbtable@Tuo@:
 0      | 0
 1      | 4 (SheepTuod(Tuo+0)Animal)
vbi:       class  offset o.vbptr  o.vbte fVtorDisp
		  Animal       8       0       4 0
*/
//所谓的vbptr是虚基类指针,指向一个vbtable(虚基类表)

总结

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

多态

多态的基本概念

多态是C++面向对象三大特性之一
多态分为两类

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数是实现运行时多态 静态多态和动态多态的区别:
  • 静态多态的函数地址早绑定,编译阶段确定函数地址
  • 动态多态的函数地址晚绑定,运行阶段确定函数地址 下面通过案例讲解多态
#include<iostream>
using namespace std;
//多态

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

总结:
多态满足条件

  • 有继承关系
  • 子类重写父类中的虚函数
    多态使用条件
  • 父类指针或者引用指向子类对象
    重写:函数返回值类型 函数名 参数列表完全一致为重写

多态的原理剖析

#include<iostream>
using namespace std;
class Animal
{
public:
	virtual void speak()
	{
		cout << "动物说话。" << endl;
	}
	/*
	* 虚函数的情况
	class Animal    size(4):
        +---
 0      | {vfptr}
        +---

Animal::$vftable@:
        | &Animal_meta
        |  0
 0      | &Animal::speak

Animal::speak this adjustor: 0
*/
	//void speak()
	//{
	//	cout << "动物说话。" << endl;
	//}
	/*
	非虚函数的情况
	class Animal    size(1):
        +---
        +---
		*/
	/*
	由此可见*/
};
class Cat :public Animal
{
public:
	/*
	子类没有发生重写时
	class _s__CatchableType size(28):
        +---
 0      | properties
 4      | pType
 8      | _PMD thisDisplacement
20      | sizeOrOffset
24      | copyFunction
        +---

class _s__CatchableTypeArray    size(4):
        +---
 0      | nCatchableTypes
 4      | arrayOfCatchableTypes
        +---

class Cat       size(4):
        +---
 0      | +--- (base class Animal)
 0      | | {vfptr}
        | +---
        +---

Cat::$vftable@:
        | &Cat_meta
        |  0
 0      | &Animal::speak//注意此处是Animal的speak函数
 */
	void speak()
	{
		cout << "猫在说话。" << endl;
	}
	/*
	子类发生重写时
	class _s__CatchableType size(28):
        +---
 0      | properties
 4      | pType
 8      | _PMD thisDisplacement
20      | sizeOrOffset
24      | copyFunction
        +---

class _s__CatchableTypeArray    size(4):
        +---
 0      | nCatchableTypes
 4      | arrayOfCatchableTypes
        +---

class Cat       size(4):
        +---
 0      | +--- (base class Animal)
 0      | | {vfptr}
        | +---
        +---

Cat::$vftable@:
        | &Cat_meta
        |  0
 0      | &Cat::speak//此处被覆盖成猫的speak函数

Cat::speak this adjustor: 0
*/
};
void doSpeak(Animal &animal)//父类的引用接受子类的对象Animal& animal = cat;
{
	animal.speak();
}
void test01()
{
	Cat cat;
	doSpeak(cat);
	Dog dog;
	doSpeak(dog);
	/*
	小猫在说话
	小狗在说话
	*/
}
int main()
{
	test01();
}

多态案例--计算器类

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

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护 实例:
#include<iostream>
using namespace std;
////普通实现
//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;
//		}
//		//如果想扩展新的功能,需要修改源码,在真实的开发中,提倡开闭原则(对扩展进行开放,对修改进行关闭)
//	}
//	int m_Num1;//操作数1
//	int m_Num2;//操作数2
//};
//void test01()
//{
//	//创建计算器对象
//	Calculator c;
//	c.m_Num1 = 10;
//	c.m_Num2 = 10;
//	cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+") << endl;
//	cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getResult("-") << endl;
//	cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult("*") << endl;
//}
//利用多态实现计算器
//实现计算器的基类(抽象类)
class AbstractCalculator
{
public:
	int m_Num1;
	int m_Num2;
	virtual int getResult()
	{
		return 0;
	}
};
//加法计算器类
class AddCalculator :public AbstractCalculator
{
	int getResult()
	{
		return m_Num1 + m_Num2;
	}
};
//减法计算器类
class SubCalculator :public AbstractCalculator
{
	int getResult()
	{
		return m_Num1 - m_Num2;
	}
};
//乘法计算器类
class MulCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 * m_Num2;
	}
};
void test02()
{
	//多态使用条件
	//父类指针或者引用指向子类对象
	AbstractCalculator* abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
	//堆区的数据手动开辟手动销毁
	delete abc;//这是把堆区的数据释放了
	//减法运算
	abc = new SubCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
	delete abc;
	//乘法运算
	abc = new MulCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
}
//多态带来的好处
//1.组织结构清晰
//2.可读性强
//3.对于后期和前期扩展以及维护性高
int main()
{
	test02();
}

纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法 virtual 返回值类型 函数名 (参数列表) = 0; 当类中有了纯虚函数,这个类也成为抽象类
抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类纯虚函数,否则也属于抽象类
#include<iostream>
using namespace std;
//纯虚函数和抽象类
class Base
{
public:
	//纯虚函数
	//只要有一个纯虚函数,这个类称为抽象类
	//抽象类特点:
	//1.无法实例化对象
	//2.子类必须重写父类纯虚函数,否则也是抽象类
	virtual void func() = 0;
};
class Son :public Base
{
public:
	virtual void func() {
		cout<< "func函数的调用" << endl;
	};
};
void test01()
{
	//Base b抽象类无法实例化对象
	//new Base抽象类无法实例化对象
	//Son s;//子类必须重写父类中的纯虚函数,否则无法实例化对象
	Base* base = new Son;
	base->func();
}
int main()
{
	test01();
}

多态案例二-制作饮品

案例描述:
制作饮品的大致流程:煮水,冲泡,倒入杯中,加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

#include<iostream>
using namespace std;
//多态案例2制作饮品
class AbstractDrinking
{
public:
	//煮水
	virtual void Boil() = 0;
	//冲泡
	virtual void Brew() = 0;
	//倒入杯中
	virtual void PourInCup() = 0;
	//加入辅料
	virtual void PutSomething() = 0;
	//制作饮品
	void makeDrink()
	{
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};
//制作咖啡
class Coffee :public AbstractDrinking
{
public:
	virtual void Boil()
	{
		cout << "煮农夫山泉" << endl;
	}
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡咖啡" << endl;
	}
	//倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入辅料糖和牛奶" << endl;
	}
}; 
//制作茶叶
class Tea :public AbstractDrinking
{
public:
	virtual void Boil()
	{
		cout << "煮自来水" << endl;
	}
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}
	//倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入柠檬和枸杞" << endl;
	}
};
//制作函数
void doWork(AbstractDrinking* abs)
{
	abs->makeDrink();
	delete abs;
}
void test01()
{
	//制作咖啡
	doWork(new Coffee);
	//制作茶叶
	cout << "------------" << endl;
	doWork(new Tea);
}
int main()
{
	test01();
}

虚析构和纯虚析构

多态使用是,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决办法:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现 虚析构和纯虚析构区别:
  • 如果是纯虚析构,该类属于抽象类,无法实例化对象 虚析构语法:
    virtual ~类名(){} 纯虚析构语法: virtual ~类名() = 0; 类名
#include<iostream>
using namespace std;
//纯虚析构和虚析构
class Animal
{
public:
	virtual void speak() = 0;
	Animal()
	{
		cout << "Animal构造函数调用" << endl;
	}
	//虚析构
	//virtual ~Animal()
	//{
	//	cout << "Animal析构函数被调用" << endl;
	//}
	//纯虚析构
	//有纯虚析构之后,这个类也属于抽象类,无法实例化对象
	virtual ~Animal() = 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("Tom");
	animal->speak();
	//父类指针在析构的时候,不会调用子类中析构函数,导致子类如果有堆区属性,会出现内存泄漏
	delete animal;
}
int main()
{
	test01();
//没有使用虚析构时控制台输出:
/*
Animal构造函数调用
Cat构造函数被调用
Tom小猫在说话
Animal析构函数被调用
*/
//可见没有走Cat的析构函数,所以堆区的内存没有释放干净,导致内存泄漏
//利用虚析构可以解决父类指针释放子类对象时不干净的问题
//使用虚析构时控制台输出:
/*
Animal构造函数调用
Cat构造函数被调用
Tom小猫在说话
Cat析构函数被调用
Animal析构函数被调用
*/
}

总结:

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

多态案例三-电脑组装

案例描述:
电脑主要组成部分为CPU 显卡 内存条 将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel 和Lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
实例:

#include<iostream>
using namespace std;
//抽象CPU
class Cpu
{
public:
	virtual void calculate() = 0;
};
//抽象显卡
class VideoCard
{
public:
	virtual void display() = 0;
};
//抽象内存条
class Memory
{
public:
	virtual void storage() = 0;
};
//电脑类
class Computer
{
public:
	//构造函数传入三个零件指针
	Computer(Cpu* cpu, VideoCard* vc, Memory* st)
	{
		m_cpu = cpu;
		m_vc = vc;
		m_st = st;
	}
	//提供一个工作函数
	void doWork()
	{
		m_cpu->calculate();
		m_vc->display();
		m_st->storage();
	}
	//提供一个析构函数,释放零件的内存
	~Computer()
	{
		if (m_cpu != NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}
		if (m_vc != NULL)
		{
			delete m_vc;
			m_vc = NULL;
		}
		if (m_st != NULL)
		{
			delete m_st;
			m_st = NULL;
		}
	}
private:
	Cpu* m_cpu;
	VideoCard* m_vc;
	Memory* m_st;
};
//具体厂商
//Intel厂商
class IntelCpu :public Cpu
{
public:
	virtual void calculate()
	{
		cout << "Intel的cpu开始计算了" << endl;
	}
};
class IntelVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Intel的显卡开始显示了" << endl;
	}
};
class IntelMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Intel的内存条开始工作了" << endl;
	}
};
class LenovoCpu :public Cpu
{
	void calculate()
	{
		cout << "联想的cpu开始工作了" << endl;
	}
};
class LenovoVideoCard :public VideoCard
{
	void display()
	{
		cout << "联想的显卡开始工作了" << endl;
	}
};
class LenovoMemory :public Memory
{
	void storage()
	{
		cout << "联想的内存条开始工作了" << endl;
	}
};
//组装不同的电脑
void test01()
{
	//第一台电脑零件
	Cpu* intelCpu = new IntelCpu;
	VideoCard* interCard = new IntelVideoCard;
	Memory* intelMem = new IntelMemory;
	//创建第一个电脑
	cout << "-------------------------" << endl;
	cout << "第一台电脑开始工作" << endl;
	Computer* computer1 = new Computer(intelCpu, interCard, intelMem);
	computer1->doWork();
	delete computer1;
	//创建第二台电脑
	cout << "-------------------------" << endl;
	cout << "第二台电脑开始工作" << endl;
	Computer* computer2 = new Computer(new LenovoCpu, new LenovoVideoCard, new IntelMemory);
	computer2->doWork();
}
int main()
{
	test01();
}

文件操作

程序运行时产生的数据属于临时数据,程序一旦运行结束就会被释放 通过文件可以将数据持久化
c++中对文件操作需要包含头文件

  1. 文本文件 文件以文本ASCII码形式存储在计算机中
  2. 二进制文件,文件以文本的二进制存储在计算机中,用户一般不能直接读懂它们
    操作文件的三大类:
  3. ofstream:写操作
  4. ifstream:读操作
  5. fstream:读写操作

文本文件

写文件

写文件步骤如下:

  1. 包含头文件 #include
  2. 创建流对象 ofstream ofs;
  3. 打开文件 ofs.open("文件路径",打开方式)
  4. 写数据 ofs<<"写入的数据";
  5. 关闭文件 ofs.close(); 注意:文件打开方式可以配合使用,利用|操作符
    例如:用二进制方式写文件 ios::binary | ios::out
#include<iostream>
using namespace std;
#include<fstream>//头文件的包含
//文本文件写文件
void test01()
{
	//包含头文件,fstream
	//创建输出流对象
	ofstream ofs;
	//指定打开方式
	ofs.open("test.txt", ios::out);//不指定路径则默认创建在项目路径下
	//写内容
	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;
	//关闭文件
	ofs.close();
}
int main()
{
	test01();
}

总结:

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

读文件

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

  1. 包含头文件 include
  2. 创建流对象 ifstream ifs;
  3. 打开文件并判断文件是否打开成功 ifs.open("文件路径",打开方式);
  4. 读数据 四种方式读取
  5. 关闭文件 ifs.close(); 实例:
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
void test01()
{
	//创建流对象
	ifstream ifs;
	//打开文件,判断是否打开成功
	ifs.open("test.txt", ios::in);
	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}
	//读数据
	//第一种
	//char buf[1024] = { 0 };
	//while (ifs>>buf)
	//{
	//	cout << buf << endl;
	//}
	////关闭文件
	//ifs.close();
	//第二种
	/*char buf[1024] = { 0 };
	while (ifs.getline(buf,sizeof(buf)))
	{
		cout << buf << endl;
	}
	ifs.close();*/
	//第三种
	//string buf;
	//while (getline(ifs,buf))
	//{
	//	cout << buf << endl;
	//}
	//ifs.close();
	//第四种,不太推荐了,效率慢
	char c;
	while ((c=ifs.get()) != EOF)//EOF是文件尾的意思
	{
		cout << c;
	}
	ifs.close();
}
int main()
{
	test01();
}

二进制文件

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

写文件

二进制方式写文件主要是利用流对象调用成员函数write
函数原型:ostream& write(const char* buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
实例:

#include<iostream>
using namespace std;
#include<fstream>
//二进制文件写文件
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 p = { "张三",28 };
	ofs.write((const char*)&p, sizeof(Person));
	//关闭文件
	ofs.close();
}

读文件

二进制方式读文件主要利用流对象调用成员函数read 函数原型:istream& read(char* buffer,int len); 参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
实例

#include<iostream>
#include<fstream>
using namespace std;
class Person
{
public:
	int m_Age;
	char m_Name[64];
};
void test01()
{
	ifstream ifs;
	ifs.open("person.txt", ios::in | ios::binary);
	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}
	//读文件
	Person p;
	ifs.read( (char*)& p, sizeof(Person));
	cout << "姓名:" << p.m_Name << "年龄:"<<p.m_Age << endl;
}
int main()
{
	test01();
}

职工管理系统

管理系统需求

职工管理系统可以用来管理公司内所有员工的信息
本教程主要利用C++来实现一个基于多态的的职工管理系统
公司中职工分为三类:普通员工、经理、老板、显示信息时,需要显示职工编号、职工姓名、职工岗位、以及职责
普通员工的职责:完成经理交给的任务
经理职责:完成老板交给的任务,并下发给员工
老板职责:管理公司所有事物
管理系统中需要实现的功能如下:

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

创建项目

创建项目步骤如下:

  • 创建新项目
  • 添加文件

创建项目

创建管理类

管理类的负责内容如下:

  • 与用户的沟通菜单界面
  • 对职工增删改查的操作
  • 与文件的读写与交互
  1. 创建文件 在头文件和源文件的文件夹下分别创建workerManager.h和workerManager.cpp文件
  2. 头文件实现 在workerManager.h中设计管理类
    代码如下:
   #pragma once //避免头文件重复包含
#include <iostream>
using namespace std;
class workerManager
{
public:
	workerManager();
	~workerManager();
};

  1. 源文件
#include "workerManager.h"
workerManager::workerManager()
{

}
workerManager::~workerManager()
{

}

菜单功能

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

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

退出功能

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

创建职工类

  1. 创建职工抽象类
  2. 创建普通员工类
  3. 创建经理类
  4. 创建老板类
  5. 测试多态

添加职工

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

  1. 功能分析 用户在批量创建时,可能会创建不同种类的职工
    如果想将所有不同种类的员工都放入一个数组中,可以将所有员工的指针维护到一个数组中
    如果想在程序中维护这个不定长度的数组,可以将数组创建到堆区,并利用worker**的指针去维护

文件交互,写文件

功能描述:对文件进行读写
在上一个添加功能中,我们只是将所有的数据添加到了内存中,一旦程序结束就无法保存了
因此文件管理类中需要一个与文件进行交互的功能,对于文件进行读写操作

  1. 设定文件 路径 首相我们将文件路径,在workerManager.h中添加宏常量,并且包含头文件fstream
  2. 成员函数的声明 在workManager.h中类里添加成员函数void save()

c++提高编程

模板

模板的特点:

  • 模板不可以直接使用,他只是一个框架
  • 模板的通用并不是万能的

函数模板

  • C++另一种编程思想称为泛型编程,主要利用的技术就是模板
  • C++提供两种模板机制:函数模板和类模板

函数模板语法

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

#include<iostream>
using namespace std;
void changeInt(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}
void changeDouble(double& a, double& b)
{
	double temp = a;
	a = b;
	b = temp;
}
void test01()
{
	int a = 10;
	int b = 20;
	changeInt(a, b);
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	double c = 4.95;
	double d = 5.52;
	changeDouble(c, d);
	cout << "c=" <<c<< endl;
	cout << "d=" <<d<< endl;
}
//函数模板
template<typename T>//声明一个模板,告诉编译器后面代码中紧跟着T不要报错,T是一个通用的数据类型
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}
void test02()
{
	int a = 10;
	int b = 20;
	//使用函数模板的两种方式
	//1.自动类型推导
	mySwap(a, b);
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	//2.显示指定类型
	mySwap<int>(a, b);
}
int main()
{
	//test01();
	test02();
}

总结:

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

函数模板注意事项

注意事项:

  • 自动类型推导,必须推导出一致的数据类型T才可以使用(推导出类型不一样的T就不行)
  • 模板必须要确定出T的数据类型,才可以使用,也就是说必须要指定T的数据类型。
template<class T>
void func()
{
	cout<<"func 调用"<<endl;
}
void test02()
{
	func<int>();//此时必须要用显式指定了,因为没有办法用隐式指定。
}

函数模板案例

案例描述:

  • 利用模板封装一个排序的函数,可以对不同数据类型的数据进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别利用char数组和int数组进行测试 实例:
#include<iostream>
using namespace std;
template<class T>
void mySwap(T& a,T& b)
{
	T temp = a;
	a = b;
	b = temp;
}
template<class T>
void mySort(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		int max = i;
		for (int j = i+1; j < len; j++)
		{
			if (arr[max]<arr[j])
			{
				max = j;//更新最大值下标
			}
		}
		if (max != i)
		{
			//交换max和i元素
			mySwap(arr[i], arr[max]);
		}
	}
}
//提供打印数组的模板
template<class T>
void printArray(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}
void test01()
{
	//测试char数组
	char charArr[] = "badrfagahahfshsad";
	mySort(charArr, size(charArr));
	printArray(charArr, size(charArr));
}
void test02()
{
	//测试int数组
	int intArr[] = { 4,56,263,36,5474,25,2 };
	mySort(intArr, size(intArr));
	printArray(intArr, size(intArr));
}
int main()
{
	//test01();
	test02();
}

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

普通函数和函数模板的区别:

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
//普通函数
int myAdd01(int a,int b)
{
	return a+b;
}
void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';
	cout<<myAdd01(a,c)<<end;
}//输出109,这个最终输出是不会报错的,因为这里发生了隐式类型转换,将char转换为int
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
template<class T>
T myAdd02(T a,T b)
{
	return a + b;
}
void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';//
	cout<<myAdd02(a,c)<<endl;//这个最终会报错
}
  • 如果利用显示指定类型的方式,可以发生隐式类型转换
如果把刚才最后一行代码改成:  
cout<<myAdd02<int>(a,c)<<endl;//这个不会报错,会发生隐式类型转换,将char转为int

实例:

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

调用规则如下:

  1. 如果函数模板和普通函数都可以实现,优先调用普通函数
  2. 可以通过空模板参数列表来强制调用函数模板
  3. 函数模板也可以发生重载
  4. 如果函数模板可以产生更好的匹配,优先调用函数模板
#include<iostream>
using namespace std;
//普通函数和函数模板的调用规则
//如果函数模板和普通函数都可以调用,优先调用函数模板
//可以通过空模板参数列表强制调用函数模板
//函数模板可以发生函数重载
//如果函数模板可以产生更好的匹配,优先调用函数模板
void myPrint(int a, int b)
{
	cout << "调用普通函数" << endl;
}
template<class T>
void myPrint(T a, T b,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();
}

实际开发中函数重载和模板不要同时出现,避免二义性

模板的局限性

局限性:

  • 模板的通用性不是万能的 例如:
template<class T>
void f(T a,T b)
{
	a = b;
}

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

#include<iostream>
using namespace std;

class Person
{
	//姓名
public:
	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
public:
	int m_Age;
	string m_Name;
};
template<class T>
bool myCompare(T& a, T& b)
{
	if (a == b)
	{
		return true;
	}
	else
	{
		return false;
	}
}
template<> bool myCompare(Person& p1, Person& p2)
{
	if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name)
	{
		return true;
	}
	else
	{
		return false;
	}
}
void test01()
{
	int a = 10;
	int b = 20;
	bool ret = myCompare(a, b);
	string ss = ret ? "a==b" : "a!=b";
	cout << ss << endl;
}
void test02()
{
	Person p1("Tom", 110);
	Person p2("Tom", 10);
	bool ret = myCompare(p1, p2);
	string ss = ret ? "p1==p2" : "p1!=p2";
	cout << ss << endl;
}

int main()
{
	////test01();
	test02();//报错
	//有两种方法,第一是运算符重载
	//第二种方法,利用具体化Person的版本实现代码,具体化优先调用

}

总结:

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

类模板

类模板语法

类模板作用:

  • 建立一个通用类,类中成员数据类型可以不具体指定,用一个虚拟的类型来代表 语法:
template<typename T>
template<class NameType,class AgeType>
class Person
{
	public:
	Person(NameType name,AgeType age)
	{
		this->m_Name = name;
		this->m_Name = age;
	}
	void showPerson()
	{
		cout<<"name"<<this->m_Name<<"age:"this->m_Age<<endl;
	}
	NameType m_Name;
	AgeType m_Age;
}
void test01()
{
	Person<string,int>p1("孙悟空",999);
	p1.showPerson();
}

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

类模板和函数模板的 区别

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

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数
#include<iostream>
using namespace std;
template<class NameType,class AgeTyep>
class Person
{
public:
	Person(NameType name, AgeTyep age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	void showPerson()
	{
		cout << "name:" << this->m_Name << "age:" << this->m_Age << endl;
	}
public:
	NameType m_Name;
	AgeTyep m_Age;
};
//类模板没有自动类型推导的使用方式
void test01()
{
	//Person p("孙悟空", 1000);//这是错误的,无法使用自动类型推导
	Person<string, int>p("孙悟空", 1000);
}
//类模板在模拟参数列表中可以有默认参数
void test02()
{
	//如果前面的template<class NameType,class AgeTyep>写成如下形式:
	//template<class NameType,class AgeType=int>那么下面就不用在指定int的类型了
	Person<string, int>p("猪八戒", 1000);
}
int main()
{
	test01();
}

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

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

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建
#include<iostream>
using namespace std;
//类模板中成员函数只有在调用时才去创建
class Person1
{
public:
	void showPerson()
	{
		cout << "Person1 show" << endl;
	}
};
class Person2
{
public:
	void showPerson()
	{
		cout << "Person2 show" << endl;
	}
};
template<class T>
class myClass
{
public:
	T obj;
	//类模板中的成员函数
	void func1()
	{
		obj.showPerson1();
	}
	void func1()
	{
		obj.showPerson2();
	}
};
void test01()
{
	myClass<Person1>m;
	m.func1();
	m.func2();//报错
}
int main()
{
	test01;
}

类模板对象做函数参数

学习目标:

  • 类模板实例化出的对象,想函数传参的方式 一共有三种传入方式:
  1. 指定传入的类型--直接 显示对象的数据类型
  2. 参数模板化--及那个对象的参数变为模板进行传递
  3. 整个类模板化,--将这个对象类型模板化进行传递
#include<iostream>
using namespace std;
//类模板对象做函数参数
template<class T1,class T2>
class Person
{
public:
	Person(T1 name, T2 age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	void showPerson()
	{
		cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
	}
	T1 m_Name;
	T2 m_Age;
};
//指定传入类型
void printPerson1(Person<string,int>& p)
{
	p.showPerson();

}
void test01()
{
	Person<string, int>p("孙悟空", 100);
	printPerson1(p);
}
//参数模板化
template<class T1,class T2>
void printPerson2(Person<T1, T2>& p)
{
	p.showPerson();
	cout << "T1的类型为:" << typeid(T1).name() << endl;
	cout << "T2的类型为:" << typeid(T2).name() << endl;
}
void test02()
{
	Person<string, int>p("猪八戒", 90);
	printPerson2(p);
}
template<class T>
void printPerson3(T& p)
{
	p.showPerson();
	//T的数据类型
	cout << "T的数据类型;" << typeid(T).name() << endl;
}
//整个类都模板化
void test03()
{
	Person<string, int>p("唐僧", 30);
	printPerson3(p);
}
int main()
{
	//test01();
	//test02();
	test03();
	/*
	姓名:唐僧年龄:30
T的数据类型;class Person<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int>
*/
}

总结:
最常用的还是第一种直接指定传入类型

类模板和继承

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

  • 当子类继承的父类是一个类模板的时候,子类在声明的时候,要指定出父类中的T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定父类中T的类型,子类也需要变为类模板
#include<iostream>
using namespace std;
//类模板和继承
template<class T>
class Base
{
public:
	Base()
	{
		cout << "T的数据类型为:" << typeid(T).name() << endl;
	}
	T m;
};
//class Son :public Base//错误,必须要知道父类中T的类型,才能继承给子类
//{
//
//};
//正确做法,但这样父类类型被固定了
class Son :public Base<int>
{

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

总结:如果父类时类模板,子类许哟啊指定出父类中T的数据类型

类模板成员函数类外实现

学习目标:能够掌握类模板中成员函数类外实现
实例:

template <class T1,class T2>
class Person
{
	public:
	Person(T1 name,T2 name)
	// {
	// 	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<class T1,class T2>
Person<T1 ,T2>::Person(T1 name,T2 age);
{
	this->m_Name = name;
	this->m_Age = age;
}
//成员函数类外实现
template<class T1,class T2>
void Person<T1,T2>::showPerson();
{
	cout<<"姓名:"<<this->m_Name<<"年龄"<<this->m_Age<<endl;
}
void test01()
{
	Person<string,int>P("Tom",20);
	P.showPerson();
}
int main()
{
	test01();
}

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

类模板分文件编写

学习目标:

  • 掌握类模板成员函数分文件编写产生的问题和解决方式:
    问题:
  • 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到 解决:
  • 解决方式1:直接包含cpp源文件 Person.h
#pragma once
#include<iostream>
using namespace std;
template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);
	void showPerson();
	T1 m_Name;
	T2 m_Age;
};

Person.cpp

#include"Person.h"
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Age = age;
	this->m_Name = name;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout << "姓名:" << this->m_Name << "年龄" << this->m_Age << endl;
}

源.cpp

#include<iostream>
using namespace std;
#include"Person.cpp"
//类模板的分文件编写和解决

void test01()
{
	Person<string, int>p("Jerry", 18);
	p.showPerson();
}
int main()
{
	test01();
}
//注意直接包含include"Person.h"会报错,2个无法解析的外部命令,也就是指的test01里面
//的两个命令,此时只需要将include"Person.h"变为"Person.cpp"就可以。
  • 解决方式2:将声明和实现写到同一个文件中,并更改后缀名为hpp,hpp时约定的名称,并不是强制 实例:
    person.hpp中代码
#pragma once
#include<iostream>
using namespace std;
template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);
	void showPerson();
	T1 m_Name;
	T2 m_Age;
};

template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Age = age;
	this->m_Name = name;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout << "姓名:" << this->m_Name << "年龄" << this->m_Age << endl;
}

源.cpp中的代码

#include<iostream>
using namespace std;
#include"Person.hpp"
//类模板的分文件编写和解决

void test01()
{
	Person<string, int>p("Jerry", 18);
	p.showPerson();
}
int main()
{
	test01();
}
//注意直接包含include"Person.h"会报错,2个无法解析的外部命令,也就是指的test01里面
//的两个命令,此时只需要将include"Person.h"变为"Person.cpp"就可以。也可以将cpp里面的东西
//复制到h文件后面,重命名为hpp最后在源.cpp中直接包含hpp

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

类模板和友元

学习目标:

  • 掌握类模板配合友元函数的类内和类外实现 全局函数类内实现:直接在类内声明友元即可
#include<iostream>
using namespace std;
//通过全局函数打印Person信息
template<class T1,class T2>
class Person
{
	//全局函数类内实现
	friend void printPerson(Person<T1, T2> p)
	{
		cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
	}
public:
	Person(T1 name, T2 age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
private:
	T1 m_Name;
	T2 m_Age;
};
void test01()
{
	Person<string, int>p("Tom", 20);
	printPerson(p);
}
int main()
{
	test01();
}

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

#include<iostream>
using namespace std;
//提前让编译器知道Person类存在
template<class T1, class T2>
class Person;
//类外实现
template<class T1, class T2>
void printPerson2(Person<T1, T2>p)
{
	cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
}

template<class T1,class T2>
class Person
{
	//全局函数 类内实现
	//friend void printPerson2(Person<T1, T2>p);//这样写是不行的,
	//如果全局函数是类外实现,需要让编译器提前知道有这个函数存在。
	friend void printPerson2<>(Person<T1, T2> p);
public:
	Person(T1 name, T2 age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
private:
	T1 m_Name;
	T2 m_Age;
};

void test02()
{
	Person<string, int>p("Jerry", 20);
	printPerson2(p);
}
int main()
{
	test02();
}
//类外实现会发生”无法解析的外部命令的错误“,因为声明的时候
//是按照普通函数声明的,但是实现的时候按照函数模板
//的方式来实现了,所以会发生错误。

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

类模板案例

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

  • 可以对内置数据类型以及自定义数据类型的数据进行存储
  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量 实例: myArray.hpp
#pragma once
#include<iostream>
using namespace std;
template<class T>
class MyArray
{
public:
	//有参构造
	MyArray(int capacity)
	{
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->pAddress = new T[this->m_Capacity];
		cout << "MyArray的有参构造" << endl;
	}
	//拷贝构造函数
	MyArray(const MyArray& arr)
	{
		cout << "MyArray的拷贝构造函数" << endl;
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		//this->pAddress = arr.pAddress;//浅拷贝,会导致堆区内存重复释放
		this->pAddress = new T[arr.m_Capacity];
		//将arr中的数据都拷贝过来
		for (int i = 0; i < this->m_Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
	}
	//operator=防止浅拷贝问题
	MyArray& operator=(const MyArray& arr)
	{
		//先判断原来堆区是否有数据,如果有先释放
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}
		//深拷贝
		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] = 100,需要返回T的引用
	T& operator[](int index)
	{
		return this->pAddress[index];
	}
	//返回数组容量
	int getCapacity()
	{
		return this->m_Capacity;
	}
	int getSize()
	{
		return this->m_Size;
	}

	~MyArray()
	{
		cout << "MyArray析构函数" << endl;
		//MyArray的析构函数
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
		}
	}
private:
	T* pAddress;//指向堆区开辟的真实数组
	int m_Capacity;//数组容量
	int m_Size;//数组元素个数
};

源.cpp

#include"MyArray.hpp"
void printIntArray(MyArray<int>& arr)
{
	for (int i = 0; i < arr.getSize(); i++)
	{
		cout << arr[i] << endl;
	}
}
void test01()
{
	MyArray<int> arr1(5);
	for (int  i = 0; i < 5; i++)
	{
		arr1.Push_Back(i);//利用尾插法向数组中插入数据
	}
	cout << "arr1的打印输出为:" << endl;
	printIntArray(arr1);
	cout << "arr1的容量为:"<<arr1.getCapacity() << endl;
	cout << "arr1的大小为:"<<arr1.getSize() << endl;
	MyArray<int>arr2(arr1);
	cout << "arr2的打印输出为:" << endl;
	printIntArray(arr2);
	//尾删法
	arr2.Pop_Back();
	cout << "arr2尾删后:" << endl;
	printIntArray(arr2);
	cout << "arr2的容量为:" << arr2.getCapacity() << endl;
	cout << "arr2的大小为:" << arr2.getSize() << endl;
	//MyArray<int>arr2(arr1);
	//MyArray<int>arr3(100);
	//arr3 = arr1;
	/*
	输出
	MyArray的有参构造
	arr1的打印输出为:
	0
	1
	2
	3
	4
	arr1的容量为:5
	arr1的大小为:5
	MyArray的拷贝构造函数
	arr2的打印输出为:
	0
	1
	2
	3
	4
	arr2尾删后:
	0
	1
	2
	3
	arr2的容量为:5
	arr2的大小为:4
	MyArray析构函数
	MyArray析构函数
*/
}
//测试自定义数据类型
class Person
{
public:
	Person() {};
	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	string m_Name;
	int m_Age;;
};
void printPersonArray(MyArray<Person>& arr)
{
	for (int i = 0; i < arr.getSize(); i++)
	{
		cout << "姓名:" << arr[i].m_Name << "年龄:" << arr[i].m_Age << endl;
	}
}
void test02()
{
	MyArray<Person>arr(10);
	Person p1("孙悟空", 999);
	Person p2("韩信", 10);
	Person p3("李四", 150);
	Person p4("张三", 103);
	Person p5("王五", 107);
	//尾插法插入
	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;
/*
MyArray的有参构造
姓名:孙悟空年龄:999
姓名:韩信年龄:10
姓名:李四年龄:150
姓名:张三年龄:103
姓名:王五年龄:107
arr的容量为:10
arr的大小为:5
MyArray析构函数
*/
}
int main()
{
	//test01();
	test02();
}

STL初识

STL的诞生

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

STL的基本概念

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

STL六大组件

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

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

STL中容器、算法、迭代器

容器:置物之所也
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组,链表,树,栈,队列,集合,映射表等
这些容器分为序列式容器和关联式容器两种;
序列式容器:强调值的排序,序列式容器中的每个元素都有固定的位置
关联式容器:二叉树结构,个元素之间没有严格的物理上的顺序关系
算法:问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫算法(Algorithms)
算法分为:质变算法和非质变算法。
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找,计数,遍历,寻找极值等
迭代器:容器和算法之间的粘合剂
提供一种方法,是指能够依序寻访某个容器所含的各个元素,而又无需暴露该容器内部表示方式。
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针
迭代器种类:
常用的容器中迭代器为双向迭代器和随机访问迭代器

容器算法迭代器初识

了解STL中容器,算法,迭代器概念之后,我们利用代码感受STL的魅力
STL中最常用的容器为Vector,可以理解为数组,下面学习如何向容器中插入数据,并遍历这个容器

vector存放内置数据类型

容器:vector
算法:for_each
迭代器:vector

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
//vector容器存放内置数据类型
void myPrint(int val)
{
	cout << val << endl;
}
void test01()
{
	//创建vector的容器
	vector<int>v;
	//向容器中插入数据
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);
	//通过迭代器访问容器中的数据
	//vector<int>::iterator itBegin = v.begin();//起始迭代器,指向容器中第一个元素
	//vector<int>::iterator itEnd = v.end();//结束迭代器 指向容器中最后一个元素的下一个位置
	////第一种遍历方式
	//while (itBegin != itEnd)
	//{
	//	cout << *itBegin << endl;
	//	itBegin++;
	//}
	//第二种遍历方式
	//for (vector<int>::iterator it = v.begin();	it != v.end()	; it++)
	//{
	//	cout << *it << endl;
	//}
	//第三种调用方式。利用STL提供遍历算法

	for_each(v.begin(), v.end(), myPrint);
}
int main()
{
	test01();
}

vector存放自定义数据类型

学习目标:vector中存放自定义数据类型,并打印输出
实例:

#include<iostream>
#include<vector>
using namespace std;
//vector容器中存放自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	string m_Name;
	int m_Age;
};
void test01()
{
	vector<Person>v;
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);
	//向容器中添加数据
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);
	//遍历容器中的数据
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		//两种方法都行
		cout << "姓名:" << it->m_Name<<"\t" << "年龄:" << it->m_Age << endl;
		cout << "姓名:" << (*it).m_Name<<"\t" << "年龄:" << (*it).m_Age << endl;
	}
}
void test02()
{
	vector<Person*>v;
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);
	//向容器中添加数据
	v.push_back(&p1);
	v.push_back(&p2);
	v.push_back(&p3);
	v.push_back(&p4);
	v.push_back(&p5);
	//遍历容器
	for (vector<Person*>::iterator it = v.begin();it != v.end();it++)
	{
		//这里有个技巧(*it)的类型就是尖括号里面的数据类型,就是Person*
		cout << "姓名:" << (*it)->m_Name << "年龄:" << (*it)->m_Age << endl;
	}
}
int main()
{
	//test01();
	test02();
}

小技巧:*it的数据类型就是vector<>中尖括号的数据类型

vector容器中嵌套容器

学习目标:容器中嵌套容器,我们将所有数据进行遍历输出
实例:

#include<iostream>
#include<vector>
using namespace std;
//容器嵌套容器
void test01()
{
	vector<vector<int>>v;
	//创建小容器
	vector<int>v1;
	vector<int>v2;
	vector<int>v3;
	vector<int>v4;
	//向小容器添加数据
	for (int i = 0; i < 4; i++)
	{
		v1.push_back(i + 1);
		v2.push_back(i + 2);
		v3.push_back(i + 3);
		v4.push_back(i + 4);
	}
	//将小容器添加到大容器中
	v.push_back(v1);
	v.push_back(v2);
	v.push_back(v3);
	v.push_back(v4);
	//通过大容器遍历所有的数据
	for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++)
	{
		//(*it)---vector<int>
		for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++)
		{
			cout << *vit << " ";
		}
		cout << endl;
	}
}
int main()
{
	test01();
}

常用容器

string基本概念

本质:

  • string是C++风格的字符串,而string本质上是一个类 string和char*的区别
  • char*是一个指针
  • string是一个类,类内部封装了char*,管理这个字符串,是一个char型的容器 特点:
    string类内部封装了很多成员方法
    例如:查找find,拷贝copy,删除delete替换replace,插入insert
    string管理char
    所分配的内存,不用担心赋值越界和取值越界等,由类内部进行负责

string构造函数

构造函数原型:

  • string();//创建一个空的字符串,例如string str; string(const char* s);//使用字符串s初始化
  • string(const string& str);//使用一个string对象初始化另一个string对象
  • string(int n,char c);//使用n个字符串c初始化 实例:
#include<iostream>
#include<string>
using namespace std;
//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();
/*
s2=hello world
s3=hello world
s4=aaaaaaaaaa
*/
}

string赋值操作

功能描述:

  • 给string字符串进行赋值 赋值的函数原型:
  • string& operator=(const char* s);//char*类型字符串赋值给当前的字符串
  • string& operator=(const string &s)//把字符串s赋给当前的字符串
  • string& operator=(char c);//把字符赋值给当前的字符串
  • string& assign(const char* s);把字符串s赋给当前的字符串
  • string& assign(const char* s,int n);//把字符串s的前n个字符赋给当前的字符串
  • string& aasign(const string& s);//把字符串s赋给当前字符串
  • string& assign(int n,char c);//用n个字符c赋给当前字符串 实例:
#include<iostream>
using namespace std;
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 world2");
	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(6, 'd');
	cout << "str7=" << str7 << endl;
}
int main()
{
	test01();
}
/*
str1=hello world
str2=hello world
str3=a
str4=hello world2
str5=hello
str6=hello
str7=dddddd
*/

string字符串拼接

功能描述:

  • 实现在字符串末尾拼接字符串 函数原型:
  • string& operator+=(const char* str);//重载+=操作符
  • string& operator+=(const char c);//重载+=操作符
  • string& operator+=(const string& str);//重载+=操作符
  • string& append(const char* s);//把字符串s连接到当前字符串结尾
  • string& append(const char*s ,int n);//把字符串s的前几个字符连接到当前字符串结尾
  • string& append(const string& s);//同operator+=(const string& str)
  • string& append(const string &s,int pos,int n);//字符串s中从pos开始的n个字符连接到字符串结尾
#include<iostream>
using namespace std;
void test01()
{
	string str1 = "我";
	str1 += "爱玩游戏";
	cout << "str1=" << str1 << endl;
	str1 += ';';
	cout << "str1=" << str1 << endl;
	string str2 = "LoL DnF";
	str1 = str1 + str2;
	cout << "str1=" << str1 << endl;
	string str3 = "I";
	str3.append("love");
	cout << "str3=" << str3 << endl;
	str3.append("game abcd", 4);
	cout << "str3=" << str3 << endl;
	str3.append(str2);
	cout << "str3=" << str3 << endl;
	str3.append(str2, 0, 3);//只截取LOL
	cout << "str3=" << str3 << endl;
	str3.append(str2, 4, 3);//只截取DNF
	cout << "str3=" << str3 << endl;
}
int main()
{
	test01();
}

字符串重载版本很多,初学者记住几个就可以

string查找和替换

功能描述:

  • 查找:查找指定字符串是否存在
  • 替换:在指定的位置替换字符串 函数原型:
  • int find(const string& str,int pos = 0)const;//查找str第一次出现位置,从pos开始查找
  • int find(const char* s,int pos =0)const;//查找s第一次出现位置,从pos开始查找
  • int find(const char* s,int pos,int n)const;//从pos位置查找s的前n个字符第一次位置
  • int find(const char c,int pos = 0)const;//查找字符c第一次出现位置
  • int rfind(const string& str,int pos = npos)const;//查找str最后一次位置从pos开始查找
  • int rfind(const char* s,int pos = npos)const;//查找s最后一次出现的位置,从pos开始查找
  • int rfind(const char* s,int pos,int n)const;//从pos查找s的前n个字符最后一次位置
  • int rfind(const char c,int pos = 0)const;//查找字符c最后一次出现的位置
  • string& replace(int pos,int n,const string& str);//替换从pos开始n个字符为字符串str
  • string& replace(int pos,int n,const char* s);//替换从pos开始的n个字符为字符串s
#include<iostream>
using namespace std;
//字符查找和替换
//查找
void test01()
{
	string str1 = "abcdefgde";
	int pos = str1.find("de");
	cout << "pos=" << pos << endl;//如果查到了,返回索引,否则返回-1
	if (pos!=-1)
	{
		cout << "找到了字符串,pos=" << pos << endl;
	}
	else
	{
		cout << "未找到字符串" << endl;
	}
	//rfind和find的区别,
	//rfind是从右往左查找,而find从左往右查找。返回的值都一样。
	pos = str1.rfind("de");
	cout << "pos=" << pos << endl;//
}
//替换
void test02()
{
	string str1 = "abcdefg";
	str1.replace(1, 3, "1111");
	cout << "str1=" << str1 << endl;
	//输出:str1=a1111efg
}
int main()
{
	//test01();
	test02();
}

string字符串比较

功能描述:

  • 字符串之间的比较 比较方式:
  • 字符串比较是按字符串的ASCII进行比较
    = 返回0
    ">"返回1
    < 返回-1
    函数原型:
  • int compare(const string &s)const;//与字符串比较
  • int compare(const char * s)const;//与字符串比较 实例:
#include<iostream>
using namespace std;
//字符串比较
void test01()
{
	string str1 = "hello";
	string str2 = "jello";
	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();
}

对比方式是按照Ascii码逐个比较

string字符存取

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

  • char& operator[](int n);//通过[]方式取字符
  • char& at(int n);//通过at方法获取字符 实例:
#include<iostream>
using namespace std;
//string 字符获取
void test01()
{
	string str = "hello";
	cout << "str=" << str << endl;
	//通过[]访问单个字符
	for (int i = 0; i < str.size(); i++)
	{
		cout << str[i] << endl;
	}
	//通过at方法访问单个字符
	for (int i = 0; i < str.size(); i++)
	{
		cout << str.at(i) << " ";
	}
	cout << endl;
	//修改单个字符
	str[0] = 'x';
	cout << "str = " << str << endl;
	str.at(1) = 'x';
	cout << "str = " << str << endl;
}
int main()
{
	test01();
}

string插入和删除

功能描述:

  • 对string字符串进行插入和删除操作
    函数原型:
  • string& insert(int pos,const char* s);//插入字符串
  • string& insert(int pos,const string& str);//插入字符串
  • string& insert(int pos,int n,char c);//在指定位置插入n歌字符c
  • string& erase(int pos,int n = npos);//删除从pos开始的n个字符
#include<iostream>
using namespace std;
//字符串插入和删除
void test01()
{
	string str = "hello";
	//插入
	str.insert(1, "111");
	//hello
	cout << "str=" << str << endl;//str=h111ello
	//删除
	str.erase(1, 3);
	cout << "str=" << str << endl;//str=hello
}
int main()
{
	test01();

}

string字串

功能描述:

  • 从字符串中获取想要的字串 函数原型:
  • string substr(int pos = 0,int n = npos)const;//返回由pos开始的n个字符串组成的字符串 实例:
#include<iostream>
using namespace std;
//string 求字串
void test01()
{
	string str = "dfaga";
	string subStr = str.substr(1, 3);
	cout << "subStr = " << subStr << endl;//fag
}
//使用操作
void test02()
{
	string email = "zhagnsan@sina.com";
	//从邮件地址获取用户名信息
	int pos = email.find("@");
	string userName = email.substr(0, pos);
	cout << "用户名:" << userName << endl;//用户名:zhagnsan
}
int main()
{
	//test01();
	test02();
}

总结:灵活运用求字串功能,可以在实际开发中获取有效的信息

vector容器

vector基本概念

功能:

  • vector数据结构和数组非常相似也成为单端数组
    vector与普通数组去呗:
  • 不同之处在于数组是静态空间,而vector可以动态扩展
    动态扩展:
  • 并不是在原空间之后续借新空间,而是找更大的内存空间,然后将原数组拷贝新空间,释放原空间
  • vector容器的迭代器是支持随机访问的迭代器

vector构造函数

功能描述:

  • 创建vector容器 函数原型:
  • vector
  • vector(v.begin(),v.end());//将v[begin(),end())区间的元素拷贝给本身
  • vector(n,elem);//构造函数将n个elem拷贝给本身
  • vector(const vector& vec);//拷贝构造函数
#include<iostream>
using namespace std;
#include<vector>
//vector容器构造
void printVector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << (*it) << " ";
	}
	cout << endl;
}
void test01()
{
	vector<int>v1;//默认构造无参构造
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}
	printVector(v1);//0 1 2 3 4 5 6 7 8 9
	//通过区间方式构造
	vector<int>v2(v1.begin(), v1.end());
	printVector(v2);//0 1 2 3 4 5 6 7 8 9
	//n个elem方式构造
	vector<int>v3(10, 100);
	printVector(v3);//100 100 100 100 100 100 100 100 100 100
	//拷贝构造
	vector<int>v4(v3);
	printVector(v4);//100 100 100 100 100 100 100 100 100 100
}
int main()
{
	test01();
}

vector赋值操作

功能描述:

  • 给vector容器进行赋值 函数原型:
  • vector& operator=(const vector &vec);//重载等号操作符
  • assign(beg,end);//将[beg,end]区间中的数据拷贝赋值给本身
  • assign(n,elem);//将n个elem拷贝赋值给本身 实例:
#include<iostream>
#include<vector>
using namespace std;
//vector赋值
void printVector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << (*it) << " ";
		
	}
	cout << endl;
}
void test01()
{
	vector<int> v1;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}
	printVector(v1);
	//赋值operator=
	vector<int>v2;
	v2 = v1;
	printVector(v2);
	//assign
	vector<int>v3;
	v3.assign(v1.begin(), v1.end());
	printVector(v3);
	//n个elem方式赋值
	vector<int>v4;
	v4.assign(10, 100);
	printVector(v4);
/*
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
100 100 100 100 100 100 100 100 100 100
*/
}
int main()
{
	test01();

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

vector容量和大小

功能描述:

  • 对vector容器的容量和大小操作 函数原型:
  • empty();//判断容器是否为空
  • capacity();//容器的容量
  • size();//返回容器中元素的个数
  • resize(int num);//重新制定容器的长度为num,若容器变长则以默认值0填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
  • resize(int num,elem);//重新指定容器的长度为num若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除 实例:
#include<iostream>
using namespace std;
#include<vector>
//vector容器的容量和大小操作
void printVector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << (*it) << " ";
	}
	cout << endl;
}
void test01()
{
	vector<int>v1;
	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的容量为:" << v1.capacity() << endl;
	}
}
int main()
{
	test01();
}
#include<iostream>
using namespace std;
#include<vector>
//vector容器的容量和大小操作
void printVector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << (*it) << " ";
	}
	cout << endl;
}
void test01()
{
	vector<int>v1;
	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的容量为:" << v1.capacity() << endl;
		cout << "v1的大小为:" << v1.size() << endl;
/*
0 1 2 3 4 5 6 7 8 9
v1不为空
v1的容量为:13
v1的大小为:10
*/
		//容量永远大于等于大小
		//注意,容量为13而不是10,这是他内部算法决定的,动态扩展机制。
	}
	//重新制定大小
	v1.resize(15);
	//如果重新制定的比原来的长,默认用0来填充新位置
	printVector(v1);//0 1 2 3 4 5 6 7 8 9 0 0 0 0 0
	v1.resize(20, 100);//利用重载版本可以指定默认填充值,参数2
	printVector(v1);//0 1 2 3 4 5 6 7 8 9 0 0 0 0 0 100 100 100 100 100
	//重新指定的比原来短,超出部分被删掉
	v1.resize(5);
	printVector(v1);//0 1 2 3 4
}
int main()
{
	test01();
}

vector插入和删除

功能描述:

  • 对vector容器进行插入,删除操作 函数原型:
  • push_back(ele);//尾部删除ele
  • pop_back();//删除最后一个元素
  • insert(const_iterator pos,ele);//迭代器指向位置pos插入元素ele
  • insert(const_iterator pos,int count,ele);//迭代器指向位置pos插入count个元素ele
  • erase(const_iterator pos);//删除迭代器指向的元素
  • erase(const_iterator start,const_iterator end);//删除迭代器从start到end之间的元素
  • clear();//删除容器中所有的元素 实例:
#include<iostream>
using namespace std;
#include<vector>
void printVector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << (*it) << " ";
	}
	cout << endl;
}
void test01()
{
	vector<int>v1;
	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);
	printVector(v1);//10 20 30
	//尾删
	v1.pop_back();
	printVector(v1);//10 20 
	//插入
	v1.insert(v1.begin(), 100);
	printVector(v1);//100 10 20
	v1.insert(v1.begin(), 2, 1000);
	printVector(v1);//1000 1000 100 10 20
	//删除,参数也是迭代器
	v1.erase(v1.begin());
	printVector(v1);// 1000 100 10 20
	v1.erase(v1.begin(), v1.end());
	printVector(v1);//空容器,类似于清空,类似于v1.clear()
}

int main()
{
	test01();
}

vector数据存取

功能描述:

  • 对vector中的数据的存取操作 函数原型:
  • at(int idx);//返回索引idx所指的数据
  • operator[];//返回索引idx所指的数据
  • front();//返回容器中第一个数据元素
  • back();//返回容器中最后一个数据元素 实例:
#include<iostream>
using namespace std;
#include<vector>
//vector容器,数据存取
void test01()
{
	vector<int>v1;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}
	//利用[]方式访问数组中元素
	for (int i = 0; i < v1.size(); i++)
	{
		cout << v1[i] << " ";
	}
	cout << endl;
	//利用at方式访问元素
	for (int i = 0; i < v1.size(); i++)
	{
		cout << v1.at(i) << " ";
	}
	cout << endl;
	//获取第一个和最后一个元素
	cout << "第一个元素为:" << v1.front() << endl;
	//获取最后一个元素
	cout << "最后一个元素为:" << v1.back() << endl;
/*
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
第一个元素为:0
最后一个元素为:9
*/
}
int main()
{
	test01();
}

vector互换容器

功能描述:

  • 实现两个容器内元素进行互换 函数原型:
  • swap(vec)//将vec与本身的元素互换
#include<iostream>
using namespace std;
#include<vector>
//vector容器互换
void printVector(vector<int>& v)
{
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << (*it) << " ";
	}

	cout << endl;
}
//基本使用
void test01()
{
	vector<int> v1;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);

	}
	cout << "交换前的打印" << endl;
	printVector(v1);
	vector<int> v2;
	for (int i = 10; i > 0; i--)
	{
		v2.push_back(i);
	}
	printVector(v2);
	cout << "交换后的打印" << endl;
	v1.swap(v2);
	printVector(v1);
	printVector(v2);
/*
交换前的打印
0 1 2 3 4 5 6 7 8 9
10 9 8 7 6 5 4 3 2 1
交换后的打印
10 9 8 7 6 5 4 3 2 1
0 1 2 3 4 5 6 7 8 9
*/

}
//实际用途
//巧用swap可以收缩内存空间
void test02()
{
	vector<int> v;
	for (int i = 0; i < 100000; i++)
	{
		v.push_back(i);
	}
	cout << "v的容量为:" << v.capacity() << endl;
	cout << "v的大小为:" << v.size() << endl;
	//v的容量为:138255
	//v的大小为:100000
	v.resize(3);//重新指定大小
	cout << "v的容量为:" << v.capacity() << endl;
	cout << "v的大小为:" << v.size() << endl;
	//v的容量为:138255
	//v的大小为:3
	//这里可以看出容量大大浪费了,容量远大于大小
	//可以巧用swap收缩内存
	vector<int>(v).swap(v);//vector<int>(v)是匿名对象
	//刚才是调用拷贝构造函数创建匿名对象,会按v目前所用元素个数(3)来初始化匿名对象大小。
	//.swap(v)是做了容器的交换。匿名对象指向大的空间但是会被系统回收。
	cout << "v的容量为:" << v.capacity() << endl;
	cout << "v的大小为:" << v.size() << endl;
	//v的容量为:3
	//v的大小为:3
}
int main()
{
	//test01();
	test02();
}

vector预留空间

功能描述:

  • 减少vector在动态扩展容量时的扩展次数 函数原型:
  • reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问 实例:
#include<iostream>
#include<vector>
using namespace std;
//vector容器 预留空间
void test01()
{
	vector<int> v;
	//利用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;
	//num = 30,一共开辟了30次内存。
	//使用reserve之后,num = 1,只开辟了一次
}
int main()
{
	test01();
}

deque容器

deque容器基本概念

功能:

  • 双端数组,可以对头端进行插入和删除操作 deque和vector的区别:
  • vector对于头部的插入和删除效率低,数据量越大,效率越低
  • deque相对而言,对头部的插入删除速度会比vector快
  • vector访问元素时的速度会比deque快,这和两者内部实现有关 deque内部工作原理: deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据
    中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间
  • deque容器中的迭代器也是支持随机访问的

deque构造函数

功能描述:

  • deque容器构造 函数原型:
  • deque
  • deque(beg,end);//构造函数将[beg,end)区间中的元素拷贝给本身。
  • deque(n,elem);//构造函数将n个elem拷贝给本身
  • deque(const deque& deq);//拷贝构造函数 实例:
#include<iostream>
using namespace std;
#include<deque>
//deque构造函数
void printDeque(const deque<int>& d)//如果限制只读状态,则迭代器也需要变
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
void test01()
{
	deque<int>d1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	printDeque(d1);
	deque<int>d2(d1.begin(), d1.end());
	printDeque(d2);
	deque<int>d3(10, 100);
	printDeque(d3);
	deque<int>d4(d3);
	printDeque(d4);
	/*
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
100 100 100 100 100 100 100 100 100 100
100 100 100 100 100 100 100 100 100 100
*/
}
int main()
{
	test01();
}

deque赋值操作

功能描述:

  • 给deque容器进行赋值 函数原型:
  • deque& operator=(const deque& deq);//重载等号操作符
  • assign(beg,end);//将[beg,end)区间中的数据拷贝赋值给本身。
  • assign(n,elem);将n个elem拷贝赋值给本身 实例:
#include<iostream>
using namespace std;
#include<deque>
//deque容器打印
void printDeque(const deque<int>& d)
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
//deque容器赋值操作
void test01()
{
	deque<int>d1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	printDeque(d1);
	//=赋值操作
	deque<int>d2;
	d2 = d1;
	printDeque(d2);
	//assign赋值操作
	deque<int>d3;
	d3.assign(d1.begin(), d1.end());
	printDeque(d3);
	deque<int>d4;
	d4.assign(10, 100);
	printDeque(d4);
}

deque大小操作

功能描述:

  • 对deque容器的大小进行操作 函数原型:
  • deque.empty();//判断容器是否为空
  • deque.size();//返回容器中元素的个数
  • deque.resize(num);//重新指定容器的长度为num,若容器变长,则以默认值重新填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
  • deque.resize(num,elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。 实例:
#include<iostream>
#include<deque>
using namespace std;
void printDeque(const deque<int>& d)
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
//deque容器大小操作
void test01()
{
	deque<int> d1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	if (d1.empty())
	{
		cout << "d1为空" << endl;
	}
	else
	{
		cout << "d1不为空" << endl;
		cout << "d1的大小为:" << d1.size() << endl;
		//deque容器没有容量概念
	}
	//重新指定大小
	d1.resize(15);//用0来填充
	printDeque(d1);
	d1.resize(15, 1);//用1来填充
	d1.resize(5);
}
int main()
{
	test01();
}
#include<iostream>
#include<deque>
using namespace std;
void printDeque(const deque<int>& d)
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
//deque容器大小操作
void test01()
{
	deque<int> d1;
	for (int i = 0; i < 10; i++)
	{
		d1.push_back(i);
	}
	if (d1.empty())
	{
		cout << "d1为空" << endl;
	}
	else
	{
		cout << "d1不为空" << endl;
		cout << "d1的大小为:" << d1.size() << endl;
		//deque容器没有容量概念
	}
	//重新指定大小
	d1.resize(15);//用0来填充
	printDeque(d1);
	d1.resize(15, 1);//用1来填充
	printDeque(d1);
	d1.resize(5);
	printDeque(d1);
/*
d1不为空
d1的大小为:10
0 1 2 3 4 5 6 7 8 9 0 0 0 0 0
0 1 2 3 4 5 6 7 8 9 0 0 0 0 0
0 1 2 3 4
*/
}
int main()
{
	test01();
}

deque插入和删除

功能描述:

  • 向deque容器中插入和删除数据 函数原型: 两端插入操作:
  • push_back(elem);//在容器尾部添加一个数据
  • push_front(elem);//在容器头部插入一个数据
  • pop_back();//删除容器最后一个数据
  • pop_front();//删除容器第一个数据 指定位置操作:
  • insert(pos,elem);//在pos位置插入一个elem元素的拷贝,返回新数据的位置
  • insert(pos,elem);//在pos位置插入n个elem数据,无返回值。
  • insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
  • clear();//清空容器所有的数据
  • erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
  • erase(pos);//删除pos位置的数据,返回下一个数据的位置。
void test02()
{
	deque<int>d1;
	d1.push_back(10);
	d1.push_back(20);
	d1.push_front(100);
	d1.push_front(200);
	printDeque(d1);
	//200 100 10 20
	//insert插入
	d1.insert(d1.begin(), 1000);
	printDeque(d1);
	d1.insert(d1.begin(), 2, 10000);
	//按照区间插入
	deque<int>d2;
	d2.push_back(1);
	d2.push_back(2);
	d2.push_back(3);
	d1.insert(d1.begin(), d2.begin(), d2.end());//d1.begin的位置插入d2.begin()daoo d2.end的区间
}
void test03()
{
	deque<int>d1;
	d1.push_back(10);
	d1.push_back(20);
	d1.push_front(100);
	d1.push_front(200);
	//删除
	deque<int>::iterator it = d1.begin();
	it++;
	d1.erase(it);
	printDeque(d1);//200 10 20
	//按照区间方式删除
	d1.erase(d1.begin(), d1.end());//相当于d1.clear();
	printDeque(d1);
}
int main()
{
	test03();
}

总结:

  • 插入和删除提供的位置是迭代器!
  • 尾插---push_back
  • 尾删---pop_back
  • 头删---push_front
  • 头删---pop_front

deque数据存取

功能描述

  • 对deque中的数据的存取操作 函数原型:
  • at(int idx);//返回索引idx所指的数据
  • operator[];//返回索引idx所指的数据
  • front();//返回容器中第一个数据元素
  • back();//返回容器中最后一个数据元素 实例:
#include<iostream>
using namespace std;
#include<deque>
//deque容器数据存取
void test01()
{
	deque<int> d;
	d.push_back(10);
	d.push_back(20);
	d.push_back(30);
	d.push_front(100);
	d.push_front(200);
	d.push_front(300);
	//通过[]方式访问元素
	for (int i = 0; i < d.size(); i++)
	{
		cout << d[i] << " ";
	}
	cout << endl;
	//通过at方式访问
	for (int i = 0; i < d.size(); i++)
	{
		cout << d.at(i) << " ";
	}
	cout << endl;
	cout << "第一个元素" << d.front() << endl;
	cout << "最后一个元素" << d.back() << endl;
	/*
300 200 100 10 20 30
300 200 100 10 20 30
第一个元素300
最后一个元素30
*/
}
int main()
{
	test01();
}

deque排序

功能描述:

  • 利用算法实现对deque容器进行排序 算法:
  • sort(iterator beg ,iterator end)//对beg和end区间内元素进行排序 实例:
#include<iostream>
#include<deque>
#include<algorithm>
using namespace std;
void printDeque(const deque<int> d)
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
//deque容器排序
void test01()
{
	deque<int>d;
	d.push_back(10);
	d.push_back(20);
	d.push_back(30);
	d.push_front(13);
	d.push_front(26);
	d.push_front(17);
	cout << "排序前" << endl;
	printDeque(d);
	//排序操作,默认升序
	//对于支持随机访问的迭代器都可以利用sort算法对其进行排序,vector也可以。
	sort(d.begin(), d.end());
	cout << "排序后" << endl;
	printDeque(d);
}
int main()
{
	test01();
}

案例:评委打分

案例描述:

有5名选手,选手ABCDE,10个评委分别对每一名选手打分,去除最高分,去除评委中最低分,取平均分。

实现步骤

  1. 创建5名选手,放到vector中
  2. 遍历vector容器,取出来每一个选手执行for循环,可以把10个评分打分存到deque容器中
  3. sort算法对deque容器中分数排序,去除最高和最低分
  4. deque容器遍历一遍,累加总分
  5. 获取平均分 示例代码:
#include<iostream>
#include<ctime>
#include<algorithm>
using namespace std;
#include<vector>
#include<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<Person>& v)
{
	string nameSeed = "ABCDE";
	for (int i = 0; i < 5; i++)
	{
		string name = "选手";
		name += nameSeed[i];
		int score = 0;
		Person p(name, score);
		//将创建的Person对象放入容器中
		v.push_back(p);
	}
}
void setScore(vector<Person>& v)
{
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		deque<int>d;
		for (int i = 0; i < 10; i++)
		{
			int score = rand() % 41 + 60;//60~100
			d.push_back(score);
		}
		//cout << "选手:" << it->m_Name << "打分" << endl;
		//for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++)
		//{
		//	cout << *dit << " ";
		//}
		cout << endl;
		//排序
		sort(d.begin(), d.end());
		//去除最高分和最低分
		d.pop_back();
		d.pop_front();
		//取平均分
		int sum = 0;
		for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++)
		{
			sum += *dit;//累加
		}
		int avg = sum / d.size();
		//将平均分赋给选手
		(*it).m_Score = avg;
	}
}
void showScore(vector<Person>& v)
{
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << "姓名:" << it->m_Name << "平均分:" << it->m_Score << endl;
	}
}
int main()
{
	//随机数种子
	srand((unsigned int)time(NULL));
	//创建5名选手
	vector<Person> v;//存放选手容器
	createPerson(v);
	//测试
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << "姓名:" << (*it).m_Name << "得分:" << (*it).m_Score << endl;
	}
	//给5名选手打分
	setScore(v);
	//显示最后打分
	showScore(v);
}
/*
姓名:选手A得分:0
姓名:选手B得分:0
姓名:选手C得分:0
姓名:选手D得分:0
姓名:选手E得分:0

姓名:选手A平均分:81
姓名:选手B平均分:79
姓名:选手C平均分:79
姓名:选手D平均分:82
姓名:选手E平均分:80
*/

stack容器

stack基本概念

概念:stack是一种先进后出(first in last out filo)的数据结构,她只有一个出口
栈中只有顶栈的元素才可以被外界使用,因此栈不允许有遍历行为

stack常用接口

功能描述:栈容器常用的对外接口
构造函数:

  • stack
  • stack(const stack& stk)//拷贝构造函数 赋值操作:
  • stack& operator=(const stack& stk);//重载等号操作符 数据存取:
  • push(elem);//向栈顶添加元素
  • pop();//从栈顶移除第一个元素
  • top();//返回栈顶元素 大小操作:
  • empty();//判断堆栈是否为空
  • size();//返回栈的大小 实例:
#include<iostream>
#include<stack>
using namespace std;
//栈stack容器
void test01()
{
	//特点:符合先进后出的数据结构
	stack<int>s;
	//入栈
	s.push(10);
	s.push(20);
	s.push(30);
	s.push(40);
	//只要栈不为空,查看栈顶,并且执行出栈操作
	while (!s.empty())
	{
		//查看栈顶元素
		cout << "栈顶元素:" << s.top() << endl;
		//出栈
		s.pop();
	}
	cout << "栈的大小:" << s.size() << endl;
}
/*
栈顶元素:40
栈顶元素:30
栈顶元素:20
栈顶元素:10
栈的大小:0
*/
int main()
{
	test01();
}

queue容器

queue基本概念

概念:Queue是一种先进先出(first in first out,fifo)的数据结构,他有两个出口
队列容器允许从一端新增元素,从另一端移除元素
队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为
队列中进数据称为--入队 push
队列中出数据称为--出队 pop
生活中的队列:排队

queue常用接口

功能描述:栈容器常用的对外接口 构造函数:

  • queue
  • queue(const queue& que);//拷贝构造函数 赋值操作:
  • queue& operator=(const queue &que);//重载等号操作符 数据存取:
  • push(elem);//往队尾添加元素
  • pop();//从队头移除第一个元素
  • back();//返回最后一个元素
  • front();//返回第一个元素 大小操作:
  • empty();//判断堆栈是否为空
  • size();//返回栈的大小 实例:
#include<iostream>
using namespace std;
#include<queue>
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	string m_Name;
	int m_Age;
};
//队列
void test01()
{
	//创建队列
	queue<Person>q;
	//准备数据
	Person p1("唐僧", 30);
	Person p2("孙悟空", 22);
	Person p3("猪八戒", 13);
	Person p4("沙僧", 75);
	//入队
	q.push(p1);
	q.push(p2);
	q.push(p3);
	q.push(p4);
	//判断只要队列不为空,查看对头,查看队尾,出队
	while (!q.empty())
	{
		//查看对头
		cout << "队头元素---姓名:" << q.front().m_Name << "年龄:" << q.front().m_Age << endl;
		//查看队尾
		cout << "队尾元素---姓名:" << q.back().m_Name << "年龄:" << q.back().m_Age << endl;
		//出队
		q.pop();
	}
	cout << "队列的大小为:" << q.size() << endl;
}
int main()
{
	test01();
}
/*
队头元素---姓名:唐僧年龄:30
队尾元素---姓名:沙僧年龄:75
队头元素---姓名:孙悟空年龄:22
队尾元素---姓名:沙僧年龄:75
队头元素---姓名:猪八戒年龄:13
队尾元素---姓名:沙僧年龄:75
队头元素---姓名:沙僧年龄:75
队尾元素---姓名:沙僧年龄:75
队列的大小为:0
*/

list容器

list基本概念

功能:将数据进行链式存储
链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
链表的组成:一个是存储数据元素的数据域,另一个是存储下一个节点地址的指针域
STL中的链表是一个双向循环链表
优点:可以对任意位置进行快速插入删除元素
缺点:容器遍历速度没有数组快,占用的空间比数组大
由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器
list的优点:

  • 采用动态存储分配,不会造成内存浪费和溢出
  • 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素 list缺点:
  • 链表灵活,但是空间(指针域)和时间(遍历)额外耗费较大
    list有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的
    总结:STL中list和vector是两个最常使用的容器,各有优缺点。

list构造函数

功能描述:

  • 创建list容器 函数原型:
  • list
  • list(beg,end);//构造函数将[beg,end)区间中的元素拷贝给本身
  • list(n,elem);//构造函数将n个elem拷贝给本身。
  • list(const list& lst);//拷贝构造函数 实例;
#include<iostream>
using namespace std;
#include<list>
//list容器构造函数
void printListArray(const list<int>& lst)
{
	for (list<int>::const_iterator it = lst.begin(); it != lst.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
void test01()
{
	//创建list容器
	list<int>L1;//默认构造函数
	//添加元素
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);
	//遍历容器
	printListArray(L1);
	//区间方式构造
	list<int>L2(L1.begin(), L1.end());
	printListArray(L2);
	//拷贝构造
	list<int>L3(L2);
	printListArray(L3);
	//n个elem
	list<int>L4(10, 1000);
	printListArray(L4);
}
int main()
{
	test01();
}
/*
10 20 30 40
10 20 30 40
10 20 30 40
1000 1000 1000 1000 1000 1000 1000 1000 1000 1000
*/

list构造方式同其他几个STL常用容器一样

list赋值和交换

功能描述:

  • 给list容器进行赋值,以及交换list容器 函数原型:
  • assign(beg,end);//将[beg,end)区间中的数据拷贝赋值给本身。
  • assign(n,elem);//将n个elem拷贝赋值给本身。
  • list& operator=(const list& lst);//重载等号操作符
  • swap(lst);//将lst与本身的元素互换。 实例:
#include<iostream>
using namespace std;
#include<list>
//赋值
void printList(const list<int>& L)
{
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)
	{
		cout << *it << endl;
	}
}
void test01()
{
	list<int>L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);
	list<int>L2;
	L2 = L1;//operator=赋值
	printList(L2);
	list<int>L3;
	L3.assign(L2.begin(), L2.end());
	printList(L3);
	list<int>L4;
	L4.assign(10, 100);
	printList(L4);

}
void test02()
{
	list<int>L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);
	list<int>L2;
	L2.assign(10, 100);
	cout << "交换前:" << endl;
	printList(L1);
	printList(L2);
	cout << endl;
	L1.swap(L2);
	cout << "交换后:" << endl;
	printList(L1);
	printList(L2);
}
int main()
{
	test02();
/*
交换前:
10
20
30
40
100
100
100
100
100
100
100
100
100
100

交换后:
100
100
100
100
100
100
100
100
100
100
10
20
30
40
*/
}

list大小操作

功能描述:

  • 对list容器的大小进行操作 函数原型:
  • size();//返回容器中的元素
  • empty();//判断容器是否为空
  • resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则以默认值填充新位置。
  • resize(num,elem);//重新制定容器的长度为num,若容器变成,则以elem值填充新位置。如果容器变短,则末尾超出容器的长度被删除。
#include<iostream>
using namespace std;
#include<list>
//list容器大小操作
void printList(const list<int>& L)
{
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
void test01()
{
	list<int>L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);
	printList(L1);
	//判断容器是否为空
	if (L1.empty())
	{
		cout << "L1为空" << endl;
	}
	else
	{
		cout << "L1不为空" << endl;
		cout << "L1的元素个数为:" << L1.size() << endl;
	}
	//重新制定大小
	L1.resize(10,10000);
	printList(L1);
	L1.resize(2);
	printList(L1);
}
int main()
{
	test01();
}
/*
10 20 30 40
L1不为空
L1的元素个数为:4
10 20 30 40 10000 10000 10000 10000 10000 10000
10 20
*/

list插入和删除

功能描述:

  • 对list容器进行数据的插入和删除 函数原型:
  • push_back(elem);//在容器尾部假如一个元素
  • pop_back();//删除容器中最后一个元素
  • push_front(elem);//在容器开头插入一个元素
  • pop_front();//在容器开头移除第一个元素
  • insert(pos,elem);//在pos位置插入elem元素的拷贝,返回新数据的位置。pos是迭代器it
  • insert(pos,n,elem);//在pos位置插入[beg,end)区间的数据,无返回值。
  • insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
  • clear();//移除容器的所有数据
  • erase(pos);//删除pos位置的数据,返回下一个数据的位置。
  • erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
  • remove(elem);//删除容器中所有与elem值匹配的元素。elem不是迭代器,而是元素

list数据存取

功能描述:

  • 对list容器中数据进行存取 函数原型:
  • front();//返回第一个元素
  • back();//返回最后一个元素 L1[0]不可以用[]访问容器中的元素 L1.at(0)不可以用at方式访问list容器中的元素 原因list本质链表,不适用连续线性空间存储数据,迭代器也是不支持随机访问的
    it = it++;可以通过,但是it = it + 1是不支持通过的,因为不知道是+1还是加多少

list反转和排序

功能描述:

  • 将容器中的元素反转,以及将容器中的数据进行排序 函数原型:
  • reverse();//反转链表
  • sort();//链表排序 所有不支持随机访问迭代器的容器,不可以用标准算法,所以list排序不能用包含标准算法库的方式
    不支持随机访问迭代器的容器,内部会提供对应一些算法所以直接调用L1.sort()默认排序规则升序,从小到大
    那么如何实现降序排列呢?
    先定义一个函数:
bool myCompare(int v1,int v2)
{
	//降序,就让第一个数>第二个数
	return v1>v2;
}
//然后直接在sort里面传入这个函数
L1.sort(myCompare)

排序案例

案例描述:将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高
排序规则:按照年龄进行升序,如果年龄现同按照身高进行降序
实例:

#include<iostream>
using namespace std;
#include<string>
#include<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;
	}
	return p1.m_Age < p2.m_Age;
}

void test01()
{
	list<Person> L;//创建容器
	//准备数据
	Person p1("刘备", 35, 175);
	Person p2("曹操", 35, 199);
	Person p3("孙权", 80, 180);
	Person p4("赵云", 89, 156);
	Person p5("张飞", 35, 180);
	Person p6("关羽", 17, 230);
	//插入数据
	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<Person>::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<Person>::iterator it = L.begin(); it != L.end(); it++)
	{
		cout << "姓名:" << (*it).m_Name << "年龄:" << it->m_Age << "身高:" << it->m_Height << endl;
	}
}
/*
姓名:刘备年龄:35身高:175
姓名:曹操年龄:35身高:199
姓名:孙权年龄:80身高:180
姓名:赵云年龄:89身高:156
姓名:张飞年龄:35身高:180
姓名:关羽年龄:17身高:230
----------------------
排序后:
姓名:关羽年龄:17身高:230
姓名:曹操年龄:35身高:199
姓名:张飞年龄:35身高:180
姓名:刘备年龄:35身高:175
姓名:孙权年龄:80身高:180
姓名:赵云年龄:89身高:156
*/
int main()
{
	test01();
}

set/multiset容器

set基本概念

简介:

  • 所有元素都会在插入时自动被排序 本质:
  • set/multiset属于关联式容器,底层结构使用二叉树实现 set和multiset区别:
  • set不允许容器中有重复的元素
  • multiset允许容器中有重复的元素

set构造和赋值

功能描述:创建set容器以及赋值
构造:

  • set
  • set(const set& st);//拷贝构造函数 赋值:
  • set& operator=(const set& st);//重载等号操作符 实例:
#include<iostream>
#include<set>
using namespace std;
//set容器构造和赋值
void printSet(set<int>& s)
{
	for (set<int>::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
void test01()
{
	set<int>s1;
	s1.insert(10);//插入数据只有insert方式
	s1.insert(40);
	s1.insert(30);
	s1.insert(20);
	s1.insert(40);
	//遍历容器
	printSet(s1);
	//set容器的特点:所有元素插入的时候自动排序,
	//set容器不允许插入重复的值
	//拷贝构造
	set<int>s2(s1);
	printSet(s2);
}
int main()
{
	test01();
}

set大小和交换

功能描述:

  • 统计set容器大小以及交换set容器 函数原型:
  • size();//返回容器中元素的数目
  • empty();//判断容器是否为空
  • swap(st);//交换两个集合容器//s1.swap(s2)

set插入和删除

功能描述:

  • set容器进行插入数据和删除数据 函数原型:
  • insert(elem);//在容器中插入元素
  • clear();//清楚所有元素
  • erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器
  • erase(beg,end);//删除区间[beg,end)的所有元素,返回下一个元素的迭代器
  • erase(elem);//删除容器中值为elem的元素 实例:

查找和统计

功能描述:

  • 对set容器进行查找数据以及统计数据 函数原型:
  • find(key);//查找key是否存在,若存在,返回该键的元素的迭代器,若不存在,返回set.end();
set<int>::iterator pos = s1.find(30);
if(pos != s1.end())
{
	cout<<"找到元素"<<*pos<<endl;
}
else
{
	cout<<"未找到元素:"<<endl;
}
  • count(key);//统计key的元素个数
//统计30的个数
int num = s1.count(30);
//对于set而言,统计结果,要么是0要么是1
cout<<"num="<<num<<endl;

总结:

  • 查找--find(返回的是迭代器)
  • 统计--count(对于set,结果是0,或者1) 实例:

set和multiset的区别

学习目标:

  • 掌握set和multiset的区别 区别:
  • set不可以插入重复数据,而multiset可以

pair队组创建

功能描述:

  • 成对出现的数据,利用队组可以返回两个数据 两种创建方式:
  • pair<type,type>p(value1,value2);
  • pair<type,type>p = make_pair(value1,value2); 实例:
#include<iostream>
using namespace std;
#include<string>
void test01()
{
	//第一种方式
	pair<string, int>p("Tom", 20);
	cout << "姓名:" << p.first << "年龄:" << p.second << endl;
	//第二种方式
	pair<string, int>p2 = make_pair("Jerry", 20);
	cout << "姓名:" << p2.first << "年龄:" << p.second << endl;
}
int main()
{
	test01();
/*
姓名:Tom年龄:20
姓名:Jerry年龄:20
*/
}

set容器排序

学习目标:

  • set容器默认排序规则从小到大,掌握如何改变规则 主要技术点:
  • 利用仿函数,可以改变规则 实例一 set存放内置数据类型
#include<iostream>
using namespace std;
#include<string>
#include<set>
//set容器排序
//仿函数
class MyCompare
{
public:
	bool operator()( int v1,  int v2)const//()代表重载的符号()
	{
		return v1 > v2;
	}
};
void test01()
{
	set<int>s1;
	s1.insert(10);
	s1.insert(40);
	s1.insert(20);
	s1.insert(30);
	s1.insert(50);
	for (set<int>::iterator it = s1.begin(); it != s1.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	//指定排序规则从大到小
	set<int,MyCompare>s2;
	s2.insert(10);
	s2.insert(40);
	s2.insert(20);
	s2.insert(30);
	s2.insert(50);
	for (set<int,MyCompare>::iterator it = s2.begin(); it != s2.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
/*
10 20 30 40 50
50 40 30 20 10
*/
int main()
{
	test01();
}

实例二:set存放自定义数据类型

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

//set容器排序,存放自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	string m_Name;
	int m_Age;
};
class myCompare
{
public:
	bool operator()(const Person& p1, const Person& p2)const
	{
		//按照年龄降序
		return p1.m_Age > p2.m_Age;
	}
};
void test01()
{
	set<Person,myCompare>s;
	//创建Person对象
	Person p1("刘备", 24);
	Person p2("关羽", 23);
	Person p3("张飞", 22);
	Person p4("赵云", 21);
	s.insert(p1);
	s.insert(p2);
	s.insert(p3);
	s.insert(p4);
	for (set<Person,myCompare>::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << "姓名:" << it->m_Name << "年龄:" << it->m_Age << endl;
	}
}
int main()
{
	test01();
}
/*
姓名:刘备年龄:24
姓名:关羽年龄:23
姓名:张飞年龄:22
姓名:赵云年龄:21
*/

map/multimap容器

map基本概念

简介:

  • map中所有元素都是pair
  • pair中第一个元素为key(键值)起到索引作用,第二个元素为value(实值)
  • 所有元素会根据元素的键值自动排序 本质:
  • map/multimap属于关联容器,底层使用二叉树实现 优点:
  • 可以根据key值快速找到value值 map和multimap区别:
  • map不允许容器中有重复的key值元素
  • multimap允许容器中有重复key值元素

map构造和赋值

功能描述:

  • 对map容器进行构造和赋值操作 函数原型:
    构造:
  • map<T1,T2>mp;//map默认构造函数
  • map(const map& mp);//拷贝构造函数 赋值:
  • map& operator=(const map& mp);//重载等号操作符 实例:
#include<iostream>
#include<map>
using namespace std;
//map容器构造和赋值
void printMap(map<int, int>& m)
{
	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "key=" << (*it).first << "value="<<it->second << endl;
	}
	cout << endl;
}
void test01()
{
	map<int, int>m;
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));
	m.insert(pair<int, int>(4, 40));
	printMap(m);
	//拷贝构造
	map<int, int>m2(m);
	printMap(m2);
	//赋值
	map<int, int>m3;
	m3 = m2;
	printMap(m3);
}
int main()
{
	test01();
	/*
key=1value=10
key=2value=20
key=3value=30
key=4value=40
*/
}

map大小和交换

功能描述:

  • 统计map容器大小以及交换map容器 函数原型:
  • size();//返回容器中元素的数目
  • empty();//判断容器是否为空
  • swap(st);//交换两个集合容器 实例:
#include<iostream>
using namespace std;
#include<map>
void printMap(const map<int, int>& m)
{
	for (map<int, int>::const_iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "键:" << it->first << "值:" << it->second << endl;
	}
	cout << endl;
}
void test01()
{
	map<int, int>m;
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 30));
	m.insert(pair<int, int>(3, 20));
	if (m.empty())
	{
		cout << "m为空" << endl;
	}
	else
	{
		cout << "m不为空" << endl;
		cout << "m的大小为:" << m.size() << endl;
	}
	map<int, int>m2;
	m2.insert(pair<int, int>(4, 100));
	m2.insert(pair<int, int>(5, 200));
	m2.insert(pair<int, int>(6, 300));
	//交换前
	//cout << "m:" << endl;
	printMap(m);
	printMap(m2);
	//交换后
	m.swap(m2);
	printMap(m);
	printMap(m2);
}
int main()
{
	test01();
}
/*
m不为空
m的大小为:3
键:1值:10
键:2值:30
键:3值:20

键:4值:100
键:5值:200
键:6值:300

键:4值:100
键:5值:200
键:6值:300

键:1值:10
键:2值:30
键:3值:20
*/

map插入和删除

功能描述:

  • map容器进行插入数据和删除数据 函数原型:
  • insert(elem);//在容器中插入元素
  • clear();//清楚所有元素
  • erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器
  • erase(beg,end);//删除区间[beg,end)的所有元素,返回下一个元素的迭代器
  • erase(key);//删除容器中值为key的元素 插入实例:
//第一种
m.insert(pair<int,int>(1,10));
//第二种
m.insert(make_pair(2,20));
//第三种
m.insert(map<int,int>::value_type(3,30));
//第四种
m[4] = 40;//不太建议用第四种插入,但是可以用key访问到value
//原因:比如前面直插入了4个键值对,这时候输出m[5]他会创建m[5]=0

删除实例:

m.erase(m.begin());
printMap(m);
m.erase(3);//按照key删除
m.erase(m.begin(),m.end());
m.clear();

map查找和统计

功能描述:

  • 对map容器进行查找数据以及统计数据 函数原型:
  • find(key);//查找key是否存在,若存在,返回该键的元素的迭代器,若不存在,返回set.end();
  • count(key);//统计key的元素个数(对于map,结果要么是1,要么是0) 实例:
map<int,int>m;
m.insert(make_pair(1,10));
m.insert(make_pair(2,20));
m.insert(make_pair(3,30));
m.insert(make_pair(4,40));
map<int,int>::iterator pos = m.find(3);
if (pos != m.end())
{
	cout<<"查到了key="<<(*pos).first<<"value="<<pos->secend<<endl;
}
else
{
	cout<<"未找到元素"<<endl;
}

map容器排序

学习目标:

  • map容器默认排序规则为按照key值进行从小到大排序,掌握如何改变规则 主要技术点:
  • 利用仿函数,可以改变排序规则 实例:
class myCompare
{
	public:
	bool operator()(int v1,int v2)
	{
		//降序
		return v1>v2;
	}
}
void test01()
{
	map<int,int,myCompare>m;
	m.insert(make_pair(1,10));
	m.insert(make_pair(2,20));
	m.insert(make_pair(3,30));
	for(map<int,int>::iterator it = m.begin();it != m.end();it++)
	{
		cout<<"key="<<it->first<<"value="<<it->second<<endl;
	}
}

总结:

  • 利用仿函数可以指定map容器的排序规则
  • 对于自定义数据类型,map必须指定排序规则,同set容器

案例-员工分组

案例描述:

  • 公司今天招了10个员工(ABCDEFGHIJ),10名员工进入公司之后,需要指派员工在哪个部门工作
  • 员工信息有:姓名 工资组成;部门分为:策划、美术、研发
  • 随机给10名员工分配部门和工资
  • 通过multimap进行信息的插入key(部门编号)value(员工)
  • 分部门显示员工信息

实现步骤

  1. 创建10名员工,放到vector中
  2. 遍历vector容器,取出每个员工,进行随机分组
  3. 分组后,将员工部门编号作为key,具体员工作为value,放入到multimap
  4. 分部门显示员工信息 案例代码:
#include<iostream>
#include<map>
#include<vector>
#define CEHUA 0
#define YANFA 2
#define MEISHU 1
#include<ctime>
using namespace std;
class Worker
{
public:
	string m_Name;
	int m_Scalary;
};
void createWorker(vector<Worker>& v)
{
	string nameSeed = "ABCDEFGHIJ";
	for (int i = 0; i < 10; i++)
	{
		Worker worker;
		worker.m_Name = "员工";
		worker.m_Name += nameSeed[i];
		worker.m_Scalary = rand() % 10000 + 10000;//10000~19999
		//将员工放入到容器中
		v.push_back(worker);
	}
}
void setGroup(vector<Worker>& v,multimap<int,Worker>& m)
{
	for (vector<Worker>::iterator it = v.begin(); it != v.end(); it++)
	{
		//产生随机部门编号
		int deptId = rand() % 3;//0 1 2
		m.insert(make_pair(deptId, *it));
	}
}
//分组显示员工
void showWorkerByGourp(multimap<int, Worker>& m)
{
	cout << "策划部门的信息:" << endl;
	multimap<int,Worker>::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_Scalary << 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_Scalary << 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_Scalary << endl;
	}
}
int main()
{
	srand((unsigned int)time(NULL));
	vector<Worker>vWorker;
	createWorker(vWorker);
	////测试
	//for (vector<Worker>::iterator it = vWorker.begin(); it != vWorker.end(); it++)
	//{
	//	cout << (*it).m_Name << " " << it->m_Scalary << endl;
	//}
	//分组
	multimap<int, Worker>mWorker;
	setGroup(vWorker, mWorker);
	showWorkerByGourp(mWorker);
}
/*
策划部门的信息:
姓名:员工D工资:16500
姓名:员工I工资:16962
姓名:员工J工资:14464
-------------------
美术部门信息:
姓名:员工C工资:16334
姓名:员工E工资:19169
姓名:员工G工资:11478
-------------------
研发部门信息:
姓名:员工A工资:10041
姓名:员工B工资:18467
姓名:员工F工资:15724
姓名:员工H工资:19358
*/

STL-函数对象(仿函数)

函数对象

函数对象概念

概念:

  • 重载函数调用操作符的类,其对象常称为函数对象
  • 函数对象使用重载的()时,行为类似函数调用,也叫仿函数 本质:
    函数对象(仿函数)是一个类,不是一个函数

函数对象使用

特点:

  • 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
  • 函数对象超出普通函数的概念,函数对象可以有自己的状态
  • 函数对象可以作为参数传递
#include<iostream>
using namespace std;
class MyAdd
{
public:
	int operator()(int v1, int v2)
	{
		return v1 + v2;
	}
};
//函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
void test01()
{
	MyAdd myAdd;
	cout << myAdd(10, 10) << endl;;
}
//函数对象超出普通函数的概念,函数对象可以有自己的状态
class MyPrint
{
public:
	MyPrint()
	{
		this->count = 0;
	}
	void operator()(string test)
	{
		cout << test << endl;
		this->count += 1;
	}
	int count;//内部自己状态
};
void test02()
{
	MyPrint myPrint;
	myPrint("hello print");
	myPrint("hello print");
	myPrint("hello print");
	cout << "myPrint调用次数" << myPrint.count << endl;
}
//函数对象可以作为参数传递
void doPrint(MyPrint& mp, string test)
{
	mp(test);
}
void test03()
{
	MyPrint myPrint;
	doPrint(myPrint, "hello c++");
}
int main()
{
	//test01();
	//test02();
	test03();
}

谓词

谓词的概念

概念:

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

一元谓词

实例:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//仿函数,返回值类型是bool数据类型,称为谓词
//一元谓词
class GreaterFive
{
public:
	bool operator()(int val)
	{
		return val > 5;
	}
};
void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}
	//查找容器中有没有大于5的数字
	vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());//GreaterFive匿名函数对象
	if (it == v.end())
	{
		cout << "未找到" << endl;
	}
	else
	{
		cout << "找到了大于五的数字" << *it << endl;
	}
}
int main()
{
	test01();
}

二元谓词的案例:

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
//二元谓词
class MyCompare
{
public:
	bool operator()(int val1, int val2)
	{
		return val1 > val2;
	}
};
void test01()
{
	vector<int>v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);
	sort(v.begin(), v.end());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	//使用函数对象,改变算法策略,规则为从大到小
	sort(v.begin(), v.end(), MyCompare());
	cout << "------------------------" << endl;
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
}
int main()
{
	test01();
}
/*
10 20 30 40 50
--------------
50 40 30 20 10
*/

内建的函数对象

内建的函数对象意义

概念:

  • stl内建了一些函数对象 分类:
  • 算数仿函数
  • 关系仿函数
  • 逻辑仿函数 用法:
  • 这些仿函数所产生的对象,用法和一般函数完全相同
  • 使用内建函数对象,需要引入头文件#include
#include<iostream>
using namespace std;
#include<functional>
void test01()
{
	negate<int>n;
	cout << n(50) << endl;
}
void test02()
{
	plus<int>p;
	cout << p(10, 20) << endl;
}
int main()
{
	test02();
}

总结:使用内建函数对象时,需要引入头文件#include

关系仿函数

功能描述:

  • 实现关系对比 仿函数原型:
  • template
  • template
  • template
  • template
  • template
  • template
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<functional>
//内建函数对象_关系仿函数
//大于greater
class MyCompare
{
public:
	bool operator()(int v1, int v2)
	{
		return v1 > v2;
	}
};
void test01()
{
	vector<int>v;
	v.push_back(10);
	v.push_back(30);
	v.push_back(40);
	v.push_back(20);
	v.push_back(50);
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	sort(v.begin(), v.end(), MyCompare());
	//降序
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	//或者也可以使用内建的函数对象,greater<>()用的较多,sort内部默认用的是less<>()
	sort(v.begin(), v.end(), greater<int>());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
int main()
{
	test01();
}
/*
10 30 40 20 50
50 40 30 20 10
50 40 30 20 10
*/

逻辑仿函数

功能描述:

  • 实现逻辑运算 函数原型:
  • template
  • template
  • template
#include<iostream>
#include<algorithm>
#include<vector>
#include<functional>
using namespace std;
//内建函数对象,逻辑仿函数
//逻辑非 logical_not
void test01()
{
	vector<bool>v;
	v.push_back(true);
	v.push_back(false);
	v.push_back(true);
	v.push_back(false);
	v.push_back(true);
	for (vector<bool>::iterator it = v.begin(); it != v.end(); it++ )
	{
		cout << *it << " ";
	}
	cout << endl;
	//利用逻辑非将容器v搬运到容器v2中,并执行取反操作
	vector<bool>v2;
	v2.resize(v.size());//必须先指定大小才能顺利搬运
	transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());
	for (vector<bool>::iterator it = v2.begin(); it != v2.end(); it++ )
	{
		cout << *it << " ";
	}
	cout << endl;
}
int main()
{
	test01();
}
/*
1 0 1 0 1
0 1 0 1 0
*/

STL-常用算法

概述:

  • 算法主要是由头文件

常用遍历算法

学习目标:

  • 掌握常用地遍历算法 算法简介:
  • for_each//遍历容器,for_each是最常用的遍历算法
  • transform/搬运容器到另一个容器中

for_each

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//常用遍历算法,for_each
//普通函数
void print01(int val)
{
	cout << val << " ";
}
//仿函数
class print02
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};
void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}
	for_each(v.begin(), v.end(), print01);
	cout << endl;
	for_each(v.begin(), v.end(), print02());//仿函数
}
int main()
{
	test01();
}
/*
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
*/

transform

功能描述: 搬运容器到另一个容器中
函数原型:

  • transform(iterator beg1,iterator end1,iterator beg2,_func); //beg1源容器开始迭代器 //end1源容器结束迭代器 //beg2目标容器开始迭代器 //_func函数或函数对象 实例:
#include<iostream>
using namespace std;
#include<algorithm>
#include<functional>
#include<vector>
//常用遍历算法
class Print01
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};
class Transform
{
public:
	int operator()(int val)
	{
		return val+1000;
	}
};
void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}
	vector<int>vTarget;//目标容器
	//搬运的时候必须先给目标容器开辟空间
	vTarget.resize(v.size());
	transform(v.begin(), v.end(), vTarget.begin(), Transform());
	for_each(vTarget.begin(), vTarget.end(), Print01());
}
int main()
{
	test01();
}
/*1000 1001 1002 1003 1004 1005 1006 1007 1008 1009*/

常用的查找算法

学习目标:

  • 掌握常用的查找元素 算法简介:
  • find //查找元素
  • find_if//按条件查找元素
  • adjacent_find//查找相邻重复元素
  • binary_search//二分查找法
  • count//统计元素个数
  • count_if//按条件统计元素个数

find

功能描述:

  • 查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end() 函数原型:
  • find(iterator beg,iterator end,value); //按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置 //beg开始迭代器 //end结束迭代器 //value查找地元素 实例:
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
//常用查找算法
//查找内置数据类型
void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}
	//查找是否由5这个元素
	vector<int>::iterator it = find(v.begin(), v.end(), 5);
	if (it == v.end())
	{
		cout << "没有找到:" << endl;
	}
	else
	{
		cout << "找到了:" << *it << endl;
	}
}
//查找自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	//重载==让find知道如何对比
	bool operator==(const Person& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age )
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	string m_Name;
	int m_Age;
};
void test02()
{
	vector<Person>v;
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	Person pp("bbb", 20);
	vector<Person>::iterator it = find(v.begin(), v.end(), pp);
	if (it == v.end())
	{
		cout << "没找到" << endl;
	}
	else
	{
		cout << "找到了" << endl;
		cout << "姓名:" << it->m_Name << "年龄:" << it->m_Age << endl;
	}
}
int main()
{
	//test01();
	test02();
}
/*
找到了
姓名:bbb年龄:20
*/

总结:利用find可以在容器中查找指定地元素,返回值是迭代器

find_if

功能描述:

  • 按条件查找元素 函数原型:
  • find_if(iterator beg,iterator end,_pred); //按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
    //beg开始迭代器
    //end结束迭代器
    //_Pred函数或者谓词(返回bool类型的仿函数)
    实例:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	string m_Name;
	int m_Age;
};
class GreaterFive
{
public:
	bool operator()(int val)
	{
		return val > 5;
	}
};
void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}
	vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
	if (it == v.end())
	{
		cout << "没有找到:" << endl;
	}
	else
	{
		cout << "找到了:" <<*it<< endl;
	}
}
class Greater20
{
public:
	bool operator()(Person& p)
	{
		return p.m_Age > 20;
	}
};
void test02()
{
	vector<Person>v;
	//创建数据
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	//找年龄大于20的人
	vector<Person>::iterator it = find_if(v.begin(), v.end(), Greater20());
	if (it == v.end())
	{
		cout << "未找到" << endl;
	}
	else
	{
		cout << "找到了年龄大于20的人,他的姓名是:" << it->m_Name << "他的年龄是:" << it->m_Age;
	}
}
int main()
{
	//test01();
	test02();
}
//找到了:6
//找到了年龄大于20的人,他的姓名是:ccc他的年龄是:30

adjacent_find

功能描述:

  • 查找相邻重复元素 函数原型:
  • adjacent_find(iterator beg,itetator end); //查找相邻重复元素,返回相邻元素的第一个位置的迭代器
    //beg开始迭代器
    //end结束迭代器
    实例:
vector<int>v;
v.push_back(0);
v.push_back(1);
v.push_back(1);
v.push_back(2);
vector<int>::iterator pos = adjacent_find(v.begin(),v.end());
if(pos == v.end())
{
	cout<<"未找到相邻重复元素"<<endl;
}
else
{
	cout<<"找到了相邻重复元素"<<*pos<<endl;
}

binary_search二分查找

功能描述:

  • 查找指定元素是否存在 函数原型:
  • bool binary_search(iterator beg,iterator end,value); //查找指定的元素,查到返回true,否则返回false
    //注意:二分查找速度很快,但是在无序序列中不可用,必须是有序且升序的序列
    //beg 开始迭代器
    //end 结束迭代器
    //value查找的元素
    实例:
vector<int> v;
for(int i = 0;i<10;i++)
{
	v.push_back(i);
}
//查找是否有9元素
bool ret = binary_search(v.begin(),v.end(),9);

if(ret)
{
	cout<<"找到了元素"<<endl;
}
else
{
	cout<<"未找到"<<endl;
}

count

功能描述:

  • 统计元素个数 函数原型:
  • count(iterator beg,iterator end,value)
  • 统计元素出现个数
  • beg开始迭代器
  • end结束迭代器
  • value统计的元素 实例:
#include<iostream>
using namespace std;
#include<algorithm>
#include<vector>
void test01()
{
	vector<int>v;
	v.push_back(10);
	v.push_back(40);
	v.push_back(30);
	v.push_back(30);
	v.push_back(30);
	v.push_back(30);
	v.push_back(30);
	int num = count(v.begin(), v.end(), 40);
	cout << "40的元素个数为:" << num << endl;
}
//统计自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	bool operator==(const Person& p)
	{
		return this->m_Age == p.m_Age;
	}
	string m_Name;
	int m_Age;

};
void test02()
{
	vector<Person>v;
	Person p1("刘备", 34);
	Person p2("张飞", 334);
	Person p3("关羽", 324);
	Person p4("赵云", 35);
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	Person p("诸葛亮", 35);
	int num = count(v.begin(), v.end(),p);
	cout << "和诸葛亮同岁数的人员个数为:" << num << endl;
}
int main()
{
	test02();
}
//和诸葛亮同岁数的人员个数为:1

count_if

功能描述:

  • 按条件统计元素个数 函数原型:
  • count_if(iterator beg,iterator end,_pred);
  • 按条件统计元素出现次数
  • beg开始迭代器
  • end结束迭代器
  • _Pred谓词 实例:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

//常用查找算法
//统计自定义数据类型
class Greater20
{
public:
	bool operator()(int num)
	{
		return num > 20;
	}
};
void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i * 10);
	}
	int num = count_if(v.begin(), v.end(),Greater20());
	cout << "大于20的有:" << num << "个" << endl;
}
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	string m_Name;
	int m_Age;
};
class AgeGreater40
{
public:
	bool operator()(Person& p)
	{
		return p.m_Age > 40;
	}
};
void test02()
{
	vector<Person> v;
	Person p1("刘备", 34);
	Person p2("张飞", 334);
	Person p3("关羽", 324);
	Person p4("赵云", 35);
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	int num = count_if(v.begin(), v.end(), AgeGreater40());
	cout << "年龄大于40的有" << num << "个" << endl;
}
int main()
{
	//test01();
	test02();
}
//大于20的有:7个
//年龄大于40的有2个

常用排序方法

学习目标:

  • 掌握常用的排序算法 算法简介:
  • sort//对容器中元素进行排序
  • random_shuffle//洗牌,指定范围内的元素随即调整次序
  • merge//容器元素合并,并存储到另一容器
  • reverse//反转指定范围内的元素

sort

功能描述:

  • 对容器内的元素进行排序 函数原型:
  • sort(iterator beg,iterator end,_pred); //按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
    //beg开始迭代器
    实例:
#include<iostream>
using namespace std;
#include<algorithm>
#include<vector>
//常用排序算法
void myPrint(int val)
{
	cout << val<< " ";
}
void test01()
{
	vector<int>v;
	v.push_back(10);
	v.push_back(30);
	v.push_back(22);
	v.push_back(28);
	v.push_back(40);
	//利用sort进行升序
	sort(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;
	//改变降序
	sort(v.begin(), v.end(), greater<int>());
	for_each(v.begin(), v.end(), myPrint);
}
int main()
{
	test01();
	system("pause");
}
/*
10 22 28 30 40
40 30 28 22 10
*/

random_shuffle

功能描述:

  • 洗牌,指定范围内的元素随即调整次序 函数原型:
  • random_shuffle(iterator beg,iterator end);
  • 指定范围内的元素随机调整次序
  • beg开始迭代器
  • end结束迭代器
#include<iostream>
#include<time>
#include<algorithm>
using namespace std;
#include<vector>
void myPrint(int v)
{
	cout << v << " ";
}
//常用排序算法
void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}
	//利用洗牌算法打乱顺序
	random_shuffle(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;
}
int main()
{
	srand(ussigned int)time(NULL);
	test01();
}
//8 1 9 2 0 5 7 3 4 6

总结:random_shuffle洗牌算法比较实用,但是使用的时候记得加上随机数种子

merge

功能描述:

  • 两个容器元素合并,存储到另一个容器中 函数原型:
  • merge(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest); //容器元素合并,并存储到另一个容器中
    //注意:两个容器必须是有序的
    //beg1容器1开始迭代器
    //end1容器1结束迭代器
    //beg2容器2开始迭代器
    //end2容器2结束迭代器
    //dest目标容器开始迭代器
    实例
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//常用排序算法merge
void myPrint(int val)
{
	cout << val << " ";
}
void test01()
{
	vector<int>v1;
	vector<int>v2;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
		v2.push_back(i + 1);
	}
	//目标容器
	vector<int>vTarget;//目标容器是空的,需要提前分配内存
	vTarget.resize(v1.size()+v2.size());
	merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
	for_each(vTarget.begin(), vTarget.end(), myPrint);
	cout << endl;
}
int main()
{
	test01();
}
//0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10

总结:merge合并的两个容器必须是有序的序列

reverse

功能描述:

  • 反转容器中的元素 函数原型:
  • reverse(iterator beg,iterator end);
  • 反转指定范围内的元素
  • beg开始迭代器
  • end结束迭代器 实例:

常用的拷贝和替换算法

学习目标:

  • 掌握常用的拷贝和替换算法 算法简介:
  • copy//容器内指定范围的元素拷贝到另一容器中
  • replace//将容器内指定范围的旧元素修改为新元素
  • replace_if//容器内指定范围满足条件的元素替换为新元素
  • swap//互换两个容器的元素

copy

功能描述:

  • 容器内指定范围的元素拷贝到另一容器中 函数原型:
  • copy(iterator beg,iterator end,iterator dest);
  • 按值查找元素,找到返回指定位置迭代器,找不到返回结束位置迭代器
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void myPrint(int val)
{
	cout << val << " ";
}
void test01()
{
	vector<int>v1;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}
	vector<int>v2;
	v2.resize(v1.size() - 2);
	copy(v1.begin(), v1.end() - 2, v2.begin());
	for_each(v2.begin(), v2.end(), myPrint);
}
int main()
{
	test01();
}
//0 1 2 3 4 5 6 7

replace

功能描述:

  • 将容器内指定范围内的旧元素修改为新元素 函数原型:
  • replace(iterator beg,iterator end,oldvalue,newvalue);
  • 将区间内旧元素替换为新元素
  • beg开始迭代器
  • end结束迭代器
  • oldvalue旧元素
  • newvalue新元素 实例:

replace_if

功能描述:

  • 将区间内满足条件的元素,替换为指定元素 函数原型:
  • replace_if(iterator beg,iterator end,_pred,newvalue);
  • 按条件替换元素,满足条件替换成指定元素
  • beg开始迭代器
  • end结束迭代器
  • _pred谓词
  • newvalue替换的新元素 实例:
#include<iostream>
#include<vector>
#include<functional>
#include<algorithm>
using namespace std;
class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};
class Greater30
{
public:
	bool operator()(int val)
	{
		return val >= 30;
	}
};
void test01()
{
	vector<int>v1;
	v1.push_back(10);
	v1.push_back(30);
	v1.push_back(60);
	v1.push_back(85);
	v1.push_back(3);
	v1.push_back(8);
	for_each(v1.begin(), v1.end(), myPrint());
	replace_if(v1.begin(), v1.end(), Greater30(),3000);
	cout << endl;
	for_each(v1.begin(), v1.end(), myPrint());
}
int main()
{
	test01();
}
/*
10 30 60 85 3 8
10 3000 3000 3000 3 8
*/

swap

功能描述:

  • 互换两个容器的元素 函数原型:
  • swap(container c1,container c2); //互换两个容器的元素
    //c1容器1
    //c2容器2
    实例:

常用的算术生成算法

学习目标:

  • 掌握常用的算术生成算法 注意:
  • 算术生成算法属于小型算法,使用时包含头文件#include
  • accmulate//计算容器元素累计总和
  • fill//向容器中添加元素

accumulate

功能描述:

  • 计算区间内容器元素累计总和 函数原型:
  • accumulate(iterator beg,iterator end,value);
  • 计算容器元素累计总和
  • beg开始迭代器
  • end结束迭代器
  • value起始值,其实累加值 总结:accumulate使用时头文件注意是numeric这个算法很实用

fill

功能描述:

  • 向容器中填充指定的元素 函数原型:
  • fill(iterator beg,iterator end,value);
  • 向容器中填充元素
  • beg开始迭代器
  • end结束迭代器
  • value填充的值

常用集合算法

学习目标:

  • 掌握常用的集合算法 算法简介:
  • set_intersection//求两个容器的交集
  • set_union//求两个容器的并集
  • set_difference//求两个容器的差集

set_intersection

功能描述:

  • 求两个容器的交集 函数原型:
  • set_intersection(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
  • 求两个集合的交集
  • 注意:两个集合必须是有序序列
  • beg1容器1开始迭代器
  • end1容器1结束迭代器
  • beg2容器2开始迭代器
  • end2容器2结束迭代器
  • dest目标容器开始迭代器
  • 返回的是交集最后一个元素的下一个迭代器 实例:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//常用集合算法,set_intersection
class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};
void test01()
{
	vector<int>v1;
	vector<int>v2;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
		v2.push_back(i + 5);
	}
	vector<int>vTarget;
	//目标容器需要提前开辟空间
	vTarget.resize(v1.size() > v2.size() ? v2.size() : v1.size());
	cout << "v1" << endl;
	for_each(v1.begin(), v1.end(), myPrint());
	cout << endl;
	cout << "v2" << endl;
	for_each(v1.begin(), v1.end(), myPrint());
	cout << endl;
	cout << "vTarget" << endl;
	vector<int>::iterator itEnd = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
	for_each(vTarget.begin(), vTarget.end(), myPrint());//要注意这样是不行的最后输出结果5 6 7 8 9 0 0 0 0 0
	//应该要这么写
	cout << endl;
	for_each(vTarget.begin(), itEnd, myPrint());
}
int main()
{
	test01();
}
/*
v1
0 1 2 3 4 5 6 7 8 9
v2
0 1 2 3 4 5 6 7 8 9
vTarget
5 6 7 8 9 0 0 0 0 0
5 6 7 8 9
*/

总结:求交集的俩个集合必须是有序序列
目标容器开辟空间需要从两个容器中取小值
set_intersection返回值即是交集中最后一个元素的位置

set_union

功能描述:

  • 求两个集合的并集 函数原型:
  • set_union(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
  • 求两个集合的并集
  • 注意:两个集合必须是有序序列
  • beg1容器1开始迭代器
  • end1容器1结束迭代器
  • beg2容器2开始迭代器
  • end2容器2结束迭代器
  • dest目标容器开始迭代器 实例:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};
void test01()
{
	vector<int>v1;
	vector<int>v2;
	for (int i = 0; i < 4; i++)
	{
		v1.push_back(i);
	}
	for (int i = 0; i < 7; i++)
	{
		v2.push_back(i + 3);
	}
	cout << "v1" << endl;
	for_each(v1.begin(), v1.end(), myPrint());
	cout << endl;
	cout << "v2" << endl;
	for_each(v2.begin(), v2.end(), myPrint());
	cout << endl;
	vector<int>vTarget;
	vTarget.resize(v1.size() + v2.size());
	vector<int>::iterator itEnd = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
	cout << "V1+V2" << endl;
	for_each(vTarget.begin(), itEnd, myPrint());
}
int main()
{
	test01();
}
/*
v1
0 1 2 3
v2
3 4 5 6 7 8 9
V1+V2
0 1 2 3 4 5 6 7 8 9
*/

总结:

  • 求并集的两个集合必须是有序序列
  • 目标容器开辟空间需要两个容器相加
  • set_union返回值即使并集中最后一个元素的位置

set_difference

功能描述:

  • 求两个集合的差集 函数原型:
  • set_difference(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest)
  • 求两个集合的差集
  • 注意:两个集合必须是有序序列
  • beg1容器1开始迭代器
  • end1容器
  • beg1容器2开始迭代器
  • end2容器2结束迭代器
  • dist目标容器开始迭代器
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};
void test01()
{
	vector<int>v1;
	vector<int>v2;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}
	for (int i = 0; i < 8; i++)
	{
		v2.push_back(i + 2);
	}
	vector<int>vTarget;
	vTarget.resize(max(v1.size(),v2.size()));
	vector<int>::iterator itEnd = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
	for_each(vTarget.begin(), itEnd, myPrint());

}
int main()
{
	test01();
}
//0 1

总结:

  • 求差集的两个集合必须是有序序列
  • 目标容器开辟空间需要从两个容器取较大值

演讲比赛流程管理系统

演讲比赛程序需求

比赛规则

  • 学校举行一场演讲比赛,共有12个人参加,比赛共两轮,第一轮为淘汰赛,第二轮为决赛

本文章使用limfx的vscode插件快速发布