学习笔记 - 程序设计(C++)部分概念
前言
稍微地将 《程序设计教材:用C++语言编程(第三版)》 (陈家骏、郑滔著) 看完了,为了避免忘记书中的一些概念,就将一些常用的概念记了下笔记。就是方便以后查看呗。
概述
计算机工作模型
冯诺依曼结构的计算机 由 存储、输入、输出、运算、控制 五个单元构成。
- 存储单元 用于存储程序(指令序列)和数据。
- 输入单元 和输出单元用于作为计算机与外界的接口,用于实现系统的输入和输出功能。
- 运算单元 用于进行算术/逻辑运算。
- 控制单元 用于控制程序的执行流程和根据指令向其他单元发送控制信号。
硬件 是 构成计算机的元器件和设备
冯诺依曼计算机的硬件一般按 中央处理器、内存、外围设备 构成。
- 中央处理器(CPU) :计算机的核心部件,用于指向指令以完成计算任务。
- 内存 :内部存储器(或主存储器)的简称,用于存储横着运行的程序和正在使用的数据。
- 外围设备 :提供了计算机与外界的接口,主要用于计算机输入/输出以及为计算机提供大量的信息存储。
- *外部存储器 :大容量的低速存储部件(与内存相比),用于永久性地(断电后仍然保持内容不丢失)存储程序、数据、各种文档信息。
软件 是 计算机系统中的程序以及相关的文档
- 程序 :计算任务的处理对象(数据)与处理规则(算法)的描述,体现为指令序列,由计算机执行。
- 文档 :为了方便理解程序而准备的说明资料,供程序的使用者以及开发与维护者使用。
软件 可分为 系统软件 和 应用软件
-
系统软件 :计算机系统中直接让硬件发挥作用和实现计算机基本功能的软件,如操作系统,与具体应用领域无关,其他软件一般要通过系统软件发挥作用。
-
应用软件 :用于特定领域的专用软件。
-
*支撑软件 :帮助软件开发与维护的软件。
其中,由 硬件 构成的计算机常常被称为 裸机 ,在它之上,每加上一层 软件 就得到一个功能更强的计算机——虚拟机 。
程序设计
程序设计 是 为计算机编制完成各种任务所需程序的过程
程序包含了 数据 和 对数据的操作(加工) 这两部分的描述:
- 算法 :对数据的加工步骤的描述。
- 数据结构 :反应待解问题本质的数据描述。
*程序 = 算法 + 数据结构
程序设计范式 是 设计、组织和编写程序的一种方式,往往基于一组理论、原则和概念
- 过程式程序设计 :以功能为中心、基于功能分解和过程抽象。
- 对象式(面向对象)程序设计 :以数据为中心、基于数据抽象。
- 函数式程序设计 :围绕函数进行,函数也被作为值(数据)看待,函数的参数和返回值也可以是函数(基于理论为递归函数理论和lambda演算)。
- 逻辑式程序设计 :把程序组织成一组事实和一组推理规则,程序在事实的基础上运用推理规则来实施计算(基于谓词演算)。
其中,过程式程序设计 和 面向对象程序设计 属于 命令式程序设计 (强调对 “如何做” 的描述,对操作步骤和状态变化给出明确描述)。
而,函数式程序设计 和 逻辑式程序设计 属于 申述式程序设计 (强调对 “做什么” 的描述,如何做则由实现系统自动完成)
软件工程 是 采用工程方法开发软件,强调对软件开发过程的管理和加强各个阶段的文档制作
软件生存周期 是 软件从无到有,再到最后消亡(报废)的过程
软件生存周期可以分成 需求分析、系统设计、编程实现、测试与调试、运行与维护 阶段。
- 需求分析 :搞清楚要解决的问题并给出问题的明确定义的工作。
- 系统设计 :以计算机解决问题的方式设计出一个对问题的解决方案的工作。
- 编程实现 :用实际能被计算机李娇儿的程序语言把设计方案表示出来。
- 测试与调试 :
- 测试 :用以发现程序的逻辑错误和运行异常错误。
- 静态测试 :不运行程序,通过静态分析找出逻辑错误和异常错误。
- 动态测试 :利用一些测试数据,通过运行程序观察程序的运行结果是否与预期的结果相符。
- 调试 :给错误定位的过程。
- 测试 :用以发现程序的逻辑错误和运行异常错误。
- 运行与维护 :
- 维护 : 在使用过程中发现并改正程序错误的过程。
- 正确性维护 :改正程序中的错误。
- 完善性维护 :根据用户要求使得程序功能更加完善。
- 适应性维护 :把程序移植到不同计算平台或环境,是指能够运行。
- 维护 : 在使用过程中发现并改正程序错误的过程。
低级语言 是 与特定计算机体系结构密切相关的编程语言,包括机器语言和汇编语言
- 机器语言 :采用指令编码和数据的存储地址来表示指令的操作以及操作数。
- 汇编语言 :用符号来表示指令的操作和操作数。
机器语言 和 汇编语言 的语言成分一般具有一一对应的关系。
机器语言 写的程序可以 直接 在计算机上执行,用 汇编语言 写的程序必须翻译成 机器语言 程序才能被执行(由 汇编程序 进行翻译)。
高级语言 是 人容易理解和有利于人对解题过程进行描述的编程语言(通常称为程序设计语言)
高级语言 需要翻译成 机器语言 才能在计算机上运行,翻译方式有 编译、解释 两种。
- 编译 :把高级语言程序(称为源程序)首先翻译成功能上等价的机器语言程序(称为目标代码程序)或汇编语言程序(再通过汇编程序翻译成目标代码程序),然后执行目标代码程序,此时并不需要源程序。
- 解释 :对源程序中的语句进行逐句翻译并执行,翻译完了程序也就执行完毕,不产生目标代码程序,但每次执行都需要源程序。
根据把 高级语言程序 翻译成 机器语言 的翻译方式可把 翻译程序 分为 编译程序、解释程序 。
一般来说,编译 比 解释执行 运行效率要高;而 解释执行 可以实现语言的平台无关性。
任何一个语言都会涉及 语法、语义、语用 三个方面。
- 语法 :数学结构正确的语言成分应遵循的规则。
- 语义 :语言成分的含义。
- 语用 :语言成分的使用场合、以及所产生的实际效果。
C++语言
C语言 的特点是 简洁、灵活、高效 等。
C++ 的主要特点是 灵活、高效 ,是未能进行面向对象程序设计而设计的一种高级程序设计语言,保留了C语言的所有成分和特点。
其中,语言的 语法 又分为 词法、句法 。
- 词法 :语言构词的规则。
- 句法 :由次构成句子(程序)的规则。
对于 词法 :
- 字符集 :构成语言的基本符号。
- 单词 :由字符集中的字符按照一定规则构成的具有一定意义的最小语法单位。
- 标识符 :由大小写英文字母、数字、下划线构成的字符序列(用作程序实体的名字)。
- 关键字 :语言预定义的标识符,有固定的含义和作用,不能用作自定义实体名字。
- 字母常量 :在程序中直接写出的常量值。
- 操作符 :用于描述对数据的操作。
- 标点符号 :起到某些语法、语义上的作用。
C++ 程序运行步骤有 编辑、编译、连接、运行 四步骤。
- 编辑 :利用某个具有文字处理功能的 编辑程序 把C++程序输入计算机中。
- 编译 :利用某个C++编译程序对保存在外村的C++源程序进行翻译,翻译结果作为 目标程序 保存到外存的目标文件中。
- 连接 :将编译得到的目标文件和程序中用到的一些预定义的标准功能所在的目标文件(库文件)通过 连接程序 连接起来,作为一个 可执行程序 保存到外存的可执行文件中。
- 运行 :通过操作系统提供的应用程序执行机制,把某个可执行文件中的可执行程序装入内存,让程序运行起来。
简单数据类型描述——类型和表达式
概述
静态类型语言 是 要求在静态的程序(运行前的程序)必须为每个数据指定类型
动态类型语言 是 在程序运行中数据被用到时才确定它们的类型
C++ 是一种 静态类型语言 。
基本数据类型
基本数据类型 是 语言预先定义好的数据类型(又称为标准类型、内置类型)。
- 整数类型 :用于描述通常的整数。
- 实数类型 :用于描述通常的实数。
- 字符类型 :用于描述文本类型数据中的单个字符。
- 逻辑类型 :由于描述 “真” 和 “假” 的逻辑值。
数据的表现形式
待处理的数据通常以 常量、变量 形式出现。
-
常量 :在程序执行过程中不变(或不能被改变)的数据。
-
字面常量(直接常量) :在程序中直接写出常量值的常量。
-
符号常量(命名常量) :有名字的常量。
与 字面常量 相比,符号常量 优势为:
- 增加程序易读性
- 提高程序对常量使用的一致性
- 增强程序的易维护性
-
-
变量 :在程序中可变的数据。
一个变量往往包含 名字、类型、值、内存空间、地址 等特征。
数据的基本运算——操作符
操作符 是 用于描述对数据的基本操作
(由于大部分操作符是对数据进行运算,又称 运算符,被操作的数据称为 操作数 ,可以是 变量 或 常量 )
- 算术操作符 :用于实现通常意义下的数值运算,操作数类型一般为算术型。
- 关系操作符 :用于比较两个操作数直接的大小关系。
- 逻辑操作符 :用于把一些简单条件通过逻辑运算构成复杂条件。
- 位操作符 :用于对数据的二进制位进行操作。
- 赋值操作符 :用于改变变量的值。
- 其他操作符 :如 条件操作符、逗号操作符、sizeof操作符 等。
对于类型转换,C++提供了 隐式转换、显式转换 两种。
- 隐式类型转换 :由编译程序按照某种预定的规则进行自动转换。
- 显式类型转换 :由写程序的人在程序中明确地给出转换。
数据基本运算的实现——表达式
表达式 是 由操作符、操作数以及圆括号组成的运算式,构成了程序基本运算单位
- 算术表达式 :结果为算术类型。
- 关系/逻辑表达式 :结果为 bool 类型。
- 地址表达式 :结果为指针类型。
- 常量表达式 :一个表达式中的操作数为常量或在编译时刻能确定它的值(如sizeof操作结果)。
另外,也可分为 左值表达式 和 右值表达式 。
- 左值表达式 :结果有明确的内存地址(能在程序中显式获得),如 x=<表达式>、++x、–x、x 。
- 右值表达式 :结果一般存放在临时存储单元中(无法显式获得),计算完后存储单元就无效了,如 x++、x–、#define 。
优先级 是 规定相邻的两个操作符优先级搞的先运算
结合性 是 如果相邻的两个操作符具有相同的优先级,则根据结合性决定先计算谁
程序的流程控制(算法)描述——语句
概述
语句 是 用来实现程序的流程控制
顺序执行
顺序执行 是 最简单的流程控制,按语句的书写次序,从左到右、从上到下依次执行。支持顺序执行的语句有,表达式语句、复合语句、空语句 。
-
表达式语句 :在表达式后面加上分号 “;” 。
-
复合语句(块) :有一对花括号( {} )括起来的一个或多个语句。
-
空语句 :即空操作,在语法上需要一条语句的地方加上 “;” 。
-
选择执行 :根据不同情况决定程序执行语句的情况。即实现 选择控制 ,又叫 **分支 **控制,有 if、switch 语句。
- if语句(条件语句) :根据一个条件满足与否决定是否执行某个语句或从两个语句中选择一个语句执行。
- switch语句(开关语句) :根据某个整型表达式的值在多组语句中选择一组语句执行。
循环(重复)执行
循环(重复)执行 是 重复执行一组语句直到某个条件满足(或不满足)为止
循环一般由四个部分组成:
- 循环初始化 :为重复执行的语句提供初始化数据。
- 循环条件 :描述了重复操作需要满足的条件(继续或终止循环)。
- 循环体 :要重复执行的语句。
- 下一次循环准备 :为下一次循环更新数据(包括重复操作以及循环条件判断需要的数据)
循环控制常常用来实现文件求解的 迭代法 和 穷举法 。
- 迭代法 :对待解问题先指定近似的初始解,按照某种规则基于这个初始解计算出下一个近似解,基于下一个近似解计算出再下一个近似解,直到某个条件满足后得到最终解。
- 穷举法 :对所有 “可能” 的解逐一去验证它是否满足指定的条件,满足条件则它是一个解,否则它不是解。
从本质上讲,循环可分成 计数控制循环 和 事件控制循环 。
- 计数控制循环 :在循环前就直到循环的次数,通过一个 循环控制变量 来对循环次数进行计数,循环重复执行循环体直到指定的次数。
- 事件控制循环(条件控制循环) :循环前不知道循环次数,循环终止是由循环体某次执行导致循环的结束条件得以满足而引起的。
无条件转移
无条件转移 是 直接跳转至某个位置。无条件转移语句有,goto、break、continue、return
- goto :转向带有相应 <语句标号> 的语句。
- break :
- 结束switch语句的某个分支的执行。
- 退出包含它的循环语句。
- continue :立即结束当前循环,准备进入下一次循环。
程序设计风格
易读性 是 直接影响到程序的易维护性,间接影响到程序正确性
程序设计风格 是 对程序进行静态分析所确定的程序特性,涉及程序易读性
结构化程序设计 是 按照一组能过提高程序易读性与易维护性的规则进行程序设计的方法
结构化程序设计通常可以采用 顺序、选择、循环 这三种基本结构实现,对于 结构良好 是指
- 每个程序应具有单入口、单出口性质。
- 程序中不包含不会停止执行的语句,程序一定在有限时间内结束。
- 程序中没有无用语句,程序中所有语句都有被执行机会。
关于 goto 语句的使用可分为 向前的转移 和 往回的转移 。
- 向前的转移 :隐含分支结构,可以用if语句实现。
- 往回的转移 :隐含循环,可以用while、do-whilte或for结构化循环语句显式地表示。
抽象过程——函数
概述
功能分解 是 先把程序功能分解成若干子功能,每个子功能又可以分解成若干子功能,直到最终分解出的子功能相对简单、容易实现为止,形成一种自顶向下、逐步精化的设计过程
功能复合 是 先设计子功能,然后把子功能逐步组合成更大的子功能,最后得到完整系统功能,形成自底向上的设计过程
采用 功能分解 和 功能复合 手段进行程序设计往往需要基于一种抽象机制——过程抽象 或 功能抽象 。
过程抽象 为解决大型、复杂问题提供了一种重要手段,使得程序设计者能过驾驭问题的复杂度。
子程序 是 取了名字的一段程序代码,程序中需要这些代码的地方用相应的名字取代,即按代码名来调用和执行相应的代码
子程序在实现过程抽象的同时,也实现了一种 封装 和 信息隐藏 的作用,在定义子程序时需要对所需要的参数和返回值进行说明。
- 形式参数 :用以接收调用者提供的数据。
- 实在参数 :向子程序形式参数提供的数据。
较常用的参数传递形式为 值传递 和 地址/引用传递 。
- 值传递 :在子程序调用时,把实在参数的一个拷贝(值)传给相应的形式参数,在子程序中通过形式参数直接访问调用者提供的数据。(当参数传递的数据量较大时,参数传递的效率不高)
- 地址/引用传递 :在子程序调用时,把实在参数的地址传给相应的形式参数,在子程序中通过形式参数间接访问调用者提供的数据。(能提高参数出阿迪效率,还可以利用地址/引用参数传递把子程序执行结果通过参数返回给调用者,不足在于间接访问效率较低)
C++函数
函数 是 C++提供的用于实现子程序功能的语言成分
与数学上的函数区别:
- 数学上的函数是两个集合之间的一个映射,每个函数都会得到一个结果,C++的函数可以没有结果。
- 数学上的函数不会改变调用者的数据,即数学上的函数是没有副作用的,而C++的函数可以通过全局变量、指针、引用的形式参数来改变调用者数据。
其中,函数 调用过程为以下步骤:
- 计算实参的值(对于多个实参,C++没有规定它们的计算次序)
- 把实参分别传递给被调用函数的相应实参
- 执行函数体
- 函数体中执行 return 语句返回函数调用点,调用点获得返回值(如果有返回值)并执行调用之后的操作。
局部变量 是 在复合语句中定义的变量,只能在定义它们的复合语句中(包括内层复合语句)使用(函数体属于复合语句)
全局变量 是 在函数外部定义的变量,一般能被程序中所有函数访问
函数的副作用 是 函数改变了函数调用者的数据(通过在函数中改变非局部变量的值)
标识符的作用域与变量的生存期
模块 是 通常由一组 “相关(实体共同实现一个逻辑上的功能)” 的程序实体的定义构成
模块划分的基本原则为 模块内部的内聚性最大 和 模块之间的耦合度最小 。
-
内聚性 :模块内部各个实体之间的关联程度。
-
耦合度 :各模块直接按的依赖程度。
好处是 模块有较高独立性,易理解、易维护以及可靠性高
一个程序模块一般包含 接口 和 实现 。
- 模块接口 :给出在本模块中定义的、提供给其他模块使用的一些程序实体的定义和声明。
- 模块实现 :给出模块中的程序实体的定义。
在C++中,一个模块同由 头文件 和 源文件 构成。
- 头文件 :存储模块接口的文件(“*.h”)
- 源文件 :存储模块实现的文件(“*.cpp”)
标识符作用域 是 一个标识符所标识的程序实体在程序中可被访问到的程序段
C++把标识符的作用域分为若干类,包括 局部作用域、全局作用域、文件作用域、函数作用域、函数原型作用域、命名空间作用域、类作用域 。
-
局部作用域 :在函数定义或复合语句中,从标识符的定义点开始到函数定义或复合语句结束之间的程序段。
-
全局作用域 :构成C++程序的所有模块(源文件)。
-
文件作用域 :构成C++程序的某个模块(源文件)。
-
函数作用域 :由某个整个函数定义所构成的程序段(语句标号是唯一具有函数作用域的标识符)。
函数作用域 与 局部作用域 的区别
- 函数作用域包括整个函数,局部作用域是从定义点开始到函数定义或复合语句结束。
- 在函数体中,一个语句标号只能定义一次,即使在内层的复合语句中,也不能定义与外层相同的语句标号。
-
函数原型作用域 :用于函数声明的函数原型。
-
命名空间作用域 :给一组全局程序实体的定义取一个名字使之构成一个作用域。
-
类作用域 :类定义构成的一个作用域。
变量生存期 是 程序运行时一个变量占有内存空间的时间段
C++把变量的生存期分为 静态生存期、自动生存期、动态生存期 。
- 静态生存期 :内存空间从程序开始执行就进行分配,直到程序结束才会收空间。
- 自动生存期 :内存空间在程序执行到定义它们的复合语句(包括函数体)时才分配,当定义它们的复合语句执行结束时,空间将被回收。
- 动态生存期 :内存空间用 new 操作或调用函数 malloc 来进行分配,用 delete 操作或调用函数 free 来收回。
程序准备运行时,操作系统为其分配一块内存空间,包括 静态数据区、代码区、栈区、堆区。
- 静态数据区 :用于全局变量、static存储类的局部变量、某些常量的内存分配(固定大小)。
- 代码区 :用于存放程序的指令,对C++程序而言,存放的是所有函数代码(固定大小)。
- 栈区 :用于auto存储类的局部变量、函数的形式参数、函数调用时有关信息等的内存分配(不断变化)。
- 堆区 :用于动态变量的内存分配(不断变化)。
*基于栈的函数调用实现
- 调用者返回地址 => 调用者自动局部变量 => 调用者传入的参数 => 调用现场地址 => 函数自动局部变量
递归函数
递归函数 是 一个函数在其函数体中直接或间接地调用了自己
分而治之 是 把一个问题分解成若干个子问题,每个子问题的性质与原问题相同,只是它们的规模比原问题小
在定义递归函数时一定要对 一般情况 和 特殊情况 给出描述。
- 一般情况 :描述问题的分解和综合过程,其中包含递归调用。
- 特殊情况 :指出问题求解的特例,在该情况下不需要递归就能得出结果。
循环 和 递归 不同:
- 循环 是在一组变量上进行重复操作,通过不断改变这组变量的值向目标逼近,循环又常常称为迭代。
- 递归 是在不同的变量组上进行重复操作,这些变量包括函数的局部变量和形参,属于递归函数的不同实例。
标准函数库
标准库 是 C++语言为了方便程序设计,提供的定义了一些语言本身没有提供的功能的库
软件复用 是 利用已有的软件功能开发新的软件,是提高软件生产力和质量的一种重要技术
C++函数进一步讨论
带参数的宏 的不足:
- 有时会出现重复计算。
- 不进行参数类型的检查和转换。
- 不利于一些工具对程序的处理。
内联函数 是 在函数定义中的返回值类型之前加上一个关键字 “inline” ,作用是建议编译程序把该函数的函数体展开到调用点(避免函数函数调用时的开销带来的效率损失)
对于 内联函数 应该注意:
- 编译程序对内联函数的限制(只是建议编译程序这么做,编译程序不一定都能做到,如递归函数无法内联)。
- 内联函数名具有文件作用域。
函数名重载 是 给多个不同的函数取相同的名字
绑定(定联、联编、捆绑) 是 确定一个对重载函数的调用对应着哪一个重载函数定义的过程
匿名函数 的表达式格式:
[<环境变量使用说明>](形式参数)-><返回值类>{<函数体>}
复合数据的描述——构造数据
枚举类型——自定义值集的数据描述
枚举类型 是 由用户自定义的一种简单数据类型
枚举类型与C++提供的基本数据类型 不同 的是:
- 基本数据类型的值集是语言预先定义好的,枚举类型的值集是由程序定义的。
数组类型——由多个同类型元素构成的复合数据描述
数组类型 是 由固定多个同类型的具有一定次序关系的元素所构成的复合数据
数组类型可以分为 一维数组、二维数组、多维数组 。
- 一维数组 :由固定多个同类型的具有线性次序关系的数据所构成的复合数据(如 线性表 )。
- ASCIIZ串 :在字符串的最后一个字符后面存储一个ASCII码为0的字符用作字符串的结束标记。
- 二维数组 :由固定多个同类型的具有行列结构的数据所构成的复合数据。
结构类型——有若干属性构成的复合数据描述
结构类型 是 由固定多个、类型可以不同的元素所构成的复合数据,元素之间在逻辑上没有次序关系
成员 是 结构类型中的元素
结构类型是一种用户自定义类型,在一些其他语言中称为 记录类型 。
另外,结构类型的成员存在内存空间优化(如char,int中char可能补齐成4个字节)。
联合类型——用一种类型标识多种类型的数据
联合类型 是 能过表示多种数据的数据类型
联合类型也是一种用户自定义的数据,其各个成员 共享内存空间 ,内存空间大小由 占用内存空间最大 的成员决定。
指针类型——内存地址的描述
指针类型 是 用户自定义的数据类型,值集由一些指针构成的集合
指针是 内存地址 的抽象表示,一个指针代表一个 内存地址 。
*多级指针 是 指针变量所指向的变量的变量类型为指针类型
引用类型——变量的别名
引用类型 是 为一个已有的变量取一个别名
定义引用变量时必须有 初始化 ,且引用变量和被引用变量一般为 同一类型 ,在定义之后 不能再引用 其他变量。
引用类型变量的定义 不会分配 内存空间,与其所引用的变量 共有内存空间 (用的是被引用变量的内存空间)。
数据抽象——对象与类
概述
数据抽象 是 数据的一种描述方式,使得数据使用者只需要直到对数据所能实施的操作,以及这些操作之间的关系,而不必知道数据的具体表示
数据封装 是 把数据和对数据的操作作为一个完整的定义,利用封装手段将数据的具体表示对使用者隐藏起来,对数据的访问(使用)只能通过封装体提供的对外接口中的操作来完成
面向对象程序设计 是 把程序构成分为若干对象组成,每个对象由一些数据以及对这些数据所能实施的操作构成;对数据的操作是通过向包含数据的对象发送消息(调用对象对外接口的操作)来实现;对象的特征(数据、操作以及对外接口)由相应的类描述;一个类所描述的对象特征可以从其他的类继承
- 对象 :构成了面向对象程序的基本计算单位,由数据、操作、对外接口构成。
- 通信 :对象间的消息传递,是引起面向对象程序进行计算的唯一方式。
- 类 :描述了一组具有相同特征的对象。
- 继承 :对象的一部分特征描述可以从其他的类获得。
对象 是 由数据及能对其实施的操作所构成的封装体,它构成了面向对象计算模型的基本计算单位
对象的类 是 描述了对象的特征(数据、操作以及接口)
继承 是 在一个定义类时,利用已有的类的一些特征描述
- 单继承 :最多有一个直接基类。
- 多继承 :有二个或多个直接基类。
多态性 是 某一论域中的一个元素存在多种解释
- 一名多用 :同一作用域中用一个名字为不同的程序实体命名,从而有多种含义。
- 类属性 :一个程序实体能对多种类型数的据进行操作或描述的特性/
- 类属函数 :一个函数能对多种类型参数进行操作。
- 类属类型 :一个类型可以描述多种类型的数据。
- 对象类型的多态 :派生类对象即属于派生类,也属于基类。
- 对象标识的多态 :基类的指针或引用既可以指向或引用基类对象,也可以指向或引用派生类对象。
- 消息的多态 :发给基类的消息可以能发给派生类,但它们会给出不同的处理。
绑定(联编、定联) 是 对多态元素确定使用的过程
- 静态绑定(前期绑定) :在编译时刻确定对多态元素的使用。
- 动态绑定(后期绑定、延迟绑定) :在程序运行时刻解决多态元素的使用。
多态性带来的好处:
- 易于实现高层(上层)代码复用,使程序扩充变得容易(只要增加底层的具体实现)
- 增强语言的可扩充性,如通过操作符重载可以实现用已有的操作符对用户自定义类型(类)的数据进行操作。
面向对象程序设计 与 过程式程序设计 相比,好处可以从 抽象、封装、模块化、软件复用、软件维护、软件模型 等说起,其中在于:
-
抽象 :用来处理大而复杂的问题。
-
过程抽象 :把程序一些功能抽象为子程序,使用者只需要知道这些子程序的接口(功能和参数),而不需要关心其内部的实现。
不足在于:
- 它对数据和操作的描述是分离的,往往不利于程序的设计、理解和维护。
-
数据抽象 :针对某个数据,描述处能对其实施的所有操作以及这些操作之间的关系,数据的使用者只需要知道对数据所能实施的操作,而不必知道数据的具体表示。
好处在于:
- 从本质上讲,程序就是通过对数据的操作来解决问题,把数据及其操作有机地结合起来进行描述,这有利于程序的设计、理解与维护。
-
-
封装 :把一个程序的具体实现细节作为一个黑匣子对该实体的使用者隐藏起来的一种程序设计机制,它体现了 信息隐藏 原则(封装使抽象手段更好被实施)。
-
过程封装 :对数据操作的封装,是通过子程序来实现的。
不足之处:
- 虽然过程封装实现了数据的封装,但操作所需的数据是公开的,它作为子程序的参数传给子程序,过程封装缺乏对数据的保护。
-
数据封装 :把数据的表示及其操作作为一个整体来描述,并把数据隐藏起来,使得使用者看不到数据的表示,只能通过相应接口中的操作使用数据。
好处在于:
- 通过对象实现数据的封装加强了数据的保护。
-
-
模块化 :根据某些原则把程序分成若干个模块,模块划分一般遵循内聚性最大(模块中各程序实体之间关联性),耦合性最小原则(模块之间的依赖性)。
-
过程式模块 :主要依据子程序来划分模块,具体划分起来自由度相对较大。
不足之处:
- 模块的边界比较模糊。
-
面向对象式模块 :主要依据对象类来划分模块,一个类可以构造一个模块,如果类太小可以将其与具有继承关系的一些类构成一个模块。
好处在于:
-
模块边界比较清晰,划分的模块结构比较稳定。
-
-
-
软件复用 :使用已有的软件或软件知识来建立(开发)新的软件。
-
过程式软件复用 :主要采用子程序(C++为函数库)库来进行代码复用。
不足在于:
- 子程序库每个子程序粒度太小,往往只能实现一个很小的功能。
- 对于某个软件而设计的子程序(当初设计只考虑已有软件的需要),往往不能符合其他软件的要求,对其他软件就必须重新实现。
- 子程序所需要的数据由调用者提供,把同样的数据在多个合作完成一个较大功能的各个子程序之间传递会带来不一致以及效率问题。
-
面向对象式软件复用 :通过类库和继承机制来实现代码复用。
好处在于:
- 类库中的类往往实现一个较大的功能。
- 对某个应用领域来讲,对象类往往具有通用性,即使已有类不能完全符合要求,也可以利用类继承机制,把符合部分保留,重新定义不符合部分。
- 对象拥有局部数据,不必向对象所提供的操作传递大量的数据,减少大量数据在不同操作之间可能带来的不一致和效率问题。
-
-
软件维护 :为了延长软件寿命对在使用的软件进行维护。
-
对过程式 :
不足在于:
- 过程式程序设计基于功能分解,而系统功能是最容易变化的,如 用户需求、软件设计、程序中模块轻微变动 等可能会导致对整个系统功能的重新分解,造成程序结构大的变动,给软件维护带来大麻烦。
-
对面向对象式 :
好处在于:
- 面向对象程序设计基于对象和类,而对象和类相对来说比较稳定,类结构(包括类的内部和各个类之间的结构)不会随着系统其他部分的变动而发送很大的变化,减轻了程序维护工作。
- 数据与操作的封装也使得在一个类的数据的表示发生变化时,不会影响系统中的其他部分,使得程序维护比较容易。
-
类
类 是 一种用户自定义类型,但定义类时需要显式地定义它的操作集
(由于在定义类时需要给出数据的具体表示和操作的实现,类实际上是 抽象数据类型 的实现)
数据成员 是 类的对象所包含的数据,可以是常量和变量
成员函数 是 描述了对类中定义的数据成员所能实施的操作
可以对类成员的访问做一定的限制,如 public、private、protected 访问控制修饰符。
- public :成员访问不受限制,在程序中如何地方都可以访问一个类的 public 成员。
- private :成员只能在本类和友元中访问。
- protected :成员只能在本类、友元、派生类中访问。
对象
对象可以通过 直接方式 和 间接方式 创建。
- 全局对象 :在所有函数外定义的对象。
- 局部对象 :在函数(或复合语句)内定义的对象。
- 动态对象 :用 new 创建的对象。
this 作用为:
- 由于类定义中描述的数据成员对该类的每个对象都有一个拷贝,但成员函数对该类的所有对象则只有一个拷贝,需要通过 this 在成员函数中实现对类成员的访问。
对象初始化和消亡前处理
构造函数 是 在对象类定义或声明与类同名、无返回值类型的成员函数,在创建对象时,构造函数会自动调用(用malloc间接创建的对象不会调用)
- 默认构造函数 :不带参数(或所有参数都有默认值)的构造函数。
成员初始化表 是 在构造函数的函数头和函数体之间加入的一个对数据成员进行初始化代表
(成员初始化由数据成员 定义次序 决定,与成员初始化表的初始化次序无关)
析构函数 是 名为 “~<类名>” 、没有参数和返回值类型的成员函数,当对象消亡时,在系统收回它所占的内存空间之前,析构函数会被调用(用free收回间接创建的对象不会调用)
成员对象 是 类型为类的数据成员
拷贝构造函数 是 在创建对象时,用另一个同类对象对其初始化所调用的特殊构造函数
- 隐式拷贝构造函数 :逐个成员拷贝初始化,对于普通数据成员,采用常规成员初始化操作;对于成员对象,调用成员对象类的拷贝构造函数来实现成员对象的初始化。
(当类定义中有 自定义的拷贝构造函数 ,则默认调用成员对象的 默认构造函数 )
类作为模块
对于类作为模块的方式:
- 头文件中(*.h) :存放类的定义。
- 源文件中(*.cpp) :存放类外定义的成员函数(需要文件包含对应的头文件,以便编译程序在编译时知道类的定义)。
Demeter法则 是 一个类的成员函数除了能访问自身类结构的直接子结构(表层子结构)外,不能以任何方式依赖于任何其他类的结构;每个成员函数只应向某个有限集合中的对象发送消息
Demeter法则存在 类表达形式 和 对象表达形式 两种形式。
-
类表达形式(L1) :
对于类 C 中的任何成员函数 M ,M 中能访问或引用的对象必须属于下述类之一:
- 类 C 本身。
- 成员函数 M 的参数类。
- M 或 M 所调用的成员函数所创建的对象类。
- 全局对象所属的类。
- 类 C 的成员对象所属的类
-
对象表达形式(L2) :
对于类 C 中的任何成员函数 M ,M 中能访问或引用的对象必须属于下述对象之一:
- this 指向的对象。
- 成员函数 M 的参数对象。
- M 或 M 所调用的成员函数所创建的对象。
- 全局对象中包含的对象。
- C 的成员对象。
L1 法则适合于 静态类型 的面向对象语言,可以在编译时刻检查程序是否满足,L2 法则适合于 动态类型 面向对象语言,需要在运行时刻检查程序是否满足法则。
对象与类的进一步讨论
对象的成员函数可分为 修改对象状态的成员函数 和 获取对象状态的成员函数 。
常成员函数 是 用以获取对象的状态,无法改变对象数据成员的值的成员函数
静态成员 是 在一个类中可共享数据的成员
静态成员分为 静态数据成员 和 静态成员函数 。
(静态数据成员需要在 类外 赋值,static 的 整型变量 和 枚举型常量 成员可以在类定义中进行初始化)
友元 是 在类定义中,指定某个全局函数、某个其他类、某个其他类的某个成员函数能直接访问该类的私有和保护成员(友元不具有传递性)
友元的类型可称为 友元函数、友元类、友元类成员函数 ,统称为 友元 。
友元作用在于:
- 提供面向对象程序设计的灵活性,是数据保护和数据存取效率之间的一个折衷方案。
转移构造函数 是 要求相应实参只能是临时对象或即将消亡的对象的构造函数,用以实现资源转移
操作符重载 是 用以实现用已有操作符对自定义类型是数据操作
操作符重载是通过 函数(操作符重载函数) 实现的,可以是一个类的 非静态成员函数 或 带有类、结构、枚举以及它们引用类型参数的全局函数 实现。
操作符重载基本原则为:
- 遵循已有操作符语法 :只能重载C++语言中已有的操作符,不可臆造新的操作符。
- 遵循已有操作符语义(不是必须) :尽量遵循操作符远来的含义。
- 可重载的操作符 :除了 **.(成员选择符)、.*(间接成员选择符)、::(域解析符)、?:(条件操作符)、sizeof(数据占内存大小)**外,其他C++操作符都可以重载。
常见的操作符重载包括 单目操作符重载、双目操作符重载、赋值操作符重载、下标操作符重载、动态存储分配与去配操作符重载、函数调用操作符重载、间接类成员访问操作符重载、类型转换操作符重载 等。
继承(类的复用)——派生类
概述
继承 是 在定义一个新的类时,把某个或某些已有的类的所有特征包含进来,然后在新的类中再定义新的特征或对已包含的特征进行重定义(修改)
基类(父类) 是 被继承的类
派生类(子类) 是 继承后得到的类
继承的作用为:
- 对事物按层次进行分类 :利用类之间的继承关系,可以把事物(概念)以层次结构表示出来。
- 对概念进行组合 :用类之间的多继承关系可以表示概念的组合。
- 支持软件增量开发 :可以用两个(或多个)具有继承关系的类来表示软件增量开发的阶段性成果。
单继承
单继承 是 一个派生类只有一个基类
protected 成员访问控制作用:
- 在基类中声明为protected的成员可以被派生类使用,但不能被基类的实例用户使用。一个类就存在两个对外接口,一个接口由类的public成员构成,它提供给实例用户使用;另一个接口由类的public和protected成员构成,该接口提供给派生类使用(缓解了继承与数据封装的矛盾)。
派生类对基类成员的访问控制(继承方式)作用:
- 决定了派生类的实例用户和派生类的派生类对基类成员的访问权限。
继承方式可以是 public、private、protected 三种,默认为 private 。
- public :
- 基类 public 成员,在派生类中成为 public 成员。
- 基类 protected 成员,在派生类中成为 protected 成员。
- 基类 private 成员,在派生类中成为 不可直接使用 的成员。
- private :
- 基类的 public 成员,在派生类中成为 private 成员。
- 基类的 protected 成员,在派生类中成为 private 成员。
- 基类的 private 成员,在派生类中成为 不可直接使用 的成员。
- protected :
- 基类的 public 成员,在派生类中成为 protected 成员。
- 基类的 protected 成员,在派生类中成为 protected 成员。
- 基类的 private 成员,在派生类中成为 不可直接使用 的成员。
在C++中, public 继承方式意义:
-
以public方式继承的派生类可以看成基类的子类型。
-
对基类对象所能实施的操作也能作用于派生类对象。
-
在需要基类对象的地方可以用派生类对象替代。
-
聚集 是 部分与整体的关系(部分可独立存在)
组合 是 部分与整体的关系(部分不可独立存在)
消息(成员函数调用)的动态绑定
以public继承的派生类存在下面三种多态:
- 对象类型的多态 :派生类对象的累计既可以是派生类,也可以是基类,即一个对象可以属于多种类型。
- 对象标识的多态 :基类的指针或引用可以指向或引用基类对象,也可以指向或引用派生类对象,即一个对象标识可以属于多种类型,它可以标识多种对象(在对象标识符定义时指定的类型称为静态类型,在运行时实际标识的对象类型为动态类型)。
- 消息的多态 :一个可以发送基类对象的消息,也可以发送到派生类对象,从而可能得到不同的解释。
虚函数 是 用以当用指针或引用访问对象成员函数时通过动态绑定,访问到对应的成员函数上
虚函数隐含:
- 基类中的一个成员函数如果被定义成虚函数,则在派生类中定义的与之具有想同型构的成员函数是对基类该成员函数的重定义(覆盖)(可以通过基类的指针或引用访问派生类中对基类重定义的成员函数)。
虚函数的限制为:
- 只有类成员函数才可以是虚函数
- 静态成员函数不能是虚函数
- 构造函数不能是虚函数
- 析构函数可以(往往)是虚函数
(只有通过 指针 或 引用 访问对象类的虚函数时才进行动态绑定)
纯虚函数 是 只给出函数声明而没给出实现(包括在类定义的内部和外部)的虚成员函数
抽象类 是 包含纯虚函数的类(抽象类无法被实例化)
抽象类的作用为:
- 为派生类提供一个基本框架和一个公共的对外接口,派生类(或派生类的派生类等)应对抽象基类的所有纯虚成员函数进行实现。
*虚函数动态绑定的一种实现 :对于每一个类如果它有虚函数(包括从基类继承来的),则编译程序将会为其创建一个 虚函数表 ,表中记录了该类所有虚函数的入口。当创建一个包含虚函数的类的对象时,在所创建对象的内存空间中有一个 隐藏指针 ,指向该对象所属类的 虚函数表 。
多继承
多继承 是 一个类可以有两个或两个以上的直接基类
对于多继承:
- 继承方式及访问控制的规定同单继承
- 派生类拥有所有基类的成员
- 基类的声明次序决定:
- 对基类构造函数/析构函数的调用次序
- 对基类数据成员的存储安排
命名冲突 是 当多个基类中包含同名的成员
重复继承 是 直接基类有公共的基类(公共基类中的数据成员在多继承的派生类中有多个拷贝)
应对的解决方案:
- 命名冲突 :使用基类名受限访问。
- 重复继承 :使用虚基类(在继承方式前加上 virtual )。
对于虚基类:
- 虚基类的构造函数由最新(最后一个)派生出的的类的构造函数调用。
- 虚基类的构造函数优先非虚基类的构造函数执行。
类属类型(泛型)——模板
概述
类属性 是 一个程序实体能对多种数据类型的数据进程操作和描述的特性
具有类属性的程序实体通常有 类书函数 和 类属类。
- 类属函数 :一个函数能对不同类型的数据(参数)完成相同的操作。
- 类属类 :一个类的成员类型可变,从而可以用它描述不同种类的对象。
类属程序设计(泛型程序设计) 是 基于具有类属性的程序实体进行程序设计的技术,为软件复用提供了另一种途径
模板
模板 是 一段程序代码,带有类型参数,在程序中可以通过给这些参数提供一些类型来得到针对不同类型的具体代码
函数模板 是 带有类型参数的函数定义
模板实参推演 是 编译程序根据调用时实参的类型自动地将函数或类模板实例化为具体的函数或类
函数模板可以进行 显式实例化 或 隐式实例化 。
类模板 是 带有类型参数的类定义
类模板需要进行 显式实例化 。
C++标准模板库
标准模板库(STL) 是 由C++标准库中以函数模板和类模板形式提供的新功能所构成
标准模板库(STL)主要包含一些 容器模板、算法模板、迭代器模板 。
- 容器模板 :用于存储数据,由同类型的元素所构成的长度可变的序列,通过类模板实现。
- 算法模板 :用于对容器中的元素进行一些常用的操作,通过函数模板实现。
- 迭代器模板 :用于对容器中的元素进行访问,通过具有抽象指针功能的类模板实现。
在标准模板库(STL)中算法参数为迭代器是为了:
- 提高算法与容器之间的相互独立型。
迭代器 起到了 容器 和 算法 之间的 桥梁作用 ,使得一个算法可以作用于多种容器,从而保证算法的 通用性 。
一些常用的 容器 :
- vector<元素类型> :用于需要快速定位(访问)任意位置上的元素以及主要在元素尾部增加/删除元素的场合。(用动态数组实现)
- list<元素类型> :用于经常在元素序列任意位置上插入/删除元素的场合。(用双向链表实现)
- deque<元素类型> :用于主要在元素序列的两端增加/删除元素以及快速定位(访问)任意位置上的元素场合。(用分段的连续空间实现)
- stack<元素类型> :用于仅在元素序列的尾部增加/删除元素的场合。(一般基于deque实现,分段连续空间)
- queue<元素类型> :用于仅在元素序列的尾部增加、头部删除元素的场合。(一般基于deque实现)
- <priority_queue<元素类型> :与queue操作相似,不同在于,每次增加元素后,将对元素位置进行调整,使得头部元素总是最大。即每次删除的元素总是最大(大根堆)。(一般基于vector和heap实现)
- map<关键字类型,值类型> 或 multimap<关键字类型,值类型> :容器中每个元素由 **<关键字, 值> **构成,(属于一种pair结构类型,该结构有两个成员:first和second,关键字对于first成员,值对应second成员),元素是根据其关键字排序的,用户需要根据关键字来访问元素的场合。对于map,不同元素的关键字不能相同;对于multimap,不同元素的关键字可以相同。(用某种二叉树实现)
- set<元素类型> 或 multiset<元素类型> :在 set 和 multiset 中,每个元素只有关键字而没有值,或者说,关键字和值合一。
- basic_string<字符类型> :与vector类似,不同之处在于其元素为字符类型,并提供了一系列与字符串相关的操作。string 和 wstring 是它的两个实例,即 basic_string<char> 和 basic_string<wchar_t> 。
迭代器 实现了抽象的指针(智能指针)功能,它们指向容器中的元素,用于对容器中的元素进行访问和遍历(queue、stack、priority_queue 不支持迭代器)。
-
输出迭代器 :只能用于 修改 它所指向的容器元素。
-
输入迭代器 :只能用于 读取 它所指向的容器元素。
-
前向迭代器 :具有 输出迭代器 和 输入迭代器 的所有功能。
-
双向迭代器 :具有 前向迭代器 的所有功能,还可以对其进行 – 操作,以实现双向遍历容器元素的功能。
-
随机访问迭代器 :具有 前向迭代器 的所有功能,还可以对它进行 随机访问元素操作(如数组元素访问操作符,下标访问操作符),以及对它进行 + - += -= < > <= >= 操作。
大多数容器类都有相应的迭代器,但对于不同的容器,与它们关联的的迭代器会有所不同。
- 随机访问迭代器:vector deque basic_string 容器类。
- 双向迭代器 :**list map/multimap set/multiset ** 容器类。
谓词函数 是 由算法使用者提供,参数类型为元素类型,返回值类型为bool的,用来作为算法操作条件的函数
操作函数 是 由算法使用者提供,参数和返回值由相应算法决定的,用来参与算法操作的函数
标准模板库(STL)中的算法,可分为:
- 调序算法 :实现按某个要求改变容器中元素次序的操作。
- 编辑算法 :实现对容器的赋值、替换、删除、赋值等操作。
- 查找算法 :实现堆容器中查找元素或子元素序列等操作。
- 算术算法 :实现按堆容器内的元素进行求和、内积和、差等操作。
- 集合算法 :实现按集合的基本运算。该类算法要求容器内元素已排序。
- 堆算法 :实现基于堆结构的容器元素操作。具有堆结构的容器主要特点是第一个元素最大。
- 元素遍历算法 for_each :依次访问一个范围内的每个元素,并对每个元素调用某个指定的操作函数对其操作。
在标准模板库(STL)中,算术算法在 numeric 中定义,其他算法在 algorithm 中定义。
输入/输出
概述
C++的输入/输出基于 字节流 的操作:
- 输入操作 :把输入的数据看成逐个字节地从外流入计算机内部(内存)
- 输出操作 :把输出的数据看成逐个字节地从内部流出到外设。
输入/输出操作可分为 面向控制台I/O、面向文件I/O、面向字符串变量I/O 。
- 面向控制台I/O :从标准输入设备(如键盘)获得数据,以及把程序结果从标准输出设备(如显示器)输出。
- 面向文件I/O :从外存文件获得数据,以及把程序结果保存到外存文件中。
- 面向字符串变量I/O :从程序中的字符串变量获得数据,以及把程序结果保存到字符串变量中。
面向控制台的输入/输出
控制台I/O 是 从计算机系统的标准输入设备输入程序所需要的数据,以及把程序的计算结果或错误信息输出到计算机系统的标准输出设备或标准的错误信息输出设备
有关 输出 的函数:
- int putchar(int ch);
- *int puts(const char p);
- *int printf(const char format [, <参数表>]); (format为格式字符串,包含 普通字符 和 控制字符 )
- cout << [标准数据类型(或重载了插入操作符的自定义数据类型)];
- ostream &ostream::put(char ch);
- *ostream &ostream::write(const char p, int cout);
有关 输入 的函数:
- int getchar();
- **char gets(char p);
- *int scanf(const char format [, <参数表>]);
- cin >> [标准数据类型(或重载了抽取操作符的自定义数据类型)];
- istream &istream::get(char &ch);
- *istream &istream::getline(char p, int count, char delim=‘\n’)
- *istream &istream::read(char p, int count);
面向文件的输入/输出
在外部存储器中保存数据的方式有 文件 和 数据库 。
流式文件 是 把文件看成由一系列字节所构成的字节串
对文件的操作分为 打开文件 和 关闭文件 。
- 打开文件 :把程序内部一个标识文件的变量或对象与外部的一个具体文件进行关联起来,并创建内存缓冲区。
- 关闭文件 :把暂存在内存缓冲区中的内容写入文件中,并归还打开文件时申请的内存资源。
在文件中,数据存储方式有 文本方式 和 二进制方式 。
-
文本方式 :用于存储具有 “行” 结构的文字数据。
-
二进制方式 :用于存储无显式结构的数据,数据的格式由应用程序来解释。
文件方式 和 二进制方式 的区别在于:
- 文本文件中只包含可显示的字符和有限的几个控制字符;二进制文件中可以包含任意的二进制字节。
(以二进制方式组织的文件不利于文件在不同计算机平台山使用)
有关 文件输出 的函数:
- **FILE *fopen(const char filename, const char mode);
- *int fputc(int c, FILE stream);
- **int fputs(const char string, FILE stream);
- **int fprintf(FILE stream, const char format [, argument] …);
- **size_t fwrite(const void buffer, size_t size, size_t count, FILE stream);
- *int fclose(FILE stream);
- ofstream out_file(<文件名>,<打开方式>);
- ofstream out_file;out_file.open(<文件名>,<打开方式>);
- out_file.write(<(char)需输出地址>,<输出长度>);*
有关 文件输入 的函数:
- **FILE *fopen(const char filename, const char mode);
- *int fgetc(FILE stream);
- **char *fgets(char string, int n, FILE stream);
- **int fscanf(FILE stream, const char format [, argument] …);
- **size_t fread(const void buffer, size_t size, size_t count, FILE stream);
- *int feof(FILE stream);
- *int fclose(FILE stream);
- ifstream in_file(<文件名>,<打开方式>);
- ifstream in_file;in_file.open(<文件名>,<打开方式>);
- in_file.read(<(char)需输入地址>,<输入长度>);*
有关 文件随机读写 的函数:
- *int fseek(FILE stream, long offset, int origin);
- *long ftell(FILE stream);
- istream &istream::seekg(<位置>);
- istream &istream::seekg(<偏移量>, <参照位置>);
- streampos istream::tellg();
- ostream &ostream::seekp(<位置>);
- ostream &ostream::seekp(<偏移量>, <参照位置>);
- streampos &ostream::tellp();
文件随机读写 是 将文件位置指针指向需要进行读写的位置,再进行读写操作
关于 文件读/写 部分相关选项解释:
-
r :打开一个外部文件用于 读 操作,外部文件必须存在。
-
r+ :打开一个外部文件用于 读写 操作。外部文件必须存在。
-
w :打开一个外部文件用于 写 操作,如果外部文件已存在,则受限把它的内容清除;否则先创建该外部文件。
-
w+ :打开一个外部文件用于 读写 操作。如果文件不存在,则首先创建一个空文件,否则首先清空已有文件的内容。
-
a :打开一个外部文件用于 添加(从文件末尾)操作。如果外部文件不存在,则先创建该外部文件。
-
a+ :打开一个外部文件用于 读写添加 操作。如果文件不存在,则首先创建一个空文件。以这种方式打开的文件,输出操作总是在文件尾进行。
-
b :以二进制方式打开文件。
-
t :以文本方式打开文件。
-
ios::in :打开一个外部文件用于 读 操作,外部文件必须存在。
-
ios::out :打开一个外部文件用于 写 操作,如果外部文件已存在,则受限把它的内容清除;否则先创建该外部文件。
-
ios::app :a打开一个外部文件用于 添加(从文件末尾)操作。如果外部文件不存在,则先创建该外部文件。
-
ios::in|ios::out :打开一个外部文件用于 读写 操作。外部文件必须存在。
-
ios::binary :以二进制方式打开文件。
关于 文件随机读/写 部分相关选项解释:
- SEEK_SET :以文件头作为参考位置。
- SEEK_CUR :以当前位置作为参考位置。
- SEEK_END :以文件末尾作为参考位置。
- ios::beg :以文件头作为参考位置。
- ios::cur :以当前位置作为参考位置。
- ios::end :以文件末尾作为参考位置。
面向字符串变量的输入/输出
基于字符串变量的输入/输出功能的库函数主要是 ssanf 和 sprintf 。
- **int sprintf(char buffer, const char format [,argument] …);
- **int sscanf(const char buffer, const char format [,argument] …);
对于基于I/O类库的字符串变量输入/输出,可以用 ostrstream、istrstream、strstream 。
关于 输出 的I/O类库:
- ostream str_buf;
- char buf[100];ostrstream str_buf(buf,100);
- str_buf << [标准数据类型(或重载了插入操作符的自定义数据类型)];
- *char p=str_buf.str();
关于 输入 的I/O类库:
- char buf[100]={xxx};istrstream str_buf(buf);
- char buf[100];istrstream str_buf(buf,100);
- str_buf >> [标准数据类型(或重载了抽取操作符的自定义数据类型)];
其中, strstream 可以做到 ostrstream 和 istrstream 能做的事(输入/输出)。
(在新的C++标准中,分别被 ostringstream、istringstream、stringstream 代替)
异常处理
概述
程序错误包括 语法错误、逻辑错误、运行异常 。
- 语法错误 :程序书写不符合语言的语法规则,可以由编译程序在编译时刻发现并指出。
- 逻辑错误 :程序设计不当造成程序没有完成预期的功能,可以通过对程序进行静态分析和动态测试发现。
- 运行异常 :程序设计时对程序运行环境考虑不周而造成的程序运行错误。
鲁棒性(健硕性) 是 程序在各种极端情况下能够正确运行的程度
异常处理 是 在程序中,对各种可预见的异常情况进行处理
异常处理的方式通常有 就地处理 和 异地处理 。
- 就地处理 :在发现异常的地方直接处理异常。
- 异地处理 :在发现遗产规定地方不处理异常,而是把发现的异常交给程序其他地方来处理。
C++异常处理机制
C++异常处理机制 是 把有可能遭遇异常的一系列操作(语句或函数调用)构成一个try语句;如果try语句中的某个操作在执行中发现了异常,则通过执行一个throw语句抛掷(产生)一个异常对象;抛掷的异常对象由能够处理逐个异常的地方通过catch语句来捕获并处理
关于 try、throw、catch 需要注意:
- 在 try 语句块的语句序列执行中如果没有抛掷(throw)异常对象,则其后的 catch 语句不执行,而是继续执行 try 语句块之后的非 catch 语句。
- 在 try 语句块的语句序列执行中如果抛掷(throw)了异常对象,则:
- 如果该 try 语句块之后有能够捕获该异常对象的 catch 语句,则执行这个 catch 语句中的语句序列,然后继续执行这个 catch 语句之后的非 catch 语句。
- 如果该 try 语句块之后没有能捕获异常对象的 catch 语句,则由函数调用链的上一层函数中的 try 语句块的 catch 来捕获。
- 如果抛掷异常对象的 throw 语句不是由程序中的某个 try 语句块中的语句序列调用的,则抛掷的异常不会被程序中的 catch 捕获,它将由系统进行标准异常处理。
有关 exit 和 abort 函数区别:
- exit :在终止程序运行前,会进行关闭 被程序打开的文件、调用全局变量、 static 存储类局部对象的析构函数(不要再这些对象的析构函数中调用 exit )。
- abort :立即终止程序执行, 不做任何善后 处理工作。
terminate 是 用来进行标准的异常处理的函数(如果到了函数调用链最顶端(函数main)也没有能捕获异常对象的 catch 语句,terminate函数默认会调用abort函数)
基于断言的程序调试
断言 是 一个逻辑表达式,描述了程序执行到断言处应满足的条件,如果条件满足则程序继续执行,否则程序执行异常终止
宏assert 是 由C++标准库提供用来实现断言机制的
- 当assert为false :会显示相应的 表达式、其所在的源文件名、行号 等诊断信息,然后调用 abort 终止程序运行。
- 当assert为true :程序继续执行。
其中,宏assert 是通过 条件编译预处理命令 来实现的,只有在 宏名NDEBUG 没有定义才有效。
附录
C++数据类型
有关 C++数据类型 为:
- 基本数据类型
- 整数类型
- 实数类型
- 字符类型
- 逻辑类型
- 空值类型
- 构造数据类型
- 枚举类型
- 数组类型
- 结构类型
- 联合类型
- 指针类型
- 引用类型
- 抽象数据类型
- 类
- 派生类
C++语句
有关 C++语句 为:
- 顺序执行语句
- 表达式语句
- 复合语句
- 空语句
- 选择执行语句
- if语句
- switch语句
- 循环执行语句
- while语句
- do-while语句
- for语句
- 无条件跳转语句
- goto语句
- break语句
- continue语句
- return语句
- 数据定义语句(由于在数据(变量、常量等)定义可以进行初始化,数据定义也包含操作,可以把数据定义也作为语句看待)
C++标识符作用域
有关 C++标识符作用域 为:
-
全局作用域 :构成C++程序的所有模块(源文件)。
-
文件作用域 :构成C++程序的某个模块(源文件)。
-
局部作用域 :在函数定义或复合语句中,从标识符的定义点开始到函数定义或复合语句结束之间的程序段。
-
函数作用域 :由某个整个函数定义所构成的程序段(语句标号是唯一具有函数作用域的标识符)。
-
函数原型作用域 :用于函数声明的函数原型。
-
命名空间作用域 :给一组全局程序实体的定义取一个名字使之构成一个作用域。
-
类作用域 :类定义构成的一个作用域。
C++部分操作符(运算符)名称
-
throw(抛出异常操作符)
-
sizeof()(类型长度操作符)
-
new(内存分配操作符)
-
delete(内存去配操作符)
-
castname_cast(类型转换操作符)
-
<<(位左移操作符/插入操作符)
-
>>(位右移操作符/抽取操作符)
-
&(取地址操作符)
-
*(间接访问操作符/地址访问操作符)
-
(Type)(强制类型转换操作符)
-
::(域解析操作符)
-
.(成员访问操作符)
-
->(指向成员操作符)
-
[](下标操作符/数组元素访问操作符)
-
()(括号/函数调用操作符)
-
.*(间接成员访问操作符/成员指针操作符)
-
?:(条件操作符)
-
=(赋值操作符)
-
++(自增操作符)
-
–(自减操作符)
-
*+= -= = /= %= <<= >>= &= |= ^=(复合赋值操作符)
-
+(加号/正号)
-
-(减号/负号)
-
*(乘号)
-
/(除号)
-
<(小于号)
-
<=(小于等于号)
-
==(等于号/判等操作符)
-
!=(不等于号)
-
>(大于号)
-
>=(大于等于号)
-
&(按位与)
-
^(按位异或)
-
|(按位或)
-
&&(逻辑且)
-
||(逻辑或)
后话
实话说,对于吾等这种初学者来说,书中的内容讲的真的很好,就属于那种很丰富,但又容易看懂。反正整个阅读的过程中学习体验感很不错。另外,虽然不知道是否合适,不过还是得说一下,在50页的图3-2中循环执行语句写的是while-do(感觉应该是do-while?),308页的随机访问迭代器介绍那一段,多了一个 **、**字符。
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。也欢迎您共享此博客,以便更多人可以参与。如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。谢谢 !