C++学习笔记(以供复习查阅)

news/2024/5/14 23:01:08/文章来源:https://blog.csdn.net/xijuezhu8128/article/details/129375472

视频链接
代码讲义
提取密码: 62bb

文章目录

  • 1、C++基础
    • 1.1 C++初识
      • (1) 第一个C++程序
      • (2)注释
      • (3)变量
      • (4)常量
      • (5)关键字
      • (6)标识符命名规则
    • 1.2 数据类型
      • (1)整型
      • (2)浮点型
      • (3)字符型
      • (3)字符串型
      • (4)布尔型
      • (5)转义字符
      • (6)sizeof关键字
    • 1.3 运算符
      • (1)算术运算符
      • (2)赋值运算符
      • (3)比较运算符
      • (4)逻辑运算符
    • 1.4 程序流结构
      • (1)选择结构-if
      • (2)选择结构-三目运算符
      • (3)选择结构-switch
      • (4)循环结构-while
      • (5)循环结构-do while
      • (6)循环结构-for
      • (7)跳转语句-break
      • (8)跳转语句-continue
    • 1.5 I/O
      • (1)头文件
      • (2)标准输出流(cout)
      • (3)标准输入流(cin)
      • (4)标准错误流(cerr)
      • (5)标准日志流(clog)
      • (6)文件输出(写)
      • (7)文件输入(读)
    • 1.6 数组
      • (1)一维数组
      • (2)二维数组
    • 1.7 函数
      • (1)定义
      • (2)调用
      • (3)值传递
      • (4)声明
      • (5)分文件编写
      • (6)默认参数
      • (7)占位参数
      • (8)函数重载
    • 1.8 指针
      • (1)指针基本概念
      • (2)空指针和野指针
      • (3)const修饰指针
      • (4)指针访问数组元素
      • (5)地址传递
    • 1.9 内存分区模型
      • (1)内存四区
      • (2)程序运行前
      • (3)程序运行后
      • (4)new操作符
    • 1.10 引用
      • (1)定义
      • (2)引用传递
      • (3)引用做函数返回值
      • (4)引用的本质
      • (5)常量引用
    • 1.11 结构体
      • (1)结构体定义和创建
      • (2)结构体数组
      • (3)结构体指针
  • 2、C++面向对象编程
    • 2.1 封装
      • (1)封装的意义
      • (2)类定义
      • (3)对象实例化
      • (4)访问成员
      • (5)struct和class区别
    • 2.2 对象的初始化和清理
      • (1)构造函数
      • (2)析构函数
      • (3)构造函数分类及调用
      • (4)拷贝构造函数调用时机
      • (5)构造函数调用规则
      • (7)深拷贝与浅拷贝
      • (8)初始化列表
      • (9)类对象作为类成员
      • (10)静态成员
    • 2.3 C++对象模型和this指针
      • (1)成员变量和成员函数存储方式
      • (2)this指针
      • (3)空指针访问成员函数
      • (4)常函数和常对象
    • 2.4 友元
      • (1)全局函数做友元
      • (2)类做友元
      • (3)成员函数做友元
    • 2.5 运算符重载
      • (1)加号运算符重载
      • (2)左移运算符重载
      • (3)递增运算符重载
      • (4)赋值运算符重载
      • (5)关系运算符重载
      • (6)函数调用运算符重载
    • 2.6 继承
      • (1)语法
      • (2)继承方式
      • (3)继承中构造和析构顺序
      • (4)继承同名成员处理方式
      • (5)继承同名静态成员处理方式
      • (6)多继承
      • (7)菱形继承
    • 2.7 多态
      • (1)概念
      • (2)多态满足条件和使用
      • (3)纯虚函数和抽象类
      • (4)虚析构和纯虚析构
  • 3、C++泛型编程
    • 3.1 函数模版
      • (1)函数模版语法
      • (2)普通函数与函数模版的区别
      • (3)同名普通函数与函数模版的调用规则
      • (4)函数模版的局限性
    • 3.2 类模版
      • (1)类模版语法
      • (2) 类模板与函数模板区别
      • (3)类模板中成员函数创建时机
      • (4)类模板对象做函数参数
      • (5)类模板与继承
      • (6)类模板成员函数类外实现
      • (7)类模板分文件编写
      • (8)类模版与友元
  • 4、STL初识
    • 4.1 STL的诞生
    • 4.2 STL基本概念
    • 4.3 STL六大组件
    • 4.4 STL中容器、算法、迭代器
    • 4.5 容器算法迭代器初识
      • (1) vector存放内置数据类型
      • (2) Vector存放自定义数据类型
      • (3) Vector容器嵌套容器
  • 5、 STL- 常用容器
    • 5.1 string容器
      • (1) string基本概念
      • (2) string构造函数
      • (3) string赋值操作
      • (4) string字符串拼接
      • (5) string查找和替换
      • (6) string字符串比较
      • (7) string字符存取
      • (8) string插入和删除
      • (9) string子串
    • 5.2 vector容器
      • (1) vector基本概念
      • (2) vector构造函数
      • (3) vector赋值操作
      • (4) vector容量和大小
      • (5)vector插入和删除
      • (6) vector数据存取
      • (7) vector互换容器
      • (8) vector预留空间
    • 5.3 deque容器
      • (1) deque容器基本概念
      • (2) deque构造函数
      • (3) deque赋值操作
      • (4) deque大小操作
      • (5) deque 插入和删除
      • (6) deque 数据存取
      • (7) deque 排序
    • 5.4 stack容器
      • (1) stack 基本概念
      • (2) stack 常用接口
    • 5.5 queue 容器
      • (1) queue 基本概念
      • (2) queue 常用接口
    • 5.6 list容器
      • (1) list基本概念
      • (2) list构造函数
      • (3) list 赋值和交换
      • (4) list 大小操作
      • (5) list 插入和删除
      • (6) list 数据存取
      • (7) list 反转和排序
      • (8) 排序案例
    • 5.7 set/ multiset 容器
      • (1) set基本概念
      • (2) set构造和赋值
      • (3) set大小和交换
      • (4) set插入和删除
      • (5) set查找和统计
      • (6) set和multiset区别
      • (7) pair对组创建
      • (8) set容器排序
    • 5.8 map/ multimap容器
      • (1) map基本概念
      • (2) map构造和赋值
      • (3) map大小和交换
      • (4) map插入和删除
      • (5) map查找和统计
      • (6) map容器排序
  • 6、 STL- 函数对象
    • 6.1 函数对象
      • (1) 函数对象概念
      • (2) 函数对象使用
    • 6.2 谓词
      • (1) 谓词概念
      • (2) 一元谓词
      • (3) 二元谓词
    • 6.3 内建函数对象
      • (1) 内建函数对象意义
      • (2) 算术仿函数
      • (3) 关系仿函数
      • (4)逻辑仿函数
  • 7、 STL- 常用算法
    • 7.1 常用遍历算法
      • (1) for_each
      • (2) transform
    • 7.2 常用查找算法
      • (1) find
      • (2) find_if
      • (3) adjacent_find
      • (4) binary_search
      • (5) count
      • (6) count_if
    • 7.3 常用排序算法
      • (1) 5.3.1 sort
      • (2) random_shuffle
      • (3) merge
      • (4) reverse
    • 7.4 常用拷贝和替换算法
      • (1) copy
      • (2) replace
      • (3) replace_if
      • (4) swap
    • 7.5 常用算术生成算法
      • (1) accumulate
      • (2) fill
    • 7.6 常用集合算法
      • (1) set_intersection
      • (2) set_union
      • (3) set_difference

1、C++基础

1.1 C++初识

(1) 第一个C++程序

  • 第一个程序

    #include<iostream>
    using namespace std;int main() {cout << "Hello world" << endl;return 0;
    }
    
    • 标准输入输出是一个流模型,使用时需包含<iostream>头文件;
    • 大括号{}用来标记一组代码,称为代码块,也叫复合语句;
  • 运行程序-Linux

    • Linux每个发行版都会自带GUN C++编译器,将上面的程序保存到hello.cpp,可以通过以下命令完成编译:

      g++ hello.cpp
      
    • 编译出的的二进制文件默认为a.out,也可以通过如下命令指定输出文件的名字:

      g++ hello.cpp -o hello
      

(2)注释

  • 单行注释: // 描述信息
  • 多行注释: /* 描述信息 */

(3)变量

  • 作用:给一段指定的内存空间起名,方便操作这段内存;
  • 语法:数据类型 变量名 = 初始值;
    • 举例:int a = 10;
    • 注意:C++在创建变量时,必须给变量一个初始值,否则会报错

(4)常量

  • 作用:用于记录程序中不可更改的数据;
  • #define 宏常量: #define 常量名 常量值
    • 举例:#define day 7
  • const修饰的变量: const 数据类型 常量名 = 常量值;
    • 举例:const int month = 12;

(5)关键字

  • 在定义变量或者常量时候,不要使用以下关键字;
asmdoifreturntypedef
autodoubleinlineshorttypeid
booldynamic_castintsignedtypename
breakelselongsizeofunion
caseenummutablestaticunsigned
catchexplicitnamespacestatic_castusing
charexportnewstructvirtual
classexternoperatorswitchvoid
constfalseprivatetemplatevolatile
const_castfloatprotectedthiswchar_t
continueforpublicthrowwhile
defaultfriendregistertrue
deletegotoreinterpret_casttry

(6)标识符命名规则

  • C++给标识符(变量、常量)命名时应遵循以下规则规则:

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

1.2 数据类型

(1)整型

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

(2)浮点型

数据类型占用空间有效数字范围
float4字节7位有效数字
double8字节15~16位有效数字

(3)字符型

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

  • 语法:char ch = 'a';

    • 在显示字符型变量时,用单引号将字符括起来,不要用双引号
    • 单引号内只能有一个字符,不可以是字符串
    • C和C++中字符型变量只占用1个字节
    • 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元
  • ASCII码表格:

    ASCII控制字符ASCII字符ASCII字符ASCII字符
    0NUT32(space)64@96
    1SOH33!65A97a
    2STX34"66B98b
    3ETX35#67C99c
    4EOT36$68D100d
    5ENQ37%69E101e
    6ACK38&70F102f
    7BEL39,71G103g
    8BS40(72H104h
    9HT41)73I105i
    10LF42*74J106j
    11VT43+75K107k
    12FF44,76L108l
    13CR45-77M109m
    14SO46.78N110n
    15SI47/79O111o
    16DLE48080P112p
    17DCI49181Q113q
    18DC250282R114r
    19DC351383S115s
    20DC452484T116t
    21NAK53585U117u
    22SYN54686V118v
    23TB55787W119w
    24CAN56888X120x
    25EM57989Y121y
    26SUB58:90Z122z
    27ESC59;91[123{
    28FS60<92/124|
    29GS61=93]125}
    30RS62>94^126`
    31US63?95_127DEL

(3)字符串型

  • C风格字符串: char 变量名[] = "字符串值"
    • 举例:char str1[] = "hello world";
  • C++风格字符串: string 变量名 = "字符串值"
    • 举例:string str = "hello world";
    • 需要加入头文件#include <string>

(4)布尔型

  • 作用:布尔数据类型代表真或假的值
  • bool类型只有两个值:
    • true — 真(本质是1)
    • false — 假(本质是0)
  • bool类型占1个字节大小

(5)转义字符

  • 作用:用于表示一些不能显示出来的ASCII字符;

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

(6)sizeof关键字

  • 作用:利用sizeof关键字可以统计数据类型所占内存大小
  • 语法: sizeof( 数据类型 / 变量)
  • 举例:sizeof(short)

1.3 运算符

(1)算术运算符

  • 作用:用于处理四则运算

    运算符术语示例结果
    +正号+33
    -负号-3-3
    +10 + 515
    -10 - 55
    *10 * 550
    /10 / 52
    %取模(取余)10 % 31
    ++前置递增a=2; b=++a;a=3; b=3;
    ++后置递增a=2; b=a++;a=3; b=2;
    前置递减a=2; b=–a;a=1; b=1;
    后置递减a=2; b=a–;a=1; b=2;
  • 在除法运算中,除数不能为0;

  • 只有整型变量可以进行取模运算;

  • 前缀运算符返回修改后的值,后缀运算符返回修改前的值;

(2)赋值运算符

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

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

(3)比较运算符

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

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

(4)逻辑运算符

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

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

1.4 程序流结构

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

(1)选择结构-if

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

  • 多行格式if语句

    if(条件) 
    { 条件满足执行的语句 }
    else
    { 条件不满足执行的语句 }
    
  • 多条件的if语句

    if(条件1)
    { 条件1满足执行的语句 }
    else if(条件2)
    { 条件2满足执行的语句 }
    ... 
    else
    { 都不满足执行的语句 }
    
  • 注意:if条件表达式后不要加分号

(2)选择结构-三目运算符

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

(3)选择结构-switch

  • 作用:执行多条件分支语句
  • 语法:
    switch(表达式)
    {case 结果1:执行语句;break;case 结果2:执行语句;break;...default:执行语句;break;
    }
    
  • 注意1:switch语句中表达式类型只能是整型或者字符型;
  • 注意2:case里如果没有break,那么程序会一直向下执行;

(4)循环结构-while

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

  • 语法:

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

  • 在执行循环语句时候,程序必须提供跳出循环的出口,否则出现死循环;

(5)循环结构-do while

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

  • 语法:

    do
    { 循环语句 
    } while(循环条件);
    
  • 与while的区别在于:do…while会先执行一次循环语句,再判断循环条件;

(6)循环结构-for

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

(7)跳转语句-break

  • 作用:用于跳出选择结构或者循环结构
  • break使用的时机:
    • 出现在switch条件语句中,作用是终止case并跳出switch
    • 出现在循环语句中,作用是跳出当前的循环语句
    • 出现在嵌套循环中,跳出最近的内层循环语句

(8)跳转语句-continue

  • 作用:在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环;
  • continue并没有使整个循环终止,而break会跳出循环;

1.5 I/O

(1)头文件

数据类型说明
iostream定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。
ofstream该数据类型表示输出文件流,用于创建文件并向文件写入信息。
ifstream该数据类型表示输入文件流,用于从文件读取信息。
fstream该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。

(2)标准输出流(cout)

  • 预定义的对象 cout 是 iostream 类的一个实例;
  • cout 对象结合流插入运算符 << 用于向显示器输出信息;
  • 流插入运算符 << 在一个语句中可以链式使用;
  • endl 用于在行末添加一个换行符;
  • 举例:
    cout << "您的名称是: " << name << endl;
    

(3)标准输入流(cin)

  • 预定义的对象 cin 是 iostream 类的一个实例;

  • cin 结合流提取运算符从键盘读取输入;

  • 流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:

    cin >> name >> age;
    

    这相当于下面两个语句:

    cin >> name;
    cin >> age;
    

(4)标准错误流(cerr)

  • 预定义的对象 cerr 是 iostream 类的一个实例。
  • cerr 对象是非缓冲的,每个流插入到 cerr 都会立即输出。
  • cerr 也是与流插入运算符 << 结合使用的,如下所示:
    cerr << "Error message : " << str << endl;
    

(5)标准日志流(clog)

  • 预定义的对象 clog 是 iostream 类的一个实例。

  • clog 对象是缓冲的,这意味着每个流插入到 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出;

  • clog 也是与流插入运算符 << 结合使用的,如下所示:

    clog << "Error message : " << str << endl;
    

(6)文件输出(写)

  • 写文件步骤如下:

    // 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 <fstream>void test01()
    {ofstream ofs;ofs.open("test.txt", ios::out);ofs << "姓名:张三" << endl;ofs << "性别:男" << endl;ofs << "年龄:18" << endl;ofs.close();
    }int main() {test01();return 0;
    }
    

(7)文件输入(读)

  • 示例

    #include <fstream>
    #include <string>
    void test01()
    {ifstream ifs;ifs.open("test.txt", ios::in);if (!ifs.is_open()){cout << "文件打开失败" << endl;return;}//第一种方式//char buf[1024] = { 0 };//while (ifs >> buf)//{//	cout << buf << endl;//}//第二种//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){cout << c;}ifs.close();}int main() {test01();return 0;
    }
    

1.6 数组

  • 数组中的每个数据元素都是相同的数据类型
  • 数组是由连续的内存位置组成的

(1)一维数组

  • 一维数组定义的三种方式:

    • 数据类型 数组名[ 数组长度 ];
      • int score[10];
    • 数据类型 数组名[ 数组长度 ] = { 值1,值2 ...};
      //如果{}内不足10个数据,剩余数据用0补全
      int score2[10] = { 100, 90,80,70,60,50,40,30,20,10 };
      
    • 数据类型 数组名[ ] = { 值1,值2 ...};
      • int score3[] = { 100,90,80,70,60,50,40,30,20,10 };
  • 一维数组操纵

    • 统计数组在内存中的长度
      int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };cout << "整个数组所占内存空间为: " << sizeof(arr) << endl;
      cout << "每个元素所占内存空间为: " << sizeof(arr[0]) << endl;
      cout << "数组的元素个数为: " << sizeof(arr) / sizeof(arr[0]) << endl;
      
    • 获取数组在内存中的首地址
      // 直接打印数组名,可以查看数组所占内存的首地址
      cout << "数组首地址为: " << (int)arr << endl;
      cout << "数组中第一个元素地址为: " << (int)&arr[0] << endl;
      cout << "数组中第二个元素地址为: " << (int)&arr[1] << endl;
      

(2)二维数组

  • 二维数组定义的四种方式

    • 数据类型 数组名[ 行数 ][ 列数 ];
      • int arr[2][3];
    • 数据类型 数组名[ 行数 ][ 列数 ] = { {数据1,数据2 } ,{数据3,数据4 } };
      int arr2[2][3] =
      {{1,2,3},{4,5,6}
      };
      
    • 数据类型 数组名[ 行数 ][ 列数 ] = { 数据1,数据2,数据3,数据4};
      • int arr3[2][3] = { 1,2,3,4,5,6 };
    • 数据类型 数组名[ ][ 列数 ] = { 数据1,数据2,数据3,数据4};
      • int arr4[][3] = { 1,2,3,4,5,6 };
  • 二维数组操纵

    • 统计数组在内存中的长度
      int arr[2][3] =
      {{1,2,3},{4,5,6}
      };cout << "二维数组大小: " << sizeof(arr) << endl;
      cout << "二维数组一行大小: " << sizeof(arr[0]) << endl;
      cout << "二维数组元素大小: " << sizeof(arr[0][0]) << endl;cout << "二维数组行数: " << sizeof(arr) / sizeof(arr[0]) << endl;
      cout << "二维数组列数: " << sizeof(arr[0]) / sizeof(arr[0][0]) << endl;
      
    • 获取数组在内存中的首地址
      // 直接打印数组名,可以查看数组所占内存的首地址
      cout << "二维数组首地址:" << arr << endl;
      cout << "二维数组第一行地址:" << arr[0] << endl;
      cout << "二维数组第二行地址:" << arr[1] << endl;cout << "二维数组第一个元素地址:" << &arr[0][0] << endl;
      cout << "二维数组第二个元素地址:" << &arr[0][1] << endl;
      

1.7 函数

(1)定义

  • 语法:

    返回值类型 函数名 (参数列表)
    {函数体语句return表达式
    }
    
  • return类型要和返回值类型一致;

(2)调用

  • 语法: 函数名(参数)
  • 函数定义里小括号内称为形参,函数调用时传入的参数称为实参;

(3)值传递

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

(4)声明

  • 语法:返回值类型 函数名 (参数列表);
  • 函数的声明可以多次,但是函数的定义只能有一次;

(5)分文件编写

  • 函数分文件编写一般有4个步骤:
    1. 创建后缀名为.h的头文件
    2. 创建后缀名为.cpp的源文件
    3. 在头文件中写函数的声明
    4. 在源文件中写函数的定义

(6)默认参数

  • 函数的形参列表中的形参是可以有默认值的。

  • 语法:

    返回值类型  函数名 (数据类型 参数= 默认值)
    {return ;
    }
    
  • 示例:

    int func(int a, int b = 10, int c = 10) {return a + b + c;
    }//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
    //2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
    int func2(int a = 10, int b = 10);
    int func2(int a, int b) {return a + b;
    }int main() {cout << "ret = " << func(20, 20) << endl;cout << "ret = " << func(100) << endl;return 0;
    }
    

(7)占位参数

  • C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
  • 语法: 返回值类型 函数名 (数据类型){}
    返回值类型  函数名 (数据类型)
    {return ;
    }
    
  • 示例:
    //函数占位参数 ,占位参数也可以有默认参数
    void func(int a, int) {cout << "this is func" << endl;
    }int main() {func(10,10); //占位参数必须填补return 0;
    }
    

(8)函数重载

  • 函数重载条件:

    • 同一个作用域下
    • 函数名称相同
    • 函数参数类型不同 或者 个数不同 或者 顺序不同
    • 函数的返回值不可以作为函数重载的条件
  • 引用可以作为重载条件,举例如下:

    #include<iostream>
    using namespace std;
    void fun(int &a)
    {cout << "fun(int &a) 的调用" << endl;
    }
    void fun(const int &a)//引用作为重载条件
    {cout << "fun(const int &a) 的调用" << endl;
    }
    int main()
    {int a = 3;fun(10);//调用参数为const int &a的函数,因为10是常数,const就是修饰常数的fun(a);//调用参数为int &a的函数,因为a是变量system("pause");return 0;
    }
    

1.8 指针

(1)指针基本概念

  • 内存编号是从0开始记录的,一般用十六进制数字表示,可以通过指针间接访问内存

  • 因此,可以利用指针变量保存内存地址;

  • 指针变量定义语法: 数据类型 * 变量名;

    int main() {int a = 10; //定义整型变量a//指针定义语法: 数据类型 * 变量名 ;int * p;	//指针变量赋值p = &a; //指针指向变量a的地址cout << &a << endl; //打印数据a的地址cout << p << endl;  //打印指针变量p//2、指针的使用//通过*操作指针变量指向的内存cout << "*p = " << *p << endl;return 0;
    }
    
  • 指针变量和普通变量的区别

    • 普通变量存放的是数据,指针变量存放的是地址
    • 指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用
  • 可以通过 & 符号 获取变量的地址;

  • 利用指针可以记录地址;

  • 对指针变量解引用,可以操作指针指向的内存数据;

  • 指针也是种数据类型,所有指针类型在32位操作系统下是4个字节;

(2)空指针和野指针

  • 空指针
    • 空指针指向内存地址编号为0的空间;
    • 空指针用来初始化指针变量;
    • 空指针指向的内存是不可以访问的,因为内存编号0 ~255为系统占用内存,不允许用户访问;
      //指针变量p指向内存地址编号为0的空间
      int * p = NULL;//访问空指针报错 
      cout << *p << endl;
      
  • 野指针
    • 指向非法的内存地址指针叫作野指针
    • 野指针形成原因如下:
      • 指针变量未初始化:任何指针变量刚被创建时不会自动成为NULL指针,所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
      • 指针释放之后未置空:有时指针在free或delete后未赋值 NULL,free或delete后它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。
      • 指针操作超越变量作用域:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

(3)const修饰指针

  • const修饰指针有三种情况
    • const修饰指针 — 常量指针
    • const修饰常量 — 指针常量
    • const即修饰指针,又修饰常量
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 = &b; //错误
//*p3 = 100; //错误

(4)指针访问数组元素

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int * p = arr;  //指向数组的指针cout << "第一个元素: " << arr[0] << endl;
cout << "指针访问第一个元素: " << *p << endl;for (int i = 0; i < 10; i++)
{//利用指针遍历数组cout << *p << endl;p++;
}

(5)地址传递

  • 作用:利用指针作函数参数,相当于将实参的地址传递给函数,可以修改实参的值
//值传递
void swap1(int a ,int b)
{int temp = a;a = b; b = temp;
}//地址传递
void swap2(int * p1, int * p2)
{int temp = *p1;*p1 = *p2;*p2 = temp;
}int main() {int a = 10;int b = 20;swap1(a, b); // 值传递不会改变实参swap2(&a, &b); //地址传递会改变实参cout << "a = " << a << endl;cout << "b = " << b << endl;return 0;
}

1.9 内存分区模型

(1)内存四区

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

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

(2)程序运行前

在程序编译后,生成了exe(linux a.out)可执行程序,未执行该程序前分为两个区域

  • 代码区:
    • 存放 CPU 执行的机器指令
    • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
    • 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
  • 全局区:
    • 全局变量和静态变量存放在此.
    • 全局区还包含了常量区, 字符串常量和其他常量也存放在此.
    • 该区域的数据在程序结束后由操作系统释放.

(3)程序运行后

  • 栈区:

    • 由编译器自动分配释放, 存放函数的参数值,局部变量等
    • 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
  • 堆区:

    • 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
    • 在C++中主要利用new关键字在堆区开辟内存

(4)new操作符

  • C++中利用new操作符在堆区开辟数据

  • 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete

  • 语法: new 数据类型

  • 利用new创建的数据,会返回该数据对应的类型的指针

  • 基本示例

    int* func()
    {int* a = new int(10);return a;
    }int main() {int *p = func();cout << *p << endl;cout << *p << endl;//利用delete释放堆区数据delete p;//cout << *p << endl; //报错,释放的空间不可访问return 0;
    }
    
  • 开辟数组示例

    //堆区开辟数组
    int main() {int* arr = new int[10];for (int i = 0; i < 10; i++){arr[i] = i + 100;}for (int i = 0; i < 10; i++){cout << arr[i] << endl;}//释放数组 delete 后加 []delete[] arr;return 0;
    }
    

1.10 引用

(1)定义

  • 作用: 给变量起别名
  • 语法: 数据类型 &别名 = 原名
  • 举例
    int a = 10;
    int &b = a;
    
  • 注意:
    • 引用必须初始化
    • 引用在初始化后,不可以改变

(2)引用传递

  • 函数传参时,可以利用引用的技术让形参修饰实参,其相比于指针地址传递更简洁;

    //1. 值传递
    void mySwap01(int a, int b) {int temp = a;a = b;b = temp;
    }//2. 地址传递
    void mySwap02(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
    }//3. 引用传递
    void mySwap03(int& a, int& b) {int temp = a;a = b;b = temp;
    }int main() {int a = 10;int b = 20;mySwap01(a, b);cout << "a:" << a << " b:" << b << endl;mySwap02(&a, &b);cout << "a:" << a << " b:" << b << endl;mySwap03(a, b);cout << "a:" << a << " b:" << b << endl;return 0;
    }
    

(3)引用做函数返回值

  • 语法:类型 &函数名(形参列表){ 函数体 }
  • 引用作为函数的返回值时,必须在定义函数时在函数名前加&
  • 用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本
  • 注意
    • 不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了”无所指”的引用,类似于野指针,程序会进入未知状态。
    • 不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
    • 可以返回类成员的引用,但最好是 const,这样可以避免在无意的情况下破坏该类的成员。
    • 通过函数返回引用这种机制,函数就可以放在赋值语句的左边

(4)引用的本质

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

讲解示例:

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

(5)常量引用

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

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

    //引用使用的场景,通常用来修饰形参
    void showValue(const int& v) {//v += 10;cout << v << endl;
    }int main() {//int& ref = 10;  引用本身需要一个合法的内存空间,因此这行错误//加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;const int& ref = 10;//ref = 100;  //加入const后不可以修改变量cout << ref << endl;//函数中利用常量引用防止误操作修改实参int a = 10;showValue(a);return 0;
    }
    

1.11 结构体

(1)结构体定义和创建

  • 定义:struct 结构体名 { 结构体成员列表 };

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

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

    • 定义结构体时的关键字是struct,不可省略
    • 创建结构体变量时,关键字struct可以省略
    • 结构体变量利用操作符 ‘’.‘’ 访问成员
  • 示例

    //结构体定义
    struct student
    {//成员列表string name;  //姓名int age;      //年龄int score;    //分数
    }stu3; //结构体变量创建方式3 int main() {//结构体变量创建方式1struct student stu1; //struct 关键字可以省略stu1.name = "张三";stu1.age = 18;stu1.score = 100;cout << "姓名:" << stu1.name << " 年龄:" << stu1.age  << " 分数:" << stu1.score << endl;//结构体变量创建方式2struct student stu2 = { "李四",19,60 };cout << "姓名:" << stu2.name << " 年龄:" << stu2.age  << " 分数:" << stu2.score << endl;stu3.name = "王五";stu3.age = 18;stu3.score = 80;cout << "姓名:" << stu3.name << " 年龄:" << stu3.age  << " 分数:" << stu3.score << endl;return 0;
    }
    

(2)结构体数组

  • 数组中每个元素都是一个结构体类型的变量;
  • 语法: struct 结构体名 数组名[元素个数] = { {} , {} , ... {} }
  • 示例
    //结构体定义
    struct student
    {//成员列表string name;  //姓名int age;      //年龄int score;    //分数
    }int main() {//结构体数组struct student arr[3]={{"张三",18,80 },{"李四",19,60 },{"王五",20,70 }};return 0;
    }
    

(3)结构体指针

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

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

  • 示例

    //结构体定义
    struct student
    {//成员列表string name;  //姓名int age;      //年龄int score;    //分数
    };int main() {struct student stu = { "张三",18,100, };struct student * p = &stu;p->score = 80; //指针通过 -> 操作符可以访问成员return 0;
    }
    

2、C++面向对象编程

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

2.1 封装

(1)封装的意义

  • 将属性(类成员变量)和行为(类成员函数)作为一个整体,表现生活中的事物

  • 类在设计时,可以把属性和行为放在不同的权限下,加以控制

    权限说明
    public公共权限
    protected保护权限
    private私有权限

(2)类定义

class Box
{public:double length;   // 长度double breadth;  // 宽度double height;   // 高度// 成员函数声明double get(void);void set( double len, double bre, double hei );
};// 成员函数定义
double Box::get(void)
{return length * breadth * height;
}void Box::set( double len, double bre, double hei)
{length = len;breadth = bre;height = hei;
}

(3)对象实例化

Box Box1;          // 声明 Box1,类型为 Box
Box Box2;          // 声明 Box2,类型为 Box

(4)访问成员

// 访问成员变量Box1.height = 5.0; Box1.length = 6.0; Box1.breadth = 7.0;// 访问成员函数Box1.set(5.0, 6.0, 7.0); volume = Box3.get(); 

(5)struct和class区别

  • 在C++中 struct和class唯一的区别就在于 默认的访问权限不同
    • struct 默认权限为公共
    • class 默认权限为私有

2.2 对象的初始化和清理

  • c++利用构造函数析构函数来解决对象的初始化和清理问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
  • 如果我们不提供构造和析构,编译器会提供,编译器提供的构造函数和析构函数是空实现。

(1)构造函数

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
  • 构造函数语法:类名(){}
    • 构造函数,没有返回值也不写void
    • 函数名称与类名相同
    • 构造函数可以有参数,因此可以发生重载
    • 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

(2)析构函数

  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
  • 析构函数语法: ~类名(){}
    • 析构函数,没有返回值也不写void
    • 函数名称与类名相同,在名称前加上符号 ~
    • 析构函数不可以有参数,因此不可以发生重载
    • 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

(3)构造函数分类及调用

  • 两种分类方式:
    • 按参数分为: 有参构造和无参构造
    • 按类型分为: 普通构造和拷贝构造
  • 三种调用方式:
    • 括号法:调用默认构造函数的时候,不要加(),会被编译器认为是函数声明,而不是调用。
    • 显示法:不要利用拷贝构造函数初始化匿名对象,编译器会认为那是对象的实例化,程序会因为对象重定义报错。
    • 隐式转换法
  • 示例:
    class Person {
    public://无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int a) {age = a;cout << "有参构造函数!" << endl;}//拷贝构造函数Person(const Person& p) {age = p.age;cout << "拷贝构造函数!" << endl;}//析构函数~Person() {cout << "析构函数!" << endl;}
    public:int age;
    };//2、构造函数的调用
    //调用无参构造函数
    void test01() {Person p; //调用无参构造函数
    }//调用有参的构造函数
    void test02() {//2.1  括号法,常用Person p1(10);//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明//Person p2();//2.2 显式法Person p2 = Person(10); Person p3 = Person(p2);//Person(10)单独写就是匿名对象  当前行结束之后,马上析构//2.3 隐式转换法Person p4 = 10; // Person p4 = Person(10); Person p5 = p4; // Person p5 = Person(p4); //不要利用拷贝构造函数初始化匿名对象//Person(p5); 编译器会认为Person(p5)这个语法等价于Person p5,是对象的实例化,程序会因为重定义报错。
    }int main() {test01();//test02();return 0;
    }
    

(4)拷贝构造函数调用时机

  • C++中拷贝构造函数调用时机通常有三种情况

    • 使用一个已经创建完毕的对象来初始化一个新对象
    • 值传递的方式给函数参数传值
    • 以值方式返回局部对象
    class Person {
    public:Person() {cout << "无参构造函数!" << endl;mAge = 0;}Person(int age) {cout << "有参构造函数!" << endl;mAge = age;}Person(const Person& p) {cout << "拷贝构造函数!" << endl;mAge = p.mAge;}//析构函数在释放内存之前调用~Person() {cout << "析构函数!" << endl;}
    public:int mAge;
    };//1. 使用一个已经创建完毕的对象来初始化一个新对象
    void test01() {Person man(100); //p对象已经创建完毕Person newman(man); //调用拷贝构造函数Person newman2 = man; //拷贝构造//Person newman3;//newman3 = man; //不是调用拷贝构造函数,赋值操作
    }//2. 值传递的方式给函数参数传值
    //相当于Person p1 = p;
    void doWork(Person p1) {}
    void test02() {Person p; //无参构造函数doWork(p);
    }//3. 以值方式返回局部对象
    Person doWork2()
    {Person p1;cout << (int *)&p1 << endl;return p1;
    }void test03()
    {Person p = doWork2();cout << (int *)&p << endl;
    }int main() {//test01();//test02();test03();return 0;
    }
    

(5)构造函数调用规则

  • 默认情况下,c++编译器至少给一个类添加3个函数
    • 默认构造函数(无参,函数体为空)
    • 默认析构函数(无参,函数体为空)
    • 默认拷贝构造函数,对属性进行值浅拷贝
  • 构造函数调用规则如下:
    • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
    • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数

(7)深拷贝与浅拷贝

  • 浅拷贝:简单的赋值拷贝操作

  • 深拷贝:在堆区重新申请空间,进行拷贝操作

  • 注意:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,其中利用深拷贝在堆区创建新内存,防止浅拷贝带来的重复释放堆区问题

    class Person {
    public://无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int age ,int height) {cout << "有参构造函数!" << endl;m_age = age;m_height = new int(height);}//拷贝构造函数  Person(const Person& p) {cout << "拷贝构造函数!" << endl;//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题m_age = p.m_age;m_height = new int(*p.m_height);}//析构函数~Person() {cout << "析构函数!" << endl;if (m_height != NULL){delete m_height;}}
    public:int m_age;int* m_height;
    };void test01()
    {Person p1(18, 180);Person p2(p1);cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
    }int main() {test01();return 0;
    }
    

(8)初始化列表

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

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

    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) {}void PrintPerson() {cout << "mA:" << m_A << endl;cout << "mB:" << m_B << endl;cout << "mC:" << m_C << endl;}
    private:int m_A;int m_B;int m_C;
    };int main() {Person p(1, 2, 3);p.PrintPerson();return 0;
    }
    

(9)类对象作为类成员

  • C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
  • B类中有对象A作为成员,A为对象成员。那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
    • 构造的顺序是 :先调用对象成员的构造,再调用本类构造;
    • 析构顺序与构造相反;

(10)静态成员

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

  • 静态成员变量

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

    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量
  • 静态成员的访问可以通过对象,也可以通过类名:

    //1、通过对象
    Person p1;
    p1.m_A = 100;
    p1.func();//2、通过类名
    Person::m_A = 100;
    Person::func();
    

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

(1)成员变量和成员函数存储方式

  • 非静态成员变量占对象空间
  • 静态成员变量不占对象空间
  • 函数也不占对象空间,所有函数共享一个函数实例
  • 静态成员函数也不占对象空间

(2)this指针

  • 问题:每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码,这一块代码是如何区分那个对象调用自己的呢?
  • 解决方案:c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
  • this指针是隐含在每一个非静态成员函数内的一种指针,不需要定义,直接使用即可
  • this指针的用途:
    • 当形参和成员变量同名时,可用this指针来区分
    • 在类的非静态成员函数中返回对象本身,可使用return *this
    class Person
    {
    public:Person(int age){//1、当形参和成员变量同名时,可用this指针来区分this->age = age;}Person& PersonAddPerson(Person p){this->age += p.age;//返回对象本身return *this;}int age;
    };void test01()
    {Person p1(10);cout << "p1.age = " << p1.age << endl;Person p2(10);p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);cout << "p2.age = " << p2.age << endl;
    }int main() {test01();return 0;
    }
    

(3)空指针访问成员函数

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

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

    //空指针访问成员函数
    class Person {
    public:void ShowClassName() {cout << "我是Person类!" << endl;}void ShowPerson() {if (this == NULL) {return;}cout << mAge << endl;}public:int mAge;
    };void test01()
    {Person * p = NULL;p->ShowClassName(); //空指针,可以调用成员函数p->ShowPerson();  //但是如果成员函数中用到了this指针,就不可以了
    }int main() {test01();return 0;
    }
    

(4)常函数和常对象

  • 常函数:

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

    • 声明对象前加const称该对象为常对象
    • 常对象只能调用常函数
    class Person {
    public:Person() {m_A = 0;m_B = 0;}//this指针的本质是一个指针常量,指针的指向不可修改//如果想让指针指向的值也不可以修改,需要声明常函数void ShowPerson() const {//const Type* const pointer;//this = NULL; //不能修改指针的指向 Person* const this;//this->mA = 100; //但是this指针指向的对象的数据是可以修改的//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量this->m_B = 100;}void MyFunc() const {//mA = 10000;}public:int m_A;mutable int m_B; //可修改 可变的
    };//const修饰对象  常对象
    void test01() {const Person person; //常量对象  cout << person.m_A << endl;//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问person.m_B = 100; //但是常对象可以修改mutable修饰成员变量//常对象访问成员函数person.MyFunc(); //常对象不能调用非const的函数}int main() {test01();return 0;
    }
    

2.4 友元

  • 友元的目的就是让一个函数或者类 访问另一个类中私有成员
  • 友元的关键字为 friend
  • 友元的三种实现
    • 全局函数做友元
    • 类做友元
    • 成员函数做友元

(1)全局函数做友元

class Building
{//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容friend void goodGay(Building * building);public:Building(){this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";}public:string m_SittingRoom; //客厅private:string m_BedRoom; //卧室
};void goodGay(Building * building)
{cout << "好基友正在访问: " << building->m_SittingRoom << endl;cout << "好基友正在访问: " << building->m_BedRoom << endl;
}void test01()
{Building b;goodGay(&b);
}int main(){test01();return 0;
}

(2)类做友元

class Building;
class goodGay
{
public:goodGay();void visit();private:Building *building;
};class Building
{//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容friend class goodGay;public:Building();public:string m_SittingRoom; //客厅
private:string m_BedRoom;//卧室
};Building::Building()
{this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";
}goodGay::goodGay()
{building = new Building;
}void goodGay::visit()
{cout << "好基友正在访问" << building->m_SittingRoom << endl;cout << "好基友正在访问" << building->m_BedRoom << endl;
}void test01()
{goodGay gg;gg.visit();}int main(){test01();return 0;
}

(3)成员函数做友元

class Building;
class goodGay
{
public:goodGay();void visit(); //只让visit函数作为Building的好朋友,可以访问Building中私有内容void visit2(); private:Building *building;
};class Building
{//告诉编译器  goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容friend void goodGay::visit();public:Building();public:string m_SittingRoom; //客厅
private:string m_BedRoom;//卧室
};Building::Building()
{this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";
}goodGay::goodGay()
{building = new Building;
}void goodGay::visit()
{cout << "好基友正在访问" << building->m_SittingRoom << endl;cout << "好基友正在访问" << building->m_BedRoom << endl;
}void goodGay::visit2()
{cout << "好基友正在访问" << building->m_SittingRoom << endl;//cout << "好基友正在访问" << building->m_BedRoom << endl;
}void test01()
{goodGay  gg;gg.visit();}int main(){test01();system("pause");return 0;
}

2.5 运算符重载

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

(1)加号运算符重载

class Person {
public:Person() {};Person(int a, int b){this->m_A = a;this->m_B = b;}//成员函数实现 + 号运算符重载Person operator+(const Person& p) {Person temp;temp.m_A = this->m_A + p.m_A;temp.m_B = this->m_B + p.m_B;return temp;}public:int m_A;int m_B;
};//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
//	Person temp(0, 0);
//	temp.m_A = p1.m_A + p2.m_A;
//	temp.m_B = p1.m_B + p2.m_B;
//	return temp;
//}//运算符重载 可以发生函数重载 
Person operator+(const Person& p2, int val)  
{Person temp;temp.m_A = p2.m_A + val;temp.m_B = p2.m_B + val;return temp;
}void test() {Person p1(10, 10);Person p2(20, 20);//成员函数方式Person p3 = p2 + p1;  //相当于 p2.operaor+(p1)cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;Person p4 = p3 + 10; //相当于 operator+(p3,10)cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;}int main() {test();return 0;
}

(2)左移运算符重载

  • 使用全局函数重载左移运算符配合友元可以实现输出自定义数据类型
class Person {friend ostream& operator<<(ostream& out, Person& p);public:Person(int a, int b){this->m_A = a;this->m_B = b;}//成员函数 实现不了  p << cout 不是我们想要的效果//void operator<<(Person& p){//}private:int m_A;int m_B;
};//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person& p) {out << "a:" << p.m_A << " b:" << p.m_B;return out;
}void test() {Person p1(10, 20);cout << p1 << "hello world" << endl; //链式编程
}int main() {test();system("pause");return 0;
}

(3)递增运算符重载

  • 前置递增返回引用,后置递增返回值
class MyInteger {friend ostream& operator<<(ostream& out, MyInteger myint);public:MyInteger() {m_Num = 0;}//前置++MyInteger& operator++() {//先++m_Num++;//再返回return *this;}//后置++MyInteger operator++(int) {//先返回MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;m_Num++;return temp;}private:int m_Num;
};ostream& operator<<(ostream& out, MyInteger myint) {out << myint.m_Num;return out;
}//前置++ 先++ 再返回
void test01() {MyInteger myInt;cout << ++myInt << endl;cout << myInt << endl;
}//后置++ 先返回 再++
void test02() {MyInteger myInt;cout << myInt++ << endl;cout << myInt << endl;
}int main() {test01();//test02();system("pause");return 0;
}

(4)赋值运算符重载

  • c++编译器至少给一个类添加4个函数

    1. 默认构造函数(无参,函数体为空)
    2. 默认析构函数(无参,函数体为空)
    3. 默认拷贝构造函数,对属性进行值拷贝
    4. 赋值运算符 operator=, 对属性进行值拷贝
  • 如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

    class Person
    {
    public:Person(int age){//将年龄数据开辟到堆区m_Age = new int(age);}//重载赋值运算符 Person& operator=(Person &p){if (m_Age != NULL){delete m_Age;m_Age = NULL;}//编译器提供的代码是浅拷贝//m_Age = p.m_Age;//提供深拷贝 解决浅拷贝的问题m_Age = new int(*p.m_Age);//返回自身return *this;}~Person(){if (m_Age != NULL){delete m_Age;m_Age = NULL;}}//年龄的指针int *m_Age;};void test01()
    {Person p1(18);Person p2(20);Person 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() {test01();//int a = 10;//int b = 20;//int c = 30;//c = b = a;//cout << "a = " << a << endl;//cout << "b = " << b << endl;//cout << "c = " << c << endl;system("pause");return 0;
    }
    

(5)关系运算符重载

  • 重载关系运算符,可以让两个自定义类型对象进行对比操作

    class Person
    {
    public:Person(string name, int age){this->m_Name = name;this->m_Age = age;};bool operator==(Person & p){if (this->m_Name == p.m_Name && this->m_Age == p.m_Age){return true;}else{return false;}}bool operator!=(Person & 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 test01()
    {//int a = 0;//int b = 0;Person a("孙悟空", 18);Person b("孙悟空", 18);if (a == b){cout << "a和b相等" << endl;}else{cout << "a和b不相等" << endl;}if (a != b){cout << "a和b不相等" << endl;}else{cout << "a和b相等" << endl;}
    }int main() {test01();system("pause");return 0;
    }
    

(6)函数调用运算符重载

  • 函数调用运算符()也可以重载

  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数。

  • 仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。

    class MyPrint
    {
    public:void operator()(string text){cout << text << endl;}};
    void test01()
    {//重载的()操作符 也称为仿函数MyPrint myFunc;myFunc("hello world");
    }class MyAdd
    {
    public:int operator()(int v1, int v2){return v1 + v2;}
    };void test02()
    {MyAdd add;int ret = add(10, 10);cout << "ret = " << ret << endl;//匿名对象调用  cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
    }int main() {test01();test02();system("pause");return 0;
    }
    

2.6 继承

(1)语法

  • 继承的语法:class 子类 : 继承方式 父类
  • 子类 又称为 派生类
  • 父类 又称为 基类

(2)继承方式

继承方式一共有三种:

继承方式说明
public公共继承
protected保护继承
private私有继承

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

(3)继承中构造和析构顺序

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

(4)继承同名成员处理方式

  • 问题:当子类与父类出现同名的成员,此时子类会隐藏父类中同名成员函数,那么如何访问到子类或父类中同名的数据呢?
    • 访问子类同名成员 直接访问即可:s.m_A
    • 访问父类同名成员 需要加作用域:s.Base::m_A
      Son s;cout << "Son下的m_A = " << s.m_A << endl;
      cout << "Base下的m_A = " << s.Base::m_A << endl;
      

(5)继承同名静态成员处理方式

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

  • 区别在于静态成员可以直接通过类名访问,也可以通过对象访问;

    //通过对象访问
    Son s;
    cout << "Son  下 m_A = " << s.m_A << endl;
    cout << "Base 下 m_A = " << s.Base::m_A << endl;//通过类名访问
    cout << "Son  下 m_A = " << Son::m_A << endl;
    cout << "Base 下 m_A = " << Son::Base::m_A << endl;
    

(6)多继承

  • C++允许一个类继承多个类
  • 语法: class 子类 :继承方式 父类1 , 继承方式 父类2...
  • 多继承可能会引发父类中有同名成员出现,需要加作用域区分
Son s;
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;

(7)菱形继承

  • 菱形继承概念:
    • 两个派生类继承同一个基类
    • 又有某个类同时继承者两个派生类
    • 这种继承被称为菱形继承,或者钻石继承

在这里插入图片描述

  • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。(羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。)

  • 利用虚继承可以解决菱形继承问题

    //继承前加virtual关键字后,变为虚继承
    //此时公共的父类Animal称为虚基类
    class Sheep : virtual public Animal {};
    class Tuo   : virtual public Animal {};
    class SheepTuo : public Sheep, public Tuo {};
    

2.7 多态

(1)概念

  • 多态分为两类

    • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
    • 动态多态: 派生类和虚函数实现运行时多态
  • 静态多态和动态多态区别:

    • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
    • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

(2)多态满足条件和使用

  • 多态满足条件
    • 有继承关系
    • 子类重写父类中的虚函数(函数返回值类型 函数名 参数列表 完全一致称为重写
  • 多态使用:
    • 父类指针或引用指向子类对象
//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{
public :virtual int getResult(){return 0;}int m_Num1;int m_Num2;
};//加法计算器
class AddCalculator :public AbstractCalculator
{
public:int getResult(){return m_Num1 + m_Num2;}
};//减法计算器
class SubCalculator :public AbstractCalculator
{
public:int getResult(){return m_Num1 - m_Num2;}
};//乘法计算器
class MulCalculator :public AbstractCalculator
{
public:int getResult(){return m_Num1 * m_Num2;}
};void test02()
{//创建加法计算器AbstractCalculator *abc = new AddCalculator;abc->m_Num1 = 10;abc->m_Num2 = 10;cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;delete abc;  //用完了记得销毁//创建减法计算器abc = new SubCalculator;abc->m_Num1 = 10;abc->m_Num2 = 10;cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;delete abc;  //创建乘法计算器abc = new MulCalculator;abc->m_Num1 = 10;abc->m_Num2 = 10;cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;delete abc;
}int main() {//test01();test02();system("pause");return 0;
}

(3)纯虚函数和抽象类

  • 在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
  • 纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
  • 当类中有了纯虚函数,这个类也称为抽象类
  • 抽象类特点
    • 无法实例化对象
    • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base
{
public://纯虚函数//类中只要有一个纯虚函数就称为抽象类//抽象类无法实例化对象//子类必须重写父类中的纯虚函数,否则也属于抽象类virtual void func() = 0;
};class Son :public Base
{
public:virtual void func() {cout << "func调用" << endl;};
};void test01()
{Base * base = NULL;//base = new Base; // 错误,抽象类无法实例化对象base = new Son;base->func();delete base;//记得销毁
}int main() {test01();system("pause");return 0;
}

(4)虚析构和纯虚析构

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

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

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

  • 虚析构和纯虚析构共性:

    • 可以解决父类指针释放子类对象
    • 都需要有具体的函数实现
  • 虚析构和纯虚析构区别:

    • 如果是纯虚析构,该类属于抽象类,无法实例化对象
  • 虚析构语法:virtual ~类名(){}

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

class Animal {
public:Animal(){cout << "Animal 构造函数调用!" << endl;}virtual void Speak() = 0;//析构函数加上virtual关键字,变成虚析构函数//virtual ~Animal()//{//	cout << "Animal虚析构函数调用!" << endl;//}virtual ~Animal() = 0;
};Animal::~Animal()
{cout << "Animal 纯虚析构函数调用!" << endl;
}//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。class Cat : public Animal {
public:Cat(string name){cout << "Cat构造函数调用!" << endl;m_Name = new string(name);}virtual void Speak(){cout << *m_Name <<  "小猫在说话!" << endl;}~Cat(){cout << "Cat析构函数调用!" << endl;if (this->m_Name != NULL) {delete m_Name;m_Name = NULL;}}public:string *m_Name;
};void test01()
{Animal *animal = new Cat("Tom");animal->Speak();//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏//怎么解决?给基类增加一个虚析构函数//虚析构函数就是用来解决通过父类指针释放子类对象delete animal;
}int main() {test01();system("pause");return 0;
}

总结:

​ 1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

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

​ 3. 拥有纯虚析构函数的类也属于抽象类

3、C++泛型编程

  • C++另一种编程思想称为 泛型编程 ,主要利用的技术就是模板

  • C++提供两种模板机制:函数模板类模板

3.1 函数模版

(1)函数模版语法

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

  • 语法:

    template<typename T>
    函数声明或定义
    
    • template — 声明创建模板
    • typename — 表面其后面的符号是一种数据类型,可以用class代替
    • T — 通用的数据类型,名称可以替换,通常为大写字母
  • 使用函数模板有两种方式:自动类型推导、显式指定类型

    • 自动类型推导,必须推导出一致的数据类型T,才可以使用
    • 显式指定语法: 函数名称<指定数据类型>(函数参数)
  • 示例:

    //利用模板提供通用的交换函数
    template<typename T>
    void mySwap(T& a, T& b)
    {T temp = a;a = b;b = temp;
    }void test01()
    {int a = 10;int b = 20;//利用模板实现交换//1、自动类型推导mySwap(a, b);//2、显示指定类型mySwap<int>(a, b);cout << "a = " << a << endl;cout << "b = " << b << endl;
    }int main() {test01();return 0;
    }
    

(2)普通函数与函数模版的区别

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)

  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换

  • 如果利用显示指定类型的方式,可以发生隐式类型转换

    //普通函数
    int myAdd01(int a, int b)
    {return a + b;
    }//函数模板
    template<class T>
    T myAdd02(T a, T b)  
    {return a + b;
    }//使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换
    void test01()
    {int a = 10;int b = 20;char c = 'c';cout << myAdd01(a, c) << endl; //正确,将char类型的'c'隐式转换为int类型  'c' 对应 ASCII码 99//myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换myAdd02<int>(a, c); //正确,如果用显示指定类型,可以发生隐式类型转换
    }int main() {test01();return 0;
    }
    

(3)同名普通函数与函数模版的调用规则

  • 如果函数模板和普通函数都可以实现,优先调用普通函数
  • 可以通过空模板参数列表来强制调用函数模板,函数名<>(函数参数);
  • 函数模板也可以发生重载
  • 如果函数模板可以产生更好的匹配,优先调用函数模板

(4)函数模版的局限性

  • 内置数据类型可以直接使用通用的函数模板;
  • 但是,对于自定义数据类型,普通的函数模版无法解决,需要为这些特定数据类型提供具体化的模版重载;
#include<iostream>
using namespace std;#include <string>class Person
{
public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age;
};//普通函数模板
template<class T>
bool myCompare(T& a, T& b)
{if (a == b){return true;}else{return false;}
}//具体化函数模版重载以`template<>`开头,并显式指定数据类型
//调用时,具体化函数模版重载优先于常规模板
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 test01()
{int a = 10;int b = 20;//内置数据类型可以直接使用通用的函数模板bool ret = myCompare(a, b);if (ret){cout << "a == b " << endl;}else{cout << "a != b " << endl;}
}void test02()
{Person p1("Tom", 10);Person p2("Tom", 10);//自定义数据类型,不会调用普通的函数模板//可以创建具体化的Person数据类型的模板,用于特殊处理这个类型bool ret = myCompare(p1, p2);if (ret){cout << "p1 == p2 " << endl;}else{cout << "p1 != p2 " << endl;}
}int main() {test01();test02();return 0;
}

3.2 类模版

(1)类模版语法

  • 类模板用来建立一个通用类,类中的成员数据类型可以不具体指定,用一个虚拟的类型来代表。

  • 类模板和函数模板语法相似,将声明模板template后面加类而不是函数;

  • 语法:

    template<typename T>
  • 示例:

    #include <string>
    //类模板
    template<class NameType, class AgeType> 
    class Person
    {
    public:Person(NameType name, AgeType age){this->mName = name;this->mAge = age;}void showPerson(){cout << "name: " << this->mName << " age: " << this->mAge << endl;}
    public:NameType mName;AgeType mAge;
    };void test01()
    {// 显式指定NameType 为string类型,AgeType 为 int类型Person<string, int>P1("孙悟空", 999);P1.showPerson();
    }int main() {test01();return 0;
    }
    

(2) 类模板与函数模板区别

  • 类模板没有自动类型推导的使用方式
  • 类模板在模板参数列表中可以有默认参数
  • 示例:
#include <string>
//类模板
template<class NameType, class AgeType = int> 
class Person
{
public:Person(NameType name, AgeType age){this->mName = name;this->mAge = age;}void showPerson(){cout << "name: " << this->mName << " age: " << this->mAge << endl;}
public:NameType mName;AgeType mAge;
};//1、类模板没有自动类型推导的使用方式
void test01()
{// Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导Person <string ,int>p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板p.showPerson();
}//2、类模板在模板参数列表中可以有默认参数
void test02()
{Person <string> p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数p.showPerson();
}int main() {test01();test02();return 0;
}

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

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

(4)类模板对象做函数参数

  • 类模板对象做函数参数一共有三种传入方式:

    1. 指定传入的类型 — 直接显示对象的数据类型
    2. 参数模板化 — 将对象中的参数变为模板进行传递
    3. 整个类模板化 — 将这个对象类型 模板化进行传递
  • 使用比较广泛是第一种:指定传入的类型

  • 示例:

    #include <string>
    //类模板
    template<class NameType, class AgeType = int> 
    class Person
    {
    public:Person(NameType name, AgeType age){this->mName = name;this->mAge = age;}void showPerson(){cout << "name: " << this->mName << " age: " << this->mAge << endl;}
    public:NameType mName;AgeType mAge;
    };//1、指定传入的类型
    void printPerson1(Person<string, int> &p) 
    {p.showPerson();
    }
    void test01()
    {Person <string, int >p("孙悟空", 100);printPerson1(p);
    }//2、参数模板化
    template <class T1, class T2>
    void printPerson2(Person<T1, T2>&p)
    {p.showPerson();cout << "T1的类型为: " << typeid(T1).name() << endl;cout << "T2的类型为: " << typeid(T2).name() << endl;
    }
    void test02()
    {Person <string, int >p("猪八戒", 90);printPerson2(p);
    }//3、整个类模板化
    template<class T>
    void printPerson3(T & p)
    {cout << "T的类型为: " << typeid(T).name() << endl;p.showPerson();}
    void test03()
    {Person <string, int >p("唐僧", 30);printPerson3(p);
    }int main() {test01();test02();test03();system("pause");return 0;
    }
    

(5)类模板与继承

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型

  • 如果不指定,编译器无法给子类分配内存

  • 如果想灵活指定出父类中T的类型,子类也需变为类模板

  • 示例:

    template<class T>
    class Base
    {T m;
    };//class Son:public Base  //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
    class Son :public Base<int> //必须指定一个类型
    {
    };
    void test01()
    {Son c;
    }//类模板继承类模板 ,可以用T2指定父类中的T类型
    template<class T1, class T2>
    class Son2 :public Base<T2>
    {
    public:Son2(){cout << typeid(T1).name() << endl;cout << typeid(T2).name() << endl;}
    };void test02()
    {Son2<int, char> child1;
    }int main() {test01();test02();system("pause");return 0;
    }
    

(6)类模板成员函数类外实现

  • 类模板中成员函数类外实现时,需要加上模板参数列表,类似函数模版,只不过前面会加上类名作用域。

  • 示例:

    #include <string>//类模板中成员函数类外实现
    template<class T1, class T2>
    class Person {
    public://成员函数类内声明Person(T1 name, T2 age);void showPerson();public:T1 m_Name;T2 m_Age;
    };//构造函数 类外实现
    template<class T1, class T2>
    Person<T1, T2>::Person(T1 name, T2 age) {this->m_Name = name;this->m_Age = age;
    }//成员函数 类外实现
    template<class T1, class T2>
    void Person<T1, T2>::showPerson() {cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
    }void test01()
    {Person<string, int> p("Tom", 20);p.showPerson();
    }int main() {test01();system("pause");return 0;
    }
    

(7)类模板分文件编写

  • 问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写(.h声明,.cpp实现)时链接不到
  • 解决方案:
    • 直接包含.cpp源文件
    • 将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

(8)类模版与友元

  • 全局函数类内实现 - 直接在类内声明友元即可

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

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

  • 示例:

    #include <string>//2、全局函数配合友元  类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元
    template<class T1, class T2> class Person;//如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到
    //template<class T1, class T2> void printPerson2(Person<T1, T2> & p); template<class T1, class T2>
    void printPerson2(Person<T1, T2> & p)
    {cout << "类外实现 ---- 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
    }template<class T1, class T2>
    class Person
    {//1、全局函数配合友元   类内实现friend void printPerson(Person<T1, T2> & p){cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;}//全局函数配合友元  类外实现friend void printPerson2<>(Person<T1, T2> & p);public:Person(T1 name, T2 age){this->m_Name = name;this->m_Age = age;}private:T1 m_Name;T2 m_Age;};//1、全局函数在类内实现
    void test01()
    {Person <string, int >p("Tom", 20);printPerson(p);
    }//2、全局函数在类外实现
    void test02()
    {Person <string, int >p("Jerry", 30);printPerson2(p);
    }int main() {//test01();test02();system("pause");return 0;
    }
    

4、STL初识

4.1 STL的诞生

  • 长久以来,软件界一直希望建立一种可重复利用的东西

  • C++的面向对象泛型编程思想,目的就是复用性的提升

  • 大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作

  • 为了建立数据结构和算法的一套标准,诞生了STL

4.2 STL基本概念

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

4.3 STL六大组件

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

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

4.4 STL中容器、算法、迭代器

**容器:**置物之所也

STL容器就是将运用最广泛的一些数据结构实现出来

常用的数据结构:数组, 链表,树, 栈, 队列, 集合, 映射表 等

这些容器分为序列式容器关联式容器两种:

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

**算法:**问题之解法也

有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)

算法分为:质变算法非质变算法

质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等

非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等

**迭代器:**容器和算法之间粘合剂

提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。

每个容器都有自己专属的迭代器

迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针

迭代器种类:

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

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

4.5 容器算法迭代器初识

了解STL中容器、算法、迭代器概念之后,我们利用代码感受STL的魅力

STL中最常用的容器为Vector,可以理解为数组,下面我们将学习如何向这个容器中插入数据、并遍历这个容器

(1) vector存放内置数据类型

容器: vector

算法: for_each

迭代器: vector<int>::iterator

示例:

#include <vector>
#include <algorithm>void MyPrint(int val)
{cout << val << endl;
}void test01() {//创建vector容器对象,并且通过模板参数指定容器中存放的数据的类型vector<int> v;//向容器中放数据v.push_back(10);v.push_back(20);v.push_back(30);v.push_back(40);//每一个容器都有自己的迭代器,迭代器是用来遍历容器中的元素//v.begin()返回迭代器,这个迭代器指向容器中第一个数据//v.end()返回迭代器,这个迭代器指向容器元素的最后一个元素的下一个位置//vector<int>::iterator 拿到vector<int>这种容器的迭代器类型vector<int>::iterator pBegin = v.begin();vector<int>::iterator pEnd = v.end();//第一种遍历方式:while (pBegin != pEnd) {cout << *pBegin << endl;pBegin++;}//第二种遍历方式:for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << endl;}cout << endl;//第三种遍历方式://使用STL提供标准遍历算法  头文件 algorithmfor_each(v.begin(), v.end(), MyPrint);
}int main() {test01();system("pause");return 0;
}

(2) Vector存放自定义数据类型

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

示例:

#include <vector>
#include <string>//自定义数据类型
class Person {
public:Person(string name, int age) {mName = name;mAge = age;}
public:string mName;int mAge;
};
//存放对象
void test01() {vector<Person> v;//创建数据Person p1("aaa", 10);Person p2("bbb", 20);Person p3("ccc", 30);Person p4("ddd", 40);Person p5("eee", 50);v.push_back(p1);v.push_back(p2);v.push_back(p3);v.push_back(p4);v.push_back(p5);for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {cout << "Name:" << (*it).mName << " Age:" << (*it).mAge << endl;}
}//放对象指针
void test02() {vector<Person*> v;//创建数据Person p1("aaa", 10);Person p2("bbb", 20);Person p3("ccc", 30);Person p4("ddd", 40);Person p5("eee", 50);v.push_back(&p1);v.push_back(&p2);v.push_back(&p3);v.push_back(&p4);v.push_back(&p5);for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {Person * p = (*it);cout << "Name:" << p->mName << " Age:" << (*it)->mAge << endl;}
}int main() {test01();test02();system("pause");return 0;
}

(3) Vector容器嵌套容器

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

示例:

#include <vector>//容器嵌套容器
void test01() {vector< vector<int> >  v;vector<int> v1;vector<int> v2;vector<int> v3;vector<int> v4;for (int i = 0; i < 4; i++) {v1.push_back(i + 1);v2.push_back(i + 2);v3.push_back(i + 3);v4.push_back(i + 4);}//将容器元素插入到vector v中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 vit = (*it).begin(); vit != (*it).end(); vit++) {cout << *vit << " ";}cout << endl;}}int main() {test01();system("pause");return 0;
}

5、 STL- 常用容器

5.1 string容器

(1) string基本概念

本质:

  • string是C++风格的字符串,而string本质上是一个类

string和char * 区别:

  • char * 是一个指针
  • string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器。

特点:

string 类内部封装了很多成员方法

例如:查找find,拷贝copy,删除delete 替换replace,插入insert

string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责

(2) string构造函数

构造函数原型:

  • string(); //创建一个空的字符串 例如: string str;
    string(const char* s); //使用字符串s初始化
  • string(const string& str); //使用一个string对象初始化另一个string对象
  • string(int n, char c); //使用n个字符c初始化

示例:

#include <string>
//string构造
void test01()
{string s1; //创建空字符串,调用无参构造函数cout << "str1 = " << s1 << endl;const char* str = "hello world";string s2(str); //把c_string转换成了stringcout << "str2 = " << s2 << endl;string s3(s2); //调用拷贝构造函数cout << "str3 = " << s3 << endl;string s4(10, 'a');cout << "str3 = " << s3 << endl;
}int main() {test01();system("pause");return 0;
}

总结:string的多种构造方式没有可比性,灵活使用即可

(3) string赋值操作

功能描述:

  • 给string字符串进行赋值

赋值的函数原型:

  • string& operator=(const char* s); //char*类型字符串 赋值给当前的字符串
  • string& operator=(const string &s); //把字符串s赋给当前的字符串
  • string& operator=(char c); //字符赋值给当前的字符串
  • string& assign(const char *s); //把字符串s赋给当前的字符串
  • string& assign(const char *s, int n); //把字符串s的前n个字符赋给当前的字符串
  • string& assign(const string &s); //把字符串s赋给当前字符串
  • string& assign(int n, char c); //用n个字符c赋给当前字符串

示例:

//赋值
void test01()
{string str1;str1 = "hello world";cout << "str1 = " << str1 << endl;string str2;str2 = str1;cout << "str2 = " << str2 << endl;string str3;str3 = 'a';cout << "str3 = " << str3 << endl;string str4;str4.assign("hello c++");cout << "str4 = " << str4 << endl;string str5;str5.assign("hello c++",5);cout << "str5 = " << str5 << endl;string str6;str6.assign(str5);cout << "str6 = " << str6 << endl;string str7;str7.assign(5, 'x');cout << "str7 = " << str7 << endl;
}int main() {test01();system("pause");return 0;
}

总结:

​ string的赋值方式很多,operator= 这种方式是比较实用的

(4) 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个字符连接到字符串结尾

示例:

//字符串拼接
void test01()
{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 ");str3.append("game abcde", 4);//str3.append(str2);str3.append(str2, 4, 3); // 从下标4位置开始 ,截取3个字符,拼接到字符串末尾cout << "str3 = " << str3 << endl;
}
int main() {test01();system("pause");return 0;
}

总结:字符串拼接的重载版本很多,初学阶段记住几种即可

(5) 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

示例:

//查找和替换
void test01()
{//查找string str1 = "abcdefgde";int pos = str1.find("de");if (pos == -1){cout << "未找到" << endl;}else{cout << "pos = " << pos << endl;}pos = str1.rfind("de");cout << "pos = " << pos << endl;}void test02()
{//替换string str1 = "abcdefgde";str1.replace(1, 3, "1111");cout << "str1 = " << str1 << endl;
}int main() {//test01();//test02();system("pause");return 0;
}

总结:

  • find查找是从左往后,rfind从右往左
  • find找到字符串后返回查找的第一个字符位置,找不到返回-1
  • replace在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串

(6) string字符串比较

功能描述:

  • 字符串之间的比较

比较方式:

  • 字符串比较是按字符的ASCII码进行对比

= 返回 0

> 返回 1

< 返回 -1

函数原型:

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

示例:

//字符串比较
void test01()
{string s1 = "hello";string s2 = "aello";int ret = s1.compare(s2);if (ret == 0) {cout << "s1 等于 s2" << endl;}else if (ret > 0){cout << "s1 大于 s2" << endl;}else{cout << "s1 小于 s2" << endl;}}int main() {test01();system("pause");return 0;
}

总结:字符串对比主要是用于比较两个字符串是否相等,判断谁大谁小的意义并不是很大

(7) string字符存取

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

  • char& operator[](int n); //通过[]方式取字符
  • char& at(int n); //通过at方法获取字符

示例:

void test01()
{string str = "hello world";for (int i = 0; i < str.size(); i++){cout << str[i] << " ";}cout << endl;for (int i = 0; i < str.size(); i++){cout << str.at(i) << " ";}cout << endl;//字符修改str[0] = 'x';str.at(1) = 'x';cout << str << endl;}int main() {test01();system("pause");return 0;
}

总结:string字符串中单个字符存取有两种方式,利用 [ ] 或 at

(8) 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个字符

示例:

//字符串插入和删除
void test01()
{string str = "hello";str.insert(1, "111");cout << str << endl;str.erase(1, 3);  //从1号位置开始3个字符cout << str << endl;
}int main() {test01();system("pause");return 0;
}

**总结:**插入和删除的起始下标都是从0开始

(9) string子串

功能描述:

  • 从字符串中获取想要的子串

函数原型:

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

示例:

//子串
void test01()
{string str = "abcdefg";string subStr = str.substr(1, 3);cout << "subStr = " << subStr << endl;string email = "hello@sina.com";int pos = email.find("@");string username = email.substr(0, pos);cout << "username: " << username << endl;}int main() {test01();system("pause");return 0;
}

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

5.2 vector容器

(1) vector基本概念

功能:

  • vector数据结构和数组非常相似,也称为单端数组

vector与普通数组区别:

  • 不同之处在于数组是静态空间,而vector可以动态扩展

动态扩展:

  • 并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间

在这里插入图片描述

  • vector容器的迭代器是支持随机访问的迭代器

(2) vector构造函数

功能描述:

  • 创建vector容器

函数原型:

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

示例:

#include <vector>void printVector(vector<int>& v) {for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << " ";}cout << endl;
}void test01()
{vector<int> v1; //无参构造for (int i = 0; i < 10; i++){v1.push_back(i);}printVector(v1);vector<int> v2(v1.begin(), v1.end());printVector(v2);vector<int> v3(10, 100);printVector(v3);vector<int> v4(v3);printVector(v4);
}int main() {test01();system("pause");return 0;
}

**总结:**vector的多种构造方式没有可比性,灵活使用即可

(3) vector赋值操作

功能描述:

  • 给vector容器进行赋值

函数原型:

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

  • assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。

  • assign(n, elem); //将n个elem拷贝赋值给本身。

示例:

#include <vector>void printVector(vector<int>& v) {for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << " ";}cout << endl;
}//赋值操作
void test01()
{vector<int> v1; //无参构造for (int i = 0; i < 10; i++){v1.push_back(i);}printVector(v1);vector<int>v2;v2 = v1;printVector(v2);vector<int>v3;v3.assign(v1.begin(), v1.end());printVector(v3);vector<int>v4;v4.assign(10, 100);printVector(v4);
}int main() {test01();system("pause");return 0;
}

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

(4) vector容量和大小

功能描述:

  • 对vector容器的容量和大小操作

函数原型:

  • empty(); //判断容器是否为空

  • capacity(); //容器的容量

  • size(); //返回容器中元素的个数

  • resize(int num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

  • resize(int num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除

示例:

#include <vector>void printVector(vector<int>& v) {for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << " ";}cout << endl;
}void test01()
{vector<int> v1;for (int i = 0; i < 10; i++){v1.push_back(i);}printVector(v1);if (v1.empty()){cout << "v1为空" << endl;}else{cout << "v1不为空" << endl;cout << "v1的容量 = " << v1.capacity() << endl;cout << "v1的大小 = " << v1.size() << endl;}//resize 重新指定大小 ,若指定的更大,默认用0填充新位置,可以利用重载版本替换默认填充v1.resize(15,10);printVector(v1);//resize 重新指定大小 ,若指定的更小,超出部分元素被删除v1.resize(5);printVector(v1);
}int main() {test01();system("pause");return 0;
}

总结:

  • 判断是否为空 — empty
  • 返回元素个数 — size
  • 返回容器容量 — capacity
  • 重新指定大小 — resize

(5)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 <vector>void printVector(vector<int>& v) {for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << " ";}cout << endl;
}//插入和删除
void test01()
{vector<int> v1;//尾插v1.push_back(10);v1.push_back(20);v1.push_back(30);v1.push_back(40);v1.push_back(50);printVector(v1);//尾删v1.pop_back();printVector(v1);//插入v1.insert(v1.begin(), 100);printVector(v1);v1.insert(v1.begin(), 2, 1000);printVector(v1);//删除v1.erase(v1.begin());printVector(v1);//清空v1.erase(v1.begin(), v1.end());v1.clear();printVector(v1);
}int main() {test01();system("pause");return 0;
}

总结:

  • 尾插 — push_back
  • 尾删 — pop_back
  • 插入 — insert (位置迭代器)
  • 删除 — erase (位置迭代器)
  • 清空 — clear

(6) vector数据存取

功能描述:

  • 对vector中的数据的存取操作

函数原型:

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

示例:

#include <vector>void test01()
{vector<int>v1;for (int i = 0; i < 10; i++){v1.push_back(i);}for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;for (int i = 0; i < v1.size(); i++){cout << v1.at(i) << " ";}cout << endl;cout << "v1的第一个元素为: " << v1.front() << endl;cout << "v1的最后一个元素为: " << v1.back() << endl;
}int main() {test01();system("pause");return 0;
}

总结:

  • 除了用迭代器获取vector容器中元素,[ ]和at也可以
  • front返回容器第一个元素
  • back返回容器最后一个元素

(7) vector互换容器

功能描述:

  • 实现两个容器内元素进行互换

函数原型:

  • swap(vec); // 将vec与本身的元素互换

示例:

#include <vector>void printVector(vector<int>& v) {for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << " ";}cout << endl;
}void test01()
{vector<int>v1;for (int i = 0; i < 10; i++){v1.push_back(i);}printVector(v1);vector<int>v2;for (int i = 10; i > 0; i--){v2.push_back(i);}printVector(v2);//互换容器cout << "互换后" << endl;v1.swap(v2);printVector(v1);printVector(v2);
}void test02()
{vector<int> v;for (int i = 0; i < 100000; i++) {v.push_back(i);}cout << "v的容量为:" << v.capacity() << endl;cout << "v的大小为:" << v.size() << endl;v.resize(3);cout << "v的容量为:" << v.capacity() << endl;cout << "v的大小为:" << v.size() << endl;//收缩内存vector<int>(v).swap(v); //匿名对象cout << "v的容量为:" << v.capacity() << endl;cout << "v的大小为:" << v.size() << endl;
}int main() {test01();test02();system("pause");return 0;
}

总结:swap可以使两个容器互换,可以达到实用的收缩内存效果

(8) vector预留空间

功能描述:

  • 减少vector在动态扩展容量时的扩展次数

函数原型:

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

示例:

#include <vector>void test01()
{vector<int> v;//预留空间v.reserve(100000);int num = 0;int* p = NULL;for (int i = 0; i < 100000; i++) {v.push_back(i);if (p != &v[0]) {p = &v[0];num++;}}cout << "num:" << num << endl;
}int main() {test01();system("pause");return 0;
}

总结:如果数据量较大,可以一开始利用reserve预留空间

5.3 deque容器

(1) deque容器基本概念

功能:

  • 双端数组,可以对头端进行插入删除操作

deque与vector区别:

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

在这里插入图片描述

deque内部工作原理:

deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据

中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间

在这里插入图片描述

  • deque容器的迭代器也是支持随机访问的

(2) deque构造函数

功能描述:

  • deque容器构造

函数原型:

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

示例:

#include <deque>void printDeque(const deque<int>& d) 
{for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {cout << *it << " ";}cout << endl;
}
//deque构造
void test01() {deque<int> d1; //无参构造函数for (int i = 0; i < 10; i++){d1.push_back(i);}printDeque(d1);deque<int> d2(d1.begin(),d1.end());printDeque(d2);deque<int>d3(10,100);printDeque(d3);deque<int>d4 = d3;printDeque(d4);
}int main() {test01();system("pause");return 0;
}

**总结:**deque容器和vector容器的构造方式几乎一致,灵活使用即可

(3) deque赋值操作

功能描述:

  • 给deque容器进行赋值

函数原型:

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

  • assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。

  • assign(n, elem); //将n个elem拷贝赋值给本身。

示例:

#include <deque>void printDeque(const deque<int>& d) 
{for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {cout << *it << " ";}cout << endl;
}
//赋值操作
void test01()
{deque<int> d1;for (int i = 0; i < 10; i++){d1.push_back(i);}printDeque(d1);deque<int>d2;d2 = d1;printDeque(d2);deque<int>d3;d3.assign(d1.begin(), d1.end());printDeque(d3);deque<int>d4;d4.assign(10, 100);printDeque(d4);}int main() {test01();system("pause");return 0;
}

总结:deque赋值操作也与vector相同,需熟练掌握

(4) deque大小操作

功能描述:

  • 对deque容器的大小进行操作

函数原型:

  • deque.empty(); //判断容器是否为空

  • deque.size(); //返回容器中元素的个数

  • deque.resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

  • deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

示例:

#include <deque>void printDeque(const deque<int>& d) 
{for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {cout << *it << " ";}cout << endl;
}//大小操作
void test01()
{deque<int> d1;for (int i = 0; i < 10; i++){d1.push_back(i);}printDeque(d1);//判断容器是否为空if (d1.empty()) {cout << "d1为空!" << endl;}else {cout << "d1不为空!" << endl;//统计大小cout << "d1的大小为:" << d1.size() << endl;}//重新指定大小d1.resize(15, 1);printDeque(d1);d1.resize(5);printDeque(d1);
}int main() {test01();system("pause");return 0;
}

总结:

  • deque没有容量的概念
  • 判断是否为空 — empty
  • 返回元素个数 — size
  • 重新指定个数 — resize

(5) deque 插入和删除

功能描述:

  • 向deque容器中插入和删除数据

函数原型:

两端插入操作:

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

指定位置操作:

  • insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置。

  • insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值。

  • insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值。

  • clear(); //清空容器的所有数据

  • erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置。

  • erase(pos); //删除pos位置的数据,返回下一个数据的位置。

示例:

#include <deque>void printDeque(const deque<int>& d) 
{for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {cout << *it << " ";}cout << endl;
}
//两端操作
void test01()
{deque<int> d;//尾插d.push_back(10);d.push_back(20);//头插d.push_front(100);d.push_front(200);printDeque(d);//尾删d.pop_back();//头删d.pop_front();printDeque(d);
}//插入
void test02()
{deque<int> d;d.push_back(10);d.push_back(20);d.push_front(100);d.push_front(200);printDeque(d);d.insert(d.begin(), 1000);printDeque(d);d.insert(d.begin(), 2,10000);printDeque(d);deque<int>d2;d2.push_back(1);d2.push_back(2);d2.push_back(3);d.insert(d.begin(), d2.begin(), d2.end());printDeque(d);}//删除
void test03()
{deque<int> d;d.push_back(10);d.push_back(20);d.push_front(100);d.push_front(200);printDeque(d);d.erase(d.begin());printDeque(d);d.erase(d.begin(), d.end());d.clear();printDeque(d);
}int main() {//test01();//test02();test03();system("pause");return 0;
}

总结:

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

(6) deque 数据存取

功能描述:

  • 对deque 中的数据的存取操作

函数原型:

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

示例:

#include <deque>void printDeque(const deque<int>& d) 
{for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {cout << *it << " ";}cout << endl;
}//数据存取
void test01()
{deque<int> d;d.push_back(10);d.push_back(20);d.push_front(100);d.push_front(200);for (int i = 0; i < d.size(); i++) {cout << d[i] << " ";}cout << endl;for (int i = 0; i < d.size(); i++) {cout << d.at(i) << " ";}cout << endl;cout << "front:" << d.front() << endl;cout << "back:" << d.back() << endl;}int main() {test01();system("pause");return 0;
}

总结:

  • 除了用迭代器获取deque容器中元素,[ ]和at也可以
  • front返回容器第一个元素
  • back返回容器最后一个元素

(7) deque 排序

功能描述:

  • 利用算法实现对deque容器进行排序

算法:

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

示例:

#include <deque>
#include <algorithm>void printDeque(const deque<int>& d) 
{for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {cout << *it << " ";}cout << endl;
}void test01()
{deque<int> d;d.push_back(10);d.push_back(20);d.push_front(100);d.push_front(200);printDeque(d);sort(d.begin(), d.end());printDeque(d);}int main() {test01();system("pause");return 0;
}

总结:sort算法非常实用,使用时包含头文件 algorithm即可

5.4 stack容器

(1) stack 基本概念

概念:stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口

在这里插入图片描述

栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为

栈中进入数据称为 — 入栈 push

栈中弹出数据称为 — 出栈 pop

生活中的栈:

在这里插入图片描述

在这里插入图片描述

(2) stack 常用接口

功能描述:栈容器常用的对外接口

构造函数:

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

赋值操作:

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

数据存取:

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

大小操作:

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

示例:

#include <stack>//栈容器常用接口
void test01()
{//创建栈容器 栈容器必须符合先进后出stack<int> s;//向栈中添加元素,叫做 压栈 入栈s.push(10);s.push(20);s.push(30);while (!s.empty()) {//输出栈顶元素cout << "栈顶元素为: " << s.top() << endl;//弹出栈顶元素s.pop();}cout << "栈的大小为:" << s.size() << endl;}int main() {test01();system("pause");return 0;
}

总结:

  • 入栈 — push
  • 出栈 — pop
  • 返回栈顶 — top
  • 判断栈是否为空 — empty
  • 返回栈大小 — size

5.5 queue 容器

(1) queue 基本概念

概念:Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口

在这里插入图片描述

队列容器允许从一端新增元素,从另一端移除元素

队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为

队列中进数据称为 — 入队 push

队列中出数据称为 — 出队 pop

生活中的队列:

在这里插入图片描述

(2) queue 常用接口

功能描述:栈容器常用的对外接口

构造函数:

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

赋值操作:

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

数据存取:

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

大小操作:

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

示例:

#include <queue>
#include <string>
class Person
{
public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age;
};void test01() {//创建队列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);//队列不提供迭代器,更不支持随机访问	while (!q.empty()) {//输出队头元素cout << "队头元素-- 姓名: " << q.front().m_Name << " 年龄: "<< q.front().m_Age << endl;cout << "队尾元素-- 姓名: " << q.back().m_Name  << " 年龄: " << q.back().m_Age << endl;cout << endl;//弹出队头元素q.pop();}cout << "队列大小为:" << q.size() << endl;
}int main() {test01();system("pause");return 0;
}

总结:

  • 入队 — push
  • 出队 — pop
  • 返回队头元素 — front
  • 返回队尾元素 — back
  • 判断队是否为空 — empty
  • 返回队列大小 — size

5.6 list容器

(1) list基本概念

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

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

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

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

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

在这里插入图片描述

由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

list的优点:

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

list的缺点:

  • 链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大

List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。

总结:STL中List和vector是两个最常被使用的容器,各有优缺点

(2) list构造函数

功能描述:

  • 创建list容器

函数原型:

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

示例:

#include <list>void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}void test01()
{list<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);list<int>L4(10, 1000);printList(L4);
}int main() {test01();system("pause");return 0;
}

总结:list构造方式同其他几个STL常用容器,熟练掌握即可

(3) list 赋值和交换

功能描述:

  • 给list容器进行赋值,以及交换list容器

函数原型:

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

示例:

#include <list>void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}//赋值和交换
void test01()
{list<int>L1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);printList(L1);//赋值list<int>L2;L2 = L1;printList(L2);list<int>L3;L3.assign(L2.begin(), L2.end());printList(L3);list<int>L4;L4.assign(10, 100);printList(L4);}//交换
void test02()
{list<int>L1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);list<int>L2;L2.assign(10, 100);cout << "交换前: " << endl;printList(L1);printList(L2);cout << endl;L1.swap(L2);cout << "交换后: " << endl;printList(L1);printList(L2);}int main() {//test01();test02();system("pause");return 0;
}

总结:list赋值和交换操作能够灵活运用即可

(4) list 大小操作

功能描述:

  • 对list容器的大小进行操作

函数原型:

  • size(); //返回容器中元素的个数

  • empty(); //判断容器是否为空

  • resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

  • resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

      ​					    //如果容器变短,则末尾超出容器长度的元素被删除。
    

示例:

#include <list>void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}//大小操作
void test01()
{list<int>L1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);if (L1.empty()){cout << "L1为空" << endl;}else{cout << "L1不为空" << endl;cout << "L1的大小为: " << L1.size() << endl;}//重新指定大小L1.resize(10);printList(L1);L1.resize(2);printList(L1);
}int main() {test01();system("pause");return 0;
}

总结:

  • 判断是否为空 — empty
  • 返回元素个数 — size
  • 重新指定个数 — resize

(5) list 插入和删除

功能描述:

  • 对list容器进行数据的插入和删除

函数原型:

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

示例:

#include <list>void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}//插入和删除
void test01()
{list<int> L;//尾插L.push_back(10);L.push_back(20);L.push_back(30);//头插L.push_front(100);L.push_front(200);L.push_front(300);printList(L);//尾删L.pop_back();printList(L);//头删L.pop_front();printList(L);//插入list<int>::iterator it = L.begin();L.insert(++it, 1000);printList(L);//删除it = L.begin();L.erase(++it);printList(L);//移除L.push_back(10000);L.push_back(10000);L.push_back(10000);printList(L);L.remove(10000);printList(L);//清空L.clear();printList(L);
}int main() {test01();system("pause");return 0;
}

总结:

  • 尾插 — push_back
  • 尾删 — pop_back
  • 头插 — push_front
  • 头删 — pop_front
  • 插入 — insert
  • 删除 — erase
  • 移除 — remove
  • 清空 — clear

(6) list 数据存取

功能描述:

  • 对list容器中数据进行存取

函数原型:

  • front(); //返回第一个元素。
  • back(); //返回最后一个元素。

示例:

#include <list>//数据存取
void test01()
{list<int>L1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);//cout << L1.at(0) << endl;//错误 不支持at访问数据//cout << L1[0] << endl; //错误  不支持[]方式访问数据cout << "第一个元素为: " << L1.front() << endl;cout << "最后一个元素为: " << L1.back() << endl;//list容器的迭代器是双向迭代器,不支持随机访问list<int>::iterator it = L1.begin();//it = it + 1;//错误,不可以跳跃访问,即使是+1
}int main() {test01();system("pause");return 0;
}

总结:

  • list容器中不可以通过[]或者at方式访问数据
  • 返回第一个元素 — front
  • 返回最后一个元素 — back

(7) list 反转和排序

功能描述:

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

函数原型:

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

示例:

void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}bool myCompare(int val1 , int val2)
{return val1 > val2;
}//反转和排序
void test01()
{list<int> L;L.push_back(90);L.push_back(30);L.push_back(20);L.push_back(70);printList(L);//反转容器的元素L.reverse();printList(L);//排序L.sort(); //默认的排序规则 从小到大printList(L);L.sort(myCompare); //指定规则,从大到小printList(L);
}int main() {test01();system("pause");return 0;
}

总结:

  • 反转 — reverse
  • 排序 — sort (成员函数)

(8) 排序案例

案例描述:将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高

排序规则:按照年龄进行升序,如果年龄相同按照身高进行降序

示例:

#include <list>
#include <string>
class Person {
public:Person(string name, int age , int height) {m_Name = name;m_Age = age;m_Height = height;}public:string m_Name;  //姓名int m_Age;      //年龄int m_Height;   //身高
};bool ComparePerson(Person& p1, Person& p2) {if (p1.m_Age == p2.m_Age) {return p1.m_Height  > p2.m_Height;}else{return  p1.m_Age < p2.m_Age;}}void test01() {list<Person> L;Person p1("刘备", 35 , 175);Person p2("曹操", 45 , 180);Person p3("孙权", 40 , 170);Person p4("赵云", 25 , 190);Person p5("张飞", 35 , 160);Person p6("关羽", 35 , 200);L.push_back(p1);L.push_back(p2);L.push_back(p3);L.push_back(p4);L.push_back(p5);L.push_back(p6);for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << " 身高: " << it->m_Height << endl;}cout << "---------------------------------" << endl;L.sort(ComparePerson); //排序for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << " 身高: " << it->m_Height << endl;}
}int main() {test01();system("pause");return 0;
}

总结:

  • 对于自定义数据类型,必须要指定排序规则,否则编译器不知道如何进行排序

  • 高级排序只是在排序规则上再进行一次逻辑规则制定,并不复杂

5.7 set/ multiset 容器

(1) set基本概念

简介:

  • 所有元素都会在插入时自动被排序

本质:

  • set/multiset属于关联式容器,底层结构是用二叉树实现。

set和multiset区别

  • set不允许容器中有重复的元素
  • multiset允许容器中有重复的元素

(2) set构造和赋值

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

构造:

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

赋值:

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

示例:

#include <set>void printSet(set<int> & s)
{for (set<int>::iterator it = s.begin(); it != s.end(); it++){cout << *it << " ";}cout << endl;
}//构造和赋值
void test01()
{set<int> s1;s1.insert(10);s1.insert(30);s1.insert(20);s1.insert(40);printSet(s1);//拷贝构造set<int>s2(s1);printSet(s2);//赋值set<int>s3;s3 = s2;printSet(s3);
}int main() {test01();system("pause");return 0;
}

总结:

  • set容器插入数据时用insert
  • set容器插入数据的数据会自动排序

(3) set大小和交换

功能描述:

  • 统计set容器大小以及交换set容器

函数原型:

  • size(); //返回容器中元素的数目
  • empty(); //判断容器是否为空
  • swap(st); //交换两个集合容器

示例:

#include <set>void printSet(set<int> & s)
{for (set<int>::iterator it = s.begin(); it != s.end(); it++){cout << *it << " ";}cout << endl;
}//大小
void test01()
{set<int> s1;s1.insert(10);s1.insert(30);s1.insert(20);s1.insert(40);if (s1.empty()){cout << "s1为空" << endl;}else{cout << "s1不为空" << endl;cout << "s1的大小为: " << s1.size() << endl;}}//交换
void test02()
{set<int> s1;s1.insert(10);s1.insert(30);s1.insert(20);s1.insert(40);set<int> s2;s2.insert(100);s2.insert(300);s2.insert(200);s2.insert(400);cout << "交换前" << endl;printSet(s1);printSet(s2);cout << endl;cout << "交换后" << endl;s1.swap(s2);printSet(s1);printSet(s2);
}int main() {//test01();test02();system("pause");return 0;
}

总结:

  • 统计大小 — size
  • 判断是否为空 — empty
  • 交换容器 — swap

(4) set插入和删除

功能描述:

  • set容器进行插入数据和删除数据

函数原型:

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

示例:

#include <set>void printSet(set<int> & s)
{for (set<int>::iterator it = s.begin(); it != s.end(); it++){cout << *it << " ";}cout << endl;
}//插入和删除
void test01()
{set<int> s1;//插入s1.insert(10);s1.insert(30);s1.insert(20);s1.insert(40);printSet(s1);//删除s1.erase(s1.begin());printSet(s1);s1.erase(30);printSet(s1);//清空//s1.erase(s1.begin(), s1.end());s1.clear();printSet(s1);
}int main() {test01();system("pause");return 0;
}

总结:

  • 插入 — insert
  • 删除 — erase
  • 清空 — clear

(5) set查找和统计

功能描述:

  • 对set容器进行查找数据以及统计数据

函数原型:

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

示例:

#include <set>//查找和统计
void test01()
{set<int> s1;//插入s1.insert(10);s1.insert(30);s1.insert(20);s1.insert(40);//查找set<int>::iterator pos = s1.find(30);if (pos != s1.end()){cout << "找到了元素 : " << *pos << endl;}else{cout << "未找到元素" << endl;}//统计int num = s1.count(30);cout << "num = " << num << endl;
}int main() {test01();system("pause");return 0;
}

总结:

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

(6) set和multiset区别

学习目标:

  • 掌握set和multiset的区别

区别:

  • set不可以插入重复数据,而multiset可以
  • set插入数据的同时会返回插入结果,表示插入是否成功
  • multiset不会检测数据,因此可以插入重复数据

示例:

#include <set>//set和multiset区别
void test01()
{set<int> s;pair<set<int>::iterator, bool>  ret = s.insert(10);if (ret.second) {cout << "第一次插入成功!" << endl;}else {cout << "第一次插入失败!" << endl;}ret = s.insert(10);if (ret.second) {cout << "第二次插入成功!" << endl;}else {cout << "第二次插入失败!" << endl;}//multisetmultiset<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() {test01();system("pause");return 0;
}

总结:

  • 如果不允许插入重复数据可以利用set
  • 如果需要插入重复数据利用multiset

(7) pair对组创建

功能描述:

  • 成对出现的数据,利用对组可以返回两个数据

两种创建方式:

  • pair<type, type> p ( value1, value2 );
  • pair<type, type> p = make_pair( value1, value2 );

示例:

#include <string>//对组创建
void test01()
{pair<string, int> p(string("Tom"), 20);cout << "姓名: " <<  p.first << " 年龄: " << p.second << endl;pair<string, int> p2 = make_pair("Jerry", 10);cout << "姓名: " << p2.first << " 年龄: " << p2.second << endl;
}int main() {test01();system("pause");return 0;
}

总结:

两种方式都可以创建对组,记住一种即可

(8) set容器排序

学习目标:

  • set容器默认排序规则为从小到大,掌握如何改变排序规则

主要技术点:

  • 利用仿函数,可以改变排序规则

示例一 set存放内置数据类型

#include <set>class MyCompare 
{
public:bool operator()(int v1, int v2) {return v1 > v2;}
};
void test01() 
{    set<int> s1;s1.insert(10);s1.insert(40);s1.insert(20);s1.insert(30);s1.insert(50);//默认从小到大for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {cout << *it << " ";}cout << endl;//指定排序规则set<int,MyCompare> s2;s2.insert(10);s2.insert(40);s2.insert(20);s2.insert(30);s2.insert(50);for (set<int, MyCompare>::iterator it = s2.begin(); it != s2.end(); it++) {cout << *it << " ";}cout << endl;
}int main() {test01();system("pause");return 0;
}

总结:利用仿函数可以指定set容器的排序规则

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

#include <set>
#include <string>class Person
{
public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age;};
class comparePerson
{
public:bool operator()(const Person& p1, const Person &p2){//按照年龄进行排序  降序return p1.m_Age > p2.m_Age;}
};void test01()
{set<Person, comparePerson> s;Person p1("刘备", 23);Person p2("关羽", 27);Person p3("张飞", 25);Person p4("赵云", 21);s.insert(p1);s.insert(p2);s.insert(p3);s.insert(p4);for (set<Person, comparePerson>::iterator it = s.begin(); it != s.end(); it++){cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << endl;}
}
int main() {test01();system("pause");return 0;
}

总结:

对于自定义数据类型,set必须指定排序规则才可以插入数据

5.8 map/ multimap容器

(1) map基本概念

简介:

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

本质:

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

优点:

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

map和multimap区别

  • map不允许容器中有重复key值元素
  • multimap允许容器中有重复key值元素

(2) map构造和赋值

功能描述:

  • 对map容器进行构造和赋值操作

函数原型:

构造:

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

赋值:

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

示例:

#include <map>void printMap(map<int,int>&m)
{for (map<int, int>::iterator it = m.begin(); it != m.end(); it++){cout << "key = " << it->first << " value = " << it->second << endl;}cout << endl;
}void test01()
{map<int,int>m; //默认构造m.insert(pair<int, int>(1, 10));m.insert(pair<int, int>(2, 20));m.insert(pair<int, int>(3, 30));printMap(m);map<int, int>m2(m); //拷贝构造printMap(m2);map<int, int>m3;m3 = m2; //赋值printMap(m3);
}int main() {test01();system("pause");return 0;
}

总结:map中所有元素都是成对出现,插入数据时候要使用对组

(3) map大小和交换

功能描述:

  • 统计map容器大小以及交换map容器

函数原型:

  • size(); //返回容器中元素的数目
  • empty(); //判断容器是否为空
  • swap(st); //交换两个集合容器

示例:

#include <map>void printMap(map<int,int>&m)
{for (map<int, int>::iterator it = m.begin(); it != m.end(); it++){cout << "key = " << it->first << " value = " << it->second << endl;}cout << endl;
}void test01()
{map<int, int>m;m.insert(pair<int, int>(1, 10));m.insert(pair<int, int>(2, 20));m.insert(pair<int, int>(3, 30));if (m.empty()){cout << "m为空" << endl;}else{cout << "m不为空" << endl;cout << "m的大小为: " << m.size() << endl;}
}//交换
void test02()
{map<int, int>m;m.insert(pair<int, int>(1, 10));m.insert(pair<int, int>(2, 20));m.insert(pair<int, int>(3, 30));map<int, int>m2;m2.insert(pair<int, int>(4, 100));m2.insert(pair<int, int>(5, 200));m2.insert(pair<int, int>(6, 300));cout << "交换前" << endl;printMap(m);printMap(m2);cout << "交换后" << endl;m.swap(m2);printMap(m);printMap(m2);
}int main() {test01();test02();system("pause");return 0;
}

总结:

  • 统计大小 — size
  • 判断是否为空 — empty
  • 交换容器 — swap

(4) map插入和删除

功能描述:

  • map容器进行插入数据和删除数据

函数原型:

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

示例:

#include <map>void printMap(map<int,int>&m)
{for (map<int, int>::iterator it = m.begin(); it != m.end(); it++){cout << "key = " << it->first << " value = " << it->second << endl;}cout << endl;
}void test01()
{//插入map<int, int> m;//第一种插入方式m.insert(pair<int, int>(1, 10));//第二种插入方式m.insert(make_pair(2, 20));//第三种插入方式m.insert(map<int, int>::value_type(3, 30));//第四种插入方式m[4] = 40; printMap(m);//删除m.erase(m.begin());printMap(m);m.erase(3);printMap(m);//清空m.erase(m.begin(),m.end());m.clear();printMap(m);
}int main() {test01();system("pause");return 0;
}

总结:

  • map插入方式很多,记住其一即可
  • 插入 — insert
  • 删除 — erase
  • 清空 — clear

(5) map查找和统计

功能描述:

  • 对map容器进行查找数据以及统计数据

函数原型:

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

示例:

#include <map>//查找和统计
void test01()
{map<int, int>m; m.insert(pair<int, int>(1, 10));m.insert(pair<int, int>(2, 20));m.insert(pair<int, int>(3, 30));//查找map<int, int>::iterator pos = m.find(3);if (pos != m.end()){cout << "找到了元素 key = " << (*pos).first << " value = " << (*pos).second << endl;}else{cout << "未找到元素" << endl;}//统计int num = m.count(3);cout << "num = " << num << endl;
}int main() {test01();system("pause");return 0;
}

总结:

  • 查找 — find (返回的是迭代器)
  • 统计 — count (对于map,结果为0或者1)

(6) map容器排序

学习目标:

  • map容器默认排序规则为 按照key值进行 从小到大排序,掌握如何改变排序规则

主要技术点:

  • 利用仿函数,可以改变排序规则

示例:

#include <map>class MyCompare {
public:bool operator()(int v1, int v2) {return v1 > v2;}
};void test01() 
{//默认从小到大排序//利用仿函数实现从大到小排序map<int, int, MyCompare> m;m.insert(make_pair(1, 10));m.insert(make_pair(2, 20));m.insert(make_pair(3, 30));m.insert(make_pair(4, 40));m.insert(make_pair(5, 50));for (map<int, int, MyCompare>::iterator it = m.begin(); it != m.end(); it++) {cout << "key:" << it->first << " value:" << it->second << endl;}
}
int main() {test01();system("pause");return 0;
}

总结:

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

6、 STL- 函数对象

6.1 函数对象

(1) 函数对象概念

概念:

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

本质:

函数对象(仿函数)是一个,不是一个函数

(2) 函数对象使用

特点:

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

示例:

#include <string>//1、函数对象在使用时,可以像普通函数那样调用, 可以有参数,可以有返回值
class MyAdd
{
public :int operator()(int v1,int v2){return v1 + v2;}
};void test01()
{MyAdd myAdd;cout << myAdd(10, 10) << endl;
}//2、函数对象可以有自己的状态
class MyPrint
{
public:MyPrint(){count = 0;}void operator()(string test){cout << test << endl;count++; //统计使用次数}int count; //内部自己的状态
};
void test02()
{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 test03()
{MyPrint myPrint;doPrint(myPrint, "Hello C++");
}int main() {//test01();//test02();test03();system("pause");return 0;
}

总结:

  • 仿函数写法非常灵活,可以作为参数进行传递。

6.2 谓词

(1) 谓词概念

概念:

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

(2) 一元谓词

示例:

#include <vector>
#include <algorithm>//1.一元谓词
struct GreaterFive{bool operator()(int val) {return val > 5;}
};void test01() {vector<int> v;for (int i = 0; i < 10; i++){v.push_back(i);}vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());if (it == v.end()) {cout << "没找到!" << endl;}else {cout << "找到:" << *it << endl;}}int main() {test01();system("pause");return 0;
}

总结:参数只有一个的谓词,称为一元谓词

(3) 二元谓词

示例:

#include <vector>
#include <algorithm>
//二元谓词
class MyCompare
{
public:bool operator()(int num1, int num2){return num1 > num2;}
};void test01()
{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 << " ";}cout << endl;cout << "----------------------------" << endl;//使用函数对象改变算法策略,排序从大到小sort(v.begin(), v.end(), MyCompare());for (vector<int>::iterator it = v.begin(); it != v.end(); it++){cout << *it << " ";}cout << endl;
}int main() {test01();system("pause");return 0;
}

总结:参数只有两个的谓词,称为二元谓词

6.3 内建函数对象

(1) 内建函数对象意义

概念:

  • STL内建了一些函数对象

分类:

  • 算术仿函数

  • 关系仿函数

  • 逻辑仿函数

用法:

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

(2) 算术仿函数

功能描述:

  • 实现四则运算
  • 其中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> //取反仿函数

示例:

#include <functional>
//negate
void test01()
{negate<int> n;cout << n(50) << endl;
}//plus
void test02()
{plus<int> p;cout << p(10, 20) << endl;
}int main() {test01();test02();system("pause");return 0;
}

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

(3) 关系仿函数

功能描述:

  • 实现关系对比

仿函数原型:

  • template<class T> bool equal_to<T> //等于
  • template<class T> bool not_equal_to<T> //不等于
  • template<class T> bool greater<T> //大于
  • template<class T> bool greater_equal<T> //大于等于
  • template<class T> bool less<T> //小于
  • template<class T> bool less_equal<T> //小于等于

示例:

#include <functional>
#include <vector>
#include <algorithm>class MyCompare
{
public:bool operator()(int v1,int v2){return v1 > v2;}
};
void test01()
{vector<int> v;v.push_back(10);v.push_back(30);v.push_back(50);v.push_back(40);v.push_back(20);for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << " ";}cout << endl;//自己实现仿函数//sort(v.begin(), v.end(), MyCompare());//STL内建仿函数  大于仿函数sort(v.begin(), v.end(), greater<int>());for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << " ";}cout << endl;
}int main() {test01();system("pause");return 0;
}

总结:关系仿函数中最常用的就是greater<>大于

(4)逻辑仿函数

功能描述:

  • 实现逻辑运算

函数原型:

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

示例:

#include <vector>
#include <functional>
#include <algorithm>
void test01()
{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() {test01();system("pause");return 0;
}

总结:逻辑仿函数实际应用较少,了解即可

7、 STL- 常用算法

概述:

  • 算法主要是由头文件<algorithm> <functional> <numeric>组成。

  • <algorithm>是所有STL头文件中最大的一个,范围涉及到比较、 交换、查找、遍历操作、复制、修改等等

  • <numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数

  • <functional>定义了一些模板类,用以声明函数对象。

7.1 常用遍历算法

学习目标:

  • 掌握常用的遍历算法

算法简介:

  • for_each //遍历容器
  • transform //搬运容器到另一个容器中

(1) for_each

功能描述:

  • 实现遍历容器

函数原型:

  • for_each(iterator beg, iterator end, _func);

    // 遍历算法 遍历容器元素

    // beg 开始迭代器

    // end 结束迭代器

    // _func 函数或者函数对象

示例:

#include <algorithm>
#include <vector>//普通函数
void print01(int val) 
{cout << val << " ";
}
//函数对象
class print02 
{public:void operator()(int val) {cout << val << " ";}
};//for_each算法基本用法
void test01() {vector<int> v;for (int i = 0; i < 10; i++) {v.push_back(i);}//遍历算法for_each(v.begin(), v.end(), print01);cout << endl;for_each(v.begin(), v.end(), print02());cout << endl;
}int main() {test01();system("pause");return 0;
}

**总结:**for_each在实际开发中是最常用遍历算法,需要熟练掌握

(2) transform

功能描述:

  • 搬运容器到另一个容器中

函数原型:

  • transform(iterator beg1, iterator end1, iterator beg2, _func);

//beg1 源容器开始迭代器

//end1 源容器结束迭代器

//beg2 目标容器开始迭代器

//_func 函数或者函数对象

示例:

#include<vector>
#include<algorithm>//常用遍历算法  搬运 transformclass TransForm
{
public:int operator()(int val){return val;}};class MyPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{vector<int>v;for (int i = 0; i < 10; i++){v.push_back(i);}vector<int>vTarget; //目标容器vTarget.resize(v.size()); // 目标容器需要提前开辟空间transform(v.begin(), v.end(), vTarget.begin(), TransForm());for_each(vTarget.begin(), vTarget.end(), MyPrint());
}int main() {test01();system("pause");return 0;
}

总结: 搬运的目标容器必须要提前开辟空间,否则无法正常搬运

7.2 常用查找算法

学习目标:

  • 掌握常用的查找算法

算法简介:

  • find //查找元素
  • find_if //按条件查找元素
  • adjacent_find //查找相邻重复元素
  • binary_search //二分查找法
  • count //统计元素个数
  • count_if //按条件统计元素个数

(1) find

功能描述:

  • 查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()

函数原型:

  • find(iterator beg, iterator end, value);

    // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

    // beg 开始迭代器

    // end 结束迭代器

    // value 查找的元素

示例:

#include <algorithm>
#include <vector>
#include <string>
void test01() {vector<int> v;for (int i = 0; i < 10; i++) {v.push_back(i + 1);}//查找容器中是否有 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;}//重载==bool operator==(const Person& p) {if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {return true;}return false;}public:string m_Name;int m_Age;
};void test02() {vector<Person> v;//创建数据Person p1("aaa", 10);Person p2("bbb", 20);Person p3("ccc", 30);Person p4("ddd", 40);v.push_back(p1);v.push_back(p2);v.push_back(p3);v.push_back(p4);vector<Person>::iterator it = find(v.begin(), v.end(), p2);if (it == v.end()) {cout << "没有找到!" << endl;}else {cout << "找到姓名:" << it->m_Name << " 年龄: " << it->m_Age << endl;}
}

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

(2) find_if

功能描述:

  • 按条件查找元素

函数原型:

  • find_if(iterator beg, iterator end, _Pred);

    // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

    // beg 开始迭代器

    // end 结束迭代器

    // _Pred 函数或者谓词(返回bool类型的仿函数)

示例:

#include <algorithm>
#include <vector>
#include <string>//内置数据类型
class GreaterFive
{
public:bool operator()(int val){return val > 5;}
};void test01() {vector<int> v;for (int i = 0; i < 10; i++) {v.push_back(i + 1);}vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());if (it == v.end()) {cout << "没有找到!" << endl;}else {cout << "找到大于5的数字:" << *it << endl;}
}//自定义数据类型
class Person {
public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}
public:string m_Name;int m_Age;
};class Greater20
{
public:bool operator()(Person &p){return p.m_Age > 20;}};void test02() {vector<Person> v;//创建数据Person p1("aaa", 10);Person p2("bbb", 20);Person p3("ccc", 30);Person p4("ddd", 40);v.push_back(p1);v.push_back(p2);v.push_back(p3);v.push_back(p4);vector<Person>::iterator it = find_if(v.begin(), v.end(), Greater20());if (it == v.end()){cout << "没有找到!" << endl;}else{cout << "找到姓名:" << it->m_Name << " 年龄: " << it->m_Age << endl;}
}int main() {//test01();test02();system("pause");return 0;
}

总结:find_if按条件查找使查找更加灵活,提供的仿函数可以改变不同的策略

(3) adjacent_find

功能描述:

  • 查找相邻重复元素

函数原型:

  • adjacent_find(iterator beg, iterator end);

    // 查找相邻重复元素,返回相邻元素的第一个位置的迭代器

    // beg 开始迭代器

    // end 结束迭代器

示例:

#include <algorithm>
#include <vector>void test01()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(5);v.push_back(2);v.push_back(4);v.push_back(4);v.push_back(3);//查找相邻重复元素vector<int>::iterator it = adjacent_find(v.begin(), v.end());if (it == v.end()) {cout << "找不到!" << endl;}else {cout << "找到相邻重复元素为:" << *it << endl;}
}

总结:面试题中如果出现查找相邻重复元素,记得用STL中的adjacent_find算法

(4) binary_search

功能描述:

  • 查找指定元素是否存在

函数原型:

  • bool binary_search(iterator beg, iterator end, value);

    // 查找指定的元素,查到 返回true 否则false

    // 注意: 在无序序列中不可用

    // beg 开始迭代器

    // end 结束迭代器

    // value 查找的元素

示例:

#include <algorithm>
#include <vector>void test01()
{vector<int>v;for (int i = 0; i < 10; i++){v.push_back(i);}//二分查找bool ret = binary_search(v.begin(), v.end(),2);if (ret){cout << "找到了" << endl;}else{cout << "未找到" << endl;}
}int main() {test01();system("pause");return 0;
}

**总结:**二分查找法查找效率很高,值得注意的是查找的容器中元素必须的有序序列

(5) count

功能描述:

  • 统计元素个数

函数原型:

  • count(iterator beg, iterator end, value);

    // 统计元素出现次数

    // beg 开始迭代器

    // end 结束迭代器

    // value 统计的元素

示例:

#include <algorithm>
#include <vector>//内置数据类型
void test01()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(4);v.push_back(5);v.push_back(3);v.push_back(4);v.push_back(4);int num = count(v.begin(), v.end(), 4);cout << "4的个数为: " << num << endl;
}//自定义数据类型
class Person
{
public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}bool operator==(const Person & p){if (this->m_Age == p.m_Age){return true;}else{return false;}}string m_Name;int m_Age;
};void test02()
{vector<Person> v;Person p1("刘备", 35);Person p2("关羽", 35);Person p3("张飞", 35);Person p4("赵云", 30);Person p5("曹操", 25);v.push_back(p1);v.push_back(p2);v.push_back(p3);v.push_back(p4);v.push_back(p5);Person p("诸葛亮",35);int num = count(v.begin(), v.end(), p);cout << "num = " << num << endl;
}
int main() {//test01();test02();system("pause");return 0;
}

总结: 统计自定义数据类型时候,需要配合重载 operator==

(6) count_if

功能描述:

  • 按条件统计元素个数

函数原型:

  • count_if(iterator beg, iterator end, _Pred);

    // 按条件统计元素出现次数

    // beg 开始迭代器

    // end 结束迭代器

    // _Pred 谓词

示例:

#include <algorithm>
#include <vector>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(4);v.push_back(5);v.push_back(3);v.push_back(4);v.push_back(4);int num = count_if(v.begin(), v.end(), Greater4());cout << "大于4的个数为: " << num << endl;
}//自定义数据类型
class Person
{
public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age;
};class AgeLess35
{
public:bool operator()(const Person &p){return p.m_Age < 35;}
};
void test02()
{vector<Person> v;Person p1("刘备", 35);Person p2("关羽", 35);Person p3("张飞", 35);Person p4("赵云", 30);Person p5("曹操", 25);v.push_back(p1);v.push_back(p2);v.push_back(p3);v.push_back(p4);v.push_back(p5);int num = count_if(v.begin(), v.end(), AgeLess35());cout << "小于35岁的个数:" << num << endl;
}int main() {//test01();test02();system("pause");return 0;
}

**总结:**按值统计用count,按条件统计用count_if

7.3 常用排序算法

学习目标:

  • 掌握常用的排序算法

算法简介:

  • sort //对容器内元素进行排序
  • random_shuffle //洗牌 指定范围内的元素随机调整次序
  • merge // 容器元素合并,并存储到另一容器中
  • reverse // 反转指定范围的元素

(1) 5.3.1 sort

功能描述:

  • 对容器内元素进行排序

函数原型:

  • sort(iterator beg, iterator end, _Pred);

    // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

    // beg 开始迭代器

    // end 结束迭代器

    // _Pred 谓词

示例:

#include <algorithm>
#include <vector>void myPrint(int val)
{cout << val << " ";
}void test01() {vector<int> v;v.push_back(10);v.push_back(30);v.push_back(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() {test01();system("pause");return 0;
}

**总结:**sort属于开发中最常用的算法之一,需熟练掌握

(2) random_shuffle

功能描述:

  • 洗牌 指定范围内的元素随机调整次序

函数原型:

  • random_shuffle(iterator beg, iterator end);

    // 指定范围内的元素随机调整次序

    // beg 开始迭代器

    // end 结束迭代器

示例:

#include <algorithm>
#include <vector>
#include <ctime>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{srand((unsigned int)time(NULL));vector<int> v;for(int i = 0 ; i < 10;i++){v.push_back(i);}for_each(v.begin(), v.end(), myPrint());cout << endl;//打乱顺序random_shuffle(v.begin(), v.end());for_each(v.begin(), v.end(), myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

**总结:**random_shuffle洗牌算法比较实用,使用时记得加随机数种子

(3) merge

功能描述:

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

函数原型:

  • merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

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

    // 注意: 两个容器必须是有序的

    // beg1 容器1开始迭代器
    // end1 容器1结束迭代器
    // beg2 容器2开始迭代器
    // end2 容器2结束迭代器
    // dest 目标容器开始迭代器

示例:

#include <algorithm>
#include <vector>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{vector<int> v1;vector<int> v2;for (int i = 0; i < 10 ; i++) {v1.push_back(i);v2.push_back(i + 1);}vector<int> vtarget;//目标容器需要提前开辟空间vtarget.resize(v1.size() + v2.size());//合并  需要两个有序序列merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vtarget.begin());for_each(vtarget.begin(), vtarget.end(), myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

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

(4) reverse

功能描述:

  • 将容器内元素进行反转

函数原型:

  • reverse(iterator beg, iterator end);

    // 反转指定范围的元素

    // beg 开始迭代器

    // end 结束迭代器

示例:

#include <algorithm>
#include <vector>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{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(), myPrint());cout << endl;cout << "反转后: " << endl;reverse(v.begin(), v.end());for_each(v.begin(), v.end(), myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

**总结:**reverse反转区间内元素,面试题可能涉及到

7.4 常用拷贝和替换算法

学习目标:

  • 掌握常用的拷贝和替换算法

算法简介:

  • copy // 容器内指定范围的元素拷贝到另一容器中
  • replace // 将容器内指定范围的旧元素修改为新元素
  • replace_if // 容器内指定范围满足条件的元素替换为新元素
  • swap // 互换两个容器的元素

(1) copy

功能描述:

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

函数原型:

  • copy(iterator beg, iterator end, iterator dest);

    // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

    // beg 开始迭代器

    // end 结束迭代器

    // dest 目标起始迭代器

示例:

#include <algorithm>
#include <vector>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{vector<int> v1;for (int i = 0; i < 10; i++) {v1.push_back(i + 1);}vector<int> v2;v2.resize(v1.size());copy(v1.begin(), v1.end(), v2.begin());for_each(v2.begin(), v2.end(), myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

**总结:**利用copy算法在拷贝时,目标容器记得提前开辟空间

(2) replace

功能描述:

  • 将容器内指定范围的旧元素修改为新元素

函数原型:

  • replace(iterator beg, iterator end, oldvalue, newvalue);

    // 将区间内旧元素 替换成 新元素

    // beg 开始迭代器

    // end 结束迭代器

    // oldvalue 旧元素

    // newvalue 新元素

示例:

#include <algorithm>
#include <vector>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{vector<int> v;v.push_back(20);v.push_back(30);v.push_back(20);v.push_back(40);v.push_back(50);v.push_back(10);v.push_back(20);cout << "替换前:" << endl;for_each(v.begin(), v.end(), myPrint());cout << endl;//将容器中的20 替换成 2000cout << "替换后:" << endl;replace(v.begin(), v.end(), 20,2000);for_each(v.begin(), v.end(), myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

**总结:**replace会替换区间内满足条件的元素

(3) replace_if

功能描述:

  • 将区间内满足条件的元素,替换成指定元素

函数原型:

  • replace_if(iterator beg, iterator end, _pred, newvalue);

    // 按条件替换元素,满足条件的替换成指定元素

    // beg 开始迭代器

    // end 结束迭代器

    // _pred 谓词

    // newvalue 替换的新元素

示例:

#include <algorithm>
#include <vector>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};class ReplaceGreater30
{
public:bool operator()(int val){return val >= 30;}};void test01()
{vector<int> v;v.push_back(20);v.push_back(30);v.push_back(20);v.push_back(40);v.push_back(50);v.push_back(10);v.push_back(20);cout << "替换前:" << endl;for_each(v.begin(), v.end(), myPrint());cout << endl;//将容器中大于等于的30 替换成 3000cout << "替换后:" << endl;replace_if(v.begin(), v.end(), ReplaceGreater30(), 3000);for_each(v.begin(), v.end(), myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

**总结:**replace_if按条件查找,可以利用仿函数灵活筛选满足的条件

(4) swap

功能描述:

  • 互换两个容器的元素

函数原型:

  • swap(container c1, container c2);

    // 互换两个容器的元素

    // c1容器1

    // c2容器2

示例:

#include <algorithm>
#include <vector>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{vector<int> v1;vector<int> v2;for (int i = 0; i < 10; i++) {v1.push_back(i);v2.push_back(i+100);}cout << "交换前: " << endl;for_each(v1.begin(), v1.end(), myPrint());cout << endl;for_each(v2.begin(), v2.end(), myPrint());cout << endl;cout << "交换后: " << endl;swap(v1, v2);for_each(v1.begin(), v1.end(), myPrint());cout << endl;for_each(v2.begin(), v2.end(), myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

**总结:**swap交换容器时,注意交换的容器要同种类型

7.5 常用算术生成算法

学习目标:

  • 掌握常用的算术生成算法

注意:

  • 算术生成算法属于小型算法,使用时包含的头文件为 #include <numeric>

算法简介:

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

  • fill // 向容器中添加元素

(1) accumulate

功能描述:

  • 计算区间内 容器元素累计总和

函数原型:

  • accumulate(iterator beg, iterator end, value);

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

    // beg 开始迭代器

    // end 结束迭代器

    // value 起始值

示例:

#include <numeric>
#include <vector>
void test01()
{vector<int> v;for (int i = 0; i <= 100; i++) {v.push_back(i);}int total = accumulate(v.begin(), v.end(), 0);cout << "total = " << total << endl;
}int main() {test01();system("pause");return 0;
}

**总结:**accumulate使用时头文件注意是 numeric,这个算法很实用

(2) fill

功能描述:

  • 向容器中填充指定的元素

函数原型:

  • fill(iterator beg, iterator end, value);

    // 向容器中填充元素

    // beg 开始迭代器

    // end 结束迭代器

    // value 填充的值

示例:

#include <numeric>
#include <vector>
#include <algorithm>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{vector<int> v;v.resize(10);//填充fill(v.begin(), v.end(), 100);for_each(v.begin(), v.end(), myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

**总结:**利用fill可以将容器区间内元素填充为 指定的值

7.6 常用集合算法

学习目标:

  • 掌握常用的集合算法

算法简介:

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

  • set_union // 求两个容器的并集

  • set_difference // 求两个容器的差集

(1) set_intersection

功能描述:

  • 求两个容器的交集

函数原型:

  • set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

    // 求两个集合的交集

    // 注意:两个集合必须是有序序列

    // beg1 容器1开始迭代器
    // end1 容器1结束迭代器
    // beg2 容器2开始迭代器
    // end2 容器2结束迭代器
    // dest 目标容器开始迭代器

示例:

#include <vector>
#include <algorithm>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{vector<int> v1;vector<int> v2;for (int i = 0; i < 10; i++){v1.push_back(i);v2.push_back(i+5);}vector<int> vTarget;//取两个里面较小的值给目标容器开辟空间vTarget.resize(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, myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

总结:

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

(2) set_union

功能描述:

  • 求两个集合的并集

函数原型:

  • set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

    // 求两个集合的并集

    // 注意:两个集合必须是有序序列

    // beg1 容器1开始迭代器
    // end1 容器1结束迭代器
    // beg2 容器2开始迭代器
    // end2 容器2结束迭代器
    // dest 目标容器开始迭代器

示例:

#include <vector>
#include <algorithm>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{vector<int> v1;vector<int> v2;for (int i = 0; i < 10; i++) {v1.push_back(i);v2.push_back(i+5);}vector<int> vTarget;//取两个容器的和给目标容器开辟空间vTarget.resize(v1.size() + v2.size());//返回目标容器的最后一个元素的迭代器地址vector<int>::iterator itEnd = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());for_each(vTarget.begin(), itEnd, myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

总结:

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

(3) set_difference

功能描述:

  • 求两个集合的差集

函数原型:

  • set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

    // 求两个集合的差集

    // 注意:两个集合必须是有序序列

    // beg1 容器1开始迭代器
    // end1 容器1结束迭代器
    // beg2 容器2开始迭代器
    // end2 容器2结束迭代器
    // dest 目标容器开始迭代器

示例:

#include <vector>
#include <algorithm>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{vector<int> v1;vector<int> v2;for (int i = 0; i < 10; i++) {v1.push_back(i);v2.push_back(i+5);}vector<int> vTarget;//取两个里面较大的值给目标容器开辟空间vTarget.resize( 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, myPrint());cout << endl;cout << "v2与v1的差集为: " << endl;itEnd = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), vTarget.begin());for_each(vTarget.begin(), itEnd, myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

总结:

  • 求差集的两个集合必须的有序序列
  • 目标容器开辟空间需要从两个容器取较大值
  • set_difference返回值既是差集中最后一个元素的位置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_268562.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端jQuery ajax请求,后端node.js使用cors跨域

前言 跨域&#xff0c;一句话介绍&#xff1a; 你要请求的URL地址与当前的URL地址&#xff0c;协议不同、域名不同、端口不同时&#xff0c;就是跨域。 步入正题 前端&#xff0c;jQuery ajax请求 $.ajax({async: false,method: post,//URl和端口与后台匹配好&#xff0c;当…

HTTPS加密解析

日升时奋斗&#xff0c;日落时自省 目录 1、加密解释 2、对称加密 3、非对称加密 4、证书 HTTPS&#xff08;HyperText Transfer Protocol over Secure Socket Layer&#xff09;也是一个应用层协议&#xff0c;是在HTTP协议的基础上引入了一个加密层 HTTP协议内容都是按…

Docker学习(二十一)构建 java 项目基础镜像

目录1.下载 JDK 包2.编写 Dockerfile3.构建镜像4.创建容器测试1.下载 JDK 包 JDK各版本官网下载地址&#xff1a; https://www.oracle.com/java/technologies/downloads/archive/#JavaSE 这里我们以 JDK 8u351 为例&#xff0c;点击 Java SE (8U211 and later)。 点击下载 jd…

自动化测试实战篇(9),jmeter常用断言方法,一文搞懂9种测试字段与JSON断言

Jmeter常用的断言主要有&#xff0c;JSON断言和响应断言这两种方式。 断言主要就是帮助帮助人工进行快速接口信息验证避免繁杂的重复的人工去验证数据 第一种响应断言Apply to&#xff1a;表示应用范围测试字段&#xff1a;针对响应数据进行不同的匹配响应文本响应代码响应信息…

WPF 自定义DataGrid控件样式模板5个

WPF 自定义DataGrid控件样式样式一&#xff1a;样式代码&#xff1a;<!--DataGrid样式--><Style TargetType"DataGrid"><!--网格线颜色--><Setter Property"CanUserResizeColumns" Value"false"/><Setter Property&q…

day48第九章动态规划(二刷)

今日任务 198.打家劫舍213.打家劫舍II337.打家劫舍III 今天就是打家劫舍的一天&#xff0c;这个系列不算难&#xff0c;大家可以一口气拿下。 198.打家劫舍 题目链接&#xff1a; https://leetcode.cn/problems/house-robber/description/ 题目描述&#xff1a; 你是一个…

[ Azure ] Episode 03 | 描述云计算运营中的 CapEx 与 OpEx,如何区分 CapEx 与 OpEx

正常情况如果你不是会计&#xff0c;或者对钱相关的数字比较敏感的财务&#xff0c;本文的一些东西你不会接触的&#xff0c;但是最为云架构或者云运营&#xff0c;你可能会遇到如何采购亦或者估算的我成本和运营成本等等&#xff0c;所以本文的一些知识点就需要进行一定的了解…

Fikker安装SSL证书

Fikker 基于nginx&#xff0c; 订单详细中下载nginx格式&#xff0c; 解压后包含 yourdomain.com.crt 和 yourdomain.com.key 2个文件&#xff0c;将内容粘贴到输入框中.1、说明&#xff1a;在【主机管理】中设置网站域名对应的SSL 数字证书&#xff08;CRT/CER&#xff09;和证…

MySQL 02 :三层结构、备份删除数据库

MySQL 02 &#xff1a;数据库三层结构-破除MySQL神秘 请添加图片描述 通过golang操作MySQL 创建删除数据库 备份恢复数据库 第一次需要配置环境&#xff0c;否则会报错 报错&#xff1a;mysqldump: Got error: 1045: Access denied for user ‘root’‘localhost’ (using …

云端IDE:TitanIDE保障代码安全

原文作者&#xff1a;行云创新技术总监 邓冰寒 引言 2019年&#xff0c;哔哩哔哩的后台源代码被上传至GitHub&#xff0c;其中包含部分用户名及密码信息&#xff0c;受该事件影响&#xff0c;B站盘前股价跌超4%。 2022年&#xff0c;丰田汽车公司远程车载信息通信服务应用程序…

训练自己的GPT2-Chinese模型

文章目录效果抢先看准备工作环境搭建创建虚拟环境训练&预测项目结构模型预测续写训练模型遇到的问题及解决办法显存不足生成的内容一样文末效果抢先看 准备工作 从GitHub上拉去项目到本地&#xff0c;准备已训练好的模型百度网盘&#xff1a;提取码【9dvu】。 gpt2对联训…

java多线程(二四)java多线程基础总结

一、进程与线程 1.进程 进程是操作系统结构的基础&#xff1b;是一次程序的执行&#xff1b;是一个程序及其数据在处理机上顺序执行时所发生的活动。操作系统中&#xff0c;几乎所有运行中的任务对应一条进程&#xff08;Process&#xff09;。一个程序进入内存运行&#xff…

Kalman Filter in SLAM (6) ——Error-state Kalman Filter (EsKF, 误差状态卡尔曼滤波)

文章目录0.前言1. IMU的误差状态空间方程2. 误差状态观测方程3. 误差状态卡尔曼滤波4. 误差状态卡尔曼滤波方程细节问题0.前言 这里先说一句&#xff1a;什么误差状态卡尔曼&#xff1f;完全就是在扯淡&#xff01; 回想上面我们推导的IMU的误差状态空间方程&#xff0c;其实…

synchronized轻量级锁优化

synchronized优化轻量级锁 使用场景 如果一个对象虽然有多个线程访问&#xff0c;但多线程访问时间是错开的&#xff0c;也就是没有竞争&#xff0c;那么可以使用轻量级锁优化&#xff1b; 原理 1、每个线程的栈帧中有锁记录 包括&#xff1a;记录锁对象的地址Object refer…

NLL loss(负对数似然损失)

NLL损失在NLP中含义 &#xff1a; 在自然语言处理中&#xff0c;通常用于分类任务&#xff0c;例如语言模型、情感分类等。NLL损失全称为Negative Log-Likelihood Loss&#xff0c;其含义是负对数似然损失。 在NLP任务中&#xff0c;我们通常将文本数据表示为一个序列&#x…

Netty channelHandler注意事項——super.channelRead(ctx, msg)

通过nioSocketChannel.pipeline()的addLast添加入站处理器&#xff0c;如果有多个必须显示的唤醒下一个入站处理器&#xff0c;否则执行链中间会断掉。 protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {log.debug(nioSocketChannel.toStrin…

Laya小游戏开发,laya3D美术篇——1——关于laya自带的几个shader的基础运用讲解。

最近三年&#xff0c;基本上做的都是laya小游戏项目。也就是微信小程序&#xff0c;很多业内同行都觉得laya做小游戏不好用&#xff0c;去用了其他平台&#xff0c;甚至还有些做app游戏的&#xff0c;都不来趟laya这个坑。原因有那么以下几点。laya对于unity的辅助开发&#xf…

pandas库中的read_csv函数读取数据时候的路径问题详解(ValueError: embedded null character)

read_csv()函数不仅是R语言中的一个读取csv文件的函数&#xff0c;也是pandas库中的一个函数。pandas是一个用于数据分析和处理的python库。它的read_csv函数可以读取csv文件里的数据&#xff0c;并将其转化为pandas里面的DataFrame对象。它由很多参数可以设置&#xff0c;例如…

体验 Kubeshark

体验 KubesharkKubeshark 官网地址Kubeshark简介Linux下安装 Kubeshark身份感知的服务地图Kubeshark 官网地址 https://kubeshark.co/ Kubeshark简介 Kubeshark是Kubernetes的API流量查看器&#xff0c;为Kubernetes的内部网络提供实时的、协议感知的可见性&#xff0c;捕捉…

拉链表

每日的用户更新表获取的三种方式&#xff1a;一是监听mysql库数据的变化&#xff0c;比如用canal合并每日的变化&#xff0c;获取到最后的一个状态二是每天获得一份切片数据&#xff0c;可以通过去两天切片数据的不同来作为每日更新表&#xff0c;可以对所有字段先进性concat&a…