文件是一组有意义的信息/数据集合。 ## 一、文件的逻辑结构 - 逻辑结构:在用户看来,文件内部的数据应该是如何组织起来的。 - 物理结构:在操作系统看来,文件的数据是如何存放在外存的。 - 无结构文件: 文件内部的数据由一系列二进制流或字符流组成,又称“流式文件”。 - 有结构文件:由一组相似的记录组成,又称“记录式文件” 以下介绍三种有结构文件。 #### 1. 顺序文件 文件中的记录一个接一个地顺序排列(逻辑上)。各个记录在物理上可以顺序存储链式存储。 - 串结构:记录顺序与关键字无关。 - 顺序结构:记录按关键字顺序排列。 链式存储:无法实现随机存取,每次只能从第一个记录开始依次往后查找。 顺序存储(顺序文件一般指顺序存储的文件): - 可变长记录无法实现随机存取; - 定长记录可以随机存取,记录长度为L,则第i个记录存放的相对位置是i*L; -采用顺序结构的定长记录,还可以快速找到某关键字对应的记录; #### 2. 索引文件 - 建立一张索引表,每条记录对应一个索引项; - 索引表本身是定长记录的顺序文件,因此可以快速找到第i个记录对应的索引项; - 若索引表按关键字顺序排列,则可支持快速检索; - 解决了顺序文件不方便增删记录的问题,同时让不定长记录文件实现随机存取,但索引表可能占据很多空间; #### 3. 索引顺序文件 为文件建立一张索引表,每组记录对应一个索引表项。 ## 二、文件目录 #### 1. 文件控制块 文件控制块(FCB):目录文件(可理解成文件夹)中的一条记录就是一个文件控制块。包含了: - 文件的基本信息:文件名、物理地址、逻辑地址、物理结构等; - 存取控制信息:是否可读/可写、禁止访问的用户名单等; - 使用信息:文件的建立时间、修改时间等; FCB实现了文件名和文件之间的映射,使用户(用户程序)可以实现“按名存取”。 对目录的操作:搜索、创建文件、删除文件、显示文件、修改文件。 #### 2. 目录结构 单级目录结构:整个系统只建立一张目录表,每个文件占一个目录项; 两级目录结构:分为主文件目录(MFD)和用户文件目录(UFD); - 主文件目录记录用户名及相应用户文件存放文字; - 用户不能对自己的文件进行分类,缺乏灵活性; 多级目录结构(树形目录结构) - 相对路径 VS 绝对路径; - 不便于实现文件的共享无环图目录结构:在树形目录结构基础上,增加一些指向同一节点的有向边,形成有向无环图。 - 为共享结点设置共享计数器,计数器为0才删除结点; - 可以更方便地实现多个用户间的文件共享; #### 3. 索引节点(FCB的改进) 对目录表进行瘦身,保留文件名+索引结点指针信息,其他描述信息全都放到指针指向的地址; - 减小了目录表所占空间; - 提高了检索效率(一个磁盘块可以放入更多FCB,启动磁盘次数减少); ## 三、文件的物理结构 该部分探讨文件数据应该怎么存放在外存中,可以理解成文件分配方式问题。 和内存分页类似,磁盘中存储单元也会被分为一个个“块/磁盘块/物理块”。很多操作系统中,磁盘块的大小与内存块、页面的大小相同。 - 文件的逻辑地址也可以表示为(逻辑块号,块内地址);、 - 操作系统为文件分配存储空间以块为单位; - 操作系统负责从逻辑地址到物理地址的映射; 这里介绍连续分配链接分配索引分配三种分配方式: #### 1.连续分配 连续分配要求每个文件在磁盘上占有一组连续的块。 文件目录记录文件存放的起始块号长度。 - 物理块号=起始块号+逻辑块号; 优点: - 支持顺序访问随机访问(直接访问); - 在顺序读/写时速度最快; 缺点: - 不方便扩展; - 存储空间利用率低,会产生难以利用的磁盘碎片; 地址转换方式: #### 2.链接分配 隐式链接:文件中除最后一个磁盘块,每个磁盘块都会保存指向下一个磁盘块的指针。 - 只支持顺序访问,不支持随机访问; - 拓展方便; - 磁盘利用率高; 显示链接:把用于链接文件各物理块的指针显示地存放在一张表中,即文件分配表(FAT); - FAT中,每个物理块号都记录下一块的地址; - 每个磁盘仅设置一张FAT,开机时将FAT读入内存; - 每个文件的起始块号记录在目录项中; 优点: - 支持顺序访问,也支持随机访问(查询FAT表不需要读磁盘); - 不会产生外部碎片,也方便拓展; 缺点: - 文件分配表需要占据一定存储空间; #### 3.索引分配 为每个文件建立一张索引表,索引表中记录了文件各个逻辑块对应的物理块(类似于页表); - 索引表存放的磁盘称为索引块,文件数据存放的磁盘成为数据块; - FCB中记录索引块地址; - 支持随机访问、容易拓展; 当一个磁盘块装不下整张索引表,可以采取以下三种解决方式: - 链接方案:多个索引块链接起来存放; - 多层索引:类似于多级页表,为索引表建立索引表,除了顶级索引表都可以离散存放; - 混合索引:顶级索引表中,既包含直接地址索引,又包含间接索引,使得对于小文件来说,访问一个数据块所需的读磁盘次数更少(为什么小文件需要多层索引?); ## 四、文件存储空间管理 对磁盘空闲空间的管理。 #### 1. 存储空间的划分和初始块 物理磁盘划分为一个个文件卷(C盘、D盘、E盘等); 各个文件卷分为目录区(存放FCB、用于存储磁盘空间管理的信息)、文件区(存放文件数据);

加下来介绍4种存储空间管理方法: #### 2. 空闲表法 记录每个空闲区间的起始位置和长度。 - 适用于连续分配方式; - 可采用首次适应、最佳适应、最坏适应等算法为文件分配区间; #### 3. 空闲链表法 空闲盘块链:以盘块为单位组成一条空闲链; - 空闲盘块存储这下一个空闲盘块的指针; - 操作系统保存着链头、链尾指针; 空闲盘区链:以盘区为单位组成一条空闲链; - 操作系统保存着链头、链尾指针; - 空闲盘区中的第一个盘块记录了盘区的长度、下一个盘区的指针; - 离散分配、连续分配都适用,为一个文件分配多个盘块时效率更高; #### 4. 位示图法 每个二进制位对应一个盘块,位示图一般用连续的“字”表示,用(字号,位号)对应一个盘块。 - 字号i = b/n,位号j = b%n,n表示字长,b表示盘块号; - 注意盘块号、字号、位号从0开始还是从1开始; #### 5. 成组链接法 文件卷的目录区设置一个磁盘块为“超级块”。 - 超级块记录下一组空闲盘块数、每个空闲盘块号; - 系统启动时超级块读入内存,并随着磁盘的超级块更新; - 超级块的第一个磁盘块存放了下一组的信息; - 适用于大型操作系统;

五、文件的基本操作

创建文件(creat系统调用): - 在外存中找到文件所需空间; - 创建该文件对应的目录项; 删除文件(delete系统调用): - 找到文件名对应的目录项; - 回收文件占用的磁盘块; - 删除文件对应的目录项; 打开文件(open系统调用): - 找到文件名对应的目录项; - 将目录项复制到内存的“打开文件表”中,并将打开文件表的索引号返回给用户; - 每个进程有自己的打开文件表,系统也有一张总的打开文件表; - 进程打开文件表特有属性:读写指针、访问权限(是否只读); - 系统打开文件表特有属性:打开计数器; 关闭文件(close系统调用): - 将进程的打开文件表相应表项删除; - 回收分配给该文件的内存空间等资源; - 系统打开文件表打开计数器-1,若count为0则删除对应表项; 读文件(read系统调用): - 根据读指针、读入数据量、内存位置将文件数据从外存读入内存; 写文件(write系统调用): - 根据读指针、读入数据量、内存位置将文件数据从内存写出外存; ## 六、文件共享和文件保护 #### 1. 文件共享 基于索引节点的共享方式(硬链接): - 每个用户的目录项指向同一个索引结点; - 索引结点中有链接技术count; - 某用户删除文件时只删除目录项,count--,只有count==0才删除文件数据; 基于符号链接的共享方式(软链接): - 在一个link型文件(如快捷方式)记录共享文件存放路径; - 软连接访问共享文件需要查询多级目录,需要多次I/O操作,访问速度慢; #### 2. 文件保护 口令保护: - 为文件设置一个口令,用户访问时需提供口令; - 开销小,但口令需存放系统中,不太安全; 加密保护: - 用一个密码对文件加密(如异或加密),需要提供相同密码才能正确解密; - 安全性高,但加密解密需耗时; 访问控制: - 用访问控制表(ACL)记录不同用户的访问权限; - 对文件访问类型可以分为:读/写/执行/删除 等; - 实现灵活,可以实现复杂的文件保护功能;

传统存储方式特点 - 一次性:作业必须一次性全部装入内存后才能开始运行; - 驻留性:一旦作业装入内存,就会一直驻留在内存中,直至作业结束; 局部性原理 - 时间局部性 - 空间局部性 ## 虚拟内存 - 基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以开始执行程序; - 在程序执行过程中,当访问的信息不在内存时,由操作系统负责将所需信息调入内存,然后继续执行程序;(请求调页/请求调段) - 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换到外存;(页面置换/段置换) #### 虚拟内存容量 虚拟内存最大容量:由计算机地址结构(CPU寻址范围决定),如32位计算机地址结构:2^32B=4GB; 虚拟内存实际容量:min(内外存容量之和,CPU寻址范围); #### 特征 - 多次性; - 对换性; - 虚拟性; ## 请求分页管理方式 #### 请求页表 - 内存块号 - 状态栏:表示页面是否已在内存中; - 访问字段:记录最近被访问过几次,或记录上次访问的时间,供置换算法选择换出页面时参考; - 修改位:表示页面调入内存后是否被修改过; - 外存地址:页面在外存中存放的位置; #### 缺页中断机构 - 在请求分页系统中,当访问的页面不在内存(状态位为0)时,便产生一个缺页中断,由操作系统的缺页中断处理程序处理中断。 - 此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。 - 如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入,并修改相应页表项; - 如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面被修改过则需写回外存; #### 地址变换机构 - 找到页表项时需检查页面是否在内存中; - 若页面不在内存中,需请求调页; - 如内存空间不够,需换出页面; - 页面调入内存后,需要修改相应页表项; ## 页面置换算法 追求更小的缺页率(缺页次数/访问次数)。 #### 最佳置换算法 每次选择以后永不使用,或长时间内不再被访问的页面淘汰,保证最低缺页率。 操作系统无法预判,所以无法实现。 #### 先进先出置换算法(FIFO) 每次淘汰最早进入内存的页面。FIFO会产生Belady异常。 Belady异常:当为进程分配的物理块数增大时,缺页次数不减反增。 #### 最近最久未使用置换算法(LRU) 每次淘汰最近最久未使用的页面。用访问字段记录上次被访问以来经历的时间t。 性能好,但实现困难、开销大。 #### 时钟置换算法(CLOCK) 简单的CLOCK算法: - 为每个页面设置一个访问位,通过链接指针链接成一个循环队列; - 一个页面被访问时,访问位设为1; - 需要淘汰一个页面时,按队列顺序检查访问位,访问位为0则换出,访问位为1则设为0; 简单的CLOCK算法仅考虑一个页面最近是否被访问过。事实上,只有被修改过的淘汰页面才需要写回外存,因此没有被修改过的页面应该优先淘汰,避免I/O操作。 改进型的时钟置换算法: -用(访问为,修改位)表示各页面状态; - 第一轮:扫描找到第一个(0,0)页面用于替换,不修改任何标志位; - 第二轮:若第一轮扫描失败,则继续扫描查找第一个(0,1)的帧用于替换,并将扫描过的访问位设为0; - 第三轮:若第二轮扫描失败,则继续扫描查找第一个(0,0)的帧用于替换,不修改任何标志位; - 第四轮:若第三轮扫描失败,则继续扫描查找第一个(0,1)的帧用于替换; ## 页面分配策略 驻留集:指请求分页存储管理中给进程分配的物理块的集合。 采用了虚拟存储技术的系统中,驻留集一般小于进程总大小。 - 驻留集太小,换页频繁,开销大; - 驻留集太大,并发度小,资源利用率低; 固定分配 VS 可变分配:驻留集大小是否可变; 局部置换 VS 全局置换:是否可以将其他进程的物理块换出; - 固定分配局部置换:很难在刚开始就为每个进程分配合理的物理块数量,灵活性差; - 可变分配全局置换:部分进程物理块被减少,导致缺页率过高; - 可变分配局部置换:根据缺页的频率来动态调整进程物理块; #### 何时调入页面 - 预调页策略:进程运行前预测不久之后可能访问到的页面,用于首次调入,由程序员指出; - 请求调页策略:进程运行期间发现缺页才将所缺页面调入内存,需要频繁的I/O操作; #### 从何处调入页面 外存分为文件区和对换区。 |对换区|文件区| |:---:|:---:| |读写速度快|读写速度慢| |连续分配|离散分配| |一般空间较小|一般空间较大| - 对换区空间充足:进程运行前相关数据复制到对换区,页面调入、调出都在内存和对换区进行; - 对换区空间不足:不会被修改的数据直接从文件区调入; - UNIX方式:运行之前进程相关数据全部放在文件区,运行时内存换出的页面写回对换区; #### 工作集 抖动(颠簸)现象:刚刚换出/换入的页面马上又要换入/换出,频繁缺页。 工作集:在某段时间间隔里(窗口尺寸),进程实际访问页面的集合。 一般驻留集大小不能小于工作集大小,否则容易频繁缺页。

内存的连续分配方式都存在明显缺陷,如果允许将一个进程分散地装入到许多不相邻的分区中,便可充分地利用内存,从而产生非连续分配方式。 非连续分配:为用户进程分配的可以是一些分散的内存空间。 # 基本分页存储管理 思想:把内存分为一个个相等的小分区,再按照分区大小把进程拆分成一个个小部分。 - 每个内存分区称为“页框”、“页帧”、“内存块”、“物理块”; - 每个页框有一个编号,成为“页框号”、“页帧号”“内存块号”、“物理块号”; - 每个用户进程拆分成的部分称为“页”、“页面”,页面与页框一一对应,页面编号称为“页号”; - 进程的最后一个页面小于页框,会产生内部碎片; ## 页表与页表项 为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表。 - 一个进程对应一张页表; - 页表项:进程的每一页对应一个页表项,每个页表项由页号块号组成; - 页表记录进程页面(页号,连续)和实际存放的内存块(块号,非连续)之间的对应关系; - 各页表项会按顺序连续地存放在内存中(因此页号是隐含的,只需要存放块号的空间),实际中通常使一个页框放入整数个页表项; ## 地址转换 逻辑地址由页号+页内偏移量组成。假如由32个二进制位表示逻辑地址,页面大小为4KB,则前20位表示页号,后12位表示页内偏移量(212B=4KB),且最多有220个页面。 逻辑地址转换成物理地址: - 根据逻辑地址计算页号(逻辑地址/页面长度)、页内偏移量(逻辑地址%页面长度); -页号合法性检查(与页表长度对比); -根据页表起始地址、页号找到对应页表项; - 根据页表项记录的内存块号、页内偏移量得到物理地址; - 访问内存单元; ## 两级页表 页表必须连续存放,在单级页表中,当页表很大时,需要占用很多个连续的页框。 可以将页表再分页,形成两级页表多级页表(为页表建立页表)。 - 逻辑地址结构:一级页号+二级页号+页面偏移量; - 页目录表、外层页表、顶级页表; 两级页表的地址转换 - 根据逻辑地址得到一级页号、二级页号、页面偏移量 - 从PCB中读出页目录表始址,根据一级页号查找页目录表,找到下一级页表在内存中的存放位置; - 根据二级页号查表,找到最终想访问的内存块号; - 结合页内偏移量得到物理地址; # 基本分段存储管理 分段:将地址空间按程序自身的逻辑关系划分为若干个段,每段从0开始编址。每个段在内存中占据连续空间,但各段之间可以不相邻。 逻辑地址:段号(段名)+段内地址(段内偏移量) - 段号的位数决定了每个进程可以分几个段; - 段内地址位数决定了每个段的最大长度是多少; ## 段表 - 每个段对应一个段表项,记录了该段在内存中的起始位置(基址)、段的长度; - 各个段表项的长度是相同的,段号是隐含的; ## 地址转换 - 由逻辑地址得到段号、段内地址; - 段号与段表寄存器中的段表长度比较,检查是否越界; - 由段表始址、段号找到对应段表项; - 根据段表中记录的段长,检查段内地址是否越界; - 由段表中记录的基址、段内地址得到物理地址; - 访问内存单元; ## 分段、分页管理的比较
分页管理 分段管理
页是信息的物理单位 段是信息的逻辑单位
分页对用户不可见 分段对用户可见
页的大小固定 段的大小决定于用户编写的程序
地址空间一维 地址空间二维
空间利用率高,不会产生外部碎片 会产生外部碎片
分段更容易实现信息的共享和保护
访问一个逻辑地址都需要两次访存

段页式管理方式

段页式管理:将进程按逻辑模块分段,再将各段分页,再将内存空间分为大小相同的内存块。 逻辑地址:段号+页号+页内偏移量 ## 段表、页表 - 每个段对应一个段表项,由段号(隐含)、页表长度、页表存放块号组成; - 每个页面对应一个页表项,每个页表项由页号(隐含)、页面存放的内存块号组成 ## 地址变换 - 由逻辑地址得到段号、页号、页面偏移量; - 段号与段表寄存器中的段长度比较,检查是否越界; - 由段表始址、段号找到对应的段表项; - 根据段表中记录的页表长度,检查页号是否越界; - 由段表中的页表地址、页号查询页表,找到对应页表项; - 由页面存放的内存块号、页内偏移量得到最终的物理地址; - 访问目标单元;

map/multimap属于关联式容器,底层结构是用二叉树实现。 - map中所有元素都是pair - pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值) - 所有元素都会根据元素的键值自动排序 优点: - 可以根据key值快速找到value值 map和multimap区别: - map不允许容器中有重复key值元素 - multimap允许容器中有重复key值元素 ## map构造和赋值

1
2
3
4
5
//构造:
map<T1, T2> mp; //map默认构造函数:
map(const map &mp); //拷贝构造函数
//赋值:
map& operator=(const map &mp); //重载等号操作符
## map大小和交换
1
2
3
size(); //返回容器中元素的数目
empty(); //判断容器是否为空
swap(st); //交换两个集合容器
## map插入和删除
1
2
3
4
5
insert(elem); //在容器中插入元素。
clear(); //清除所有元素
erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(key); //删除容器中值为key的元素。
## map查找和统计
1
2
find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
count(key); //统计key的元素个数
## map容器排序 map容器默认排序规则为从小到大,可通过仿函数改变排序规则:
1
2
3
4
5
6
7
8
9
class MyCompare
{
public:
bool operator()(int v1, int v2)
{
return v1 > v2;
}
};
创建:``map<int, int, MyCompare> m;``

set/multiset属于关联式容器,所有元素都会在插入时自动被排序,底层结构是用红黑树实现的。 set和multiset区别:

  • set不可以插入重复数据,而multiset可以
  • set插入数据的同时会返回插入结果,表示插入是否成功
  • multiset不会检测数据,因此可以插入重复数据 ## set构造和赋值
    1
    2
    3
    4
    5
    //构造:
    set<T> st; //默认构造函数:
    set(const set &st); //拷贝构造函数
    //赋值:
    set& operator=(const set &st); //重载等号操作符
    1
    2
    3
    4
    5
    ## set大小和交换
    ``` C++
    size(); //返回容器中元素的数目
    empty(); //判断容器是否为空
    swap(st); //交换两个集合容器
    ## set插入和删除
    1
    2
    3
    4
    5
    insert(elem); //在容器中插入元素。
    clear(); //清除所有元素
    erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
    erase(beg, end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
    erase(elem); //删除容器中值为elem的元素。
    ## set查找和统计
    1
    2
    find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
    count(key); //统计key的元素个数
    ## pair对组创建 成对出现的数据,利用对组可以返回两个数
    1
    2
    pair<type, type> p ( value1, value2 );
    pair<type, type> p = make_pair( value1, value2 );
    ## set容器排序 set容器默认排序规则为从小到大,可通过仿函数改变排序规则:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class MyCompare
    {
    public:
    bool operator()(int v1, int v2)
    {
    return v1 > v2;
    }
    };
    创建:``set<int,MyCompare> s2;``

stack:栈

1
2
3
4
5
6
7
8
9
10
11
12
//构造函数:
stack<T> stk; //stack采用模板类实现, stack对象的默认构造形式
stack(const stack &stk); //拷贝构造函数
//赋值操作:
stack& operator=(const stack &stk); //重载等号操作符
//数据存取:
push(elem); //向栈顶添加元素
pop(); //从栈顶移除第一个元素
top(); //返回栈顶元素
//大小操作:
empty(); //判断堆栈是否为空
size(); //返回栈的大小
queue:队列
1
2
3
4
5
6
7
8
9
10
11
12
13
//构造函数:
queue<T> que; //queue采用模板类实现,queue对象的默认构造形式
queue(const queue &que); //拷贝构造函数
//赋值操作:
queue& operator=(const queue &que); //重载等号操作符
//数据存取:
push(elem); //往队尾添加元素
pop(); //从队头移除第一个元素
back(); //返回最后一个元素
front(); //返回第一个元素
//大小操作:
empty(); //判断堆栈是否为空
size(); //返回栈的大小

list:链表 链表的组成:链表由一系列结点组成 结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域 STL中的链表是一个双向循环链表 list的优点: - 采用动态存储分配,不会造成内存浪费和溢出 - 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素 list的缺点: - 链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大 List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的 ## list构造函数

1
2
3
4
list<T> lst; //list采用采用模板类实现,对象的默认构造形式:
list(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem); //构造函数将n个elem拷贝给本身。
list(const list &lst); //拷贝构造函数。
## list赋值和交换
1
2
3
4
assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem); //将n个elem拷贝赋值给本身。
list& operator=(const list &lst); //重载等号操作符
swap(lst); //将lst与本身的元素互换。
## list大小操作
1
2
3
4
5
6
size(); //返回容器中元素的个数
empty(); //判断容器是否为空
resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
## list插入和删除
1
2
3
4
5
6
7
8
9
10
11
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。
## list数据存取
1
2
front(); //返回第一个元素。
back(); //返回最后一个元素。
## list反转和排序
1
2
reverse(); //反转链表
sort(); //链表排序

vector数据结构和数组非常相似,也称为单端数组,不同之处在于数组是静态空间,而vector可以动态扩展,即: - vector并不会在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间 - vector容器的迭代器是支持随机访问的迭代器 ## vecotr构造函数

1
2
3
4
vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end()); //将v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem); //构造函数将n个elem拷贝给本身。
vector(const vector &vec); //拷贝构造函数。
## vector赋值
1
2
3
vector& operator=(const vector &vec); //重载等号操作符
assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem); //将n个elem拷贝赋值给本身。
## vector容量和大小
1
2
3
4
5
6
7
empty(); //判断容器是否为空
capacity(); //容器的容量
size(); //返回容器中元素的个数
resize(int num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除
## vector插入和删除
1
2
3
4
5
6
7
push_back(ele); //尾部插入元素ele
pop_back(); //删除最后一个元素
insert(const_iterator pos, ele); //迭代器指向位置pos插入元素ele
insert(const_iterator pos, int count,ele); //迭代器指向位置pos插入count个元素ele
erase(const_iterator pos); //删除迭代器指向的元素
erase(const_iterator start, const_iterator end); //删除迭代器从start到end之间的元素
clear(); //删除容器中所有元素
## vector数据存取
1
2
3
4
at(int idx); //返回索引idx所指的数据
operator[]; //返回索引idx所指的数据
front(); //返回容器中第一个数据元素
back(); //返回容器中最后一个数据元素
## vector预留空间 预留空间可减少动态扩展容量时的扩展次数
1
reserve(int len); //容器预留len个元素长度,预留位置不初始化,元素不可访问。
## vector互换容器 swap可以使两个容器互换,可以达到实用的收缩内存效果
1
swap(vec); // 将vec与本身的元素互换

string是C++风格的字符串,本质上是个类 ## string构造函数 构造函数原型:

1
2
3
4
string(); //创建一个空的字符串 例如: string str;
string(const char* s); //使用字符串s初始化
string(const string& str); //使用一个string对象初始化另一个string对象
string(int n, char c); //使用n个字符c初始化
## string赋值操作 赋值函数原型:
1
2
3
4
5
6
7
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赋给当前字符串
## string字符串拼接 用于在字符串末尾拼接字符串
1
2
3
4
5
6
7
string& operator+=(const char* str); //重载+=操作符
string& operator+=(const char c); //重载+=操作符
string& operator+=(const string& str); //重载+=操作符
string& append(const char *s); //把字符串s连接到当前字符串结尾
string& append(const char *s, int n); //把字符串s的前n个字符连接到当前字符串结尾
string& append(const string &s); //同operator+=(const string& str)
string& append(const string &s, int pos, int n); //字符串s中从pos开始的n个字符连接到字符串结尾
## string查找和替换 查找:查找指定字符串是否存在 替换:在指定的位置替换字符串
1
2
3
4
5
6
7
8
9
10
int find(const string& str, int pos = 0) const; //查找str第一次出现位置,从pos开始查找
int find(const char* s, int pos = 0) const; //查找s第一次出现位置,从pos开始查找
int find(const char* s, int pos, int n) const; //从pos位置查找s的前n个字符第一次位置
int find(const char c, int pos = 0) const; //查找字符c第一次出现位置
int rfind(const string& str, int pos = npos) const; //查找str最后一次位置,从pos开始查找
int rfind(const char* s, int pos = npos) const; //查找s最后一次出现位置,从pos开始查找
int rfind(const char* s, int pos, int n) const; //从pos查找s的前n个字符最后一次位置
int rfind(const char c, int pos = 0) const; //查找字符c最后一次出现位置
string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str
string& replace(int pos, int n,const char* s); //替换从pos开始的n个字符为字符串s
## string字符串比较 字符串之间通过ASCII码比较,一般用于比较是否相等
1
2
int compare(const string &s) const; //与字符串s比较
int compare(const char *s) const; //与字符串s比较
## 字符串存取
1
2
char& operator[](int n); //通过[]方式取字符
char& at(int n); //通过at方法获取字符
## string插入和删除
1
2
3
4
string& insert(int pos, const char* s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
string& insert(int pos, int n, char c); //在指定位置插入n个字符c
string& erase(int pos, int n = npos); //删除从Pos开始的n个字符
## string子串
1
string substr(int pos = 0, int n = npos) const; //返回由pos开始的n个字符组成的字符串

基本语法

以置换函数为例:

1
2
3
4
5
6
7
8
//使用模板必须确定数据类型T,并能够推导出一致的类型
template<class T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
调用方式: - 自动类型推导:mySwap(a, b); - 显示指定类型:mySwap<int>(a, b); ## 隐式类型转换 在调用函数时,若实参与形参数据类型不一致,实参根据形参类型进行转化(如char类型转化成对应的ASCII码) 普通函数&显示指定类型模板函数会发生隐式类型转换 自动类型推导不会发生,会报错 ## 调用规则 - 优先调用普通函数 - 可以通过空模板参数列表强制调用函数模板 myPrint<>(a,b); - 函数模板可以发生重载 - 如果函数模板更好匹配,优先调用模板 比如函数模板不用发生隐式类型转换,而普通函数需要 - 提供了函数模板,最好不要再提供普通函数 ## 具体化 模板的通用性不是万能的,如以下代码:
1
2
3
4
5
6
template<class T>
bool ifEqual(T a, T b)
{
if (a==b) return 1;
return 0;
}
如果传入的a和b是一个类,就无法实现了。 可以通过具体化实现自定义类型的通用化 :
1
2
3
4
5
template<> bool ifEqual(person &p1, person &p2)
{
if (p1.name==p2.name && p1.age==p2.age) return1;
return 0;
}
## 类模板 ### 基本语法 template后面加类,如:
1
2
3
4
5
6
7
8
9
10
11
12
template<class NameType, calss AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
m_Name = name;
m_Age = age;
}
NameType m_Name;
AgeType m_Age;
};
调用:Person<string, int> p1("name",age); ### 类模板注意事项 1. 类模板无法自动类型推导 2. 类模板参数列表可以有默认参数,如:template<class NameType, calss AgeType = int> 3. 类模板成员函数在调用时才会创建 ### 类模板对象作函数参数 1. 指定传入类型:void 函数名(类名<数据类型1, 数据类型2>&对象名) 2. 参数模板化:template<class T1, class T2> 3. 整个类模板化
1
2
3
4
template<class T>
void func(T &p)
{
}
### 类模板的继承 1. 如果父类是类模板,子类继承时需要给定T类型,如:calss son: public Base<int> 2. 如果想灵活指定父类T类型,子类也需要写成类模板
1
2
3
4
template<class T1, calss T2>
class son: public Base<T2>
{
};
### 类模板成员函数类外实现 以构造函数为例:
1
2
3
4
5
6
template<class T1, class T2> //需要加上模板参数列表
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
### 类模板分文件编写 - 问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到 - 解决: 方式1:直接包含.cpp源文件 方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

0%