《数据库系统概论》(王珊 萨师煊)复习笔记(二)

第二章 关系数据库

1 关系数据结构及形式化定义

  1. 二维表:逻辑结构,从用户角度,关系模型中数据的逻辑结构是一张二维表

  2. 建立在集合代数的基础上

  3. 域:一组具有相同数据类型的值的集合

  4. 笛卡尔积:给定一组域D1,D2,…,Dn,这些域中可以有相同的。

    D1×D2×…×Dn ={(d1,d2,…,dn)|di属于Di,i=1,2,…,n}

    元组:笛卡尔积中每一个元素(d1,d2,…,dn)叫作一个n元组(n-tuple)或简称元组(Tuple)
    (张三,计算机专业,男)等都是元组 ,通常用t表示
    分量:笛卡尔积元素(d1,d2,…,dn)中的每一个值di叫作一个分量。张三,计算机专业,男等都是分量
    基数:若Di(i=1,2,…,n)为有限集,其基数为mi(i=1,2,…,n),则D1×D2×…×Dn的基数M为:M = M1M2…..*Mn

    表示方法:笛卡尔积可表示为一个二维表,表中的每行对应一个元组,表中的每列对应一个域

    示例:D1为教师集合(T)= {t1,t2}, D2为学生集合(S)= {s1,s2 ,s3},D3为课程集合(C)= {c1,c2}
    则D1×D2×D3是个三元组集合,元组个数为2×3×2,是所有可能的(教师,学生,课程)元组集合

    子集

  • 关系
    D1×D2×…×Dn的子集叫作在域D1,D2,…,Dn上的关系,表示为R(D1,D2,…,Dn)
    R:关系名
    n:关系的目或度(Degree)
    • 元组
      关系中的每个元素是关系中的元组,通常用t表示。
    • 单元关系与二元关系
      当n=1时,称该关系为单元关系(Unary relation)或一元关系
      当n=2时,称该关系为二元关系(Binary relation)
  • 关系的表示
    关系也是一个二维表,表的每行对应一个元组,表的每列对应一个域
    • 属性
      关系中不同列可以对应相同的域,为了加以区分,必须对每列起一个名字,称为属性。n目关系必有n个属性
  1. 候选码(Candidate key):若关系中的某一属性组的值能唯一地标识一个元组,则称该属性组为候选码。简单的情况:候选码只包含一个属性

  2. 全码(All-key):最极端的情况:关系模式的所有属性组是这个关系模式的候选码,称为全码

  3. 主码:若一个关系有多个候选码,则选定其中一个为主码(Primary key)

  4. 主属性:候选码的诸属性称为主属性(Prime attribute)
    不包含在任何侯选码中的属性称为非主属性( Non-Prime attribute)或非码属性(Non-key attribute)

  5. 三类关系
    基本关系(基本表或基表):实际存在的表,是实际存储数据的逻辑表示
    查询表:查询结果对应的表
    视图表:由基本表或其他视图表导出的表,是虚表,不对应实际存储的数据

  6. 关系模式:是型,是对关系的描述,是静态的、稳定的

    • 是元素集合的结构
    • 元组语义以及完整性约束条件
    • 属性间的数据依赖关系集合

    通常简记为R (U) 或 R (A1,A2,…,An) R: 关系名 A1,A2,…,An : 属性名

  7. 关系数据库
    在一个给定的应用领域中,所有关系的集合构成一个关系数据库

  • 型:关系数据库模式,包括若干域的定义;在这些域上定义的若干关系模式。
  • 值:关系模式在某一时刻对应的关系的集合,简称为关系数据库

2 关系操作

  1. 常用的关系操作
    • 查询:选择、投影、连接、除、并、交、差
    • 数据更新:插入、删除、修改,查询的表达能力是其中最主要的部分
    • 选择、投影、并、差、笛卡尔积是5种基本操作
  2. 操作特点:
    • 集合操作方式:操作的对象和结果都是集合,一次一集合的方式
    • 高度非过程化:只要指出“做什么”,不需要描述“怎么做”

3 关系代数

  1. 关系数据库语言分类
    • 关系代数语言:用对关系的运算来表达查询要求。代表:ISBL
    • 关系演算语言:用谓词来表达查询要求
      元组关系演算语言:谓词变元的基本对象是元组变量。代表:APLHA, QUEL
      域关系演算语言:谓词变元的基本对象是域变量。代表:QBE
    • 关系代数和关系演算是相互等价的
    • 具有关系代数和关系演算双重特点的语言。代表:SQL(Structured Query Language)
  2. 定义:关系数据库的一种抽象的查询语言,用对关系的运算来表达查询。
  3. 要素:运算对象(关系)、关系运算符
  4. 关系代数运算符

    • R和S:具有相同的目n;相应的属性取自同一个域

    • R∩S:仍为n目关系,由既属于R又属于S的元组组成

      R∩S = { t|t 属于 R ∧ t 属于S }

    • R和S:具有相同的目n,相应的属性取自同一个域

    • R - S :仍为n目关系,由属于R而不属于S的所有元组组成

      R -S = { t|t属于R ∧ t属于S }

    • R和S:具有相同的目n(即两个关系都有n个属性),相应的属性取自同一个域

    • R∪S :仍为n目关系,由属于R或属于S的元组组成 R∪S = { t|t 属于 R ∨ t 属于S }

  1. 笛卡尔积

    • 严格地讲应该是广义的笛卡尔积(Extended Cartesian Product)
      R: n目关系,k1个元组
      S: m目关系,k2个元组
    • R×S
      列:(n+m)列元组的集合,元组的前n列是关系R的一个元组,后m列是关系S的一个元组
      行:k1×k2个元组
  1. 选择

    • 在关系R中选择满足给定条件的元组 F(R)={t | t 属于 R ^F(t) = ‘真’}

      F是选择的条件, F(t)要么为真,要么为假。F的形式:由逻辑运算符连接关系表达式而成

  2. 投影

    从关系R中取若干列组成新的关系(从列的角度),投影的结果中要去掉相同的行

  3. 连接

    连接操作是从两个关系的广义笛卡尔积中选择属性间满足一定条件的元组。通常写为:

  4. 等值连接和自然连接

    等值连接:从关系R与S的广义笛卡尔积中选取A、B属性值相等的那些元组

    自然连接:若R和S具有相同的属性组(来自相同的域,表示相同的含义),且连接的运算符θ为“=”,在连接的结果中去掉重复的属性组

  5. 外连接
    如果把舍弃的元组也保存在结果关系中,而在其他属性上填空值(Null),这种连接就叫做外连接。

  6. 左外连接
    如果只把左边关系R中要舍弃的元组保留就叫做左外连接。

  7. 右外连接
    如果只把右边关系S中要舍弃的元组保留就叫做右外连接。

  8. 象集

    关系R(X , Z),X、 Z是属性组,x是X上的取值,定义x在R中的象集为从R中选出在X上取值为x的元组,去掉X上的分量,只留Z上的分量 Zx = { t[Z] | t属于R 且 t[X]= x }

    • 给定关系R(X,Y)和S(Y,Z),其中X,Y,Z为属性组。R中的Y与S中的Y可以有不同的属性名,但必须出自相同的值域。
    • R与S的除运算得到一个新的关系P(X),P是R中满足下列条件的元组在X属性列上的投影:元组在X上分量值x的象集Yx包含S在Y上投影的集合。

4 关系的完整性

  1. 实体完整性和参照完整性:关系模型必须满足的完整性约束条件,称为关系的两个不变性,应该由关系系统自动支持。
  2. 用户定义的完整性: 应用领域需要遵循的约束条件,体现了具体领域中的语义约束
  3. 实体完整性:若属性A是基本关系R的主属性,则属性A不能取空值
  4. 外码
    • 设F是基本关系R的一个或一组属性,但不是关系R的码。如果F与基本关系S的主码Ks相对应,则称F是基本关系R的外码
    • 基本关系R称为参照关系(Referencing Relation)
    • 基本关系S称为被参照关系(Referenced Relation)或目标关系(Target Relation)
    • 例:学生关系的“专业号”与专业关系的主码“专业号”相对应
      “专业号”属性是学生关系的外码
      专业关系是被参照关系,学生关系为参照关系
    • 关系R和S不一定是不同的关系
    • 目标关系S的主码Ks 和参照关系的外码F必须定义在同一个(或一组)域上
    • 外码并不一定要与相应的主码同名,当外码与相应的主码属于不同关系时,往往取相同的名字,以便于识别
  5. 参照完整性
    • 若属性(或属性组)F是基本关系R的外码,它与基本关系S的主码Ks相对应(基本关系R和S不一定是不同的关系),则对于R中每个元组在F上的值必须为:
      • 或者取空值(F的每个属性值均为空值)
      • 或者等于S中某个元组的主码值

《数据库系统概论》(王珊 萨师煊)复习笔记(一)

第一章 绪论

1 数据库技术发展

  • 层次数据库技术:Rockwell公司与IBM公司合作研制了基于层次模型的数据管理系统IMS
  • 网状数据库技术:通用电气公司的Charles Bachman主持设计与实现了网状模型的数据库管理系统IDS;CODASYL下属的数据库任务组DBTG提出的网状数据库模型以及数据定义和数据操纵语言即DDL和DML的规范说明,推出了DBTG报告;
  • 关系数据库技术:IBM公司San Jose实验室的研究员Edgar F.Codd提出了关系数据模型
  • 新一代数据库技术
    1. 面向对象数据库
    2. XML数据库
    3. 网络数据库、嵌入式移动数据库、多媒体数据库、知识数据库、模糊数据库……

2 数据库系统概述

2.1 四个基本概念

  • 数据(Data)

    1. 数据库中存储的基本对象,描述事物的符号记录

    2. 数据的种类

      侠义:数字

      广义:文字、图形、图象、声音

    3. 数据与其语义不可分

      例如:93是一个数据,语义可以是学生某门课的成绩

    4. 记录是计算机中表示和存储数据的一种格式,是有结构的数据

  • 数据库(DB)

    ​ 长期储存在计算机内,有组织的、可共享的大量数据的集合

  • 数据库管理系统(DBMS)

    1. 位于用户和操作系统之间的一层数据管理软件,是基础软件,一个大型复杂的软件系统。科学的组织和存储数据、高效的获取和维护数据。

    2. 主要功能

      • 数据定义功能

      ​ 提供数据定义语言(DDL),及其翻译处理程序
      ​ 定义数据库中的数据对象

      • 数据操纵功能

        提供数据操纵语言(DML),及其编译程序
        ​ 操纵数据实现对数据库的基本操作(查询、插入、删除和修改)

      • 数据组织、存储和管理
        ​分类组织、存储和管理各种数据; 确定组织数据的文件结构和存取;实现数据之间的联系;提供多种存取方法提高存取效率

      • 数据库的运行管理

        保证数据的安全性、完整性; 多用户对数据的并发使用; 发生故障后的系统恢复

      • 数据库的建立和维护功能(实用程序)

        数据库数据批量装载; 数据库转储;介质故障恢复; 数据库的重组织;性能监视等

  • 数据库系统(DBS)

    在计算机系统中引入数据库后的系统构成,包括数据库、数据库管理系统、应用系统、数据库管理员

2.2 数据管理技术的产生和发展

  • 数据管理:对数据进行分类、组织、编码、存储、检索和维护

  • 发展

    1. 人工管理阶段:数据不保存;应用程序管理数据;数据不共享,冗余度极大;数据不具有独立性

    2. 文件系统阶段:

      优点: 数据可长期保存;应用程序管理数据 ;文件系统管理数据
      缺点:数据共享性差、冗余度大;数据独立性差

    3. 数据库系统阶段

2.3 数据库系统的特点

  • 数据结构化

    文件系统:每个文件内部是有结构的,但是记录之间没有联系

    关系数据库:不仅数据内部结构化,整体也是结构化的,数据之间具有联系

  • 数据的共享性高,冗余度低,易扩充

  • 数据独立性高

    1. 物理独立性

      指用户的应用程序与存储在磁盘上的数据库中数据是相互独立的。当数据的物理存储改变了,应用程序不用改变。

    2. 逻辑独立性
      指用户的应用程序与数据库的逻辑结构是相互独立的。数据的逻辑结构改变了,用户程序也可以不变。

    3. 数据独立性是由DBMS的二级映像功能来保证的

  • 数据由DBMS统一管理和控制

    1. 数据的安全性(Security)保护
      保护数据,以防止不合法的使用造成的数据的泄密和破坏。

    2. 数据的完整性(Integrity)检查
      将数据控制在有效的范围内,或保证数据之间满足一定的关系。

    3. 并发(Concurrency)控制
      对多用户的并发操作加以控制和协调,防止相互干扰而得到错误的结果。

    4. 数据库恢复(Recovery)

      将数据库从错误状态恢复到某一已知的正确状态。

3 数据模型

3.1 两大数据模型

  • 在数据库中用数据模型这个工具来来抽象、表示和处理现实世界中的事物。通俗地讲数据模型就是现实世界的模拟。
  • 数据模型应满足三方面要求:能比较真实地模拟现实世界,容易为人所理解,便于在计算机上实现
  • 分类
    1. 概念模型(信息模型):它是按用户的观点来对数据和信息建模,用于数据库设计。
    2. 逻辑模型和物理模型:按计算机系统的观点对数据建模,用于DBMS的实现。
      • 逻辑模型主要包括网状模型、层次模型、关系模型、面向对象模型等,按计算机系统的观点对数据建模,用于DBMS实现。
      • 物理模型是对数据最底层的抽象,描述数据在系统内部的表示方式和存取方法,在磁盘或磁带上的存储方式和存取方法。

3.2 数据模型的组成要素

  • 数据结构
  • 数据操作
  • 完整性约束条件

3.3 概念模型

  • 信息世界的基本概念
    1. 实体(Entity):客观存在并可相互区别的事物称为实体,可以是具体的人、事、物或抽象的概念。
    2. 属性(Attribute):实体所具有的某一特性称为属性。一个实体可以由若干个属性来刻画。
    3. 码(Key):唯一标识实体的属性集称为码。
    4. 域(Domain):属性的取值范围称为该属性的域。
    5. 实体型(Entity Type):用实体名及其属性名集合来抽象和刻画同类实体称为实体型。例如:学生(学号、姓名、性别)
    6. 实体集(Entity Set):同一类型实体的集合称为实体集
    7. 联系(Relationship):现实世界中事物内部以及事物之间的联系在信息世界中反映为实体内部的联系和实体之间的联系。
      • 实体内部的联系通常是指组成实体的各属性之间的联系
      • 实体之间的联系通常是指不同实体集之间的联系
      • 一对一联系(1:1)、一对多联系(1:n)、多对多联系(m:n)
  • 实体 — 联系方法(E-R方法、E-R模型)
    1. 实体型:用矩形表示,矩形框内写实体名
    2. 属性:用椭圆表示,并用无向边将其与相应的实体连接起来
    3. 联系:用菱形表示,菱形框内写明联系名,并用无向边分别与有关实体连接起来,同时在无向边旁标上联系的类型(1:1、1:n或m:n)
    4. 联系的属性:联系本身也是一种实体型,也 可以有属性。如果一个联系具有属性,则这些属性也要用无向边与该联系连接起来

3.4 最常用的数据模型

  • 层次模型:用树形结构来表示各类实体以及实体间的联系

  • 网状模型:采用网状模型作为数据的组织方式

  • 关系模型:采用关系模型作为数据的组织方式

    1. 在用户观点下,关系模型中数据的逻辑结构是一张二维表,它由行和列组成。

      学号 姓名 年龄 性别
      0001 张三 16
      0002 李四 18
    2. 数据结构

      • 关系:一个关系对应通常说的一张表
      • 元组(Tuple):表中的一行即为一个元组
      • 属性(Attribute):表中的一列即为一个属性,给每一个属性起一个名称即属性
      • 主码(Key):表中的某个属性组,它可以唯一确定一个元组。
    3. 域(Domain):属性的取值范围。

      • 分量:元组中的一个属性值。
      • 关系模式:对关系的描述,关系名(属性1,属性2,…,属性n)
    4. 关系的完整性约束条件

      • 实体完整性
      • 参照完整性
      • 用户定义的完整性

4 数据库系统结构

4.1 分类

  • 从最终用户角度:

    1. 单用户结构

      整个数据库系统(应用程序、DBMS、数据)装在一台计算机上,为一个用户独占,不同机器之间不能共享数据。

    2. 主从式结构

      • 一个主机带多个终端的多用户结构
      • 数据库系统,包括应用程序、DBMS、数据,都集中存放在主机上,所有处理任务都由主机来完成。各个用户通过主机的终端并发地存取数据库,共享数据资源。
      • 优点
        易于管理、控制与维护。
      • 缺点
        当终端用户数目增加到一定程度后,主机的任务会过分繁重,成为瓶颈,从而使系统性能下降。
        系统的可靠性依赖主机,当主机出现故障时,整个系统都不能使用。
    3. 分布式结构

      • 数据库中的数据在逻辑上是一个整体,但物理地分布在计算机网络的不同结点上。
      • 网络中的每个结点都可以独立处理本地数据库中的数据,执行局部应用
      • 同时也可以同时存取和处理多个异地数据库中的数据,执行全局应用
      • 优点
        适应了地理上分散的公司、团体和组织对于数据库应用的需求。
      • 缺点
        数据的分布存放给数据的处理、管理与维护带来困难。
        当用户需要经常访问远程数据时,系统效率会明显地受到网络传输的制约。
    4. 客户/服务器

      • 把DBMS功能和应用分开
        网络中某个(些)结点上的计算机专门用于执行DBMS功能,称为数据库服务器,简称服务器,其他结点上的计算机安装DBMS的外围应用开发工具,用户的应用系统,称为客户机
      • 集中的服务器结构
        一台数据库服务器,多台客户机
      • 分布的服务器结构
        在网络中有多台数据库服务器
        分布的服务器结构是客户/服务器与分布式数据库的结合
    5. 浏览器/应用服务器/数据库服务器多层结构

      • 客户端:
        浏览器软件、用户界面

        浏览器的界面统一,广大用户容易掌握

        大大减少了培训时间与费用。

      • 服务器端分为两部分:
        Web服务器、应用服务器
        数据库服务器等

  • 从数据库管理系统角度,数据库系统通常采用三级模式结构,是数据库系统内部的系统结构

4.2 数据库系统的三级模式结构

  • 结构图

  • 模式
    1. 模式(逻辑模式)
      数据库中全体数据的逻辑结构和特征的描述
      所有用户的公共数据视图,综合了所有用户的需求
      一个数据库只有一个模式
    2. 模式的地位:是数据库系统模式结构的中间层
      与数据的物理存储细节和硬件环境无关
      与具体的应用程序、开发工具及高级程序设计语言无关
    3. 模式的定义
      数据的逻辑结构(数据项的名字、类型、取值范围等)
      数据之间的联系
      数据有关的安全性、完整性要求
  • 外模式(子模式、用户模式)
    1. 数据库用户(包括应用程序员和最终用户)使用的局部数据的逻辑结构和特征的描述,数据库用户的数据视图,是与某一应用有关的数据的逻辑表示
    2. 外模式的地位:介于模式与应用之间
      • 模式与外模式的关系:一对多
        外模式通常是模式的子集。
        一个数据库可以有多个外模式。反映了不同的用户的应用需求、看待数据的方式、对数据保密的要求。
        对模式中同一数据,在外模式中的结构、类型、长度、保密级别等都可以不同。
      • 外模式与应用的关系:一对多
        同一外模式也可以为某一用户的多个应用系统所使用
        但一个应用程序只能使用一个外模式
    3. 外模式的用途
      • 保证数据库安全性的一个有力措施
      • 每个用户只能看见和访问所对应的外模式中的数据
  • 内模式(存储模式)
    1. 是数据物理结构和存储方式的描述
    2. 是数据在数据库内部的表示方式

4.3 数据库二级映像功能与数据独立性

  • 二级映象在DBMS内部实现这三个抽象层次的联系和转换

    1. 外模式/模式映像

      • 每一个外模式,数据库系统都有一个外模式/模式映象,定义外模式与模式之间的对应关系
      • 当模式改变时,数据库管理员修改有关的外模式/模式映象,使外模式保持不变
      • 应用程序是依据数据的外模式编写的,从而应用程序不必修改,保证了数据与程序的逻辑独立性,简称数据的逻辑独立性
    2. 模式/内模式映像

      • 模式/内模式映象定义了数据全局逻辑结构与存储结构之间的对应关系。例如,说明逻辑记录和字段在内部是如何表示的

      • 数据库中模式/内模式映象是唯一的,该映象定义通常包含在模式描述中

      • 当数据库的存储结构改变了(例如选用了另一种存储结构),数据库管理员修改模式/内模式映象,使模式保持不变

      • 应用程序不受影响。保证了数据与程序的物理独立性,简称数据的物理独立性

deque

deque 容器

1. 基本概念

  • 功能:双端数组,可以对头端进行插入删除操作,deque的迭代器支持随机访问
  • 和vector的区别
    1. vector对于头部的插入删除效率低,数据量越大,效率越低
    2. deque相对而言,对头部的插入删除速度比vector快
    3. vector访问元素时的速度没有deque快

  • 内部工作原理

    1. deque有一个中控器,维护每段缓冲区的内容,缓冲区中存放真实数据
    2. 中控器维护的是每个缓冲区的地址,是的使用deque时像一片连续的内存空间
    3. 这种结构决定了比vector插入数据时要快,然而访问数据比vector慢

2. 构造函数

  • 函数原型
    1. deque< T > deq; //默认构造函数
    2. deque(beg,end); //构造函数将[begin,end)区间的元素拷贝给本身
    3. deque(n,elem); //构造函数将n个elem拷贝给本身
    4. deque(const deque &deq);//拷贝构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
#include<deque>
using namespace std;
void printDeque(const deque<int>& deq) {
for (deque<int>::const_iterator it = deq.begin(); it != deq.end(); it++) {
//*it = 100;//报错,限定为只读状态
cout << *it << " ";
}
}
int main() {
deque<int> deq;
for (int i = 0; i < 10; i++) {
deq.push_back(i);
}
printDeque(deq);
deque<int> d1(deq.begin(),deq.end());
deque<int> d2(10, 100);
deque<int>d3(d1);
return 0;
}

3.赋值操作

  • 函数原型
    1. deque& operator = (const deque &deq);
    2. assign(beg,end);// 将[begin,end)区间的元素拷贝给本身
    3. assign(n,elem);//将n个elem拷贝给本身

4. 大小操作

  • 函数原型
    1. deque.empty(); //判断容器是否为空
    2. deque.size();//返回容器中元素的个数
    3. deque.resize(num);//重新指定容器的长度为num,若瑢变长,则以默认值填充新位置,如果变短,则末尾超出的容器长度的元素将会被删除
    4. deque.resize(n,elem);//多出的值用elem填充

5. 插入删除

  • 函数原型

    两端插入操作

    1. push_back();//在容器尾部插入一个数据
    2. push_front(); //在容器头部插入一个数据
    3. pop_back();//删除容器最后一个数据
    4. pop_front();//删除容器第一个数据

    指定位置操作

    1. insert(pos,elem);//在pos位置插入一个elem,返回新数据的位置
    2. insert(pos,n,elem);//在pos位置插入n个elem元素,无返回值
    3. insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值
    4. clear();
    5. erase(beg,end);//删除[beg,end)区间的数据,无返回值
    6. erase(pos);//删除pos位置的元素,返回下一个数据的位置

6. 数据存取

  • 函数原型
    1. at(int idx);
    2. operator[];
    3. front();
    4. back();

7.排序

  • 函数原型

    sort(iterator beg,iterator end);//默认升序

    对于支持随机访问的迭代器的访问,都可以利用sort直接排序

优先级队列

STL优先级队列

  • priority_queue:自适应容器(容器适配器),不能使用list

  • 最大值优先级队列:最大的在队列的最前面

  • 最小值优先级队列:最小的在队列最前面

  • 包含的头文件 queue

  • 函数接口

    priority_queue<int,deque< int > > pq; //默认最大值优先级队列

    priority_queue<int,vector< int > > pq;

    priority_queue< int, deque< int >,greater< int >> pq;//最小值优先级队列

    pq.empty();

    pq.size();

    pq.top();

    pq.pop();

    pq.push(item);

vector容器

Vector 容器

1 vector基本概念

  • 功能

    vector数据结构和数组十分相似,也称为单端数组

  • vector和普通数组的区别

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

  • 动态扩展

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

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

2 vector构造函数

  • 功能描述

    创造vector容器

  • 函数原型

    1. vector <T> v //采用模板实现类实现,默认构造函数
    2. vector(v.begin(),v.end()); 将v[ begin(), end() ) 区间中的元素拷贝给本身
    3. vector(n,elem); //构造函数将n个elem拷贝给本
    4. vector(const vector &vec); //拷贝构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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(5, 100);
printVector(v3);
vector<int>v4(v3);
printVector(v4);
}

3 vector赋值操作

  • 函数原型
    1. vector& operator=(const vector &vec)
    2. assign(beg,end); //给[beg, end)区间中的数据拷贝复制给本身
    3. assign(n,elem) //将n个elem拷贝赋值给本身
1
2
3
4
5
6
7
8
9
10
11
12
13
void test02() {
vector<int>v1;
for (int i = 0; i < 10; i++) { v1.push_back(i); }
vector<int>v2;
v2 = v1;
printVector(v2);
vector<int>v3;
v3.assign(v1.begin(), v1.end());
printVector(v3);
vector<int>v4;
v4.assign(5, 10);
printVector(v4);
}

4 vector容量和大小

  • 函数原型
    1. empty(); //判断容器是否为空
    2. capacity(); //容器的容量,容量大于等于长度
    3. size(); //返回容器中元素的个数
    4. resize(int num); //重新指定容器的长度为num,若容器边长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除
    5. resize(int num,elem) //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。若容器变短,则末尾超出容器长度的元素被删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void test03() {
vector<int>v1;
for (int i = 0; i < 5; i++) { v1.push_back(i); }
if (v1.empty()) cout << "v1为空" << endl;
else {
cout << "v1不为空" << endl;
cout << "v1的容量为" << v1.capacity() << endl;
cout << "v1的大小为" << v1.size() << endl;
}
//重回新指定大小
v1.resize(7);
printVector(v1);//0 1 2 3 4 0 0
v1.resize(10,8);
printVector(v1);//0 1 2 3 4 0 0 8 8 8
v1.resize(5);
printVector(v1);//0 1 2 3 4
}

5 vector插入和删除

  • 函数原型
    1. push_back(elem); //尾部插入元素
    2. pop_back(); //删除最后一个元素
    3. insert(const_iterator pos,ele); //迭代器指向位置pos插入元素ele
    4. insert(const_iterator pos,int count,ele);//迭代器指向位置pos插入count个元素ele
    5. erase(const_iterator pos);//删除迭代器指向的元素
    6. erase(const_iterator start,const_iterator end);//删除迭代器从start到end之间的元素
    7. clear();//删除容器中所有的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void test04() {
vector<int>v1;
for (int i = 0; i < 5; i++) { v1.push_back(i); }
v1.pop_back();
printVector(v1);
//插入
v1.insert(v1.begin(), 100);
printVector(v1);
v1.insert(v1.begin(),2, 50);
printVector(v1);
//删除
v1.erase(v1.end()-1);
printVector(v1);

v1.erase(v1.begin(),v1.end()-2);
printVector(v1);
v1.clear();
}

6 vector数据存取

  • 函数原型
    1. at(int idx); //返回索引 idx 所值的数据
    2. operator[]; //返回索引 idx 所指的数据
    3. front(); //返回容器中第一个数据
    4. back(); // 返回容器中最后一个数据元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void test05() {
vector<int>v1;
for (int i = 0; i < 5; 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.front() << endl;
cout << "最后一个元素" << v1.back() << endl;
}

7 vector互换容器

  • 函数原型

    swap(vec);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//巧用swap可以收缩内存空间
void test07() {
vector<int>v1;
vector<int>v2;
for (int i = 0; i < 100000; i++)
{
v1.push_back(i);
v2.push_back(i + 2);
}
cout << "v1的容量为" << v1.capacity() << endl;
cout << "v1的大小为" << v1.size() << endl;
v1.resize(3);//容量没有变
cout << "v1的容量为" << v1.capacity() << endl;
cout << "v1的大小为" << v1.size() << endl;
//收缩内存
vector <int>(v1).swap(v1);
//vector <int>(v1)
cout << "v1的容量为" << v1.capacity() << endl;
cout << "v1的大小为" << v1.size() << endl;
}

运行结果:

1
2
3
4
5
6
v1的容量为138255
v1的大小为100000
v1的容量为138255
v1的大小为3
v1的容量为3
v1的大小为3

8 vector预留空间

  • 功能描述

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

  • 函数原型

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void test08() {
vector<int>v;
int num = 0;//记录动态扩展次数
int* p = NULL;//指向当前容器中的首元素
for (int i = 0; i < 300; i++) {
v.push_back(i);
//判断指针p所指的首元素地址是否发生变化,如果变化,则重新分配了内存,num加一并更新p
if (p != &v[0]) {
num++;
p = &v[0];
}
}
cout << "动态扩展次数:" << num << endl;
}

运行结果:

1
动态扩展次数:15

使用reserve后

1
v.reserve(300);//预留空间

运算结果

1
动态扩展次数:1

string容器

string容器

1 string基本概念

本质

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

*string 和 char 的区别:

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

特点

  • string类内部封装了很多成员方法,例如查找find,拷贝copy,删除delete,插入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 初始化

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;
void test01() {
string s1;

const char* str = "hello world";
string s2(str);
cout << s2 << endl;

string s3(s2);
cout << s3 << endl;

string s4(10, 'a');
cout << s4 << endl;
}
int main() {
test01();
return 0;
}

3 string赋值函数

  • 赋值的函数原型:

    (1)stirng& operator=(const char* s); //char *类型字符串赋值给当前的字符串

    (2)string& operator=(const string &s); //把字符串 s 赋给当前的字符串

    (3)string& operator=(char c); //字符赋给当前的字符串

    (4)string& assign(const char* s); //把字符串 s 赋给当前的字符串

    (5)string& assign(const char* s,int n); //把字符串 s 的前n个字符赋给当前的字符串

    (6)string& assign(const string &s); //把字符串 s 赋给当前的字符串

    (7)string& assign(int n,char c); //用n个字符 c 赋给当前字符串

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void test02() {
//stirng& operator=(const char* s); //char *类型字符串赋值给当前的字符串
string str1 = "hello world";

//string& operator=(const string & s); //把字符串 s 赋给当前的字符串
string str2 = str1;

//string& operator=(char c); //字符赋给当前的字符串
string str3;
str3 = 'a';

//string& assign(const char* s); //把字符串 s 赋给当前的字符串
string str4;
str4.assign("hello");

//string& assign(const char* s, int n);//把字符串 s 的前n个字符赋给当前的字符串
string str5;
str5.assign("hello c++", 5);

//string& assign(const string & s); //把字符串 s 赋给当前的字符串
string str6;
str6.assign(str5);

//string& assign(int n,char c); //用n个字符 c 赋给当前字符串
string str7;
str7.assign(5, 'x');
}

4 string字符串拼接

  • 功能描述

    实现在字符串末尾拼接字符串

  • 函数原型

    (1)string& operator +=(const char* str); // 重载+=操作符

    (2)string& operator +=(const char c); // 重载+=操作符

    (3)string& operator +=(const string& str); // 重载+=操作符

    (4)string& append(const char *s); //把字符串s连接到当前字符串结尾

    (5)string& append(const char *s,int n); //把字符串 s 的前n个字符连接到当前字符串结尾

    (6)string& append(const string &s); //同 operator +=(const string& str)

    (7)string& append(const string &s,int pos,int n); //字符串s中从pos开始的n个字符连接到字符串结尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void test03() {
//string& operator +=(const char* str); 重载+=操作符
string str1 = "我";
str1 += "很好";

//string& operator +=(const char c); 重载+=操作符
str1 += ':';

//string& operator +=(const string & str); 重载+=操作符
string str2 = "加油\n";
str1 += str2;

//string& append(const char* s); 把字符串s连接到当前字符串结尾
string str3 = "I ";
str3.append("Love ");

//string& append(const char* s,int n); 把字符串 s 的前n个字符连接到当前字符串结尾
str3.append("Gameabcd",4);
//string& append(const string & s); 同 operator +=(const string& str)
str3.append(str2);
//string& append(const string & s, int pos, int n); 字符串s中从pos开始的n个字符连接到字符串结尾
}

5 string查找和替换

  • 功能描述

    (1)查找:查找指定字字符串是否存在

    (2)替换:在指定的位置替换字符串

  • 函数原型

    (1)int find(const string& str ,int pos = 0) const; //查找 str 第一次出现的位置,从pos开始查找

    (2)int find(const char* s,int pos = 0) const; //查找 s 第一次出现的位置,从pos开始查找

    (3)int find(const char* s, int pos,int n) const; //从pos位置查找s的前n个字符的第一个位置

    (4)int find(const char c,int pos = 0) const; //查找字符c第一次出现的位置

    (5)int rfind(const string& str,int pos = npos) const; //查找 str 最后一次出现的位置,从pos开始查找

    (6)int rfind(const char* s,int pos = npos) const; //查找 s 最后一次出现的位置,从pos开始查找

    (7)int rfind(const char* s, int pos, int n) const; //从pos位置查找s的前n个字符的最后一个位置

    (8)int rfind(const char c,int pos = 0) const //查找字符c最后一次出现的位置

    (9)string& replace(int pos,int n, const string& str); //替换从pos开始n个字符为字符串str

    (10) string& replace(int pos, int n, const char* s); //替换从pos开始n个字符为字符串s

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
void test04() {
string str1 = "abcdefghde";
int pos = str1.find("de");//找到返回位置,是从0开始算,未找到返回-1
cout << pos << endl;
pos = str1.find("k");//找到返回位置,是从0开始算,未找到返回-1
cout << pos << endl;
pos = str1.rfind("de");//从右往左查找
cout << pos << endl;
//替换
string str2 = "asdfgh";
str2.replace(1, 3, "1111");//从1号位置起三个字符替换为 1111
cout << "str = " << str2 << endl;
}

6 string字符串比较

  • 功能函数

    字符串之间的比较

  • 比较方式

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

    = 返回 0 > 返回 1 < 返回 -1

  • 函数原型

    (1)int compare(const string &s ) const;

    (2)int compare(const char *s ) const;

示例:

1
2
3
4
5
6
7
8
9
void test05() {
string str1 = "hello";
string str2 = "hello";
string str3 = "world";
if (str1.compare(str2) == 0)
cout << "str1 = str2" << endl;
if (str1.compare(str3) == -1)
cout << "str1 < str2" << endl;
}

7 string字符存取

  • string单个字符存取方式

    (1)char& operator[] (int n)

    (2)char& at(int n)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void test06() {
    string str = "HELLO";
    //读
    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] = 'Q';
    str.at(2) = 'W';
    cout << str << endl;
    }

8 string插入和删除

  • 功能描述

    对string字符串进行插入和删除字符操作

  • 函数原型

    (1)string& insert(int pos,const char* s)

    (2)string& insert(int pos,const string& str)

    (3)string& insert(int pos,int n,char c)

    (4)string& erase(int pos,int n = npos) //删除从Pos开始的n个字符

1
2
3
4
5
6
7
void test07() {
string str = "HELLO";
str.insert(str.size(), "World");
cout << str << endl;
str.erase(1, 5);
cout << str << endl;
}

9 string子串

  • 功能描述

    从字符串获取想要的子串

  • 函数原型

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

1
2
3
4
5
6
void test08() {
string email = "zhangsan@sina.com";
int pos = email.find('@');
string userName = email.substr(0, pos);
cout << "userName = " << userName << endl;
}

STL初识

STL 初识

2.1 STL的诞生

  • c++的面向对象和泛型编程思想,目的就是复用性的提升。为了简历数据结构和算法的一套标准,诞生了STL。

2.2 STL基本概念

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

2.3 STL六大组件

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

    (1)容器:各种数据结构,如vector、list、deque、set、map等,用来存放函数

    (2)算法:各种常用的算法,如sort、find、copy、for_each等

    (3)迭代器:扮演了容器和算法之间的胶合剂

    (4)仿函数:行为类似函数,可作为算法的某种策略

    (5)适配器:一种用来修饰容器或者仿函数或者迭代器接口的东西

    (6)空间配置器:负责空间的配置和管理

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

2.4.1 容器

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

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

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

    (1)序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置

    (2)关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系

2.4.2 算法(algorithm)

  • 质变算法:运算过程中会更改区间内的元素的内容,如拷贝、替换、删除
  • 非质变算法:运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值

2.4.3 迭代器

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

  • 每个容器都有自己专属的迭代器,这种迭代器非常类似于指针。

  • 种类

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

2.5 容器算法迭代器初识

2.5.1 vector存放内置数据类型

  • 容器:vector
  • 算法:for_each
  • 迭代器:vector<int>::iterator

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
#include <vector>
#include <algorithm>
using namespace std;
void Myprint(int val) {
cout << val << " ";
}
void test01() {
//创建一个vector容器,数组
vector <int> v;
//向容器中插入数据
v.push_back(10);
v.push_back(20);
v.push_back(30);
//通过迭代器访问容器中的数据
vector <int>::iterator itBegin = v.begin();//起始迭代器,指向容器的第一个元素
vector <int>::iterator itEnd = v.end();//结束迭代器,指向容器的最后一个元素的下一个位置
//第一种遍历方式
while (itBegin != itEnd) {
cout << *itBegin << " ";
itBegin++;
}
cout << endl;
//第二种遍历方式
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
//第三种遍历方式
//参数:起始,结束,自定义函数
for_each(v.begin(),v.end(),Myprint);

}
int main() {
test01();
return 0;
}

2.5.2 vector存放自定义数据类型

  • vector 存放自定义的类型,并用迭代器输出

示例:存放类、指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
#include <vector>
using namespace std;
class Person {
public:
string m_name;
int m_age;
Person(string name, int age) :m_name(name), m_age(age) { }
};
void test01() {
vector<Person> v;
Person p1("张三", 18);
Person p2("李四", 19);
Person p3("王五", 20);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
//遍历容器中的数据
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
cout << it->m_age << " " << it->m_name << endl;
}
}
void test02() {
vector<Person*> v;//容器中存放指针
Person p1("张三", 18);
Person p2("李四", 19);
Person p3("王五", 20);
v.push_back(&p1);//传地址
v.push_back(&p2);
v.push_back(&p3);
//遍历容器中的数据
for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {
//it 是指向容器元素的指针,*it则是容器中的元素值,即指向对象的指针
cout << (*it)->m_age << " " << (*it)->m_name << endl;
}
}
int main() {
test01();
test02();
return 0;
}

2.5.4 vector容器嵌套容器

  • 容器中嵌套容器,将所有数据遍历输出

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <vector>
using namespace std;
void test01() {
vector< vector<int> > v;
vector<int> v1;
vector<int> v2;
vector<int> v3;
for (int i = 0; i < 3; i++) {
v1.push_back(i);
v2.push_back(i + 1);
v3.push_back(i + 2);
}
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
//遍历所有数据
for (vector<vector <int> >::iterator It = v.begin(); It != v.end(); It++) {
for (vector<int>::iterator it = (*It).begin(); it != (*It).end(); it++) {
cout << *it << " ";
}
cout << endl;
}
}
int main() {
test01();
return 0;
}

函数调用运算符重载(基础)

函数调用运算符重载(基础)

  • 函数调用运算符 ()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
using namespace std;
//打印类
class MyPrint {
public:
void operator()(string test) {
cout << test << endl;
}
};
//加法类
class MyAdd {
public:
int operator()(int a, int b) {
return a + b;
}
};
void test01() {
MyPrint Myprint;
Myprint("hello world");//重载函数调用
MyAdd Myadd;
cout << "Myadd(100, 100) = " << Myadd(100, 100) << endl;
//匿名函数对象
cout << "Myadd()(100, 100) = " << MyAdd()(100, 100) << endl;
}
int main() {
test01();
return 0;
}

运算符重载(赋值运算符)

赋值运算符重载

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

    (1)默认构造函数(无参,函数体为空)

    (2)默认析构函数(无参,函数体为空)

    (3)默认拷贝构造函数,对属性进行值拷贝

    (4)赋值运算符 operator= ,对属性进行值拷贝

  • 如果类中有属性指向堆区,做赋值操作也会出现深浅拷贝问题

  • 在c++中为了实现连等,所以函数返回值为自身,并且可以提高效率,减少临时对象的开销。

示例:c++中内置连等,其运算结果为 a = 2 b = 1 c = 2,显然在执行完括号内赋值后返回了 a 本身。

1
2
3
4
5
int a = 0;
int b = 1;
int c = 2;
(a = b) = c;
cout << a << " " << b << " " << c << endl;

重载实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<iostream>
using namespace std;
class Person {
public:
int *m_age;
Person(int age){
m_age = new int(age);
}
~Person()
{
if (this->m_age != NULL) {
delete m_age;
m_age = NULL;
}
}
Person& operator=(Person& p) {
if (this->m_age != NULL) {
delete m_age;
m_age = NULL;
}
//深拷贝
m_age = new int(*p.m_age);
return *this;//返回自身,实现连等运算
}
};
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();
return 0;
}

运算符重载(递增运算符)

递增操作符的重载

  • 前置递增:比如 ++i:先对 i 进行加一操作,i++:返回的是 i 自增之前的值

  • ++i 的返回是可修改值,然而 i++ 的返回是不可以修改的值。

    (1)(i++)++ ; 编译错误

    (2)(++i)++ ; 编译通过

  • 重载代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
using namespace std;
class MyInt {
friend ostream& operator<<(ostream& cout, MyInt p);
public:
MyInt(int num) :m_num(num){ }
//重载 前置++运算符
MyInt& operator++() {//先加1,后返回
this->m_num += 1;
return *this;//返回解引用,即返回自身,实现链式递增
}
//重载 后置++运算符
//c++中要求:int 代表占位参数,用来区分前置和后置
const MyInt operator++(int) {
//把旧的先复制一份,在把原来的加一,最后将复制的那份返回去。
MyInt temp = *this;//先记录当前结果
m_num++;//递增
return temp;//返回记录结果
}
private:
int m_num;
};
ostream& operator<<(ostream& cout, MyInt p) {
cout << p.m_num;
return cout;
}
void test01() {
MyInt p(10);
cout << p << endl;
cout << ++p << endl;
}
void test02() {
MyInt p(10);
cout << p++ << endl;
cout << p << endl;
}
int main() {
test01();
test02();
return 0;
}

前置后置的区别

  • 返回值

    前置递增返回的是操作对象自身,所以返回值类型是引用。

    后置递增返回的是局部对象,并且因为c++自身实现的后置递增运算 i++ 是不可修改的,所以返回值是 const 对象,否则像 (i++)++ 这样的代码是可以通过编译的,让人误以为真的可以实现递增两次,实际上是不可以的,第一次递增作用在自身,第二次是作用在返回的临时对象上。

  • 效率

    后置递增相比前置递增而言,增加了一个临时对象的创建,就会多出构造和析构的开销,效率便不如前置递增。