C++编写软件(推荐):Visual Studio

Visual Studio安装:

1 C++初识

1.1 创建新项目

选择Visual C++下的空项目,然后给项目取一个名;右键源文件,添加->新建项,在弹出的页面中选择C++文件(.cpp),给它取一个名称.

1.2 注释

作用:方便程序员阅读

俩种格式:
1.单行注释: //描述信息

  • 通常放在一行代码的上方,或者一条语句的末尾,对该行代码说明
    2.多行注释: /* 描述信息 */
  • 通常放在一段代码的上方,对该段代码做整体说明

1.3 变量

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

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

1
int a = 10;

1.4 常量

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

C++定义常量的俩种方式:
1.#define宏常量: #define 常量名 常量值

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

const int month = 12;

1.5 关键字

作用: 定义变量或者常量时,不能使用关键字

C++关键字如下:

asmdoifreturntypedef
autodoubleinlineshorttypeid
booldynamic_castintsignedtypename
breakelselongsizeofunion
caseenummutablestaticunsigned
catchexplicitnamespacestatic_castusing
charexportnewstructvirtual
classexternoperatorswitchvoid
constfalseprivatetemplatevolatile
const_castfloatprotectedthiswchar_t
continueforpublicthrowwhile
defaultfriendregistertrue
deletegotoreinterpret_casttry

1.6 标识符命名规则

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

标识符争取做到见名知意的效果

2 数据类型

2.1 整型

数据类型占用空间取值范围
short(短整型)2字节-2^15~2^15-1
int(整型)4字节-2^31~2^31-1
long(长整型)Window为4字节,Linux为4字节(32位),8字节(64位)-2^31~2^31-1
long long(长长整型)8字节-2^63~2^63-1

2.2 sizeof关键字

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

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

1
2
3
4
cout << "short占用内存空间为:" << sizeof(short) << endl;
cout << "int占用内存空间为:" << sizeof(int) << endl;
cout << "long占用内存空间为:" << sizeof(long) << endl;
cout << "long long占用内存空间为:" << sizeof(long long) << endl;

大小比较:

short < int <= long <= long long

2.3 实型(浮点型)

作用: 用于表示小数

浮点型变量分为俩种:
1.单精度float
2.双精度double

数据类型占用空间有效数字范围
float4字节7位有效数字
double8字节15~16位有效数字
1
2
3
4
5
float f1 = 3.14f;
double d1 = 3.14;
//科学计数法
float f2 = 3e2;//3*10^2
float f3 = 3e-2;//3*0.1^2

默认情况下输出一个小数会显示出6位有效数字

2.4 字符型

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

语法: char ch = 'a';

注意1: 在显示字符型变量时,用单引号将字符括起来,不要用双引号

注意2: 单引号内只能有一个字符,不可能是字符串

  • C和C++中字符型变量只占用1个字节
  • 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码(int(ch))放入到存储单元

a - 97
A - 65

2.5 转义字符

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

转义字符含义ASCII码值(十进制)
\a警报007
\b退格(BS),将当前位置移到前一列008
\f换页(FF),将当前位置移到下页开头012
\n换行(LF),将当前位置移到下一行开头010
\r回车(CR),将当前位置移到本行开头013
\t水平制表(HT) (跳到下一个TAB位置)009
\v垂直制表(VT)011
\代表一个反斜线字符""092
代表一个单引号(撇号)字符039
"代表一个双引号字符034
\?代表一个问号063
\0数字0000
\ddd8进制转义字符,d范围0~73位8进制
\xhh16进制转义字符,h范围0~9,A~F3位16进制

2.6 字符串型

作用: 用于表示一串字符

俩种风格:
1.C风格字符串: char 变量名[] = "字符串值";

1
2
   //C风格字符串 等号后面要用双引号
char str1[] = "hello world";

2.C++风格字符串: string 变量名 = "字符串值";

1
2
   //C++风格字符串 需包含头文件:#include<string>
string str2 = "hello world";

2.7 布尔类型bool

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

bool类型只有俩个值:

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

bool类型占1个字节大小

1
bool flag = true;

2.8 数据的输入

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

关键字: cin

语法: cin >> 变量

bool类型只要是非0的值都代表真

3 运算符

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

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

3.1 算术运算符

作用: 用于处理四则运算

运算符术语示例结果
+正号+33
-负号-3-3
+1+23
-2-11
*1*22
/2/12
%取模(取余)10%31
++前置递增a=2;b=++a;a=3;b=3;
++后置递增a=2;b=a++;a=3;b=2;
前置递减a=2;b=–a;a=1;b=1;
后置递减a=2;b=a–;a=1;b=2;

俩个整数相除结果依然是整数,将小数部分去除

前置递增 先让变量+1,然后进行表达式计算;后置递增 先进行表达式计算,然后让变量+1(递减同理)

3.2 赋值运算符

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

运算符术语示例结果
=赋值a=2;b=3;a=2;b=3;
+=加等于a=0;a+=2;a=2;
-=减等于a=5;a-=3;a=2;
*=乘等于a=2;a*=2;a=4;
/=除等于a=4;a/=2;a=2;
%=模等于a=3;a%=2;a=1;

3.3 比较运算符

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

运算符术语示例结果
==相等于4==30
!=不等于4!=31
<小于4<30
>大于4>31
<=小于等于4<=30
>=大于等于4>=31

3.4 逻辑运算符

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

运算符术语示例结果
!!a如果a为假,则!a为真;如果a为真,则!a为假。
&&a&&b如果a和b都为真,则结果为真,否则为假。
||a||b如果a和b有一个为真,则结果为真,二者都为假时,结果为假。

在C++中,除0外都为真

4 程序流程结构

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

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

4.1 选择结构

4.1.1 if语句

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

if语句的三种形式

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

1.单行格式if语句: if(条件){条件满足执行的语句}

2.多行格式if语句: if(条件){条件满足执行的语句}else{条件不满足执行的语句}

3.多条件的if语句: if(条件1){条件1满足执行的语句}else if(条件2){条件2满足执行的语句}... else{条件都不满足执行的语句}

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
using namespace std;
int main()
{
int score = 0;
cout << "请输入你的分数:" << endl;
cin >> score;
cout << "你的分数为:" << score << endl;
if (score > 600)// 注意:if 条件后面不能加分号
{
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;
}else
{
cout << "未考上本科大学" << endl;
}
system("pause");
return 0;
}

4.1.2 三目运算符

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

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

解释:

  • 如果表达式1的值为真,执行表达式2,并返回表达式2的结果;
  • 如果表达式1的值为假,执行表达式3,并返回表达式3的结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
int main()
{
//三目运算符
int a = 10;
int b = 20;
int c = 0;
c = (a > b ? a : b);
//C++中三目运算符返回的是变量可以继续赋值 (a < b ? a : b)=100; ==>a=100;b=20
system("pause");
return 0;
}

4.1.3 switch语句

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

语法:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
using namespace std;
int main()
{
int score = 0;
cout << "请打分" << endl;
cin >> score;
cout << "你打的分为" << score << endl;
switch (score)
//缺点:只能是整数或者字符
//优点:结构清晰,执行效率高
{
case 10:
cout << "经典" << endl;
break;
case 9:
cout << "经典" << endl;
break;
case 8:
cout << "很好" << endl;
break;
case 7:
cout << "很好" << endl;
break;
case 6:
cout << "一般" << endl;
break;
case 5:
cout << "一般" << endl;
break;
default:
cout << "不好" << endl;
break;
}
system("pause");
return 0;
}

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

if与switch区别?
switch 缺点:判断时只能是整数或者字符,不可以是一个区间
switch 优点:结构清晰,执行效率高

4.2 循环结构

4.2.1 while循环语句

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

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

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

写循环时应避免死循环!!!

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using namespace std;
int main1()
{
int num = 0;
//打印0~9
while (num < 10)
{
cout << num << endl;
num++;
}
system("pause");
return 0;
}

4.2.2 do…while循环语句

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

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

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using namespace std;
int main()
{
int num = 0;
//打印0~9
do
{
cout << num << endl;
num++;
} while (num<10);
system("pause");
return 0;
}

生成随机数
添加随机数种子 利用系统当前时间生成随机数,防止每次随机数相同
srand((unsigned int)time(NULL));
int num = rand() % 100 + 1;
生成 0 + 1 ~ 99 + 1随机数

案例: 水仙花数

案例描述: 水仙花数是指一个3位数,它的每个位上的数字的3次幂之和等于它本身
例如: 1^3+5^3+3^3=153

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
using namespace std;
int main()
{
//1.先打印所有三位数
int num = 100;
do
{
//2.从所有三位数中找到水仙花数
int a = num % 10; // 个位
int b = num / 10 % 10;//十位
int c = num / 100;//百位
if (a*a*a+b*b*b+c*c*c == num)//如果是水仙花数才打印
{
cout << num << endl;
}
num++;
} while (num<1000);

system("pause");
return 0;
}

4.2.3 for循环语句

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

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

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
using namespace std;
int main()
{
//打印0~9
for (int i = 0;i < 10;i++) {
cout << i << endl;
}
system("pause");
return 0;
}

4.2.4 嵌套循环

作用: 在循环体中再嵌套一层循环,解决一些设计问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>
using namespace std;
int main()
{
/* 打印10*10星图
for (int i = 0;i < 10;i++)
{
for (int j = 0;j < 10;j++)
{
cout << "*";
}
cout << endl;
}*/
//九九乘法表
for (int i = 1;i <= 9;i++)
{
for (int j = 1;j <= i;j++)
{
cout << j << "x" << i << "=" << (i * j)<<"\t";
}
cout << endl;
}
system("pause");
return 0;
}

4.3 跳转语句

4.3.1 break语句

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

break 使用场景:
1.switch语句中 case:break;
2.循环语句中 跳出当前循环
3.嵌套循环语句中 跳出内部循环

4.3.2 continue语句

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

continue:筛选条件符合的内容

4.3.3 goto语句

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

语法: goto 标记;
翻译: 如果标记的名称存在,在执行到goto语句时,会跳转到标记的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
using namespace std;
int main()
{
//goto:
//语法:goto 标记;
cout << "1 ..." << endl;
cout << "2 ..." << endl;
goto FLAG;
cout << "3 ..." << endl;
cout << "4 ..." << endl;
FLAG:
cout << "5 ..." << endl;
system("pause");
return 0;
}

运行结果:

5 数组

5.1 概述

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

特点1:数组中的每个数据元素都是相同的数据类型
特点2:数组是由连续的内存位置组成的

5.2 一维数组

5.2.1 一维数组定义方式

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;
int main()
{
//一维定义:
//1.
int arr1[5];
arr1[0] = 10;
arr1[1] = 20;
arr1[2] = 30;
arr1[3] =40;
arr1[4] = 50;
//2.
int arr2[5]={10,20,30,40,50};
//3.
int arr3[]={10,20,30,40,50};
//定义数组的时候,必须有初始长度
//数组名(常量,不可修改):
//1.统计整个数组在内存中的长度 sizeof(arr)
//2.获取数组在内存中的首地址 cout<<(int)arr<<endl; 第一个元素地址: (int)&arr[0]
//3.元素个数 sizeof(arr)/sizeof(arr[0])
system("pause");
return 0;
}
数组元素逆置

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
using namespace std;
int main()
{
//元素逆置:(start<end执行互换)
int arr[] = { 1,2,3,5,7 };
cout << "逆序前" << endl;
for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++)
{
cout << arr[i] << endl;
}
int start = 0;
int end = sizeof(arr) / sizeof(arr[0]) - 1;//末尾元素下标
while (start<end)
{
//起始下标与结束下标的元素互换
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
//起始位置++;结束位置--
start++;
end--;
}
cout << "逆序后" << endl;
for (int j = 0; j < sizeof(arr) / sizeof(arr[0]); j++)
{
cout << arr[j] << endl;
}
system("pause");
return 0;
}

运行结果:

冒泡排序

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<iostream>
using namespace std;
int main()
{
//冒泡排序
int arr[9] = { 4,2,8,0,5,7,1,3,9 };
cout << "排序前" << endl;
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
cout << arr[i] << endl;
}
//排序总轮数=元素个数-1
//每轮对比次数=元素个数-排序轮数-1
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]) - 1; i++)
{
for (int j = 0; j < sizeof(arr) / sizeof(arr[0])-i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
cout << "排序后" << endl;
for (int j = 0; j < sizeof(arr) / sizeof(arr[0]); j++)
{
cout << arr[j] << endl;
}
system("pause");
return 0;
}

运行结果:

5.3 二维数组

5.3.1 二维数组定义方式

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
using namespace std;
int main()
{
//二维定义:
int arr[2][3] = {
{1,2,3},
{4,5,6}
};
//数组名(常量,不可修改):
//1.占内存空间大小 sizeof(arr) 第一行占用内存 sizeof(arr[0]) 第一个元素 sizeof(arr[0][0])
//行数:sizeof(arr)/sizeof(arr[0]) 列数:sizeof(arr[0])/sizeof(arr[0][0])
//2.获取数组在内存中的首地址 cout<<arr<<endl; (arr arr[0] &arr[0][0])
system("pause");
return 0;
}

6 函数

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

6.1 函数的定义

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

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

语法:

1
2
3
4
5
6
返回值类型 函数名 (参数列表)
{
函数体语句

return 表达式
}

6.2 函数的调用

功能: 使用定义好的函数

语法: 函数名 (参数)

当调用函数时候,实参的值会传递给形参

6.3 值传递

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

6.4 函数的常见样式

1.无参无返
2.有参无返
3.无参有返
4.有参有返

6.5 函数的声明

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

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

6.6 函数的分文件编写

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

函数分文件编写一般有4个步骤:
1.创建.h头文件
头文件右键->添加->新建项,在弹出页面中选择头文件(eg:max.h)

2.创建.cpp源文件
源文件右键->添加->新建项,在弹出页面中选择源文件(eg:max.cpp)
3.头文件写函数声明
4.源文件写函数定义

代码:

max.h

1
2
3
4
//函数声明
#include<iostream>
using namespace std;
int max(int a, int b);

max.cpp

1
2
3
4
5
//函数定义
int max(int a, int b)
{
return a > b ? a : b;
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
using namespace std;
#include"max.h"
int main()
{
int a = 10;
int b = 20;
int c = max(a, b);
cout << c << endl;
system("pause");
return 0;
}

7 指针

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

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

7.1 指针变量的定义和使用

语法: 数据类型 * 变量名;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
using namespace std;
int main()
{
int a = 10;
/*int* p;
p = &a;*/
//32位系统指针占4个字节
//64位系统指针占8个字节
int* p = &a;
cout << "a地址为" << &a << endl;
cout << "p=" << p << endl;
/*通过解引用使用指针
*p = 100;
cout << "*p=" << *p << endl;
cout << "a=" << a << endl;
*/
system("pause");
return 0;
}

7.2 空指针和野指针

空指针:
1.用于指针变量初始化: int* p =NULL;
2.空指针不可以进行访问:0 ~ 255之间的内存编号是系统占用

野指针:避免出现
int* p= (int *)0x1100;

7.3 const修饰指针

const修饰指针:常量指针const int* p = &a(指针指向可以修改,指针指向的值不可以改)
指针常量int * const p = &a(指针指向不可以改,指针指向的值可以修改)
const int* const p = &a(指针指向,指针指向的值都不可以改)

7.4 指针和数组

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
using namespace std;
int main()
{
//指针与数组:
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr;
cout << "arr[0]=" << *p<<endl;
p++;
cout << "arr[1]=" << *p << endl;
cout << "指针遍历数组" << endl;
int* p2 = arr;
for (int i = 0; i < 10; i++)
{
cout << *p2 << endl;
p2++;
}
system("pause");
return 0;
}

7.5 指针和函数

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
using namespace std;
void swap(int* p1, int* p2);
int main()
{
/*地址传递:
*/
int num1 = 10;
int num2 = 20;
swap(&num1, &num2);
cout << "num1=" << num1 << endl;
cout << "num2=" << num2 << endl;
system("pause");
return 0;
}
void swap(int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}

运行结果:

案例: 指针、数组、函数

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

代码:

bubbleSort.h

1
2
3
4
#include<iostream>
using namespace std;
void bubbleSort(int* arr, int len);
void printArray(int* arr, int len);

bubbleSort.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
using namespace std;
void bubbleSort(int* arr, int len)
{
//排序总轮数=元素个数-1
//每轮对比次数=元素个数-排序轮数-1
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;
}
}
}
}
void printArray(int* arr, int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << endl;
}
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
#include"bubbleSort.h"
int main()
{
//案例:
int arr[10] = {4,3,6,9,1,2,10,8,7,5};
int len = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, len);
printArray(arr, len);
system("pause");
return 0;
}

运行结果:

8 结构体

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

8.1 结构体定义和使用

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

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

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
using namespace std;
#include<string>
struct Student
{
string name;
int age;
int score;
}s3;
int main()
{
struct Student s1;
//等同于Student s1;
s1.name = "张三";
s1.age = 18;
s1.score = 80;
struct Student s2= { "李四",19,88 };
s3.name = "王五";
s3.age = 17;
s3.score = 90;
system("pause");
return 0;
}

8.2 结构体数组

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

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
using namespace std;
//结构体数组
//1.定义结构体
#include<string>
struct Student
{
string name;
int age;
int score;
};
int main()
{
//2.创建结构体数组
struct Student stuArray[3] =
{
{"张三",18,100},
{"李四",16,89},
{"王五",18,98}
};
//3.给结构体数组赋值
stuArray[2].name = "张六";
stuArray[2].age = 19;
stuArray[2].score = 90;
//4.遍历结构体数组
for (int i = 0; i < 3; i++)
{
cout << "姓名:" << stuArray[i].name
<< " 年龄:" << stuArray[i].age
<< " 分数:" << stuArray[i].score << endl;
}
system("pause");
return 0;
}

8.3 结构体指针

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

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
using namespace std;
//结构体指针
#include<string>
struct Student
{
string name;
int age;
int score;
};
int main()
{
//1.创建结构体变量
Student s = { "张三",18,90 };
//2.通过指针指向结构体变量
Student* p = &s;
//3.通过指针访问数据
cout << "姓名:" << p->name
<< " 年龄:" << p->age
<< " 分数:" << p->score << endl;
system("pause");
return 0;
}

8.4 结构体嵌套结构体

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<iostream>
using namespace std;
//结构体嵌套
#include<string>
struct Student
{
string name;
int age;
int score;
};
struct Teacher
{
int id;
string name;
int age;
struct Student stu;
};

int main()
{
//1.创建结构体变量
Teacher t;
t.id = 10;
t.name = "老王";
t.age = 50;
t.stu.name = "小王";
t.stu.age = 18;
t.stu.score = 100;
cout << "老师姓名:" << t.name
<< " 老师年龄:" << t.age
<< "学生姓名:" << t.stu.name
<< " 学生年龄:" << t.stu.age
<< " 学生分数:" << t.stu.score << endl;
system("pause");
return 0;
}

8.5 结构体做函数参数

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

传递方式有俩种:

  • 值传递
  • 地址传递

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<iostream>
using namespace std;
struct student
{
string name;
int age;
int score;
};
//值传递
void printStudent1(struct student s)
{
cout << "值传递子函数 : 姓名:" << s.name << endl;
cout << "值传递子函数 : 年龄:" << s.age << endl;
cout << "值传递子函数 : 分数:" << s.score << endl;
}
//地址传递
void printStudent2(struct student *p)
{
cout << "地址传递子函数 : 姓名:" << p->name << endl;
cout << "地址传递子函数 : 年龄:" << p->age << endl;
cout << "地址传递子函数 : 分数:" << p->score << endl;
}
int main()
{
struct student s;
s.name = "张三";
s.age = 20;
s.score = 85;
cout << "main : 姓名:" << s.name << endl;
cout << "main : 年龄:" << s.age<< endl;
cout << "main : 分数:" << s.score << endl;
printStudent1(s);
printStudent2(&s);
system("pause");
return 0;
}

运行结果:

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

8.6 结构体中const使用场景

作用: 用const来防止误操作

案例: 结构体

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

五名英雄信息如下:

1
2
3
4
5
{"刘备",23,"男"},
{"关羽",22,"男"},
{"张飞",20,"男"},
{"赵云",21,"男"},
{"貂蝉",19,"女"},

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<iostream>
using namespace std;
#include<string>
//英雄结构体
struct Hero
{
string name;
int age;
string sex;
};
void bubbleSort(struct Hero heroArray[], int length)
{
for (int i = 0; i < length-1; i++)
{
for (int j = 0; j < length-i-1; j++)
{
if (heroArray[j].age > heroArray[j + 1].age)
{
struct Hero temp = heroArray[j];
heroArray[j] = heroArray[j + 1];
heroArray[j + 1] = temp;
}
}
}
}
void printHero(struct Hero heroArray[], int length)
{
for (int i = 0; i < length; i++)
{
cout << "姓名:" << heroArray[i].name << " 年龄:" << heroArray[i].age << " 性别:" << heroArray[i].sex << endl;
}
}
int main()
{
struct Hero heroArray[5] = {
{"刘备",23,"男"},
{"关羽",22,"男"},
{"张飞",20,"男"},
{"赵云",21,"男"},
{"貂蝉",19,"女"} };
int length = sizeof(heroArray) / sizeof(heroArray[0]);
cout << "排序前:" << endl;
printHero(heroArray, length);
bubbleSort(heroArray, length);
cout << "排序后:" << endl;
printHero(heroArray, length);
system("pause");
return 0;
}

运行结果:

通讯录管理系统

功能要求:

  • 菜单功能:用户选择功能的界面 void showMenu()
  • 添加联系人:向通讯录中添加新人,信息包括(姓名、性别、年龄、联系电话、家庭地址)最多记录1000人
    步骤:
    • 设计联系人结构体
    • 设计通讯录结构体
    • main函数中创建通讯录
    • 封装添加联系人函数
    • 测试添加联系人功能
  • 显示联系人:显示通讯录中所有联系人的信息
    步骤:
    • 封装显示联系人函数
    • 测试显示联系人功能
  • 删除联系人:按照姓名进行删除指定联系人
    步骤:
    • 封装检测联系人是否存在
    • 封装删除联系人函数
    • 测试删除指定联系人
  • 查找联系人:按照姓名查看指定联系人信息
    步骤:
    • 封装查找联系人函数
    • 测试查找指定联系人
  • 修改联系人:按照姓名重新修改指定联系人
    步骤:
    • 封装修改联系人函数
    • 测试修改指定联系人
  • 清空联系人:清空通讯录中所有信息
  • 退出通讯录:退出当前使用的通讯录 当用户选择0时,退出

提示代码:
system("cls");//清屏

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#include<iostream>
using namespace std;
#include<string>
#define MAX 1000
//联系人结构体
struct Person
{
string m_Name;
int m_Sex;
int m_Age;
string m_Phone;
string m_Addr;
};
//通讯录结构体
struct Addressbooks
{
struct Person personArray[MAX];
int m_Size;
};
//添加联系人
void addPerson(Addressbooks* abs)
{
if (abs->m_Size==MAX)
{
cout << "通讯录已满,无法添加" << endl;
return;
}
else
{
string name;
cout << "请输入姓名:" << endl;
cin >> name;
abs->personArray[abs->m_Size].m_Name = name;
cout << "请输入性别:" << endl;
cout << "1 --- 男" << endl;
cout << "2 --- 女" << endl;
int sex = 0;

while (true)
{
cin >> sex;
if (sex==1||sex==2)
{
abs->personArray[abs->m_Size].m_Sex = sex;
break;
}
cout << "输入有误,重新输入" << endl;
}
cout << "请输入年龄:" << endl;
int age = 0;
while (true)
{

cin >> age;
if (age > 0 && age<150)
{
abs->personArray[abs->m_Size].m_Age = age;
break;
}
cout << "输入有误,重新输入" << endl;
}

cout << "请输入联系电话:" << endl;
string phone;
cin >> phone;
abs->personArray[abs->m_Size].m_Phone = phone;
cout << "请输入家庭住址:" << endl;
string address;
cin >> address;
abs->personArray[abs->m_Size].m_Addr = address;
abs->m_Size++;//更新
cout << "添加成功" << endl;
system("pause");
system("cls");//清屏
}
}
//显示联系人
void showPerson(Addressbooks* abs)
{
if (abs->m_Size == 0)
{
cout << "当前记录为空" << endl;
}
else
{
for (int i = 0; i < abs->m_Size; i++)
{
cout << "姓名:" << abs->personArray[i].m_Name << "\t";
cout << "性别:" << (abs->personArray[i].m_Sex==1?"男":"女")<< "\t";
cout << "年龄:" << abs->personArray[i].m_Age << "\t";
cout << "电话:" << abs->personArray[i].m_Phone<< "\t";
cout << "地址:" << abs->personArray[i].m_Addr << endl;

}
}
system("pause");
system("cls");
}
//检查联系人是否存在,存在返回其在数组中位置,不存在返回-1
int isExist(Addressbooks* abs, string name)
{
for (int i = 0; i < abs->m_Size; i++)
{
if (abs->personArray[i].m_Name == name)
{
return i;
}
}
return -1;
}
//删除联系人
void deletePerson(Addressbooks* abs)
{
cout << "请输入删除的联系人" << endl;
string name;
cin >> name;
int ret = isExist(abs, name);
if ( ret!= -1)
{
for (int i = ret; i < abs->m_Size; i++)
{
//数据前移
abs->personArray[i] = abs->personArray[i + 1];
}
abs->m_Size--;//更新
cout << "删除成功" << endl;
}
else
{
cout << "查无此人" << endl;
}
system("pause");
system("cls");//清屏
}
//查找联系人
void findPerson(Addressbooks *abs)
{
cout << "请输入要查找的联系人" << endl;
string name;
cin >> name;
int ret=isExist(abs, name);
if (ret != -1)
{
cout << "姓名:" << abs->personArray[ret].m_Name << "\t";
cout << "性别:" << (abs->personArray[ret].m_Sex == 1 ? "男" : "女") << "\t";
cout << "年龄:" << abs->personArray[ret].m_Age << "\t";
cout << "电话:" << abs->personArray[ret].m_Phone << "\t";
cout << "地址:" << abs->personArray[ret].m_Addr << endl;
}
else
{
cout << "查无此人" << endl;
}
system("pause");
system("cls");//清屏
}
//修改联系人
void modifyPerson(Addressbooks* abs)
{
cout << "请输入要修改的联系人" << endl;
string name;
cin >> name;
int ret = isExist(abs, name);
if (ret != -1)
{
string name;
cout << "请输入姓名:" << endl;
cin >> name;
abs->personArray[ret].m_Name = name;
cout << "请输入性别:" << endl;
cout << "1 --- 男" << endl;
cout << "2 --- 女" << endl;
int sex = 0;

while (true)
{
cin >> sex;
if (sex == 1 || sex == 2)
{
abs->personArray[ret].m_Sex = sex;
break;
}
cout << "输入有误,重新输入" << endl;
}
cout << "请输入年龄:" << endl;
int age = 0;
while (true)
{
cin >> age;
if (age > 0 && age < 150)
{
abs->personArray[ret].m_Age = age;
break;
}
cout << "输入有误,重新输入" << endl;
}
cout << "请输入联系电话:" << endl;
string phone;
cin >> phone;
abs->personArray[ret].m_Phone = phone;
cout << "请输入家庭住址:" << endl;
string address;
cin >> address;
abs->personArray[ret].m_Addr = address;
}
else
{
cout << "查无此人" << endl;
}
system("pause");
system("cls");//清屏
}
//清空联系人
void clearPerson(Addressbooks* abs)
{
abs->m_Size = 0;
cout << "通讯录已清空" << endl;
system("pause");
system("cls");//清屏
}
//菜单界面
void showMenu()
{
cout << "************************" << endl;
cout << "***** 1.添加联系人 *****" << endl;
cout << "***** 2.显示联系人 *****" << endl;
cout << "***** 3.删除联系人 *****" << endl;
cout << "***** 4.查找联系人 *****" << endl;
cout << "***** 5.修改联系人 *****" << endl;
cout << "***** 6.清空联系人 *****" << endl;
cout << "***** 0.退出通讯录 *****" << endl;
cout << "************************" << endl;
}
int main()
{
Addressbooks abs;
abs.m_Size = 0;
int select = 0;
while (true)
{
showMenu();//菜单
cin >> select;
switch (select)
{
case 1://1.添加联系人
addPerson(&abs);//地址传递,可以修饰实参
break;
case 2://2.显示联系人
showPerson(&abs);
break;
case 3://3.删除联系人
deletePerson(&abs);
break;
case 4://4.查找联系人
findPerson(&abs);
break;
case 5://5.修改联系人
modifyPerson(&abs);
break;
case 6://6.清空联系人
clearPerson(&abs);
break;
case 0://0.退出通讯录
cout << "欢迎下次使用!" << endl;
system("pause");
return 0;
break;
default:
cout << "请重新输入" << endl;
break;
}
}
system("pause");
return 0;
}

2 C++核心编程

2.1 内存分区模式

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的 特点:共享、只读
  • 全局区:存放全局变量和静态变量以及常量 该区域的数据在程序结束后由操作系统释放
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等 不要返回局部变量的地址:第一次可以正确打印是因为编译器做了保留,第二次这个数据就不再保留了
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收 主要利用new在堆区开辟内存

程序运行前:代码区、全局区

不在全局区:局部变量,const修饰局部变量(局部常量)
全局区:全局变量,静态变量(static关键字修饰 eg:static int s_a = 10),常量:字符串常量、const修饰全局变量

程序运行后:栈区、堆区

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

指针本质是局部变量

2.1.1 new操作符

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

语法: new 数据类型

利用new创建的数据,会返回该数据对应的类型的指针
释放数组delete[] arr;

2.2 引用

2.2.1 引用的基本使用

作用: 给变量起别名

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

  • 引用必须初始化
  • 引用一旦初始化就不能更改

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<iostream>
using namespace std;

int main()
{
//引用基本语法:
//数据类型 &别名=原名
int a = 10;
int& b = a;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
int c = 100;
b = c;//赋值操作不是更改应用
cout << "a=" << a << endl;
cout << "b=" << b << endl;
}
//a=10,b=10
//a=100,b=100

2.2.2 引用做函数参数

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
using namespace std;

int main()
{
int a = 10;
int b = 20;
mySwap(a,b);
cout << "a=" << a << endl;
cout << "b=" << b << endl;
}
//引用做函数参数
//优点:简化指针修改实参
//作用:让形参修饰实参
void mySwap(int &a,int &b)
{
int temp=a;
a=b;
b=temp;
}

2.2.3 引用做函数返回值

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

1.不能返回局部变量的引用
2.函数的调用可以作为左值

本质:指针常量 int * const ref = &a

2.2.4 常量引用

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

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;
void showValue(const int& val);

int main()
{
//常量引用
/*int a = 10;
int& ref = a;*/
//const int& ref = 10;//加入const变为只读,不能修改
int a = 100;
showValue(a);
system("pause");
return 0;
}
void showValue(const int& val)
{
cout << "val=" << val << endl;
}

2.3 函数(提升)

2.3.1 函数默认参数

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

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

注意:
1.如果某个位置已经有了默认参数,那么从这个位置往后,从左往右都必须有默认值
2.如果函数声明有默认参数,函数实现就不能有默认参数(声明和实现只能有一个默认参数)

2.3.2 函数占位参数

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

占位参数也可以有默认参数(void func(int a,int = 10))

2.3.3 函数重载

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

条件:
1.同一作用域
2.函数名称相同
3.函数参数类型不同,或者个数不同,或者顺序不同

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

  • 引用可作为重载条件:void func(int& a)(传入变量时执行)与 void func(const int& a)(传入数据时执行)
  • 函数重载遇到函数默认参数

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<iostream>
using namespace std;
void func()
{
cout << "func()的调用" << endl;
}
void func(int a)
{
cout << "func(int a)的调用" << endl;
}
void func(double a)
{
cout << "func(double a)的调用" << endl;
}
void func(int a,double b)
{
cout << "func(int a,double b)的调用" << endl;
}
void func(double a,int b)
{
cout << "func(double a,int b)的调用" << endl;
}
int main()
{
//条件:
//1.同一作用域
//2.函数名称相同
//3.函数参数类型不同,或者个数不同,或者顺序不同
//函数返回值不可以作为函数重载的条件
//func();
//func(10);
//func(3.14);
//func(10,3.14);
func(3.14,10);
//引用可作为重载条件:void func(int& a)(传入变量执行)与 void func(const int& a)(传入数据执行)

system("pause");
return 0;
}

2.4 类和对象

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

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

具有相同性质的对象可以抽象称为类

2.4.1 封装

封装的意义:

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

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

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

类中的属性和行为 我们统一称为 成员
属性: 成员属性 成员变量
行为: 成员函数 成员方法

封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:

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

struct和class区别:

  • struct默认权限为公共
  • class默认权限为私有

成员属性设置为私有:
优点:
1.将所有成员属性设置为私有,可以自己控制读写权限 set写;get读
2.对于写权限,我们可以检测数据的有效性

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<iostream>
using namespace std;
#include<string>
//class 与 struct区别:
/*
* class默认私有
* struct默认公共
*/
class Student
{//类中属性与行为称为成员
//属性 成员属性 成员变量 (设置为私有)
//行为 成员函数 成员方法
public://权限:
/*
* public 公共 成员类内可以访问 成员类外可以访问
* protected 保护 成员类内可以访问 成员类外不可以访问
* private 私有 成员类内可以访问 成员类外不可以访问
*/
string m_Name;
int m_Id;
void showStudent()//显示姓名与学号
{
cout << "姓名:" << m_Name << ";学号:" << m_Id << endl;
}
//给姓名赋值
void setName(string name)
{
m_Name = name;
}
//给学号赋值
void setId(int id)
{
m_Id = id;
}
protected:
string m_Car;
private:
string m_Password;
public:
void func()
{
m_Name = "张三";
m_Car = "bike";
m_Password = "123456";
}
};
/*int main()
{
Student s1;
//s1.m_Name = "张三";
s1.setName("张三");
s1.m_Id = 1;
s1.showStudent();
system("pause");
return 0;
}*/
案例: 设计立方体类

案例描述:

设计立方体类(Cube)
求出立方体的面积和体积
分别用全局函数和成员函数判断俩个立方体是否相等

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include<iostream>
using namespace std;
//创建类
class Cube
{
public:
//设置长
void setL(int l)
{
m_L = l;
}
//获取长
int getL()
{
return m_L;
}
//设置宽
void setW(int w)
{
m_W = w;
}
//获取宽
int getW()
{
return m_W;
}
//设置高
void setH(int h)
{
m_H = h;
}
//获取高
int getH()
{
return m_H;
}
//获取立方体面积
int calculateS()
{
return 2 * m_L*m_W + 2 * m_W*m_H + 2 * m_H*m_L;
}
//获取立方体体积
int calculateV() {
return m_L * m_W * m_H;
}
//利用成员函数判断俩个立方体是否相等
bool isSameByClass(Cube &c)
{
if (m_L == c.getL() && m_W == c.getW() && m_H == c.getH())
{
return true;
}
return false;
}
private:
int m_L;//长
int m_W;//宽
int m_H;//高
};
//利用全局函数判断俩立方体是否相等
bool isSame(Cube &c1, Cube &c2)
{
if (c1.getL()==c2.getL()&&c1.getW()==c2.getW()&&c1.getH()==c2.getH())
{
return true;
}
return false;
}
int main()
{
Cube c1;
c1.setL(10);
c1.setH(10);
c1.setW(10);
cout << "面积为:" << c1.calculateS() << endl;
cout << "体积为:" << c1.calculateV() << endl;
Cube c2;
c2.setL(10);
c2.setH(10);
c2.setW(10);
cout << "全局函数判断" << endl;
bool ret = isSame(c1, c2);
if (ret)
{
cout << "c1与c2相等" << endl;
}
else {
cout << "c1与c2不相等" << endl;
}
cout << "成员函数判断" << endl;
ret = c1.isSameByClass(c2);
if (ret)
{
cout << "c1与c2相等" << endl;
}
else {
cout << "c1与c2不相等" << endl;
}
system("pause");
return 0;
}
案例: 点和圆的关系

案例描述:
设计一个圆形类(Circle)和一个点类(Point),计算点和圆的关系

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//点与圆关系
#include<iostream>
using namespace std;
//点类
class Point
{
public:
//设置x
void setX(int x)
{
m_X = x;
}
//获取x
int getX()
{
return m_X;
}
//设置y
void setY(int y)
{
m_Y = y;
}
//获取y
int getY()
{
return m_Y;
}
private:
int m_X;
int m_Y;
};
//圆类
class Circle
{
public:
//设置半径
void setR(int r)
{
m_R = r;
}
//获取半径
int 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)
{
//计算俩点间距离平方
int 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 c;
c.setR(10);
Point center;
center.setX(10);
center.setY(0);
c.setCenter(center);

Point p;
p.setX(10);
p.setY(10);
isInCircle(c, p);
system("pause");
return 0;
}

分文件:

point.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once
#include<iostream>
using namespace std;
//点类
class Point
{
public:
//设置x
void setX(int x);
//获取x
int getX();
//设置y
void setY(int y);
//获取y
int getY();
private:
int m_X;
int m_Y;
};

point.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include"point.h"
//点类
//设置x
void Point::setX(int x)
{
m_X = x;
}
//获取x
int Point::getX()
{
return m_X;
}
//设置y
void Point::setY(int y)
{
m_Y = y;
}
//获取y
int Point::getY()
{
return m_Y;
}

circle.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma once
#include<iostream>
using namespace std;
#include"point.h"
//圆类
class Circle
{
public:
//设置半径
void setR(int r);
//获取半径
int getR();
//设置圆心
void setCenter(Point center);
//获取圆心
Point getCenter();
private:
int m_R;
Point m_Center;
};

circle.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include"circle.h"
//圆类
//设置半径
void Circle::setR(int r)
{
m_R = r;
}
//获取半径
int Circle::getR()
{
return m_R;
}
//设置圆心
void Circle::setCenter(Point center)
{
m_Center = center;
}
//获取圆心
Point Circle::getCenter()
{
return m_Center;
}

Main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//点与圆关系
#include<iostream>
using namespace std;
#include"point.h"
#include"circle.h"
void isInCircle(Circle& c, Point& p)
{
//计算俩点间距离平方
int 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 c;
c.setR(10);
Point center;
center.setX(10);
center.setY(0);
c.setCenter(center);

Point p;
p.setX(10);
p.setY(10);
isInCircle(c, p);
system("pause");
return 0;
}

2.4.2 对象的初始化和清理

构造函数和析构函数:解决对象的初始化和清理问题

如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。

构造函数语法:类名(){}
1.构造函数无返回值 不用写void
2.函数名与类名相同
3.可以有参数,可以发生重载
4.创建对象时会自动调用构造函数且只调用一次

析构函数:~类名(){}
1.无返回值 不用写void
2.函数名与类名相同 在名称前加~
3.不可以有参数,不可以发生重载
4.对象在销毁前会自动调用,且只调用一次

构造函数的分类及调用
1.分类

  • 按参数分:有参构造和无参构造
  • 按类型分:普通构造和拷贝构造
    2.三种调用方式
  • 括号法
  • 显示法
  • 隐式转换法

拷贝构造函数调用时机

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

构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<iostream>
using namespace std;


class Person
{
public:
//构造函数 初始化
/*1构造函数无返回值 不用写void
* 2.函数名与类名相同
* 3.可以有参数,可以发生重载
* 4.创建对象时,构造函数会自动调用,且只调用一次
*/
Person()
{
cout << "无参构造函数调用" << endl;
}
Person(int a)
{
cout << "有参构造函数调用" << endl;
}
//析构函数 清理(作用:将堆区开辟数据做释放操作)
/*
* 1.无返回值 不用写void
* 2.函数名与类名相同 在名称前加~
* 3.不可以有参数,不可以发生重载
* 4.对象在销毁前会自动调用,且只调用一次
*/
~Person()
{
cout << "析构函数调用" << endl;
}
//拷贝构造函数
Person(const Person& p)
{
cout << "拷贝构造函数调用" << endl;
}
};
void test01()
{
//调用
//1.括号法
//Person p1;//栈上数据,test01执行完毕后会释放这个对象
//Person p2(10);
//Person p3(p2);
//2.显示法
Person p1;
Person p2=Person(10);
Person p3=Person(p2);
Person(10);//匿名对象 (不能用拷贝对象初始化)
//3.隐式转换法
Person p4 = 10;
Person p5 = p4;
}
int main()
{
//test01();
Person p;
system("pause");
return 0;
}

深拷贝和浅拷贝:
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作

浅拷贝:问题:堆区内存重复释放
深拷贝:m_Height=new int(*p.m_Height);

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person默认构造函数调用" << endl;
}
Person(int age,int height)
{
m_Age = age;
m_Height = new int(height);
cout << "Person有参构造函数调用" << 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()
{
//析构代码,将堆区开辟的数据释放
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
cout << "Person析构函数调用" << endl;
}
int m_Age;//年龄
int* m_Height;
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄:" << p1.m_Age <<"身高:"<<*p1.m_Height<< endl;
Person p2(p1);
cout << "p2的年龄:" << p2.m_Age << "身高:" << *p2.m_Height << endl;

}
int main()
{
test01();
system("pause");
return 0;
/*
Person有参构造函数调用
p1的年龄:18身高:160
拷贝构造函数的调用
p2的年龄:18身高:160
Person析构函数调用
Person析构函数调用
*/
}

初始化列表:

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

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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();
system("pause");
return 0;
}
//m_A=30
//m_B=20
//m_C=10

类对象作为类成员:

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

例如:

1
2
3
4
5
class A{}
class B
{
A a;
}

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

当其他类对象作为本类成员,构造时先构造类对象,再构造自身
构造先构造其他再构造自身,析构与其相反

静态成员:

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

静态成员分为:

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

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

类内的成员变量和成员函数分开存储 只有非静态成员变量才属于类的对象上

空对象占用内存空间为1
C++编译器会给每个空对象分配一个字节的空间,是为了区分空对象占内存的位置
每个空对象也应该有一个独一无二的内存空间

this指针:
this指针指向被调用的成员函数所属的对象

this指针是隐含每一个非静态的成员函数内的一种指针
this指针不需要定义,直接使用即可

this指针用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<iostream>
using namespace std;
//this:指向被调用成员函数所属的对象

class Person
{
public:
Person(int age)
{
this->age = age;
}
Person& PersonAddAge(Person &p)//返回引用
{
this->age += p.age;
return *this;//this 为指向p2的指针
}
int age;
};
//1.解决名称冲突
void test01()
{
Person p1(18);
cout << "p1的年龄为" << p1.age << endl;
}
//2.返回对象本身用*this
void test02()
{
Person p1(10);
Person p2(10);
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);//链式编程
cout << "p2的年龄为" << p2.age << endl;

}
int main()
{
test02();
system("pause");
return 0;
}
//p2的年龄为40

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

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

1
2
3
4
if(this==NULL)
{
return;
}

const修饰成员函数:

常函数:

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

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<iostream>
using namespace std;
//常函数
class Person
{
public:
//this指针本质:指针常量:指针指向不可以修改
//const Person* const this;
void showPerson() const//修饰this指针,让this指向的值也不能修改
{
this->m_B = 100;
}
int m_A;
mutable int m_B;//即使在常函数中也可以修改
};
void test01()
{
Person p;
p.showPerson();
}

void test02()
{
const Person p;//常对象:只能调用常函数
p.m_B = 100;
}
int main()
{
test01();
system("pause");
return 0;
}

2.4.4 友元

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

关键字:friend

友元三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<iostream>
using namespace std;
//友元:friend
/*
* 1.全局函数做友元:在class 添加:friend 全局函数;(friend void Person(Building* building);)
* 2.类做友元:在class 添加:friend 类;(friend class Person;)
* 3.成员函数做友元:在class 添加:friend 成员函数;(friend void Person::visit();)
*/
class Building
{
//可以访问Building中私有成员
friend void goodGay(Building* building);
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
//全局函数
void goodGay(Building *building)
{
cout << "访问" << building->m_SittingRoom << endl;
cout << "访问" << building->m_BedRoom << endl;
}
void test01()
{
Building building;
goodGay(&building);
}
int main()
{
test01();
//类外构造函数 类名::函数名(){}
system("pause");
return 0;
}
//访问客厅
//访问卧室

2.4.5 运算符重载

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

加法重载代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//运算符重载也可以发生函数重载
//加法
#include<iostream>
using namespace std;
class Person
{
public:
//1.成员函数
//Person operator+(Person& p)
//{
//Person temp;
//temp.m_A = this->m_A + p.m_A;
//temp.m_B = this->m_B + p.m_B;
//return temp;
//}
int m_A;
int m_B;
};
//全局函数
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;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
cout << "p3.m_A=" << p3.m_A << endl;
cout << "p3.m_B=" << p3.m_B << endl;
}
int main()
{
test01();
system("pause");
return 0;
}

左移重载:
作用:可以输出自定义数据类型
不会利用成员函数重载<<运算符,因为无法实现cout在左边
只能利用全局函数重载左移运算符
左移重载代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//运算符重载也可以发生函数重载
//左移
#include<iostream>
using namespace std;
class Person
{
friend ostream& operator<<(ostream& cout, Person& p);
public:
Person(int a,int b)
{
m_A = a;
m_B = b;
}

private:
//成员函数:通常不用
int m_A;
int m_B;
};
//全局函数
ostream& operator<<(ostream &cout,Person &p)
{
cout << "m_A=" << p.m_A << "m_B=" << p.m_B;
return cout;
}
void test01()
{
Person p(10,10);
cout << p<<endl;

}
int main()
{
test01();
system("pause");
return 0;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//运算符重载也可以发生函数重载
//递增
#include<iostream>
using namespace std;
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);

public:
MyInteger()
{
m_Num = 0;
}
//重载++前置
MyInteger& operator++()//返回引用是为了对一个数据进行++
{
m_Num++;
return *this;
}
//重载++后置
MyInteger operator++(int)//int 表示占位参数 可以用于区分前置和后置递增 返回值
{
//先记录,再递增,最后返回记录值
MyInteger temp = *this;
m_Num++;
return temp;
}
private:
int m_Num;
};
//全局函数
ostream& operator<<(ostream& cout, MyInteger myint)
{
cout <<myint.m_Num;
return cout;
}
void test01()
{
MyInteger myint;
cout << ++myint << endl;
cout << myint << endl;
}
void test02()
{
MyInteger myint1;
cout << myint1++ << endl;
cout << myint1 << endl;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}

赋值重载:
C++编译器至少给一个类添加4个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
4.赋值运算符 operator=,对属性进行值拷贝

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//运算符重载也可以发生函数重载
//赋值
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{
if (m_Age!= NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//重载赋值
Person& operator=(Person& p)
{
//深拷贝 先判断时是否有属性在堆区,如果有先释放干净,再进行深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);//深拷贝
return *this;
}
int *m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
p2 = p1;//赋值
cout << "p1的年龄为" << *p1.m_Age << endl;
cout << "p2的年龄为" << *p2.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//关系运算符
#include<iostream>
using namespace std;
class Person
{
public:
Person(string name,int age)
{
m_Age = age;
m_Name = name;
}
//重载==
bool operator==(Person& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}
//重载!=
bool operator!=(Person& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
return true;
}
string m_Name;
int m_Age;
};
void test01()
{
Person p1("Tom", 18);
Person p2("Tom", 18);
if (p1 == p2)
{
cout << "p1与p2相等" << endl;
}
else
{
cout << "p1与p2不相等" << endl;
}
if (p1 != p2)
{
cout << "p1与p2不相等" << endl;
}
else
{
cout << "p1与p2相等" << endl;
}
}
int main()
{
test01();
system("pause");
return 0;
}

函数调用运算符重载:

  • 函数调用运算符()也可以发生重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
    小括号重载代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//小括号 十分灵活
#include<iostream>
using namespace std;
#include<string>
class MyPrint
{
public:
void operator() (string test)
{
cout << test << endl;
}
};
void test01()
{
MyPrint myPrint;
myPrint("hello world");
}
int main()
{
test01();
system("pause");
return 0;
}

总结:
1.对于内置的数据类型的表达式的运算符是不可能改变的
2.不要滥用运算符重载

2.4.6 继承

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

好处:减少重复代码

基本语法: class 子类 : 继承方式 父类
子类也称为派生类
父类也称为基类

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

继承方式一共有三种:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#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;//父类保护,子类保护
//父类私有,子类访问不到
}
};
void test01()
{
Son1 s1;
s1.m_A = 100;
}
//保护继承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2 :protected Base2
{
public:
void func()
{
m_A = 10;//父类公共,子类保护
m_B = 10;//父类保护,子类保护
//父类私有,子类访问不到
}
};
void test02()
{
Son2 s2;
}
//私有继承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3 :private Base3
{
public:
void func()
{
m_A = 10;//父类公共,子类私有
m_B = 10;//父类保护,子类私有
//父类私有,子类访问不到
}
};
void test03()
{
Son3 s3;
}
int main()
{
system("pause");
return 0;
}

继承中的对象模型:
父类中所有非静态成员属性都会被子类继承下去
父类中的私有成员属性 是被编译器隐藏了 所以访问不到 但是确实被继承下去了

查看对象模型:打开开发人员命令行工具找到对应.cpp所在的目录,输入cl /dl reportSingleClassLayout所属类名 "对应.cpp"

继承中的构造和析构顺序:
先构造父类,再构造子类;析构与构造顺序相反
子类继承父类后,当创建子类对象,也会调用父类的构造函数

继承同名成员(函数)处理方式:

  • 访问子类同名成员(函数) 直接访问即可
  • 访问父类同名成员(函数) 需要加作用域 s.Base::m_A

如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
如果想访问到父类中隐藏的同名成员函数,需要加作用域

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

可以通过对象访问,也可以通过类名访问

  • 访问子类同名成员(函数) 直接访问即可
    • 对象:s.m_A
    • 类名:Son::m_A
  • 访问父类同名成员(函数) 需要加作用域
    • 对象:s.Base::m_A
    • 类名:Son::Base::m_A

如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
如果想访问到父类中隐藏的同名成员函数,需要加作用域

多继承语法:
C++允许一个类继承多个类

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

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

当父类中出现同名函数,需要加作用域区分

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

菱形继承:
概念:

  • 俩个派生类B、C继承同一个基类A
  • 又有某个类D同时继承俩个派生类
  • 这种继承方式被称为菱形继承,或者钻石继承
    问题:
    1.某个类D使用数据时,会产生二义性
    2.D继承了A的俩份数据,我们其实只需要一份就行

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//菱形继承:虚继承(继承之前加上virtual)Animal叫做虚基类 可以解决菱形继承问题
#include<iostream>
using namespace std;
//动物类
class Animal
{
public:
int m_Age;
};
//羊类
class Sheep :virtual public Animal
{

};
//驼类
class Tuo :virtual public Animal
{

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

};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
//菱形继承,俩个父类拥有相同数据,需加作用域区分
cout << "Sheep::m_Age=" << st.Sheep::m_Age << endl;
cout << "Tuo::m_Age=" << st.Tuo::m_Age << endl;
//只需一份数据
cout << "st.m_Age=" << st.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
//Sheep::m_Age=28
//Tuo::m_Age=28
//st.m_Age=28

2.4.7 多态

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

多态分为俩类:

  • 静态多态:函数重载和运算符重载属于静态重载,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态
    条件:
    • 1.有继承关系
    • 2.子类重写父类的虚函数 重写 函数返回值类型 函数名 参数列表 完全相同
      使用:
    • 父类的指针或者引用执行子类对象

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

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
案例: 计算器类

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

多态的优点:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include<iostream>
using namespace std;
#include<string>
//重写父类虚函数 virtual+函数名
class Calulator
{
public:
int getResult(string oper)
{
if (oper == "+")
{
return m_Num1 + m_Num2;
}
else if (oper=="-")
{
return m_Num1 - m_Num2;
}
else if (oper=="*")
{
return m_Num1 * m_Num2;
}
//如果想扩展新的功能,需要修改源码
//在真正开发中 提倡 开闭原则
//开闭原则:对扩展进行开发,对修改进行关闭
}
int m_Num1;
int m_Num2;
};
void test01()
{
Calulator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+") << endl;
cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getResult("-") << endl;
cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult("*") << endl;
}
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1+m_Num2;
}
};
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 12;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完后销毁
delete abc;
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 12;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 12;
cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
//10+10=20
//10-10=0
//10*10=100
//10+12=22
//10-12=-2
//10*12=120

纯虚函数和抽象类:

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

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

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

抽象类特点:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<iostream>
using namespace std;
class Base//抽象类:无法实例化对象,子类必须重写父类的纯虚函数,否则子类也属于抽象类
{
public:
virtual void func() = 0;//纯虚函数
};
class Son :public Base
{
public:
virtual void func()
{
cout << "func函数调用" << endl;
}
};
void test01()
{
Base* base = new Son;
base->func();
delete base;
}
int main()
{
test01();
system("pause");
return 0;
}

虚析构和纯虚析构:

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

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

虚析构和纯虚析构共性:

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

虚析构和纯虚析构共性:

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

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

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

案例: 电路组装

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include<iostream>
using namespace std;
class CPU
{
public:
virtual void calculator() = 0;
};
class VideoCard
{
public:
virtual void display() = 0;
};
class Memory
{
public:
virtual void storage() = 0;
};
class Computer
{
public:
Computer(CPU* cpu, VideoCard* vc, Memory* mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
void work()
{
m_cpu->calculator();
m_vc->display();
m_mem->storage();
}
~Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
private:
CPU* m_cpu;
VideoCard* m_vc;
Memory* m_mem;
};
class IntelCPU :public CPU
{
virtual void calculator()
{
cout << "Intel Cpu开始计算" << endl;
}
};
class IntelVideoCard :public VideoCard
{
virtual void display()
{
cout << "Intel 显卡开始显示" << endl;
}
};
class IntelMemory :public Memory
{
virtual void storage()
{
cout << "Intel 内存条开始存储" << endl;
}
};
class LenovoCPU :public CPU
{
virtual void calculator()
{
cout << "Lenovo CPU开始计算" << endl;
}
};
class LenovoVideoCard :public VideoCard
{
virtual void display()
{
cout << "Lenovo 显卡开始显示" << endl;
}
};
class LenovoMemory :public Memory
{
virtual void storage()
{
cout << "Lenovo 内存条开始存储" << endl;
}
};
void test01()
{
CPU* intelCpu = new IntelCPU;
VideoCard* intelCard = new IntelVideoCard;
Memory* intelMem = new IntelMemory;
Computer* computer1 = new Computer(intelCpu, intelCard, intelMem);
computer1->work();
delete computer1;
Computer* computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);
computer2->work();
delete computer2;
}
int main()
{
test01();
system("pause");
return 0;
}

2.5 文件操作

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

文件类型分为俩种:
1.文本文件 - 文件以文本ASCII码形式存储在计算机中
2.二进制文件- 文件以文本的二级制形式存储在计算机中,用户一般不能直接读懂他们

操作文件的三大类:
1.ofstream: 写操作
2.ifstream: 读操作
3.fstream : 读写操作

2.5.1 文本文件

一.写文件
步骤:
1.包含头文件
#include<fstream>
2.创建流对象
ofstream ofs;
3.打开文件
ofs.open("文件路径",打开方式);
4.写数据
ofs<<"写入的数据"
5.关闭文件
ofs.close();

文件打开方式:

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

注意:文件打开方式可以配合使用,利用|操作符
例如:用二进制方式写文件 ios::binary | ios::out

二.读文件

步骤:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<iostream>
using namespace std;
#include<fstream>
#include<string>
void test01()
{
ofstream ofs;//创建文件流
ofs.open("test.txt",ios::out);//指定打开方式 写文件
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close();//关闭文件

}
void test02()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//读文件
/*1.
char buf[1024] = { 0 };
while (ifs>>buf)
{
cout << buf << endl;
}*/
/*2.
char buf[1024] = { 0 };
while (ifs.getline(buf,sizeof(buf)))
{
cout << buf << endl;
}*/
/*3.
string buf;
while (getline(ifs,buf))
{
cout << buf << endl;
}*/
//4.不推荐
char c;
while ((c=ifs.get())!=EOF) //EOF 文件尾
{
cout << c << endl;
}
//关闭文件
ifs.close();
}
int main()
{
test02();
system("pause");
return 0;
}

2.5.2 二进制文件

打开方式:ios::binary

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

2.读文件主要利用流对象调用成员函数read
函数原型: istream& read(char * buffer,int len);
参数:字符指针buffer指向内存中一段空间,len是读写的字节数

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<iostream>
using namespace std;
#include<fstream>
#include<string>
//二进制写文件
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test03()
{
ofstream ofs;
ofs.open("person.txt", ios::out | ios::binary);
Person p = { "张三",18 };
ofs.write((const char*)&p, sizeof(Person));
ofs.close();
}
void test04()
{
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;
ifs.close();
}
int main()
{
test04();
system("pause");
return 0;
}
案例: 职工管理系统

案例描述:利用C++来实现一个基于多态的职工管理系统

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

普通职工职责:完成经理交给的任务
经理职责:完成老板交给的任务,并下发任务给员工
老板职责:管理公司所有事物

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

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

代码:

创建管理类workerManager:

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

workerManager.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#pragma once//防止头文件重复包含
#include<iostream>
using namespace std;
#include"worker.h"
#include"employee.h"
#include"manager.h"
#include"boss.h"
#include<fstream>
#define FILENAME "empFile.txt"
class WorkerManager
{
public:
WorkerManager();
void Show_Menu();//展示菜单
void ExitSystem();//退出系统
int m_EmpNum;//职工人数
Worker** m_EmpArray;//职工数组指针
//添加职工
void Add_Emp();
//保存文件
void save();

bool m_FileIsEmpty;
//统计文件人数
int get_EmpNum();
void init_Emp();
//显示职工
void Show_Emp();
//删除职工
void Del_Emp();
//判断职工是否存在
int IsExist(int id);
//修改职工
void Mod_Emp();
//查找职工
void Find_Emp();
//排序
void Sort_Emp();
//清空文件
void Clean_File();
~WorkerManager();

};

workerManager.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
#include"workerManager.h"
WorkerManager::WorkerManager()
{

//1.文件不存在
ifstream ifs;
ifs.open(FILENAME, ios::in);
if (!ifs.is_open())
{
cout << "文件不存在" << endl;
//初始化属性
this->m_EmpNum = 0;
this->m_EmpArray = NULL;
this->m_FileIsEmpty = true;
ifs.close();
return;
}//文件存在但为空
char ch;
ifs >> ch;
if (ifs.eof())
{
//文件为空
cout << "文件为空" << endl;
//初始化属性
this->m_EmpNum = 0;
this->m_EmpArray = NULL;
this->m_FileIsEmpty = true;
ifs.close();
return;
}
//3.文件存在并不为空
int num = this->get_EmpNum();
cout << "职工人数为:" << num << endl;
this->m_EmpNum = num;

this->m_EmpArray = new Worker * [this->m_EmpNum];//开辟空间
this->init_Emp();//将文件中的数据存储到数组中
//测试代码
/*for (int i = 0; i < this->m_EmpNum; i++)
{
cout << "职工编号:" << this->m_EmpArray[i]->m_Id
<< "姓名:" << this->m_EmpArray[i]->m_Name
<< "部门编号:" << this->m_EmpArray[i]->m_DeptId << endl;
}*/
}
void WorkerManager::Show_Menu()
{
cout << "******************************" << endl;
cout << "**** 欢迎使用职工管理系统 ****" << endl;
cout << "******* 0.退出管理系统 *******" << endl;
cout << "******* 1.添加职工信息 *******" << endl;
cout << "******* 2.显示职工信息 *******" << endl;
cout << "******* 3.删除离职职工 *******" << endl;
cout << "******* 4.修改职工信息 *******" << endl;
cout << "******* 5.查找职工信息 *******" << endl;
cout << "******* 6.按照编号排序 *******" << endl;
cout << "******* 7.清空所用文档 *******" << endl;
cout << "******************************" << endl;
cout << endl;
}
void WorkerManager::ExitSystem()
{
cout << "欢迎下次使用" << endl;
system("pause");
exit(0);
}
//添加职工
void WorkerManager::Add_Emp()
{
cout << "请输入添加职工数量:" << endl;
int addNum = 0;
cin >> addNum;
if (addNum>0)
{
//添加
//计算添加空间大小
int newSize = this->m_EmpNum + addNum;
//开辟新空间
Worker** newSpace = new Worker * [newSize];
//将原来空间数据拷贝到新空间
if (this->m_EmpArray!=NULL)
{
for (int i = 0; i < this->m_EmpNum; i++)
{
newSpace[i] = this->m_EmpArray[i];
}
}
//添加新数据
for (int i = 0; i < addNum; i++)
{
int id;
string name;
int dSelect;//部门选择
cout << "请输入第" << i + 1 << "个新职工编号:" << endl;
cin >> id;
cout << "请输入第" << i + 1 << "个新职工姓名:" << endl;
cin >> name;
cout << "请选择该职工岗位:" << endl;
cout << "1.普通员工" << endl;
cout << "2.经理" << endl;
cout << "3.老板" << endl;
cin >> dSelect;
Worker* worker = NULL;
switch (dSelect)
{
case 1:
worker = new Employee(id, name, 1);
break;
case 2:
worker = new Manager(id, name, 2);
break;
case 3:
worker = new Boss(id, name, 3);
break;
default:
break;
}
//将创建的职工指针保存到数组中
newSpace[this->m_EmpNum + i] = worker;
}
//释放原有空间
delete[] this->m_EmpArray;
//更改新空间指向
this->m_EmpArray = newSpace;
//更新新的职工人数
this->m_EmpNum = newSize;
//更新职工不为空
this->m_FileIsEmpty = false;
//成功添加后保存到文件中
cout << "成功添加" << addNum << "名新职工" << endl;
this->save();
}
else
{
cout << "输入有误" << endl;
}
system("pause");
system("cls");
}
//统计文件人数
int WorkerManager::get_EmpNum()
{
ifstream ifs;
ifs.open(FILENAME, ios::in);
int id;
string name;
int dId;
int num = 0;
while (ifs>>id&&ifs>>name&&ifs>>dId)
{
num++;
}
return num;
}
void WorkerManager::save()
{
ofstream ofs;
ofs.open(FILENAME, ios::out);
for (int i = 0; i < this->m_EmpNum; i++)
{
ofs << this->m_EmpArray[i]->m_Id << " "
<< this->m_EmpArray[i]->m_Name << " "
<< this->m_EmpArray[i]->m_DeptId << endl;
}
ofs.close();
}
void WorkerManager::init_Emp()
{
ifstream ifs;
ifs.open(FILENAME, ios::in);
int id;
string name;
int dId;
int index = 0;
while (ifs >> id && ifs >> name && ifs >> dId)
{
Worker* worker = NULL;
if (dId==1)
{
worker = new Employee(id, name, dId);
}else if (dId == 2)
{
worker = new Manager(id, name, dId);
}
else
{
worker = new Boss(id, name, dId);
}
this->m_EmpArray[index] = worker;
index++;
}
ifs.close();
}
//显示职工
void WorkerManager::Show_Emp()
{
//判断文件是否为空
if (this->m_FileIsEmpty)
{
cout << "文件不存在或者记录为空" << endl;
}
else
{
for (int i = 0; i < m_EmpNum; i++)
{
//利用多态调用程序接口
this->m_EmpArray[i]->showInfo();
}
}
system("pause");
system("cls");
}
//删除职工
void WorkerManager::Del_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或记录为空" << endl;
}
else
{
cout << "请输入想要删除职工的编号:" << endl;
int id = 0;
cin >> id;
int index = this->IsExist(id);
if (index!=-1)
{
//数据前移
for (int i = index; i < this->m_EmpNum-1; i++)
{
this->m_EmpArray[i] = this->m_EmpArray[i + 1];
}
this->m_EmpNum--;//更新人数
//更新到文件中
this->save();
cout << "删除成功" << endl;
}
else
{
cout << "未找到职工,删除失败" << endl;
}
}
system("pause");
system("cls");
}
//判断职工是否存在
int WorkerManager::IsExist(int id)
{
int index = -1;
for (int i = 0; i < this->m_EmpNum; i++)
{
if (this->m_EmpArray[i]->m_Id == id)
{
//找到职工
index = i;
break;
}
}
return index;
}
//修改职工
void WorkerManager::Mod_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或记录为空" << endl;
}
else
{
cout << "请输入要修改职工的编号:" << endl;
int id;
cin >> id;
int ret = this->IsExist(id);
if (ret!=-1)
{
delete this->m_EmpArray[ret];
int newId = 0;
string newName = "";
int dSelect = 0;
cout << "查找到" << id << "号职工,请输入新的职工号:" << endl;
cin >> newId;
cout << "请输入新的姓名:" << endl;
cin >> newName;
cout << "请输入新的岗位:" << endl;
cout << "1.普通员工" << endl;
cout << "2.经理" << endl;
cout << "3.老板" << endl;
cin >> dSelect;
Worker* worker = NULL;
switch (dSelect)
{
case 1:
worker = new Employee(newId, newName, dSelect);
break;
case 2:
worker = new Manager(newId, newName, dSelect);
break;
case 3:
worker = new Boss(newId, newName, dSelect);
break;
default:
break;
}
this->m_EmpArray[ret] = worker;
cout << "修改成功" << endl;
this->save();
}
else
{
cout << "查无此人,修改失败" << endl;
}
}
system("pause");
system("cls");
}
//查找职工
void WorkerManager::Find_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或记录为空" << endl;
}
else
{
cout << "请输入查找的方式:" << endl;
cout << "1.按职工编号查找" << endl;
cout << "2.按职工姓名查找" << endl;
int select = 0;
cin >> select;
if (select == 1)
{
int id;
cout << "请输入查找的职工编号:" << endl;
cin >> id;
int ret = IsExist(id);
if (ret != -1)
{
cout << "查找成功!该职工信息如下:" << endl;
this->m_EmpArray[ret]->showInfo();
}
else {
cout << "查无此人" << endl;
}
}
else if (select == 2)
{
string name;
cout << "请输入查找的职工姓名:" << endl;
cin >> name;
bool flag = false;//添加是否查到的标志
for (int i = 0; i < m_EmpNum; i++)
{
if (this->m_EmpArray[i]->m_Name==name)
{
cout << "查找成功!职工编号为:"
<< this->m_EmpArray[i]->m_Id
<< "号职工信息如下" << endl;
flag = true;
this->m_EmpArray[i]->showInfo();
}
}
if (flag == false)
{
cout << "查找失败,查无此人" << endl;
}
}
else
{
cout << "输入有误" << endl;
}
}
system("pause");
system("cls");
}
//排序
void WorkerManager::Sort_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或记录为空" << endl;
system("pause");
system("cls");
}
else
{
cout << "请选择排序方式" << endl;
cout << "1.按照职工号升序" << endl;
cout << "2.按照职工号降序" << endl;
int select = 0;
cin >> select;
for (int i = 0; i < m_EmpNum; i++)
{
int minOrMax = i;//声明最小或最大值下标
for (int j = i+1; j < this->m_EmpNum; j++)
{
if (select == 1)
{
if (this->m_EmpArray[minOrMax]->m_Id > this->m_EmpArray[j]->m_Id)
{
minOrMax = j;
}
}
else if (select == 2)
{
if (this->m_EmpArray[minOrMax]->m_Id < this->m_EmpArray[j]->m_Id)
{
minOrMax = j;
}
}
else
{
cout << "输入有误" << endl;
}
}
if (i!=minOrMax)
{
Worker* temp = this->m_EmpArray[i];
this->m_EmpArray[i] = this->m_EmpArray[minOrMax];
this->m_EmpArray[minOrMax] = temp;
}
}
cout << "排序成功,排序后的结果为:" << endl;
this->save();
this->Show_Emp();//展示职工
}
}
//清空文件
void WorkerManager::Clean_File()
{
cout << "确认清空?" << endl;
cout << "1.确定" << endl;
cout << "2.返回" << endl;
int select = 0;
cin >> select;
if (select == 1)
{
ofstream ofs(FILENAME,ios::trunc);//删除文件后重新创建
ofs.close();
if (this->m_EmpArray != NULL)
{
for (int i = 0; i < this->m_EmpNum; i++)
{
//删除堆区每个对象
delete this->m_EmpArray[i];
this->m_EmpArray[i] = NULL;
}//删除堆区数组指针
delete[] this->m_EmpArray;
this->m_EmpArray = NULL;
this->m_EmpNum = 0;
this->m_FileIsEmpty = true;
}
cout << "清空成功" << endl;
}
system("pause");
system("cls");
}
WorkerManager::~WorkerManager()
{
if (this->m_EmpArray!=NULL)
{
for (int i = 0; i < this->m_EmpNum; i++)
{
if (this->m_EmpArray != NULL)
{
delete this->m_EmpArray[i];
}
}
delete[] this->m_EmpArray;
this->m_EmpArray = NULL;
}
}

创建抽象职工类:

职工的分类:普通职工、经理、老板
将三种职工抽象到一个类(worker)中,利用多态管理不同职工种类
职工的属性:职工编号、职工姓名、职工所在部门编号
职工的行为:岗位职责描述,获取岗位名称

worker.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma once
#include<iostream>
using namespace std;
#include<string>
class Worker//职工抽象类
{
public:
//显示个人信息
virtual void showInfo() = 0;

//获取岗位名称
virtual string getDeptName() = 0;
int m_Id;
string m_Name;
int m_DeptId;
};

老板类:

boss.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma once
#include<iostream>
using namespace std;
#include"worker.h"
//老板类
class Boss :public Worker
{
public:
//构造函数
Boss(int id, string name, int dId);
//显示个人信息
virtual void showInfo();

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

boss.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include"boss.h"
//构造函数
Boss::Boss(int id, string name, int dId)
{
this->m_Id = id;
this->m_Name = name;
this->m_DeptId = dId;
}
//显示个人信息
void Boss::showInfo()
{
cout << "职工编号:" << this->m_Id
<< "\t\t职工姓名:" << this->m_Name
<< "\t\t岗位:" << this->getDeptName()
<< "\t\t岗位职责: 管理公司所有事务" << endl;
}

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

普通员工类:

employee.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//普通员工
#pragma once
#include<iostream>
using namespace std;
#include"worker.h"
class Employee :public Worker
{
public:
//构造函数
Employee(int id,string name,int dId);
//显示个人信息
virtual void showInfo();

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

employee.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include"employee.h"
//构造函数
Employee::Employee(int id, string name, int dId)
{
this->m_Id = id;
this->m_Name = name;
this->m_DeptId = dId;
}
//显示个人信息
void Employee::showInfo()
{
cout << "职工编号:" << this->m_Id
<< "\t\t职工姓名:" << this->m_Name
<< "\t\t岗位:" << this->getDeptName()
<< "\t\t岗位职责: 完成经理交给的任务" << endl;
}

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

经理类:

manager.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma once
#include<iostream>
using namespace std;
#include"worker.h"
//经理类
class Manager:public Worker
{
public:
//构造函数
Manager(int id, string name, int dId);
//显示个人信息
virtual void showInfo();

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

manager.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include"manager.h"
//构造函数
Manager::Manager(int id, string name, int dId)
{
this->m_Id = id;
this->m_Name = name;
this->m_DeptId = dId;
}
//显示个人信息
void Manager::showInfo()
{
cout << "职工编号:" << this->m_Id
<< "\t\t职工姓名:" << this->m_Name
<< "\t\t岗位:" << this->getDeptName()
<< "\t\t岗位职责: 完成老板交给的任务,并下发任务给员工" << endl;
}

//获取岗位名称
string Manager::getDeptName()
{
return string("经理");
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<iostream>
using namespace std;
#include"workerManager.h"
#include"worker.h";
#include"employee.h"
#include"manager.h"
#include"boss.h"
int main()
{
//多态测试代码
/*
Worker* worker = NULL;
worker = new Employee(1, "张三", 1);
worker->showInfo();
worker = new Manager(2, "李四", 2);
worker->showInfo();
worker = new Boss(3, "王五", 3);
worker->showInfo();
*/

//实例化对象
WorkerManager wm;
int choice = 0;
while (true)
{
wm.Show_Menu();
cout << "请输入你的选择:" << endl;
cin >> choice;
switch (choice)
{
case 0://退出系统
wm.ExitSystem();
break;
case 1://添加职工信息
wm.Add_Emp();
break;
case 2://显示职工信息
wm.Show_Emp();
break;
case 3://删除离职职工
wm.Del_Emp();
break;
case 4://修改职工信息
wm.Mod_Emp();
break;
case 5://查找职工信息
wm.Find_Emp();
break;
case 6://按照编号排序
wm.Sort_Emp();
break;
case 7://清空所用文档
wm.Clean_File();
break;
default:
system("cls");
break;
}
}
system("pause");
return 0;
}

3 C++提高编程

泛型编程STL

3.1 模板

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

特点:

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

3.1.1 函数模板

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

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

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

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

注意事项:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<iostream>
using namespace std;
//函数模板
//1.如果函数模板和普通函数都可以调用,优先调用普通函数
//2.可以通过空模板参数列表(mySwap<>(a,b))强制调用函数模板
//3.函数模板可以发生函数重载
//4.如果函数模板可以发生更好的匹配,优先调用函数模板
template<typename T>//声明一个模板,T为通用数据类型
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
template<class T>
void func()
{
cout << "func调用" << endl;
}
void swapInt(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
void swapDouble(double& a, double& b)
{
double temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 10;
int b = 20;
//swapInt(a, b);
//使用
//1.自动类型推导 必须推导出一致的数据类型T才能使用 不可以发生隐式类型转换
//mySwap(a, b);
//2.显示指定类型 可以发生隐式类型转换
mySwap<int>(a, b);
cout << "a=" << a << endl;
cout << "b=" << b << endl;
double c = 1.1;
double d = 2.2;
swapDouble(c, d);
cout << "c=" << c << endl;
cout << "d=" << d << endl;
func<int>();
}
int main()
{
test01();
system("pause");
return 0;
}

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

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

建议使用显式指定类型

普通函数与函数模板调用规则:
1.如果函数模板和普通函数都可以实现,优先调用普通函数
2.可以通过空模板参数列表来强制调用函数模板 func<>();
3.函数模板也可以发生重载
4.如果函数模板可以产生更好的匹配,优先调用函数模板

模板局限性:
传入参数为数组或者自定义数据类型,就无法正常使用
解决方法:用特定数据类型提供具体化操作 重载 template<> bool myCompare(Person &p1,Person &p2){函数实现}

3.1.2 类模板

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

语法:
template<typename T> 类

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
using namespace std;
#include<string>
//类模板
//1.没用自动类型推导使用方式
//2.类模板在模板参数列表中可以有默认参数
//类模板成员函数在调用时创建
template<class NameType,class AgeType>
class Person
{
public:
Person(NameType name,AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
void showPerson()
{
cout << "name:" << this->m_Name << "age:" << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
Person<string, int>p1("张三", 78);
p1.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}

类模板和函数模板区别:
1.类模板没有自动类型推导的使用方式,只能用显式指定类型
2.类模板在模板参数列表中可以有默认参数

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

  • 普通类中成员函数一开始就创建
  • 类模板中成员函数在调用时才创建

类模板对象做函数参数
三种传入方式:
1.指定传入的类型 --直接显示对象的数据类型
2.参数模板化 --将对象中的参数变为模板进行传递
3.整个类模板化 --将这个对象类型模板化进行传递

代码:

分文件编写:
问题:
类模板中成员函数创建时期是在调用阶段,导致文件编写时调用不到
解决方法:
1.直接包含.cpp源文件

person.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include"person.h"
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;
}

person.h

1
2
3
4
5
6
7
8
9
10
11
12
#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;
};

2.将声明和实现写在同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

person.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#pragma once
#include<iostream>
using namespace std;
#include<string>
//类模板对象做函数参数
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
/*{
this->m_Name = name;
this->m_Age = age;
}*/
void showPerson();
/*{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}*/
T1 m_Name;
T2 m_Age;
};
template<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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include<iostream>
using namespace std;
//1.
//#include"person.cpp"
//2.
#include"person.hpp"
//#include<string>
////类模板对象做函数参数
//template<class T1,class T2>
//class Person
//{
//public:
// Person(T1 name, T2 age);
// /*{
// this->m_Name = name;
// this->m_Age = age;
// }*/
// void showPerson();
// /*{
// cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
// }*/
// T1 m_Name;
// T2 m_Age;
//};
//构造函数类外实现
//template<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;
//}
//1.指定传入类型 <最常用>
void printPerson1(Person<string ,int>&p)
{
p.showPerson();
}
void test01()
{
Person<string, int>p("张三", 78);
printPerson1(p);
}

//2.参数模板化
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("李四", 98);
printPerson2(p);
}
//3.整个类模板化
template<class T>
void printPerson3(T& p)
{
p.showPerson();
cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03()
{
Person<string, int>p("王五", 8);
printPerson3(p);
}
int main()
{
test03();
system("pause");
return 0;
}

查看数据类型:typeid(T).name()

类模板与继承:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<iostream>
using namespace std;
//类模板与继承
template<class T>
class Base
{
T m;
};
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 test01()
{
Son2<int, char>s2;
}
int main()
{
test01();
system("pause");
return 0;
}

类模板成员函数类外实现

  • 加模板参数列表和作用域

类模板和友元:

  • 全局函数类内实现 --直接在类内声明友元就行了
  • 全局函数类外实现 --需要提前让编译器知道全局函数的存在

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<iostream>
using namespace std;
#include<string>
//类模板与友元
//提前让编译器知道Person类存在
template<class T1, class T2>
class Person;
//通过全局函数 打印Person信息
template<class T1, class T2>
void print2(Person<T1, T2> p)
{
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}
template<class T1,class T2>
class Person
{
//全局函数 类内实现
friend void print1(Person<T1,T2> p)
{
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}
//全局函数 类外实现
//空模板参数列表
//如果全局函数是类外实现,需要让编译器提前知道这个函数的存在
friend void print2<>(Person<T1, T2> p);
public:
Person(T1 name, T2 age);
{
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};

void test01()
{
Person<string, int>p("Tom", 20);
print2(p);
}
int main()
{
test01();
system("pause");
return 0;
}
案例: 类模板案例

案例描述:

  • 可以对内置函数类型以及自定义数据类型进行存储
  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//自己的通用的数组类
#pragma once
#include<iostream>
using namespace std;
template<class T>
class MyArray
{
public:
//有参构造 参数 容量
MyArray(int capacity)
{
//cout << "MyArray的有参构造" << endl;
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
//拷贝构造
MyArray(const MyArray& arr)
{
//cout << "MyArray的拷贝构造" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//this->pAddress = 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=防止浅拷贝问题 a=b=c
MyArray& operator=(const MyArray& arr)
{
//cout << "MyArray的operator=" << endl;
//先判断原来堆区是否有数据,如果有先释放
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Capacity = 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--;
}
//通过下标访问

T& operator[](int index)
{
return this->pAddress[index];
}
//返回数组容量
int getCapacity()
{
return this->m_Capacity;
}

//返回数组大小
int getSize()
{
return this->m_Size;
}

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

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<iostream>
using namespace std;
#include"MyArray.hpp"

void printArray(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;
printArray(arr1);
cout << "arr1容量:" << arr1.getCapacity() << " 大小:" << arr1.getSize() << endl;
MyArray <int>arr2(arr1);
arr2.Pop_Back();
cout << "arr2:" << endl;
printArray(arr2);
cout << "arr2容量:" << arr2.getCapacity() << " 大小:" << arr2.getSize() << endl;
//MyArray<int>arr3(100);
//arr3 = arr1;
}
//测试自定义数据类型
class Person
{
public:
Person() {};
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;

};
void printPersonArray(MyArray<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("韩信", 20);
Person p3("李白", 25);
Person p4("安其拉", 18);
Person p5("蔡文姬", 12);
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() << " 大小:" << arr.getSize() << endl;
}
int main() {
Test02();
system("pause");
return 0;
}

3.2 STL

STL(标准模块库)
从广义上分为:容器算法迭代器
容器和算法间通过迭代器进行无缝连接

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

3.2.1 容器算法迭代器

容器:置物之所也
STL容器就是运用最广泛的一些数据结构实现出来
常见的数据结构:数组、链表、树、栈、队列、集合、映射表 等
分类:

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

算法:问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法
分类:

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

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

迭代器种类:

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

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

vector存放内置数据类型

容器: vector
算法: for_each
迭代器: vector<int>::iterator

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include<iostream>
#include<vector>
#include<algorithm>//标准算法头文件
using namespace std;
//STL六大组件:容器、算法、迭代器、仿函数、适配器(配接器)、空间配装器

//vector容器存放内置数据类型
void MyPrint(int val)
{
cout << val << endl;
}
void test01()
{
vector<int> v;
//向容器中插入数据
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
//通过迭代器访问数据
vector<int>::iterator itBegin = v.begin();//起始迭代器,指向容器中第一个元素
vector<int>::iterator itEnd = v.end();//结束迭代器,指向容器最后一个元素的下一个位置
//第一种遍历方式
while (itBegin!=itEnd)
{
cout << *itBegin << endl;
itBegin++;
}
//第二种
for (vector<int>::iterator it = v.begin();it != v.end();it++)
{
cout << *it << endl;
}
//第三种 STL(for_each)包含头文件#include<algorithm>
for_each(v.begin(), v.end(), MyPrint);
}
//容器嵌套容器
void test02()
{
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()
{
test02();
system("pause");
return 0;
}

vector也可以存放自定义数据类型

3.2.2 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初始化
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& assign(const string &s); //把字符串s赋给当前字符串
  • string& assign(int n,char c); //把n个字符c赋给当前字符串

operator=方式比较实用

string字符串拼接

功能:实现在字符串末尾拼接字符串

函数原型:

  • string& operator+=(const char* str); //重载+=操作符
  • string& operator+=(const char c); //重载+=操作符
  • string& operator+=(const string& str); //重载+=操作符
  • string& append(const char *s); //把字符串s拼接到当前字符串结尾
  • string& append(const char *s,int n); //把字符串s的前n个字符连接到当前的字符串结尾
  • string& append(const string &s); //同operator+=(const string& str)
  • string& append(const string &s,int pos,int n); //字符串s中从pos开始的n个字符连接到字符串结尾
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
string字符串比较

功能:字符串之间的比较

比较方式:
字符串比较是按字符的ASCII码进行对比

  • = 返回 0
  • 返回 1

  • < 返回 -1

函数原型:

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

主要用于比较俩个字符串是否相等

string字符存取

存取方式:

  • char& operator[](int n); //通过[]方式取字符
  • char& at(int n); //通过at方法获取字符
string插入和删除

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

函数原型:

  • string& insert(int pos,const char* s); //插入字符串
  • string& insert(int pos,const string& str); //插入字符串
  • string& insert(int pos,int n,char c); //在指定位置插入n个字符c
  • string& erase(int pos,int n = npos); //删除从pos开始的n个字符

插入和删除下标从0开始

string子串

功能:从字符串中获取想要的子串

函数原型:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#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;
}
//string 赋值操作
void test02()
{
string str1;
str1 = "hello world";
cout << "str1=" << str1 << endl;

string str2;
str2 = str1;
cout << "str2=" << str2 << endl;

string str3;
str3 = 's';
cout << "str3=" << str3 << endl;

string str4;
str4.assign("hello c++");
cout << "str4=" << str4 << endl;

string str5;
str5.assign("hello c++",5);
cout << "str5=" << str5 << endl;

string str6;
str6.assign(str5);
cout << "str6=" << str6 << endl;

string str7;
str7.assign(10, 'w');
cout << "str7=" << str7 << endl;
}
//string字符串拼接
void test03()
{
string str1 = "我";
str1 += "爱学习";
cout << "str1=" << str1 << endl;
str1 += ':';
cout << "str1=" << str1 << endl;
string str2=" java,python";
str1 += str2;
cout << "str1=" << str1 << endl;
string str3 = "I";
str3.append(" Love");
cout << "str3=" << str3 << endl;
str3.append(" You,",4);
cout << "str3=" << str3 << endl;
//str3.append(str2);
str3.append(str2, 0, 5);//第二个为开始位置(从0开始)第三个参数为截取长度
cout << "str3=" << str3 << endl;
}
//string字符串查找
void test04()
{
string str1 = "abcdefgde";
int pos=str1.find("de");
cout << "find:pos=" << pos << endl;
//rfind和find区别
//rfind从右往左查找,find从左往右查找
pos = str1.rfind("de");
cout << "rfind:pos=" << pos << endl;
}
//字符串替换
void test05()
{
string str1 = "abcdefg";
//从1号位置(从0开始)起,3个字符替换为“1111”
str1.replace(1, 3, "1111");
cout << "str1=" << str1 << endl;//a1111efg
}
//字符串比较
void test06()
{
string str1 = "xbcd";
string str2 = "abcd";
//主要用于比较是否相等
if (str1.compare(str2) == 0)
{
cout << "str1=str2" << endl;
}
else if (str1.compare(str2) > 0)
{
cout << "str1>str2" << endl;
}
else
{
cout << "str1<str2" << endl;
}
}
//字符存取
void test07()
{
string str1 = "hello";
str1[0] = 'x';
str1.at(1) = 'x';
//1.通过[]访问单个字符
for (int i = 0;i < str1.size();i++)
{
cout << str1[i] << " ";
}
cout << endl;
//2.通过at方式访问单个字符
for (int i = 0;i < str1.size();i++)
{
cout << str1.at(i) << " ";
}
cout << endl;
}
//字符串插入和删除
void test08()
{
string str1 = "hello";
//插入
str1.insert(1, "111");
cout << "str1=" << str1 << endl;
//删除
str1.erase(1, 3);//第一个参数:从当前位置起,第二个参数:删除多少个
cout << "str1=" << str1 << endl;
}
//string 子串
void test09()
{
string str = "abcdefg";
string subStr = str.substr(1, 3);//第一个参数:从当前位置起,第二个参数:截取多少个
cout << "subStr=" << subStr << endl;
/*
string email = "xxx@qq.com";
int pos = email.find("@");
string username = email.substr(0,pos);
*/
}
int main()
{
test09();
system("pause");
}

3.2.3 vector容器

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

vector与普通数组区别: 不同之处在于数组是静态空间,而vector可以动态扩展

动态扩展:

  • 并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间
  • vector容器的迭代器是支持随机访问的迭代器
vector构造函数

功能:创建vector容器

函数原型:

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

功能:给vector容器进行赋值

函数原型:

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

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

函数原型:

  • empty(); //判断容器是否为空
  • capacity(); //容器的容量
  • size(); //返回容器中元素的个数
  • resize(int num); //重新指定容器的长度为num。若容器变长,则以默认值填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
  • resize(int num,elem); //重新指定容器的长度为num。若容器变长,则以elem填充新位置;如果容器变短,则末尾超出容器长度的元素被删除
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(); //删除容器中所有元素
vector数据读取

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

函数原型:

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

功能:实现俩个容器中元素进行互换

函数原型:

  • swap(vec); //将vec与本身元素互换
vector预留空间

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

函数原型:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#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;
}
//vector容器构造
void test01()
{
vector<int>v1;//默认构造 无参构造
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
printVector(v1);
//通过区间方式进行构造
vector<int>v2(v1.begin(), v1.end());
printVector(v2);
//n个elem方式构造
vector<int>v3(10, 100);
printVector(v3);
//拷贝构造
vector<int>v4(v3);
printVector(v4);
}
//vector赋值操作
void test02()
{
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);
}
//vector容量和大小
void test03()
{
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不为空" << " 容量为 " << v1.capacity() << " 大小 " << v1.size() << endl;
}
//重新指定大小
v1.resize(15,100);//默认以0填充 这里指定为100
printVector(v1);
v1.resize(5);
printVector(v1);//超出容器长度的元素被删除
}
//vector插入和删除
void test04()
{
vector<int>v1;
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
v1.push_back(40);
v1.push_back(50);
//遍历
printVector(v1);
//尾删
v1.pop_back();
printVector(v1);
//插入
v1.insert(v1.begin(), 100);
printVector(v1);

v1.insert(v1.begin(), 2, 1000);
printVector(v1);

//删除 参数也是迭代器
v1.erase(v1.begin());
printVector(v1);
//清空
//v1.erase(v1.begin(), v1.end());
//printVector(v1);
v1.clear();
printVector(v1);
}
//vector数据读取
void test05()
{
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;
}
//vector互换容器
//1.基本使用
void test06()
{
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);
}
//2.实际用途
//巧用swap可以收缩内存空间
void test07()
{
vector<int>v;
for (int i = 0; i < 100000; i++)
{
v.push_back(i);
}
cout << "v 的容量:" << v.capacity() << endl;
cout << "v 的大小:" << v.size() << endl;
v.resize(3);//重新指定大小
cout << "v 的容量:" << v.capacity() << endl;
cout << "v 的大小:" << v.size() << endl;
//巧用swap可以收缩内存空间
vector<int>(v).swap(v);
cout << "v 的容量:" << v.capacity() << endl;
cout << "v 的大小:" << v.size() << endl;
}
//vector预留空间
void test08()
{
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 << endl;
}
int main()
{
test08();
system("pause");
return 0;
}

3.2.4 deque容器

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

deque与vector区别:

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

deque内部工作原理:
deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据
中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间

  • deque容器的迭代器也是支持随机访问的
deque构造函数

功能:deque容器构造

函数原型:

  • deque<T>deqT; //默认构造形式
  • deque(beg,end); //构造函数将[beg,end)区间中的元素拷贝给本身
  • deque(n,elem); //构造函数将n个elem拷贝给本身
  • deque(const deque &deq); //拷贝构造函数
deque赋值操作

功能:给deque容器赋值

函数原型:

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

功能:对deque容器的大小进行操作

函数原型:

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

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

函数原型:

俩端插入操作:

  • push_back(elem); //在容器尾部插入一个数据
  • push_front(elem); //在容器头部插入一个数据
  • pop_back(); //删除容器最后一个数据
  • pop_front(); //删除容器第一个数据

指定位置操作:

  • insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置
  • insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值
  • insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值
  • clear(); //删除容器中所有数据
  • erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置
  • erase(pos); //删除pos位置的数据,返回下一个数据的位置
deque数据存取

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

函数原型:

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

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

算法:

  • sort(iterator beg,iterator end) //对beg和end区间内元素进行排序

对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其进行排序

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#include<iostream>
using namespace std;
#include<deque>
#include<algorithm>//标准算法头文件
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(d1.begin(),d1.end());
printDeque(d2);
deque<int>d3(10,100);
printDeque(d3);
deque<int>d4(d3);
printDeque(d4);
}
//deque赋值操作
void test02()
{
deque<int>d1;
for (int i = 0;i < 10;i++)
{
d1.push_back(i);
}
printDeque(d1);
//operator=赋值
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大小操作
void test03()
{
deque<int>d1;
for (int i = 0;i < 10;i++)
{
d1.push_back(i);
}
printDeque(d1);
if (d1.empty())
{
cout << "d1为空" << endl;
}
else
{
cout << "d1不为空" << endl;
cout << "d1大小为:" << d1.size() << endl;//没有容量概念
}
//重新指定大小
d1.resize(15,1);
printDeque(d1);//默认以0填充

d1.resize(5);
printDeque(d1);
}
//deque插入和删除
void test04()
{
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
//尾删
d1.pop_back();
printDeque(d1);
//头删
d1.pop_front();
printDeque(d1);
}
//指定位置插入和删除
void test05()
{
deque<int>d1;
//尾插
d1.push_back(10);
d1.push_back(20);
//头插
d1.push_front(100);
d1.push_front(200);
printDeque(d1);
//insert 插入
d1.insert(d1.begin(), 1000);
printDeque(d1);

d1.insert(d1.begin(), 2, 10000);
printDeque(d1);

//按照区间插入
deque<int>d2;
d2.push_back(1);
d2.push_back(2);
d2.push_back(3);

d1.insert(d1.begin(), d2.begin(), d2.end());
printDeque(d1);

//删除
deque<int>::iterator it = d1.begin();
it++;//第二个元素
d1.erase(it);
printDeque(d1);

//清空
//d1.erase(d1.begin(), d1.end());
d1.clear();
printDeque(d1);
}
//deque数据存取
void test06()
{
deque<int>d1;
//尾插
d1.push_back(10);
d1.push_back(20);
d1.push_back(30);
//头插
d1.push_front(100);
d1.push_front(200);
d1.push_front(300);
//通过[]方式访问元素
for (int i = 0;i < d1.size();i++)
{
cout << d1[i] << " ";
}
cout << endl;
//通过at方式访问元素
for (int i = 0;i < d1.size();i++)
{
cout << d1.at(i) << " ";
}
cout << endl;
cout << "第一个元素:" << d1.front() << endl;
cout << "最后一个元素:" << d1.back() << endl;
}
//deque排序
void test07()
{
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);
printDeque(d);

//排序 #include<algorithm>//标准算法头文件
sort(d.begin(), d.end());
cout << "排序后:" << endl;
printDeque(d);
}
int main()
{
test07();
system("pause");
return 0;
}
案例: 评委打分

案例描述:
有5名选手:选手ABCDE,10个评委分别对每一个选手打分,去除最高分,去除评委最低分,求平均分

实现步骤:
1.创建五名选手,放到vector中
2.遍历vector容器,取出每一位选手,执行for循环,可以把10个评分分存到deque容器中
3.sort算法对deque容器中的分数进行排序,取出最高和最低分
4.deque容器遍历一遍,累加总分
5.获取平均分

代码

3.2.5 stack容器

stack是一种先进后出的数据结构,它只有一个出口

栈中只有顶端元素才可以被访问,因此不允许有遍历的行为

  • 入栈: push
  • 出栈: pop
常用接口

构造函数:

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

赋值操作:

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

数据存取:

  • push(elem); //向栈顶添加元素
  • pop(); //从栈顶移除第一个元素
  • top(); //返回栈顶元素

大小操作:

  • empty(); //判断栈是否为空
  • size(); //返回栈的大小

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;
#include<stack>
void test01()
{
stack<int>s;
//入栈
s.push(10);
s.push(20);
s.push(30);
s.push(40);
if (!s.empty())
{
cout << "栈顶元素: " << s.top() << endl;
s.pop();//出栈
}
cout << "栈的大小: " << s.size() << endl;
}
int main()
{
test01();
system("pause");
return 0;
}

3.2.6 queue容器

queue是一种先进先出的数据结构,他有2个出口

队列容器只允许从一端新增数据,从另一端移除数据

队列中只有队头元素和队尾元才可以被访问,因此不允许有遍历的行为

  • 入队: push
  • 出队: pop
常用接口

构造函数:

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

赋值操作:

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

数据存取:

  • push(elem); //向队尾添加元素
  • pop(); //从队头移除第一个元素
  • back(); //返回最后一个元素
  • front(); //返回第一个元素

大小操作:

  • empty(); //判断队列是否为空
  • size(); //返回队列的大小

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<iostream>
using namespace std;
#include<queue>
void test01()
{
queue<int>q;
//入队
q.push(10);
q.push(20);
q.push(30);
q.push(40);
if (!q.empty())
{
cout << "第一个元素: " << q.front() << endl;
cout << "最后一个元素: " << q.back() << endl;
q.pop();//出队
cout << "第一个元素: " << q.front() << endl;
cout << "最后一个元素: " << q.back() << endl;
}
cout << "队列的大小: " << q.size() << endl;
}
int main()
{
test01();
system("pause");
return 0;
}

3.2.7 list容器

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

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

  • 优点:可以对任意位置快速插入和删除
  • 缺点:遍历速度没有数组快,占用空间比数组大

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

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

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

list中的迭代器只支持前移和后移,属于双向迭代器

list优点:

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

list缺点:

  • 空间(指针域)和时间(遍历)耗费较大

插入和删除操作都不会造成原有list迭代器的失效,这在vector中是不成立的

STL中List和vector是最常使用的容器

list构造函数

功能:创建list容器

函数原型:

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

功能:给list容器进行赋值,以及交换list容器

函数原型:

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

功能:对list容器的大小进行操作

函数原型:

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

功能:对list容器数据进行插入、删除操作

函数原型:

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

功能:对list中的数据进行存取

函数原型:

  • front(); //返回第一个元素
  • back(); //返回最后一个元素
list反转和排序

功能:将容器中的元素反转,以及将容器中的数据进行排序

函数原型:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#include<iostream>
using namespace std;
#include<list>

void printList(const list<int>& l)
{
for (list<int>::const_iterator it = l.begin();it != l.end();it++)
{
cout << *it << " ";
}
cout << endl;
}
void test01()
{
//创建list容器
list<int>l1;
//添加数据
l1.push_back(10);
l1.push_back(20);
l1.push_back(30);
l1.push_back(40);
//遍历容器
printList(l1);
//区间方式构造
list<int>l2 (l1.begin(), l1.end());
printList(l2);
//拷贝构造
list<int>l3(l2);
printList(l3);
//n个elem
list<int>l4(10, 1000);
printList(l4);
}
//list赋值和交换
void test02()
{
//创建list容器
list<int>l1;
//添加数据
l1.push_back(10);
l1.push_back(20);
l1.push_back(30);
l1.push_back(40);
//遍历容器
printList(l1);
list<int>l2;
l2 = l1;
printList(l2);

list<int>l3;
l3.assign(l2.begin(), l2.end());
printList(l3);

list<int>l4;
l4.assign(10, 1000);
printList(l4);
}
//交换
void test03()
{
//创建list容器
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, 1000);
cout << "交换前:" << endl;
printList(l1);
printList(l2);
cout << endl;
l1.swap(l2);
cout << "交换后:" << endl;
printList(l1);
printList(l2);
}
//list大小操作
void test04()
{
//创建list容器
list<int>l1;
//添加数据
l1.push_back(10);
l1.push_back(20);
l1.push_back(30);
l1.push_back(40);
if (l1.empty())
{
cout << "l1为空" << endl;
}
else
{
cout << "l1不为空" << endl;
cout << "l1的大小为:" << l1.size() << endl;
}
//重新指定大小
l1.resize(10);
printList(l1);
l1.resize(2);
printList(l1);
}
//list插入和删除
void test05()
{
//创建list容器
list<int>l1;
//尾插
l1.push_back(10);
l1.push_back(20);
l1.push_back(30);
//头插
l1.push_front(100);
l1.push_front(200);
l1.push_front(300);
printList(l1);

//尾删
l1.pop_back();
printList(l1);
//头删
l1.pop_front();
printList(l1);

//插入
list<int>::iterator it = l1.begin();
l1.insert(++it, 1000);
printList(l1);

//删除
it = l1.begin();
l1.erase(++it);//删除第二个元素
printList(l1);

//移除
l1.push_back(10000);
l1.push_back(10000);
l1.push_back(10000);
printList(l1);
l1.remove(10000);
printList(l1);

//清空
l1.clear();
printList(l1);
}
//list数据存取
void test06()
{
//创建list容器
list<int>l1;
//尾插
l1.push_back(10);
l1.push_back(20);
l1.push_back(30);
l1.push_back(40);
//cout<<l1.at(0)<<endl;//错误 不支持at访问数据
//cout<<l1[0]<<endl;//错误 不支持[]访问数据
cout << "第一个元素:" << l1.front() << endl;
cout << "最后一个元素:" << l1.back() << endl;
//list容器的迭代器是双向迭代器,不支持随机访问
list<int>::iterator it = l1.begin();
//it = it + 1;//错误 不可以跳跃访问 即使是+1
}
//list反转和排序
bool myCompare(int val1, int val2)
{
return val1 > val2;
}
void test07()
{
//创建list容器
list<int>l1;
//尾插
l1.push_back(90);
l1.push_back(30);
l1.push_back(20);
l1.push_back(70);
printList(l1);
//反转
l1.reverse();
printList(l1);
//排序
l1.sort();//默认排序规则 从小到大
printList(l1);

l1.sort(myCompare);//指定规则 从大到小
printList(l1);

}
int main()
{
test07();
system("pause");
return 0;
}
案例: list容器排序

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<iostream>
using namespace std;
#include<list>
class Person
{
public:
Person(string name, int age, int height)
{
this->m_Name = name;
this->m_Age = age;
this->m_Height = height;
}
string m_Name;
int m_Age;
int m_Height;
};
//指定排序规则
bool comparePerson(Person &p1,Person &p2)
{
if (p1.m_Age == p2.m_Age)
{
//年龄相同 身高降序
return p1.m_Height > p2.m_Height;
}
else
{
//按照年龄升序
return p1.m_Age < p2.m_Age;
}
}
void test01()
{
list<Person>L;
//准备数据
Person p1("刘备", 35, 175);
Person p2("关羽", 45, 180);
Person p3("孙权", 40, 170);
Person p4("赵云", 25, 190);
Person p5("张飞", 35, 160);
Person p6("关羽", 35, 200);
//插入数据
L.push_back(p1);
L.push_back(p2);
L.push_back(p3);
L.push_back(p4);
L.push_back(p5);
L.push_back(p6);
for (list<Person>::iterator it = L.begin();it != L.end();it++)
{
cout << "姓名: " << (*it).m_Name <<
" 年龄: " << it->m_Age <<
" 身高: " << it->m_Height << 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;
}
}
int main()
{
test01();
system("pause");
return 0;
}

3.2.8 set/multiset容器

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

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

set与multiset区别:

  • set不允许容器中有重复的元素
  • multiset允许容器中有重复的元素
set构造和赋值

功能:创建set容器以及赋值

构造:

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

赋值:

  • set& operator=(const set &st); //重载等号操作符
set大小和交换

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

函数原型:

  • size(); //返回容器中元素的数目
  • empty(); //判断容器是否为空
  • swap(st); //交换俩个集合容器
set插入和删除

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

函数原型:

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

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

函数原型:

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

区别:

  • set不允许插入重复元素,而multiset可以
  • set插入数据的同时会返回插入结果,表示插入是否成功
  • multiset不会检测数据,因此可以插入重复数据
pair对组创建

功能:成对出现的数据,利用对组可以返回俩个数据

创建方式:

  • pair<type,type> p (value1,value2);
  • pair<type,type> p = make_pair(value1,value2);
set容器排序

1.内置数据类型

2.自定义数据类型

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#include<iostream>
using namespace std;
#include<set>
#include<string>

void printSet(set<int>& s)
{
for (set<int>::iterator it = s.begin();it != s.end();it++)
{
cout << *it << " ";
}
cout << endl;
}
//set构造和赋值
void test01()
{
set<int>s1;
//插入数据只有insert方法
s1.insert(10);
s1.insert(20);
s1.insert(40);
s1.insert(30);
//遍历容器
//set容器特点:所有元素插入时候自动排序
//不允许插入重复值
printSet(s1);
//拷贝构造
set<int>s2(s1);
printSet(s2);
//赋值
set<int>s3;
s3 = s2;
printSet(s3);
}
//set大小和交换
void test02()
{
set<int>s1;
//插入数据只有insert方法
s1.insert(10);
s1.insert(20);
s1.insert(40);
s1.insert(30);
if (s1.empty())
{
cout << "s1为空" << endl;
}
else {
cout << "s1不为空" << endl;
cout << "s1大小为:" << s1.size() << endl;
}
set<int>s2;
s2.insert(1);
s2.insert(2);
s2.insert(9);
s2.insert(3);
cout << "交换前:" << endl;
printSet(s1);
printSet(s2);
cout << "交换后:" << endl;
s1.swap(s2);
printSet(s1);
printSet(s2);
}
//set插入和删除
void test03()
{
set<int>s1;
//插入数据只有insert方法
s1.insert(10);
s1.insert(20);
s1.insert(40);
s1.insert(30);
printSet(s1);

//删除 排序后的
s1.erase(s1.begin());
printSet(s1);

s1.erase(30);
printSet(s1);
//清空
//s1.erase(s1.begin(), s1.end());
s1.clear();
printSet(s1);
}
//set查找和统计
void test04()
{
set<int>s1;
//插入数据只有insert方法
s1.insert(10);
s1.insert(20);
s1.insert(40);
s1.insert(30);
s1.insert(30);
s1.insert(30);
printSet(s1);
set<int>::iterator pos = s1.find(30);
if (pos != s1.end()) {
cout << "找到元素:" << *pos << endl;
}
else
{
cout << "没找到" << endl;
}
//统计30 的个数 对于set而言结果只能为0或者1
int num = s1.count(30);
cout << num << endl;
}
//set与multiset区别
void test05()
{
set<int>s1;
//插入数据只有insert方法
pair<set<int>::iterator,bool> ret = s1.insert(10);
if (ret.second)
{
cout << "第一次插入成功" << endl;
}
else
{
cout << "第一次插入失败" << endl;
}
ret = s1.insert(10);
if (ret.second)
{
cout << "第二次插入成功" << endl;
}
else
{
cout << "第二次插入失败" << endl;
}
multiset<int>ms;
ms.insert(10);
ms.insert(10);
for (multiset<int>::iterator it = ms.begin();it != ms.end();it++)
{
cout << *it << " ";
}
cout << endl;
}
//pair对组创建
void test06()
{
//1
pair<string, int>p1("Tom", 20);
cout << "姓名:" << p1.first << "年龄:" << p1.second << endl;
//2
pair<string, int>p2 = make_pair("Jory", 18);
cout << "姓名:" << p2.first << "年龄:" << p2.second << endl;
}
//set排序
//内置数据类型
class MyCompare
{
public:
bool operator()(int v1,int v2) const
{
return v1 > v2;
}
};
void test07()
{
set<int>s1;
s1.insert(10);
s1.insert(40);
s1.insert(20);
s1.insert(50);
s1.insert(30);
printSet(s1);
//指定排序规则为从大到小 在插数据之前
set<int,MyCompare>s2;
s2.insert(10);
s2.insert(40);
s2.insert(20);
s2.insert(50);
s2.insert(30);
for (set<int, MyCompare>::iterator it = s2.begin();it != s2.end();it++)
{
cout << *it << " ";
}
cout << endl;
}
//自定义数据类型
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
class comparePerson
{
public:
bool operator()(const Person& p1, const Person& p2) const
{
//按照年龄降序
return p1.m_Age > p2.m_Age;
}
};
void test08()
{
//自定义数据类型都会指定排序规则
set<Person,comparePerson>s;
Person p1("刘备", 24);
Person p2("关羽", 28);
Person p3("张飞", 25);
Person p4("赵云", 21);
s.insert(p1);
s.insert(p2);
s.insert(p3);
s.insert(p4);
for (set<Person,comparePerson>::iterator it = s.begin();it != s.end();it++)
{
cout << "姓名:" << it->m_Name << " 年龄:" << it->m_Age << endl;;
}
}

int main()
{
test08();
system("pause");
return 0;
}

3.2.9 map/multimap容器

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

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

优点:

  • 可以根据key值快速找到value值

map和multimap区别:

  • map不允许容器中有重复的key值元素
  • multimap允许容器中有重复的key值元素
map构造和赋值

功能:对map容器进行构造和赋值操作

构造:

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

赋值:

  • map& operator=(const map &map); //重载等号操作符
map大小和交换

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

函数原型:

  • size(); //返回容器中元素的数目
  • empty(); //判断容器是否为空
  • swap(st); //交换俩个集合容器
map插入和删除

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

函数原型:

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

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

函数原型:

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

默认按照key值进行从小到大排序

利用仿函数

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include<iostream>
using namespace std;
#include<map>
#include<string>

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;
}
//map构造和赋值
void test01()
{
//创建map容器
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);
}
//map大小和交换
void test02()
{
//创建map容器
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));
if (m.empty())
{
cout << "m为空" << endl;
}
else
{
cout << "m不为空" << endl;
cout << "m的大小为:" << m.size() << endl;
}
map<int, int>m2;
m2.insert(pair<int, int>(1, 230));
m2.insert(pair<int, int>(2, 130));
m2.insert(pair<int, int>(3, 340));
m2.insert(pair<int, int>(4, 240));
cout << "交换前:" << endl;
printMap(m);
printMap(m2);
cout << "交换后:" << endl;
m.swap(m2);
printMap(m);
printMap(m2);
}
//map插入和删除
void test03()
{
//创建map容器
map<int, int>m;
//插入4种方式
m.insert(pair<int, int>(1, 10));
m.insert(make_pair(2, 20));
m.insert(map<int, int>::value_type(3, 30));
//[]不建议去插入 可以使用[]通过key访问value
m[4] = 40;
printMap(m);
//删除
m.erase(m.begin());
printMap(m);
m.erase(3);//按照key删除
printMap(m);
//清空
//m.erase(m.begin(), m.end());
m.clear();
printMap(m);
}
//map查找和统计
void test04()
{
map<int, int>m;
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(2, 20));
m.insert(pair<int, int>(3, 30));
map<int, int>::iterator pos = m.find(3);
if (pos != m.end())
{
cout << "找到元素: key = " << (*pos).first << " value = " << pos->second << endl;
}
else
{
cout << "未找到元素" << endl;
}
//统计 结果只能为0或1 因为map不允许插入重复key
//multimap的统计结果可能大于1
int num = m.count(3);
cout << "num = " << num << endl;
}
//map容器排序
class MyCompare
{
public:
bool operator()(int v1,int v2) const
{
//降序
return v1 > v2;
}
};
void test05()
{
//在插入数据前修改排序规则
map<int, int,MyCompare>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));
m.insert(pair<int, int>(5, 50));
for (map<int, int, MyCompare>::iterator it = m.begin();it != m.end();it++)
{
cout << "key = " << it->first << " value = " << it->second << endl;
}
cout << endl;
}
int main()
{
test05();
system("pause");
return 0;
}
案例: 员工分组

案例描述:

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

步骤:
1.创建10名员工,放到vector中
2.遍历vector容器,取出每个员工,进行随机分组
3.分组后将员工部门编号作为key,具体员工为value,放入到multimap容器中
4.分部门显示员工信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include<iostream>
using namespace std;
#include<vector>
#include<string>
#include<map>
#include<ctime>
#define CEHUA 0
#define MEISHU 1
#define YANFA 2
class Worker
{
public:
string m_Name;
int m_Salary;
};
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_Salary = 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;
m.insert(make_pair(deptId, *it));
}
}
void showWorkerByGroup(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_Salary << 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_Salary << 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_Salary << endl;
}
}
int main()
{
srand((unsigned int)time(NULL));
//1.创建员工
vector<Worker>vWorker;
createWorker(vWorker);
//2.员工分组
multimap<int, Worker>mWorker;
setGroup(vWorker,mWorker);
//3.分组显示员工
showWorkerByGroup(mWorker);
system("pause");
return 0;
}

3.3 STL函数对象

概念:

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

本质:函数对象是一个类,不是一个函数

使用特点:

  • 函数对象在使用时,可以像普通函数那样调用,可以有参数和返回值
  • 函数对象超出普通函数概念,函数对象可以有自己的状态
    • 可以使用成员属性记录状态
  • 函数对象可以作为参数传递

3.3.1 谓词

概念:

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

3.3.2 内建函数对象

概念:STL内建了一些函数对象

分类:

  • 算术仿函数
  • 关系仿函数
  • 逻辑仿函数

用法:

  • 这些仿函数所产生的对象用法和一般函数完全相同
  • 使用内建函数对象,需要引入头文件#include<functional>
算术仿函数

功能描述:

  • 实现四则运算
  • 其中negate是一元运算,其他都是二元运算

仿函数原型:

  • template<class T> T plus<T> //加法仿函数
  • template<class T> T minus<T> //减法仿函数
  • template<class T> T multiplies<T> //乘法仿函数
  • template<class T> T divides<T> //除法仿函数
  • template<class T> T modulus<T> //取模仿函数
  • template<class T> T negate<T> //取反仿函数
1
2
3
4
5
6
//一元运算 negate
negate<int>n;
cout << n(50) << endl;
//二元运算 plus
plus<int>p;
cout << p(10, 20) << endl;
关系仿函数

功能描述:

  • 实现关系比较

仿函数原型:

  • template<class T> bool equal_to<T> //等于
  • template<class T> bool not_equal_to<T> //不等于
  • template<class T> bool greater<T> //大于
  • template<class T> bool greater_equal<T> //大于等于
  • template<class T> bool less<T> //小于
  • template<class T> bool less_equal<T> //小于等于
逻辑仿函数

功能描述:

  • 实现逻辑运算

仿函数原型:

  • template<class T> bool logical_and<T> //逻辑与
  • template<class T> bool logical_or<T> //逻辑或
  • template<class T> bool logical_not<T> //逻辑非

3.4 STL常用算法

概述:

  • 算法主要是由头文件<algorithm> <functional> <numeric> 组成
  • <algorithm> 是所有STL头文件中最大的一个,范围涉及到比较、交换、查找、遍历操作、复制、修改等等
  • <functional> 体积很小,只包括几个在序列上面进行的简单数学运算的模板函数
  • <numeric> 定义了一些模板类,用以声明函数对象

3.4.1 常用遍历算法

  • for_each //遍历容器

函数原型:

  • for_each(iterator beg,iterator end,_func);
    //beg开始迭代器
    //end结束迭代器
    //_func函数或者函数对象

  • transform //搬运容器到另一个容器

    函数原型:

    • transform(iterator beg1,iterator end1,iterator beg2,_func);
      //beg1源容器开始迭代器
      //end1源容器结束迭代器
      //beg2目标容器开始迭代器
      //_func函数或者函数对象

目标容器需要提前开辟空间

3.4.2 常用查找算法

  • find //查找元素

    函数原型:

    • find(iterator beg,iterator end,value);
      //按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
      //beg开始迭代器
      //end结束迭代器
      //value查找的元素
  • find_if //按条件查找元素

    函数原型:

    • find_if(iterator beg,iterator end,_Pred);
      //按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
      //beg开始迭代器
      //end结束迭代器
      //_Pred函数或者谓词(返回bool类型的仿函数)
  • adjacent_find //查找相邻重复元素

    函数原型:

    • adjacent_find(iterator beg,iterator end);
      //查找相邻重复元素,返回相邻元素的第一个位置的迭代器
      //beg开始迭代器
      //end结束迭代器
  • binary_search //二分查找法

    函数原型:

    • bool binary_search(iterator beg,iterator end,value);
      //查找指定的元素 找到返回true 否则返回false
      //beg开始迭代器
      //end结束迭代器
      //value查找的元素
  • count //统计元素个数

    函数原型:

    • count(iterator beg,iterator end,value);
      //统计元素出现次数
      //beg开始迭代器
      //end结束迭代器
      //value统计的元素
  • count_if //按条件统计元素个数

    函数原型:

    • count_if(iterator beg,iterator end,_Pred);
      //按条件统计元素出现次数
      //beg开始迭代器
      //end结束迭代器
      //_Pred谓词

3.4.3 常用排序算法

  • sort //对容器内元素进行排序

    函数原型:

    • sort(iterator beg,iterator end,_Pred);
      //对容器内元素进行排序
      //beg开始迭代器
      //end结束迭代器
      //_Pred谓词
  • random_shuffle //洗牌 指定范围内的元素随机调整次序

    函数原型:

    • random_shuffle(iterator beg,iterator end);
      //指定范围内的元素随机调整次序
      //beg开始迭代器
      //end结束迭代器

    可以添加随机数种子: srand((unsigned int)time(NULL))

  • merge //容器元素合并,并存储到另一个容器中

    函数原型:

    • merge(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
      //注意:俩个容器必须是有序且一致(同升同降)的
      //beg1容器1开始迭代器
      //end1容器1结束迭代器
      //beg2容器2开始迭代器
      //end2容器2结束迭代器
      //目标容器需要提前分配空间
      //dest目标容器开始迭代器
  • reverse //反转指定范围的元素

    函数原型:

    • reverse(iterator beg,iterator end);
      //beg开始迭代器
      //end结束迭代器

3.4.4 常用拷贝和构造算法

  • copy //容器内指定范围的元素拷贝到另一个容器中

    函数原型:

    • copy(iterator beg,iterator end,iterator dest);
      //beg开始迭代器
      //end结束迭代器
      //目标容器需要提前分配空间
      //dest目标容器起始迭代器
  • 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新元素
  • swap //互换俩个容器的元素

    函数原型:

    • swap(container c1,container c2);
      //注意c1、c2必须为同一种容器
      //c1容器1
      //c2容器2

3.4.5 常用算术生成算法

包含头文件#include<numeric>

  • accumulate //计算容器元素累计总和

    函数原型:

    • accumulate(iterator beg,iterator end,value);
      //beg开始迭代器
      //end结束迭代器
      //value起始值
  • fill //向容器中添加元素

    函数原型:

    • fill(iterator beg,iterator end,value);
      //beg开始迭代器
      //end结束迭代器
      //value填充的值

3.4.6 常用集合算法

  • set_intersection //求俩个容器的交集

    函数原型:

    • set_intersection(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
      //注意:俩个容器必须是有序序列且一致(同升同降)的
      //beg1容器1开始迭代器
      //end1容器1结束迭代器
      //beg2容器2开始迭代器
      //end2容器2结束迭代器
      //目标容器需要提前分配空间 resize
      //最特殊情况 大容器包含小容器 开辟空间 取小容器size即可: resize(min(v1.size(),v2.size()));
      //遍历时结束迭代器使用返回回来的迭代器dest
      //dest目标容器开始迭代器
  • set_union //求俩个容器的并集

    函数原型:

    • set_union(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
      //注意:俩个容器必须是有序序列且一致(同升同降)的
      //beg1容器1开始迭代器
      //end1容器1结束迭代器
      //beg2容器2开始迭代器
      //end2容器2结束迭代器
      //目标容器需要提前分配空间 resize
      //最特殊情况 俩容器没有交集 开辟空间 取俩容器size之和即可: resize(v1.size()+v2.size());
      //遍历时结束迭代器使用返回回来的迭代器dest
      //dest目标容器开始迭代器
  • set_difference //求俩个容器的差集

    函数原型:

    • set_union(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
      //注意:俩个容器必须是有序序列且一致(同升同降)的
      //beg1容器1开始迭代器
      //end1容器1结束迭代器
      //beg2容器2开始迭代器
      //end2容器2结束迭代器
      //目标容器需要提前分配空间 resize
      //最特殊情况 俩容器没有交集 开辟空间 取大容器size即可: resize(max(v1.size(),v2.size());
      //遍历时结束迭代器使用返回回来的迭代器dest
      //dest目标容器开始迭代器
案例: 演讲比赛流程管理系统

比赛规则:

  • 学校举行一场演讲比赛,共有12人参加。比赛共俩轮,第一轮为淘汰赛,第二轮为决赛
  • 每名选手都有对应的编号:如10001 ~ 10012
  • 比赛方式:分组比赛每组6人
  • 每一轮分为俩个小组,整体按照选手编号进行抽签后顺序演讲
  • 十个评委分别给每个选手打分,去除最高分和最低分,求的平均分为本轮选手的成绩
  • 当小组演讲完后,淘汰组内排名最后的三个选手,前三名晋级,进入下一轮比赛
  • 第二轮为决赛,前三名胜出
  • 每轮比赛过后需要显示晋级选手的信息

程序功能:

  • 开始演讲比赛:完成整届比赛的流程,每个比赛阶段需要给用户一个提示,用户按任意键后继续下一个阶段
  • 查看往届记录:查看之前比赛前三名结果,每次比赛都会记录到文件中,文件用.csv后缀名保存
  • 清空比赛记录:将文件中数据清空
  • 退出比赛程序:可以退出当前程序

代码

机房预约系统

身份简介

  • 学生代表:申请使用机房
  • 教师:审核学生的预约申请
  • 管理员:给学生老师创建账号

机房简介

  • 1号机房:最大容量20人
  • 2号机房:最大容量50人
  • 3号机房:最大容量100人

申请简介

  • 申请的订单每周由管理员清空
  • 学生可以预约未来一周内的机房使用,预约的日期为周一至周五,预约时需要选择时间段(上午、下午)
  • 教师来审核预约,依据实际情况审核预约通过或者不通过

系统具体要求

  • 首先进入登录界面,可选身份有:
    • 学生代表
    • 老师
    • 管理员
    • 退出
  • 每个身份都需要进型验证后,进入子菜单
    • 学生需要输入:学号、姓名、登录密码
    • 老师需要输入:职工号、姓名、登录密码
    • 学生需要输入:管理员姓名、登录密码
  • 学生具体功能
    • 申请预约 — 预约机房
    • 查看自身的预约 — 查看自身的预约状态
    • 查看所有预约 — 查看全部预约信息以及预约状态
    • 取消预约 — 取消自身的预约,预约成功或审核中的预约都可以取消
    • 注销登录 — 退出登录
  • 教师具体功能
    • 查看所有预约 — 查看全部预约信息以及预约状态
    • 审核预约 — 对学生的预约进行审核
    • 注销登录 — 退出登录
  • 管理员具体功能
    • 添加账号 — 添加学生或教师账号,需要检测学生编号和教师职工编号是否重复
    • 查看账号 — 可以选择查看学生或老师的全部信息
    • 查看机房 — 查看所有机房的消息
    • 清空预约 — 清空所有预约记录
    • 注销登录 — 退出登录

代码