因为本人在学校只学了一点C,想继续深入学习的C++,这是我根据黑马C++视频一点一点整理的学习笔记,前面几集有省略写,自用复习,应该有不少毛病,欢迎捉虫,后面应该还会完善
黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili
目录
C++
基础知识
常量
作用:用于记录程序中不可更改的数据
C++定义常量的两个方式
-
#define (#define 常量名 常量值)
宏变量(通常在文件上方定义,表示一个常量)
-
const 修饰的变量 constant 数据类型 常量名 = 常量值
通常在变量定义前加关键字const,修饰该变量为常量,不可修改
浮点型
单精度:float f1 = 3.14f;
双精度:double d1 = 3.14;
默认情况下 输出提高小数,会显示出6位有效数字
科学计数法:
float f2 = 3e2; //3*10^2
float f3 = 3e-2; //3*0.1^2
字符型
字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元
a-97 A-65 B-66
char ch = 'a'; //注意只能单引号一个字符,只占1字符
转义符
换行符 \n
反斜杠 \\
cout << "\\\\\" << endl;
水平制表符 \t 作用:可以整齐输出数据
字符串
1.c风格字符串
注意事项 char 字符串名 [],等号后面要用双引号包含字符串
char str[] = "HELLO WORLD";
cout << str << endl;
2.C++风格字符串
一定要包含#include< string>这个头文件
string str2 = "HELLO WORLD";
cout << str2 << endl;
布尔类型
只有真假两种,占1字符,非零即1
bool flag = false;
cout << "请给布尔类型flag赋值" << endl;
cin >> flag;
cout << "布尔类型flag=" << flag << endl;
键盘输入:100 //0
输出:布尔类型flag=1 //0
算术运算符
//前置递增,先让变量+1 然后进行表达式运算
int a2 = 10;
int b2 = ++a2 * 10;
cout << "a2 = " << a2 << endl;
cout << "b2 = " << b2 << endl;
//后置递增,先表达式运算 然后让变量+1
int a3 = 10;
int b3 = a3++ * 10;
cout << "a3 = " << a3 << endl;
cout << "b3 = " << b3 << endl;
三目运算符
创建三个变量,将a和b作比较,将变量大的值赋值给变量c
int a = 10;
int b = 20;
int c = 0;
//将a和b作比较,将变量大的值赋值给变量c
c=(a>b ? a:b)
cout << "c=" << c<< endl; c=20
//在C++中三目运算符返回的是变量,可以继续赋值
(a<b ? a:b) = 100;
cout << "a=" << a << endl; a=100
cout << "b=" << b << endl; b=20
if和switch的区别
Switch
缺点,判断时候只能是整型或者字符型,不可以是一个区间
优点,结构清晰,执行效率高
注意:case里如果没有break,那么程序会一直向下执行
程序流程语句
选择结构
if
循环结构
用while写猜数字程序(随机数)
#include<iostream>
using namespace std;
//time系统时间头文件包含
#include <ctime>
int main1()
{
//添加随机数种子,作用利用当前系统时间生成随机数,防止每次随机数都一样
srand((unsigned int)time(NULL));
//1、系统生成随机数
int num = rand() % 100 + 1; //rand() % 100 + 1;生成 0+1~99+1随机数
//2、玩家进行猜测
int val = 0;
//3、判断玩家的猜测
while (val != num) { //while (1)
cin >> val;
if (val > num) {
cout << "大了" << endl;
}
else if(val < num) {
cout << "小了" << endl;
}
else {
cout << "猜对了" << endl;
}
}
system("pause");
return 0;
}
do..while
与while的区别在于do...while会先执行一次循环语句,在判断循环条件
//while语句
int num1 = 0;
while (num1 < 10) {
cout << num1 << endl;
num1++;
}
//do..while语句
int num = 0;
do {
cout << num << endl;
num++;
} while (num < 10);
for循环语句
for(起始表达式 ; 条件表达式;末尾循环体){循环语句;}
嵌套循环
//打印10*10的矩阵
int main() {
for (int i = 0; i <= 10; i++) {
for (int j = 0; j <= 10; j++) {
cout << "* ";
}
cout << endl;
}
return 0;
}
注意endl表示换行;
乘法口诀程序
#include<iostream>
using namespace std;
int main() {
int num;
for (int i = 1; i < 10; i++) {
for (int j = 1; j <= i; j++) {
num = i * j;
cout << j << "*" << i << "=" << num << "\t";
}
cout << endl;
}
return 0;
}
注意:列数*行数=计算结果 列数<=当前行数
\t 水平制表符,让输出结果更美观
跳转结构
break语句
作用:用于跳出选择结构和循环结构
break使用时机:
-
在switch条件语句中,作用是终止case并跳出switch、
-
在循环语句中,作用是跳出当前的循环语句,最常用
-
在嵌套循环中,跳出最近的内层循环语句
continue语句
在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环。
goto语句
作用:可以无条件跳转语句,不要乱用,以免造成程序混乱
解释:如果标记的名称存在,执行到goto语句时,会跳转到标记的位置
数组(难点)
概述
所谓数组,就是一个集合,里面存放了相同类型的数据元素
特点1:数组中的每个数据元素都是相同的数据类型
特点2:数组是由连续的内存位置组成的
一维数组
三种定义方式
-
数据类型 数组名[数组长度];
-
数据类型 数组名[数组长度] = {值1,值2 ... };
-
数据类型 数组名[ ] = {值1,值2 ... };
注意:1.数组下标是从0开始的
2.如果在初始化数据时候,没有全部填写完,会用0来填补剩余数据
3.定义数组时候必须有初始长度
//利用循环 输出数组中的元素 int arr[5] = {10,20,30,40,50}; for(int i =0; i<5; i++){ cout << arr[i] << endl; }
一维数组组名
一维数组名称的用途:
1、可以统计整个数组在内存中的长度
sizeof(arr) / sizeof(arr[0]) //数组中元素个数=整个数组占用内存空间 / 每个元素占用内存空间
2、可以获取数组在内存中的首地址
(int)arr //数组首地址 (int)&arr[0] //数组中第一个元素地址为 (int)&arr[1] //数组中第二个元素地址为
注意:数组名是常量,不可以进行赋值操作
一维数组应用案例难点
元素逆置
#include <iostream>
using namespace std;
int main() {
int arr[5];
cout << "请输入五个数字" << endl;
for (int i = 0; i < 5; i++) {
cin >> arr[i];
}
//实现逆置
//记录起始下标位置
//记录结束下标位置
//起始下标与结束下标的元素互换
//起始位置++,结束位置--
//循环执行,直至起始位置>=结束位置
int start = 0;
int end = sizeof(arr) / sizeof(arr[0]) - 1;
while (start < end) {
//实现元素互换
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
//下表更新
start++;
end--;
}
//打印逆置后的数组
cout << "数组元素逆置后:" << endl;
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
return 0;
}
注意:本题主要是运用了起始,结尾两个指针
冒泡排序
重点:总共排序轮数为 = 元素个数 - 1
次数 = 元素个数 - 当前轮数 - 1
#include <iostream>
using namespace std;
//将数组升序排序
int main() {
int arr[9] = { 4,2,8,0,5,7,1,3,9 };
cout << "排序前" << endl;
for (int i = 0; i < 9; i++) {
cout << arr[i] << " ";
}
cout << endl;
//开始冒泡排序
//总共排序轮数为 元素个数-1
for (int i = 0; i < 9 - 1; i++) {
//内层循环对比 次数=元素个数-当前轮数-1
for (int j = 0; j < 9 - i - 1; j++) {
//如果第一个数大于第二个数则交换位置
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
//排序后结果
cout << "排序后" << endl;
for (int i = 0; i < 9; i++) {
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
二维数组
二维数组定义方式
-
数据类型 数组名[ 行数 ] [ 列数 ];
-
数据类型 数组名[ 行数 ] [ 列数 ] = { { 数据1 ,数据2 },{ 数据3 ,数据4 } ... };
-
数据类型 数组名[ 行数 ] [ 列数 ] = {数据1,数据2 ,数据3,数据4 ... };
-
数据类型 数组名[ ] [ 列数 ] = {数据1,数据2 ,数据3,数据4 ... };
二维数组数组名
-
查看二维数组所占内存空间
sizeof(arr) //二维数组占用内存空间
sizeof(arr[0]) //二维数组第一行占用内存空间
sizeof(arr[0][0]) // 二维数组第一个元素占用内存空间
sizeof(arr) / sizeof(arr[0]) //二维数组行数
sizeof(arr[0]) / sizeof(arr[0][0]) //二维数组列数
-
获取二维数组首地址
(int)arr //二维数组首地址 (int)&arr[0] //数组中第一行首地址为 (int)&arr[0][0] //数组中第一个元素元素地址为 //以上三个地址相同
二维数组应用案例难点
函数
概述
作用:将一段经常使用的代码封装起来,减少重复代码
一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能
函数的定义
函数的定义一般主要有5个步骤:
1、返回值类型:一个函数可以返回一个值,在函数定义中 int
2、函数名:给函数起个名称 add
3、参数列表:使用该函数时,传入的数据 (int num1 , int num2)
4、函数体语句:花括号内的代码,函数内需要执行的语句 int sum = num1 +num2
5、return表达式:和返回值类型挂钩,函数执行完后,函数相应的数据 return sum;
语法:
返回值类型 函数名 (参数列表)
{
函数体语句
return 语句
}
函数的调用
功能:使用定义好的函数
其中a b是实参,num1,num2是形参没有实际的值
int add(int num1 , int num2){
int sum = num1 +num2;
return sum;
}
int main(){
int a = 10;
int b = 20;
int c = add(a,b);
cout << "c=" << c << endl;
}
//输出结果c=30
值传递
就是函数调用时实参将数值传入给形参
值传递时,如果形参发生改变,并不会影响到实参
所以两数交换后输出,要在函数体语句中写,而不是主函数中写。
注意:return返回值不需要的时候,可以不写return
函数的常见样式
常见样式有4种:
1、无参无返
2、有参无返
3、无参有返
4、有参有返
//1、无参无返 void test01(){ //void a = 10; //无类型不可以创建变量,原因无法分配内存 cout << "test01" << endl; } //2、有参无返 void test02(int a){ cout << "test02 a=" << a << endl; } //3、无参有返 int test03(){ cout << "test03" << endl; return 1000; } //4、有参有返 int test04(int a){ cout << "test04 a=" << a << endl; return a; } int main(){ //1、无参无返函数调用 test01(); //2、有参无返函数调用 test02(100); //3、无参有返函数调用 int num1 = test03(); cout << "num1=" << num << endl; //4、有参有返函数调用 int num2 = test04(10000); cout << "num2=" << num << endl; }
函数的声明
作用:告诉编译器函数名称及如何调用函数,函数的实际主体可以单独定义
函数的声明可以多次,但是函数的定义只能有一次
//声明定义,两数比较选大的 int max(int a, int b) { return a > b ? a : b; } int main() { int a = 10; int b = 20; cout << max(a, b) << endl; return 0; }
函数的分文件编写
作用:让代码结构更加清晰
函数分文件编写一般有4个步骤:
1、创建后缀名为.h的头文件
2、创建后缀名为.cpp的源文件
3、在头文件中写函数的声明
4、在源文件中写函数的定义
指针
指针的基本概念
指针的作用:可以通过指针间接访问内存
-
内存编号是从0开始记录的,一般用十六进制数字表示
-
可以利用指针变量保存地址
指针变量的定义和使用
指针变量定义语法:数据类型 * 变量名
解引用:指针前加一个*,来找到指针指向的内存
//1、定义指针 int a =10; //指针定义的语法:数据类型 * 指针变量名: int * p; //让指针记录变量a的地址 p = &a; cout << "a的地址为:" << &a << endl; //2、使用指针 //可以通过解引用的方式来找到指针指向的内存 //解引用:指针前加一个*,例: *p *p = 1000; cout << "a=" << a << endl; cout << "*p=" << *p << endl;
指针所占内存内存空间
指针也是一种数据类型,其占用的内存空间:
在32位的操作系统下:占用4个字符,64位下:占用8个字符;
int a = 10; //int* p; //p = &a; int * p = &a; cout << *p << endl; cout << sizeof(p) << endl; //sizeof(int *) cout << sizeof(char *) << endl; cout << sizeof(float *) << endl; cout << sizeof(double *) << endl; return 0;
空指针
定义:指针变量指向内存中编号为0的空间
用途:初始化指针变量
注意:空指针指向的内存不可访问,0~255的内存编号是系统占用的,因此不可以访问
//空指针 int * p = NULL;
野指针
定义:指针变量指向非法的内存空间
在程序中,尽量避免写野指针
int * p = (int *)0x1100; //语法没错误,但是报错没有权限访问
const修饰指针
有三种情况:
1、const修饰指针——常量指针:指针的指向可以修改,但是指针指向的值不可以改(常量的指针)
int a =10; int b = 10; const int * p = &a; *p=20; //错误,指针指向的值不可以改 p = &b; //正确,指针的指向可以修改
2、const修饰常量——指针常量:指针的指向不可以修改,但是指针指向的值可以改(指针的常量)
int a =10; int b = 10; int * const p = &a; *p=20; //正确,指针指向的值可以改 p = &b; //错误,指针的指向不可以修改
3、const即修饰指针,又修饰常量:指针的指向,指针指向的值都不可以修改
int a =10; int b = 10; const int * const p = &a; *p=20; //错误,指针指向的值不可以改 p = &b; //错误,指针的指向不可以修改
指针和数组
作用:利用指针访问数组中的元素
int arr[10] = {1,2,3,4,5,6,7,8,9,10} int * p = arr; //arr就是数组的首地址 p++; //指针向后偏移四个字节 int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; cout << "利用指针遍历数组" << endl; int * p = arr; for (int i = 0; i < 10; i++) { cout << *p << endl; p++; } return 0; }
指针和函数
作用:利用指针作函数参数,可以修改实参的值
地址传递
void swap02(int * p1,int * p2) { int temp = *p1; *p1 = *p2; *p2 = temp; } int main() { int a = 10; int b = 20; //地址传递 //如果是地址传递,可以修饰实参 swap02(&a, &b); cout << "a=" << a << endl; //a=20 cout << "b=" << b << endl; //b=10 return 0; }
指针数组和函数的案例 难点
描述:封装一个函数,利用冒泡排序,实现对整型数组的升序排列
#include <iostream> using namespace std; //冒泡函数排序,参数1 数组的首地址,参数2 数组长度 void bubbleSort(int * arr,int len) { for (int i = 0; i < len - 1; i++) { for (int j = 0; j < len - i - 1; j++) { //如果j>j+1的值 交换数字,指针偏移 if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } //打印数组 void printArray(int* arr, int len) { for (int i = 0; i < len; i++) { cout << arr[i] << endl; } } int main() { //1、先创建数组 int arr[10] = { 4,3,6,9,1,2,10,8,7,5 }; //数组长度 int len = sizeof(arr) / sizeof(arr[0]); //2、创建函数,实现冒泡排序 bubbleSort(arr, len); //3、打印排序后的数组 printArray(arr, len); return 0; }
结构体
结构体基本概念
结构体属于用户自定义的数据类型,允许用户存储不用的数据类型
结构体定义和使用
语法:struct 结构体名 {结构体成员列表}
通过结构体创建变量的方式有三种:
-
struct结构体名 变量名
-
struct结构体名 变量名 = {成员1值 , 成员2值...}
-
定义结构体时顺便创建变量 //用的少,上两个多
#include <iostream> using namespace std; #include <string> //1、创建学生数据类型:学生包括(姓名,年龄,分数) //自定义数据类型,一些类型集合组成的一个类型 struct Student { //成员列表 //姓名 string name; //年龄 int age; //分数 int score; }s3; //顺便创建结构体变量 //2、通过学生类型创建具体学生 int main() { //1、struct Student s1 //struct可以省略 Student s1; //给s1属性赋值,通过,访问结构体变量中的属性 s1.name = "张三"; s1.age = 18; s1.score = 100; cout << "姓名:" << s1.name << " 年龄:" << s1.age << " 分数:" << s1.score << endl; //2、struct Student s2={...} struct Student s2 = { "李四",19,80 }; cout << "姓名:" << s2.name << " 年龄:" << s2.age << " 分数:" << s2.score << endl; //3、在定义结构体时顺便创建结构体变量struct Student s2={...} s3.name = "王五"; s3.age = 20; s3.score = 60; cout << "姓名:" << s3.name << " 年龄:" << s3.age << " 分数:" << s3.score << endl; return 0; }
结构体数组
作用:将自定义的结构体放入到数组中方便维护
语法:struct 结构体名 数组名[元素个数] = { {},{}, ... {} }
//2、创建结构体数组 struct Student stuArray[3] = { { "张三", 18, 100 }, { "李四", 28, 80 }, { "王五", 38, 66 } }; //3、给结构体数组中的元素赋值 stuArray[2].name = "赵六"; stuArray[2].age = 80; stuArray[2].score = 60; //遍历结构体数组 for (int i = 0; i < 3; i++) { cout << "姓名:" << stuArray[i].name << " 年龄:" << stuArray[i].age << " 分数:" << stuArray[i].score << endl; }
结构体指针
作用:通过指针访问结构体的成员
利用操作符号->
可以通过结构体指针访问结构体属性
//创建学生结构体变量 Student s = { "张三", 18, 100 }; //通过指针指向结构体变量 struct Student * p = &s; //通过指针访问结构体变量中的数据 cout << "姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl;
结构体嵌套结构体
作用:结构体中的成员可以是另一个结构体
int main(){ teacher t; t.id=10000; t.name = "老王"; t.age = 50; t.stu.name = "小王"; t.stu.age = 20; t.stu.score = 60; return 0; }
结构体做函数参数
作用:将结构体作为参数向函数中传递
传递方式两种:1、值传递 2、地址传递
总结:如果不想修改主函数中的数据,用值传递,反之用地址传递
#include <iostream> using namespace std; #include <string> struct Student { string name; int age; int score; }; //打印学生信息函数 //1、值传递 void printStudent1(Student s) { s.age = 100; cout << "子函数1中 姓名:" << s.name << " 年龄:" << s.age << " 分数:" << s.score << endl; } //2、地址传递 void printStudent2(Student * p) { p->age = 200; cout << "子函数1中 姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl; } int main() { Student s = { "张三", 18, 100 }; //printStudent1(s); printStudent2(&s); cout << "mian函数中 姓名:" << s.name << " 年龄:" << s.age << " 分数:" << s.score << endl; }
结构体中const使用场景
作用:用const来防止误操作
#include <iostream> using namespace std; #include <string> struct Student { string name; int age; int score; }; //将函数中的形参改为指针,可以减少内存空间,而且不会复制新的副本出来 void printStudent(const Student * s) { //s->age = 150; //加入const之后,常量指针,值不能改变,一旦有修改的操作就会报错,可以防止我们的误操作 cout << "姓名:" << s->name << " 年龄:" << s->age << " 分数:" << s->score << endl; } int main() { Student s = { "张三", 18, 100 }; printStudent(&s); cout << "main函数中年龄为:" << s.age << endl; return 0; }
结构体案例
描述:
学校正在做毕设项目,每名老师带领5个学生,总共3名老师,需求如下:
设计学生和老师的结构体,其中老师的结构体中,有老师姓名和一个存放5名学生的数组作为成员
学生的成员有姓名,考试分数,创建数组存放3名老师,通过每个老师及所带的学生赋值
最终打印出老师数据以及老师索塞的学生的数据\
#include <iostream> using namespace std; #include <string> #include <ctime> struct student { string sName; int score; }; struct teacher { string tName; struct student s[5]; }; //给老师和学生赋值的函数 void allocateSpace(struct teacher t[], int len) { string nameSeed = "ABCDE"; //给老师赋值 for (int i = 0; i < len; i++) { t[i].tName = "teacher_"; t[i].tName += nameSeed[i]; //字符串的拼接 //通过循环给每名老师所带的学生赋值 for (int j = 0; j < 5; j++) { t[i].s[j].sName = "student_"; t[i].s[j].sName += nameSeed[j]; int random = rand() % 61 + 40; //0~59+40=0~99 -> 0~60+40=0~100 t[i].s[j].score = random; } } } void printTeacher(teacher t[],int len) { for (int i = 0; i < len; i++) { cout << "老师名" << t[i].tName << endl; for (int j = 0; j < 5; j++) { cout << "\t学生姓名" << t[i].s[j].sName << " 分数" << t[i].s[j].score << endl; } } } int main() { //随机数种子,作用利用当前系统时间生成随机数,防止每次随机数都一样 srand((unsigned int)time(NULL)); teacher t[3]; //通过函数给三个老师的信息赋值,并给老师带的学生信息赋值 int len = sizeof(t) / sizeof(t[0]); allocateSpace(t, len); //打印 printTeacher(t,len); return 0; }
结构体案例2
描述:设计一个英雄的结构体,包括成员姓名,年龄,性别;创建结构体数组,数组中存放5名英雄。
通过冒泡排序的算法,将数组中的英雄按照年龄进行升序排序,最终稿打印排序后的结果
#include <iostream> using namespace std; #include <string> struct hero { string name; int age; string sex; }; struct heroS { hero h[5]; }; //地址传递 void bubbleSort(hero h[], int len) { for (int i = 0; i < len - 1; i++) { for (int j = 0; j < len - i - 1; j++) { if (h[j].age > h[j + 1].age) { hero temp = h[j]; h[j] = h[j + 1]; h[j + 1] = temp; } } } } void printHero(hero h[], int len) { for (int i = 0; i < len; i++) { cout << "姓名:" << h[i].name << " 年龄:" << h[i].age << " 性别:" << h[i].sex << endl; } } int main() { hero h[5] = { {"刘备",23,"男"}, {"关羽",22,"男"}, {"张飞",20,"男"}, {"赵云",21,"男"}, {"貂蝉",19,"女"} }; int len = sizeof(h) / sizeof(h[0]); /*for (int i = 0; i < len; i++) { cout << "姓名:" << h[i].name << " 年龄:" << h[i].age << " 性别:" << h[i].sex << endl; }*/ bubbleSort(h, len); printHero(h, len); return 0; }
通讯录管理系统
添加联系人
步骤:
-
设计联系人结构体
-
设计通信录结构体 (要记录人数最多1k)
-
main函数中创建通讯录
-
封装添加联系人函数
-
测试添加联系人功能
显示联系人
步骤:
-
封装显示联系人函数
-
测试显示联系人功能
删除联系人
描述:按照姓名进行删除联系人
步骤:
-
封装检测联系人是否存在
-
封装显示联系人函数
-
测试显示联系人功能
封装检测联系人是否存在
思路:删除联系人之前需要先判断联系人是否存在,如果存在删除没不存在要提示用户没有此联系人,因此我们需要把检测联系人是否存在封装成一个函数中,如果存在,返回联系人在通讯录中的位置,不存在返回-1
封装删除联系人函数
根据用户输入的联系人判断改通讯录中是否有此人
查找到进行删除,并提示删除成功
查不到提升查无此人
清空通讯录
思路:将通讯录所有联系人信息清除掉,只要将通讯录记录的联系人数量变为0,做逻辑上清空。
本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓
内存分区模型
C++程序在执行时,将内存大方向分为4个区域
-
代码区:存放函数体的二进制代码,由操作系统进行管理的
-
全局区:存放全局变量和静态变量以及常量
-
栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
-
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
分区意义:
不同区域存放的数据,赋予不同的声生命周期,给我们更大的灵活编程
1.1 程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
存放CPU执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码就行
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区
全局变量和静态变量存放在此
全局区还包含了常量区,字符串常量和其他常量也存放在此
该区域的数据在程序结束后由操作系统释放
#include <iostream> using namespace std; //全局变量:不在任何函数声明 int g_a = 10; int g_b = 10; //const修饰的全局变量,全局常量 const int c_g_a = 10; const int c_g_b = 10; int main() { 创建普通局部变量 int a = 10; int b = 10; cout << "局部变量a的地址为:" << (int)&a << endl; cout << "局部变量b的地址为:" << (int)&b << endl; cout << "全局变量g_a的地址为:" << (int)&g_a << endl; cout << "全局变量g_b的地址为:" << (int)&g_b << endl; //静态变量 在普通变量前加static static int s_a = 10; static int s_b = 10; cout << "静态变量s_a的地址为:" << (int)&s_a << endl; cout << "静态变量s_b的地址为:" << (int)&s_b << endl; //常量 //字符串常量 cout << "字符串常量的地址为:" << (int)&"hello world" << endl; //const修饰的变量 //const修饰的全局变量 cout << "全局常量c_g_a的地址为:" << (int)&c_g_a << endl; cout << "全局常量c_g_b的地址为:" << (int)&c_g_b << endl; const修饰的局部变量 int c_l_a = 10; //c-const g-global全局 l-local局部 int c_l_b = 10; cout << "局部常量c_l_a的地址为:" << (int)&c_l_a << endl; cout << "局部常量c_l_b的地址为:" << (int)&c_l_b << endl; return 0; }
总结:
-
C++中在程序运行前分为全局区和代码区
-
代码区特点是共享和只读
-
全局区中存放全局变量,静态变量,常量
-
常量区中存放const修饰的全局常量和字符串变量
1.2 程序运行后
栈区:
由编译器自动分配释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
#include <iostream> using namespace std; int* func() { /*int a = 10; return &a;*/ //利用new关键字,可以将数据开辟到堆区 //指针 本质也是局部变量,放在栈上,指针保存的数据是放在堆区 int * p = new int(10); return p; } int main() { //在堆区开辟数据 int *p = func(); cout << *p << endl; cout << *p << endl; cout << *p << endl; return 0; }
总结:
-
堆区数据由程序员管理开辟和释放
-
堆区数据利用new关键字进行开辟内存
1.3new操作符
C++中利用new操作符在堆区开辟数据
堆区开辟数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
#include <iostream> using namespace std; //1、在堆区创建整型数据 int* func() { //new 返回的是该数据类型的指针 int * p = new int(10); return p; } void test01() { int * p = func(); cout << *p << endl; cout << *p << endl; //堆区数据由程序员管理开辟,释放 //利用delete释放 delete p; //cout << *p << endl; //报错,内存已经释放,在访问就是非法操作 } //2、在堆区利用new开辟数组 void test02() { //创建10个整型数据的数组,在堆区 int * arr = new int[10]; //数组10个元素 for (int i = 0; i < 10; i++) { arr[i] = i + 100; //给十个数赋值 100~109 } for (int i = 0; i < 10; i++) { cout << arr[i] << endl; //arr[i]=*(arr+i) } //释放数组,要加[]不然就会只释放了首地址 delete[] arr; } int main() { //test01(); test02(); return 0; }
引用
作用:给变量起别名
语法:数据类型 &别名 = 原名
注意:
-
引用必须初始化
-
引用在初始化后,不可以改变
引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
//3、引用传递 void mySwap03(int& a, int& b) { //这里不是取址符,是引用 int temp = a; a = b; b = temp; } int main6() { int a = 10; int b = 20; mySwap03(a, b); //引用传递实参改变,(类似指针常量) cout << "a=" << a << endl; cout << "b=" << b << endl; }
引用做函数返回值
作用:引用时可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值(可以被引用的对象叫做左值)
#include <iostream> using namespace std; //引用做函数的返回值 //1、不要返回局部变量的引用 int & test01() { int a = 10; // 局部变量存放在四区中的 栈区 return a; } //2、函数的调用可以作为左值 int& test02() { static int a = 10; // 静态变量存放在四区中的 全局区,这上面的数据在程序结束后系统释放 return a; } int main() { //int& ref = test01(); int& ref = test02(); cout << "ref=" << ref << endl; //第一次结果正确,编译器做了保留 cout << "ref=" << ref << endl; //第二次结果错误,因为a的内存已经释放 test02() = 1000; //返回的a的引用被赋值,ref是a的别名 cout << "ref=" << ref << endl; //因此如果函数的返回值是引用,这个函数调用可以作为左值 cout << "ref=" << ref << endl; return 0; }
引用的本质
本质:引用的本质在c++内部实现是一个指针常量
//发现是引用,转换为 int * const ref = &a; void func(int& ref){ ref = 100; //ref是引用,转换为*ref = 100 } int main(){ int a = 10; //自动转换为int * const ref = &a;指针常量是指针指向不可改,也说明为什么引用不可更改 int & ref = a; ref = 20; //内存发现ref是引用,自动帮我们转换为*ref = 20; cout << "a:" << a << endl; cout << "ref:" << ref << endl; func(a); return 0; }
常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
#include <iostream> using namespace std; void showValue(const int & val) { //val = 1000; //报错,加上const后变为只读,不可修改 cout << "val:" << val << endl; } int main() { //常量引用场景:修饰形参,防止误操作 //int a = 10; //int& ref = a; //必须引用一块合法内存空间 int & ref = 10 不被允许 //const int& ref = 10; //加上const之后,编译器将代码修改 int temp = 10; int& ref = temp; //ref = 20; //报错,加上const后变为只读,不可修改 int a = 100; showValue(a); cout << "a:" << a << endl; return 0; }
函数提高
函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值
语法:返回值类型 函数名 (参数 = 默认值){}
#include <iostream> using namespace std; //函数默认参数 //如果我们自己传入数据,就用自己的数据,如果没有,那么默认值 //语法:`返回值类型 函数名 (参数 = 默认值){}` int func(int a, int b = 20, int c =30) { return a + b + c; } //注意: //1、如果某个位置已经有了默认参数,那么从这个位置往后从左到右都必须有默认值,如下面c也必须要有默认值 //int func2(int a, int b = 20, int c) { // return a + b + c; //} //2、如果函数声明有默认参数,函数实现就不能有默认参数 //int func2(int a=10, int b=10); //int func2(int a=10, int b=10) { // return a + b; //} int main() { cout << func(10,30) << endl; // 结果70 //cout << func2(10,10) << endl; return 0; }
函数占位参数
c++中函数的形参列表里可以有占位参数,用来占位,调用函数时必须填补改位置
语法:返回值类型 函数名 (数据类型){}
在现阶段函数占位参数讯在意义不大,但是后面的课程中会用到技术
函数重载
函数重载概述
作用:函数名可以相同,提高复用性
函数重载满足条件:
-
用一个作用域下
-
函数名称相同
-
函数参数类型不同或者个数不同或者顺序不同
注意:函数的返回值不可以作为函数重载的条件
#include <iostream> using namespace std; //函数重载 //函数名可以相同,提高复用性 //函数重载满足条件 //1、同一作用域下 //2、函数名称相同 //3、函数参数类型不同或者个数不同或者顺序不同 void func() { cout << "func的调用" << endl; } void func(int a) { cout << "func(int a)的调用" << endl; } void func(double a) { cout << "func(double a)的调用" << endl; } void func(int a,double b) { cout << "func(int a,double b)的调用" << endl; } void func(double a, int b) { cout << "func(double a, int b)的调用" << endl; } //注意函数的返回值不可以作为函数重载的条件 //int func(double a, int b) { // cout << "func(double a, int b)的调用" << endl; //} int main() { //func(); //调用第一个 //func(10); //调用第二个 //func(3.14); //调用第三个 func(10,3.14); //调用第四个 return 0; }
函数重载注意事项:
-
引用作为函数重载条件
-
函数重载碰到函数默认参数
#include <iostream> using namespace std; //1、引用作为重载的条件 void func(int& a) { //int & a = 10;不合法 cout << "func(int &a)调用" << endl; } void func(const int& a) { //const int &a = 10;合法,常量引用 cout << "func(const int &a)调用" << endl; } //2、函数重载碰到默认参数 void func2(int a,int b ) { //void func2(int a,int b = 10)出现二义性报错,尽量避免这类错误 cout << "func2(int a,int b)的调用" << endl; } void func2(int a) { cout << "func2(int a)的调用" << endl; } int main() { //int a = 10; //func(a); //调用第一个因为const只读不写 //func(10); //调用第二个 func2(10); //调用第三个 return 0; }
类和对象
C++面向对象的三大特性:封装、继承、多态
C++认为万事万物皆为对象,对象上有其属性和行为
人可以作为对象,属性有姓名,年龄,身高等等,行为有走,跑,跳,吃饭等等
具有相同性质的对象,我们可以抽象为类。人属于人类,车属于车类
封装
封装的意义
封装是C++面向对象三大特性之一
意义:
-
将属性和行为作为一个整体,表现生活的事物
-
将属性和行为加以权限控制
封装的意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{ 访问权限:属性 / 行为 };
#include <iostream> using namespace std; //设计一个圆类:求圆的周长 //圆周率 const double PI = 3.14; //class代表设计一个类,类后面紧跟着的是类名 class Circle { //访问权限 //公共权限 public: //属性 //半径 int r; //行为 //获取圆的周长 double calculateZC() { return 2 * PI * r; } }; int mian() { //通过圆类 创建具体的圆(对象) //实例化 (通过一个类 创建一个对象的过程) Circle cl; //给圆对象的属性进行赋值 cl.r = 10; //2 * PI * r = 62.8 cout << "圆的周长" << cl.calculateZC() << endl; return 0; }
封装的意义二
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
1.public 公共权限 成员 类内可以访问 类外可以访问
2.protected 保护权限 成员 类内可以访问 类外不可以访问 子类也可以访问父类中保护的内容
3.private 私有权限 成员 类内可以访问 类外不可以访问 子类不可以访问父类中私有的内容
class Person{ public: string name; protected: string car; private: int password; private: void func(){ name = "张三"; car = "奔驰"; password = "123"; } } int main(){ //实例化具体对象 Person p1; p1.name = "李四"; p1.car = "奔驰"; //报错,保护权限内容,在类外访问不到 p1.password = "123" //报错,私有权限内容,在类外访问不到 p1.func(); return 0; }
struct和class区别
在C++中struct和class都能创建类,唯一区别就在于:默认的访问权限不同
区别:
-
struct默认权限为公共
-
class 默认权限为私有
成员属性设置为私有
优点1:将所有成员属性设为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
对象的初始化和清理
-
生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
-
C++中面向对象来源于生活,每个对象也都会有初始化设置以及对象销毁前的清理数据的设置
构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知
同样的使用玩一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供
编译器提供的构造函数和析构函数是空实现
-
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用无需手动调用
-
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候自动调用构造,无需手动调用,而且只会调用一次
析构函数:~类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同,在名称前加上符号~
3.构造函数不可以有参数,因此不可以发生重载
4.程序在销毁对象前自动调用析构,无需手动调用,而且只会调用一次
#include <iostream> using namespace std; //对象的初始化和清理 class Person1 { public: //1、构造函数 进行初始化操作 //没有返回值也不写void //函数名称与类名相同 //构造函数可以有参数,因此可以发生重载 //程序在销毁对象时候自动调用构造,无需手动调用,而且只会调用一次 Person1() { cout << "Person构造函数的调用" << endl; } //2.析构函数 进行清理操作 //没有返回值也不写void //函数名称与类名相同 前面加符号~ //构造函数不可以有参数,因此不可以发生重载 //程序在销毁对象时候自动调用析构,无需手动调用,而且只会调用一次 ~Person1() { cout << "Person析构函数的调用" << endl; } }; //构造析构都是必须有的实现,如果我们自己不提供,编译器会提供一下空实现的构造和析构 void test01() { Person1 p; //局部变量,创建在栈上,test01执行完后就会释放这个对象,析构在销毁前自动调用 } int main() { //test01(); Person1 p; //在main函数里不会被释放,按下pause任意键后会被释放,但是同时窗口关闭可能看不到 system("pause"); return 0; }
构造函数的分类和调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
括号法,显示法,隐式转换法
#include <iostream> using namespace std; //分类 // 按照参数分类:无参构造(默认构造) 有参构造 // 按照类型分类:普通构造 拷贝构造 class Person2 { public: //构造函数 Person2() { cout << "Person无参构造函数的调用" << endl; } Person2(int a) { age = a; cout << "Person有参构造函数的调用" << endl; } //拷贝构造函数 Person2(const Person2 &p) { //拷贝要const防止误改,并且要引用方式传入 //将传入的人身上所有属性,拷贝到我身上 age = p.age; cout << "Person拷贝构造函数的调用" << endl; } ~Person2() { cout << "Person析构函数的调用" << endl; } int age; }; //调用 void test02() { //1、括号法 //Person2 p1; //默认构造函数调用方法 //Person2 p2(10); //调用有参构造函数 //Person2 p3(p2); //调用拷贝构造函数 //!!注意:调用默认函数时候不要用小括号 //因为下面这个函数,编译器会认为是一个函数的声明,返回类型是Person,函数名p1,不会认为在创建对象 //Person p1(); //2、显示法 Person2 p1; Person2 p2 = Person2(10); //有参构造 Person2 p3 = Person2(p2); //拷贝构造 //Person2(10); //匿名对象有参构造,当前行执行后,系统会立即回收掉匿名对象 //cout << "aaa" << endl; //会在析构函数调用后打印,说明是在匿名对象销毁掉后在打印 //!!注意:不要利用拷贝构造函数初始化匿名对象 //Person2(p3); //报错重定义,编译器会认为是对象声明等价于 Person2 p3;是在定义p3对象,与上面重名 //3、隐式转换法 Person2 p4 = 10; //相当于,写了 Person p4 = Person(10);是有参构造的调用 Person2 p5 = p4; //拷贝构造 } int main() { test02(); system("pause"); return 0; }
构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况:
-
使用一个已经创建完毕的对象来初始化一个新对象
-
值传递的方式给函数参数传值
-
以值方式返回局部对象
#include <iostream> using namespace std; class Person3 { public: Person3() { cout << "Person默认构造函数调用" << endl; } Person3(int age) { cout << "Person有参构造函数的调用" << endl; m_Age = age; } Person3(const Person3& p) { cout << "Person拷贝构造函数的调用" << endl; m_Age = p.m_Age; } ~Person3() { cout << "Person析构函数调用" << endl; } int m_Age; }; //1、使用一个已经创建完毕的对象来初始化一个新对象 void test03() { Person3 p1(20); Person3 p2(p1); cout << "p2的年龄为:" << p2.m_Age << endl; } //2、值传递的方式给函数参数传值 //值传递相当于 Person3 p = p 拷贝构造函数的隐式写法 void doWork(Person3 p) { p.m_Age = 1000; //值传递不会改变实参数 } void test04() { Person3 p = 20; //有参构造函数 doWork(p); //这里p不等于doWork(Person3 p)中的p,是拷贝Person3 p = 20;的p cout << p.m_Age << endl; //打印为20 } //3、值方式返回局部对象 Person3 doWork2() { Person3 p1; //默认构造函数,局部变量,会被释放 cout << (int*) & p1 << endl; return p1; //这里返回的p1不是上一行的p1,而是克隆体 } void test05() { Person3 p = doWork2(); cout << (int*)&p << endl; //p地址转换成一个整形的指针,就是&p } int main() { //test03(); //test04(); test05(); system("pause"); return 0; }
构造函数调用规则
默认情况下,C++编译器至少给一个类添加三个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
-
如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
-
如果用户定义拷贝构造函数,C++不会再提供其他构造函数
#include <iostream> using namespace std; //1.默认构造函数(无参,函数体为空) //2.默认析构函数(无参,函数体为空) //3.默认拷贝构造函数,对属性进行值拷贝 class Person4 { public: /*Person4() { cout << "默认构造函数调用" << endl; }*/ /*Person4(int age) { cout << "有参构造函数调用" << endl; m_Age = age; }*/ Person4(const Person4& p) { cout << "拷贝构造函数调用" << endl; m_Age = p.m_Age; } ~Person4() { cout << "析构构造函数调用" << endl; } int m_Age; }; //void test4() { // Person4 p; // p.m_Age = 18; //虽然没有了拷贝构造函数,但是系统会自动写m_Age = p.m_Age;并隐藏 // // Person4 p2(p); // cout << "p2年龄:" << p2.m_Age << endl; //} //将默认构造注释,如果我们写了有参构造函数,编译器就不再提供给默认构造,但是依然构造拷贝构造 //void test5() { // Person4 p(28); // // Person4 p2(p); // cout << "p2年龄:" << p2.m_Age << endl; //} //如果我们写了拷贝构造函数,编译器就不再提供给其他构造 void test6() { //Person4 p; //报错,当只有拷贝构造函数时,其他构造函数都不会自动写 } int main() { //test4(); //test5(); test6(); return 0; }
对象的初始化和清理
深拷贝和浅拷贝
深浅拷贝时面试经典问题,常见第一个坑
浅拷贝 :简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#include <iostream> using namespace std; class Person { public: Person() { cout << "默认构造函数调用" << endl; } Person(int age,int height) { m_Age = age; m_Height = new int(height); //new将数据开辟到堆区 cout << "有参构造函数调用" << endl; } //自己实现一个拷贝构造函数。来解决浅拷贝问题 Person(const Person& p) { cout << "拷贝构造函数调用" << endl; m_Age = p.m_Age; //m_Height = p.m_Height编译器默认实现就是这行代码 //深拷贝操作 m_Height = new int(*p.m_Height); //重新开辟数据堆区 } ~Person() { //p1,p2都会调用析构函数,其中堆区的数据在第一次析构函数调用时就被释放,第二次调用析构函数再去删去m_Heght的堆区是没有数据的从所以报错 // 浅拷贝的问题需要深拷贝来解决 //析构代码,会将堆区开辟数据做释放操作 if (m_Height != NULL) { delete m_Height; m_Height = NULL; //防止野指针出现 } cout << "析构函数调用" << endl; } int m_Age; int * m_Height; }; void test01() { Person p1(18,160); cout << "p1年龄为:" << p1.m_Age << " 身高为:" << *p1.m_Height << endl; Person p2(p1); cout << "p2年龄为:" << p1.m_Age << " 身高为:" << *p2.m_Height << endl; } int main() { test01(); return 0; }
总结:如果属性在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
初始化列表
作用:用来初始化属性
语法:构造函数(): 属性1(值1),属性2(值2)...{}
//初始化列表初始化属性 Person2(int a,int b,int c) :m_A(a), m_B(b), m_C(c) { } void test(){ Person2 p(20,10,30); }
类对象作为类成员
C++类中的成员可以说另一个类的对象,我们称该成员为 对象成员
class A{} class B{ A a; }
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
当类中成员是其他类对象时,我们称该成员为对象成员
构造的顺序是:先调用对象成员构造,在调用本类构造
析构顺序与构造相反
静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
-
静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
-
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
#include <iostream> using namespace std; class Person3 { public: //1.所有对象都共享同一份数据 //2.在编译阶段分配内存 //3.类内声明 static int m_A; //静态成员变量也是有访问权限的 private: static int m_B; }; //4.类外初始化 int Person3 :: m_A = 100; int Person3 :: m_B = 200; void test3() { Person3 p; cout << p.m_A << endl; //100 Person3 p2; p2.m_A = 200; cout << p.m_A << endl; //200因为共享 } void test32() { //静态成员变量 不属于某个对象上,所有对象都共享同一份数据 //因此静态成员变量有两种访问方式 //1.通过对象进行访问 /*Person3 p; cout << p.m_A << endl;*/ //2.通过类名进行访问 cout << Person3::m_A << endl; //cout << Person3::m_B << endl; //报错,因为是私有出了作用域类外是访问不到的 } int main() { //test3(); test32(); return 0; }
成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
面试:空对象占用内存空间为:1
#include <iostream> using namespace std; class Person1{ int m_A; //非静态成员变量 static int m_B; // 静态成员变量,不属于类的对象上 void func(){} //非静态成员函数,不属于类的对象上 static void func2() {}; // 静态成员函数,不属于类的对象上 }; int Person1::m_B = 0; void test11() { Person1 p; //空对象占用内存空间为:1 //因为C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置 //每个空对象也应该有个独一无二的内存地址 cout << "size of p=" << sizeof(p) << endl; } void test12() { Person1 p; // cout << "size of p=" << sizeof(p) << endl; } int main() { //test11(); test12(); return 0; }
this指针的用途
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
问:这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象(谁调用指向谁)
this指针是隐含每一个非静态成员函数的一种指针
this指针不需要定义,直接使用即可
用途:
-
当形参和实参同名时可以使用this来区分
-
在类的非静态成员函数中返回对象本身,可返回return *this
#include <iostream> using namespace std; class Person2 { public: Person2(int age) { //this指向 被调用的成员函数 所属的对象,指向p1 this->age = age; } Person2& PersonAddAge(Person2& p) { //用引用的方式返回 //如果没有&是以值的方式返回,会拷贝构造一个新的数据而不是p2本体 this->age += p.age; //this指向p2指针,而*this指向的就是p2这个对象本体 return *this; } int age; //如果没有this指针,成员变量与形参名称冲突 }; //1.解决名称冲突 void test01() { Person2 p1(18); cout << "p1的年龄为" << p1.age << endl; } //2.返回对象本身 void test22() { Person2 p1(10); Person2 p2(10); //链式编程思想 p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); //因为p2.PersonAddAge(p1)返回的就是p2本体,实现了(10+10)+10+10叠加 cout << "p2的年龄为" << p2.age << endl; } int main() { //test01(); test22(); return 0; }
空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
#include <iostream> using namespace std; class Person3 { public: void showClassName() { cout << "this is Person class" << endl; } void showPersonAge() { if (this == NULL) { return; //如果为空直接扔掉 } cout << "age=" << m_Age << endl; //都会默认加上this->m_Age,因为传入的this指针为空 } int m_Age; }; void test31() { Person3* p = NULL; p->showClassName(); //可以运行 p->showPersonAge(); } int main() { test31(); return 0; }
const修饰成员函数
常函数:
-
成员函数后加const后我们称为这个函数为常函数
-
常函数内不可以修改成员属性
-
成员属性声明时关键字mutable后,在常函数中依然可以修改
常对象:
-
声明对象前加const称改对象为常对象
-
常对象只能调用常函数
#include <iostream> using namespace std; class Person4 { public: //this指针的本质 是指针常量 指针的指向是不可以修改的 //const Person4 * const this //在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以改 void showPerson() const{ this->m_B = 100; //this->m_A = 100; 加了const指向的值也不能改了 //this = NULL; //指针的指向是不可以修改的 } void func() { } int m_A; mutable int m_B; //特殊变量,即使在常函数中,也可以改变这个值,在前面加上mutable }; void test41() { Person4 p; p.showPerson(); } //常对象 void test42() { const Person4 p; //在对象前面加const,变为常对象 //p.m_A = 100; 报错 p.m_B = 100; //m_B是特殊值,在常对象下也可以修改 //常对象只能调用常函数 p.showPerson(); //p.func(); 报错,如果这个能调用,因为没有普通成员函数可以修改成员变量,就侧面的说明可以在func函数中更改变量,这是不允许的 } int main() { test41(); return 0; }
友元
在程序里,有些私有属性 也想让类外特殊的一些函数或者类访问,就需要用到友元的技术
友元的目的:让一个函数或者类,访问另一个类中私有成员
友元关键字:friend
三种实现:
-
全局函数做友元
-
类做友元
-
成员函数做友元
全局函数做友元
#include <iostream> using namespace std; //建筑物类 class Building { //goodBoy全局函数是Building好盆友,可以访问私有属性 friend void goodBoy(Building* building); public: Building() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } public: string m_SittingRoom; private: string m_BedRoom; }; //全局函数 void goodBoy(Building* building) { cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl; cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl; //报错,私有属性在类外不能访问 } void test51() { Building building; goodBoy(&building); } int main() { test51(); return 0; }
友元类
#include <iostream> using namespace std; class Building5; class GoodGay { public: GoodGay(); void visit(); //参观函数 访问building中的属性 Building5 * building5; }; class Building5 { //goodBoy全局函数是Building好盆友,可以访问私有属性 friend class GoodGay; public: Building5(); public: string m_SittingRoom; private: string m_BedRoom; }; //类外写成员函数 Building5::Building5() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; }; GoodGay::GoodGay() { //创建一个建筑物对象 building5 = new Building5(); //building是指针在GoodGay完成后,会被释放,这里开辟堆区存放数据,new什么数据类型,返回什么数据类型 } void GoodGay::visit() { cout << "好基友正在访问:" << building5->m_SittingRoom << endl; cout << "好基友正在访问:" << building5->m_BedRoom << endl; } void test61() { GoodGay gg; gg.visit(); } int main() { test61(); return 0; }
成员函数做友元
#include <iostream> using namespace std; class Building7; class GoodGay7 { public: GoodGay7(); void visit71(); //让visit71函数可以访问Building中的私有成员 void visit72(); //让visit72函数不可以访问Building中的私有成员 Building7* building7; }; class Building7 { //告诉GoodGay作为本类的好朋友,可以访问私有成员 friend void GoodGay7::visit71(); public: Building7(); public: string m_SittingRoom; private: string m_BedRoom; }; //类外实现成员函数 GoodGay7::GoodGay7() { building7 = new Building7(); } void GoodGay7::visit71() { cout << "visit函数正在访问" << building7->m_SittingRoom << endl; cout << "visit函数正在访问" << building7->m_BedRoom << endl; } void GoodGay7::visit72() { cout << "visit函数正在访问" << building7->m_SittingRoom << endl; //cout << "visit函数正在访问" << building7->m_BedRoom << endl; } Building7::Building7() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } void test7() { GoodGay7 gg; gg.visit71(); gg.visit72(); } int main() { test7(); return 0; }
运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加号运算符重载
作用:实现两个自定义数据类型相加的运算
#include <iostream> using namespace std; //加号运算符重载 class Person { public: 1、成员函数重载加号 //Person operator+(Person& p) { // Person temp; // temp.m_A = this->m_A + p.m_A; // temp.m_B = this->m_B + p.m_B; // return temp; //} int m_A; int m_B; }; //2、全局函数重载加号 Person operator+(Person& p1, Person& p2) { Person temp; temp.m_A = p1.m_A + p2.m_A; temp.m_B = p1.m_B + p2.m_B; return temp; } //函数重载版本 Person operator+(Person& p1, int num) { Person temp; temp.m_A = p1.m_A + num; temp.m_B = p1.m_B + num; return temp; } void test11() { Person p1; p1.m_A = 10; p1.m_B = 10; Person p2; p2.m_A = 10; p2.m_B = 10; // 成员函数重载本质:Person p3=p1.operator+(p2) //全局函数重载本质:Person p3=operator(p1,p2) Person p3= p1 + p2; //报错 //运算符重载也可以发生函数重载 Person p4 = p1 +100; //是person类型和int类型相加 cout << "p3.m_A=" << p3.m_A << endl; cout << "p3.m_B=" << p3.m_B << endl; cout << "p4.m_A=" << p4.m_A << endl; cout << "p4.m_B=" << p4.m_B << endl; } int main() { test11(); return 0; }
总结:
1.对于内置的数据类型的表达式的运算符是不可以改变的
2.不要滥用运算符重载(名加法写减法)
左移运算符重载
#include <iostream> using namespace std; class Person2 { friend ostream& operator<<(ostream& cout, Person2& p); public: Person2(int a, int b) { m_A = a; m_B = b; } private: //利用成员函数重载左移运算符 p.operator<<(cout) 简化版本p<<cout //因此不会利用成员函数重载左移运算符,因为无法实现cout在左侧 /*void operator<< (cout) { }*/ int m_A; int m_B; }; //只能利用全局函数重载左移运算符 //本质 operator<<(cout ,p) 简化版本cout << p ostream& operator<<(ostream &cout,Person2 &p) { //ostream输出流且全局只能有一个,用引用的方法 cout << "m_A=" << p.m_A << " m_B=" << p.m_B << endl; return cout; } void test2() { Person2 p(10,10); /*p.m_A = 10; p.m_B = 10;*/ cout << p << " hello world" << endl; } int main() { test2(); return 0; }
递增运算符重载
作用:实现自己的整型数据
#include <iostream> using namespace std; //重载递增运算符 //自定义整型 class MyInteger { friend ostream& operator<<(ostream & cout, MyInteger myint); public: MyInteger() { m_Num = 0; } //重载前置++运算符,返回引用是为了一直对一个数据进行递增 MyInteger& operator++() { m_Num++; //将自身返回 return *this; } //重载后置++运算符 //int代表的是占位参数,用于区分前置和后置递增,并且只认int,double等不认 //局部变量temp执行完会被释放,不能返回接着引用,是非法的,也可以用前面的开辟堆区返回*temp的方式 MyInteger operator++(int) { //先记录结果 MyInteger temp = *this; //后递增 m_Num++; //最后将记录的结果返回 return temp; } private: int m_Num; }; //重载运算符<< ostream& operator<<(ostream & cout, MyInteger myint) { cout << myint.m_Num; return cout; } void test3() { MyInteger myint; cout << ++(++myint) << endl; //前置重载时如果不用引用,结果是2,1,第一次返回的是新的变量 cout << myint << endl; } void test32() { MyInteger myint; cout << myint++ << endl; cout << myint << endl; } int main() { //test3(); test32(); int a = 0; cout << ++(++a) << endl; cout << a << endl; return 0; }
赋值运算符重载
c++编译器至少会给一个类添加4个函数
-
默认构造函数(无参,函数体为空)
-
默认析构函数(无参,函数体为空)
-
默认拷贝构造函数,对属性进行值拷贝
-
赋值运算符operator,做赋值操作时也会出现深浅拷贝问题
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
#include <iostream> using namespace std; class Person4 { public: Person4(int age) { m_Age = new int(age); } ~Person4() { if (m_Age != NULL) { delete m_Age; m_Age = NULL; //堆区内存重复释放,浅拷贝崩溃 } } //重载 赋值运算符 Person4& operator=(Person4& p) { //编译器是提供浅拷贝 //m_Age = p.m_Age; //应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝 if (m_Age != NULL) { delete m_Age; m_Age = NULL; } //深拷贝 m_Age = new int(*p.m_Age); //返回对象本身 return *this; } int *m_Age; }; void test4() { Person4 p1(18); Person4 p2(20); Person4 p3(30); p3 = p2 = p1; cout << "p1的年龄为:" << *p1.m_Age << endl; cout << "p2的年龄为:" << *p2.m_Age << endl; cout << "p3的年龄为:" << *p3.m_Age << endl; } int main() { test4(); return 0; }
关系运算符重载
作用:可以两个自定义类型对象进行对比操作
#include <iostream> using namespace std; class Person5 { public: Person5(string name, int age) { m_Age = age; m_Name = name; } //重载==号 bool operator==(Person5& p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return true; } else return false; } bool operator!=(Person5& p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return false; } else return true; } string m_Name; int m_Age; }; void test5() { Person5 p1("Tom", 18); Person5 p2("Tom", 18); if (p1 == p2) { cout << "p1和p2相等" << endl; } else { cout << "p1和p2不相等" << endl; } if (p1 != p2) { cout << "p1和p2不相等" << endl; } else { cout << "p1和p2相等" << endl; } } int main() { test5(); return 0; }
函数调用运算符重载
-
函数调用运算符()也可以重载
-
由于重载后使用的方式非常像函数的调用,也称为仿函数
-
仿函数没有固定写法,非常灵活
#include <iostream> using namespace std; //打印输出类 class MyPrint { public: //重载函数调用运算符 void operator()(string test) { cout << test << endl; } }; void MyPrint02(string test) { cout << test << endl; } //仿函数非常灵活,没有固定写法,加法类 class MyAdd { public: int operator()(int num1, int num2) { return num1 + num2; } }; void test6() { MyPrint myPrint; myPrint("hello world"); //由于使用起来非常像函数,因此也被称为仿函数 MyPrint02("hello world"); } void test62() { MyAdd myadd; int ret = myadd(100, 100); cout << "ret=" << ret << endl; //匿名函数对象,第二个括号是使用重载 cout << MyAdd()(100, 100) << endl; } int main() { //test6(); test62(); return 0; }
继承
继承是面向对象三大特征之一
定义类时,下级别成员除了拥有上级别的共性,也有自己的特性(如:布偶猫,加菲猫)
这个时候我们就可以考虑利用继承的技术,减少重复代码
继承的基本语法
#include <iostream> using namespace std; //普通实现页面 //!!继承好处:减少重复代码 //!语法:class子类 :继承方式 父类 //! 子类也成为派生类 //! 父类也成为基类 //简称实现页面 //公共页面 class BasePage { public: void header() { cout << "首页、公开课、登录、注册...(公共头部)" << endl; } void footer() { cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; } void left() { cout << "Java、Python、C++/...(公共分类列表)" << endl; } }; //Java页面 class Java :public BasePage { public: void content() { cout << "Java学科视频" << endl; } }; class Python :public BasePage { public: void content() { cout << "Python学科视频" << endl; } }; class CPP :public BasePage { public: void content() { cout << "C++学科视频" << endl; } }; void test1() { cout << "Java下载视频页面如下:" << endl; Java ja; ja.header(); ja.content(); ja.left(); ja.footer(); cout << "-----------------------------" << endl; cout << "Python下载视频页面如下:" << endl; Python py; py.header(); py.content(); py.left(); py.footer(); cout << "-----------------------------" << endl; cout << "C++下载视频页面如下:" << endl; CPP cpp; cpp.header(); cpp.content(); cpp.left(); cpp.footer(); } int main() { test1(); return 0; }
继承方式
继承语法:class子类 :继承方式 父类
三种方式:1、公共继承public 2、保护继承protected 3、私有继承private
子类不论那种继承方式,都不能访问父类私有权限成员,子类权限收缩
例如:当子类以保护方式继承父类,则父类公共,保护成员,到子类都变为保护成员
当子类以私有方式继承父类,则父类公共,保护成员,到子类都变为私有成员
但是都不能访问到父类私有权限成员
继承的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
#include <iostream> using namespace std; class Base { public: int m_A; protected: int m_B; private: int m_C; }; class Son :public Base { public: int m_D; }; //利用开发人员命令提示工具查看对象模型 //跳转盘符 E: //跳转文件路径 cd 具体路径下 //查看命名 //cl /d1 reportSingleClassLayoutSon类名 void test2() { //父类中所有非静态成员属性都会被子类继承下去 //父类中私有成员属性,是被编译器隐藏了,因此是访问不到,但是确实被继承下去了 cout << "size of Son =" << sizeof(Son) << endl; } int main() { test2(); return 0; }
继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序谁先谁后?
先构造父类,再构造子类,析构的顺序与构造相反
#include <iostream> using namespace std; class Base { public: Base() { cout << "Base构造函数!" << endl; } ~Base() { cout << "Base析构函数!" << endl; } }; class Son :public Base{ public: Son() { cout << "Son构造函数!" << endl; } ~Son() { cout << "Son析构函数!" << endl; } }; void test3() { //Base b; //继承中的构造和析构顺序如下: //先构造父类,再构造子类,析构的顺序与构造相反 Son s; } int main() { test3(); return 0; }
继承同名成员处理方式
问:当子类与父类出现同名的成员,如果通过子类对象,访问到子类或父类中同名的数据呢?
-
访问子类同名成员,直接访问即可
-
访问父类同名成员,需要加作用域
#include <iostream> using namespace std; class Base4{ public: Base4() { m_A = 100; } void func() { cout << "Base - func()调用" << endl; } void func(int a) { cout << "Base - func(int a)调用" << endl; } int m_A; }; class Son4 :public Base4{ public: Son4() { m_A = 200; } void func() { cout << "Son - func()调用" << endl; } int m_A; }; void test4() { Son4 s; cout << "Son下的m_A = " << s.m_A << endl; //如果通过子类对象 访问到父类中同名成员,需要加作用域 cout << "Base下的m_A = " << s.Base4::m_A << endl; } //同名成员函数处理 void test42() { Son4 s; s.func(); //直接调用 调用的是子类中的同名成员 //如何调用父类中的同名成员函数 s.Base4::func(); //如果子类出现了和父类同名成员函数,子类的同名成员函数会隐藏掉父类中所有同名成员函数 //如果想要访问父类中被隐藏的同名成员函数,要加作用域 s.Base4::func(100); } int main() { //test4(); test42(); return 0; }
总结:
-
子类对象可以直接访问到子类同名成员
-
子类对象加作用域可以访问到父类同名成员
-
当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
继承同名静态成员处理方式
问:继承中同名的静态成员在子类对象上如何进行访问
静态成员和非静态成员出现同名,处理方式一致
-
访问子类同名成员,直接访问即可
-
访问父类同名成员,需要加作用域
#include <iostream> using namespace std; class Base5 { public: static int m_A; static void func() { cout << "Base - func()调用" << endl; } static void func(int a) { cout << "Base - func(int a)调用" << endl; } }; int Base5::m_A = 100; class Son5 :public Base5 { public: static int m_A; static void func() { cout << "Son- func()调用" << endl; } }; int Son5::m_A = 200; void test5() { Son5 s; //通过对象访问 cout << "通过对象访问" << endl; cout << "Son下的m_A = " << s.m_A << endl; cout << "Base下的m_A = " << s.Base5::m_A << endl; //通过类名访问 cout << "通过类名访问" << endl; cout << "Son下的m_A = " << Son5::m_A << endl; //第一个::代表通过类名方式访问,第二个::代表访问父类作用域下 cout << "Base下的m_A = " << Son5::Base5::m_A << endl; } //同名成员函数处理 void test52() { Son5 s; //通过对象访问 cout << "通过对象访问" << endl; s.func(); s.Base5::func(); //通过类名访问 cout << "通过类名访问" << endl; Son5::func(); Son5::Base5::func(); //如果子类和父类拥有同名成员函数,子类的同名成员函数会隐藏掉父类中所有同名成员函数 Son5::Base5::func(100); } int main() { //test5(); test52(); return 0; }
总结:同名静态成员处理方式与非静态处理方式一样,只不过有两种访问的方式(通过对象和类名)
多继承语法
C++中允许一个类继承多个类
语法:class子类 : 继承方式 父类1, 继承方式 父类2
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
#include <iostream> using namespace std; class Base6 { public: Base6() { m_A = 100; } int m_A; }; class Base62 { public: Base62() { m_A = 200; } int m_A; }; class Son6 : public Base6, public Base62 { public: Son6() { m_C = 300; m_D = 400; } int m_C; int m_D; }; void test6() { Son6 s; //C,D两个字节(8),两个父类各一个字节(4) 8+4+4=16 cout << "sizeof Son =" << sizeof(s) << endl; //当父类中出现了同名成员,需要加作用域 cout << "base下的m_A = " << s.Base6::m_A << endl; cout << "base2下的m_A = " << s.Base62::m_A << endl; } int main() { test6(); return 0; }
菱形继承
概念:
-
两个派生类继承同一个基类
-
又有某个类同时继承这两个派生类
-
这种继承被称为菱形继承,或者钻石继承
菱形继承问题:
-
羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性
-
羊驼继承自动物的数据继承了两份,但是我们只需要一份就可以了
#include <iostream> using namespace std; class Animal{ public: int m_Age; }; //利用虚继承来解决菱形继承问题 //继承之前 加上关键字 virtual 变成虚继承 //Animal类变成虚基类 class Sheep :virtual public Animal{}; class Tuo :virtual public Animal{}; class SheepTuo : public Sheep, public Tuo { }; void test7() { SheepTuo st; st.Sheep::m_Age = 18; st.Tuo::m_Age = 28; //相当于变成共享数据了,继承了两指针 //菱形继承,有两个父类拥有相同的数据,需要加作用域区分 cout << "st.Sheep::m_Age =" << st.Sheep::m_Age << endl; cout << "st.Tuo::m_Age =" << st.Tuo::m_Age << endl; cout << "st.m_Age =" << st.m_Age << endl; //只要一份数据,菱形继承导致数据有两份,资源浪费 } int main() { test7(); return 0; }
总结:利用虚继承来解决菱形继承问题,继承之前 加上关键字 virtual 变成虚继承 只要一份数据,菱形继承导致数据有两份,资源浪费
多态
基本概念
多态是C++面向对象三大特性之一
多态分为两类:
-
静态多态:函数重载和运算符重载属于静态多态,复用函数名
-
动态多态:派生类和虚函数实现运行时的多态
静态多态和动态多态区别:
-
静态多态的函数地址早绑定 - 编译阶段确定函数地址
-
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
#include <iostream> using namespace std; class Animal { public: //虚函数 virtual void speak() { cout << "动物在说话" << endl; } }; class Cat :public Animal { public: //重写:函数返回值类型 函数名 参数列表 完全相同virtual不影响 void speak() { cout << "小猫在说话" << endl; } }; class Dog :public Animal { public: void speak() { cout << "小狗在说话" << endl; } }; //执行说话的函数 //地制早绑定 在编译阶段确定函数地址 //如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定 //让Animal.speak变成虚函数 //动态多态满足条件 //1.有继承关系 //2.子类重写父类的虚函数 //动态多态使用 //父类的指针或者引用来指向子类的对象Animal& animal = cat void doSpeak(Animal& animal) { //Animal& animal = cat animal.speak(); } void test1() { Cat cat; Dog dog; doSpeak(cat); doSpeak(dog); } int main() { test1(); return 0; }
总结:动态多态满足条件 1.有继承关系 2.子类重写父类的虚函数
动态多态使用 父类的指针或者引用来指向子类的对象Animal& animal = cat
重写:函数返回值类型 函数名 参数列表 完全相同virtual不影响
多态案例—计算器类
描述:分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态优点:
-
代码组织结构清晰
-
可读性强
-
利用前期和后期的扩展以及维护
#include <iostream> using namespace std; //利用普通写法和多态技术实现计算器 //普通写法 class Calulator { public: int getResult(string oper) { if (oper == "+") { return m_Num1 + m_Num2; } else if (oper == "-") { return m_Num1 - m_Num2; } else if (oper == "*") { return m_Num1 * m_Num2; } //如果想扩展新的功能,需要修改源代码 //在真实开发中,提倡开闭原则 //对扩展进行开放,对修改进行关闭 } int m_Num1; //操作数1 int m_Num2; }; void test21() { Calulator c; c.m_Num1 = 10; c.m_Num2 = 10; cout << c.m_Num1 << " + " << c.m_Num2 << "= " << c.getResult("+") << endl; cout << c.m_Num1 << " - " << c.m_Num2 << "= " << c.getResult("-") << endl; cout << c.m_Num1 << " * " << c.m_Num2 << "= " << c.getResult("*") << endl; } //利用多态实现计算器 //多态优点 // 1、组织结构清晰 // 2、可读性强 // 3、对于前期和后期扩展以及维护 //实现计算器的抽象类 class AbstractCalculator { public: virtual int getResult() { return 0; } int m_Num1; int m_Num2; }; //加法计算器 class AddCalculator :public AbstractCalculator { public: int getResult() { return m_Num1 + m_Num2; } }; //减法计算器 class SubCalculator :public AbstractCalculator { public: int getResult() { return m_Num1 - m_Num2; } }; //乘法计算器 class MulCalculator :public AbstractCalculator { public: int getResult() { return m_Num1 * m_Num2; } }; void test22() { //父类指针或者引用指向子类对象 //加法运算 AbstractCalculator* abc = new AddCalculator; abc->m_Num1 = 10; abc->m_Num2 = 10; cout << abc->m_Num1 << " + " << abc->m_Num2 << "= " << abc->getResult() << endl; //用完后记得销毁 delete abc; //减法计算 abc = new SubCalculator; abc->m_Num1 = 100; abc->m_Num2 = 100; cout << abc->m_Num1 << " - " << abc->m_Num2 << "= " << abc->getResult() << endl; delete abc; //乘法计算 abc = new MulCalculator; abc->m_Num1 = 100; abc->m_Num2 = 100; cout << abc->m_Num1 << " * " << abc->m_Num2 << "= " << abc->getResult() << endl; delete abc; } int main() { test21(); return 0; }
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0
当类中有了纯虚函数,这个类也称为抽象类
特点:
-
无法实例化对象
-
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include <iostream> using namespace std; //纯虚函数和抽象类 class Base { public: //必须虚函数基础上等于零才合法,为纯虚函数 //当类中有了一个纯虚函数,称为抽象类 //抽象类特点: //1、无法实例化对象 //2、抽象类的子类 必须要重写父类中的纯虚函数 否则也属于抽象类 virtual void func() = 0; }; class Son :public Base { public: virtual void func(){ cout << "func函数调用" << endl; }; }; void test3() { //Base b; //抽象类无法实现实例化对象 Son s; //抽象类的子类 必须要重写父类中的纯虚函数 否则也属于抽象类也不能实现实例化对象 Base* base = new Son; base->func(); } int main() { test3(); return 0; }
多态案例—制作饮品
描述:制作饮品大致流程:煮水-冲泡-倒入杯中-加入辅料
#include <iostream> using namespace std; class AbstractDrinking { public: //煮水 virtual void Boil() = 0; //冲泡 virtual void Brew() = 0; //倒入杯中 virtual void PourInCup() = 0; //加入辅料 virtual void PutSomething() = 0; void makeDrink() { Boil(); Brew(); PourInCup(); PutSomething(); } }; class Coffee :public AbstractDrinking { public: virtual void Boil() { cout << "煮农夫山泉" << endl; }; virtual void Brew() { cout << "冲泡咖啡" << endl; }; virtual void PourInCup() { cout << "倒入杯中" << endl; }; virtual void PutSomething() { cout << "加入牛奶和糖" << endl; }; }; class Tea :public AbstractDrinking { public: virtual void Boil() { cout << "煮矿泉水" << endl; }; virtual void Brew() { cout << "冲泡茶叶" << endl; }; virtual void PourInCup() { cout << "倒入杯中" << endl; }; virtual void PutSomething() { cout << "加入枸杞" << endl; }; }; void doWork(AbstractDrinking *abs) { //AbstractDrinking *abs = new Cofee abs->makeDrink(); delete abs; //记得释放 } void test4() { doWork(new Coffee); cout << "-------------------" << endl; doWork(new Tea); } int main() { test4(); return 0; }
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决办法:将父类中的析构函数改为虚析构或者 纯虚析构
虚析构和纯虚析构的共性:
-
可以解决父类指针释放子类对象
-
都需要具体的函数实现
区别:
-
如果时纯虚析构,该类属于抽象类,无法实例化对象
语法:
虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名() = 0
类名 :: ~类名(){}
#include <iostream> using namespace std; class Animal5 { public: Animal5() { cout << "Animal的构造函数调用" << endl; } 利用虚析构可以解决,父类指针释放子类对象时不干净的问题 //virtual ~Animal5() { // cout << "Animal的虚析构函数调用" << endl; //} //纯虚析构也能解决该问题与虚析构二选一,且都要代码实现 //有了纯虚析构后,这个类也属于抽象类,无法实现实例化对象 virtual ~Animal5() = 0; //纯虚析构声明 //纯虚函数 virtual void speak5() = 0; }; //纯虚析构代码实现 Animal5 :: ~Animal5() { cout << "Animal的纯虚析构函数调用" << endl; } class Cat5 :public Animal5 { public: Cat5(string name) { cout << "cat的构造函数调用" << endl; m_Name = new string(name); } virtual void speak5() { cout << *m_Name <<"小猫在说话" << endl; } ~Cat5() { if (m_Name != NULL) { cout << "cat的析构函数调用" << endl; delete m_Name; m_Name = NULL; } } string* m_Name; }; void test5() { Animal5* animal = new Cat5("Tom"); animal->speak5(); //父类指针在析构时候 不会调用子类中析构函数 导致子类如果有堆区属性 会出现内存泄露 delete animal; } int main() { test5(); return 0; }
总结:
-
虚析构或纯虚析构都是用来解决父类指针释放子类对象
-
如果子类中没有堆区数据,可以不写虚析构或者纯虚析构
-
拥有纯虚析构函数的类也属于抽象类
多态案例—电脑组装
描述:电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include <iostream> using namespace std; class CPU { public: virtual void calculate() = 0; }; //显卡 class VideoCard { public: virtual void display() = 0; }; //内存条 class Memory{ public: virtual void storage() = 0; }; //组装电脑 class Computer { public: Computer(CPU* cpu, VideoCard* vc, Memory* mem) { m_cpu = cpu; m_vc = vc; m_mem = mem; } //提供工作函数 void work() { //让零件工作起来,调用接口 m_cpu->calculate(); m_vc->display(); m_mem->storage(); } //提供析构函数 释放3个零件 ~Computer() { if (m_cpu != NULL) { delete m_cpu; m_cpu = NULL; } if (m_vc != NULL) { delete m_vc; m_vc = NULL; } if (m_mem != NULL) { delete m_mem; m_mem = NULL; } } private: CPU* m_cpu; VideoCard* m_vc; Memory* m_mem; }; //具体厂商 class IntelCPU :public CPU { void calculate() { cout << "Intel的CPU开始计算" << endl; } }; class IntelVideoCard :public VideoCard{ void display() { cout << "Intel的显卡开始显示" << endl; } }; class IntelMemory :public Memory { void storage() { cout << "Intel的内存条开始存储" << endl; } }; //Lenovo厂商 class LenovoCpu :public CPU { void calculate() { cout << "Lenovo的CPU开始计算" << endl; } }; class LenovoVideoCard :public VideoCard { void display() { cout << "Lenovo的显卡开始显示" << endl; } }; class LenovoMemory :public Memory { void storage() { cout << "Lenovo的内存条开始存储" << endl; } }; void test6() { //第一台电脑零件 CPU* intelCpu = new IntelCPU; VideoCard* intelCard = new IntelVideoCard; Memory* itelMem = new IntelMemory; cout << "第一台电脑开始工作" << endl; //第一台电脑 Computer* computer1 = new Computer(intelCpu, intelCard, itelMem); computer1->work(); delete computer1; cout << "---------------------" << endl; cout << "第二台电脑开始工作" << endl; //第二台电脑 Computer* computer2 = new Computer(new LenovoCpu,new LenovoVideoCard,new LenovoMemory); computer2->work(); delete computer2; cout << "---------------------" << endl; cout << "第三台电脑开始工作" << endl; //第三台电脑 Computer* computer3 = new Computer(new IntelCPU, new LenovoVideoCard, new LenovoMemory); computer3->work(); delete computer3; } int main() { test6(); return 0; }
文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件<fstream>
文件类型分为两种:
-
文件文本,文件以文本的ASCII码形式存储在计算机中
-
二进制文件,文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
1.ofstream:写操作 2.ifstream:读操作 3.fstream:读写操作
文本文件
写文件
步骤:1.包含头文件 #include <fstream>
2.创建流对象 ofstream ofs;
3.打开文件 ofs.open("文件路径",打开方式);
4.写数据 ofs << "写入的数据"
5.关闭文件 ofs.close();
文件打开方式:
打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为 写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,在创建 |
ios::binary | 二进制方式 |
注意:文件打开方式可以配合使用,利用|操作符
例如:用二进制方式写文件:ios::binary | ios::out
#include <iostream> #include <fstream> using namespace std; void test7() { //创建流对象 ofstream ofs; //指定打开方式 ofs.open("test.txt",ios::out); //写内容 ofs << "姓名:张三" << endl; ofs << "性别:男" << endl; ofs << "年龄:18" << endl; //关闭文件 ofs.close(); } int main() { test7(); return 0; }
总结:
-
文件操作必须包含头文件
-
读文件可以利用ofstream,或者fstream类
-
打开文件时候需要指定操作文件的路径,以及打开方式
-
利用<<可以向文件中写数据
-
操作完毕,要关闭文件
读文件
读文件与写文件步骤相似,但是读取方式相对于比较多
步骤:1.包含头文件 #include <fstream>
2.创建流对象 ifstream ifs;
3.打开文件并判断文件是否打开成功 ifs.open("文件路径",打开方式);
4.读数据 四种方式读取
5.关闭文件 ifs.close();
#include <iostream> #include <fstream> #include <string>; using namespace std; void test8() { ifstream ifs; ifs.open("test.txt", ios::in); //判断是否打开成功 if (!ifs.is_open()) { cout << "文件打开失败" << endl; return; } //读文件 第一种 //char buf[1024] = { 0 }; //while (ifs >> buf) { // cout << buf << endl; //} 第二种 //char buf[1024] = { 0 }; //while (ifs.getline(buf,sizeof(buf))){ // cout << buf << endl; //} 第三种 //string buf; 全局 //while (getline(ifs,buf)) { // cout << buf << endl; //} //第四种不推荐 char c; while ((c = ifs.get()) != EOF) { //EOF=end of file读取到最后 cout << c; } ifs.close(); } int main() { test8(); return 0; }
总结:
-
读文件可以利用ifstream ,或者fstream类
-
利用is_open函数可以判断文件是否成功打开
-
ifs.close()关闭
二进制文件
以二进制的方式进行读写操作
打开方式要指定为ios::binary
写文件
二进制方式写文件主要利用流对象调用对象成员函数write
函数原型:ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
#include <iostream> #include <fstream> using namespace std; class Person { public: //再写字符串时最好不要用string,其本质是一个类,会把类写进去 /*string m_Name;*/ char m_Name[64]; int m_Age; }; void test9() { ofstream ofs; ofs.open("person.txt", ios::out | ios::binary); Person p = {"张三" ,18}; ofs.write((const char *)&p,sizeof(Person)); ofs.close(); } int main() { test9(); return 0; }
总结:
-
文件输出流对象,可以通过write函数,以二进制方式写数据
读文件
二进制读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len时读写的字节数
#include <iostream> #include <fstream> using namespace std; class Person { public: char m_Name[64]; int m_Age; }; void test10() { ifstream ifs; ifs.open("person.txt", ios::in | ios::binary); if (!ifs.is_open()) { cout << "文件打开失败" << endl; return; } Person p; //数据地址,并强转化为char型 所占内存空间 ifs.read((char*)&p, sizeof(Person)); cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl; ifs.close(); } int main() { test10(); return 0; }
职工管理系统
公司中职工分为三类:普通员工,经理,老板,显示信息是,需要显示职工编号,职工姓名,职工岗位,以及职责
普通员工职责:完成经理交给的任务
经理职责:完成老板交给的任务,并下发任务给员工
老板职责:管理公司所有事务
创建管理类:
内容:
-
与用户的沟通菜单页面
-
对职工增删改查的操作
-
与文件的读写交互
菜单功能:
描述:与用户的沟通页面
添加成员函数:
在管理类WorkerManager.h中添加成员函数void Show_Menu()
菜单功能实现:
在管理类WorkerManager.cpp中实现Show_Menu()函数
测试菜单功能:
在职工管理系统.cpp中测试菜单功能
退出功能:
提供退出接口
在main函数中提供分支选择,提供每个功能接口
实现退出功能
在WorkerManager.h提供退出系统的成员函数void exitSystem();
在WorkerManager.cpp中提供具体的功能实现
测试功能
在mian函数分支0选项中,调用退出程序的接口
创建职工:
抽象类和普通员工类:
职工分类为:普通员工、经理、老板
将三种职工抽象到一个类(worker)中,利用多态管理不同职工种类
职工的属性为:职工编号,职工姓名,职工所在部门编号
职工的行为:岗位职责信息描述,获取岗位名称
创建职工抽象类:
在头文件文件夹下面创建worker.h
创建普通员工类:
普通员工类继承职工抽象类,并重写父类中纯虚函数
在头文件和源文件的文件夹下分别创建employee.h和employee.cpp文件
添加职工:
功能描述:批量添加职工,并保存到文件中
功能分析:
用户在批量创建时,可能会创建不同种类的职工
如果想将所有不同种类的员工都放入到一个数组中,可以将所有员工的指针维护到一个数组里
如果想在程序中维护职工不定长度的数组,可以将数组创建到堆区,并利用Worker**的指针维护(二级指针)
文件交互-写文件
描述:对文字读写
在上一个添加功能中,我们只是将所有的数据添加到内存中,一旦程序结束就无法保存了
因此文件管理类中需要一个与文件进行交互的功能,对于文件进行读写操作
设定文件路径
首先我们将文件路径,在wokerManager.h中添加宏常量,并且包含头文件fstream,文件流
成员函数声明
在workerManager.h中类里添加成员函数void save()
文件交互-读文件
描述:将文件中的内容读取到程序中
虽然我们实现了添加职工后保存到文件的操作,但是每次开始运行程序,并没有将文件中数据读取到程序中
而我们的程序功能中还有清空文件的需求
因此构造函数初始化数据的清空分为三种
1.第一次使用,文件未创建
2.文件存在,但是数据被用户清空
3.文件存在,并且保存职工的所有数据
文件未创建
在workerManager.h中添加新的成员属性m_FilelsEmpty标志文件是否为空
修改WorkerManager.cpp中构造函数代码
文件存在且数据为空
在wokerManager.cpp中构造函数追加代码
文件存在且保存职工数据
获取记录的职工人数
在workerManager.h添加成员函数int get_EmpNum();
初始化数组
根据职工的数据以及职工数据,初始化workerManager**m_EmpArray指针
在WorkerManager.h中添加成员函数void init_Emp()
;
本阶段主要针对C++泛型编程和STL技术做详细讲解,探讨C++更深层的使用
模板
模板的概念
模板就是建立通用的道具,大大提高复用性
模板的特点:
-
模板不可以直接使用,它只是一个框架
-
模板的通用并不是万能的
函数模板
-
C++另一种编程思想称为泛型编程,主要利用的技术就是模板
-
C++提供两者模板机制:函数模板和类模板
函数模板语法
函数模板作用:建立一个通用函数,其返回函数类型和形参类型可以不具体制定,用一个虚拟的类型来代表
语法:
template<typename T> 函数声明或定义
解释:
template ---- 声明创建模板
typename ---- 表面其后面的符号是一种数据类型,可以用class代替
T---- 通用的数据类型,名称可以替换,通常为大写字母
//函数模板 template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型 void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } void test1() { int a = 10; int b = 20; //swapInt(a, b); //利用函数模板交换 //两种方式使用函数模板 //1、自动类型推导 // mySwap(a, b); //2、显示指定类型 mySwap<int>(a, b); cout << "a=" << a << endl; cout << "b=" << b << endl; }
总结:
-
函数模板利用关键字template
-
使用函数模板有两种方式:自动类型推导,显示指定类型
-
模板的目的是为了提高复用性,将类型参数化
函数模板注意事项
注意事项:
-
自动类型推导,必须推导出一直的数据类型T,才可以使用
-
模板必须要确定出T的数据类型,才可以使用
#include <iostream> using namespace std; //函数模板注意事项 template<class T> //template可以替换成class void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } //1、自动类型推导,必须推导出一直的数据类型T,才可以使用 void test21() { int a = 10; int b = 20; char c = 'c'; mySwap(a, b); //mySwap(a, c); //错误,推导不出一致的T类型 cout << "a=" << a << endl; cout << "b=" << b << endl; } //2、模板必须要确定出T的数据类型,才可以使用 template<class T> void func() { cout << "func调用" << endl; } void test22() { //func(); //错误 func<int>(); } int main() { test22(); return 0; }
函数模板案例
描述:
-
利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
-
排序规则从大到小,排序算法为选择排序
-
分别利用char数组和int数组进行测试
#include <iostream> using namespace std; //实现通用 对数组进行排序的函数 //交换函数模板 template<class T> void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } //排序算法 template<class T> void mySort(T arr[], int len) { for (int i = 0; i < len; i++) { int max = i; for (int j = i + 1; j < len; j++) { //认定的最大值比遍历出的数值要小,说明j下标的元素才是真正的最大值 if (arr[max] < arr[j]) { max = j; // 更新最大值下标 } } if (max != i) { //交换max和i元素 mySwap(arr[max], arr[i]); } } } //打印数组模板 template<class T> void printArray(T arr[], int len) { for (int i = 0; i < len; i++) { cout << arr[i] << " "; } cout << endl; } void test3() { //测试char数组 char charArr[] = "badcfe"; int num = sizeof(charArr) / sizeof(char); mySort(charArr, num); printArray(charArr, num); } void test32() { //测试数组 int intArr[] = { 7,5,4,2,9,1,3,8,6 }; int num = sizeof(intArr) / sizeof(int); mySort(intArr, num); printArray(intArr, num); } int main() { test3(); test32(); return 0; }
普通函数与函数模板的区别
-
普通函数调用时可以发现自动类型转换(隐式类型转换)
-
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
-
如果利用显示指定类型的方式,可以发生隐式类型转换
#include <iostream> using namespace std; //普通函数 int myAdd01(int a, int b) { return a + b; } //函数模板 template<class T> T myAdd02(T a, T b) { return a + b; } void test4() { int a = 10; int b = 20; char c = 'c'; //a=97 c=99 cout << myAdd01(a, c) << endl; //自动类型推导 //cout << myAdd02(a, c) << endl; 报错, 不会发生隐式转换 //显示指定类型 cout << myAdd02<int> << endl; } int main() { test4(); return 0; }
普通函数和函数模板的调用规则
规则如下:
-
如果函数模板和普通函数都可以实现,优先调用普通函数
-
可以通过空模板参数列表来强调函数模板
-
函数模板也可以发生重载
-
如果函数模板可以产生更好的匹配,优先调用函数模板
#include <iostream> using namespace std; void myPrint(int a, int b) { cout << "调用普通函数" << endl; } template<class T> void myPrint(T a, T b) { cout << "调用函数模板" << endl; } template<class T> void myPrint(T a, T b,T c) { cout << "调用重载的模板" << endl; } void test5() { int a = 10; int b = 20; //myPrint(a, b); //优先普通 //通过空模板参数列表来强调函数模板 //myPrint<>(a, b); //函数模板也可以发生重载 //myPrint(a, b, 100); //如果函数模板产生更好的匹配,优先调用函数模板 char c1 = 'a'; char c2 = 'b'; myPrint(c1, c2); } int main() { test5(); return 0; }
总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
模板的局限性
模板的通用性不是万能的,有些特定数据类型,需要用具体化方式做特殊实现
例如:
template<class T> void f(T a, T b) { a = b; }
在上述赋值操作中,如果传入的a和b时一个数组,就无法实现了
#include <iostream> using namespace std; class Person { public: //姓名 Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; string m_Age; }; //对比两个数据是否相等函数 template<class T> bool myCompare(T& a, T& b) { return (a == b) ? true : false; } //利用具体化Person的版本实现代码,具体化优先调用 template<> bool myCompare(Person& p1, Person& p2) { if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) { return true; } else { return false; } } void test6() { int a = 10; int b = 20; bool ret = myCompare(a, b); if (ret) { cout << "a == b" << endl; } else { cout << "a != b" << endl; } } void test62() { Person p1("Tom", 10); Person p2("Tom", 10); bool ret = myCompare(p1, p2); if (ret) { cout << "p1 == p2" << endl; } else { cout << "p1 != p2" << endl; } } int main() { test6(); test62(); return 0; }
总结:
-
利用具体化的模板,可以解决自定义类型的通用化
-
学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
类模板
类模板语法
-
建立一个通用类,类中的成员,数据类型可以不具体制定,用一个虚拟的类型来代表
template<typename T> 类
与函数模板一样
#include <iostream> using namespace std; // template<class NameType,class AgeType> class Person7 { public: //姓名 Person7(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "name: " << this->m_Name << " age: " << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; void test7() { Person7<string, int>p1("孙悟空", 999); p1.showPerson(); } int main() { test7(); return 0; }
类模板和函数模板的区别
主要区别:
-
类模板没有自动类型推导的使用方式
-
类模板在模板参数列表中可以有默认参数
#include <iostream> using namespace std; template<class NameType, class AgeType = int> class Person8 { public: //姓名 Person8(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "name: " << this->m_Name << " age: " << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; //类模板没有自动类型推导的使用方式 void test81() { //Person8 p("孙悟空", 999); //错误,无法自动类型推导 Person8<string, int>p1("孙悟空", 999); //正确,只能显示指定类型 p1.showPerson(); } //类模板在模板参数列表中可以有默认参数 void test82() { Person8<string>p2("猪八戒", 990); //正确,只能显示指定类型 p2.showPerson(); } int main() { test81(); test82(); return 0; }
总结:
-
类模板使用只能用显示指定类型方式
-
类模板中的模板参数可以有默认参数
类模板中成员函数创建时机
类模板中成员函数与普通类中成员函数创建时机是有区别的:
-
普通类中成员函数一开始就可以创建
-
类模板中的成员函数在调用时才创建
#include <iostream> using namespace std; class Person9 { public: void showPerson1() { cout << "Person1 show" << endl; } }; class Person92{ public: void showPerson2() { cout << "Person2 show" << endl; } }; template<class T> class MyClass { public: T obj; //类模板中成员函数 void func1() { obj.showPerson1(); } void func2() { obj.showPerson2(); } }; void test9() { MyClass<Person9>m; m.func1(); //m.func2(); //出错,说明函数调用才会去创建成员函数 } int main() { test9(); return 0; }
总结:类模板中的成员函数斌不是一开始就创建的,在调用时才去创建
类模板对象做函数参数
目标:
类模板实话出的对象,向函数传参的方式
一共三种传入方式:
-
指定传入的类型 ——直接显示对象的数据类型
-
参数模板化 ——将对象中的参数变为模板进行传递
-
整个类模板化——将这个对象类型 模板化进行传递
#include <iostream> using namespace std; template<class T1,class T2> class Person10 { public: Person10(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "name: " << this->m_Name << " age: " << this->m_Age << endl; } T1 m_Name; T2 m_Age; }; //1、指定传入类型 void printPerson1(Person10<string,int>&p) { p.showPerson(); } void test10() { Person10<string, int>p("孙悟空", 999); printPerson1(p); } //2、参数模板化 template<class T1, class T2> void printPerson2(Person10<T1, T2>& p) { p.showPerson(); cout << "T1的类型为:" << typeid(T1).name() << endl; //string的全名 cout << "T2的类型为:" << typeid(T2).name() << endl; } void test102() { Person10<string, int>p("猪八戒", 888); printPerson2(p); } //3、整个类模板化 template<class T> void printPerson3(T & p) { p.showPerson(); cout << "T的类型为:" << typeid(T).name() << endl; //Person string int } void test103() { Person10<string, int>p("唐僧", 40); printPerson3(p); } int main() { test10(); test102(); test103(); return 0; }
类模板与继承
注意以下:
-
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
-
如果不指定,编译器无法给子类分配内存
-
如果想灵活指定出父类T的类型,子类也需要变为类模板
#include <iostream> using namespace std; template<class T> class Base { T m; }; //class Son :public Base {}; //错误,必须要知道父类T中的数据类型 class Son : public Base<int> {}; void test11() { Son s1; } //如果想灵活指定出父类T的类型,子类也需要变为类模板 template<class T1,class T2> class Son2 :public Base<T2> { public: Son2() { cout << "T1的类型为:"<< typeid(T1).name() << endl; //int cout << "T2的类型为:"<< typeid(T2).name() << endl; //char } T1 obj; }; void test112() { Son2<int,char> s2; } int main() { test11(); test112(); return 0; }
类模板成员函数类外实现
#include <iostream> using namespace std; template<class T1, class T2> class Person12 { public: Person12(T1 name, T2 age); /*{ this->m_Name = name; this->m_Age = age; }*/ void showPerson(); /*{ cout << "name: " << this->m_Name << " age: " << this->m_Age << endl; }*/ T1 m_Name; T2 m_Age; }; //构造函数类外实现 template<class T1, class T2> Person12<T1, T2>::Person12(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } //成员函数类外实现 template<class T1, class T2> void Person12<T1, T2>::showPerson() { cout << "name: " << this->m_Name << " age: " << this->m_Age << endl; } void test12() { Person12<string, int>p("Tom", 20); p.showPerson(); } int main() { test12(); return 0; }
类模板拆分文件编写
掌握类模板成员函数分文件编写产生的问题以及解决方式
问题:
-
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
-
解决方式1:直接包含.cpp源文件
-
解决方式2:将声明和实现写道同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制(主流)
类模板与友元
掌握类模板配合友元函数的类内和类外实现
全局函数类内实现-直接在类内声明友元即可
全局函数类外实现-需要提前让编译器知道全局函数的存在
#include <iostream> using namespace std; //通过全局函数 打印Person信息 //2、提前让编译器知道Person类 template<class T1, class T2> class Person14; template<class T1, class T2> void printPerson2(Person14<T1, T2> p) { cout << "类外实现—姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl; } template<class T1,class T2> class Person14 { //全局函数 类内实现 friend void printPerson(Person14<T1, T2> p) { cout << "类内实现—姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl; } //全局函数 类外实现 普通函数声明变成函数模板的函数声明 //加空模板参数列表 //如果全局函数是类外实现,需要编译器提前知道整个函数的存在 //这个函数要提前 friend void printPerson2<>(Person14<T1, T2> p); public: Person14(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } private: T1 m_Name; T2 m_Age; }; //1、全局函数 类内实现 void test14() { Person14<string, int>p("Tom", 20); printPerson(p); } //2、全局函数在类外实现 void test142() { Person14<string, int>p("Jerry", 20); printPerson2(p); } int main() { test14(); return 0; }
总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别
类模板案例
描述:实现一个通用的数组类,要求如下:
-
可以对内置数据类型以及自定义数据类型的数据进行存储
-
将数组中的数据存储到堆区
-
构造函数中可以传入数组的容量
-
提供对应的拷贝构造函数以及operator=防止浅拷贝问题
-
提供尾插法和尾删法对数组中的数据进行增加和删除
-
可以通过下标的方式访问数组中的元素
-
可以获取数组中当前元素个数和数组的元素
-
可以获取数组中当前元素个数和数组的容量
STL初识
STL的诞生
-
长久以来,软件界一直希望建立一种可重复利用的东西
-
C++的面向对象和泛型编程思想,目的就是复用性的提升
-
大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作
-
为了建立数据结构和算法的一套标准,诞生了STL
STL基本概念
-
STL(Standard Template Library,标准模板库)
-
STL从广义上分为:容器(container)算法(algorithm)迭代器(iterator)
-
容器和算法之间通过迭代器进行无缝连接
-
STL几乎所有的代码都采用了模板类或者模板函数
STL六大组件
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
-
容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
-
算法:各种常用算法,如sort、find、copy、for_each等
-
迭代器:扮演了容器与算法之间的胶合剂
-
仿函数:行为类似函数,可作为算法的某种策略
-
适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
-
空间适配器:负责空间的配置与管理
STL中容器、算法、迭代器
容器:置物之所也
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组、链表、树、栈、队列、集合、映射表等
这些容器分为序列式容器和关联式容器两种:
序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
算法:问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)
算法分为:质变算法和非质变算法
质变算法:是指算法过程中会更改区间内的元素的内容,例如:拷贝、删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等
迭代器:容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数据的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 对数据的只写访问 | 只写,支持++ |
前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 读写操作,并能向前和向后操作 | 读写,支持++、-- |
随机访问迭代器 | 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 | 读写,支持++、--、[n]、-n、<、<=、>、>= |
容器算法迭代器初识
了解STL容器、算法、迭代器概念之后,我们利用代码感受STL的魅力
STL中最常用的容器为Vector,可以理解为数组
vector存放内置数据类型
容器:vector
算法:for_each
迭代器:vector<int>::iterator
#include<iostream> using namespace std; #include <vector> #include <algorithm> //标准算法头文件 void myPrint(int val) { cout << val << endl; } void test1() { //创建一个vector容器,数组 vector<int> v; //向容器中插入数据 v.push_back(10); v.push_back(20); v.push_back(30); v.push_back(40); //通过迭代器访问容器中的数据 vector<int>::iterator itBegin = v.begin(); //起始迭代器 指向容器中第一个元素 vector<int>::iterator itEnd = v.end(); //结束迭代器 指向容器中最后一个元素的下一个 //第一种遍历方式 while (itBegin != itEnd) { cout << *itBegin << endl; itBegin++; } //第二种遍历方式 for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << endl; } //第三种遍历方式 利用STL提供遍历算法 for_each(v.begin(), v.end(), myPrint); } int main() { test1(); return 0; }
vector存放自定义数据类型
vector中存放自定义数据类型,并打印输出
#include<iostream> using namespace std; #include <vector> #include <algorithm> //标准算法头文件 class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; void test2() { vector<Person>v; Person p1("aaa", 10); Person p2("bbb", 20); Person p3("ccc", 30); Person p4("ddd", 40); Person p5("eee", 50); //向容器中添加数据 v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); //遍历容器中的数据 for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { //cout << "姓名:" << (*it).m_Name << " 年龄:" << (*it).m_Age << endl; cout << "姓名:" << it->m_Name << " 年龄:" << it->m_Age << endl; } } //存放自定义数据类型 指针 void test22() { vector<Person*>v; Person p1("aaa", 10); Person p2("bbb", 20); Person p3("ccc", 30); Person p4("ddd", 40); Person p5("eee", 50); //向容器中添加数据 v.push_back(&p1); v.push_back(&p2); v.push_back(&p3); v.push_back(&p4); v.push_back(&p5); //遍历容器中的数据 for (vector<Person *>::iterator it = v.begin(); it != v.end(); it++) { //it代表指向person指针 cout << "姓名:" << (*it)->m_Name << " 年龄:" << (*it)->m_Age << endl; } } int main() { test2(); test22(); return 0; }
vector容器嵌套容器
容器中嵌套人容器,我们将所有所有数据进行遍历输出
#include<iostream> using namespace std; #include <vector> #include <algorithm> //标准算法头文件 void test3() { vector<vector<int>>v; //创建小容器 vector<int>v1; vector<int>v2; vector<int>v3; vector<int>v4; //向小容器中添加数据 for (int i = 0; i < 4; i++) { v1.push_back(i + 1); v2.push_back(i + 2); v3.push_back(i + 3); v4.push_back(i + 4); } //将小容器插入到大容器中 v.push_back(v1); v.push_back(v2); v.push_back(v3); v.push_back(v4); //通过大容器,把所有数据遍历一遍 for (vector < vector<int>>::iterator it = v.begin(); it != v.end(); it++) { //(*it)代表一个容器 vector<int> for(vector<int>::iterator vit = (*it).begin(); vit != (*it).end();vit++){ cout << *vit << " "; } cout << endl; } } int main() { test3(); return 0; }
STL常用容器
String容器
string基本概念
本质:
-
string是C++风格的字符串,而string本质是一个类
string和char*区别
-
char*是一个指针
-
string是一个类,类内部封装了char*
管理整个字符串,是一个char*型的容器
特点:
string类内部封装了很多成员方法
例如:查找find,拷贝copy,删除delete,替换replace,插入insert
string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
string构造函数
构造函数原型:
-
string();
//创建一个空符号串,例如:string str;string(const char* s);
//使用字符串s初始化 -
string(const string& str);
//使用一个string对象初始化另外一个string对象 -
string(int n,char c);
//使用n个字符c初始化
#include<iostream> using namespace std; //string的构造函数 //-string(); //创建一个空符号串,例如:string str; //string(const char* s); //使用字符串s初始化 //- string(const string & str); //使用一个string对象初始化另外一个string对象 //- string(int n, char c); //使用n个字符c初始化 void test1() { string s1; //默认构造 const char* str = "hello world"; string s2(str); cout << "s2=" << s2 << endl; string s3(s2); cout << "s3=" << s3 << endl; string s4(10, 'a'); cout << "s4=" << s4 << endl; } int main() { test1(); return 0; }
string赋值操作
给string字符串进行赋值
赋值函数原型:
-
string& operator=(const char* s);
//char*类型字符串 赋值给当前的字符串 -
string& operator=(const char &s);
//把字符串s赋给当前的字符串 -
string& operator=(char c);
//字符赋值给当前的字符串 -
string& assign=(const char* s);
//把字符串s赋给当前的字符串 -
string& assign=(const char* s,int n);
//把字符串s的前几个字符赋给当前的字符串 -
string& assign=(const sring &s);
//把字符串s赋给当前的字符串 -
string& assign=(int n,char c);
//用n个字符c赋给当前字符串
#include<iostream> using namespace std; //-string& operator=(const char* s); //char*类型字符串 赋值给当前的字符串 //-string& operator=(const char& s); //把字符串s赋给当前的字符串 //-string& operator=(char c); //字符赋值给当前的字符串 //-string& assign = (const char* s); //把字符串s赋给当前的字符串 //-string& assign = (const char* s, int n); //把字符串s的前几个字符赋给当前的字符串 //-string& assign = (const sring & s); //把字符串s赋给当前的字符串 //-string& assign = (int n, char c); //用n个字符c赋给当前字符串 void test2() { string str1; str1 = "hello world"; cout << "str1=" << str1 << endl; string str2; str2 = str1; cout << "str2=" << str2 << endl; string str3; str3 = 'a'; cout << "str3=" << str3 << endl; string str4; str4.assign("hello C++"); cout << "str4=" << str4 << endl; string str5; str5.assign("hello C++", 5); cout << "str5=" << str5 << endl; string str6; str6 = str5; cout << "str6=" << str6 << endl; string str7; str7.assign(10, 'w'); cout << "str7=" << str7 << endl; } int main() { test2(); return 0; }
string字符串拼接
实现在字符串末尾拼接字符串
-
string& operator+=(const char* str);
//重载+=操作符 -
string& operator+=(const char c);
//重载+=操作符 -
string& operator+=(const string& str);
//重载+=操作符 -
string& append(const char *s);
//把字符串s连接到当前字符串结尾 -
string& append(const char *s,int n);
//把字符串s的前n个字符连接到当前字符串结尾 -
string& append(const string &s);
//同operator+=(const string& str) -
string& append(const string &s,int pos,int n);
//字符串s中从pos开始的n个字符连接到字符串结尾
#include<iostream> using namespace std; //- string& operator+=(const char* str); //重载+=操作符 //- string& operator+=(const char c); //重载+=操作符 //- string& operator+=(const string & str); //重载+=操作符 //- string& append(const char* s); //把字符串s连接到当前字符串结尾 //- string& append(const char* s,int n); //把字符串s的前n个字符连接到当前字符串结尾 //- string& append(const string & s); //同operator+=(const string& str) //- string& append(const string & s, int pos, int n); //字符串s中从pos开始的n个字符连接到字符串结尾 void test3() { string str1= "我"; str1 += "爱玩游戏"; cout << "str1=" << str1 << endl; str1 += ':'; cout << "str1=" << str1 << endl; string str2 = "LOL DNF"; str1 += str2; cout << "str1=" << str1 << endl; string str3 = "I"; str3.append("love"); cout << "str3=" << str3 << endl; str3.append("game abcde", 4); cout << "str3=" << str3 << endl; //str3.append(str2); //str3.append(str2,0,3); //只截取到LOL str3.append(str2,4,3); //只截取到DNF //参数2从哪个位置开始截取,参数3截取个数 cout << "str3=" << str3 << endl; } int main() { test3(); return 0; }
string查找和替换
查找:查找指定字符串是否存在
替换:在指定的位置替换字符串
函数原型
-
int find(const string & str, int pos=0) const;
//查找str第一次出现位置,从pos开始查找 -
int find(const char* s,int pos=0) const;
//查找s第一次出现位置,从pos开始查找 -
int find(const char* s,int pos,int n) const;
//从pos位置查找s的前n个字符第一次位置 -
int find(const char c,int pos=0) const;
//查找字符c第一次出现位置 -
int rfind(const string& str,int pos=npos) const;
//查找str最后一次位置,从pos开始寻找 -
int rfind(const char* s,int pos = npos) const;
//查找s最后一次出现位置,从pos开始查找 -
int rfind(const char* s,int pos,int n) const;
//从pos位置查找s的前n个字符最后一次位置 -
int rfind(const char* s,int pos = 0) const;
//查找字符c最后一次出现位置 -
string& replace(int pos,int n,const string& str);
//替换从pos开始n个字符为字符串str -
string& replace(int pos,int n,const char* s);
//替换从pos开始的n个字符为字符串
#include<iostream> using namespace std; //查找 void test41() { string str1 = "abcdefgde"; int pos = str1.find("de"); if (pos == -1) { cout << "未找到字符串" << endl; } else { cout << "找到字符串,pos=" << pos << endl; //3从0开始算,d是第四个数,没有查找到返回-1 } //rfind和find的区别 //rfind查找方向从右往左,find从左往右,但是输出都是从左往右的从0序号 pos = str1.rfind("de"); cout << "找到字符串,pos=" << pos << endl; } //替换 void test42() { string str1 = "abcdefg"; str1.replace(1, 3, "1111"); cout << str1 << endl; } int main() { test41(); test42(); return 0; }
string字符串比较
字符串之间的比较
比较方式:
-
字符串比较是按字符的ASCII码进行对比
=返回 0 , >返回 1,<返回 -1
函数原型:
-
int compare(const string &s)const;
//与字符串s比较 -
int compare(const char *s)const;
//与字符串s比较
#include<iostream> using namespace std; void test5() { string str1 = "aello"; string str2 = "hello"; if (str1.compare(str2) == 0) { cout << "str1等于str2" << endl; } else if (str1.compare(str2) > 0) { cout << "str1大于str2" << endl; } else { cout << "str1小于str2" << endl; } } int main() { test5(); return 0; }
总结:字符串对比主要是用于比较两个字节是否相等,判断谁大谁小意义不大
string字符存取
string中单个字符存取方式有两种
-
char& operator[](int n);
//通过[]方法取字符 -
char& at(int n);
//通过at方法获取字符
#include<iostream> using namespace std; void test6() { string str = "hello"; //cout << "str=" << str << endl; //1、通过[]访问单个字符 for (int i = 0; i < str.size(); i++) { cout << str[i] << " "; } cout << endl; //2、通过at方式访问单个字符 for (int i = 0; i < str.size(); i++) { cout << str.at(i) << " "; } cout << endl; //修改单个字符 str[0] = 'X'; cout << "str = " << str << endl; str.at(1) = 'X'; cout << "str = " << str << endl; } int main() { test6(); return 0; }
总结:string字符串中单个字符存取有两种方式,利用[]或at
string插入和删除
对string字符串进行插入和删除字符操作
函数原型:
-
string& insert(int pos,const char* s);
//插入字符串 -
string& insert(int pos,const string& str);
//插入字符串 -
string& insert(int pos,int n,char c);
//在指定位置插入n个字符c -
string& erase(int pos,int n=npos);
//删除从Pos开始的n个字符
#include<iostream> using namespace std; void test7() { string str = "hello"; //插入 str.insert(1, "111"); //h111ello cout << str << endl; //删除 str.erase(1, 3); cout << str << endl; } int main() { test7(); return 0; }
string子串获取
从字符串中获取想要的子串
函数原型
-
string substr(int pos=0,int n = pos)const;
//返回由pos开始的n个字符组成的字符串
#include<iostream> using namespace std; void test8() { string str = "abcdef"; string subStr = str.substr(1, 3); cout << "subStr = " << subStr << endl; } //使用操作 void test82() { string email = "zhangsan@sina.com"; //从邮件地址中 获取 用户名信息 //string name = email.substr(0, 8); int pos = email.find("@"); //8 string name = email.substr(0, pos); cout << "name = " << name << endl; } int main() { test8(); test82(); return 0; }
vector容器
vector基本概念
功能:
-
vector数据结构和数组非常相似,也称为单端数组
vector与普通数组区别:
-
不同之处在于数组是静态空间,而vector可以动态扩展
动态扩展
-
并不是在原空间之后续接新空间,而是找更大的内存空间,然后将元数据拷贝到新空间,释放原空间
-
vector容器的迭代器是支持随机访问的迭代器
vector构造函数
-
创建vector容器
函数原型
-
vector<T> v;
//采用模板实现类实现,默认构造函数 -
vector(v.begin(),v.end());
//将v[begin(),end()]区间中的圆度拷贝给本身 -
vector(n,elem);
//构造函数将n个elem拷贝给本身 -
vector(const vector &vec);
//拷贝构造函数
#include <iostream> using namespace std; #include<vector> void printVector(vector<int>&v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } void test1() { vector<int>v1; //默认构造 无参构造 for (int i = 0; i < 10; i++) { v1.push_back(i); } printVector(v1); //通过区间方式进行构造 vector<int>v2(v1.begin(),v1.end()); printVector(v2); //n个elem方式构造 参数1:个数 参数2:赋值 vector<int>v3(10,100); printVector(v3); //!!用的多,拷贝构造 vector<int>v4(v3); printVector(v4); } int main() { test1(); return 0; }
总结:vector的多种构造方式没有可比性,灵活运用即可
vector赋值操作
给vector容器进行赋值
函数原型
-
vector& operator=(const vector &vec);
//重载等号操作符 -
assign(beg,end);
//将[beg,end]区间中的数据拷贝赋值给本身 -
assign(n,elem);
//将n个elem拷贝赋值给本身
#include <iostream> using namespace std; #include<vector> void printVector2(vector<int>& v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } void test2() { vector<int>v1; for (int i = 0; i < 10; i++) { v1.push_back(i); } printVector2(v1); //赋值 operator= vector <int>v2; v2 = v1; printVector2(v2); //assign vector<int>v3; v3.assign(v1.begin(), v1.end()); printVector2(v3); //n个elem方式赋值 vector<int>v4; v4.assign(10, 100); printVector2(v4); } int main() { test2(); return 0; }
vector容量和大小
对vector容器的容量和大小操作
函数原型:
-
empty(); //判断容器是否为空
-
capacity(); //容器的容量
-
size(); //返回容器中元素的个数
-
resize(int num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置
//如果容器变短,则末尾超出容器长度的元素被删除
-
resize(int num,elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置
//如果容器变短,则末尾超出容器常速的元素被删除
#include <iostream> using namespace std; #include<vector> void printVector3(vector<int>& v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } void test3() { vector<int>v1; for (int i = 0; i < 10; i++) { v1.push_back(i); } printVector3(v1); if (v1.empty()) { //为真 代表容器为空 cout << "v1为空" << endl; } else { cout << "v1不为空" << endl; cout << "v1的容量为:" << v1.capacity() << endl; cout << "v1的大小为:" << v1.size() << endl; } //重新指定大小 v1.resize(15,100); //利用重载版本,可以指定默认填充值,参数2 printVector3(v1); //如果重新指定的比原长了,默认用0填充新的位置 v1.resize(5); printVector3(v1); //如果重新指定的比原短了,超出部分元素会删除 } int main() { test3(); return 0; }
vector插入和删除
对vector容器进行插入,删除操作
函数原型:
-
push_back(ele);
//尾部插入元素ele -
pop_back();
//删除最后一个元素 -
insert(const_iterator pos,ele);
//迭代器指向位置pos插入元素ele -
insert(const_iterator pos,int count,ele);
//迭代器指向位置pos插入count个元素ele -
erase(const_iterator pos);
//删除迭代器指向的元素 -
erase(const_iterator start,const_iterator end);
//删除迭代器从start到end之间的元素 -
clear();
//删除容器中所有元素
#include <iostream> using namespace std; #include<vector> //- push_back(ele); //尾部插入元素ele //- pop_back(); //删除最后一个元素 //- insert(const_iterator pos, ele); //迭代器指向位置pos插入元素ele //- insert(const_iterator pos, int count, ele); //迭代器指向位置pos插入count个元素ele //- erase(const_iterator pos); //删除迭代器指向的元素 //- erase(const_iterator start, const_iterator end); //删除迭代器从start到end之间的元素 //- clear(); //删除容器中所有元素 void printVector4(vector<int>& v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } void test4() { vector<int>v1; //尾插 v1.push_back(10); v1.push_back(20); v1.push_back(30); v1.push_back(40); v1.push_back(50); //遍历 printVector4(v1); //尾删 v1.pop_back(); printVector4(v1); //插入 v1.insert(v1.begin(), 100); printVector4(v1); v1.insert(v1.begin(),2, 100); printVector4(v1); //删除 参数也是迭代器 v1.erase(v1.begin()); printVector4(v1); //清空 //v1.erase(v1.begin(), v1.end()); v1.clear(); printVector4(v1); } int main() { test4(); return 0; }
vector数据存取
对vector中的数据的存取操作
函数原型:
-
at(int idx); //返回索引idx所指的数据
-
operator[]; //返回索引idx所指的数据
-
front(); //返回容器中第一个数据元素
-
back(); //返回容器中最后一个数据元素
#include <iostream> using namespace std; #include<vector> void test5() { vector<int>v1; for (int i = 0; i < 10; i++) { v1.push_back(i); } //利用[]方式访问数组中元素 for (int i = 0; i < v1.size(); i++) { cout << v1[i] << " "; } cout << endl; //利用at方式访问元素 for (int i = 0; i < v1.size(); i++) { cout << v1.at(i) << " "; } cout << endl; //获取第一个元素 cout << "第一个元素为:" << v1.front() << endl; //获取最后一个元素 cout << "最后一个元素为:" << v1.back() << endl; } int main() { test5(); return 0; }
总结:
-
除了用迭代器获取vector容器中元素,[]和at也可以
-
front返回容器第一个元素
-
back返回容器最后一个元素
vector互换容器
实现两个容器内元素进行互换
函数原型
-
swap(vec); //将vec与本身的元素互换
#include <iostream> using namespace std; #include<vector> void printVector6(vector<int>& v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } void test6() { vector<int>v1; for (int i = 0; i < 10; i++) { v1.push_back(i); } cout << "交换前:" << endl; printVector6(v1); vector<int>v2; for (int i = 10; i > 0; i--) { v2.push_back(i); } printVector6(v2); cout << "交换后:" << endl; v1.swap(v2); printVector6(v1); printVector6(v2); } //实际运用 //巧用swap可以收缩内存空间 void test62() { vector<int>v; for (int i = 0; i < 100000; i++) { v.push_back(i); } cout << "v的容量为:" << v.capacity() << endl; cout << "v的大小为:" << v.size() << endl; v.resize(3); //重新指定大小 cout << "v的容量为:" << v.capacity() << endl; cout << "v的大小为:" << v.size() << endl; //巧用swap收缩内存 vector<int>(v).swap(v); cout << "v的容量为:" << v.capacity() << endl; cout << "v的大小为:" << v.size() << endl; } int main() { test6(); test62(); return 0; }
总结:swap可以使两个容器互换,可以达到实用的收缩内存效果;
vector预留空间
-
减少vector在动态扩展容量时的扩展次数
函数原型:
-
reserve(int len);
//容器预留len个元素长度,预留位置不初始化,元素不可访问
#include <iostream> using namespace std; #include<vector> //预留空间 void test7() { vector<int>v; //利用reserve预留空间 v.reserve(1000000); int num = 0; //冲击开辟次数 int* p = NULL; for (int i = 0; i < 100000; i++) { v.push_back(i); if (p != &v[0]) { p = &v[0]; num++; } } cout << "num=" << num << endl; //1 } int main() { test7(); return 0; }
deque容器
deque容器基本概念
功能:双端数组,可以对头段进行插入删除操作
deque与vector区别:(deque相当于双端数组,vector相当于动态数组,类似中间插入的队列和栈)
-
vector对于头部的插入删除效率低,数据量越大,效率越低
-
deque相对而言,对头部的插入删除速度回比vector快
-
vector访问元素时的速度会比deque快,这和两者内部实现有关
-
deque容器的迭代器也是支持随机访问的
deque构造函数
功能描述:
-
deque容器构造
函数原型:
-
deque<T>deqT; //默认构造形式
-
deque(beg,end); //构造函数[beg,end]区间中的元素拷贝给本身
-
deque(n,elem); //构造函数将n个elem拷贝给本身
-
deque(const deque &deq); //拷贝构造函数
#include<iostream> using namespace std; #include<deque> void printDeque(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //*it = 100; 容器中数据不可修改 cout << *it << endl; } cout << endl; } void test1() { deque<int>d1; for (int i = 0; i < 10; i++) { d1.push_back(i); } printDeque(d1); //构造函数[beg,end]区间中的元素拷贝给本身 deque<int>d2(d1.begin(), d1.end()); printDeque(d2); //构造函数将n个elem拷贝给本身 deque<int>d3(10, 100); printDeque(d3); //拷贝构造函数 deque<int>d4(d3); printDeque(d4); } int main() { test1(); }
deque赋值操作
函数原型
-
deque& operator=(const deque &deq); //重载等号操作符
-
assign(beg,end); //将[beg,end]区间中的数据拷贝赋值给本身
-
assign(n,elem); //将n个elem拷贝赋值给本身
#include<iostream> using namespace std; #include<deque> void printDeque2(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //*it = 100; 容器中数据不可修改 cout << *it <<" "; } cout << endl; } void test2() { deque<int>d1; for (int i = 0; i < 10; i++) { d1.push_back(i); } printDeque2(d1); //operator=赋值 deque<int>d2; d2 = d1; printDeque2(d2); //assign赋值 deque<int>d3; d3.assign(d1.begin(), d1.end()); printDeque2(d3); deque<int>d4; d4.assign(10, 100); printDeque2(d4); } int main() { test2(); return 0; }
deque大小操作
函数原型:
-
deque.empty( ); //判断容器是否为空
-
deque.size(); //返回容器中元素的个数
-
deque.resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置
//如果容器变短,则末尾超出容器长度的元素被删除
-
deque.resize(num,elem); //重新指定容器的长度为num,若容器变长,则以elem填充新位置
//如果容器变短,则末尾超出容器长度的元素被删除
#include<iostream> using namespace std; #include<deque> void printDeque3(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //*it = 100; 容器中数据不可修改 cout << *it << " "; } cout << endl; } void test3() { deque<int>d1; for (int i = 0; i < 10; i++) { d1.push_back(i); } printDeque3(d1); if (d1.empty()) { cout << "d1为空" << endl; } else { cout << "d1不为空" << endl; cout << "d1的大小为:"<< d1.size() << endl; //deque容器没有容量概念,无限大 } //重新指定大小 //d1.resize(15); d1.resize(15,1); printDeque3(d1); d1.resize(5); printDeque3(d1); } int main() { test3(); return 0; }
deque插入和删除
函数原型:
两端插入操作
-
push_back(elem);
//尾部插入元素elem -
push_front(elem);
//头部插入元素elem -
pop_back();
//删除最后一个元素 -
pop_front();
//删除第一个元素
指定位置操作:
-
insert(pos,elem);
//迭代器指向位置pos插入元素elem元素的拷贝,返回型数据的位置 -
insert(pos,n,elem);
//迭代器指向位置pos插入n个元素elem,无返回值 -
insert(pos,beg,end);
//迭代器指向位置pos插入[beg,end]区间的数据,无返回值 -
erase(pos);
//删除迭代器指向的元素,返回下一个数据的位置 -
erase(beg,end);
//删除迭代器从beg到end之间的元素,返回下一个数据的位置 -
clear();
//删除容器中所有元素
#include<iostream> using namespace std; #include<deque> void printDeque4(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //*it = 100; 容器中数据不可修改 cout << *it << " "; } cout << endl; } void test4() { deque<int>d1; for (int i = 0; i < 10; i++) { d1.push_back(i); } printDeque4(d1); //尾插 d1.push_back(10); d1.push_back(20); printDeque4(d1); //头插 d1.push_front(100); d1.push_front(200); printDeque4(d1); //尾删 d1.pop_back(); printDeque4(d1); d1.pop_front(); printDeque4(d1); } void test42() { deque<int>d1; d1.push_back(10); d1.push_front(20); d1.push_back(100); d1.push_front(200); printDeque4(d1); //插入 d1.insert(d1.begin(), 1000); printDeque4(d1); //插入n个元素elem,无返回值 d1.insert(d1.begin(),2, 10000); printDeque4(d1); //按区间进行插入 deque<int>d2; d2.push_back(1); d2.push_back(2); d2.push_back(3); //插入[beg,end]区间的数据,无返回值 d1.insert(d1.begin(), d2.begin(), d2.end()); printDeque4(d1); } void test43() { deque<int>d1; d1.push_back(10); d1.push_front(20); d1.push_back(100); d1.push_front(200); //200 20 10 100 //删除 //迭代器往后偏移 deque<int>::iterator it = d1.begin(); it++; d1.erase(it); //200 10 100 printDeque4(d1); //按照区间方式删除 //d1.erase(d1.begin(), d1.end()); //清空 d1.clear(); printDeque4(d1); } int main() { //test4(); //test42(); test43(); return 0; }
deque数据存取
函数原型:
-
at(int idx); //返回索引idx所指的数据
-
operator[]; //返回索引idx所指的数据
-
front(); //返回容器中第一个数据元素
-
back(); //返回容器中最后一个数据元素
#include<iostream> using namespace std; #include<deque> void printDeque5(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //*it = 100; 容器中数据不可修改 cout << *it << " "; } cout << endl; } void test5() { deque<int>d1; d1.push_back(10); d1.push_back(20); d1.push_back(30); d1.push_front(100); d1.push_front(200); d1.push_front(300); for (int i = 0; i < d1.size(); i++) { cout << d1[i] << endl; } cout << endl; //通过at方式访问元素 for (int i = 0; i < d1.size(); i++) { cout << d1.at(i) << " "; } cout << endl; cout << "第一个元素:" << d1.front() << endl; cout << "最后一个元素:" << d1.back() << endl; } int main() { test5(); return 0; }
deque排序
函数原型:
-
sort(iterator beg,iterator end)
//对beg和end区间内元素进行排序
#include<iostream> using namespace std; #include<deque> #include<algorithm> void printDeque6(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //*it = 100; 容器中数据不可修改 cout << *it << " "; } cout << endl; } void test6() { deque<int>d1; d1.push_back(10); d1.push_back(20); d1.push_back(30); d1.push_front(100); d1.push_front(200); d1.push_front(300); printDeque6(d1); //排序默认从小到大,升序 //对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其进行排序 //例如vector sort(d1.begin(), d1.end()); cout << "排序后" << endl; printDeque6(d1); } int main() { test6(); return 0; }
案例-评委打分
有5名选手,选手ABCDE,10个评委分别对每一个选手打分,去除最高分
实现步骤
-
创建5名选手,放到vector中
-
遍历vector容器,取出来每一个选手,执行for循环,可以把10个评分打分存到deque容器中
-
sort算法对deque容器中分数排序,去除最高分和最低分
-
deque容器遍历一遍,累加总分
-
获取平均分
#include<iostream> using namespace std; #include<vector> #include<deque> #include<algorithm> #include<ctime> //有5名选手,选手ABCDE,10个评委分别对每一个选手打分,去除最高分 //1. 创建5名选手,放到vector中 //2. 遍历vector容器,取出来每一个选手,执行for循环,可以把10个评分打分存到deque容器中 //3. sort算法对deque容器中分数排序,去除最高分和最低分 //4. deque容器遍历一遍,累加总分 //5. 获取平均分 class Person { public: Person(string name, int score) { this->m_Name = name; this->m_Score = score; } string m_Name; int m_Score; }; void createPerson(vector<Person>& v) { string nameSeed = "ABCDE"; for (int i= 0; i < 5; i++) { string name = "选手"; name += nameSeed[i]; int score = 0; Person p(name, score); //将创建的person对象 放入到容器中 v.push_back(p); } } //打分 void setScore(vector<Person>&v) { for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { //将评委的分数,放入到deque容器中 deque<int>d; for (int i = 0; i < 10; i++) { int score = rand() % 41 + 60; //60~100 d.push_back(score); } //每个评委的打分 /*cout << "选手:" << it->m_Name << "\t打分:"; for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++) { cout << *dit << " "; } cout << endl;*/ //排序 sort(d.begin(), d.end()); //去掉最高分和最低分 d.pop_back(); d.pop_front(); //取平均分 int sum = 0; for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++) { sum += *dit; //累加每个评委的分数 } int avg = sum / d.size(); //将平均分赋值给选手 it->m_Score = avg; } } //显示分数 void showSorce(vector<Person>&v) { for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { cout << "姓名:" << it->m_Name << "\t平均分:" << it->m_Score << endl; } } int main() { //随机数种子 srand((unsigned int)time(NULL)); //1、创建5名选手 vector<Person>v; createPerson(v); //测试 /*for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { cout << "姓名:" << (*it).m_Name << "\t分数:"<< (*it).m_Score << endl; }*/ //2、给5名选手打分 setScore(v); //3、显示最后得分 showSorce(v); return 0; }
stack容器
stack基本概念
stack是一种先进后出(First In Last Out,FILO)的数据结构栈,它只有一个出口
栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为
(只有栈顶元素可以被外界访问到,当要看第二个元素时,需要把第一个元素拿出,这时候数据发生变化,不符合遍历条件)
栈可以判断容器为空嘛?
可以,empty来判断
栈可以返回元素个数嘛?
可以,size
stack常用接口
描述:栈容器常用的对外接口
构造函数:
-
stack<T> stk; //stack采用模板类实现,stack对象的默认构造形式
-
stack(const stack &stk); //拷贝构造函数
赋值操作:
-
stack& operator=(const stack &stk); //重载等号操作符
数据存取:
-
push(elem); //向栈顶添加元素
-
pop(); //从栈顶移除一个元素
-
top(); //返回栈顶元素
大小操作:
-
empty(); //判断堆栈是否为空
-
size(); //返回栈的大小
#include<iostream> using namespace std; #include<stack> void test() { //特点:先进后出 stack<int>s; //入栈 s.push(10); s.push(20); s.push(30); s.push(40); cout << "栈的大小" << s.size() << endl; //只要栈不为空,查看栈顶,并且执行出栈操作 while (!s.empty()) { //查看栈顶元素 cout << "栈顶元素" << s.top() << endl; //出栈 s.pop(); } cout << "栈的大小"<< s.size() << endl; } int main() { test(); return 0; }
queue容器
queue容器基本概念
概念:Queue是一种先进先出的数据结构(队列),它有两个出口
队列容器允许一端新增元素,另一端移除元素
队列只有队头和队尾可以被外界使用,因此队列不允许有遍历行为
queue常用接口
描述:队列容器常用的对外接口
构造函数:
-
queue<T> que; //queue采用模板类实现,queue对象的默认构造形式
-
queue(const queue &que); //拷贝构造函数
赋值操作:
-
queue& operator=(const queue &que); //重载等号操作符
数据存取:
-
push(elem); //向栈顶添加元素
-
pop(); //从栈顶移除一个元素
-
top(); //返回栈顶元素
大小操作:
-
empty(); //判断堆栈是否为空
-
size(); //返回栈的大小
#include<iostream> using namespace std; #include<queue> class Person { public: Person(string name, int age) { this->m_Name=name; this->m_Age=age; } string m_Name; int m_Age; }; void test2() { //特点:先进先出 queue<Person>q; //准备数据 Person p1("唐僧", 30); Person p2("孙悟空", 1000); Person p3("猪八戒", 900); Person p4("沙僧", 800); //入队 q.push(p1); q.push(p2); q.push(p3); q.push(p4); cout << "队列的大小" << q.size() << endl; //判断只要队列不为空,查看对头,查看队尾 while (!q.empty()) { //查看对头 cout << "队头元素——姓名:" << q.front().m_Name << "年龄:" << q.front ().m_Age << endl; cout << "队尾元素——姓名:" << q.back().m_Name << "年龄:" << q.back().m_Age << endl; //出队 q.pop(); } cout << "队列的大小" << q.size() << endl; } int main() { test2(); return 0; }
list容器
list基本概念
功能:将数据进行链式存储
链表是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
链表的组成:链表由一系列节点组成
结点的组成:一个是存储数据元素的数据域,另一个是储存下一个结点地址的指针域
STL中的链表是一个双向循环链表
由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器
优点:
-
采用动态存储分配,不会造成内存浪费和溢出
-
可以对任意位置进行快速插入或删除元素,修改指针即可,不需要移动大量元素
缺点:
-
链表灵活,但是空间(指针域)和时间(遍历)额外消费较大
-
容器遍历速度,没有数组快
-
占用空间比数组大
总结:STL中List和vector是两个最常被使用的容器,各有优缺点
list构造函数
函数原型:
-
list<T> lst; //默认构造形式
-
list(beg,end); //构造函数[beg,end]区间中的元素拷贝给本身
-
list(n,elem); //构造函数将n个elem拷贝给本身
-
list(const list &lst); //拷贝构造函数
#include<iostream> using namespace std; #include<list> void printList(const list<int>& L) { for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) { cout << *it << " "; } cout << endl; } void test3() { list<int>L1; L1.push_back(10); L1.push_back(20); L1.push_back(30); L1.push_back(40); printList(L1); //区间方式构造 list<int>L2(L1.begin(), L1.end()); printList(L2); //拷贝构造 list<int>L3(L2); printList(L3); //n个elem list<int>L4(10, 10000); printList(L4); } int main() { test3(); return 0; }
list赋值和交换
函数原型:
-
list& operator=(const list &lst); //重载等号操作符
-
assign(beg,end); //将[beg,end]区间中的数据拷贝赋值给本身
-
assign(n,elem); //将n个elem拷贝赋值给本身
-
swap(lst); //将list与本身的元素互换
#include<iostream> using namespace std; #include<list> void printList4(const list<int>& L) { for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) { cout << *it << " "; } cout << endl; } void test4() { list<int>L1; L1.push_back(10); L1.push_back(20); L1.push_back(30); L1.push_back(40); printList4(L1); list<int>L2; L2 = L1; printList4(L2); list<int>L3; L3.assign(L2.begin(), L2.end()); printList4(L3); list<int>L4; L4.assign(10, 100); printList4(L4); } void test42() { list<int>L1; L1.push_back(10); L1.push_back(20); L1.push_back(30); L1.push_back(40); list<int>L2; L2.assign(10, 100); cout << "交换前:" << endl; printList4(L1); printList4(L2); L1.swap(L2); cout << "交换后:" << endl; printList4(L1); printList4(L2); } int main() { test4(); test42(); return 0; }
list大小操作
-
empty( ); //判断容器是否为空
-
size(); //返回容器中元素的个数
-
resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置
//如果容器变短,则末尾超出容器长度的元素被删除
-
resize(num,elem); //重新指定容器的长度为num,若容器变长,则以elem填充新位置
//如果容器变短,则末尾超出容器长度的元素被删除
#include<iostream> using namespace std; #include<list> void printList5(const list<int>& L) { for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) { cout << *it << " "; } cout << endl; } void test5() { list<int>L1; L1.push_back(10); L1.push_back(20); L1.push_back(30); L1.push_back(40); printList5(L1); if (L1.empty()) { cout << "容器为空" << endl; } else { cout << "容器不为空" << endl; cout << "L1元素个数:" << L1.size() << endl; } //重新指定大小 L1.resize(10,10000); printList5(L1); L1.resize(2); printList5(L1); } int main() { test5(); return 0; }
list插入和删除
-
push_back(elem);
//尾部插入元素elem -
push_front(elem);
//头部插入元素elem -
pop_back();
//删除最后一个元素 -
pop_front();
//删除第一个元素 -
insert(pos,elem);
//迭代器指向位置pos插入元素elem元素的拷贝,返回型数据的位置 -
insert(pos,n,elem);
//迭代器指向位置pos插入n个元素elem,无返回值 -
insert(pos,beg,end);
//迭代器指向位置pos插入[beg,end]区间的数据,无返回值 -
erase(pos);
//删除迭代器指向的元素,返回下一个数据的位置 -
erase(beg,end);
//删除迭代器从beg到end之间的元素,返回下一个数据的位置 -
remove(elem);
//删除容器中所有与elem匹配的元素 -
clear();
//删除容器中所有元素
#include<iostream> using namespace std; #include<list> void printList6(const list<int>& L) { for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) { cout << *it << " "; } cout << endl; } void test6() { list<int>L1; //尾插 L1.push_back(10); L1.push_back(20); L1.push_back(30); L1.push_back(40); //头插 L1.push_front(100); L1.push_front(200); L1.push_front(300); L1.push_front(400); printList6(L1); //尾删 L1.pop_back(); printList6(L1); //头删 L1.pop_front(); printList6(L1); //insert插入 list<int>::iterator it = L1.begin(); L1.insert(++it, 1000); //L1.insert(L1.begin(), 1000); printList6(L1); //删除 it = L1.begin(); L1.erase(++it); printList6(L1); //删除特定元素 L1.push_back(10000); L1.push_back(10000); L1.push_back(10000); printList6(L1); L1.remove(10000); printList6(L1); L1.clear(); printList6(L1); } int main() { test6(); return 0; }
list数据存取
函数原型
-
front(); //返回容器中第一个数据元素
-
back(); //返回容器中最后一个数据元素
list存储不是连续的空间,因此不能使用at和[]存取,迭代器也不支持随机访问
#include<iostream> using namespace std; #include<list> void printList7(const list<int>& L) { for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) { cout << *it << " "; } cout << endl; } void test7() { list<int>L1; //尾插 L1.push_back(10); L1.push_back(20); L1.push_back(30); L1.push_back(40); //L[0]不可以用[]访问list容器中的元素 //L1.at不可以用at访问list容器中的元素 //原因,list本身是个链表,不是连续空间储存数据,迭代器也不支持随机访问 cout << "第一个元素:" << L1.front() << endl; cout << "最后一个元素:" << L1.back() << endl; //验证迭代器不支持随机访问 list<int>::iterator it = L1.begin(); //it = it + 1; 报错,但是it++,it--支持,+具体数字是跳跃式访问 it++; it--; } int main() { test7(); return 0; }
list反转和排序
函数原型:
-
reverse()
//链表反转 -
sort()
//链表排序
#include<iostream> using namespace std; #include<list> void printList8(const list<int>& L) { for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) { cout << *it << " "; } cout << endl; } bool myCompare(int v1,int v2) { //降序 第一个数> 第二个数 return v1 > v2; } void test8() { list<int>L1; //尾插 L1.push_back(20); L1.push_back(30); L1.push_back(10); L1.push_back(40); printList8(L1); L1.reverse(); cout << "反转后:" << endl; printList8(L1); //所有不支持随机访问迭代器的容器,不可以用标准算法 //不支持随机访问迭代器的容器,内部会提供对应一些算法 //sort(L1.begin(), L1.end()); L1.sort(); //默认从小到大,升序 cout << "排序后:" << endl; printList8(L1); L1.sort(myCompare); //降序 对于sort提供两个版本重载,可以提供一个函数或者仿函数来改变默认排序规则 printList8(L1); } int main() { test8(); return 0; }
排序案例
描述:将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高
排序规则:按照年龄进行排序,如果年龄相同按照升高进行降序
#include<iostream> using namespace std; #include<list> //将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高 //排序规则:按照年龄进行排序,如果年龄相同按照身高进行降序 class Person9 { public: Person9(string name, int age, int height) { this->m_Name = name; this->m_Age = age; this->m_Height = height; } string m_Name; int m_Age; int m_Height; }; //指定排序规则 bool myComparePerson(Person9& q1, Person9& q2) { //按照身高 降序 if(q1.m_Age == q2.m_Age){ return q1.m_Height > q2.m_Height; } else { return q1.m_Age < q2.m_Age; } } void test9() { list<Person9>L; Person9 q1("张三", 20, 185); Person9 q2("李四", 30, 175); Person9 q3("王五", 30, 165); Person9 q4("谢六", 40, 155); L.push_back(q1); L.push_back(q2); L.push_back(q3); L.push_back(q4); for (list<Person9>::const_iterator it = L.begin(); it != L.end(); it++) { cout << "姓名: " << it->m_Name << "\t年龄:" << (*it).m_Age << "\t身高:" << (*it).m_Height << "cm" << endl; //用*和指针都行,it就是指针 } //排序后 cout << "-----------------------" << endl; cout << "排序后:" << endl; L.sort(myComparePerson); for (list<Person9>::const_iterator it = L.begin(); it != L.end(); it++) { cout << "姓名: " << it->m_Name << "\t年龄:" << (*it).m_Age << "\t身高:" << (*it).m_Height << "cm" << endl; } } int main() { test9(); return 0; }
set/multist容器
set基本概念
-
也叫集合容器,关联式容器,所有元素都会在插入时自动被排序
本质
-
set/multist属于关联式容器,底层结构是用二叉树实现
set和multist区别
-
set不允许容器中有重复的元素
-
multist允许容器中有重复的元素
set构造和赋值
构造:
-
set<T> st;
//默认构造函数 -
set(const set &st);
//拷贝构造函数
插入数据 只有insert方式
赋值:
-
set& operator=(const set &st);
//重载等号操作
#include<iostream> using namespace std; #include <set> void printSet(const set<int>& s) { for (set<int>::const_iterator it = s.begin(); it != s.end(); it++) { //*it = 100; 容器中数据不可修改 cout << *it << " "; } cout << endl; } void test() { set<int>s1; //插入数据 只有insert方式 //s1.push_back(10); s1.insert(10); s1.insert(20); s1.insert(30); s1.insert(40); s1.insert(30); //遍历容器 //特点,所有元素插入时候自动被排序 //set容器不允许插入重复值 printSet(s1); //拷贝构造 set<int>s2(s1); printSet(s1); //赋值 set<int>s3; s3 = s2; printSet(s1); } int main() { test(); return 0; }
set大小和交换
函数原型
-
empty( ); //判断容器是否为空
-
size(); //返回容器中元素的个数
-
swap(); //交换两个集合容器
#include<iostream> using namespace std; #include <set> void printSet2(const set<int>& s) { for (set<int>::const_iterator it = s.begin(); it != s.end(); it++) { //*it = 100; 容器中数据不可修改 cout << *it << " "; } cout << endl; } void test2() { set<int>s1; s1.insert(10); s1.insert(20); s1.insert(30); s1.insert(40); if (s1.empty()) { cout << "容器为空" << endl; } else { cout << "set元素大小:" << s1.size() << endl; } cout << "交换前:" << endl; printSet2(s1); set<int>s2; s2.insert(100); s2.insert(200); s2.insert(300); s2.insert(400); printSet2(s2); s2.swap(s1); cout << "交换后:" << endl; printSet2(s1); printSet2(s2); } int main() { test2(); return 0; }
set插入和删除
函数原型
-
insert(elem);
//迭代器插入元素 -
erase(pos);
//删除迭代器指向的元素,返回下一个数据的位置 -
erase(beg,end);
//删除迭代器从beg到end之间的元素,返回下一个数据的位置 -
erase(elem);
//删除迭代器所有elem元素 -
clear();
//删除容器中所有元素
#include<iostream> using namespace std; #include <set> void printSet3(const set<int>& s) { for (set<int>::const_iterator it = s.begin(); it != s.end(); it++) { cout << *it << " "; } cout << endl; } void test3() { set<int>s1; s1.insert(50); s1.insert(10); s1.insert(20); s1.insert(30); s1.insert(40); printSet3(s1); //删除 s1.erase(s1.begin()); printSet3(s1); //重载删除 s1.erase(20); printSet3(s1); //清空 s1.erase(s1.begin(), s1.end()); printSet3(s1); s1.clear(); printSet3(s1); } int main() { test3(); return 0; }
set查找和统计
函数原型
-
find(key); //查看key是否存在,返回该键的元素的迭代器,若不存在,返回set.end();
-
count(key); //统计key的元素个数
#include<iostream> using namespace std; #include <set> void printSet4(const set<int>& s) { for (set<int>::const_iterator it = s.begin(); it != s.end(); it++) { cout << *it << " "; } cout << endl; } void test4() { set<int>s1; s1.insert(10); s1.insert(20); s1.insert(30); s1.insert(40); printSet4(s1); //返回的是迭代器,所以要迭代器接收 set<int>::iterator pos= s1.find(10); if (pos != s1.end()) { cout << "找到元素" << *pos << endl; } else { cout << "未找到元素" << endl; } } //统计 void test42() { set<int>s1; s1.insert(10); s1.insert(20); s1.insert(30); s1.insert(40); int num = s1.count(10); //对于set而言,结果要么0要么1 cout << "num=" << num << endl; } int main() { test4(); test42(); return 0; }
set和multist区别
-
set不允许容器中有重复的元素,multist允许
-
set插入数据的同时会返回插入结果,表示插入是否成功
-
multist不会监测数据,因此可以插入重复数据
#include<iostream> using namespace std; #include <set> void test5() { set<int>s1; pair<set<int>::iterator,bool> ret =s1.insert(10); if (ret.second) { cout << "第一次插入成功" << endl; } else { cout << "第一次插入失败" << endl; } ret = s1.insert(10); if (ret.second) { cout << "第二次插入成功" << endl; } else { cout << "第二次插入失败" << endl; } multiset<int>ms; ms.insert(10); ms.insert(10); for (multiset<int>::iterator it = ms.begin(); it != ms.end(); it++) { cout << *it << " "; } cout << endl; } int main() { test5(); return 0; }
pair对组创建
描述:
成对出现的数据,利用对组可以返回两个数据
两种创建方式:
-
pair<type,type> p (value1,value2);
-
pair<type,type> p =make_pair(value1,value2);
#include<iostream> using namespace std; void test6() { //第一种方式 pair<string, int>p("Tom", 20); cout << "姓名:" << p.first << " 年龄:" << p.second << endl; //第二种方式 pair<string, int>p2 = make_pair("Jerry", 30); cout << "姓名:" << p2.first << " 年龄:" << p2.second << endl; } int main() { test6(); return 0; }
set容器排序
-
set容器默认排序规则从小到大,掌握如何改变排序规则
主要技术点:
-
利用仿函数,可以改变排序规则
示例一:set存放内置数据类型,利用仿函数指定set容器的排序规则
#include<iostream> using namespace std; #include <set> class myCompare7 { public: bool operator()(int v1, int v2)const{ return v1 > v2; } }; void test7() { set<int>s1; s1.insert(10); s1.insert(40); s1.insert(20); s1.insert(30); s1.insert(50); for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) { cout << *it << " "; } cout << endl; //指定排序规则升序 set<int, myCompare7>s2; s2.insert(10); s2.insert(40); s2.insert(20); s2.insert(30); s2.insert(50); for (set<int, myCompare7>::iterator it = s2.begin(); it != s2.end(); it++) { cout << *it << " "; } cout << endl; } int main() { test7(); return 0; }
示例二set存放自定义数据类型
#include<iostream> using namespace std; #include <set> class Person8 { public: Person8(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; class comparePerson { public: bool operator()(const Person8& p1, const Person8& p2)const { //按照年龄 降序 return p1.m_Age > p2.m_Age; } }; void test8() { //自定义数据类型 都会指定排序规则 set<Person8, comparePerson>s; //创建Person对象 Person8 q1("张三", 20); Person8 q2("李四", 30); Person8 q3("王五", 33); s.insert(q1); s.insert(q2); s.insert(q3); for (set<Person8>::iterator it = s.begin(); it != s.end(); it++) { cout << "姓名: " << it->m_Name << "\t年龄:" << (*it).m_Age << endl; } } int main() { test8(); return 0; }
map和multimap容器
map基本概念
简介:
-
map中所有元素都是pair
-
pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
-
所有元素都会根据元素的键值自动排序
本质:
-
map/multimap属于关联式容器,底层结构是用二叉树实现
优点:
-
可以根据key值快速找到value值
map和multimap区别
-
map不允许容器中有重复key值元素
-
multimap允许容器中有重复key值元素
map构造和赋值
#include<iostream> using namespace std; #include<map> void printMap(map<int, int>& m) { for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) { cout << "key=" << (*it).first << "\tvalue=" << (*it).second << endl; } cout << endl; } void test() { map<int, int>m; //map每个元素都是对组 m.insert(pair<int, int>(1, 10)); m.insert(pair<int, int>(2, 20)); m.insert(pair<int, int>(3, 30)); m.insert(pair<int, int>(4, 40)); printMap(m); //拷贝构造 map<int, int>m2(m); printMap(m2); //赋值 map<int, int>m3; m3 = m2; printMap(m3); } int main() { test(); return 0; }
map大小和交换
函数原型
-
empty( ); //判断容器是否为空
-
size(); //返回容器中元素的个数
-
swap(); //交换两个集合容器
#include<iostream> using namespace std; #include<map> void printMap2(map<int, int>& m) { for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) { cout << "key=" << (*it).first << "\tvalue=" << (*it).second << endl; } cout << endl; } void test2() { map<int, int>m; //map每个元素都是对组 m.insert(pair<int, int>(1, 10)); m.insert(pair<int, int>(2, 20)); m.insert(pair<int, int>(3, 30)); m.insert(pair<int, int>(4, 40)); printMap2(m); if (m.empty()) { cout << "为空" << endl; } else { cout << "不为空" << endl; cout << "大小为:" << m.size() << endl; } } //交换 void test22() { map<int, int>m; m.insert(pair<int, int>(1, 10)); m.insert(pair<int, int>(2, 20)); m.insert(pair<int, int>(3, 30)); m.insert(pair<int, int>(4, 40)); map<int, int>m2; m2.insert(pair<int, int>(5, 100)); m2.insert(pair<int, int>(6, 200)); m2.insert(pair<int, int>(7, 300)); m2.insert(pair<int, int>(8, 400)); cout << "交换前" << endl; printMap2(m); printMap2(m2); m2.swap(m); cout << "交换后" << endl; printMap2(m); printMap2(m2); } int main() { test2(); test22(); return 0; }
map插入和删除
函数原型
-
insert(elem);
//迭代器插入元素 -
erase(pos);
//删除迭代器指向的元素,返回下一个数据的位置 -
erase(beg,end);
//删除迭代器从beg到end之间的元素,返回下一个数据的位置 -
erase(key);
//删除迭代器所有key元素 -
clear();
//删除容器中所有元素
#include<iostream> using namespace std; #include<map> void printMap3(map<int, int>& m) { for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) { cout << "key=" << (*it).first << "\tvalue=" << (*it).second << endl; } cout << endl; } void test3() { map<int, int>m; //插入第一种 m.insert(pair<int, int>(1, 10)); //插入第二种 m.insert(make_pair(2, 20)); //插入第三种 m.insert(map<int, int>::value_type(3, 30)); //第四种 m[4] = 40; //[]不建议插入,用途,可以利用key访问到value cout << m[5] << endl; cout << m[4] << endl; printMap3(m); //删除 m.erase(m.begin()); printMap3(m); m.erase(3); //按照key来删 printMap3(m); //m.erase(m.begin(), m.end()); m.clear(); printMap3(m); } int main() { test3(); return 0; }
map查找和统计
函数原型
-
find(key); //查看key是否存在,返回该键的元素的迭代器,若不存在,返回set.end();
-
count(key); //统计key的元素个数
#include<iostream> using namespace std; #include<map> void printMap4(map<int, int>& m) { for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) { cout << "key=" << (*it).first << "\tvalue=" << (*it).second << endl; } cout << endl; } void test4() { //查找 map<int, int>m; m.insert(pair<int, int>(1, 10)); m.insert(pair<int, int>(2, 20)); m.insert(pair<int, int>(3, 30)); m.insert(pair<int, int>(3, 40)); m.find(3); map<int, int>::iterator pos = m.find(3); if (pos != m.end()) { cout << "找到元素key= " << (*pos).first << " value=" << (*pos).second << endl; } else { cout << "未找到元素" << endl; } //统计 //map不允许插入重复key,对于count统计而言,结果要么0,要么1 //multimap可以大于1 int num = m.count(3); cout << "num=" << num << endl; } int main() { test4(); return 0; }
map容器排序
默认按照key从小到大
主要技术点:
-
利用仿函数,可以改变排序规则
#include<iostream> using namespace std; #include<map> class myCompare5{ public: bool operator()(int v1, int v2)const { return v1 > v2; } }; void test5() { map<int, int>m; m.insert(pair<int, int>(3, 10)); m.insert(pair<int, int>(1, 20)); m.insert(pair<int, int>(4, 30)); m.insert(pair<int, int>(2, 40)); m.insert(pair<int, int>(5, 50)); for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) { cout << "key=" << (*it).first << "\tvalue=" << (*it).second << endl; } cout << endl; //指定排序规则升序 map<int, int,myCompare5>m2; m2.insert(pair<int, int>(3, 10)); m2.insert(pair<int, int>(1, 20)); m2.insert(pair<int, int>(4, 30)); m2.insert(pair<int, int>(2, 40)); m2.insert(pair<int, int>(5, 50)); for (map<int, int, myCompare5>::iterator itm = m2.begin(); itm != m2.end(); itm++) { cout << "key=" << (*itm).first << "\tvalue=" << (*itm).second << endl; } cout << endl; } int main() { test5(); return 0; }
案例-员工分组
描述:
#include<iostream> using namespace std; #include<map> #include<vector> //定义0-2代表 #define CEHUA 0 #define MEISHU 1 #define YANFA 2 class Person { public: /*Person(string name,int wage) { this->m_Name; this->m_Wage; }*/ string m_Name; int m_Wage; }; void createPerson(vector<Person>& v) { string nameSeed = "ABCDEFGHIJ"; for (int i = 0; i < 10; i++) { Person p; p.m_Name = "员工"; p.m_Name += nameSeed[i]; p.m_Wage = rand() % 10001 + 10000; //1w~2w int wage = 0; //将创建的person对象 放入到容器中 v.push_back(p); } } //分组 void setGroup(vector<Person>&v,multimap<int,Person>&m) { for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { //产生随机部门编号 int id = rand() % 3; //0,1,2 //将员工插入到分组 //key代表部门编号,value代表员工 m.insert(make_pair(id, *it)); } } void showGroup(multimap<int, Person>& m) { //0号部门 A B C 1号部门 D E F cout << "策划部门" << endl; multimap<int, Person>::iterator pos = m.find(CEHUA); int count = m.count(CEHUA); //统计具体人数 int index = 0; //索引值 for (; pos != m.end() && index < count; pos++, index++) { cout << "姓名:" << pos->second.m_Name << "\t工资:" << pos->second.m_Wage << endl; } cout << "---------------" << endl; cout << "美术部门" << endl; pos = m.find(MEISHU); count = m.count(MEISHU); //统计具体人数 index = 0; for (; pos != m.end() && index < count; pos++, index++) { cout << "姓名:" << pos->second.m_Name << "\t工资:" << pos->second.m_Wage << endl; } cout << "---------------" << endl; cout << "研发部门" << endl; pos = m.find(YANFA); count = m.count(YANFA); //统计具体人数 index = 0; for (; pos != m.end() && index < count; pos++, index++) { cout << "姓名:" << pos->second.m_Name << "\t工资:" << pos->second.m_Wage << endl; } } int main() { //随机数种子 srand((unsigned int)time(NULL)); //1、创建员工 vector<Person>v; createPerson(v); //测试 /*for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { cout << "姓名:" << (*it).m_Name << "\t工资:"<< (*it).m_Wage << endl; }*/ //2、员工分组 multimap<int, Person>m; setGroup(v,m); //3、分组显示员工 showGroup(m); return 0; }
函数对象
函数对象
函数对象概念
概念:
-
重载函数调用操作符的类,其对象常称为函数对象
-
函数对象使用重载的()时,行为类似函数调用,也叫仿函数
本质:
函数对象(仿函数)是一个类,不是一个函数
函数对象使用
特点:
-
函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
-
函数对象超出普通函数的概念,函数对象可以有自己的状态
-
函数对象可以作为参数传递
#include<iostream> using namespace std; class MyAdd { public: int operator()(int v1, int v2){ return v1 + v2; } }; //1、函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值 void test() { MyAdd myAdd; cout << myAdd(10,10) << endl; } //2、函数对象超出普通函数的概念,函数对象可以有自己的状态 class MyPrint { public: MyPrint() { this->count = 0; } void operator()(string test) { cout << test << endl; this->count++; } int count; //内部自己的状态 }; void test12() { MyPrint myPrint; myPrint("hello world"); myPrint("hello world"); myPrint("hello world"); cout << "myPrint的调用次数" << myPrint.count << endl; } //3、函数对象可以作为参数传递 void doPrint(MyPrint &mp,string test) { mp(test); } void test13() { MyPrint myPrint; doPrint(myPrint, "Hello c++"); } int main() { test(); test12(); test13(); return 0; }
谓词
谓词概念
predicate
概念:
-
返回bool类型的仿函数称为谓词
-
如果operator()接受一个参数,那么叫做一元谓词
-
如果operator()接受两个参数,那么叫做二元谓词
一元谓词
#include<iostream> using namespace std; #include<vector> #include<algorithm> //一元谓词 class GreaterFive { public: bool operator()(int val) { return val > 5; } }; void test2() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } //查找容器中 没有没大于5的数字 //GreaterFive()匿名函数对象,不想给类取名可以这样,或者直接放类名 vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive()); if (it == v.end()) { cout << "未找到" << endl; } else { cout << "找到了大于5的数为:" << *it << endl; } } int main() { test2(); return 0; }
二次谓词
#include<iostream> using namespace std; #include<vector> #include<algorithm> class MyCompare { public: bool operator()(int val1, int val2) { return val1 > val2; } }; void test3() { vector<int>v; v.push_back(10); v.push_back(40); v.push_back(20); v.push_back(30); v.push_back(50); sort(v.begin(), v.end()); for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << endl; } cout << endl; //使用函数对象,改变算法策略,变为排序规则为从大到小 sort(v.begin(), v.end(), MyCompare()); cout << "---------------------" << endl; for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << endl; } cout << endl; } int main() { test3(); return 0; }
内建函数对象
内建函数对象意义
概念
-
STL内建了一些函数对象
分类
-
算术仿函数
-
关系仿函数
-
逻辑仿函数
用法
-
这些仿函数所产生的对象,用法和一般函数完全相同
-
使用内建函数对象,需要引入头文件
include <functional>
算术仿函数
功能描述
-
实现四则运算
-
其中negate是一元运算,其他都是二元运算
仿函数原型
-
template<class T> T puls<T>
//加法仿函数 -
template<class T> T minus<T>
//减法仿函数 -
template<class T> T multiplies<T>
//乘法法仿函数 -
template<class T> T divides<T>
//除法仿函数 -
template<class T> T modulus<T>
//取模仿函数 -
template<class T> T negate<T>
//取反仿函数
#include<iostream> using namespace std; #include <functional> //内建函数对象头文件 //negate 一元仿函数 取反仿函数 void test4() { negate<int>n; cout << n(50) << endl; } //plus 二元仿函数 加法 void test42() { plus<int>p; cout << p(10, 20) << endl; } int main() { test4(); test42(); return 0; }
关系仿函数
描述:实现关系对比
仿函数原理:
-
template<class T> bool equal_to<T>
//等于 -
template<class T> bool not_equal<T>
//不等于 -
template<class T> bool greater<T>
//大于 最常用 -
template<class T> bool greater_equal<T>
//大于等于 -
template<class T> bool less<T>
//小于 -
template<class T> bool less_equal<T>
//小于等于
#include<iostream> using namespace std; #include <vector> #include<algorithm> #include <functional> //内建函数对象头文件 class myCompare{ public: bool operator()(int v1, int v2)const { return v1 > v2; } }; void test5() { vector<int>v; v.push_back(10); v.push_back(20); v.push_back(30); v.push_back(40); v.push_back(50); for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; //降序 //sort(v.begin(), v.end(), myCompare()); sort(v.begin(), v.end(), greater<int>()); for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } int main() { test5(); return 0; }
逻辑仿函数
函数原型
-
template<class T> bool logical_and<T>
//逻辑与 -
template<class T> bool logical_or<T>
//逻辑或 -
template<class T> bool logical_not<T>
//逻辑非
#include<iostream> using namespace std; #include <vector> #include<algorithm> #include <functional> //内建函数对象头文件 void test6() { vector<bool>v; v.push_back(true); v.push_back(false); v.push_back(true); v.push_back(false); for (vector<bool>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; //利用逻辑非 将容器v 搬运到 容器v2中 并执行取反操作 vector<bool>v2; v2.resize(v.size()); // 必须提前开辟空间 transform(v.begin(), v.end(), v2.begin(), logical_not<bool>()); for (vector<bool>::iterator it = v2.begin(); it != v2.end(); it++) { cout << *it << " "; } cout << endl; } int main() { test6(); return 0; }
STL算法
概述
-
算法主要是由头文件
<algorithm>
<functional>
<numeric>
组成 -
<algorithm>
是所有STL头文件中最大的的一个,范围涉及到比较、交换、查找、遍历操作、复制、修改等等 -
<numeric>
体积很小,只包括几个在序列上面进行简单数学运算的模板函数 -
<functional>
定义了一些模板类,用以声明函数对象
常用遍历算法
学习目标:
-
掌握常用的遍历算法
算法简介:
-
for_each //遍历容器
-
transform //搬运容器到另一个容器
for_each
描述:实现遍历容器,是最常用的算法,要熟练
函数原型:
-
for_each(iterator beg,iterator end,_func);
//遍历算法 遍历容器元素
//beg开始迭代器
//end结束迭代器
//-func函数或者函数对象
#include<iostream> using namespace std; #include<vector> #include<algorithm> void print1(int val) { cout << val << " "; } //仿函数 class print12 { public: void operator()(int val) { cout << val << endl; } }; void test1() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } //普通函数放函数名 for_each(v.begin(), v.end(), print1); cout << endl; //仿函数放进函数对象 for_each(v.begin(), v.end(), print12()); cout << endl; } int main() { test1(); return 0; }
transform
功能描述:搬运容器到另一个容器中
函数原型
-
transform(iterator beg1,iterator end1,iterator beg2,_func);
//beg1 源容器开始迭代器
//end1 源容器结束迭代器
//beg2 目标容器开始迭代器
//_func 函数或者函数对象
#include<iostream> using namespace std; #include<vector> #include<algorithm> class Transform { public: int operator()(int v) { return v+100; } }; class MyPrint { public: void operator()(int val) { cout << val << " "; } }; void test21() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } vector<int>vTarget; //目标容器 vTarget.resize(v.size()); //目标容器 需要提前开辟空间 否则程序崩溃 transform(v.begin(),v.end(),vTarget.begin(),Transform()); for_each(vTarget.begin(), vTarget.end(), MyPrint()); cout << endl; } int main() { test21(); return 0; }
总结:搬运的目标必须要提前开辟空间,否则无法正常搬运
常用查找算法
算法简介
-
find //查找元素
-
find_if //按条件查找元素
-
adjacent_find //查找相邻重复元素
-
binary_search //二分查找法
-
count //统计元素个数
-
count_if //按条件统计个数
find
功能描述
-
查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()
函数原型
-
find(iterator beg,iterator )
#include<iostream> using namespace std; #include<vector> #include<algorithm> //查找 内置数据类型 void test31() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } //查找容器中是否有5这个元素 vector<int>::iterator it=find(v.begin(), v.end(), 5); if (it == v.end()) { cout << "没有找到" << endl; } else { cout << "找到了:" << *it << endl; } } class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } //重载== 底层find知道如何对比person数据类型 bool operator==(const Person & p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return true; } else { return false; } } string m_Name; int m_Age; }; //自定义数据类型 void test32() { vector<Person>v; Person p1("aaa", 10); Person p2("bbb", 20); Person p3("ccc", 30); Person p4("ddd", 40); v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); Person pp("bbb", 20); vector<Person>::iterator it = find(v.begin(), v.end(), pp); if (it == v.end()) { cout << "没有找到" << endl; } else { cout << "找到元素 姓名:" <<it->m_Name <<"年龄:" <<it->m_Age << endl; } } int main() { test31(); test32(); return 0; }
find_if
描述:按条件查找元素
函数原型:
-
find_if(iterator beg,iterator end,_Pred);
//按值查找元素,找到返回值指定位置迭代器,找不到返回结束迭代器位置
//beg开始迭代器
//end结束迭代器
//_Pred函数或者谓词(返回bool类型的仿函数)
#include<iostream> using namespace std; #include<vector> #include<algorithm> class GreaterFive { public: bool operator()(int val) { return val > 5; } }; //查找内置数据类型 void test4() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } vector<int>::iterator it=find_if(v.begin(), v.end(), GreaterFive()); if (it == v.end()) { cout << "没有找到" << endl; } else { cout << "找到大于5的数字为:" << *it << endl; } } //查找内置数据类型 class Person4 { public: public: Person4(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; class Greater20 { public: bool operator()(Person4 &p) { return p.m_Age > 20; } }; void test42() { vector<Person4>v; Person4 p1("aaa", 10); Person4 p2("bbb", 20); Person4 p3("ccc", 30); Person4 p4("ddd", 40); Person4 p5("eee", 50); v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); //找年龄大于20的人 vector<Person4>::iterator it = find_if(v.begin(), v.end(), Greater20()); if (it == v.end()) { cout << "没有找到" << endl; } else { for (; it != v.end(); it++) { cout << "找到元素 姓名:" << it->m_Name << "年龄:" << it->m_Age << endl; } } } int main() { test4(); test42(); return 0; }
adjacent_find
查找相邻重复元素(用的少)
函数原型:
-
adjacent_find(iterator beg,iterator end,_Pred);
//查找相邻重复元素,返回相邻元素的第一个位置的迭代器
//beg开始迭代器
//end结束迭代器
#include<iostream> using namespace std; #include<vector> #include<algorithm> void test5() { vector<int>v; v.push_back(0); v.push_back(2); v.push_back(0); v.push_back(3); v.push_back(1); v.push_back(4); v.push_back(3); v.push_back(3); vector<int>::iterator pos = adjacent_find(v.begin(), v.end()); if (pos == v.end()) { cout << "没有相邻且重复元素" << endl; } else { cout << "找到相邻且重复元素,数字为:" << *pos << endl; } } int main() { test5(); return 0; }
binary_search
查找指定元素是否存在(用指数的方式查,所以速度很快)
函数原型:
-
bool binary_search(iterator beg,iterator end,value);
//查找指定的元素,查找返回true,否则false
//注意:在无序序列中不可用
//beg开始迭代器
//end结束迭代器
//value查找到元素
#include<iostream> using namespace std; #include<vector> #include<algorithm> void test6() { vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } //v.push_back(2); 如果是无需序列,结果未知 //查找容器中是否有9元素 //注意,容器必须有序的序列 bool ret = binary_search(v.begin(), v.end(),9); if (ret) { cout << "找到元素" << endl; } else { cout << "未找到元素:" << endl; } } int main() { test6(); return 0; }
总结:二分查找法效率很高,值得注意的是查找的容器中元素必须是有序序列
count
统计元素个数
函数原型:
-
count(iterator beg,iterator end,value);
#include<iostream> using namespace std; #include<vector> #include<algorithm> //统计内置数据类型 void test7() { vector<int>v; v.push_back(10); v.push_back(40); v.push_back(30); v.push_back(40); v.push_back(20); v.push_back(40); int num = count(v.begin(), v.end(), 40); cout << "40的元素个数为:" << num << endl; } //统计自定义数据类型 class Person7 { public: public: Person7(string name, int age) { this->m_Name = name; this->m_Age = age; } //重载== 底层find知道如何对比person数据类型 bool operator==(const Person7& p) { if (this->m_Age == p.m_Age) { return true; } else { return false; } } string m_Name; int m_Age; }; void test72() { vector<Person7>v; Person7 p1("aaa", 50); Person7 p2("bbb", 20); Person7 p3("ccc", 30); Person7 p4("ddd", 20); Person7 p5("eee", 30); v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); Person7 p("fff", 50); int num = count(v.begin(), v.end(), p); cout << "和p同岁数的人数有" << num << endl; } int main() { test7(); test72(); return 0; }
count_if
按照条件统计
函数原型:
-
count_if(iterator beg,iterator end,_Pred);
#include<iostream> using namespace std; #include<vector> #include<algorithm> //统计内置数据类型 class Greater208 { public: bool operator()(int val) { return val > 20; } }; void test8() { vector<int>v; v.push_back(10); v.push_back(40); v.push_back(30); v.push_back(20); v.push_back(40); v.push_back(20); int num = count_if(v.begin(), v.end(), Greater208()); cout << "大于20的元素个数为:" << num << endl; } //统计自定义数据类型 class Person8 { public: Person8(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; class AgeGreater20 { public: bool operator()(const Person8 & p) { return p.m_Age > 20; } }; void test82() { vector<Person8>v; Person8 p1("aaa", 50); Person8 p2("bbb", 20); Person8 p3("ccc", 30); Person8 p4("ddd", 20); Person8 p5("eee", 30); v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); //找年龄大于20的人数 int num = count_if(v.begin(), v.end(), AgeGreater20()); if (num == 0) { cout << "没有年龄大于20的人" << endl; } else { cout << "年龄大于20的人数有:" << num << endl; } } int main() { test8(); test82(); return 0; }
常用排序算法
算法简介:
-
sort //对容器内元素
-
random_shuffle //洗牌 指定范围内的元素随机调整次序
-
merge //容器元素合并,并存储到另一个容器中
-
reverse //反转指定范围的元素
sort
最常用的排序算法
函数原型
-
sort(iterator beg,iterator end,_Pred);
#include<iostream> using namespace std; #include<vector> #include<algorithm> void myPrint(int val) { cout << val << endl; } void test1() { vector<int>v; v.push_back(10); v.push_back(30); v.push_back(50); v.push_back(20); v.push_back(40); //利用sort排序 sort(v.begin(), v.end()); for_each(v.begin(), v.end(), myPrint); cout << endl; //改变为降序 sort(v.begin(), v.end(), greater<int>()); for_each(v.begin(), v.end(), myPrint); cout << endl; } int main() { test1(); return 0; }
random_shuffle
描述:洗牌:指定范围内元素随机调整次序
函数原型:
-
random_shuffle(iterator beg,iterator end);
#include<iostream> using namespace std; #include<vector> #include<algorithm> void myPrint2(int val) { cout << val << " "; } void test2() { srand((unsigned int)time(NULL)); vector<int>v; for (int i = 0; i < 10; i++) { v.push_back(i); } //利用洗牌 算法 打乱排序 random_shuffle(v.begin(), v.end()); for_each(v.begin(), v.end(), myPrint2); cout << endl; } int main() { test2(); return 0; }
merge
描述:两个容器元素合并,并储存到另一个容器中
函数原型:
-
merge(iterator beg1,iterator end1,iterator beg2,iterator end2 ,iterator dest);
//注意两个容器必须是有序的
//并且顺序要一致的
#include<iostream> using namespace std; #include<vector> #include<algorithm> void myPrint3(int val) { cout << val << " "; } void test3() { vector<int> v1; vector<int> v2; for (int i = 0; i < 10; i++) { v1.push_back(i); v2.push_back(i+1); } //目标容器 vector<int>vTarget; //提前给目标容器分配空间 vTarget.resize(v1.size() + v2.size()); merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin()); for_each(vTarget.begin(), vTarget.end(), myPrint3); cout << endl; } int main() { test3(); return 0; }
reverse
将容器进行反转
函数原型
-
reverse(iterator beg,iterator end);
#include<iostream> using namespace std; #include<vector> #include<algorithm> void myPrint4(int val) { cout << val << " "; } void test4() { vector<int> v; v.push_back(10); v.push_back(30); v.push_back(50); v.push_back(20); v.push_back(40); cout << "反转前:" << endl; for_each(v.begin(), v.end(), myPrint4); cout << endl; cout << "反转后:" << endl; reverse(v.begin(), v.end()); for_each(v.begin(), v.end(), myPrint4); } int main() { test4(); return 0; }
常用拷贝和替换算法
算法简介:
-
copy //容器内指定范围的元素拷贝到另一容器中
-
replace //将容器内指定范围的旧元素修改为新元素
-
replace_if //容器内指定范围满足条件喜欢成新元素
-
swap //互换两个容器元素
copy
容器内指定范围元素拷贝到另一个容器中
函数原型
-
copy(iterator beg,iterator end,iterator dest);
//dest目标容器
#include<iostream> using namespace std; #include<vector> #include<algorithm> void myPrint5(int val) { cout << val << " "; } void test5() { vector<int>v1; for (int i = 0; i < 10; i++) { v1.push_back(i); } vector<int>v2; v2.resize(v1.size()); copy(v1.begin(), v1.end(), v2.begin()); for_each(v2.begin(), v2.end(), myPrint5); cout << endl; } int main() { test5(); return 0; }
replace
将容器内指定范围的旧元素改为新元素
函数原型:
-
replace(iterator beg,iterator end,oldvalue,newvalue);
#include<iostream> using namespace std; #include<vector> #include<algorithm> void myPrint6(int val) { cout << val << " "; } void test6() { vector<int>v; v.push_back(10); v.push_back(30); v.push_back(50); v.push_back(20); v.push_back(40); v.push_back(10); v.push_back(20); cout << "替换前" << endl; for_each(v.begin(), v.end(), myPrint6); cout << endl; cout << "替换后" << endl; replace(v.begin(), v.end(), 20, 2000); for_each(v.begin(), v.end(), myPrint6); cout << endl; } int main() { test6(); return 0; }
replace_if
将区间内满足条件的元素,替换成指定元素
函数原型:
-
replace_if(iterator beg,iterator end,_pred,newvalue);
//_pred 谓词
#include<iostream> using namespace std; #include<vector> #include<algorithm> class myPrint7 { public: void operator()(int val) { cout << val << " "; } }; class Greater30 { public: bool operator()(int val) { return val >= 30 && val < 60; } }; void test7() { vector<int>v; v.push_back(10); v.push_back(20); v.push_back(30); v.push_back(40); v.push_back(50); v.push_back(60); cout << "替换前" << endl; for_each(v.begin(), v.end(), myPrint7()); cout << endl; //将大于等于30 小于60 替换为3000 //如果是自定义函数,要重载= cout << "替换后" << endl; replace_if(v.begin(), v.end(), Greater30(), 3000); for_each(v.begin(), v.end(), myPrint7()); cout << endl; } int main() { test7(); return 0; }
swap
互换两个容器的元素,注意交换的容器要同种类型
函数元素:
-
swap(container c1,container c2);
#include<iostream> using namespace std; #include<vector> #include<algorithm> class myPrint8 { public: void operator()(int val) { cout << val << " "; } }; void test8() { vector<int>v1; for (int i = 0; i < 10; i++) { v1.push_back(i); } vector<int>v2; for (int i = 100; i < 110; i++) { v2.push_back(i); } cout << "互换前" << endl; for_each(v1.begin(), v1.end(), myPrint8()); cout << endl; for_each(v2.begin(), v2.end(), myPrint8()); cout << endl; swap(v1, v2); cout << "互换后" << endl; for_each(v1.begin(), v1.end(), myPrint8()); cout << endl; for_each(v2.begin(), v2.end(), myPrint8()); cout << endl; } int main() { test8(); return 0; }
常用算术生成算法
注意:算术生成属于小型算法,使用时包含的头文件为#include <numeric>
算法简介:
-
accumulate //计算容器元素总和
-
fill //向容器中添加元素
accumulate
计算区间内 容器元素累计总和
注意:使用时注意头文件#include<numeric>
函数原型:
-
accumulate(iterator beg,iterator end,vlaue)
#include<iostream> using namespace std; #include<vector> #include<numeric> void test9() { vector<int>v1; for (int i = 0; i <= 100; i++) { v1.push_back(i); } //参数3是 起始的累加值 不需要就写0 int total=accumulate(v1.begin(), v1.end(), 0); cout << "total=" << total << endl; } int main() { test9(); return 0; }
fill
向容器中填充指定的元素
函数原型:
-
fill(iterator beg,iterator end,vlaue)
#include<iostream> using namespace std; #include<vector> #include<algorithm> #include<numeric> class myPrint10 { public: void operator()(int val) { cout << val << " "; } }; void test10() { vector<int>v; v.resize(10); //后期重新填充 fill(v.begin(), v.end(), 100); for_each(v.begin(), v.end(), myPrint10()); } int main() { test10(); return 0; }
常用集合算法
算法简介:
-
set_intersection //求两个容器的交集
-
set_union //求两个容器的并集
-
set_difference //求两个容器的差集
set_intersection
求两个容器交集
函数原型:
-
set_intersection (iterator beg1,iterator end1,iterator beg2,iterator end2 ,iterator dest);
注意:
-
求交集的两个集合必须是有序序列
-
目标容器开辟空间需要从两个容器中取最小值
-
set_intersection 返回值既是交集中最后一个交集位置
#include<iostream> using namespace std; #include<vector> #include<algorithm> class myPrint11 { public: void operator()(int val) { cout << val << " "; } }; void test11() { vector<int>v1; vector<int>v2; for (int i = 0; i < 10; i++) { v1.push_back(i); } for (int i = 5; i < 15; i++) { v2.push_back(i); } //目标容器 vector<int>vTarget; //开辟空间 vTarget.resize(min(v1.size() , v2.size())); //获取交集 vector<int>::iterator itEnd=set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin()); for_each(vTarget.begin(), itEnd, myPrint11()); cout << endl; } int main() { test11(); return 0; }
set_union
求两个集合的并集
函数原型:
-
set_union (iterator beg1,iterator end1,iterator beg2,iterator end2 ,iterator dest);
注意:
-
两个集合必须是两个有序序列
-
目标容器开辟空间需要两个容器相加
-
set_union返回值既是交集中最后一个交集位置
#include<iostream> using namespace std; #include<vector> #include<algorithm> class myPrint12 { public: void operator()(int val) { cout << val << " "; } }; void test12() { vector<int>v1; vector<int>v2; for (int i = 0; i < 10; i++) { v1.push_back(i); } for (int i = 5; i < 15; i++) { v2.push_back(i); } //目标容器 vector<int>vTarget; //开辟空间 vTarget.resize(v1.size()+ v2.size()); //获取并集 vector<int>::iterator itEnd = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin()); for_each(vTarget.begin(), itEnd, myPrint12()); cout << endl; } int main() { test12(); return 0; }
set_difference
求两个集合的差集
函数原型:
-
set_difference (iterator beg1,iterator end1,iterator beg2,iterator end2 ,iterator dest);
#include<iostream> using namespace std; #include<vector> #include<algorithm> class myPrint13 { public: void operator()(int val) { cout << val << " "; } }; void test131() { vector<int>v1; vector<int>v2; for (int i = 0; i < 10; i++) { v1.push_back(i); v2.push_back(i+5); } //目标容器 vector<int>vTarget; //开辟空间 vTarget.resize(max(v1.size(),v2.size())); //取交集 cout << "v1和v2的差集为:" << endl; vector<int>::iterator itEnd = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin()); for_each(vTarget.begin(), itEnd, myPrint13()); cout << endl; cout << "v2和v1的差集为:" << endl; itEnd = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), vTarget.begin()); for_each(vTarget.begin(), itEnd, myPrint13()); cout << endl; } int main() { test131(); return 0; }