作者引言:出于具体需求,今日开始正式入坑C++,’ 大鹏一日同风起,扶摇直上九万里 ',浏览和关注的朋友如果对于博文或者C++的学习望不吝赐教,大家一起加油,这篇博文我也会更新到我的C++基础知识学完为止。
“不积跬步无以至千里,不积小流无以成江海。”
如果中间有些知识点有些错误或者不完善我在学习的过程中也会慢慢完善的。
书籍推荐:
Stephen Prata《C++ Primer Plus》
侯捷《STL源码解析》
Scott Meyers《Effective C++》
Stanley B.Lippman《深度探索C++对象模型》
当然,书籍只是推荐哦~~正常情况下还是建议各位自己动手去试错然后总结归纳,提高实践能力。
C++程序运行机理:
- 预处理器:根据以"#"开头的命令,修改源程序,得到另外一个C++程序;
- 编译器:将预处理后的文件转换成汇编程序,以标准的文本格式描述每条机器语言指令;
- 汇编器:将汇编语言翻译成机器语言指令,打包成一种可定位的目标程序格式,即为二进制文件;
- 连接器:调用cout对象,由系统负责执行;
在转化成二进制文件的过程中不同的语言有不同的方式,一般情况下,西语用的是ASCll和Unicode编码,而对于中文而言,一般用的是GB2312、GBK、GB18030等编码转化成二进制文件。
ASCll不同进制表示常量的表示形式
进制类型 | 整数19不同进制的表示 | 整数-19不同进制的表示 | 特点 |
---|---|---|---|
十进制(decimal) | 19 | -19 | 由0~9的数字序列组成,以10为基的数字系统,可以转化成二进制,八进制和十六进制 |
二进制(binary) | 00010011 | 10010011 | 由0,1的数字序列组成,以2为基的数字系统,可以转化成十进制,八进制和十进制 |
八进制(octal) | 023 | -023 | 由0~7的数字序列组成,以8为基的数字系统,可以转化成二进制,十进制和十六进制 |
十六进制(hexadecimal) | 0x13 | -0x13 | 由09,AF(或a~f)的序列组成,以16为基的数字系统,可以转化成二进制,八进制和十进制 |
函数积累
函数 | 作用 |
---|---|
cin(x) | 通过按键输入数据 |
get(x) | 通过按键输入数据 |
swap(int x,int y) | 把x,y的位置互换 |
基础操作
注释:
作用:在代码里面加一些说明和解释,方便自己或其他程序员阅读代码
// 单行注释
/*
多行注释
*/
变量:
作用:给一段指定的内存空间起名,方便操作这段内存
意义:方便我们管理内存空间
// 数据类型 变量名 = 初始值
常量:
作用:用于记录程序中不可更改的数据
/* 1.
# define宏常量
# define 常量名 常量值
通常在文件上方定义,表示一个常量*/
/* 2.
const修饰的变量
const 数据类型 常量名 = 常量值
通常在变量定义前加关键字const,修饰该变量为常量,不可修改*/
标识符命名规则
- 标识符不能是关键字;
- 标识符只能由字母,数字,下划线组成;
- 第一个字符必须为字母或下划线;
- 标识符字母区分大小写;
输出格式:
// 以输出"laoliu is so handsome that i like him"为例
// 1. 第一种
cout << "laoliu is so handsome that i like him" << endl;
// 2. 第二种
cout << "laoliu is so"
<< "handsome that"
<< "i like him"
<< "endl";
// 3. 第三种
cout << "laoliu is so";
cout << "handsome that";
cout << "i like him";
cout << "endl";
第一阶段:基础知识
数据类型
原因:C++规定在创建一个变量或常量时,必须指出相应的数据类型,否则无法给变量分配内存
- 整型:
数据类型 | 占用空间 | 取值范围 |
---|---|---|
short(短整型) | 2字节 | (-215~215-1) |
int(整型) | 4字节 | (-231~231-1) |
long(长整型) | Windows为4字节,Linux为4字节(32位),8字节(16位) | (-231~231-1) |
long long(长长整型) | 8字节 | (-263~263-1) |
关键字sizeo
作用:利用sizeo关键字可以统计数据类型所占内存大小
语法:sizeof(数据类型 / 变量)
- 实形(浮点型)
作用:用来表示小数
分类:单精度float;双精度double
数据类型 | 占用空间 | 有效数字范围 |
---|---|---|
float | 4字节 | 7位有效数字 |
double | 8字节 | 15~16位有效数字 |
- 字符型
作用:字符型变量用于显示单个字符
语法:char ch = 'a';
careful:
- 在显示字符型变量的时候,用单引号将字符括起来,而不用双引号;
- 单引号内只能有一个字符,不可以是字符串;
- C++里面字符型变量只占用一个字节;
- 字符型变量并不是把字符串本身放入内存中存储,而是将对应的ascll编码放入到存储单元
- 转义字符
作用:用于表示一些不能显示出来的ascll字符,如:\n,\,\t
转义字符 | 意义 | ASCII码值(十进制) |
---|---|---|
\a | 响铃,警报(BEL) | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\ | 代表一个反斜线字符’’’ | 092 |
’ | 代表一个单引号(撇号)字符 | 039 |
" | 代表一个双引号字符 | 034 |
? | 代表一个问号 | 063 |
\0 | 空字符(NULL) | 000 |
\ddd | 1到3位八进制数所代表的任意字符 | 三位八进制 |
\xhh | 1到2位十六进制所代表的任意字符 | 二位十六进制 |
- 字符串型
作用:用于表示一串字符
// string 变量名 = "字符串值"
- 布尔类型
作用:布尔数据类型代表真或假的值
分类:true—真(本质是1);false—假(本质是0)
bool类型占1个字节大小
- 数据的输入
作用:用于从键盘获取数据
关键字:cin
语法:cin >> 变量
#include <iostream>
using namespace std;
int main()
{
int a = 0;
cout << "请给a赋值:" << endl;
cin >> a;
cout << "a的值为:" << a << endl;
system("pause");
return 0;
}
运算符
作用:用于执行代码的运算
运算符类型 | 作用 |
---|---|
算术运算符 | 用于处理四则运算 |
赋值运算符 | 用于将表达式的值赋值给变量 |
比较运算符 | 用于表达式的比较,并返回一个真值或假值 |
逻辑运算符 | 用于根据表达式的值返回真值或假值 |
- 算数运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | -3 | -3 |
+ | 加 | 10 + 5 | 15 |
- | 减 | 10 — 5 | 5 |
* | 乘 | 10 * 5 | 50 |
/ | 除 | 10 / 5 | 2 |
% | 取模(取余) | 10 % 3 | 1 |
++ | 前置递增 | a=2;b=++a; | a=3;b=3; |
++ | 后置递增 | a=2;b=a++; | a=3;b=2; |
– | 前置递减 | a=2;b=–a; | a=1;b=1; |
– | 后置递减 | a=2;b=a–; | a=1;b=2; |
- 赋值运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
= | 赋值 | a=2;b=3; | a=2;b=3; |
+= | 加等于 | a=0;a+=2; | a=2; |
-= | 减等于 | a=0;a-=2; | a=2; |
*= | 乘等于 | a=0;a*=2; | a=4; |
/= | 除等于 | a=0;a/=2; | a=2; |
%= | 模等于 | a=0;a%=2; | a=1; |
- 比较运算符
提示:根据前面布尔类型可知,'0’代表false,'1’代表true
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
== | 相等于 | 4==3 | 0 |
!= | 不等于 | 4!=3 | 1 |
< | 小于 | 4<3 | 0 |
> | 大于 | 4>3 | 1 |
<= | 小于等于 | 4<=3 | 0 |
>= | 大于等于 | 4>=1 | 1 |
- 逻辑运算符
作用:用于根据表达式的值返回真值或假值
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
! | 非 | !a | 如果a为假,则!a为真;如果a为真,则!a为假。 |
&& | 与 | a && b | 如果a和b都为真,则结果为真,否则为假。 |
|| | 或 | a || b | 如果a和b有一个为真,则结果为真,二者都为假的时候,结果为假 |
程序流程结构
分类:顺序结构,选择结构,循环结构
- 顺序结构:程序按顺序执行,不发生跳转;
- 选择结构:依据条件是否满足,有选择的执行响应功能;
- 循环结构:依据条件是否满足,循环多次执行某段代码;
- 选择结构
if语句
作用:执行满足条件的语句
/*
if语句的三种格式:
单行格式if语句:if(条件){条件满足执行的语句};
多行格式if语句:if(条件){条件满足执行的语句}else{条件不满足执行的语句};
多条件的if语句:if(条件1){条件1满足执行的语句}else if(条件2){条件2满足执行的语句}...else{都不满足执行的语句}
*/
switch语句
作用:执行多条件分支语句
switch("表达式")
{
case "结果1":执行语句;break;
case "结果2":执行语句;break;
case "结果3":执行语句;break;
...
default:执行语句;break;
}
- 循环结构
while循环语句
作用:满足循环条件,执行循环语句
语法:while("循环条件"){"循环语句"}
解释:只要循环条件的结果为真,就执行循环语句
int num = 1;
while(num < 5){
cout << "输出的数字为:" << endl;
num++;
}
do…while循环语句
作用:满足循环条件,执行循环语句
语法:do{"循环语句"} while("循环条件"){"循环语句"};
carefully:与while的区别在于do…while会先执行一次循环语句,再判断循环条件
int main() {
int num = 0;
do{
cout << num << endl;
num++;
}
while(num < 10){
system("pause")
}
}
for循环语句
作用:满足循环条件,执行循环语句
语法:for(起始表达式; 条件表达式; 末尾循环体){循环语句;}
int main() {
for (int i = 0; i < 10; i++){
cout << i << endl;
}
system("pause");
return 0;
}
- 跳转语句
break语句
作用:用于跳出选择结构或者循环结构
break使用动机:
- 出现在switch条件语句中,作用是终止case并跳出switch;
- 出现在循环语句中,作用是跳出当前的循环语句;
- 出现在嵌套循环中,跳出最近的内层循环语句;
continue语句
作用:在循环语句中,跳过本次循环中余下尚未执行的代码,继续执行下一次循环
int main() {
for (int i = 1; i < 10; i++){
if (i % 2 == 0){
continue;
}
cout << i << endl;
}
system("pause");
return 0;
}
goto语句
作用:可以无条件跳转循环
语法:goto 标记
解释:如果标记的名称存在,执行到goto语句,会跳转到标记的位置
int main() {
cout << "1" << endl;
goto FLAG;
cout << "2" << endl;
cout << "3" << endl;
cout << "4" << endl;
FLAG;
cout << "5" << endl;
system("pause");
return 0;
}
数组
定义:所谓数组,就是一个集合,里面存放了相同类型的数据元素
特点:
- 数组中的每个数据元素都是相同类型的数据类型;
- 数组是由连续的内存位置存放的;
- 一维数组
/*
一维数组定义方式:
1. 数据类型 数组名[数组长度];
2. 数据类型 数组名[数组长度] = {值1, 值2...};
3. 数据类型 数组名[] = {值1, 值2...};
*/
int main() {
// 1. 数据类型 数组名[数组长度];
int arr_1[10];
// 利用下标赋值
arr_1[0] = 1;
arr_1[1] = 2;
...
arr_1[9] = 10;
// 2. 数据类型 数组名[数组长度] = {值1, 值2...};
int arr_2[5] = {1, 2, 3, 4, 5}
// 3. 数据类型 数组名[] = {值1, 值2...};
int arr_3[] = {1, 2, 3, 4, 5}
/*
关于输出可查看:https://blog.csdn.net/u014485485/article/details/80482836
*/
// 对于字符形数组
char chr_4[] = "asc";
cout << chr_4 << endl; // 输出是整个字符串
char arr_4[] = {'a', 'b', 'c'};
cout << arr_4 << endl; // 输出是一个乱码,比如abc#¥%&,因此必须在列表字符串数组后面加上一个'\0'
char arr_5[] = {'a', 'b', 'c', '\0'}; // 此时输出正确,abc
cout << arr_5 << endl;
printf("%p \n", arr_5);
}
一维数组名称用途:
/*
1. 可以统计整个数组在内存中的长度:sizeof(arr)
获取数组长度:(1). 数据类型是字符串:strlen();
(2). 其他:sizeof(arr)/sizeof(arr[0])
末尾元素的下标:sizeof(arr)/sizeof(arr[0]) - 1
2. 可以获取数组在内存中的首地址(原本输出的是十六进制,int转化成十进制来查看):(int*)arr或者arr转为 printf("%d", arr);
或printf("%I64d", arr);
3. 可以看数组中第一个元素的地址:(int*)&arr[0]或者&arr[0]转为同上
*/
冒泡排序
作用:最常用的算法,对数组内元素进行排序
叙述:
- 比较相邻的元素,如果第一个比第二个大,那就交换他们;
- 对每一对相邻元素做同样的工作,执行完毕后,找到第一个最大值;
- 重复以上的步骤,每次比较次数-1,直到不需要比较;
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; i < 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;
system("pause");
return 0;
}
- 二维数组
/*
二维数组定义方式:
1. 数据类型 数组名[行数][列数];
2. 数据类型 数组名[行数][列数] = {{数据1, 数据2}, {数据3, 数据4}}; 更加直观,提高了代码的可读性
3. 数据类型 数组名[行数][列数] = {数据1, 数据2, 数据3, 数据4};
4. 数据类型[][列数] = {数据1, 数据2, 数据3, 数据4};
*/
二维数组名称用途
/*
1. 查看二维数组所占内存空间:sizeof(arr)
2. 查看二维数组第一行所占内存空间:sizeof(arr[0])
3. 查看二维数组第一个元素所占内存空间:sizeof(arr[0][0])
4. 获取二维数组的首地址:int(arr)
5. 可以看数组中第一行元素的地址:(int)&arr[0]
6. 可查看数组中第一个元素的地址:(int)&arr[0][0]
*/
函数
作用:将一段经常使用的代码封装起来,减少重复代码,一个较大的程序,一般分为若干个程序块,每个模块实现特有的功能
函数的定义:返回值类型;函数名;参数表列;函数体语句;return表达式
语法:
返回值类型 函数名 (参数列表){
函数名语句
return 表达式
}
函数的调用
功能:使用定义好的函数
语法:函数名(参数)
// 函数定义
int add(int num1, int num2){ // 定义中的num1,num2成为参数形式,简称形参
int sum = num1 + num2;
return sum;
}
int main() {
int a = 10;
int b = 10;
// 调用add函数
int sum = add(a, b); // 调用时的a,b成为实际参数,简称实参
cout << sum << endl;
system("pause");
return 0;
}
函数的常见样式:无参无返,无参有返,有参无返,有参有返
#include<iostream>
using namespace std;
// 函数的声明
int max(int a, int b);
int max(int a, int b){
return a > b ? a : b
}
int main() {
int a = 1;
int b = 2;
cout << max(a, b) << endl;
system("pause");
return 0;
}
函数的分文件编写
- 创建后缀名为.h的头文件;
- 创建后缀名为.cpp的源文件;
- 在头文件中写函数的声明;
- 在源文件中写函数的定义;
// 第一个文件max.h头文件
#include<iostream>
using namespace std;
// 函数的声明
int max(int a, int b);
// 第二个文件max.cpp源文件
#include "max.h9999"
#include<iostream>
using namespace std;
指针
作用:可以通过指针间接访问内存,提高代码运行的效率。
- 内存编号是从0开始记录的,一般用十六进制表示;
- 可以利用指针变量保存地址;
指针变量的定义和使用
语法:数据类型 * 变量名;
int main() {
// 1. 定义指针
int a = 1;
// 2. 指针定义的语法:数据类型 * 指针变量名
int * p;
// 3. 让指针记录变量a的地址
p = &a;
cout << "a的地址为" << &a << endl;
cout << "a的地址为" << p << endl;
// 4. 使用指针
// 可以通过解引用的方式来找到指针指向的内存
// 指针前加一个*代表解引用找到数据并修改内存
*p = 1000;
cout << a <<endl;
cout << p <<endl;
system("pause");
return 0;
}
指针的关系运算
#include<iostream>
using namespace std;
// 判断一个数是否是回文数
int main() {
char s[71], *p;
get(s); // 标准库函数get()与cin类似甚至更好
top = &s[0]; // top值表示的是s变量的首地址
end = &s[sizeof(s)-1]; // end表示的是s变量的最后一个字节的地址,此处还可用&s[strlen(s)-1]
for(top, end;top < end; top++, end--) { // 此处end是高地址,top是低地址
if(*top != *end) { break; }
}
if(top >= end) {
cout << "是一个回文数" << endl;
}else {
cout << "不是回文数" << endl;
}
system("pause");
return 0;
}
/*
指针的相减和赋值运算
如果指针p1指向数组a里面的第二个元素a[1],指针p2指向数组a里面的第五个元素a[4],那么p1 - p2 = 3(表示的是两指针所指变量之间相距的同类型变量的个数)
指针没有相加运算,没有任何意义
*/
指针所占内存空间
- 在32位操作系统下,占用4个字节;
- 在64位操作系统下,占用8个字节;
空指针和野指针
空指针:指针变量指针内存中编号为0的空间;
用途:初始化指针变量;
注意:空指针指向的内存是不可以访问的;
int main() {
// 指针变量指针内存中编号为0的空间
int * p = NULL;
// 访问空指针报错
// 内存编号0~255位系统所占内存,不允许用户访问
// *p = 100;
cout << *p << endl;
system("pause");
return 0;
}
野指针:指针变量指向非法的内存空间
int main() {
// 指针变量p指向内存地址中编号为0x1100的空间
int * p = (int *)0x1100;
// 访问野指针报错
cout << *p << endl;
system("pause");
return 0;
}
const修饰指针
const修饰指针:常量指针;
const修饰常量:指针常量;
const即修饰指针,又修饰常量;
int main() {
int a = 10;
int b = 10;
// const修饰的是指针,指针指向可以改,指针指向的值不可以更改
const int * p1 = &a;
p1 = &b; // 正确
// *p1 = 100; 报错
// const修饰的是常量,指针指向不可以改,指针指向的值可以更改
int * const p2 = &a;
// p2 = &b; 错误
*p2 = 100; // 正确
// const修饰的是常量和指针,指针指向不可以改,指针指向的值不可以更改
const int * const p3 = &a;
*p3 = 100; // 错误
p4 = &b; // 错误
}
指针和数组
概念:C++语言中的指针和数组有着密切的关系,数组名可以认为是一个常量指针
作用:利用指针访问数组元素
// 对于一维数组
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 指向数组的指针,此时arr代表的是数组的首地址,数组名可以认为是一个常量指针,因此可以直接写成int * p = arr;
int * p = arr; //这里的arr=&arr[0]
cout << "第一个元素:" << arr[0] << endl;
cout << "指针访问的第一个元素:" << *p << endl;
p++; // 让指针向后偏移4个字节
for(int i = 0; i < 10; i++){
cout << *p << endl;
P++;
}
system("pause");
return 0;
}
// 对于二维数组
int main() {
int a[3][3] = {{1, 3, 5}, {7, 9, 11}, {13, 15, 17}}
int *p = a[0]; // p是一个执行整型变量的指针,他可以指向一般的整型变量,也可以指向数组中的元素
for(p; a[0] < a[0] + 8; p++) {
cout << *p << " ";
cout << endl;
}
}
指针和函数
作用:利用指针做函数参数,可以修改实参的值
// 值传递
void max(int a, int b){
int temp = a;
a = b;
b = temp;
}
// 地址传递
void max(int * p1, int * p2){ // 传入的必须是一个地址&a
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
// 针形函数
void *func(int x) { // 返回值必须是一个int型指针
cout << x << endl;
}
int main() {
int a = 1;
int b = 2;
max(&a, &b);
}
结构体
定义:结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
结构体的定义和使用
语法:struct 结构体名{结构体成员列表};
通过结构体创建变量方式有三种:
struct 结构体名 变量名;
struct 结构体名 变量名 = {成员1值,成员2值}
- 定义结构体时顺便创建变量
// 结构体定义
struct Student{
// 成员列表
string name; // 姓名
int age; // 年龄
int score; //分数
}
int main() {
// struct 结构体名 变量名;
struct Student s1;
// 给s1属性赋值,通过访问结构体变量中的属性
s1.name = "张三";
s1.age = 13;
s1.score = 99;
//struct 结构体名 变量名 = {成员1值,成员2值}
struct Student s2 = {"张三", 23, 98};
/*
定义结构体时顺便创建变量
struct Student{
// 成员列表
string name; // 姓名
int age; // 年龄
int score; //分数
}s3;
*/
s3.name = "张三";
s3.age = 13;
s3.score = 99;
system("pause");
return 0;
}
结构体数组
作用:将自定义的结构体放入到数组中方便维护
语法:struct 结构体名 数组名[元素个数] = {{}, {}, {}, {}...{}}
// 结构体定义
struct Student{
// 成员列表
string name; // 姓名
int age; // 年龄
int score; //分数
}
int main() {
// 结构体数组
struct Student arr[3] = {
{"张三", 13, 34},
{"李四", 23, 45},
{"王五", 12, 45}
}
// 给结构体数组的元素赋值
arr[2].name = "赵六";
arr[2].age = 34;
arr[2].score = 78;
// 遍历结构体数组
for(i = 0;i <3; i++){
cout << arr[i].name;
cout << arr[i].age;
cout << arr[i].score;
cout << endl;
}
}
结构体指针
作用:通过指针访问结构体中的成员
语法:利用操作符->可以通过结构体访问结构体属性
// 结构体定义
struct Student{
// 成员列表
string name; // 姓名
int age; // 年龄
int score; //分数
}
int main() {
// 创建学生结构体变量
struct Student stu = {"张三", 12, 34};
// Student stu = {"张三", 12, 34}; // 结构体作为一个数据类型,也可以写成数据类型 变量 = ...格式
// 通过指针指向结构体变量
struct Student * p = &stu;
// Student * p = &stu;
// 通过指针访问结构体变量中的数据
cout << p -> age;
cout << p -> name;
cout << p -> socre;
cout << endl;
}
结构体嵌套结构体
作用:结构体中的成员可以是另外第一个结构体
例如:每个老师辅导一个学员,一个老师的结构体中,记录一个学生的结构体
#include<iostream>
using namspace std;
#include<string>
// 结构体定义
struct Student {
// 成员列表
string name; // 姓名
int age; // 年龄
int score; //分数
}
struct Teacher {
// 成员列表
int id; // 编号
string name ; // 姓名
int age; //分数
struct Student stu; // 辅导的学生
}
int main() {
// 结构体嵌套结构体
struct Teacher t;
t.id = 1000;
t.name = "不圆";
t.age = 99;
t.stu.name = "八戒";
t.stu.age = 45;
t.stu.score = 56;
cout << t.id;
cout << t.name;
cout << t.age;
cout << t.stu.name;
cout << t.stu.age;
cout << t.stu.score;
}
结构体做函数参数
作用:将结构体作为参数向函数中传递
传递方式:值传递,地址传递
#include<iostream>
using namspace std;
#include<string>
// 结构体定义
struct Student {
// 成员列表
string name; // 姓名
int age; // 年龄
int score; //分数
}
// 值传递,形参发生了改变不会影响实参
void printStudent(struct Student stu){ // 或者(Student stu)
cout << stu.name;
cout << stu.age;
cout << stu.score;
cout << endl;
}
int main() {
struct Student stu;
stu.name = "张三";
stu.age = 21;
stu.score = 89;
printStudent(stu)
}
// 地址传递,形参发生了改变会影响实参
void printStudent(struct Student *p){
cout << p->name << endl;
}
int main() {
struct Student stu;
stu.name = "张三";
stu.age = 21;
stu.score = 89;
printStudent(&stu)
}
结构体中const的使用场景
作用:用const防止误操作
#include<iostream>
using namspace std;
#include<string>
// 结构体定义
struct student {
// 成员列表
string name; // 姓名
int age; // 年龄
int score; //分数
}
// 将形参改成指针,可以减少内存空间,而且不会复制出新的副本出来
void printstudent(const struct student *s) {
// s -> = 150 加入const后不可修改
cout << s->name;
cout << s->age;
cout << s->score;
cout << endl;
}
int main() {
// 创建结构体变量
struct student s = {"张三", 23, 34};
printstudent(&s);
system("pause");
return 0;
}
C++核心编程
内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数的二进制文件,由操作系统进行管理的;
- 全局区:存放全局变量和静态变量以及常量;
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等;
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统完成;
意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
程序运行前:
在程序编译后,生成了exe可执行文件,未执行该程序前分成两个区域
代码区:
- 存放CPU执行的机器指令;
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可;
- 代码区是只读的,使其只读的原因是防止程序意外的修改了程序;
全局区:
- 全局变量和静态变量存放于此;
- 全局区还包括了常量区,字符串常量和其他常量也存放于此;
- 该区域的数据在程序结束后由操作系统释放;
int a = 0; // 全局变量
int main() {
int b = 1; // 局部变量
static int c = 2; // 静态变量
}
程序运行后:
栈区:
- 由编译器自动分配释放,存放函数的参数值,局部变量等;
- 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放;
堆区:
- 由程序员分配释放,若程序员不释放,程序结束的时由操作系统回收;
- 在C++中主要利用new在栈区开辟内存
void *func() { // 返回为地址要加*
// 利用new关键字将数据开辟到堆区
int *p = new int(10);
return p;
}
int main() {
// 在堆区开辟数据
int *p = func();
cout << *p << endl;
system("pause");
return 0;
}
new操作符
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型;
利用new创建的数据,会返回该数对应的类型的指针
// new的基本语法
int *func(){
// 在堆区创建整型数据
// new返回是 该数据类型的指针
int *a = new int(10);
return a;
}
void test01() {
int *p = func();
cout << *p << endl;
// 堆区的数据,程序员手动开辟,手动释放
// 释放利用操作符delete
delete p;
}
// 在堆区利用new开辟数组
void test02() {
// 创建10整型数据的数组,在堆区;
int * arr = new int[10]; // 10代表数组有十个元素
for(int i = 0;i < 10; i++) {
arr[i] = i + 100
}
for(int i =0 ;i < 10; i++) {
cout << arr[i] << endl;
}
// 释放数组的时候加上[]才可以
delete[] arr;
}
引用
引用的基本使用:
作用:给变量起别名
语法:数据类型 &别名 = 原名
int a = 10;
int &b = a;
引用的注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
// 2. 地址传递
void he(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 3. 引用传递
int my(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
引用函数做返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
// 返回局部变量引用,不可用
int& test01() {
int a = 10; // 局部变量在四区中的栈区
return a;
}
// 返回静态变量引用
int& test02() { // int& 函数名表示以引用的方式返回,其他如int* 函数名表示以指针的方式返回
static int a = 10;
return a;
}
int main() {
int &ref = test01();
cout << ref << endl;
system("pause");
return 0;
}
引用的本质:在C++内部实现一个指针常量
常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数列表中,可以加const修饰形参,防止形参改变实参
// 引用使用的场景,通常用来修饰形参
int main() {
// 常量引用
// 使用场景:用来修饰形参,防止误操作
// int a = 10;
// 加上const后编译器将代码修改
const int &b = 10; // 引用必须引一块合法分内存空间
// ref = 10 报错,加上const后不可修改
system("pause");
return 0;
}
void getnum(const int &a) { // int &a表示以引用的方式接收这个值,如果在外面则是以什么样的方式返回
// a = 100; // 不加const能直接修改
cout << a << endl;
}
int main() {
int a = 10;
getnum(a);
}
函数的提高
函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的
语法:返回值类型 函数名 (参数=默认值){}
int func(int a, int b=10, int c=12) {
return a+b+c;
}
/*
如果某个位置参数有默认值,那么从这个位置往后,从左往右,必须有默认值
如果函数声明有默认值,函数实现的时候就不能有默认值
*/
int func_2(int a, int b){} // 函数声明
int func_2(int a = 1, int b = 2) {
return a + b;
}
int main() {
func(10, 20, 30);
system("pause");
return 0;
}
函数的占位参数
C++函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型){}
void func(int a, int) {
cout << "this is func" << endl;
}
int main() {
func(10, 10); // 占位参数必须填补
system("pause");
return 0;
}
函数重载
作用:函数名可以相同,提高复用性
满足条件:
- 同一作用域下;
- 函数名称相同;
- 函数参数类型不同,或者个数不同或者顺序不同;
careful:函数的返回值不可以作为函数重载的条件
// 函数重载
// 可以让函数名相同
/* 函数重载满足的条件
1. 同一作用域下;
2. 函数名称相同;
3. 函数参数类型不同,或者个数不同或者顺序不同;
*/
void func() {
cout << "第一个" << endl;
}
void func(int a) {
cout << "第二个(int a)" << endl;
}
int main() {
func();
int b = 1;
func(b)
system("pause");
return 0;
}
函数重载注意事项
- 引用作为重载条件;
- 函数重载碰到函数默认参数;
// 函数重载注意事项
// 1. 引用作为重载条件
void func(int &a) { // int &a = 10不合法
cout << a << endl;
}
void func(const int &a) { // 只读 const int &a = 10合法
cout << a << endl;
}
int main() {
int a = 10;
func(a); // 调用的是第一个函数
func(10); // 调用的是第二个函数
}
// 2. 函数重载时碰到默认参数
void func(int a){
cout << a << endl;
}
void func(int a, int b = 10){ // b = 10已定义,为默认参数
cout << a << endl;
}
int main() {
func(1); // 出现二义性
system("pause");
return 0;
}
类和对象
C++面向对象三大特性:封装,继承,多态
C++认为万事万物皆可为对象,对象上有其属性和行为
such as:
- 人可以作为对象,属性有姓名,年龄,身高,体重…行为有走,跑,吃饭等等;
- 车也可以做为对象,属性有轮胎,方向盘…行为有载人,放音乐等等;
- 具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类;
封装
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物;
- 将属性和行为加以权限控制;
语法:class 类名{访问权限:属性 / 行为};
// 设计一个圆类,求圆的周长
#include<iostream>
using namespace std;
const double PI = 3.14;
class Circle{
// 访问权限
public:
// 属性
int m_r;
// 行为
// 获取圆的周长
double calculateZC() {
return 2 * PI * m_r;
};
};
int main() {
// 通过圆类创建一个具体的圆
Circle cl;
// 给圆对象的属性进行赋值
cl.m_r = 10;
cout << "周长为" << cl.calculateZC() << endl;
system("pause");
return 0;
}
访问权限
/*
公共权限(public):成员类内可以访问,类外可以访问
保护权限(protected):成员类内可以访问,类外不可以访问,子类可以访问父类中的保护内容
私有权限(private):成员类内可以访问,类外不可以访问,子类可以不访问父类中的保护内容
*/
class Person {
public:
string m_Car;
protected:
int m_P;
private:
int m_S;
}
class 和 struct的区别
在C++中唯一的区别就在于默认的访问权限不同
区别:
- struct默认权限为公共;
- class默认权限为私有;
class C1 {
int m_a; // 默认是私有权限
}
struct C2 {
int m_2; // 默认是公共权限
}
成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限;
优点2:对于写权限,我们可以检测数据的有效性;
class Person {
public:
// 设置姓名
void setName(string name) { // 类外读出
m_Name = name;
}
// 获取姓名
string getName() { // 类外写出
return m_Name
}
private: //当成员设置为私有之后利用setName和getName成员函数进行获取值
string m_name; // 类外可读可写
int age; // 类外只读
}
int main() {
Person p;
p.setName("张三");
cout << p.getName() << endl;
}
对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除自己信息数据保证安全;
- C++的面向对象来源于生活,每个对象也都会有初始化设置以及对象销毁前清理数据的设置;
构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
- 一个对象或者变量没有初始状态,对其使用后结果是未知
- 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作,对象的初始化和清理工作是编译器强制我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
- 析构函数:主要作用在于对象销毁前自动调用,执行一些清理工作
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include<iostream>
using namespace std;
class Person {
public:
// 构造函数:用于初始化类
Person() {
cout << "sd" << endl;
}
// 析构函数
~Person() {
cout << "sad" << endl;
}
/*
~Cat() {
if (m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
*/
};
void test01() {
Person p; // 对象的初始化操作
}
int main() {
test01();
system("pause");
return 0;
}
构造函数的分类及调用
两种分类方式
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
#include<iostream>
using namespace std;
// 1. 构造参数的分类和调用
/* 按照参数分类:无参构造(默认构造)和有参构造
按照类型分类:普通构造和拷贝构造
*/
class Person {
public:
// 无参构造
Person() {
cout << "a" << endl;
}
// 有参构造
Person(int a) {
age = a;
cout << "a" << endl;
}
// 拷贝(copy)构造
Person(const Person &p) {
// 将传入的人身上所有属性(age)拷贝到我身上
age = p.age;
}
~Person() {
cout << "b" << endl;
}
int age;
};
// 调用
int main() {
system("pause");
return 0;
}
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
#include<iostream>
using namespace std;
// 1. 构造参数的分类和调用
/* 按照参数分类:无参构造(默认构造)和有参构造
按照类型分类:普通构造和拷贝构造
*/
class Person {
public:
// 无参构造
Person() {
cout << "a" << endl;
}
// 有参构造
Person(int a) {
age = a;
cout << "a" << endl;
}
// 拷贝(copy)构造
Person(const Person &p) {
// 将传入的人身上所有属性拷贝到我身上
age = p.age;
}
~Person() {
cout << "b" << endl;
}
int age;
};
// 调用
void test01() {
// 括号法
Person p1; // 默认构造函数
Person p2(10); // 有参构造
Person p3(p2); // 拷贝(有参)构造
// 显示法
Person p1; // 默认构造
Person p2 = Person(10); // 有参构造
Person p3 = Person(p2); // 拷贝构造
// 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // 拷贝构造
};
int main() {
system("pause");
return 0;
}
拷贝构造函数调用时机
- 使用一个已经创建完毕的对象来初始化一个对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#include<iostream>
using namespace std;
// 拷贝构造函数调用时机
class Person {
public:
// 构造函数
Person() {
cout << "a" << endl;
}
Person(int age) {
m_age = age;
cout << "a" << endl;
}
Person(const Person& p) {
m_age = p.m_age;
cout << "a" << endl;
}
// 析构函数
~Person() {
cout << "b" << endl;
}
int m_age;
};
// 1. 使用一个已经创建完毕的对象来初始化一个对象
void test01() {
Person p1(20);
Person p2(p1);
}
// 2. 值传递的方式给函数参数传值
void dowe(Person p) {
}
void test02() {
Person p;
dowe(p);
}
// 3. 以值方式返回局部对象
void dowe2() {
Person p1;
return p1;
}
void test03() {
Person p = dowe2(); // 此时的p不是dowe2传递的p1,地址不同但是值相同
}
int main() {
test01();
test02();
test03();
system("pause");
return 0;
}
构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行拷贝
构造函数调用规则:
- 如果用户有定义构造函数,C++不在提供默认无参狗砸,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会提供其他构造函数
***后续完善:深拷贝和浅拷贝
- 浅拷贝:简单的赋值拷贝操作;
- 深拷贝:在堆区重新申请空间,进行拷贝操作;
初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1), 属性2(值2),...{}
#include<iostream>
using namespace std;
// 初始化列表
class Person {
public:
// 传统的初始化操作
Person(int a, int b, int c) {
m_A = a;
m_B = b;
m_C = c;
}
// 初始化列表操作初始化属性
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {
}
int m_A;
int m_B;
int m_C;
};
void test01() {
Person p(10, 20, 30);
cout << p.m_A;
cout << p.m_B;
cout << p.m_C;
cout << endl;
}
int main() {
test01();
system("pause");
return 0;
}
类成员作为类对象
C++类中中的成员可以是另一个类的对象,我们称该成员为对象成员
such as
class A {}
class B {
A a; // B类中有对象A作为成员,A为对象成员
}
#include<iostream>
using namespace std;
#include<string>
// 手机类
class Phone {
public:
Phone(string m_name) {
m_name = m_name;
}
string m_name;
};
// 人类
class Person {
public:
Person(string name, string m_name): name(name), m_Phone(m_name)
{
}
string name;
Phone m_Phone;
};
void test01() {
Person p("张三", "apple");
}
int main() {
test01();
system("pause");
return 0;
}
静态成员
分类:
- 静态成员变量
- 所有对象共享一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量
#include<iostream>
using namespace std;
#include<string>
class Person {
public:
// 静态成员函数
static void func() {
m_A = 100; // 静态成员函数可以访问静态成员变量
// m_B = 200,报错,静态成员函数不可以访问非静态成员变量
cout << "sad" << endl;
}
static int m_A; // 静态成员变量,类内声明
};
int Person::m_A = 0; // 静态成员变量,类外初始化
// 有两种访问方式
void test01() {
// 1. 通过对象访问
Person p;
p.func();
// 2. 通过类名访问
Person::func();
}
int main() {
test01();
system("pause");
return 0;
}
this指针概念
成员变量和成员函数是分开存储的,每一个非静态函数只会诞生一份函数实例,也就是说多个同类型的对象会公园一块代码,那么问题是:这一块代码是如何区分哪个对象调用自己的呢?
- this指针指向被调用的成员函数所属的对象;
- this指针是隐含每一个非静态成员函数内的一种指针;
- this指针不需要定义,直接使用即可;
this指针的用途:
当形参和成员变量同名时,可以用this指针来区分;
在类的非静态成员函数中返回对象本身,可使用return *this
#include <iostream>
using namespace std;
class Person {
public:
Person(int age) {
// this指针指向被调用的成员函数所属的对象
this ->age = age;
// 当形参和成员变量同名时,可以用this指针来区分
// 此时this ->age表示的是成员变量int age
}
int age;
};
// 解决名称冲突
void test01() {
Person p1(18);
cout << p1.age << endl;
}
// 返回对象本身用*this
void test02() {
Person p1(10);
Person p2(10);
cout << p1.age << endl;
}
int main()
{
test01();
test02();
cout << "Hello World!" << endl;
}
空指针访问成员函数
// 成员变量默认前面加了个this
const修饰成员函数
常函数:
- 成员函数加const后我们称这个函数为常函数;
- 常函数内不可以修改成员属性;
- 成员属性声明加关键字mutable后,在常函数中依旧可以改变;
常对象:
- 声明对象前加const称该对象为常对象;
- 常对象只能调用常函数;
友元
目的:让一个函数或者类访问另一个类中私有成员0
关键字:friend
友元实现方式:
- 全局函数做友元
#include <iostream>
using namespace std;
#include<string>
// 建筑物类
class Building {
// goodgay全局函数是building的好朋友,可以访问building私有成员
friend void goodgay(Building* building);
public:
Building() {
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
public:
string m_sittingroom; // 客厅
private:
string m_bedroom; // 卧室
};
// 全局函数
void giidgay(Building *building) {
cout << building->m_sittingroom << endl;
}
void test01() {
Building building;
giidgay(&building);
}
int main() {
system("pause");
return 0;
}
- 类做友元
- 成员函数做友元
运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加号运算符重载
#include <iostream>
using namespace std;
class Person {
public:
// 通过成员函数实现重载
Person operator+(Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
int m_A;
int m_B;
};
void test01() {
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
}
int main() {
test01();
system("pause");
return 0;
}
#include <iostream>
using namespace std;
class Person {
public:
int m_A;
int m_B;
};
// 全局函数重载+号
Person operator+(Person& p1, Person& p2) {
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
void test01() {
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
}
int main() {
test01();
system("pause");
return 0;
}
// 本质:p3 = p1.operator+(p2);
左移运算符重载
递增运算符重载
赋值运算符重载
关系运算符重载
函数运算符重载
继承
继承啊面向对象三大特性之一
语法:class 子类(派生类):继承方式 父类(基类)
#include <iostream>
using namespace std;
// 继承实现页面
// 继承的好处,减少重复的代码
class Base {
public:
void header() {
cout << "a" << endl;
}
};
class Java : public Base {
public:
void content() {
cout << "as" << endl;
}
};
int main() {
system("pause");
return 0;
}
继承方式
分类:公共继承,保护继承,私有继承
/*
公共继承:public:private不可继承
保护继承:protected:private不可继承
私有继承:private:
*/
继承中的对象模型:子类继承父类的属性后在内存中依旧可以找到,说明是存在一种实际继承关系,而不是短暂的虚拟的继承关系。
继承中的构造和析构顺序
子类继承父类后,当创建子类对象时,也会调用父类的构造函数,此时是父类和子类的顺序是
父类构造-子类构造-子类析构-父类析构
继承同名成员处理方法
- 访问子类同名成员,直接访问即可
- 访问父类同名函数,需要加作用域
#include<iostream>
using namespace std;
// 继承中同名成员处理
class Base {
public:
Base() {
m_A = 100;
}
void func() {
cout << "asa" << endl;
}
void func(int a) {
cout << "asa" << endl;
}
int m_A;
};
class Son :public Base {
public:
Son() {
m_A = 10;
}
void func() {
cout << "asa" << endl;
}
int m_A;
};
// 同名成员属性
void test01() {
Son s;
cout << s.Base::m_A << endl; // 加一个作用域
}
// 同名成员函数
void test02() {
Son s;
s.Base::func();
// 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类的同名成员
s.Base::func(100);
}
int main() {
system("pause");
return 0;
}
继承同名静态成员处理方法
静态成员和非静态成员出现同名,处理方法一致
- 访问子类同名成员,直接访问即可
- 访问父类同名函数,需要加作用域
多继承语法
C++允许一个类继承多个类
语法:
class 子类:继承方式 父类1,继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
菱形继承案例(略)
多态
多态是C++面向对象三大特性之一
- 多态分类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名;
- 动态多态:派生类和虚函数实现运行时多态;
- 静态多态和动态多态区别:
- 静态多态的函数地址早绑定:编译阶段确定函数地址;
- 动态多态的函数地址晚绑定:运行阶段确定函数地址;
#include<iostream>
using namespace std;
class Animal {
public:
// virtual修饰的函数表示虚函数
virtual void speak() {
cout << "aa" << endl;
}
};
class cat :public Animal {
public:
void speak() {
cout << "asa" << endl;
}
};
// 执行说话的函数
// 地址早绑定 在编译阶段确定函数地址
// 如果想执行cat说话 地址近不能提前绑定
/*
动态多态满足条件
1. 有继承关系
2. 子类重写父类的虚函数
动态多态使用
父类的指针或者引用 执行子类对象
重写:函数有返回值类型 函数名 参数列表 完全一致称为重写
*/
void dospeak(Animal& animal) { // Animal& animal = cat
animal.speak();
}
void test01() {
cat cat;
dospeak(cat);
}
int main() {
system("pause");
return 0;
}
- 多态的原理剖析
当子类发生重写时,子类中的虚函数表,内部会替换成子类的函数地址
当父类的指针或者引用指针子类对象的时候,发生多态
- 纯虚函数和抽象类
在多态中,通常父类中虚函数的是实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改成纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象;
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类;
- 虚析构和纯虚析构(叙述较为模糊,后续会加以更新)
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,因此需要将父类中的析构函数改成虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象;
- 都需要有具体的函数实现;
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名() {}
纯虚析构语法:virtual ~类名() = 0;
#include<iostream>
using namespace std;
#include<string>
class Animal {
public:
// 纯虚函数
virtual void speak() = 0; // 只要是纯虚函数,都是抽象类
};
class Cat :public Animal {
public:
Cat(string name) {
m_Name = new string(name);
}
virtual void speak() {
cout << "asa" << endl;
}
/* virtual ~Animal() // 虚析构
{
cout << "as" << endl;
}*/
/*
virtual ~Animal() = 0 // 纯虚析构
全局函数
Animal ::~Animal()
{
}
*/
~Cat() {
if (m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test01() {
Animal *animal = new Cat("muqier");
animal->speak();
// 父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
delete animal;
}
int main() {
system("pause");
return 0;
}
文件操作
程序运行时产生的数据都属于临时数据,程序一旦结束都会被释放,通过文件可以将数据持久化
C++中对文件操作需要包含头文件
文件分类:
- 文本文件:文件以文件的ASCII码形式存储在计算机中
- 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读取他们
操作文件的三大类:
- ofstream:写操作;
- ifstream:读操作;
- fstream:读写操作;
- 写文件:
// 写文件步骤如下
// 1. 包含头文件
#include<fstream>
// 2. 创建流对象
ofstream ofs;
// 3. 打开文件
ofs.open("文件路径", 打开方式);
// 4. 写数据
ofs << "写入的数据";
// 5. 关闭文件
ofs.close();
模式标记 | 适用对象 | 作用 |
---|---|---|
ios::in | ifstream fstream | 打开文件用于读取数据。如果文件不存在,则打开出错。 |
ios::out | ofstream fstream | 打开文件用于写入数据。如果文件不存在,则新建该文件;如果文件原来就存在,则打开时清除原来的内容。 |
ios::app | ofstream fstream | 打开文件,用于在其尾部添加数据。如果文件不存在,则新建该文件。 |
ios::ate | ifstream | 打开一个已有的文件,并将文件读指针指向文件末尾(读写指 的概念后面解释)。如果文件不存在,则打开出错。 |
ios:: trunc | ofstream | 打开文件时会清空内部存储的所有数据,单独使用时与 ios::out 相同。 |
ios::binary | ifstream ofstream fstream | 以二进制方式打开文件。若不指定此模式,则以文本模式打开。 |
ios::in | ios::out | fstream | 打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。 |
ios::in | ios::out | ofstream | 打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。 |
ios::in | ios::out | ios::trunc | fstream | 打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。 |
注意:文件打开方式可以配合使用,利用|操作符
such as:
ios::in | ios::binary
表示用二进制模式,以读取的方式打开文件。ios::out | ios::binary
表示用二进制模式,以写入的方式打开文件。
- 读文件
// 1. 包含头文件
#include<fstream>
// 2. 创建流对象
ifstream ifs;
// 3. 打开文件并判断文件是否打开成功
ifs.open("文件路径", 打开方式);
// 4. 读数据
// 四种读取方式
// (1).
char buf[1024] = { 0 };
while(ifs >> buf) {
cout << buf << endl;
}
// (2).
char buf[1024] = { 0 };
while(ifs.getline(buf, sizeof(buf))){
cout << buf << endl;
}
// (3).
string ifs;
while(getline(ifs, buf)){
cout << buf << endl;
}
// (4).
char c;
while((c = ifs.get()) != EOF) {
// EOF:the end of file
cout << c;
}
// 5. 关闭文件
ifs.close();
- 对二进制文件进行写入
二进制方式写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char * buffer, int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数。
- 对二进制文件进行读取
二进制方式读文件主要利用流对象调用成员函数read
函数原型:ostream& read(char * buffer, int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数。
C++提高编程
概念:本阶段主要针对C++泛型编程和STL技术做详细讲解,探讨更深层的使用
模板
概念:模板就是建立通用的模具,大大提高复用性
函数模板
- C++另外一种编程思想称为泛型编程,主要利用的技术就是模板;
- C++提供两种模板机制:函数模板和类模板;
作用:建立一个通用模板,其函数返回值和形参类型可以不具体指定,用一个虚拟的类型来代表
解释:
template—声明创建模板
typename—表面其后面的符号是一种数据类型,可以用class代替
T—通用的数据类型,名称可以替换,通常为大写字母
语法
template<typename T> // 函数声明和应用
such as
#include <iostream>
using namespace std;
// 函数模板
// 两个整型交换函数
//void swapint(int &a, int &b)
//{
// int temp = a;
// a = b;
// b = temp;
//}
template<typename T> // 声明一个模板,告诉编译器后面代码紧跟着T不要报错,T是通用数据类型
void swapint(T &a, T &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 2;
int b = 3;
// swapint(a, b);
// 利用函数模板
// 1. 使用1:自动类型推导
swapint(a, b); // 在此T推导出是int类型
// 2. 使用2:显示指定类型
swapint<int>(a, b);
cout << a << endl;
cout << b << endl;
return 0;
}
函数模板注意事项
- 自动推导类型,必须推导出一致的数据类型T,才可以使用;
- 模板必须要确定出T的数据类型,才可以使用;
普通函数和函数模板区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换);
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换;
- 如果利用显示指定类型的方式,可以发生隐式类型转换;
#include <iostream>
using namespace std;
// 普通函数
int myAdd01(int a, int b)
{
return a + b;
}
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
myAdd01(a, c); // myAdd01函数接收的参数为两个整型,当传入的参数不是整型的时候,系统会对于字符对应的ascll码自动转化成整型
cout << myAdd01(a, c) << endl;
}
// 模板函数
template<class T>
int myAdd01(T a, T b)
{
return a + b;
}
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
// 自动类型推导
// myAdd01(a, c); 错误
// 显示指定类型转换
myAdd01(a, c); // 可以发生隐式类型转换;
cout << myAdd01(a, c) << endl;
}
int main()
{
test01();
return 0;
}
模板函数和普通函数的调用规则
- 如果函数模板和普通模板都可以实现,优先调用普通函数;(强制调用模板用显示指定类型转换)
- 可以通过空模板参数列表来强制调用函数模板;
- 函数模板也可以发生重载;
- 如果函数模板可以产生更好的匹配,优先调用函数模板;
模板的局限性
- 模板的参数不能是一个数组;
- 模板的参数不能是自定义数据类型;
// C++提供了解决模板的参数不能是自定义数据类型的问题,提供模板的重载,为这些特定的类型提供具体化的模板
#include <iostream>
#include<string>
using namespace std;
// 对于特定类型数据
class Person
{
public:
Person(string a, int b)
{
this->m_Name = a;
this->m_Age = b;
}
// 姓名
string m_Name;
// 年龄
int m_Age;
};
void test02()
{
Person p1("asd", 23);
Person p2("any", 34);
bool ret = myCompare(p1, p2);
if(ret)
{
cout << "a == b" << endl; // 解决方法1:使用运算符重载
}
else
{
cout << "a != b" << endl;
}
}
// 利用具体化的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;
}
}
// 对比两个数据是否相等函数
template<class T>
bool myCompare(T &a, T&b)
{
if(a ==b)
{
return true;
}
else
{
return false;
}
}
void test01()
{
int a = 1;
int b = 2;
bool ret = myCompare(a, b);
if(ret)
{
cout << "a == b" << endl;
}
else
{
cout << "a != b" << endl;
}
}
int main()
{
test01();
test02();
return 0;
}
类模板
作用:建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表
语法:
template<typename T>
// 类
#include <iostream>
#include<string>
using namespace std;
// 类模板
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
m_Name = name;
m_Age = age;
}
void showPerson()
{
cout << m_Name << endl;
cout << m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
Person<string, int> p1("孙悟空", 333);
p1.showPerson();
}
int main()
{
int a = 2;
int b = 3;
swapint(a, b);
cout << a << endl;
cout << b << endl;
return 0;
}
类模板和函数模板的区别
- 类模板没有自动类型推导的使用方式;
- 类模板在模板参数列表中可以有默认参数;
#include <iostream>
#include<string>
using namespace std;
// 类模板
template<class NameType>
class Person
{
public:
Person(NameType name, int age)
{
m_Name = name;
m_Age = age;
}
void showPerson()
{
cout << m_Name << endl;
cout << m_Age << endl;
}
NameType m_Name;
int m_Age;
};
void test01()
{
Person<string> p1("孙悟空", 333);
p1.showPerson();
}
int main()
{
int a = 2;
int b = 3;
swapint(a, b);
cout << a << endl;
cout << b << endl;
return 0;
}
类模板中成员函数创建时机
- 普通类中的成员函数一开始就可以创建;
- 类模板中的成员函数在调用时才创建;
类模板对象做函数参数
传入方式:
- 指定传入的类型—直接显示对象的数据类型;
- 参数模板化—将对象中的参数变为模板进行传递;
- 整个类模板化—将这个对象类型 模板进行传递;
#include<iostream>
#include<string>
using namespace std;
// 类模板对象做函数参数
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
void showPerson()
{
cout << "姓名:" << this->m_Name << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
// 1. 指定传入的类型
void printPerson(Person<string, int>&p)
{
p.showPerson();
}
void test01()
{
Person<string, int>p("孙悟空", 100);
printPerson(p);
}
// 2. 参数模板化
template<class T1, class T2>
void printPerson2(Person<T1, T2>&p) // 查看T的类型typeid(T).name()
{
p.showPerson();
}
void test02()
{
Person<string, int>p("猪八戒", 90);
printPerson2(p);
}
// 3. 整个类模板化
template<class T>
void printPerson3(T &p) // 此时T代表的是高级数据类型对象Person
{
p.showPerson();
}
void test03()
{
Person<string, int>p("沙和尚", 80);
printPerson3(p);
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}
类模板和继承
- 当子类继承的父类是一个类模板的时候,子类在声明的时候,要指定父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指出父类中的T的类型,子类也需变为类模板
#include<iostream>
#include<string>
// 类模板和继承
template<class T>
class Base
{
T m;
};
class Son :: public Base<int> // 必须知道父类中的数据类型才能继承子类
{
};
void test01()
{
Son s1;
}
// 如果想灵活指出父类中的T的类型,子类也需变为类模板
template<class T1, T2>
class Son2 :: public Base<T2> // 表示base父类的类型为T2类型
{
T1 obj;
};
void test02()
{
Son2<int, char>p2;
}
int main()
{
test();
return 0;
}
类模板成员函数类外实现
#include<iostream>
#include<string>
// 类内实现成员函数
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
void showPerson()
{
cout << this->m_Name << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
// 类外实现成员函数
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
Person<T1, T2> :: Person(T1 name, T2 age);
{
this->m_Name = name;
this->m_Age = age;
}
void Person<T1, T2> :: showPerson()
{
cout << this->m_Name << this->m_Age << endl;
}
int main()
{
test();
return 0;
}
类模板分文件编写
Q:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
A:
- 1:直接声明包含.cpp源文件
- 2:将声明和实现写到同一个文件中,并更改后缀名为.hpp
在此处用的是第二种方法,第一种方法不做概述
person.hpp
#include<iostream>
#include<string>
using namespace std;
// 声明文件
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
// 实现文件
Person<T1, T2> :: Person(T1 name, T2 age);
{
this->m_Name = name;
this->m_Age = age;
}
void Person<T1, T2> :: showPerson()
{
cout << this->m_Name << this->m_Age << endl;
}
main.cpp
#include<iostream>
#include<string>
#include "person.cpp"
using namespace std;
int main()
{
test();
return 0;
}
STL
基本概念
- STL(standard template library,标准模板库);
- STL从广义上分为:容器(container)和算法(algorithm)迭代器(iteration);
- 容器和算法之间通过迭代器进行无缝衔接;
- STL几乎所有的代码都采用了模板类或者模板函数;
STL六大组件
- 容器,算法,迭代器,仿函数,适配器(配接器),空间配置器
- 容器:各种数据结构,如vector,list,deque,set,map等,用来存放数据;
- 算法:各种常用的算法,如sort,find,copy,for_each等;
- 迭代器:扮演了容器和算法之间的胶合剂;
- 仿函数:行为类似函数,可作为算法的某种策略;
- 适配器:一种用来修饰容器或仿函数或迭代器接口的东西;
- 空间配置器:负责空间的配置和管理;
STL容器,算法,迭代器
容器:置物之所也
STL容器就是讲运用最广泛的一些数据结构实现出来
常用的数据结构:数组,链表,树,栈,队列,集合,映射表等
这些容器分为序列式容器和关联式容器:
- 序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置;
- 关联式排序:二叉树结构,各元素之间没有严格的物理上的顺序关系;
算法:问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(algorithm)
算法分为:质变算法和非质变算法
- 质变算法:是指运算过程中更改区间内的元素的内容,例如拷贝,替换,删除等等;
- 非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找,遍历,寻找极值等等;
迭代器:容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段可以先理解为指针
种类
Input iterator(输入迭代器) | 读,不能写 | 只支持自增运算 |
---|---|---|
Output iterator(输出迭代器) ; | 写,不能读 | 只支持自增运算 |
Forward iterator(前向迭代器) | 读和写; | 只支持自增运算 |
Bidirectional iterator(双向迭代器) ; | 读和写 | 支持自增和自减运算 |
Random access iterator(随机访问迭代器) | 读和写; | 支持完整的迭代器算术运算 |
常用的有双向迭代器和随机访问迭代器
容器算法迭代器初识
STL中最常用的容器为Vector,可以理解为数组
vector存放内置数据类型
容器:vector
算法:for_each
迭代器:vector::iterator
#include <iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
void myPrint(int val)
{
cout << val << endl;
}
void test01()
{
// 创建了一个vector容器,数组
vector<int> v;
v.push_back(10); // 向容器中插入数据
v.push_back(20);
v.push_back(30);
v.push_back(40);
//通过迭代器来访问数据
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;
}
// 第三种遍历方式,遍历算法:for_each
for_each(v.begin(), v.end(), myPrint());
}
int main()
{
return 0;
}
Vector存放自定义数据类型
容器嵌套容器
#include <iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
void test01()
{
// 创建一个大容器
vector<vector <int>> v;
// 创建小容器
vector<int>v1;
vector<int>v2;
vector<int>v3;
vector<int>v4;
// 向小容器添加数据...
// 将小容器放到大容器中
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 ++)
{
// 把小容器做一个遍历输出
for(vector<int>::iterator it = *it.begin() ; it != *it.end() ; vit ++)
{
cout << *vit << endl;
}
}
}
int main()
{
return 0;
}
容器
string容器
本质:string是C++风格的字符串,而string本质上是一个类
string和char*区别
char*是一个指针
string是一个类,类内部封装了char*,管理这个字符串,是一个char*的容器
特点:
string类内部封装了很多成员方法;
such as:查找find,拷贝copy,替换replace,插入insert;
string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
- string构造函数
string(); // 创建一个空的字符串,例如string str;
string(const char* s); // 使用字符串s初始化;
string(const string& str); // 使用一个string对象初始化另一个string对象
string(int n, char c); // 使用n个字符c初始化
- string赋值操作
string& operator=(const char* s); // char*类型字符串,赋值给当前的字符串
string& operator=(const string &s); // 把字符串s赋给当前的字符串
string& operator=(char s); // 字符赋给当前的字符串
string& assign(const char* s); // 把字符串s赋给当前的字符串
string& assign(const char*, int n); // 把字符串s的前n个字符赋给当前的字符串
string& assign(const string &s); // 把字符串s赋给当前的字符串
string& assign(int n, char c); // 用n个字符c赋给当前的字符串
string字符串拼接
string& operator+=(const char* s); // 重载+=操作符
string& operator+=(const char s); // 重载+=操作符
string& operator+=(const string& s); // 重载+=操作符
string& append(const char* s); // 把字符串s连接到当前字符串结尾
string& append(const char* s, int n); // 把字符串s的前n个字符连接到当前字符串结尾
string& append(const string &s); // 同operator+=(const string& str)
string& append(const string &s, int pos, int n); // 字符串s中从pos开始的n个字符连接到字符串结尾
string查找和替换
int find(const string& str, int pos = 0) const; //查找str第一次出现位置,从pos开始查找
int find(const char* s, int pos = 0) const; //查找s第一次出现位置,从pos开始查找
int find(const char* s, int pos, int n) const; //从pos位置查找s的前n个字符第一次位置
int find(const char c, int pos = 0) const; //查找字符c第一次出现位置
int rfind(const string& str, int pos = npos) const; //查找str最后一次位置,从pos开始查找
int rfind(const char* s, int pos = npos) const; //查找s最后一次出现位置,从pos开始查找
int rfind(const char* s, int pos, int n) const; //从pos查找s的前n个字符最后一次位置
int rfind(const char c, int pos = 0) const; //查找字符c最后一次出现位置
string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str
string& replace(int pos, int n,const char* s); //替换从pos开始的n个字符为字符串s
string字符串比较
int compare(const string &s) const; //与字符串s比较
int compare(const char *s) const; //与字符串s比较
string字符存取
char& operator[](int n); //通过[]方式取字符
char& at(int n); //通过at方法获取字符
string插入和删除
string& 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个字符
string子串
string substr(int pos = 0, int n = npos) const; //返回由pos开始的n个字符组成的字符串
vector容器
vector构造函数
vector() // 创建一个空vector
vector(int nSize) // 创建一个vector,元素个数为nSize
vector(int nSize,const t& t) // 创建一个vector,元素个数为nSize,且值均为t
vector(const vector&) // 复制构造函数
vector(begin,end) // 复制[begin,end)区间内另一个数组的元素到vector中
vector增加函数
void push_back(const T& x) // 向量尾部增加一个元素X
iterator insert(iterator it,const T& x) // 向量中迭代器指向元素前增加一个元素x
iterator insert(iterator it,int n,const T& x) // 向量中迭代器指向元素前增加n个相同的元素x
iterator insert(iterator it,const_iterator first,const_iterator last) // 向量中迭代器指向元素前插入另一个相同类型向量的[first,last)间的数据
vector删除函数
iterator erase(iterator it) // 删除向量中迭代器指向元素
iterator erase(iterator first,iterator last) // 删除向量中[first,last)中元素
void pop_back() // 删除向量中最后一个元素
void clear() // 清空向量中所有元素
vector遍历函数
reference at(int pos) // 返回pos位置元素的引用
reference front() // 返回首元素的引用
reference back() // 返回尾元素的引用
iterator begin() // 返回向量头指针,指向第一个元素
iterator end() // 返回向量尾指针,指向向量最后一个元素的下一个位置
reverse_iterator rbegin() // 反向迭代器,指向最后一个元素
reverse_iterator rend() // 反向迭代器,指向第一个元素之前的位置
vector判断函数
bool empty() const // 判断向量是否为空,若为空,则向量中无元素
vector大小函数
int size() const // 返回向量中元素的个数
int capacity() const // 返回当前向量所能容纳的最大元素值
int max_size() const // 返回最大可允许的vector元素数量值
vector其他函数
void swap(vector&) // 交换两个同类型向量的数据
void assign(int n,const T& x) // 设置向量中第n个元素的值为x
void assign(const_iterator first,const_iterator last) // 向量中[first,last)中元素设置成当前向量元素
vector看着清楚
push_back // 在数组的最后添加一个数据
pop_back // 去掉数组的最后一个数据
at // 得到编号位置的数据
begin // 得到数组头的指针
end // 得到数组的最后一个单元+1的指针
front // 得到数组头的引用
back // 得到数组的最后一个单元的引用
max_size // 得到vector最大可以是多大
capacity // 当前vector分配的大小
size // 当前使用数据的大小
resize // 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值
reserve // 改变当前vecotr所分配空间的大小
erase // 删除指针指向的数据项
clear // 清空当前的vector
rbegin // 将vector反转后的开始指针返回(其实就是原来的end-1)
rend // 将vector反转构的结束指针返回(其实就是原来的begin-1)
empty // 判断vector是否为空
swap // 与另一个vector交换数据
deque容器
deque构造函数
deque() // 创建一个空deque
deque(int nSize) // 创建一个deque,元素个数为nSize
deque(int nSize,const T& t) // 创建一个deque,元素个数为nSize,且值均为t
deque(const deque &) // 复制构造函数
deque增加函数
void push_front(const T& x) // 双端队列头部增加一个元素X
void push_back(const T& x) // 双端队列尾部增加一个元素x
iterator insert(iterator it,const T& x) // 双端队列中某一元素前增加一个元素x
void insert(iterator it,int n,const T& x) // 双端队列中某一元素前增加n个相同的元素x
void insert(iterator it,const_iterator first,const_iteratorlast) // 双端队列中某一元素前插入另一个相同类型向量的[forst,last)间的数据
deque删除函数
Iterator erase(iterator it) // 删除双端队列中的某一个元素
Iterator erase(iterator first,iterator last) // 删除双端队列中[first,last)中的元素
void pop_front() // 删除双端队列中最前一个元素
void pop_back() // 删除双端队列中最后一个元素
void clear() // 清空双端队列中最后一个元素
deque遍历函数
reference at(int pos) // 返回pos位置元素的引用
reference front() // 返回手元素的引用
reference back() // 返回尾元素的引用
iterator begin() // 返回向量头指针,指向第一个元素
iterator end() // 返回指向向量中最后一个元素下一个元素的指针(不包含在向量中)
reverse_iterator rbegin() // 反向迭代器,指向最后一个元素
reverse_iterator rend() // 反向迭代器,指向第一个元素的前一个元素
deque判断函数
bool empty() const // 向量是否为空,若true,则向量中无元素
deque大小函数
Int size() const // 返回向量中元素的个数
int max_size() const // 返回最大可允许的双端对了元素数量值
deque其他函数
void swap(deque&) // 交换两个同类型向量的数据
void assign(int n,const T& x) // 向量中第n个元素的值设置为x
注明::下面的知识点简建议去了解数据结构
stack(栈)容器
// 构造函数
stack<T> stk; // stack采用模板类实现,stack对象的默认构造形式
stack(const stack &stk); // 拷贝构造函数
// 赋值操作
stack& operator=(const stack &stk); // 重载等号操作符
// 数据存取
push(elem); // 向栈顶添加元素
pop(); // 从栈顶移除第一个元素
top(); // 返回栈顶元素
// 大小操作
empty(); // 判断栈堆是否为空
size(); // 返回栈的大小
queue(队列)容器
// 构造函数
queue<T> que; // queue采用模板类实现,queue对象的默认构造形式
queue(const queue &que); // 拷贝构造函数
// 赋值操作
queue& operator=(const queue &que); // 重载等号操作符
// 数据存取
push(elem); // 向队尾添加元素
pop(); // 从队头移除第一个元素
front(); // 返回第一个元素
back(); // 返回最后一个元素
// 大小操作
empty(); // 判断队列是否为空
size(); // 返回队的大小
list(链表)容器
set容器
set的基本操作
s.begin() // 返回指向第一个元素的迭代器
s.clear() // 清除所有元素
s.count() // 返回某个值元素的个数
s.empty() // 如果集合为空,返回true(真)
s.end() // 返回指向最后一个元素之后的迭代器,不是最后一个元素
s.equal_range() // 返回集合中与给定值相等的上下限的两个迭代器
s.erase() // 删除集合中的元素
s.find() // 返回一个指向被查找到元素的迭代器
s.get_allocator() // 返回集合的分配器
s.insert() // 在集合中插入元素
s.lower_bound() // 返回指向大于(或等于)某值的第一个元素的迭代器
s.key_comp() // 返回一个用于元素间值比较的函数
s.max_size() // 返回集合能容纳的元素的最大限值
s.rbegin() // 返回指向集合中最后一个元素的反向迭代器
s.rend() // 返回指向集合中第一个元素的反向迭代器
s.size() // 集合中元素的数目
s.swap() // 交换两个集合变量
s.upper_bound() // 返回大于某个值元素的迭代器
s.value_comp() // 返回一个用于比较元素间的值的函数
创建set集合对象
set<int>s
元素的插入
s.insert(1);
s.insert(2);
map容器
声明及初始化
map<key, value> m;//创建一个名为m的空map对象,其键和值的类型分别为key和value。
map<key, value> m(m2);//创建m2的副本m,m与m2必须有相同的键类型和值类型。
map<key, value> m(b,e);//创建map类型的对象m,存储迭代器b和e标记的范围内所有元素的副本,元素的类型必须能转换为pair
map<key, value, comp> mp;//comp可选,为键值对存放策略,即键的比较函数,默认标准库使用键类型定义的 < 操作符来实现键的比较。所用的比较函数必须在键类型上定义严格的弱排序,可将其理解为键类型数据上的“小于”关系。在实际应用中,键类型必须能定义 < 操作符。对于键类型,其唯一的约束就是必须支持 < 操作符。
map成员函数
m.count(k); // 返回m中键值等于k的元素的个数。
m.find(k); // 如果m中存在按k索引的元素,则返回指向该元素的迭代器。如果不存在,则返回结束游标end()。
map删除元素
m.erase(k); // 删除m中键为k的元素,返回size_type类型的值,表示删除元素的个数。
m.erase(p); // 从m中删除迭代器p所指向的元素,p必须指向m中确实存在的元素,而且不能等于m.end(),返回void类型。
m.erase(iterator first, iterator last); // 删除一个范围,返回void类型。
map插入元素
m.insert(e) ;
/*e是一个用在m上的value_type类型的值。如果键e.first不在m中,则插入一个值为e.second的新元素;如果该键在m中已存在,那么不进行任何操作。该函数返回一个pair类型对象,包含指向键为e.first的元素的map迭代器,以及一个bool类型的对象,表示是否插入了该元素。
m.insert(beg, end);
beg和end是标记元素范围的迭代器,对于该范围内的所有元素,如果它的键在m中不存在,则将该键及其关联的值插入到m。 返回void类型。
m.insert(iter, e);
e是value_type类型的值,如果e.first不在m中,则创建新元素,并以迭代器iter为起点搜索新元素存储的位置,返回一个迭代器,指向m中具有给定键的元素。 在添加新的map元素时,使用insert成员可避免使用下标操作符带来的副作用:不必要的初始化。*/
仿函数
函数对象
-
重载函数调用操作符的类,其对象常称为函数对象;
-
函数对象使用重载的()类时,行为类似函数调用,也叫仿函数;
特点:
-
函数对象在使用的时,可以像普通函数那样调用,可以有返回值;
-
函数对象超出普通函数的概念,函数对象可以有自己的状态;
-
函数对象可以作为参数传递;
// 函数对象在使用的时,可以像普通函数那样调用,可以有返回值;
class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
}
void test()
{
cout << MyAdd myadd(10,23) << endl;
}
// 函数对象超出普通函数的概念,函数对象可以有自己的状态;
class MyAdd
{
public:
MyAdd()
{
this->count = 0;
}
int operator()(int v1, int v2)
{
// return v1 + v2;
this->count++;
}
int count; // 内部自己的状态
}
// 函数对象可以作为参数传递;
void doprint(MyPrint&m)
{
}
谓词
-
返回bool类型的仿函数被称为谓词
-
如果operator()接受的是一个参数,那么叫做一元谓词
-
如果operator()接受的是二个参数,那么叫做二元谓词
内建函数对象
分类:算术仿函数,关系仿函数,逻辑仿函数
用法:
这些仿函数所产生的对象,用法和一般函数完全相同
使用内建函数对象,需要引入头文件#include<functional>
算术仿函数
// 其中negate为一元,其他为二元
加:template<class T> T plus<T>;
减:template<class T> T minus<T>;
乘:template<class T> T multiplies<T>;
除:template<class T> T divides<T>;
模取:template<class T> T modulus<T>;
取反:template<class T> T negate<T>;
// the example:negate
void test()
{
negate<int>n;
n(50);
}
// the second example
void test()
{
plus<int>p; // 默认调用的是两个相同类型的数据
p(20, 30)
}
关系仿函数
等于:template<class T> T equal_to<T>
不等于:template<class T> T not_equal_to<T>
大于:template<class T> T greater<T>
大于等于:template<class T> T greater_equal<T>
小于:template<class T> T less<T>
小于等于:template<class T> T less_equal<T> // less_equal<int>()
逻辑仿函数
与:template<class T> bool logical_and<T>
或:template<class T> bool logical_or<T>
否:template<class T> bool logical_no<T>
算法
主要有三个头文件
<algorithm>
<function>
<numeric>
常见遍历算法
for_each:遍历容器
for_each(iterator beg, iterator end, _func);
/*
beg:开始迭代器
end:结束迭代器
_func:函数或者函数对象
*/
// give a example
#include<iostream>
#include<slgorithm>
#include<vector>
using namespace std;
// declare functions
void printdata(int);
void vec();
void iterator(vector);
void printdata(int val)
{
cout << val << endl;
}
void vec() // define a vector
{
vector<int>v;
for(int i = 0; i < 10; i++)
{
v.push_back(i)
}
}
void iterator(vector<int>& v) // define a iterator for for_each
{
for_each(v.begin(), v.end(), printdata)
}
transform:搬运容器到另一个容器中
tranform(iterator beg1, iterator end1, iterator beg2, _func);
/*
beg:源容器开始迭代器
end:源容器结束迭代器
beg2:目标容器开始迭代器
_func:函数或者函数对象
*/
find:查找元素
// 功能:查找指定元素,找到返回指定元素的迭代器,没有返回结束迭代器end()
find(v.begin(), v.end(), value)
/*
beg:开始迭代器
end:结束迭代器
value:查找的元素
*/
vector<int>::iterator it = find(v.begin(), v.end(), value) // 基本数据类型
if(it == end())
{
cout << "yes" << endl;
}
bool operator==(const Person & p) // 自定义数据类型
{
if(this->m_name = name && this->m_age = age)
{
return true;
}
else
{
return false;
}
}
find_if:按条件查找元素
adjacent_find:查找相邻重复元素
adjacent_find(iterator beg, iterator end);
//查找相邻重复元素,返回相邻元素的第一个位置的迭代器
//beg开始迭代器
//end结束迭代器
such as
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void test01()
{
vector<int>v1;
v.push_back(0);
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(3);
vector<int>::iterator pos = adjacent_find(v1.begin(), v1.end())
if (pos = v1.end())
{
cout << "找到了" << endl;
}
else
{
cout << "找不到" << endl;
}
}
int main()
{
test01();
return 0;
}
binary_search:二分查找法
// 查找指定元素是否存在,存在返回true,否则返回false
bool binary_search(iterator beg, iterator end, value);
// 在无序中不可使用
// beg开始迭代器
// end结束迭代器
// 查找的元素
such as
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void test01()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
bool ret = binary_search(v.begin(), v.end(), 9);
if(ret)
{
cout << "找到了" << endl;
}
else
{
cout << "没找到" << endl;
}
}
int main()
{
return 0;
}
count:统计元素个数
count(iterator begin, iterator end, value);
// value 统计的元素
count_if:按条件统计元素个数
count_if(iterator beg, iterator end, _Pred);
// 按条件统计元素出现次数
// beg开始迭代器
// end结束迭代器
// _Pred谓词
such as
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Greater4
{
public:
bool operator()(int val)
{
return val >= 4;
}
}
void test01()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
int num = count_if(v.begin(), v.end(), Greater4())
cout << "在元素中大于2的数据为" << num << "个" << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
sort:对容器内元素进行排序
sort(iterator beg, iterator end)
such as
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void test01()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
sort(v.begin(), v.end())
}
int main()
{
test01();
system("pause");
return 0;
}
random_shuffle:指定范围内的元素随机调整次序
random_shuffle(iterator beg, iterator end)
merge:容器元素合并,并存储到另一容器中
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, dest);
// 两个容器必须是有序的
// dest 目标容器开始迭代器
reverse:反转指定范围内的元素
reverse(iterator beg, iterator end);
copy:容器内指定范围内的元素拷贝到另一容器中
copy(iterator beg, iterator end, iterator dest);
replace:将容器内指定范围的旧元素修改为新元素
replace(iterator beg, iterator end, oldvalue, newvalue);
replace_if:将区间内满足条件的元素,替换成指定元素
replace_if(iterator beg, iterator end, _Pred, newvalue);
swap:互换两个容器的元素
swap(container c1, container c2);
算术生成方法
accumulate:计算容器元素累计综合
#include <numeric>
accumulate(iterator beg, iterator end, value);
// value 起始累加值
fill:向容器中填充指定的要素
fill(iterator beg, iterator end,value)
set_intersection:求两个容器的交集
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
set_union:求两个容器的并集
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
set_difference:求两个容器的差集
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
STL模板我后续会慢慢的更新,不过速度会稍慢,进度快的小朋友们可以直接参考查阅侯捷的《STL源码解析》