快速回顾一下, 简单的就不记了.
1.常量
作用:用于记录程序中不可更改的数据
C++定义常量两种方式
-
#define 宏常量:
#define 常量名 常量值
- 通常在文件上方定义,表示一个常量
-
const修饰的变量
const 数据类型 常量名 = 常量值
- 通常在变量定义前加关键字const,修饰该变量为常量,不可修改
1//1、宏常量
2#define day 7
3
4int main() {
5
6 cout << "一周里总共有 " << day << " 天" << endl;
7 //day = 8; //报错,宏常量不可以修改
8
9 //2、const修饰变量
10 const int month = 12;
11 cout << "一年里总共有 " << month << " 个月份" << endl;
12 //month = 24; //报错,常量是不可以修改的
13
14
15 system("pause");
16
17 return 0;
18}
2.数据类型
整型
C++中能够表示整型的类型有以下几种方式,区别在于所占内存空间不同:
数据类型 | 占用空间 | 取值范围 |
---|---|---|
short(短整型) | 2字节 | (-2^15 ~ 2^15-1) |
int(整型) | 4字节 | (-2^31 ~ 2^31-1) |
long(长整形) | Windows为4字节,Linux为4字节(32位),8字节(64位) | (-2^31 ~ 2^31-1) |
long long(长长整形) | 8字节 | (-2^63 ~ 2^63-1) |
sizeof关键字: 利用sizeof关键字可以统计数据类型所占内存大小
sizeof( 数据类型 / 变量)
浮点型
浮点型变量分为两种:
- 单精度float
- 双精度double
两者的区别在于表示的有效数字范围不同。
数据类型 | 占用空间 | 有效数字范围 |
---|---|---|
float | 4字节 | 7位有效数字 |
double | 8字节 | 15~16位有效数字 |
字符型
字符型变量用于显示单个字符
char ch = 'a';
注意1:在显示字符型变量时,用单引号将字符括起来,不要用双引号
注意2:单引号内只能有一个字符,不可以是字符串
- C和C++中字符型变量只占用1个字节。
- 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元
字符串
两种风格
- C风格字符串:
char 变量名[] = "字符串值"
注意:C风格的字符串要用双引号括起来
- C++风格字符串:
string 变量名 = "字符串值"
注意:C++风格字符串,需要加入头文件==#include<string>==
布尔值
bool类型只有两个值:
- true — 真(本质是1)
- false — 假(本质是0)
bool类型占==1个字节==大小
数据的输入: cin >> 变量
3.运算符
算术运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | -3 | -3 |
+ | 加 | 10 + 5 | 15 |
- | 减 | 10 - 5 | 5 |
* | 乘 | 10 * 5 | 50 |
/ | 除 | 10 / 5 | 2 |
% | 取模(取余) | 10 % 3 | 1 |
++ | 前置递增 | a=2; b=++a; | a=3; b=3; |
++ | 后置递增 | a=2; b=a++; | a=3; b=2; |
– | 前置递减 | a=2; b=–a; | a=1; b=1; |
– | 后置递减 | a=2; b=a–; | a=1; b=2; |
赋值运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
= | 赋值 | 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; |
比较运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
== | 相等于 | 4 == 3 | 0 |
!= | 不等于 | 4 != 3 | 1 |
< | 小于 | 4 < 3 | 0 |
> | 大于 | 4 > 3 | 1 |
<= | 小于等于 | 4 <= 3 | 0 |
>= | 大于等于 | 4 >= 1 | 1 |
逻辑运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
! | 非 | !a | 如果a为假,则!a为真; 如果a为真,则!a为假。 |
&& | 与 | a && b | 如果a和b都为真,则结果为真,否则为假。 |
|| | 或 | a || b | 如果a和b有一个为真,则结果为真,二者都为假时,结果为假。 |
4.程序流程结构
if
三元运算符
switch
语法:
1switch(表达式)
2
3{
4
5 case 结果1:执行语句;break;
6
7 case 结果2:执行语句;break;
8
9 ...
10
11 default:执行语句;break;
12
13}
14
示例
1int main() {
2
3 //请给电影评分
4 //10 ~ 9 经典
5 // 8 ~ 7 非常好
6 // 6 ~ 5 一般
7 // 5分以下 烂片
8
9 int score = 0;
10 cout << "请给电影打分" << endl;
11 cin >> score;
12
13 switch (score)
14 {
15 case 10:
16 case 9:
17 cout << "经典" << endl;
18 break;
19 case 8:
20 cout << "非常好" << endl;
21 break;
22 case 7:
23 case 6:
24 cout << "一般" << endl;
25 break;
26 default:
27 cout << "烂片" << endl;
28 break;
29 }
30
31 system("pause");
32
33 return 0;
34}
注意1:switch语句中表达式类型只能是整型或者字符型
注意2:case里如果没有break,那么程序会一直向下执行
总结:与if语句比,对于多条件判断时,switch的结构清晰,执行效率高,缺点是switch不可以判断区间
while
do…while
for
跳转语句
break
作用: 用于跳出==选择结构==或者==循环结构==
break使用的时机:
- 出现在switch条件语句中,作用是终止case并跳出switch
- 出现在循环语句中,作用是跳出当前的循环语句
- 出现在嵌套循环中,跳出最近的内层循环语句
continue
**作用:**在==循环语句==中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环
goto
**作用:**可以无条件跳转语句
语法: goto 标记;
**解释:**如果标记的名称存在,执行到goto语句时,会跳转到标记的位置
示例:
1int main() {
2
3 cout << "1" << endl;
4
5 goto FLAG;
6
7 cout << "2" << endl;
8 cout << "3" << endl;
9 cout << "4" << endl;
10
11 FLAG:
12
13 cout << "5" << endl;
14
15 system("pause");
16
17 return 0;
18}
注意:在程序中不建议使用goto语句,以免造成程序流程混乱
5.数组
一维数组
一维数组定义的三种方式:
数据类型 数组名[ 数组长度 ];
数据类型 数组名[ 数组长度 ] = { 值1,值2 ...};
(如果{}内不足10个数据,剩余数据用0补全)数据类型 数组名[ ] = { 值1,值2 ...};
一维数组名称的用途:
- 可以统计整个数组在内存中的长度
- 可以获取数组在内存中的首地址
示例:
1int main() {
2
3 //数组名用途
4 //1、可以获取整个数组占用内存空间大小
5 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
6
7 cout << "整个数组所占内存空间为: " << sizeof(arr) << endl;
8 cout << "每个元素所占内存空间为: " << sizeof(arr[0]) << endl;
9 cout << "数组的元素个数为: " << sizeof(arr) / sizeof(arr[0]) << endl;
10
11 //2、可以通过数组名获取到数组首地址
12 cout << "数组首地址为: " << (int)arr << endl;
13 cout << "数组中第一个元素地址为: " << (int)&arr[0] << endl;
14 cout << "数组中第二个元素地址为: " << (int)&arr[1] << endl;
15
16 //arr = 100; 错误,数组名是常量,因此不可以赋值
17
18
19 system("pause");
20
21 return 0;
22}
注意:数组名是常量,不可以赋值
总结1:直接打印数组名,可以查看数组所占内存的首地址
总结2:对数组名进行sizeof,可以获取整个数组占内存空间的大小
二维数组
二维数组定义的四种方式:
数据类型 数组名[ 行数 ][ 列数 ];
数据类型 数组名[ 行数 ][ 列数 ] = { {数据1,数据2 } ,{数据3,数据4 } };
数据类型 数组名[ 行数 ][ 列数 ] = { 数据1,数据2,数据3,数据4};
数据类型 数组名[ ][ 列数 ] = { 数据1,数据2,数据3,数据4};
二维数组数组名:
- 查看二维数组所占内存空间
- 获取二维数组首地址
示例:
1int main() {
2
3 //二维数组数组名
4 int arr[2][3] =
5 {
6 {1,2,3},
7 {4,5,6}
8 };
9
10 cout << "二维数组大小: " << sizeof(arr) << endl;
11 cout << "二维数组一行大小: " << sizeof(arr[0]) << endl;
12 cout << "二维数组元素大小: " << sizeof(arr[0][0]) << endl;
13
14 cout << "二维数组行数: " << sizeof(arr) / sizeof(arr[0]) << endl;
15 cout << "二维数组列数: " << sizeof(arr[0]) / sizeof(arr[0][0]) << endl;
16
17 //地址
18 cout << "二维数组首地址:" << arr << endl;
19 cout << "二维数组第一行地址:" << arr[0] << endl;
20 cout << "二维数组第二行地址:" << arr[1] << endl;
21
22 cout << "二维数组第一个元素地址:" << &arr[0][0] << endl;
23 cout << "二维数组第二个元素地址:" << &arr[0][1] << endl;
24
25 system("pause");
26
27 return 0;
28}
总结1:二维数组名就是这个数组的首地址
总结2:对二维数组名进行sizeof时,可以获取整个二维数组占用的内存空间大小
6.函数
函数的定义
语法:
1返回值类型 函数名 (参数列表)
2{
3
4 函数体语句
5
6 return表达式
7
8}
函数的调用
值传递
- 所谓值传递,就是函数调用时实参将数值传入给形参
- 值传递时,如果形参发生,并不会影响实参
函数的常见样式
函数的声明
作用: 告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。
- 函数的声明可以多次,但是函数的定义只能有一次
函数的分文件编写
函数分文件编写一般有4个步骤
- 创建后缀名为.h的头文件
- 创建后缀名为.cpp的源文件
- 在头文件中写函数的声明
- 在源文件中写函数的定义
示例:
1//swap.h文件
2#include<iostream>
3using namespace std;
4
5//实现两个数字交换的函数声明
6void swap(int a, int b);
7
1//swap.cpp文件
2#include "swap.h"
3
4void swap(int a, int b)
5{
6 int temp = a;
7 a = b;
8 b = temp;
9
10 cout << "a = " << a << endl;
11 cout << "b = " << b << endl;
12}
1//main函数文件
2#include "swap.h"
3int main() {
4
5 int a = 100;
6 int b = 200;
7 swap(a, b);
8
9 system("pause");
10
11 return 0;
12}
13
7.指针
指针变量定义语法: 数据类型 * 变量名;
示例:
1int main() {
2
3 //1、指针的定义
4 int a = 10; //定义整型变量a
5
6 //指针定义语法: 数据类型 * 变量名 ;
7 int * p;
8
9 //指针变量赋值
10 p = &a; //指针指向变量a的地址
11 cout << &a << endl; //打印数据a的地址
12 cout << p << endl; //打印指针变量p
13
14 //2、指针的使用
15 //通过*操作指针变量指向的内存
16 cout << "*p = " << *p << endl;
17
18 system("pause");
19
20 return 0;
21}
指针变量和普通变量的区别
- 普通变量存放的是数据,指针变量存放的是地址
- 指针变量可以通过" * “操作符,操作指针变量指向的内存空间,这个过程称为解引用
总结1: 我们可以通过 & 符号 获取变量的地址
总结2:利用指针可以记录地址
总结3:对指针变量解引用,可以操作指针指向的内存
指针所占内存空间
所有指针类型在32位操作系统下是4个字节
空指针和野指针
空指针:指针变量指向内存中编号为0的空间
**注意:**空指针指向的内存是不可以访问的
示例1:空指针
1int main() {
2
3 //指针变量p指向内存地址编号为0的空间
4 int * p = NULL;
5
6 //访问空指针报错
7 //内存编号0 ~255为系统占用内存,不允许用户访问
8 cout << *p << endl;
9
10 system("pause");
11
12 return 0;
13}
野指针:指针变量指向非法的内存空间
示例2:野指针
1int main() {
2
3 //指针变量p指向内存地址编号为0x1100的空间
4 int * p = (int *)0x1100;
5
6 //访问野指针报错
7 cout << *p << endl;
8
9 system("pause");
10
11 return 0;
12}
总结:空指针和野指针都不是我们申请的空间,因此不要访问。
const修饰指针
const修饰指针有三种情况
- const修饰指针 — 常量指针 const int * p1 = &a;
- const修饰常量 — 指针常量 int * const p2 = &a;
- const即修饰指针,又修饰常量 const int * const p3 = &a;
**示例: **
1int main() {
2
3 int a = 10;
4 int b = 10;
5
6 //const修饰的是指针,指针指向可以改,指针指向的值不可以更改
7 const int * p1 = &a;
8 p1 = &b; //正确
9 //*p1 = 100; 报错
10
11
12 //const修饰的是常量,指针指向不可以改,指针指向的值可以更改
13 int * const p2 = &a;
14 //p2 = &b; //错误
15 *p2 = 100; //正确
16
17 //const既修饰指针又修饰常量
18 const int * const p3 = &a;
19 //p3 = &b; //错误
20 //*p3 = 100; //错误
21
22 system("pause");
23
24 return 0;
25}
技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量
指针和数组
当数组名传入到函数作为参数时,被退化为指向首元素的指针
示例:
1int main() {
2
3 int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
4
5 int * p = arr; //指向数组的指针
6
7 cout << "第一个元素: " << arr[0] << endl;
8 cout << "指针访问第一个元素: " << *p << endl;
9
10 for (int i = 0; i < 10; i++)
11 {
12 //利用指针遍历数组
13 cout << *p << endl;
14 p++;
15 }
16
17 system("pause");
18
19 return 0;
20}
指针和函数
利用指针作函数参数,可以修改实参的值
1//地址传递
2void swap2(int * p1, int *p2)
3{
4 int temp = *p1;
5 *p1 = *p2;
6 *p2 = temp;
7}
如果不想修改实参,就用值传递,如果想修改实参,就用地址传递
8.结构体
结构体定义和使用
语法:struct 结构体名 { 结构体成员列表 };
通过结构体创建变量的方式有三种:
- struct 结构体名 变量名
- struct 结构体名 变量名 = { 成员1值 , 成员2值…}
- 定义结构体时顺便创建变量
示例:
1//结构体定义
2struct student
3{
4 //成员列表
5 string name; //姓名
6 int age; //年龄
7 int score; //分数
8}stu3; //结构体变量创建方式3
9
10
11int main() {
12
13 //结构体变量创建方式1
14 struct student stu1; //struct 关键字可以省略
15
16 stu1.name = "张三";
17 stu1.age = 18;
18 stu1.score = 100;
19
20 cout << "姓名:" << stu1.name << " 年龄:" << stu1.age << " 分数:" << stu1.score << endl;
21
22 //结构体变量创建方式2
23 struct student stu2 = { "李四",19,60 };
24
25 cout << "姓名:" << stu2.name << " 年龄:" << stu2.age << " 分数:" << stu2.score << endl;
26
27
28 stu3.name = "王五";
29 stu3.age = 18;
30 stu3.score = 80;
31
32
33 cout << "姓名:" << stu3.name << " 年龄:" << stu3.age << " 分数:" << stu3.score << endl;
34
35 system("pause");
36
37 return 0;
38}
总结1:定义结构体时的关键字是struct,不可省略
总结2:创建结构体变量时,关键字struct可以省略
总结3:结构体变量利用操作符 ‘’.'' 访问成员
结构体数组
struct 结构体名 数组名[元素个数] = { {} , {} , ... {} }
1struct student arr[3]=
2 {
3 {"张三",18,80 },
4 {"李四",19,60 },
5 {"王五",20,70 }
6 };
7
结构体指针
利用操作符 ->
可以通过结构体指针访问结构体属性
1int main() {
2
3 struct student stu = { "张三",18,100, };
4
5 struct student * p = &stu;
6
7 p->score = 80; //指针通过 -> 操作符可以访问成员
8
9 cout << "姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl;
10
11 system("pause");
12
13 return 0;
14}
结构体嵌套结构体
结构体做函数参数
传递方式有两种:
- 值传递
- 地址传递
1//值传递
2void printStudent(student stu )
3{
4 stu.age = 28;
5 cout << "子函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
6}
7
8//地址传递
9void printStudent2(student *stu)
10{
11 stu->age = 28;
12 cout << "子函数中 姓名:" << stu->name << " 年龄: " << stu->age << " 分数:" << stu->score << endl;
13}
结构体中 const使用场景
用const来防止误操作
1//const使用场景
2void printStudent(const student *stu) //加const防止函数体中的误操作
3{
4 //stu->age = 100; //操作失败,因为加了const修饰
5 cout << "姓名:" << stu->name << " 年龄:" << stu->age << " 分数:" << stu->score << endl;
6
7}
9.内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程
程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
存放 CPU 执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
全局变量和静态变量存放在此.
全局区还包含了常量区, 字符串常量和其他常量也存放在此.
==该区域的数据在程序结束后由操作系统释放==.
程序运行后
栈区:
由编译器自动分配释放, 存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
new操作符
C++中利用==new==操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 ==delete==
语法: new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
示例1: 基本语法
1int* func()
2{
3 int* a = new int(10);
4 return a;
5}
6
7int main() {
8
9 int *p = func();
10
11 cout << *p << endl;
12 cout << *p << endl;
13
14 //利用delete释放堆区数据
15 delete p;
16
17 //cout << *p << endl; //报错,释放的空间不可访问
18
19 system("pause");
20
21 return 0;
22}
示例2:开辟数组
1//堆区开辟数组
2int main() {
3
4 int* arr = new int[10];
5
6 for (int i = 0; i < 10; i++)
7 {
8 arr[i] = i + 100;
9 }
10
11 for (int i = 0; i < 10; i++)
12 {
13 cout << arr[i] << endl;
14 }
15 //释放数组 delete 后加 []
16 delete[] arr;
17
18 system("pause");
19
20 return 0;
21}
22
10.引用
引用的基本使用
给变量起别名
数据类型 &别名 = 原名
引用注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
1int main() {
2
3 int a = 10;
4 int b = 20;
5 //int &c; //错误,引用必须初始化
6 int &c = a; //一旦初始化后,就不可以更改
7 c = b; //这是赋值操作,不是更改引用
8
9 cout << "a = " << a << endl;
10 cout << "b = " << b << endl;
11 cout << "c = " << c << endl;
12
13 system("pause");
14
15 return 0;
16}
引用做函数参数
**作用:**函数传参时,可以利用引用的技术让形参修饰实参
**优点:**可以简化指针修改实参, 通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
1//1. 值传递
2void mySwap01(int a, int b) {
3 int temp = a;
4 a = b;
5 b = temp;
6}
7
8//2. 地址传递
9void mySwap02(int* a, int* b) {
10 int temp = *a;
11 *a = *b;
12 *b = temp;
13}
14
15//3. 引用传递
16void mySwap03(int& a, int& b) {
17 int temp = a;
18 a = b;
19 b = temp;
20}
引用做函数返回值
引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
1//返回局部变量引用
2int& test01() {
3 int a = 10; //局部变量
4 return a;
5}
6
7//返回静态变量引用
8int& test02() {
9 static int a = 20;
10 return a;
11}
12
13int main() {
14
15 //不能返回局部变量的引用
16 int& ref = test01();
17 cout << "ref = " << ref << endl;
18 cout << "ref = " << ref << endl;
19
20 //如果函数做左值,那么必须返回引用
21 int& ref2 = test02();
22 cout << "ref2 = " << ref2 << endl;
23 cout << "ref2 = " << ref2 << endl;
24
25 test02() = 1000;
26
27 cout << "ref2 = " << ref2 << endl;
28 cout << "ref2 = " << ref2 << endl;
29
30 system("pause");
31
32 return 0;
33}
引用的本质
引用的本质在c++内部实现是一个指针常量.
C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
1//发现是引用,转换为 int* const ref = &a;
2void func(int& ref){
3 ref = 100; // ref是引用,转换为*ref = 100
4}
5int main(){
6 int a = 10;
7
8 //自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
9 int& ref = a;
10 ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;
11
12 cout << "a:" << a << endl;
13 cout << "ref:" << ref << endl;
14
15 func(a);
16 return 0;
17}
常量引用
常量引用主要用来修饰形参,防止误操作
1//引用使用的场景,通常用来修饰形参
2void showValue(const int& v) {
3 //v += 10;
4 cout << v << endl;
5}
6
7int main() {
8
9 //int& ref = 10; 引用本身需要一个合法的内存空间,因此这行错误
10 //加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;
11 const int& ref = 10;
12
13 //ref = 100; //加入const后不可以修改变量
14 cout << ref << endl;
15
16 //函数中利用常量引用防止误操作修改实参
17 int a = 10;
18 showValue(a);
19
20 system("pause");
21
22 return 0;
23}
11.函数提高
函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的。
返回值类型 函数名 (参数= 默认值){}
1int func(int a, int b = 10, int c = 10) {
2 return a + b + c;
3}
4
5//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
6//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
7int func2(int a = 10, int b = 10);
8int func2(int a, int b) {
9 return a + b;
10}
函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
返回值类型 函数名 (数据类型){}
1//函数占位参数 ,占位参数也可以有默认参数
2void func(int a, int) {
3 cout << "this is func" << endl;
4}
5
6int main() {
7
8 func(10,10); //占位参数必须填补
9
10 system("pause");
11
12 return 0;
13}
12.函数重载
函数重载概述
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 或者 个数不同 或者 顺序不同
注意: 函数的返回值不可以作为函数重载的条件
函数重载注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
示例:
1//函数重载注意事项
2//1、引用作为重载条件
3
4void func(int &a)
5{
6 cout << "func (int &a) 调用 " << endl;
7}
8
9void func(const int &a)
10{
11 cout << "func (const int &a) 调用 " << endl;
12}
13
14
15//2、函数重载碰到函数默认参数
16
17void func2(int a, int b = 10)
18{
19 cout << "func2(int a, int b = 10) 调用" << endl;
20}
21
22void func2(int a)
23{
24 cout << "func2(int a) 调用" << endl;
25}
26
27int main() {
28
29 int a = 10;
30 func(a); //调用无const
31 func(10);//调用有const
32
33
34 //func2(10); //碰到默认参数产生歧义,需要避免
35
36 system("pause");
37
38 return 0;
39}
13.类和对象
封装
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
class 类名{ 访问权限: 属性 / 行为 };
访问权限有三种:
- public 公共权限
- protected 保护权限
- private 私有权限
示例:
1//三种权限
2//公共权限 public 类内可以访问 类外可以访问
3//保护权限 protected 类内可以访问 类外不可以访问
4//私有权限 private 类内可以访问 类外不可以访问
5
6class Person
7{
8 //姓名 公共权限
9public:
10 string m_Name;
11
12 //汽车 保护权限
13protected:
14 string m_Car;
15
16 //银行卡密码 私有权限
17private:
18 int m_Password;
19
20public:
21 void func()
22 {
23 m_Name = "张三";
24 m_Car = "拖拉机";
25 m_Password = 123456;
26 }
27};
28
29int main() {
30
31 Person p;
32 p.m_Name = "李四";
33 //p.m_Car = "奔驰"; //保护权限类外访问不到
34 //p.m_Password = 123; //私有权限类外访问不到
35
36 system("pause");
37
38 return 0;
39}
struct和class区别
在C++中 struct和class唯一的区别就在于 默认的访问权限不同
区别:
- struct 默认权限为公共
- class 默认权限为私有
1class C1
2{
3 int m_A; //默认是私有权限
4};
5
6struct C2
7{
8 int m_A; //默认是公共权限
9};
10
11int main() {
12
13 C1 c1;
14 c1.m_A = 10; //错误,访问权限是私有
15
16 C2 c2;
17 c2.m_A = 10; //正确,访问权限是公共
18
19 system("pause");
20
21 return 0;
22}
成员属性设置为私有
**优点1:**将所有成员属性设置为私有,可以自己控制读写权限
**优点2:**对于写权限,我们可以检测数据的有效性
对象的初始化和清理
I 构造函数和析构函数
c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供, 编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
II 构造函数的分类及调用
两种分类方式:
按参数分为: 有参构造和无参构造
按类型分为: 普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐式转换法
1class Person {
2public:
3 //无参(默认)构造函数
4 Person() {
5 cout << "无参构造函数!" << endl;
6 }
7 //有参构造函数
8 Person(int a) {
9 age = a;
10 cout << "有参构造函数!" << endl;
11 }
12 //拷贝构造函数
13 Person(const Person& p) {
14 age = p.age;
15 cout << "拷贝构造函数!" << endl;
16 }
17 //析构函数
18 ~Person() {
19 cout << "析构函数!" << endl;
20 }
21public:
22 int age;
23};
24
25//2、构造函数的调用
26//调用无参构造函数
27void test01() {
28 Person p; //调用无参构造函数
29}
30
31//调用有参的构造函数
32void test02() {
33
34 //2.1 括号法,常用
35 Person p1(10);
36 //注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
37 //Person p2();
38
39 //2.2 显式法
40 Person p2 = Person(10);
41 Person p3 = Person(p2);
42 //Person(10)单独写就是匿名对象 当前行结束之后,马上析构
43
44 //2.3 隐式转换法
45 Person p4 = 10; // Person p4 = Person(10);
46 Person p5 = p4; // Person p5 = Person(p4);
47
48 //注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
49 //Person p5(p4);
50}
51
52int main() {
53
54 test01();
55 //test02();
56
57 system("pause");
58
59 return 0;
60}
III 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
1class Person {
2public:
3 Person() {
4 cout << "无参构造函数!" << endl;
5 mAge = 0;
6 }
7 Person(int age) {
8 cout << "有参构造函数!" << endl;
9 mAge = age;
10 }
11 Person(const Person& p) {
12 cout << "拷贝构造函数!" << endl;
13 mAge = p.mAge;
14 }
15 //析构函数在释放内存之前调用
16 ~Person() {
17 cout << "析构函数!" << endl;
18 }
19public:
20 int mAge;
21};
22
23//1. 使用一个已经创建完毕的对象来初始化一个新对象
24void test01() {
25
26 Person man(100); //p对象已经创建完毕
27 Person newman(man); //调用拷贝构造函数
28 Person newman2 = man; //拷贝构造
29
30 //Person newman3;
31 //newman3 = man; //不是调用拷贝构造函数,赋值操作
32}
33
34//2. 值传递的方式给函数参数传值
35//相当于Person p1 = p;
36void doWork(Person p1) {}
37void test02() {
38 Person p; //无参构造函数
39 doWork(p);
40}
41
42//3. 以值方式返回局部对象
43Person doWork2()
44{
45 Person p1;
46 cout << (int *)&p1 << endl;
47 return p1;
48}
49
50void test03()
51{
52 Person p = doWork2();
53 cout << (int *)&p << endl;
54}
55
56
57int main() {
58
59 //test01();
60 //test02();
61 test03();
62
63 system("pause");
64
65 return 0;
66}
IV 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
-
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
-
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
1class Person {
2public:
3 //无参(默认)构造函数
4 Person() {
5 cout << "无参构造函数!" << endl;
6 }
7 //有参构造函数
8 Person(int a) {
9 age = a;
10 cout << "有参构造函数!" << endl;
11 }
12 //拷贝构造函数
13 Person(const Person& p) {
14 age = p.age;
15 cout << "拷贝构造函数!" << endl;
16 }
17 //析构函数
18 ~Person() {
19 cout << "析构函数!" << endl;
20 }
21public:
22 int age;
23};
24
25void test01()
26{
27 Person p1(18);
28 //如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
29 Person p2(p1);
30
31 cout << "p2的年龄为: " << p2.age << endl;
32}
33
34void test02()
35{
36 //如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
37 Person p1; //此时如果用户自己没有提供默认构造,会出错
38 Person p2(10); //用户提供的有参
39 Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供
40
41 //如果用户提供拷贝构造,编译器不会提供其他构造函数
42 Person p4; //此时如果用户自己没有提供默认构造,会出错
43 Person p5(10); //此时如果用户自己没有提供有参,会出错
44 Person p6(p5); //用户自己提供拷贝构造
45}
46
47int main() {
48
49 test01();
50
51 system("pause");
52
53 return 0;
54}
V 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
示例:
1class Person {
2public:
3 //无参(默认)构造函数
4 Person() {
5 cout << "无参构造函数!" << endl;
6 }
7 //有参构造函数
8 Person(int age ,int height) {
9
10 cout << "有参构造函数!" << endl;
11
12 m_age = age;
13 m_height = new int(height);
14
15 }
16 //拷贝构造函数
17 Person(const Person& p) {
18 cout << "拷贝构造函数!" << endl;
19 //如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
20 m_age = p.m_age;
21 m_height = new int(*p.m_height);
22
23 }
24
25 //析构函数
26 ~Person() {
27 cout << "析构函数!" << endl;
28 if (m_height != NULL)
29 {
30 delete m_height;
31 }
32 }
33public:
34 int m_age;
35 int* m_height;
36};
VI 初始化列表
C++提供了初始化列表语法,用来初始化属性
构造函数():属性1(值1),属性2(值2)... {}
1 ////传统方式初始化
2 //Person(int a, int b, int c) {
3 // m_A = a;
4 // m_B = b;
5 // m_C = c;
6 //}
7
8 //初始化列表方式初始化
9 Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
VII 类对象作为类成员
当类中成员是其他类对象时,我们称该成员为 对象成员
构造的顺序是 :先调用对象成员的构造,再调用本类构造
析构顺序与构造相反
VIII 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
C++对象模型和this指针
成员变量和成员函数分开存储
1class Person {
2public:
3 Person() {
4 mA = 0;
5 }
6 //非静态成员变量占对象空间
7 int mA;
8 //静态成员变量不占对象空间
9 static int mB;
10 //函数也不占对象空间,所有函数共享一个函数实例
11 void func() {
12 cout << "mA:" << this->mA << endl;
13 }
14 //静态成员函数也不占对象空间
15 static void sfunc() {
16 }
17};
this指针概念
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
1Person(int age)
2 {
3 //1、当形参和成员变量同名时,可用this指针来区分
4 this->age = age;
5 }
6
7 Person& PersonAddPerson(Person p)
8 {
9 this->age += p.age;
10 //返回对象本身
11 return *this;
12 }
空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
1//空指针访问成员函数
2class Person {
3public:
4
5 void ShowClassName() {
6 cout << "我是Person类!" << endl;
7 }
8
9 void ShowPerson() {
10 if (this == NULL) {
11 return;
12 }
13 cout << mAge << endl;
14 }
15
16public:
17 int mAge;
18};
19
20void test01()
21{
22 Person * p = NULL;
23 p->ShowClassName(); //空指针,可以调用成员函数
24 p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
25}
const修饰成员函数
常函数:
- 成员函数后加const后我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
示例:
1class Person {
2public:
3 Person() {
4 m_A = 0;
5 m_B = 0;
6 }
7
8 //this指针的本质是一个指针常量,指针的指向不可修改
9 //如果想让指针指向的值也不可以修改,需要声明常函数
10 void ShowPerson() const {
11 //const Type* const pointer;
12 //this = NULL; //不能修改指针的指向 Person* const this;
13 //this->mA = 100; //但是this指针指向的对象的数据是可以修改的
14
15 //const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
16 this->m_B = 100;
17 }
18
19 void MyFunc() const {
20 //mA = 10000;
21 }
22
23public:
24 int m_A;
25 mutable int m_B; //可修改 可变的
26};
27
28
29//const修饰对象 常对象
30void test01() {
31
32 const Person person; //常量对象
33 cout << person.m_A << endl;
34 //person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
35 person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
36
37 //常对象访问成员函数
38 person.MyFunc(); //常对象不能调用const的函数
39
40}
友元
在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类 访问另一个类中私有成员
友元的关键字为 ==friend==
友元的三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
全局函数做友元
1class Building
2{
3 //告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
4 friend void goodGay(Building * building);
5
6public:
7
8 Building()
9 {
10 this->m_SittingRoom = "客厅";
11 this->m_BedRoom = "卧室";
12 }
13
14
15public:
16 string m_SittingRoom; //客厅
17
18private:
19 string m_BedRoom; //卧室
20};
21
22
23void goodGay(Building * building)
24{
25 cout << "好基友正在访问: " << building->m_SittingRoom << endl;
26 cout << "好基友正在访问: " << building->m_BedRoom << endl;
27}
28
29
30void test01()
31{
32 Building b;
33 goodGay(&b);
34}
35
类做友元
1class Building;
2class goodGay
3{
4public:
5
6 goodGay();
7 void visit();
8
9private:
10 Building *building;
11};
12
13
14class Building
15{
16 //告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
17 friend class goodGay;
18
19public:
20 Building();
21
22public:
23 string m_SittingRoom; //客厅
24private:
25 string m_BedRoom;//卧室
26};
27
28Building::Building()
29{
30 this->m_SittingRoom = "客厅";
31 this->m_BedRoom = "卧室";
32}
33
34goodGay::goodGay()
35{
36 building = new Building;
37}
38
39void goodGay::visit()
40{
41 cout << "好基友正在访问" << building->m_SittingRoom << endl;
42 cout << "好基友正在访问" << building->m_BedRoom << endl;
43}
44
45void test01()
46{
47 goodGay gg;
48 gg.visit();
49
50}
成员函数做友元
1class Building;
2class goodGay
3{
4public:
5
6 goodGay();
7 void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
8 void visit2();
9
10private:
11 Building *building;
12};
13
14
15class Building
16{
17 //告诉编译器 goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
18 friend void goodGay::visit();
19
20public:
21 Building();
22
23public:
24 string m_SittingRoom; //客厅
25private:
26 string m_BedRoom;//卧室
27};
28
29Building::Building()
30{
31 this->m_SittingRoom = "客厅";
32 this->m_BedRoom = "卧室";
33}
34
35goodGay::goodGay()
36{
37 building = new Building;
38}
39
40void goodGay::visit()
41{
42 cout << "好基友正在访问" << building->m_SittingRoom << endl;
43 cout << "好基友正在访问" << building->m_BedRoom << endl;
44}
45
46void goodGay::visit2()
47{
48 cout << "好基友正在访问" << building->m_SittingRoom << endl;
49 //cout << "好基友正在访问" << building->m_BedRoom << endl;
50}
51
52void test01()
53{
54 goodGay gg;
55 gg.visit();
56
57}
运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加号运算符重载
1class Person {
2public:
3 Person() {};
4 Person(int a, int b)
5 {
6 this->m_A = a;
7 this->m_B = b;
8 }
9 //成员函数实现 + 号运算符重载
10 Person operator+(const Person& p) {
11 Person temp;
12 temp.m_A = this->m_A + p.m_A;
13 temp.m_B = this->m_B + p.m_B;
14 return temp;
15 }
16
17
18public:
19 int m_A;
20 int m_B;
21};
22
23//全局函数实现 + 号运算符重载
24//Person operator+(const Person& p1, const Person& p2) {
25// Person temp(0, 0);
26// temp.m_A = p1.m_A + p2.m_A;
27// temp.m_B = p1.m_B + p2.m_B;
28// return temp;
29//}
30
31//运算符重载 可以发生函数重载
32Person operator+(const Person& p2, int val)
33{
34 Person temp;
35 temp.m_A = p2.m_A + val;
36 temp.m_B = p2.m_B + val;
37 return temp;
38}
39
40void test() {
41
42 Person p1(10, 10);
43 Person p2(20, 20);
44
45 //成员函数方式
46 Person p3 = p2 + p1; //相当于 p2.operaor+(p1)
47 cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
48
49
50 Person p4 = p3 + 10; //相当于 operator+(p3,10)
51 cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
52
53}
左移运算符重载
重载左移运算符配合友元可以实现输出自定义数据类型
1class Person {
2 friend ostream& operator<<(ostream& out, Person& p);
3
4public:
5
6 Person(int a, int b)
7 {
8 this->m_A = a;
9 this->m_B = b;
10 }
11
12 //成员函数 实现不了 p << cout 不是我们想要的效果
13 //void operator<<(Person& p){
14 //}
15
16private:
17 int m_A;
18 int m_B;
19};
20
21//全局函数实现左移重载
22//ostream对象只能有一个
23ostream& operator<<(ostream& out, Person& p) {
24 out << "a:" << p.m_A << " b:" << p.m_B;
25 return out;
26}
27
28void test() {
29
30 Person p1(10, 20);
31
32 cout << p1 << "hello world" << endl; //链式编程
33}
34
递增运算符重载
前置递增返回引用,后置递增返回值
1class MyInteger {
2
3 friend ostream& operator<<(ostream& out, MyInteger myint);
4
5public:
6 MyInteger() {
7 m_Num = 0;
8 }
9 //前置++
10 MyInteger& operator++() {
11 //先++
12 m_Num++;
13 //再返回
14 return *this;
15 }
16
17 //后置++
18 MyInteger operator++(int) {
19 //先返回
20 MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
21 m_Num++;
22 return temp;
23 }
24
25private:
26 int m_Num;
27};
28
29
30ostream& operator<<(ostream& out, MyInteger myint) {
31 out << myint.m_Num;
32 return out;
33}
34
35
36//前置++ 先++ 再返回
37void test01() {
38 MyInteger myInt;
39 cout << ++myInt << endl;
40 cout << myInt << endl;
41}
42
43//后置++ 先返回 再++
44void test02() {
45
46 MyInteger myInt;
47 cout << myInt++ << endl;
48 cout << myInt << endl;
49}
赋值运算符重载
c++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
1class Person
2{
3public:
4
5 Person(int age)
6 {
7 //将年龄数据开辟到堆区
8 m_Age = new int(age);
9 }
10
11 //重载赋值运算符
12 Person& operator=(Person &p)
13 {
14 if (m_Age != NULL)
15 {
16 delete m_Age;
17 m_Age = NULL;
18 }
19 //编译器提供的代码是浅拷贝
20 //m_Age = p.m_Age;
21
22 //提供深拷贝 解决浅拷贝的问题
23 m_Age = new int(*p.m_Age);
24
25 //返回自身
26 return *this;
27 }
28
29
30 ~Person()
31 {
32 if (m_Age != NULL)
33 {
34 delete m_Age;
35 m_Age = NULL;
36 }
37 }
38
39 //年龄的指针
40 int *m_Age;
41
42};
43
44
45void test01()
46{
47 Person p1(18);
48
49 Person p2(20);
50
51 Person p3(30);
52
53 p3 = p2 = p1; //赋值操作
54
55 cout << "p1的年龄为:" << *p1.m_Age << endl;
56
57 cout << "p2的年龄为:" << *p2.m_Age << endl;
58
59 cout << "p3的年龄为:" << *p3.m_Age << endl;
60}
关系运算符重载
重载关系运算符,可以让两个自定义类型对象进行对比操作
1class Person
2{
3public:
4 Person(string name, int age)
5 {
6 this->m_Name = name;
7 this->m_Age = age;
8 };
9
10 bool operator==(Person & p)
11 {
12 if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
13 {
14 return true;
15 }
16 else
17 {
18 return false;
19 }
20 }
21
22 bool operator!=(Person & p)
23 {
24 if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
25 {
26 return false;
27 }
28 else
29 {
30 return true;
31 }
32 }
33
34 string m_Name;
35 int m_Age;
36};
函数调用运算符重载
- 函数调用运算符 () 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
1class MyPrint
2{
3public:
4 void operator()(string text)
5 {
6 cout << text << endl;
7 }
8
9};
10void test01()
11{
12 //重载的()操作符 也称为仿函数
13 MyPrint myFunc;
14 myFunc("hello world");
15}
16
17
18class MyAdd
19{
20public:
21 int operator()(int v1, int v2)
22 {
23 return v1 + v2;
24 }
25};
26
27void test02()
28{
29 MyAdd add;
30 int ret = add(10, 10);
31 cout << "ret = " << ret << endl;
32
33 //匿名对象调用
34 cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
35}
继承
继承的基本语法
class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。
继承方式
继承的语法:class 子类 : 继承方式 父类
继承方式一共有三种:
- 公共继承
- 保护继承
- 私有继承
1class Base1
2{
3public:
4 int m_A;
5protected:
6 int m_B;
7private:
8 int m_C;
9};
10
11//公共继承
12class Son1 :public Base1
13{
14public:
15 void func()
16 {
17 m_A; //可访问 public权限
18 m_B; //可访问 protected权限
19 //m_C; //不可访问
20 }
21};
22
23void myClass()
24{
25 Son1 s1;
26 s1.m_A; //其他类只能访问到公共权限
27}
28
29//保护继承
30class Base2
31{
32public:
33 int m_A;
34protected:
35 int m_B;
36private:
37 int m_C;
38};
39class Son2:protected Base2
40{
41public:
42 void func()
43 {
44 m_A; //可访问 protected权限
45 m_B; //可访问 protected权限
46 //m_C; //不可访问
47 }
48};
49void myClass2()
50{
51 Son2 s;
52 //s.m_A; //不可访问
53}
54
55//私有继承
56class Base3
57{
58public:
59 int m_A;
60protected:
61 int m_B;
62private:
63 int m_C;
64};
65class Son3:private Base3
66{
67public:
68 void func()
69 {
70 m_A; //可访问 private权限
71 m_B; //可访问 private权限
72 //m_C; //不可访问
73 }
74};
75class GrandSon3 :public Son3
76{
77public:
78 void func()
79 {
80 //Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
81 //m_A;
82 //m_B;
83 //m_C;
84 }
85};
继承中的对象模型
从父类继承过来的成员,哪些属于子类对象中?
私有成员只是被隐藏了,但是还是会继承下去
继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
继承同名成员处理方式
当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
1class Base {
2public:
3 Base()
4 {
5 m_A = 100;
6 }
7
8 void func()
9 {
10 cout << "Base - func()调用" << endl;
11 }
12
13 void func(int a)
14 {
15 cout << "Base - func(int a)调用" << endl;
16 }
17
18public:
19 int m_A;
20};
21
22
23class Son : public Base {
24public:
25 Son()
26 {
27 m_A = 200;
28 }
29
30 //当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
31 //如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
32 void func()
33 {
34 cout << "Son - func()调用" << endl;
35 }
36public:
37 int m_A;
38};
39
40void test01()
41{
42 Son s;
43
44 cout << "Son下的m_A = " << s.m_A << endl;
45 cout << "Base下的m_A = " << s.Base::m_A << endl;
46
47 s.func();
48 s.Base::func();
49 s.Base::func(10);
50
51}
继承同名静态成员处理方式
继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)
1class Base {
2public:
3 static void func()
4 {
5 cout << "Base - static void func()" << endl;
6 }
7 static void func(int a)
8 {
9 cout << "Base - static void func(int a)" << endl;
10 }
11
12 static int m_A;
13};
14
15int Base::m_A = 100;
16
17class Son : public Base {
18public:
19 static void func()
20 {
21 cout << "Son - static void func()" << endl;
22 }
23 static int m_A;
24};
25
26int Son::m_A = 200;
27
28//同名成员属性
29void test01()
30{
31 //通过对象访问
32 cout << "通过对象访问: " << endl;
33 Son s;
34 cout << "Son 下 m_A = " << s.m_A << endl;
35 cout << "Base 下 m_A = " << s.Base::m_A << endl;
36
37 //通过类名访问
38 cout << "通过类名访问: " << endl;
39 cout << "Son 下 m_A = " << Son::m_A << endl;
40 cout << "Base 下 m_A = " << Son::Base::m_A << endl;
41}
42
43//同名成员函数
44void test02()
45{
46 //通过对象访问
47 cout << "通过对象访问: " << endl;
48 Son s;
49 s.func();
50 s.Base::func();
51
52 cout << "通过类名访问: " << endl;
53 Son::func();
54 Son::Base::func();
55 //出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
56 Son::Base::func(100);
57}
多继承语法
C++允许一个类继承多个类
语法: class 子类 :继承方式 父类1 , 继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
1class Base1 {
2public:
3 Base1()
4 {
5 m_A = 100;
6 }
7public:
8 int m_A;
9};
10
11class Base2 {
12public:
13 Base2()
14 {
15 m_A = 200; //开始是m_B 不会出问题,但是改为mA就会出现不明确
16 }
17public:
18 int m_A;
19};
20
21//语法:class 子类:继承方式 父类1 ,继承方式 父类2
22class Son : public Base2, public Base1
23{
24public:
25 Son()
26 {
27 m_C = 300;
28 m_D = 400;
29 }
30public:
31 int m_C;
32 int m_D;
33};
34
35
36//多继承容易产生成员同名的情况
37//通过使用类名作用域可以区分调用哪一个基类的成员
38void test01()
39{
40 Son s;
41 cout << "sizeof Son = " << sizeof(s) << endl;
42 cout << s.Base1::m_A << endl;
43 cout << s.Base2::m_A << endl;
44}
菱形继承
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
- 利用虚继承可以解决菱形继承问题
1class Animal
2{
3public:
4 int m_Age;
5};
6
7//继承前加virtual关键字后,变为虚继承
8//此时公共的父类Animal称为虚基类
9class Sheep : virtual public Animal {};
10class Tuo : virtual public Animal {};
11class SheepTuo : public Sheep, public Tuo {};
12
13void test01()
14{
15 SheepTuo st;
16 st.Sheep::m_Age = 100;
17 st.Tuo::m_Age = 200;
18
19 cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
20 cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
21 cout << "st.m_Age = " << st.m_Age << endl;
22}
多态
多态分为两类
- 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态使用条件
- 父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
1class Animal
2{
3public:
4 //Speak函数就是虚函数
5 //函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
6 virtual void speak()
7 {
8 cout << "动物在说话" << endl;
9 }
10};
11
12class Cat :public Animal
13{
14public:
15 void speak()
16 {
17 cout << "小猫在说话" << endl;
18 }
19};
20
21class Dog :public Animal
22{
23public:
24
25 void speak()
26 {
27 cout << "小狗在说话" << endl;
28 }
29
30};
31//我们希望传入什么对象,那么就调用什么对象的函数
32//如果函数地址在编译阶段就能确定,那么静态联编
33//如果函数地址在运行阶段才能确定,就是动态联编
34
35void DoSpeak(Animal & animal)
36{
37 animal.speak();
38}
39//
40//多态满足条件:
41//1、有继承关系
42//2、子类重写父类中的虚函数
43//多态使用:
44//父类指针或引用指向子类对象
45
46void test01()
47{
48 Cat cat;
49 DoSpeak(cat);
50
51
52 Dog dog;
53 DoSpeak(dog);
54}
多态案例一-计算器类
1//多态实现
2//抽象计算器类
3//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
4class AbstractCalculator
5{
6public :
7
8 virtual int getResult()
9 {
10 return 0;
11 }
12
13 int m_Num1;
14 int m_Num2;
15};
16
17//加法计算器
18class AddCalculator :public AbstractCalculator
19{
20public:
21 int getResult()
22 {
23 return m_Num1 + m_Num2;
24 }
25};
26
27//减法计算器
28class SubCalculator :public AbstractCalculator
29{
30public:
31 int getResult()
32 {
33 return m_Num1 - m_Num2;
34 }
35};
36
37//乘法计算器
38class MulCalculator :public AbstractCalculator
39{
40public:
41 int getResult()
42 {
43 return m_Num1 * m_Num2;
44 }
45};
46
47
48void test02()
49{
50 //创建加法计算器
51 AbstractCalculator *abc = new AddCalculator;
52 abc->m_Num1 = 10;
53 abc->m_Num2 = 10;
54 cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
55 delete abc; //用完了记得销毁
56
57 //创建减法计算器
58 abc = new SubCalculator;
59 abc->m_Num1 = 10;
60 abc->m_Num2 = 10;
61 cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
62 delete abc;
63
64 //创建乘法计算器
65 abc = new MulCalculator;
66 abc->m_Num1 = 10;
67 abc->m_Num2 = 10;
68 cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
69 delete abc;
70}
71
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
1class Base
2{
3public:
4 //纯虚函数
5 //类中只要有一个纯虚函数就称为抽象类
6 //抽象类无法实例化对象
7 //子类必须重写父类中的纯虚函数,否则也属于抽象类
8 virtual void func() = 0;
9};
10
11class Son :public Base
12{
13public:
14 virtual void func()
15 {
16 cout << "func调用" << endl;
17 };
18};
19
20void test01()
21{
22 Base * base = NULL;
23 //base = new Base; // 错误,抽象类无法实例化对象
24 base = new Son;
25 base->func();
26 delete base;//记得销毁
27}
多态案例二-制作饮品
1//抽象制作饮品
2class AbstractDrinking {
3public:
4 //烧水
5 virtual void Boil() = 0;
6 //冲泡
7 virtual void Brew() = 0;
8 //倒入杯中
9 virtual void PourInCup() = 0;
10 //加入辅料
11 virtual void PutSomething() = 0;
12 //规定流程
13 void MakeDrink() {
14 Boil();
15 Brew();
16 PourInCup();
17 PutSomething();
18 }
19};
20
21//制作咖啡
22class Coffee : public AbstractDrinking {
23public:
24 //烧水
25 virtual void Boil() {
26 cout << "煮农夫山泉!" << endl;
27 }
28 //冲泡
29 virtual void Brew() {
30 cout << "冲泡咖啡!" << endl;
31 }
32 //倒入杯中
33 virtual void PourInCup() {
34 cout << "将咖啡倒入杯中!" << endl;
35 }
36 //加入辅料
37 virtual void PutSomething() {
38 cout << "加入牛奶!" << endl;
39 }
40};
41
42//制作茶水
43class Tea : public AbstractDrinking {
44public:
45 //烧水
46 virtual void Boil() {
47 cout << "煮自来水!" << endl;
48 }
49 //冲泡
50 virtual void Brew() {
51 cout << "冲泡茶叶!" << endl;
52 }
53 //倒入杯中
54 virtual void PourInCup() {
55 cout << "将茶水倒入杯中!" << endl;
56 }
57 //加入辅料
58 virtual void PutSomething() {
59 cout << "加入枸杞!" << endl;
60 }
61};
62
63//业务函数
64void DoWork(AbstractDrinking* drink) {
65 drink->MakeDrink();
66 delete drink;
67}
68
69void test01() {
70 DoWork(new Coffee);
71 cout << "--------------" << endl;
72 DoWork(new Tea);
73}
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类
1class Animal {
2public:
3
4 Animal()
5 {
6 cout << "Animal 构造函数调用!" << endl;
7 }
8 virtual void Speak() = 0;
9
10 //析构函数加上virtual关键字,变成虚析构函数
11 //virtual ~Animal()
12 //{
13 // cout << "Animal虚析构函数调用!" << endl;
14 //}
15
16
17 virtual ~Animal() = 0;
18};
19
20Animal::~Animal()
21{
22 cout << "Animal 纯虚析构函数调用!" << endl;
23}
24
25//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。
26
27class Cat : public Animal {
28public:
29 Cat(string name)
30 {
31 cout << "Cat构造函数调用!" << endl;
32 m_Name = new string(name);
33 }
34 virtual void Speak()
35 {
36 cout << *m_Name << "小猫在说话!" << endl;
37 }
38 ~Cat()
39 {
40 cout << "Cat析构函数调用!" << endl;
41 if (this->m_Name != NULL) {
42 delete m_Name;
43 m_Name = NULL;
44 }
45 }
46
47public:
48 string *m_Name;
49};
50
51void test01()
52{
53 Animal *animal = new Cat("Tom");
54 animal->Speak();
55
56 //通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
57 //怎么解决?给基类增加一个虚析构函数
58 //虚析构函数就是用来解决通过父类指针释放子类对象
59 delete animal;
60}
多态案例三-电脑组装
14.文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件 < fstream >
文件类型分为两种:
- 文本文件 - 文件以文本的ASCII码形式存储在计算机中
- 二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
- ofstream:写操作
- ifstream: 读操作
- fstream : 读写操作
文本文件
写文件
写文件步骤如下:
-
包含头文件
#include <fstream>
-
创建流对象
ofstream ofs;
-
打开文件
ofs.open(“文件路径”,打开方式);
-
写数据
ofs « “写入的数据”;
-
关闭文件
ofs.close();
文件打开方式:
打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
注意: 文件打开方式可以配合使用,利用|操作符
**例如:**用二进制方式写文件 ios::binary | ios:: out
示例:
1#include <fstream>
2
3void test01()
4{
5 ofstream ofs;
6 ofs.open("test.txt", ios::out);
7
8 ofs << "姓名:张三" << endl;
9 ofs << "性别:男" << endl;
10 ofs << "年龄:18" << endl;
11
12 ofs.close();
13}
14
15int main() {
16
17 test01();
18
19 system("pause");
20
21 return 0;
22}
总结:
- 文件操作必须包含头文件 fstream
- 读文件可以利用 ofstream ,或者fstream类
- 打开文件时候需要指定操作文件的路径,以及打开方式
- 利用«可以向文件中写数据
- 操作完毕,要关闭文件
读文件
读文件步骤如下:
-
包含头文件
#include <fstream>
-
创建流对象
ifstream ifs;
-
打开文件并判断文件是否打开成功
ifs.open(“文件路径”,打开方式);
-
读数据
四种方式读取
-
关闭文件
ifs.close();示例:
1#include <fstream> 2#include <string> 3void test01() 4{ 5 ifstream ifs; 6 ifs.open("test.txt", ios::in); 7 8 if (!ifs.is_open()) 9 { 10 cout << "文件打开失败" << endl; 11 return; 12 } 13 14 //第一种方式 15 //char buf[1024] = { 0 }; 16 //while (ifs >> buf) 17 //{ 18 // cout << buf << endl; 19 //} 20 21 //第二种 22 //char buf[1024] = { 0 }; 23 //while (ifs.getline(buf,sizeof(buf))) 24 //{ 25 // cout << buf << endl; 26 //} 27 28 //第三种 29 //string buf; 30 //while (getline(ifs, buf)) 31 //{ 32 // cout << buf << endl; 33 //} 34 35 char c; 36 while ((c = ifs.get()) != EOF) 37 { 38 cout << c; 39 } 40 41 ifs.close(); 42 43 44} 45 46int main() { 47 48 test01(); 49 50 system("pause"); 51 52 return 0; 53}
总结:
- 读文件可以利用 ifstream ,或者fstream类
- 利用is_open函数可以判断文件是否打开成功
- close 关闭文件
二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为 ios::binary
写文件
二进制方式写文件主要利用流对象调用成员函数write
函数原型 :ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
1#include <fstream>
2#include <string>
3
4class Person
5{
6public:
7 char m_Name[64];
8 int m_Age;
9};
10
11//二进制文件 写文件
12void test01()
13{
14 //1、包含头文件
15
16 //2、创建输出流对象
17 ofstream ofs("person.txt", ios::out | ios::binary);
18
19 //3、打开文件
20 //ofs.open("person.txt", ios::out | ios::binary);
21
22 Person p = {"张三" , 18};
23
24 //4、写文件
25 ofs.write((const char *)&p, sizeof(p));
26
27 //5、关闭文件
28 ofs.close();
29}
读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
示例:
1#include <fstream>
2#include <string>
3
4class Person
5{
6public:
7 char m_Name[64];
8 int m_Age;
9};
10
11void test01()
12{
13 ifstream ifs("person.txt", ios::in | ios::binary);
14 if (!ifs.is_open())
15 {
16 cout << "文件打开失败" << endl;
17 }
18
19 Person p;
20 ifs.read((char *)&p, sizeof(p));
21
22 cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
23}
15.模板
模板的概念
模板就是建立通用的模具,大大提高复用性
函数模板
-
C++另一种编程思想称为 泛型编程,主要利用的技术就是模板
-
C++提供两种模板机制:函数模板和类模板
函数模板语法
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
语法:
1template<typename T>
2函数声明或定义
解释:
template — 声明创建模板
typename — 表面其后面的符号是一种数据类型,可以用class代替
T — 通用的数据类型,名称可以替换,通常为大写字母
示例:
1//交换整型函数
2void swapInt(int& a, int& b) {
3 int temp = a;
4 a = b;
5 b = temp;
6}
7
8//交换浮点型函数
9void swapDouble(double& a, double& b) {
10 double temp = a;
11 a = b;
12 b = temp;
13}
14
15//利用模板提供通用的交换函数
16template<typename T>
17void mySwap(T& a, T& b)
18{
19 T temp = a;
20 a = b;
21 b = temp;
22}
23
24void test01()
25{
26 int a = 10;
27 int b = 20;
28
29 //swapInt(a, b);
30
31 //利用模板实现交换
32 //1、自动类型推导
33 mySwap(a, b);
34
35 //2、显示指定类型
36 mySwap<int>(a, b);
37
38 cout << "a = " << a << endl;
39 cout << "b = " << b << endl;
40
41}
总结:
- 函数模板利用关键字 template
- 使用函数模板有两种方式:自动类型推导、显示指定类型
- 模板的目的是为了提高复用性,将类型参数化
函数模板注意事项
-
自动类型推导,必须推导出一致的数据类型T,才可以使用
-
模板必须要确定出T的数据类型,才可以使用
1//利用模板提供通用的交换函数
2template<class T>
3void mySwap(T& a, T& b)
4{
5 T temp = a;
6 a = b;
7 b = temp;
8}
9
10
11// 1、自动类型推导,必须推导出一致的数据类型T,才可以使用
12void test01()
13{
14 int a = 10;
15 int b = 20;
16 char c = 'c';
17
18 mySwap(a, b); // 正确,可以推导出一致的T
19 //mySwap(a, c); // 错误,推导不出一致的T类型
20}
21
22
23// 2、模板必须要确定出T的数据类型,才可以使用
24template<class T>
25void func()
26{
27 cout << "func 调用" << endl;
28}
29
30void test02()
31{
32 //func(); //错误,模板不能独立使用,必须确定出T的类型
33 func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
34}
函数模板案例
案例描述:
- 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
- 排序规则从大到小,排序算法为选择排序
- 分别利用char数组和int数组进行测试
1//交换的函数模板
2template<typename T>
3void mySwap(T &a, T&b)
4{
5 T temp = a;
6 a = b;
7 b = temp;
8}
9
10
11template<class T> // 也可以替换成typename
12//利用选择排序,进行对数组从大到小的排序
13void mySort(T arr[], int len)
14{
15 for (int i = 0; i < len; i++)
16 {
17 int max = i; //最大数的下标
18 for (int j = i + 1; j < len; j++)
19 {
20 if (arr[max] < arr[j])
21 {
22 max = j;
23 }
24 }
25 if (max != i) //如果最大数的下标不是i,交换两者
26 {
27 mySwap(arr[max], arr[i]);
28 }
29 }
30}
31template<typename T>
32void printArray(T arr[], int len) {
33
34 for (int i = 0; i < len; i++) {
35 cout << arr[i] << " ";
36 }
37 cout << endl;
38}
39void test01()
40{
41 //测试char数组
42 char charArr[] = "bdcfeagh";
43 int num = sizeof(charArr) / sizeof(char);
44 mySort(charArr, num);
45 printArray(charArr, num);
46}
47
48void test02()
49{
50 //测试int数组
51 int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };
52 int num = sizeof(intArr) / sizeof(int);
53 mySort(intArr, num);
54 printArray(intArr, num);
55}
普通函数与函数模板的区别
普通函数与函数模板区别:
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
1//普通函数
2int myAdd01(int a, int b)
3{
4 return a + b;
5}
6
7//函数模板
8template<class T>
9T myAdd02(T a, T b)
10{
11 return a + b;
12}
13
14//使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换
15void test01()
16{
17 int a = 10;
18 int b = 20;
19 char c = 'c';
20
21 cout << myAdd01(a, c) << endl; //正确,将char类型的'c'隐式转换为int类型 'c' 对应 ASCII码 99
22
23 //myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换
24
25 myAdd02<int>(a, c); //正确,如果用显示指定类型,可以发生隐式类型转换
26}
普通函数与函数模板的调用规则
调用规则如下:
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
1//普通函数与函数模板调用规则
2void myPrint(int a, int b)
3{
4 cout << "调用的普通函数" << endl;
5}
6
7template<typename T>
8void myPrint(T a, T b)
9{
10 cout << "调用的模板" << endl;
11}
12
13template<typename T>
14void myPrint(T a, T b, T c)
15{
16 cout << "调用重载的模板" << endl;
17}
18
19void test01()
20{
21 //1、如果函数模板和普通函数都可以实现,优先调用普通函数
22 // 注意 如果告诉编译器 普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到
23 int a = 10;
24 int b = 20;
25 myPrint(a, b); //调用普通函数
26
27 //2、可以通过空模板参数列表来强制调用函数模板
28 myPrint<>(a, b); //调用函数模板
29
30 //3、函数模板也可以发生重载
31 int c = 30;
32 myPrint(a, b, c); //调用重载的函数模板
33
34 //4、 如果函数模板可以产生更好的匹配,优先调用函数模板
35 char c1 = 'a';
36 char c2 = 'b';
37 myPrint(c1, c2); //调用函数模板
38}
模板的局限性
例如:
1 template<class T>
2 void f(T a, T b)
3 {
4 a = b;
5 }
在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了
再例如:
1 template<class T>
2 void f(T a, T b)
3 {
4 if(a > b) { ... }
5 }
在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行
因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板
1//普通函数模板
2template<class T>
3bool myCompare(T& a, T& b)
4{
5 if (a == b)
6 {
7 return true;
8 }
9 else
10 {
11 return false;
12 }
13}
14
15
16//具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型
17//具体化优先于常规模板
18template<> bool myCompare(Person &p1, Person &p2)
19{
20 if ( p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age)
21 {
22 return true;
23 }
24 else
25 {
26 return false;
27 }
28}
29
30void test01()
31{
32 int a = 10;
33 int b = 20;
34 //内置数据类型可以直接使用通用的函数模板
35 bool ret = myCompare(a, b);
36 if (ret)
37 {
38 cout << "a == b " << endl;
39 }
40 else
41 {
42 cout << "a != b " << endl;
43 }
44}
45
46void test02()
47{
48 Person p1("Tom", 10);
49 Person p2("Tom", 10);
50 //自定义数据类型,不会调用普通的函数模板
51 //可以创建具体化的Person数据类型的模板,用于特殊处理这个类型
52 bool ret = myCompare(p1, p2);
53 if (ret)
54 {
55 cout << "p1 == p2 " << endl;
56 }
57 else
58 {
59 cout << "p1 != p2 " << endl;
60 }
61}
类模板
类模板语法
建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
语法:
1template<typename T>
2类
解释:
template — 声明创建模板
typename — 表面其后面的符号是一种数据类型,可以用class代替
T — 通用的数据类型,名称可以替换,通常为大写字母
示例:
1#include <string>
2//类模板
3template<class NameType, class AgeType>
4class Person
5{
6public:
7 Person(NameType name, AgeType age)
8 {
9 this->mName = name;
10 this->mAge = age;
11 }
12 void showPerson()
13 {
14 cout << "name: " << this->mName << " age: " << this->mAge << endl;
15 }
16public:
17 NameType mName;
18 AgeType mAge;
19};
20
21void test01()
22{
23 // 指定NameType 为string类型,AgeType 为 int类型
24 Person<string, int>P1("孙悟空", 999);
25 P1.showPerson();
26}
类模板与函数模板区别
类模板与函数模板区别主要有两点:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
1#include <string>
2//类模板
3template<class NameType, class AgeType = int>
4class Person
5{
6public:
7 Person(NameType name, AgeType age)
8 {
9 this->mName = name;
10 this->mAge = age;
11 }
12 void showPerson()
13 {
14 cout << "name: " << this->mName << " age: " << this->mAge << endl;
15 }
16public:
17 NameType mName;
18 AgeType mAge;
19};
20
21//1、类模板没有自动类型推导的使用方式
22void test01()
23{
24 // Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导
25 Person <string ,int>p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板
26 p.showPerson();
27}
28
29//2、类模板在模板参数列表中可以有默认参数
30void test02()
31{
32 Person <string> p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数
33 p.showPerson();
34}
类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
类模板对象做函数参数
一共有三种传入方式:
- 指定传入的类型 — 直接显示对象的数据类型
- 参数模板化 — 将对象中的参数变为模板进行传递
- 整个类模板化 — 将这个对象类型 模板化进行传递
1//1、指定传入的类型
2void printPerson1(Person<string, int> &p)
3{
4 p.showPerson();
5}
6void test01()
7{
8 Person <string, int >p("孙悟空", 100);
9 printPerson1(p);
10}
11
12//2、参数模板化
13template <class T1, class T2>
14void printPerson2(Person<T1, T2>&p)
15{
16 p.showPerson();
17 cout << "T1的类型为: " << typeid(T1).name() << endl;
18 cout << "T2的类型为: " << typeid(T2).name() << endl;
19}
20void test02()
21{
22 Person <string, int >p("猪八戒", 90);
23 printPerson2(p);
24}
25
26//3、整个类模板化
27template<class T>
28void printPerson3(T & p)
29{
30 cout << "T的类型为: " << typeid(T).name() << endl;
31 p.showPerson();
32
33}
34void test03()
35{
36 Person <string, int >p("唐僧", 30);
37 printPerson3(p);
38}
类模板与继承
当类模板碰到继承时,需要注意一下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需变为类模板
1template<class T>
2class Base
3{
4 T m;
5};
6
7//class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
8class Son :public Base<int> //必须指定一个类型
9{
10};
11void test01()
12{
13 Son c;
14}
15
16//类模板继承类模板 ,可以用T2指定父类中的T类型
17template<class T1, class T2>
18class Son2 :public Base<T2>
19{
20public:
21 Son2()
22 {
23 cout << typeid(T1).name() << endl;
24 cout << typeid(T2).name() << endl;
25 }
26};
27
28void test02()
29{
30 Son2<int, char> child1;
31}
类模板成员函数 类外实现
1#include <string>
2
3//类模板中成员函数类外实现
4template<class T1, class T2>
5class Person {
6public:
7 //成员函数类内声明
8 Person(T1 name, T2 age);
9 void showPerson();
10
11public:
12 T1 m_Name;
13 T2 m_Age;
14};
15
16//构造函数 类外实现
17template<class T1, class T2>
18Person<T1, T2>::Person(T1 name, T2 age) {
19 this->m_Name = name;
20 this->m_Age = age;
21}
22
23//成员函数 类外实现
24template<class T1, class T2>
25void Person<T1, T2>::showPerson() {
26 cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
27}
28
29void test01()
30{
31 Person<string, int> p("Tom", 20);
32 p.showPerson();
33}
类模板分文件编写
问题:
- 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
- 解决方式1:直接包含.cpp源文件
- 解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp
person.hpp中代码:
1#pragma once
2#include <iostream>
3using namespace std;
4#include <string>
5
6template<class T1, class T2>
7class Person {
8public:
9 Person(T1 name, T2 age);
10 void showPerson();
11public:
12 T1 m_Name;
13 T2 m_Age;
14};
15
16//构造函数 类外实现
17template<class T1, class T2>
18Person<T1, T2>::Person(T1 name, T2 age) {
19 this->m_Name = name;
20 this->m_Age = age;
21}
22
23//成员函数 类外实现
24template<class T1, class T2>
25void Person<T1, T2>::showPerson() {
26 cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
27}
类模板分文件编写.cpp中代码
1#include<iostream>
2using namespace std;
3
4//#include "person.h"
5#include "person.cpp" //解决方式1,包含cpp源文件
6
7//解决方式2,将声明和实现写到一起,文件后缀名改为.hpp
8#include "person.hpp"
9void test01()
10{
11 Person<string, int> p("Tom", 10);
12 p.showPerson();
13}
14
15int main() {
16
17 test01();
18
19 system("pause");
20
21 return 0;
22}
类模板与友元
全局函数类内实现 - 直接在类内声明友元即可
全局函数类外实现 - 需要提前让编译器知道全局函数的存在
建议全局函数做类内实现,用法简单,而且编译器可以直接识别
1#include <string>
2
3//2、全局函数配合友元 类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元
4template<class T1, class T2> class Person;
5
6//如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到
7//template<class T1, class T2> void printPerson2(Person<T1, T2> & p);
8
9template<class T1, class T2>
10void printPerson2(Person<T1, T2> & p)
11{
12 cout << "类外实现 ---- 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
13}
14
15template<class T1, class T2>
16class Person
17{
18 //1、全局函数配合友元 类内实现
19 friend void printPerson(Person<T1, T2> & p)
20 {
21 cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
22 }
23
24
25 //全局函数配合友元 类外实现
26 friend void printPerson2<>(Person<T1, T2> & p);
27
28public:
29
30 Person(T1 name, T2 age)
31 {
32 this->m_Name = name;
33 this->m_Age = age;
34 }
35
36
37private:
38 T1 m_Name;
39 T2 m_Age;
40
41};
42
43//1、全局函数在类内实现
44void test01()
45{
46 Person <string, int >p("Tom", 20);
47 printPerson(p);
48}
49
50
51//2、全局函数在类外实现
52void test02()
53{
54 Person <string, int >p("Jerry", 30);
55 printPerson2(p);
56}
16.STL初识
STL基本概念
- STL(Standard Template Library,标准模板库)
- STL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator)
- 容器和算法之间通过迭代器进行无缝连接。
- STL 几乎所有的代码都采用了模板类或者模板函数
STL六大组件
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
- 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
- 算法:各种常用的算法,如sort、find、copy、for_each等
- 迭代器:扮演了容器与算法之间的胶合剂。
- 仿函数:行为类似函数,可作为算法的某种策略。
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
- 空间配置器:负责空间的配置与管理。
STL中容器、算法、迭代器
**容器:**置物之所也
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组, 链表,树, 栈, 队列, 集合, 映射表 等
这些容器分为序列式容器和关联式容器两种:
序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
**算法:**问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)
算法分为:质变算法和非质变算法。
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等
**迭代器:**容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针
迭代器种类:
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数据的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 对数据的只写访问 | 只写,支持++ |
前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 读写操作,并能向前和向后操作 | 读写,支持++、–, |
随机访问迭代器 | 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 | 读写,支持++、–、[n]、-n、<、<=、>、>= |
常用的容器中迭代器种类为双向迭代器,和随机访问迭代器
容器算法迭代器初识
vector存放内置数据类型
1#include <vector>
2#include <algorithm>
3
4void MyPrint(int val)
5{
6 cout << val << endl;
7}
8
9void test01() {
10
11 //创建vector容器对象,并且通过模板参数指定容器中存放的数据的类型
12 vector<int> v;
13 //向容器中放数据
14 v.push_back(10);
15 v.push_back(20);
16 v.push_back(30);
17 v.push_back(40);
18
19 //每一个容器都有自己的迭代器,迭代器是用来遍历容器中的元素
20 //v.begin()返回迭代器,这个迭代器指向容器中第一个数据
21 //v.end()返回迭代器,这个迭代器指向容器元素的最后一个元素的下一个位置
22 //vector<int>::iterator 拿到vector<int>这种容器的迭代器类型
23
24 vector<int>::iterator pBegin = v.begin();
25 vector<int>::iterator pEnd = v.end();
26
27 //第一种遍历方式:
28 while (pBegin != pEnd) {
29 cout << *pBegin << endl;
30 pBegin++;
31 }
32
33
34 //第二种遍历方式:
35 for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
36 cout << *it << endl;
37 }
38 cout << endl;
39
40 //第三种遍历方式:
41 //使用STL提供标准遍历算法 头文件 algorithm
42 for_each(v.begin(), v.end(), MyPrint);
43}
Vector存放自定义数据类型
1//存放对象
2void test01() {
3
4 vector<Person> v;
5
6 //创建数据
7 Person p1("aaa", 10);
8 Person p2("bbb", 20);
9 Person p3("ccc", 30);
10 Person p4("ddd", 40);
11 Person p5("eee", 50);
12
13 v.push_back(p1);
14 v.push_back(p2);
15 v.push_back(p3);
16 v.push_back(p4);
17 v.push_back(p5);
18
19 for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
20 cout << "Name:" << (*it).mName << " Age:" << (*it).mAge << endl;
21
22 }
23}
24
25
26//放对象指针
27void test02() {
28
29 vector<Person*> v;
30
31 //创建数据
32 Person p1("aaa", 10);
33 Person p2("bbb", 20);
34 Person p3("ccc", 30);
35 Person p4("ddd", 40);
36 Person p5("eee", 50);
37
38 v.push_back(&p1);
39 v.push_back(&p2);
40 v.push_back(&p3);
41 v.push_back(&p4);
42 v.push_back(&p5);
43
44 for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {
45 Person * p = (*it);
46 cout << "Name:" << p->mName << " Age:" << (*it)->mAge << endl;
47 }
48}
Vector容器嵌套容器
1#include <vector>
2
3//容器嵌套容器
4void test01() {
5
6 vector< vector<int> > v;
7
8 vector<int> v1;
9 vector<int> v2;
10 vector<int> v3;
11 vector<int> v4;
12
13 for (int i = 0; i < 4; i++) {
14 v1.push_back(i + 1);
15 v2.push_back(i + 2);
16 v3.push_back(i + 3);
17 v4.push_back(i + 4);
18 }
19
20 //将容器元素插入到vector v中
21 v.push_back(v1);
22 v.push_back(v2);
23 v.push_back(v3);
24 v.push_back(v4);
25
26
27 for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) {
28
29 for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) {
30 cout << *vit << " ";
31 }
32 cout << endl;
33 }
34
35}
17.STL- 常用容器
string容器
string基本概念
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初始化
1#include <string>
2//string构造
3void test01()
4{
5 string s1; //创建空字符串,调用无参构造函数
6 cout << "str1 = " << s1 << endl;
7
8 const char* str = "hello world";
9 string s2(str); //把c_string转换成了string
10
11 cout << "str2 = " << s2 << endl;
12
13 string s3(s2); //调用拷贝构造函数
14 cout << "str3 = " << s3 << endl;
15
16 string s4(10, 'a');
17 cout << "str3 = " << s3 << endl;
18}
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赋给当前字符串
1void test01()
2{
3 string str1;
4 str1 = "hello world";
5 cout << "str1 = " << str1 << endl;
6
7 string str2;
8 str2 = str1;
9 cout << "str2 = " << str2 << endl;
10
11 string str3;
12 str3 = 'a';
13 cout << "str3 = " << str3 << endl;
14
15 string str4;
16 str4.assign("hello c++");
17 cout << "str4 = " << str4 << endl;
18
19 string str5;
20 str5.assign("hello c++",5);
21 cout << "str5 = " << str5 << endl;
22
23
24 string str6;
25 str6.assign(str5);
26 cout << "str6 = " << str6 << endl;
27
28 string str7;
29 str7.assign(5, 'x');
30 cout << "str7 = " << str7 << endl;
31}
32
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个字符连接到字符串结尾
1void test01()
2{
3 string str1 = "我";
4
5 str1 += "爱玩游戏";
6
7 cout << "str1 = " << str1 << endl;
8
9 str1 += ':';
10
11 cout << "str1 = " << str1 << endl;
12
13 string str2 = "LOL DNF";
14
15 str1 += str2;
16
17 cout << "str1 = " << str1 << endl;
18
19 string str3 = "I";
20 str3.append(" love ");
21 str3.append("game abcde", 4);
22 //str3.append(str2);
23 str3.append(str2, 4, 3); // 从下标4位置开始 ,截取3个字符,拼接到字符串末尾
24 cout << "str3 = " << str3 << endl;
25}
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个字符为字符串strstring& replace(int pos, int n,const char* s);
//替换从pos开始的n个字符为字符串s
find找到字符串后返回查找的第一个字符位置,找不到返回-1
1//查找和替换
2void test01()
3{
4 //查找
5 string str1 = "abcdefgde";
6
7 int pos = str1.find("de");
8
9 if (pos == -1)
10 {
11 cout << "未找到" << endl;
12 }
13 else
14 {
15 cout << "pos = " << pos << endl;
16 }
17
18
19 pos = str1.rfind("de");
20
21 cout << "pos = " << pos << endl;
22
23}
24
25void test02()
26{
27 //替换
28 string str1 = "abcdefgde";
29 str1.replace(1, 3, "1111");
30
31 cout << "str1 = " << str1 << endl;
32}
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方法获取字符
1void test01()
2{
3 string str = "hello world";
4
5 for (int i = 0; i < str.size(); i++)
6 {
7 cout << str[i] << " ";
8 }
9 cout << endl;
10
11 for (int i = 0; i < str.size(); i++)
12 {
13 cout << str.at(i) << " ";
14 }
15 cout << endl;
16
17
18 //字符修改
19 str[0] = 'x';
20 str.at(1) = 'x';
21 cout << str << endl;
22
23}
string插入和删除
string& insert(int pos, const char* s);
//插入字符串string& insert(int pos, const string& str);
//插入字符串string& insert(int pos, int n, char c);
//在指定位置插入n个字符cstring& erase(int pos, int n = npos);
//删除从Pos开始的n个字符
string子串
string substr(int pos = 0, int n = npos) const;
//返回由pos开始的n个字符组成的字符串
1//子串
2void test01()
3{
4
5 string str = "abcdefg";
6 string subStr = str.substr(1, 3);
7 cout << "subStr = " << subStr << endl;
8
9 string email = "[email protected]";
10 int pos = email.find("@");
11 string username = email.substr(0, pos);
12 cout << "username: " << username << endl;
13
14}
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);
//拷贝构造函数。
1#include <vector>
2
3void printVector(vector<int>& v) {
4
5 for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
6 cout << *it << " ";
7 }
8 cout << endl;
9}
10
11void test01()
12{
13 vector<int> v1; //无参构造
14 for (int i = 0; i < 10; i++)
15 {
16 v1.push_back(i);
17 }
18 printVector(v1);
19
20 vector<int> v2(v1.begin(), v1.end());
21 printVector(v2);
22
23 vector<int> v3(10, 100);
24 printVector(v3);
25
26 vector<int> v4(v3);
27 printVector(v4);
28}
vector赋值操作
-
vector& operator=(const vector &vec);
//重载等号操作符 -
assign(beg, end);
//将[beg, end)区间中的数据拷贝赋值给本身。 -
assign(n, elem);
//将n个elem拷贝赋值给本身。
1#include <vector>
2
3void printVector(vector<int>& v) {
4
5 for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
6 cout << *it << " ";
7 }
8 cout << endl;
9}
10
11//赋值操作
12void test01()
13{
14 vector<int> v1; //无参构造
15 for (int i = 0; i < 10; i++)
16 {
17 v1.push_back(i);
18 }
19 printVector(v1);
20
21 vector<int>v2;
22 v2 = v1;
23 printVector(v2);
24
25 vector<int>v3;
26 v3.assign(v1.begin(), v1.end());
27 printVector(v3);
28
29 vector<int>v4;
30 v4.assign(10, 100);
31 printVector(v4);
32}
vector容量和大小
-
empty();
//判断容器是否为空 -
capacity();
//容器的容量 -
size();
//返回容器中元素的个数 -
resize(int num);
//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。 //如果容器变短,则末尾超出容器长度的元素被删除。
-
resize(int num, elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。 //如果容器变短,则末尾超出容器长度的元素被删除
1void test01()
2{
3 vector<int> v1;
4 for (int i = 0; i < 10; i++)
5 {
6 v1.push_back(i);
7 }
8 printVector(v1);
9 if (v1.empty())
10 {
11 cout << "v1为空" << endl;
12 }
13 else
14 {
15 cout << "v1不为空" << endl;
16 cout << "v1的容量 = " << v1.capacity() << endl;
17 cout << "v1的大小 = " << v1.size() << endl;
18 }
19
20 //resize 重新指定大小 ,若指定的更大,默认用0填充新位置,可以利用重载版本替换默认填充
21 v1.resize(15,10);
22 printVector(v1);
23
24 //resize 重新指定大小 ,若指定的更小,超出部分元素被删除
25 v1.resize(5);
26 printVector(v1);
27}
vector插入和删除
push_back(ele);
//尾部插入元素elepop_back();
//删除最后一个元素insert(const_iterator pos, ele);
//迭代器指向位置pos插入元素eleinsert(const_iterator pos, int count,ele);
//迭代器指向位置pos插入count个元素eleerase(const_iterator pos);
//删除迭代器指向的元素erase(const_iterator start, const_iterator end);
//删除迭代器从start到end之间的元素clear();
//删除容器中所有元素
1void test01()
2{
3 vector<int> v1;
4 //尾插
5 v1.push_back(10);
6 v1.push_back(20);
7 v1.push_back(30);
8 v1.push_back(40);
9 v1.push_back(50);
10 printVector(v1);
11 //尾删
12 v1.pop_back();
13 printVector(v1);
14 //插入
15 v1.insert(v1.begin(), 100);
16 printVector(v1);
17
18 v1.insert(v1.begin(), 2, 1000);
19 printVector(v1);
20
21 //删除
22 v1.erase(v1.begin());
23 printVector(v1);
24
25 //清空
26 v1.erase(v1.begin(), v1.end());
27 v1.clear();
28 printVector(v1);
29}
vector数据存取
at(int idx);
//返回索引idx所指的数据operator[];
//返回索引idx所指的数据front();
//返回容器中第一个数据元素back();
//返回容器中最后一个数据元素
vector互换容器
swap(vec);
// 将vec与本身的元素互换
swap可以使两个容器互换,可以达到实用的收缩内存效果
1void test01()
2{
3 vector<int>v1;
4 for (int i = 0; i < 10; i++)
5 {
6 v1.push_back(i);
7 }
8 printVector(v1);
9
10 vector<int>v2;
11 for (int i = 10; i > 0; i--)
12 {
13 v2.push_back(i);
14 }
15 printVector(v2);
16
17 //互换容器
18 cout << "互换后" << endl;
19 v1.swap(v2);
20 printVector(v1);
21 printVector(v2);
22}
23
24void test02()
25{
26 vector<int> v;
27 for (int i = 0; i < 100000; i++) {
28 v.push_back(i);
29 }
30
31 cout << "v的容量为:" << v.capacity() << endl;
32 cout << "v的大小为:" << v.size() << endl;
33
34 v.resize(3);
35
36 cout << "v的容量为:" << v.capacity() << endl;
37 cout << "v的大小为:" << v.size() << endl;
38
39 //收缩内存
40 vector<int>(v).swap(v); //匿名对象
41
42 cout << "v的容量为:" << v.capacity() << endl;
43 cout << "v的大小为:" << v.size() << endl;
44}
vector预留空间
减少vector在动态扩展容量时的扩展次数
reserve(int len);
//容器预留len个元素长度,预留位置不初始化,元素不可访问。
1void test01()
2{
3 vector<int> v;
4
5 //预留空间
6 v.reserve(100000);
7
8 int num = 0;
9 int* p = NULL;
10 for (int i = 0; i < 100000; i++) {
11 v.push_back(i);
12 if (p != &v[0]) {
13 p = &v[0];
14 num++;
15 }
16 }
17
18 cout << "num:" << num << endl;
19}
deque容器
deque容器基本概念
双端数组,可以对头端进行插入删除操作
deque与vector区别:
- vector对于头部的插入删除效率低,数据量越大,效率越低
- deque相对而言,对头部的插入删除速度回比vector快
- vector访问元素时的速度会比deque快,这和两者内部实现有关
deque内部工作原理:
deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据
中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间
deque构造函数
deque<T>
deqT; //默认构造形式deque(beg, end);
//构造函数将[beg, end)区间中的元素拷贝给本身。deque(n, elem);
//构造函数将n个elem拷贝给本身。deque(const deque &deq);
//拷贝构造函数
deque赋值操作
-
deque& operator=(const deque &deq);
//重载等号操作符 -
assign(beg, end);
//将[beg, end)区间中的数据拷贝赋值给本身。 -
assign(n, elem);
//将n个elem拷贝赋值给本身。
deque大小操作
-
deque.empty();
//判断容器是否为空 -
deque.size();
//返回容器中元素的个数 -
deque.resize(num);
//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。 //如果容器变短,则末尾超出容器长度的元素被删除。
-
deque.resize(num, elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。 //如果容器变短,则末尾超出容器长度的元素被删除。
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位置的数据,返回下一个数据的位置。
1void test01()
2{
3 deque<int> d;
4 //尾插
5 d.push_back(10);
6 d.push_back(20);
7 //头插
8 d.push_front(100);
9 d.push_front(200);
10
11 printDeque(d);
12
13 //尾删
14 d.pop_back();
15 //头删
16 d.pop_front();
17 printDeque(d);
18}
19
20//插入
21void test02()
22{
23 deque<int> d;
24 d.push_back(10);
25 d.push_back(20);
26 d.push_front(100);
27 d.push_front(200);
28 printDeque(d);
29
30 d.insert(d.begin(), 1000);
31 printDeque(d);
32
33 d.insert(d.begin(), 2,10000);
34 printDeque(d);
35
36 deque<int>d2;
37 d2.push_back(1);
38 d2.push_back(2);
39 d2.push_back(3);
40
41 d.insert(d.begin(), d2.begin(), d2.end());
42 printDeque(d);
43
44}
45
46//删除
47void test03()
48{
49 deque<int> d;
50 d.push_back(10);
51 d.push_back(20);
52 d.push_front(100);
53 d.push_front(200);
54 printDeque(d);
55
56 d.erase(d.begin());
57 printDeque(d);
58
59 d.erase(d.begin(), d.end());
60 d.clear();
61 printDeque(d);
62}
deque 数据存取
at(int idx);
//返回索引idx所指的数据operator[];
//返回索引idx所指的数据front();
//返回容器中第一个数据元素back();
//返回容器中最后一个数据元素
deque 排序
sort(iterator beg, iterator end)
//对beg和end区间内元素进行排序
1void test01()
2{
3
4 deque<int> d;
5 d.push_back(10);
6 d.push_back(20);
7 d.push_front(100);
8 d.push_front(200);
9
10 printDeque(d);
11 sort(d.begin(), d.end());
12 printDeque(d);
13
14}
stack容器
stack 基本概念
stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口
stack 常用接口
构造函数:
stack<T> stk;
//stack采用模板类实现, stack对象的默认构造形式stack(const stack &stk);
//拷贝构造函数
赋值操作:
stack& operator=(const stack &stk);
//重载等号操作符
数据存取:
push(elem);
//向栈顶添加元素pop();
//从栈顶移除第一个元素top();
//返回栈顶元素
大小操作:
empty();
//判断堆栈是否为空size();
//返回栈的大小
1#include <stack>
2
3//栈容器常用接口
4void test01()
5{
6 //创建栈容器 栈容器必须符合先进后出
7 stack<int> s;
8
9 //向栈中添加元素,叫做 压栈 入栈
10 s.push(10);
11 s.push(20);
12 s.push(30);
13
14 while (!s.empty()) {
15 //输出栈顶元素
16 cout << "栈顶元素为: " << s.top() << endl;
17 //弹出栈顶元素
18 s.pop();
19 }
20 cout << "栈的大小为:" << s.size() << endl;
21
22}
queue 容器
queue 基本概念
Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口
queue 常用接口
构造函数:
queue<T> que;
//queue采用模板类实现,queue对象的默认构造形式queue(const queue &que);
//拷贝构造函数
赋值操作:
queue& operator=(const queue &que);
//重载等号操作符
数据存取:
push(elem);
//往队尾添加元素pop();
//从队头移除第一个元素back();
//返回最后一个元素front();
//返回第一个元素
大小操作:
empty();
//判断堆栈是否为空size();
//返回栈的大小
1void test01() {
2
3 //创建队列
4 queue<Person> q;
5
6 //准备数据
7 Person p1("唐僧", 30);
8 Person p2("孙悟空", 1000);
9 Person p3("猪八戒", 900);
10 Person p4("沙僧", 800);
11
12 //向队列中添加元素 入队操作
13 q.push(p1);
14 q.push(p2);
15 q.push(p3);
16 q.push(p4);
17
18 //队列不提供迭代器,更不支持随机访问
19 while (!q.empty()) {
20 //输出队头元素
21 cout << "队头元素-- 姓名: " << q.front().m_Name
22 << " 年龄: "<< q.front().m_Age << endl;
23
24 cout << "队尾元素-- 姓名: " << q.back().m_Name
25 << " 年龄: " << q.back().m_Age << endl;
26
27 cout << endl;
28 //弹出队头元素
29 q.pop();
30 }
31
32 cout << "队列大小为:" << q.size() << endl;
33}
list容器
list基本概念
将数据进行链式存储
STL中的链表是一个双向循环链表
由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器
List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。
list构造函数
list<T> lst;
//list采用采用模板类实现,对象的默认构造形式:list(beg,end);
//构造函数将[beg, end)区间中的元素拷贝给本身。list(n,elem);
//构造函数将n个elem拷贝给本身。list(const list &lst);
//拷贝构造函数。
list 赋值和交换
assign(beg, end);
//将[beg, end)区间中的数据拷贝赋值给本身。assign(n, elem);
//将n个elem拷贝赋值给本身。list& operator=(const list &lst);
//重载等号操作符swap(lst);
//将lst与本身的元素互换。
list 大小操作
-
size();
//返回容器中元素的个数 -
empty();
//判断容器是否为空 -
resize(num);
//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。 //如果容器变短,则末尾超出容器长度的元素被删除。
-
resize(num, elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。 //如果容器变短,则末尾超出容器长度的元素被删除。
list 插入和删除
- push_back(elem);//在容器尾部加入一个元素
- pop_back();//删除容器中最后一个元素
- push_front(elem);//在容器开头插入一个元素
- pop_front();//从容器开头移除第一个元素
- insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
- insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
- insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
- clear();//移除容器的所有数据
- erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
- erase(pos);//删除pos位置的数据,返回下一个数据的位置。
- remove(elem);//删除容器中所有与elem值匹配的元素。
list 数据存取
front();
//返回第一个元素。back();
//返回最后一个元素。
list容器中不可以通过[]或者at方式访问数据
1//数据存取
2void test01()
3{
4 list<int>L1;
5 L1.push_back(10);
6 L1.push_back(20);
7 L1.push_back(30);
8 L1.push_back(40);
9
10
11 //cout << L1.at(0) << endl;//错误 不支持at访问数据
12 //cout << L1[0] << endl; //错误 不支持[]方式访问数据
13 cout << "第一个元素为: " << L1.front() << endl;
14 cout << "最后一个元素为: " << L1.back() << endl;
15
16 //list容器的迭代器是双向迭代器,不支持随机访问
17 list<int>::iterator it = L1.begin();
18 //it = it + 1;//错误,不可以跳跃访问,即使是+1
19 //区别it++
20}
list 反转和排序
reverse();
//反转链表sort();
//链表排序
1void printList(const list<int>& L) {
2
3 for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
4 cout << *it << " ";
5 }
6 cout << endl;
7}
8
9bool myCompare(int val1 , int val2)
10{
11 return val1 > val2;
12}
13
14//反转和排序
15void test01()
16{
17 list<int> L;
18 L.push_back(90);
19 L.push_back(30);
20 L.push_back(20);
21 L.push_back(70);
22 printList(L);
23
24 //反转容器的元素
25 L.reverse();
26 printList(L);
27
28 //排序
29 L.sort(); //默认的排序规则 从小到大
30 printList(L);
31
32 L.sort(myCompare); //指定规则,从大到小
33 printList(L);
34}
set/ multiset 容器
set基本概念
- 所有元素都会在插入时自动被排序
- set/multiset属于关联式容器,底层结构是用二叉树实现。
set和multiset区别:
- set不允许容器中有重复的元素
- multiset允许容器中有重复的元素
set构造和赋值
构造:
set<T> st;
//默认构造函数:set(const set &st);
//拷贝构造函数
赋值:
set& operator=(const set &st);
//重载等号操作符
1void test01()
2{
3 set<int> s1;
4
5 s1.insert(10);
6 s1.insert(30);
7 s1.insert(20);
8 s1.insert(40);
9 printSet(s1);
10
11 //拷贝构造
12 set<int>s2(s1);
13 printSet(s2);
14
15 //赋值
16 set<int>s3;
17 s3 = s2;
18 printSet(s3);
19}
set大小和交换
size();
//返回容器中元素的数目empty();
//判断容器是否为空swap(st);
//交换两个集合容器
1void test01()
2{
3
4 set<int> s1;
5
6 s1.insert(10);
7 s1.insert(30);
8 s1.insert(20);
9 s1.insert(40);
10
11 if (s1.empty())
12 {
13 cout << "s1为空" << endl;
14 }
15 else
16 {
17 cout << "s1不为空" << endl;
18 cout << "s1的大小为: " << s1.size() << endl;
19 }
20
21}
22
23//交换
24void test02()
25{
26 set<int> s1;
27
28 s1.insert(10);
29 s1.insert(30);
30 s1.insert(20);
31 s1.insert(40);
32
33 set<int> s2;
34
35 s2.insert(100);
36 s2.insert(300);
37 s2.insert(200);
38 s2.insert(400);
39
40 cout << "交换前" << endl;
41 printSet(s1);
42 printSet(s2);
43 cout << endl;
44
45 cout << "交换后" << endl;
46 s1.swap(s2);
47 printSet(s1);
48 printSet(s2);
49}
set插入和删除
insert(elem);
//在容器中插入元素。clear();
//清除所有元素erase(pos);
//删除pos迭代器所指的元素,返回下一个元素的迭代器。erase(beg, end);
//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。erase(elem);
//删除容器中值为elem的元素。
1void test01()
2{
3 set<int> s1;
4 //插入
5 s1.insert(10);
6 s1.insert(30);
7 s1.insert(20);
8 s1.insert(40);
9 printSet(s1);
10
11 //删除
12 s1.erase(s1.begin());
13 printSet(s1);
14
15 s1.erase(30);
16 printSet(s1);
17
18 //清空
19 //s1.erase(s1.begin(), s1.end());
20 s1.clear();
21 printSet(s1);
22}
set查找和统计
find(key);
//查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();count(key);
//统计key的元素个数
1void test01()
2{
3 set<int> s1;
4 //插入
5 s1.insert(10);
6 s1.insert(30);
7 s1.insert(20);
8 s1.insert(40);
9
10 //查找
11 set<int>::iterator pos = s1.find(30);
12
13 if (pos != s1.end())
14 {
15 cout << "找到了元素 : " << *pos << endl;
16 }
17 else
18 {
19 cout << "未找到元素" << endl;
20 }
21
22 //统计
23 int num = s1.count(30);
24 cout << "num = " << num << endl;
25}
set和multiset区别
- set不可以插入重复数据,而multiset可以
- set插入数据的同时会返回插入结果,表示插入是否成功
- multiset不会检测数据,因此可以插入重复数据
1void test01()
2{
3 set<int> s;
4 pair<set<int>::iterator, bool> ret = s.insert(10);
5 if (ret.second) {
6 cout << "第一次插入成功!" << endl;
7 }
8 else {
9 cout << "第一次插入失败!" << endl;
10 }
11
12 ret = s.insert(10);
13 if (ret.second) {
14 cout << "第二次插入成功!" << endl;
15 }
16 else {
17 cout << "第二次插入失败!" << endl;
18 }
19
20 //multiset
21 multiset<int> ms;
22 ms.insert(10);
23 ms.insert(10);
24
25 for (multiset<int>::iterator it = ms.begin(); it != ms.end(); it++) {
26 cout << *it << " ";
27 }
28 cout << endl;
29}
pair对组创建
成对出现的数据,利用对组可以返回两个数据
pair<type, type> p ( value1, value2 );
pair<type, type> p = make_pair( value1, value2 );
1void test01()
2{
3 pair<string, int> p(string("Tom"), 20);
4 cout << "姓名: " << p.first << " 年龄: " << p.second << endl;
5
6 pair<string, int> p2 = make_pair("Jerry", 10);
7 cout << "姓名: " << p2.first << " 年龄: " << p2.second << endl;
8}
set容器排序
set容器默认排序规则为从小到大,利用仿函数,可以改变排序规则
对于自定义数据类型,set必须指定排序规则才可以插入数据
1class MyCompare
2{
3public:
4 bool operator()(int v1, int v2) {
5 return v1 > v2;
6 }
7};
8void test01()
9{
10 set<int> s1;
11 s1.insert(10);
12 s1.insert(40);
13 s1.insert(20);
14 s1.insert(30);
15 s1.insert(50);
16
17 //默认从小到大
18 for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
19 cout << *it << " ";
20 }
21 cout << endl;
22
23 //指定排序规则
24 set<int,MyCompare> s2;
25 s2.insert(10);
26 s2.insert(40);
27 s2.insert(20);
28 s2.insert(30);
29 s2.insert(50);
30
31 for (set<int, MyCompare>::iterator it = s2.begin(); it != s2.end(); it++) {
32 cout << *it << " ";
33 }
34 cout << endl;
35}
1class Person
2{
3public:
4 Person(string name, int age)
5 {
6 this->m_Name = name;
7 this->m_Age = age;
8 }
9
10 string m_Name;
11 int m_Age;
12
13};
14class comparePerson
15{
16public:
17 bool operator()(const Person& p1, const Person &p2)
18 {
19 //按照年龄进行排序 降序
20 return p1.m_Age > p2.m_Age;
21 }
22};
23
24void test01()
25{
26 set<Person, comparePerson> s;
27
28 Person p1("刘备", 23);
29 Person p2("关羽", 27);
30 Person p3("张飞", 25);
31 Person p4("赵云", 21);
32
33 s.insert(p1);
34 s.insert(p2);
35 s.insert(p3);
36 s.insert(p4);
37
38 for (set<Person, comparePerson>::iterator it = s.begin(); it != s.end(); it++)
39 {
40 cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << endl;
41 }
42}
map/ multimap容器
map基本概念
- map中所有元素都是pair
- pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
- 所有元素都会根据元素的键值自动排序
- map/multimap属于关联式容器,底层结构是用二叉树实现。
map和multimap区别:
- map不允许容器中有重复key值元素
- multimap允许容器中有重复key值元素
map构造和赋值
构造:
map<T1, T2> mp;
//map默认构造函数:map(const map &mp);
//拷贝构造函数
赋值:
map& operator=(const map &mp);
//重载等号操作符
1void printMap(map<int,int>&m)
2{
3 for (map<int, int>::iterator it = m.begin(); it != m.end(); it++)
4 {
5 cout << "key = " << it->first << " value = " << it->second << endl;
6 }
7 cout << endl;
8}
9
10void test01()
11{
12 map<int,int>m; //默认构造
13 m.insert(pair<int, int>(1, 10));
14 m.insert(pair<int, int>(2, 20));
15 m.insert(pair<int, int>(3, 30));
16 printMap(m);
17
18 map<int, int>m2(m); //拷贝构造
19 printMap(m2);
20
21 map<int, int>m3;
22 m3 = m2; //赋值
23 printMap(m3);
24}
map大小和交换
size();
//返回容器中元素的数目empty();
//判断容器是否为空swap(st);
//交换两个集合容器
map插入和删除
insert(elem);
//在容器中插入元素。clear();
//清除所有元素erase(pos);
//删除pos迭代器所指的元素,返回下一个元素的迭代器。erase(beg, end);
//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。erase(key);
//删除容器中值为key的元素。
1void test01()
2{
3 //插入
4 map<int, int> m;
5 //第一种插入方式
6 m.insert(pair<int, int>(1, 10));
7 //第二种插入方式
8 m.insert(make_pair(2, 20));
9 //第三种插入方式
10 m.insert(map<int, int>::value_type(3, 30));
11 //第四种插入方式
12 m[4] = 40;
13 printMap(m);
14
15 //删除
16 m.erase(m.begin());
17 printMap(m);
18
19 m.erase(3);
20 printMap(m);
21
22 //清空
23 m.erase(m.begin(),m.end());
24 m.clear();
25 printMap(m);
26}
map查找和统计
find(key);
//查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();count(key);
//统计key的元素个数
1void test01()
2{
3 map<int, int>m;
4 m.insert(pair<int, int>(1, 10));
5 m.insert(pair<int, int>(2, 20));
6 m.insert(pair<int, int>(3, 30));
7
8 //查找
9 map<int, int>::iterator pos = m.find(3);
10
11 if (pos != m.end())
12 {
13 cout << "找到了元素 key = " << (*pos).first << " value = " << (*pos).second << endl;
14 }
15 else
16 {
17 cout << "未找到元素" << endl;
18 }
19
20 //统计
21 int num = m.count(3);
22 cout << "num = " << num << endl;
23}
map容器排序
map容器默认排序规则为 按照key值进行 从小到大排序,利用仿函数,可以改变排序规则
1#include <map>
2
3class MyCompare {
4public:
5 bool operator()(int v1, int v2) {
6 return v1 > v2;
7 }
8};
9
10void test01()
11{
12 //默认从小到大排序
13 //利用仿函数实现从大到小排序
14 map<int, int, MyCompare> m;
15
16 m.insert(make_pair(1, 10));
17 m.insert(make_pair(2, 20));
18 m.insert(make_pair(3, 30));
19 m.insert(make_pair(4, 40));
20 m.insert(make_pair(5, 50));
21
22 for (map<int, int, MyCompare>::iterator it = m.begin(); it != m.end(); it++) {
23 cout << "key:" << it->first << " value:" << it->second << endl;
24 }
25}