导图社区 C加加5013
C加加知识点梳理,包括C加加简单程序设计,数据的共享与保护,类与对象,函数,数组指针与字符串继承与派,多态性。
编辑于2023-03-14 20:05:09 广东C++
一、绪论
二、C++简单程序设计
1.C++语言概述
1.C++的产生
1.C语言的优点
1.语言简洁灵活
2.运算符和数据结构丰富
3.具有结构化控制语句
4.程序执行效率高
5.具有高级语言与汇编语言的优点,可以直接访问物理地址
2.C++: 在C语言基础上支持面向对象的程序设计,一个通用目的的程序设计语言
2.C++的特点
1.尽量兼容C
1.保持了C的简洁,高效,接近汇编语言的特点
2.促进了C++ 的普及和面向对象技术的广泛应用
3.不是纯正的面向对象语言: 既支持面向过程,有支持面向对象
2.支持面向对象的方法
我们应按照面向对象的思维方式去编写程序
3.C++程序实例
using namespace std; 使用此行不用连名带姓的写
cout:输出流对象 <<:插入运算符 endl:换行
int main() 语法规定main函数返回int型
4.字符集
5.语法记号
1.关键字
C++预先声明的单词
2.标识符
1.定义: 程序员定义的单词,命名一些实体:函数名,变量名,类名等
2.构成规则
1.以大小写字母或下划线_开始
2.可以由大小写字母,下划线或数字0~9组成
3.大小写字母代表不同的标识符
4.不能是C++关键字
3.文字
直接使用符号表示的数据
4.操作符(运算符)
实现各种运算的符号
5.分隔符
分隔语法记号或程序正文:() {} , : ;
6.空格
1.空格,制表符(Tab),垂直制表符,换行符,回车符,注释的总称
2.注释
/* 和*/ 之间的所有字符都被作为注释处理
//开始直到它所在的行尾都被视为注释
2.基本数据类型和表达式
1.基本数据类型
bool/char/short/int/long/float/double
2.常量
1.整型常量
1.八进制以数字0开头
2.十六进制以0x开头
3.后缀L表示长整型,U表示无符号型,也可以同时使用(大小写无关)
2.实型常量
1.一般形式:12.5
2.指数形式:0.345E+2 E可以大写或者小写
3.默认为double型,后缀F可以使其成为float型
4.注:不能用=比较实数之间或实数与常熟之间的相等,可以将两个实数相减,看差值是否足够小
3.字符常量
单引号括起来的一个字符
转义序列:\a 响铃,\n 换行,\t 水平制表符,\v 垂直制表符,\\,\",\'
4.字符串常量
双引号括起来的字符序列
5.布尔常量
两个:false和true
3.变量
1.变量的声明和定义
2.变量的存储类型
auto:堆栈方式分配空间,暂时性存储,空间可多次覆盖使用
register:存放在通用寄存器中
extern:在所有函数和程序段中都可引用
static:以固定地址存放,整个程序运行期间都有效
4.符号常量
1.在使用之前一定先声明: const 数据类型说明符 常量名=常量值;
2.在声明时一定要赋初值,在程序中间不能改变其值
3.优点:提高程序可读性,修改简单,避免因修改常量值带来的不一致性
5.运算符与表达式(不要写长表达式,不要依赖优先级)
1.算术运算符与算术表达式
%:取余运算,只用于整型
/:用于两个整型相除时,取商的整数部分,小数部分自动舍弃:1/2=0
++ --:自增 自减:都有前置和后置两种形式
2.赋值运算符与赋值表达式
复合赋值运算符(编译效率会提高)
算术运算: += -= *= /= %=
位运算: <<= >>= &= ^= |=
3.逗号运算和逗号表达式
形式:表达式1,表达式2 先求1,再求2,最终结果为表达式2的值
4.逻辑运算与逻辑表达式
&&和||具有短路特性
5.条件运算符与条件表达式
1.唯一一个三元运算符 ? 实现简单的选择功能
2.形式:表达式1 ? 表达式2 : 表达式3 (表达式1必须是bool类型)
6.sizeof运算符
1.作用:计算某种类型的对象在内存中所占的字节数
2.形式:sizeof(类型名) 或 sizeof 表达式
7.位运算
1.按位与 & :可以将操作数的若干位置0,或取若干指定位
2.按位或 | :可以将操作数的若干位置1
3.按位异或 ^ :若干指定位翻转,与0异或为原值,与1异或值相反
4.按位取反 ~ :单目运算符,对一个二进制数的每一位取反
5.移位(二元运算符)
左移:<< :低位补0,高位舍弃
右移:>> :无符号数高位补0,有符号数高位补符号位或补0
8.运算符优先级与结合性
9.混合运算时数据类型的转换
1.隐含转换
低类型数据自动转换为高类型数据,安全,没有精度损失
1.逻辑运算:必须是bool型,非0为true,0为false
2.位运算:必须是整数
3.赋值运算:左值与右值类型相同
2.显式转换
1.两种转换语法形式
类型说明符(表达式) C++风格
(类型说明符)表达式 C语言风格
2.4种类型转换操作符
const_cast<类型说明符>(表达式)
static_cast<类型说明符>(表达式)
reinterpret_cast<类型说明符>(表达式)
dynamic_cast<类型说明符>(表达式)
3.可能是不安全的;是暂时的,一次性的
6.语句
3.数据的输入与输出
1.I/O流
定义: 将数据从一个对象到另一个对象的流动抽象为"流".从流中获取数据成为提取操作,添加数据称为插入操作
cin: 处理标准输入,即键盘输入
cout: 处理标准输出,即屏幕输出
2.预定义的插入符和提取符
<< :插入符 cout<<表达式1<<表达式2...
>> :提取符 cin>>表达式1>>表达式2... 以空格分隔
3.简单的I/O格式控制(第11章)
常用的I/O流类库操作符,开头包含iomainp头文件
dec:数值数据用十进制表示
hex:十六进制
oct:八进制
ws:提取空白符
endl:换行符,并刷新流
ends:插入空字符
setsprecision(int):设置浮点数的小数位数,包括小数点
setw(int):设置域宽
4.算法的基本控制结构
1.用if实现选择结构
2.多重选择结构
1.嵌套的if语句
2.if...else if语句
3.switch语句
1.每个case分支可以有多条语句,但不必用{}
2.case语句只是一个入口标号,不能确定终止点,最后加break语句,否则一直执行到结束点
3.若干分支执行相同操作时,可以使多个case分支共用一组语句
4.最后加上default语句
3.循环语句
while语句
do...while语句
for语句
4.循环结构与选择结构的嵌套
5.其他控制语句
1.break语句
从循环体和switch语句内跳出,继续执行逻辑上的下一条语句,不宜用在别处
2.continue语句:
结束本次循环,判断是否继续执行下一次循环
3.goto语句
会破坏程序的结构,少用或不用
5.自定义数据类型
1.typedef声明
1.作用: 将一个标识符声明成某个数据类型的别名,将这个标识符当做数据类型使用,使程序简洁,提高可读性
2.语法形式: typedef 已有类型名 新类型名表;(可以有多个标识符,逗号隔开)
2.枚举类型enum
1.声明形式: enum 枚举类型名 {变量值列表};
2.对枚举元素不能赋值,具有默认值:0,1,2...,也可以在声明时另行定义枚举元素的值
3.可以进行关系运算
4.枚举类型可以直接赋给整值型,整数值不能直接赋给枚举类型,需要强制类型转换
三、函数
1.函数的定义与使用
1.函数的定义
2.函数的调用
1.函数的调用形式
2.嵌套调用
3.递归调用:直接或间接地调用自身
递推: 将原问题不断分解为新的子问题,从未知向已知推进,最终到达已知条件:递归的结束条件
回归: 从已知条件按照递推的逆过程,逐一求值回归
3.函数的参数传值
1.值传递
1.参数值的单向传递过程,形参获得了值便与实参脱离关系,无论形参怎么改变,都不会影响实参
2.不能达到交换数据的目的
2.引用传递&
1.定义: 一种特殊类型的变量,是另一个变量的别名,引用名和变量名的效果一样
2.声明: 数据类型 &引用名=变量名;
3.注意: 声明引用时,必须同时初始化,指向一个已存在的对象,一旦初始化后,不能改为指向其他对象
4.引用传递: 引用作为形参:成为实参的一个别名,对形参的操作会直接作用于实参
2.内联函数inline
1.说明: 函数调用会降低执行效率,增加时间和空间的开销.对于功能简单,规模小又使用频繁的函数,可设计为内联函数
2.定义: 内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处
3.优点: 节省了参数传递,控制转移等开销
4.语法形式: inline 类型说明符 函数名(含类型说明的形参表)
5.注意: 没有用inline修饰的函数也可能被编译成内联函数,对自身直接递归调用的函数肯定无法以内联处理
3.带默认形参值的函数
1.有默认值的形参必须在形参列表的最后: 函数调用中,实参与形参按从左到右的顺序建立对应关系
2.相同作用域内,不允许在同一个函数的多个声明中对同一个参数的默认值重复定义,值相同也不行
3.函数在定义之前有原型声明,默认形参值在原型声明中给出,定义中为了清晰可用注释形式/* */
4.函数重载
1.定义: 两个以上的函数,具有相同函数名,但形参个数或类型不同
2.注意: 编译器不以形参名和返回值来区分函数,不要将功能不同的函数定义为函数重载
3.当使用默认形参值的函数重载形式时,需要防止二义性
void fun(int length, int width=2, int heigh=33);void fun(int length);当调用fun(1);时,编译器无法确定应该执行哪个重载函数,会指出语法错误.
5.C++函数系统
1.用include指令嵌入相应的头文件,就可以使用系统函数:大大减少工作量,提高运行效率和可靠性
2.两类
标准C++的函数
各种编译环境普遍支持,有很好的可移植性
非标准C++的函数
当前操作系统或编译环境中所特有的系统函数:操作系统相关事务
3.网站:www.cppreference.com : 可以查阅各种常用的标准C++函数的原型,头文件和用法
四、类与对象
1.面向对象程序设计的基本特点
1.抽象
1.定义: 对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程
2.两个方面
数据抽象: 描述对象的属性或状态,区别于其他对象的特征
行为抽象: 描述对象的共同行为或功能特征
2.封装
1.定义: 将抽象得到的数据和行为相结合,形成一个有机的整体:类, 数据和函数都是类的成员
2.优点: 使一部分成员充当类与外部的接口,其他成员隐藏起来,增强数据的安全性,简化编写工作
3.继承
类的继承机制: 在保持原有类特性的基础上,进行更具体,更详细的说明
4.多态
1.定义: 一段程序能够处理多种类型对象的能力
2.4种实现形式
1.强制多态
将一种类型的数据转换成另一种类型的数据
2.重载多态
给同一个名字赋予不同的含义
函数重载 Z3
运算符重载 Z8
特殊多态性:表面多态性
3.包含多态
用虚函数实现 Z8
4.类型参数化多态
用模板实现 Z9
函数模板
类模板
一般多态性:真正多态性
2.类和对象
1.类的定义
1.定义: 对逻辑上相关的函数与数据封装,对问题的抽象描述,集成程度更高
2.语法形式
class 类名称{public: 外部接口protect: 保护型成员private: 私有成员}
2.类成员的访问控制
1.公有类型:public
定义类的外部接口
2.私有类型:private
只能被本类的成员函数访问,外部的任何访问都是非法的
3.保护类型:protect
和私有类型相似,在继承过程中对产生的新类影响不同
3.对象
1.定义: 类的某一特定实体:实例
2.声明: 类名 对象名;
3.注意: 对象的内存空间只存放数据成员,函数成员不在对象中存储副本,每个函数的代码在内存中只占据一份空间
4.访问数据成员: 对象名.数据成员名
5.调用函数成员: 对象名.函数成员名(参数表)
4.类的成员函数
1.成员函数的实现
1.原型声明: 写在类体中,说明了参数表和返回值类型
2.具体实现: 在类定义之外,实现成员函数要指明类的名称
3.具体形式: 返回值类型 类名::函数成员名(参数表)
2.成员函数调用中的目的对象
1.定义: 调用成员函数,需要用.指出调用所针对的对象
2.成员函数中: 可以不使用. 直接引用目的对象的数据成员
3.成员函数中引用其他对象的属性和调用其他对象的方法时: 需要使用.
3.带默认形参值的成员函数
默认形参值位置: 一定写在类定义中,不能在类定义之外的函数
4.内联成员函数
两种声明方式
隐式声明
将函数体直接放在类体中
显式声明
用关键字inline,保证类定义的简洁,在函数体实现时,返回值类型前加上inline
5.程序实例
时钟类的完整程序 P106
3.构造函数和析构函数
1.构造函数
1.作用: 在对象被创建时利用特定的值构造对象
2.特殊性质: 函数名与类名相同,没有返回值,通常为公有函数
3.默认构造函数: 无须提供参数的构造函数,若类中无构造函数,系统自动生成一个隐含的参数列表和函数体都为空的默认构造函数
2.复制构造函数
1.特征: 形参是本类的对象的引用
2.作用: 使用一个已经存在的对象去初始化同类的一个新对象
3.隐含的复制构造函数: 把初始值对象的每个数据成员的值都复制到新建立的对象中
4.形式: 类名(类名 &对象名)
5.3种调用情况
1.用类的一个对象去初始化该类的另一个对象 Point b(a) 或 b=a
2.函数的形参是类的对象,调用函数,形参和实参结合 f(a)
1.只有把对象用值传递时才会调用复制构造函数,传递引用不会调用
2.传递大的对象时,传递引用比传值效率高很多
3.函数的返回值是类的对象,返回调用者时
会生成一个无名临时对象,生存期只在表达式中
3.析构函数
1.作用: 完成对象被删除前的一些清理工作,在对象的生存期即将结束时自动调用
2.形式: 类名前面加 ~ 构成,没有返回值,不接收任何参数,可以是虚函数 Z8
4.程序实例
游泳池改造预算 Circle类 P114
4.类的组合
1.组合
1.定义: 一个类内嵌其他类的对象作为成员的情况,包含与被包含的关系
2.创建类的对象
1.这个类有内嵌对象成员,则内嵌对象首先被自动创建
2.既要对本类的基本类型数据成员进行初始化,又要对内嵌对象成员进行初始化
3.构造函数定义一般形式
1.类名::类名(所有对象的形参表):内嵌对象1(形参表),内嵌对象2(形参表){类的初始化}
2.初始化列表: 内嵌对象1(形参表),内嵌对象2(形参表)
作用: 对内嵌对象进行初始化
3.举例: Line::Line(Point xp1,Point xp2):p1(xp1),p2(xp2){ }
3.构造函数的调用顺序
1.先调用内嵌对象的构造函数,顺序为内嵌对象在组合类的定义中出现的次序,与初始化列表中的顺序无关
2.再执行本类构造函数的函数体
4.注意: 有些数据成员的初始化必须在初始化列表中进行
1.没有默认构造函数的内嵌对象
初始化时必须提供参数
2.引用类型的数据成员
必须在初始化时绑定引用的对象
必须编写显示的构造函数
5.析构函数的调用执行顺序与构造函数正好相反
6.复制构造函数
1.若没有: 系统会自动生成隐含的复制构造函数,自动调用内嵌对象的复制构造函数,为各个内嵌对象初始化
2.自己编写: 需要为内嵌成员对象的复制构造函数传递参数
例子: C::C(C &c1):b(c1.b){ }
2.前向引用声明
1.定义: 引用未定义的类之前,将该类的名字告诉编译器,使它知道那是一个类名,完整定义可以在其他地方
2.缺点: 尽管使用了前向引用声明,但在提供一个完整的类定义之前,不能定义它的对象,也不能在内联成员函数中使用它的对象
3.解决
更改两个类的定义次序
将函数改为非内联形式,在类的完整定义之后,再给出函数的定义
4.注意: 只能使用被声明的符号,不能涉及类的任何细节,可以声明类的对象引用或指针
5.UML图形标识
1.UML简介
定义: UML(统一建模语言)是面向对象建模语言,而不是一种编程语言,用符号描述概念,概念间的关系描述为连接符号的线
2.UML类图
6.结构体和联合体
1.结构体 struct
1.定义: 特殊形态的类,有自己的数据成员和函数成员,构造函数和析构函数,控制访问权限,可以继承,支持包含多态
2.和类的唯一区别: 具有不同的默认访问控制属性
类中: 私有类型 private
结构体中: 公有类型 public
3.引入原因: 为了保持和C程序的兼容性
4.使用情形
结构体: 定义一些数据类型,只是为了将不同类型的数据组合成一个整体,方便保存数据
若用类定义,为了遵循"将数据成员设置为私有"的习惯,要编写专门的函数成员读取和改写属性,反而麻烦
5.赋初值
条件: 全部数据成员都是公有成员,没有用户定义的构造函数,没有基类和虚函数
形式: 类型名 变量名={成员数据1初值,成员数据2初值,...}
比较: 类的数据成员习惯设置为私有,不满足,这是结构体另一个方便之处
2.联合体 union
1.定义: 特殊形态的类,默认公有类型,全部数据成员共享同一组内存单元
2.特点
1.成员共用相同的内存单元,同时至多只有一个是有意义的
2.各个对象成员不能有自定义的构造函数,析构函数,重载的复制赋值运算符
3.联合体不能继承,也不支持包含多态
4.一般用来存储一些公有的数据,而不为它定义函数成员
3.无名联合体: 没有标记名,由成员项的名字直接访问,通常用作类或结构体的内嵌成员
五、数据的共享 与保护
1.标识符的作用域 与可见性
1.作用域
1.函数原型作用域
定义: 在函数原型声明时形式参数的作用范围
说明: 最小的作用域,作用范围就在形参列表的左右括号之间
2.局部作用域
1.函数形参列表中形参的作用域: 声明处开始,到整个函数体结束
2.函数内声明的变量(局部变量): 声明处开始,到所在的块结束的大括号为止
3.类作用域
对成员m的访问方式有三种
1.成员函数中没有声明同名的局部作用域标识符,可直接访问m
2.最基本访问方式: x.m或者X::m(访问类的静态成员)
3.ptr->m :ptr为指向X类的一个对象的指针
4.命名空间作用域
1.命名空间的语法形式
namespace 命名空间名{ 命名空间内各种声明(函数声明,类声明)}
2.引用: 空间内部直接引用标识符,引用其他命名空间: 命名空间名::标识符名
3.using 语句: 解决过于冗长
using 命名空间名::标识符
将指定的标识符暴露在当前的作用域内,可以直接引用
using namespace 命名空间名
将指定命名空间内所有标识符暴露在当前的作用域内,可以直接引用
4.两类特殊的命名空间
全局命名空间
默认的,显示声明的命名空间之外的标识符都在一个全局命名空间中
匿名命名空间
显式声明的没有名字的命名空间,用来屏蔽不希望暴露给其他源文件的标识符
5.全局变量: 具有命名空间作用域的变量
2.可见性
1.定义: 从标识符引用角度,标识符的有效范围
程序运行到某一点,能够引用到的标识符,就是该处可见的标识符
2.范围: 命名空间作用域>类作用域>局部作用域
3.一般规则
1.标识符声明在前,引用在后
2.同一作用域中,不能声明同名的标识符
3.没有相互包含的不同作用域中声明的同名标识符,互不影响
4.具有包含关系的作用域中的同名标识符,外层标识符在内层不可见
4.提示: 作用域和可见性不只适用于变量名,也适用于其他各种标识符:常量名,用户定义的类型名,函数名,枚举类型的取值
2.对象的生存期
1.静态生存期
1.定义: 对象的生存期与程序的运行期相同
2.形式
1.命名空间作用域中的对象具有静态生存期
2.局部作用域中要使用关键字 static
特点
1.不会随着每次函数调用而产生一个副本,下次调用,变量还会保持上一回的值
2.不会随着函数返回而失效
3.在定义时也可以为它赋初值,只会初始化一次
3.细节: 静态生存期变量定义时未指定初值,会被赋予0初始化;动态生存期,不指定初值意味着初值不确定
2.动态生存期
1.定义: 除了上述两种形式,也称为局部生存期:诞生于声明点,结束于所在块执行完毕
2.提示: 类的成员对象也有各自的生存期,与所属对象的生存期保持一致
3.类的静态成员 static
1.静态数据成员
1.定义: 解决同一个类的不同对象之间数据和函数共享问题
2.实例属性
一个类的所有对象具有相同的属性,非静态数据成员表示
3.类属性
定义: 描述类的所有对象共同特征的一个数据项,对任何对象实例,它的属性值是相同的
形式: 用 static 声明的静态成员,在每个类只有一个副本,所有对象共同维护和使用
4.用法
可以通过类名直接访问: 类名::标识符
类的定义中仅仅进行引用性声明,必须在类外 使用类名进行定义性说明,也可初始化
举例: int Point::count=0;(类外)
2.静态函数成员
1.定义: static声明的函数成员,属于整个类,由所有对象共同拥有
2.用法: 静态成员函数可以通过类名或对象名来调用(一般习惯通过类名调用),而非静态成员只能通过对象名来调用
3.注意: 静态成员函数可以直接访问静态数据和函数成员,但访问非静态成员,必须通过对象名
4.提示: 之所以在静态成员函数中访问类的非静态成员需要指明对象,因为对静态成员函数的调用是没有目的对象的,因此不能像非静态成员函数那样,隐含地通过目的对象访问类的非静态成员
4.类的友元 friend
1.友元函数
1.友元关系: 提供了不同类或对象的成员函数之间,类的成员函数与一般函数之间进行数据共享的机制
2.定义: 在类中用friend修饰的非成员函数,不是本类的成员函数,但在它的函数体中可以通过对象名访问类的私有和保护成员
3.注意
1.类中只声明友元函数的原型,定义在类外
2.可以是普通函数,也可以是一个类的成员函数
2.友元类
1.定义: A是B的友元类,A的所有成员函数都是B的友元函数,都可以访问B的私有和保护成员,反之不行
2.注意
1.友元关系是不能传递的
2.友元关系是单向的
3.友元关系是不被继承的
5.共享数据的保护
1.常对象
1.定义: 它的数据成员值在对象的整个生存期内不能被改变,必须进行初始化,并且不能更新
2.语法形式: const 类型说明符 对象名;
3.细节: 把const放在类型名后面也是可以的,习惯写在前面
4.改变对象数据成员值两个途径
1.通过对象名访问其成员对象
语法限制不能赋值
2.在类的成员函数中改变数据成员的值
无法预料,只能禁止,为此设立常函数成员
2.用const修饰的类成员
1.常成员函数
1.格式: 类型说明符 函数名(参数表)const;
2.注意
1.const是函数类型的组成部分,在函数的定义部分也要带const
2.常对象只能调用它的常成员函数,不能调用其他成员函数
C++从语法机制上对常对象的保护,也是常对象唯一的对外接口
3.常成员函数不能更改目标对象的数据成员,也不能针对目标对象调用没有用const修饰的成员函数
4.const可以用于对重载函数的区分
3.习惯: 在适当的地方使用const能提高程序质量,对于无须改变对象状态的成员函数,都应该使用const
2.常数据成员
1.说明: 任何函数都不能对该成员赋值.构造函数对其进行初始化,只能通过初始化列表
2.举例: 定义: const int a; 初始化: A::A(int i):a(i){}
3.细节: 静态变量和常量都应该在类定义之外加以定义,但有个例外: 类的静态常量如果具有整数类型或枚举类型,可以直接在类定义中指定常量值,但是对其取地址的情况,必须通过专门的定义为其分配空间
例子: static const int b=10;
3.常引用
1.说明: 常引用所引用的对象不能被更新,作为形参,不会意外地发生更改
2.形式: const 类型说明符 &引用名;
3.注意: 非const的引用只能绑定普通对象,常引用可以绑定常对象.一个常引用无论绑定什么对象,都只能当做常对象
4.习惯: 在函数中无须改变其值的参数,不宜使用普通引用,会使常对象无法传入,用传递常引用为宜.复制构造函数的参数也宜采用常引用传递
6.多文件结构和编译预处理命令
1.C++的一般组织结构
1.编译单元: 每个源程序文件
2.一个项目至少分为3个文件
类定义文件.h
写在头文件中
类实现文件.cpp
类的使用文件(主函数文件.cpp)
这两个文件中包含类定义文件: include ".h"
3.决定一个声明放在源文件还是头文件的一般原则
1.需要分配空间的定义放在源文件中
函数的定义,变量的定义
2.不需要分配空间的声明放在头文件中
类声明,外部函数原型声明,外部变量声明,常量的声明
3.内联函数特殊
它的内容要嵌入到每个调用它的函数中,代码应该被各个编译单元可见,放在头文件中
4.习惯: 如果误将分配了空间的定义写在头文件中,会导致空间在不同的编译单元中被分配多次,引发错误
2.外部变量与外部函数
1.外部变量
1.定义: 可以为多个源文件共享的全局变量
2.形式: 命名空间作用域中定义的变量,默认是外部变量,但其他文件使用要用extern声明
3.外部变量的声明
定义性声明
唯一的,声明的同时定义(分配内存,初始化),不用extern都是,用extern同时指定了初值
引用性声明
可以多处,引用在别处定义的变量,用extern且没有指定初值
2.外部函数
1.定义: 所有类之外声明的函数,非成员函数,具有命名空间作用域,可以在不同的编译单元中被调用,调用之前进行引用性声明即可
2.习惯: 变量和函数的定义放在源文件中,对外部变量和外部函数的引用性声明放在头文件中
3.将变量和函数限制在编译单元内
1.两个原因
出于安全性考虑
对于大工程,变量名很容易重名,连接时很容易发生名字冲突
2.解决办法
曾经: 用static修饰,和extern起相反作用,无法被其他编译单元引用
现在: 使用匿名的命名空间,变量和函数不会暴露给其他编译单元
3.习惯: 应当将不希望被其他编译单元引用的函数和变量放在匿名的命名空间中
4.注意: static 3种用法,在局部作用域,类作用域,命名空间作用域具有不尽相同的作用.共同点: 都具有静态生存期
3.标准C++库
1.输入输出类
2.容器类与ADT(抽象数据类型)
3.存储管理类
4.算法
5.错误处理
6.运行环境支持
习惯: using namespace不宜放在头文件中,会使一个命名空间不被察觉地对一个源文件开放
4.编译预处理
1.#include指令
1.作用: 将另一个源文件嵌入到当前源文件中该点处
2.两种格式
1.#include<文件名>
按标准方式搜索,文件在include子目录下
2.#include"文件名"
首先在当前目录中搜索,没有,再按标准方式搜索
3.可以嵌套使用
2.#define 和 #undef指令
1.#define能完成的功能,被C++引入的语言特性替代
1.定义符号常量
C++中在类型说明语句中使用const修饰
2.定义带参数的宏,实现简单函数计算
C++用内联函数
2.与条件编译指令一起使用,起一些特殊作用
C++中#include最常用之处
3.#undef作用: 删除用#define定义的宏
3.条件编译指令
1.作用: 限定某些内容在满足一定条件下才参与编译,使同一个源程序在不同的编译条件下产生不同的目标代码
2.形式
1.#if 常量表达式1 程序段1 #elif 常量表达式2 程序段2 #else 常量表达式3 程序段3 #endif
2.#ifdef 标识符 程序段1 #else 程序段2 #endif
多个文件#include,可能导致同一个内容在同一个文件中被#include两次,用这个后若被定义过,就不编译这段,可以防止重复定义
4.defined操作符
1.说明: 是预处理操作符,不是指令,不要以#开头
2.形式: defined(标识符)--若标识符经#define定义过,表达式为0
六、数组、指针 与字符串
1.数组
1.数组的声明与使用
2.数组的存储与初始化
1.数组可以被声明为常量,初始化后不可以改变,必须给定初值
3.数组作为函数参数
1.实参和形参都是数组名,类型要相同,传递的是地址,对应元素使用相同的数据存储地址 对形参改变,实参的数组也会改变
2.数组作为参数时,一般不指定第一维的大小,指定也会被忽略
4.对象数组
1.对象数组的元素是对象,不仅具有数据成员,还有函数成员
2.只能引用单个数组元素: 数组名[下标表达式].成员名
3.对象数组的初始化: 调用构造函数对每一个元素进行初始化
5.程序实例
利用Point类进行点的线性拟合
2.指针
1.内存空间的访问方式
变量和地址
2.指针变量的声明
1.定义: 指针变量用于存放内存单元的地址
2.声明指针语法形式: 数据类型 *标识符
3.声明指针变量指出是什么类型
1.声明了变量所需要的空间
2.限定了对变量可以进行的运算及其运算规则
3.与地址相关的运算*和&
* 指针运算符:解析(作为一元运算符)
1.声明语句,变量名之前: 声明一个指针
2.执行语句/声明语句的初始化表达式: 访问指针所指对象的内容
& 取地址运算符(作为一元运算符)
1.声明语句,变量左边: 声明的是引用
2.变量赋初值,等号右边/执行语句: 取对象的地址
4.指针的赋值
1.指针赋初值的两种方式
1.定义指针的同时进行初始化赋值: 存储类型 数据类型 *指针名=初始地址
2.定义之后单独使用赋值语句: 指针名=地址
不需要加*,不需要访问指针的内容
2.一个数组
可以直接用它的名称表示它的起始地址,数组名称实际上就是一个不能被赋值的指针:指针常量
3.指向常量的指针
const int*p1=&a; 不能通过指针改变对象的值,但指针本身可以改变:确保常量不被意外更改
4.指针类型的常量
int *const p2=&a: 指针本身的值就不能被改变
两者对比
5.void类型指针
可以存储任何类型的对象地址,使用类型显式转换便可以访问任何类型的数据:static_cast<int*>(p):只在所指向的数据类型不确定时使用
5.指针的运算
1.算术运算
与数组相连: *(p+n)= p[n]:表示p当前所指位置后方第n个数的内容
2.关系运算
1.相同类型的指针: 相等表示指向同一个地址
2.不同类型或者与非0整数: 运算是没有意义的
3.可与0比较: 0专门表示空指针(也可以用NILL):不指向任何有效地址的指针
4.如果不便于用一个有效地址给指针赋初值,应该用0作为初值,避免指向不确定的地址
6.用指针处理数组元素
1.指针加减适合处理一段连续内存空间的同类数据:数组
2.把数组当做函数的参数等价于把指向数组的指针作为参数:int p[] = int p[3] = int *p
7.指针数组
1.定义: 一个数组的每个元素都是指针变量,必须是同一类型的指针,必须先赋值后引用
2.声明一维指针数组: 数据类型 *数组名[下标表达式];
3.*(pLine[i]+j) 等价于 pLine[i][j]: 先把第i个指针读出,然后读取后方的第j个数
4.多维数组: 在形式上可以当作相应维数减1的一个多维指针数组
8.用指针作为函数参数
1.需要在不同的函数之间传送大量数据时,数据存放在一个连续的内存区域,可以只传递数据的起始地址,不必传递数据的值
2.实参和形参指针变量指向同一个内存地址,通过形参指针对数据值的改变也同样影响实参指针所指向的数据值
3.三个作用
使形参和实参指向共同的内存空间,达到参数双向传递的目的
减少数据传递的开销
通过指向函数的指针传递函数代码的首地址
4.不需要通过指针改变对象的内容,应在参数表中将其声明为指向常量的指针
5.当函数使用指针或引用都可以时,使用引用会使程序可读性更好些
9.指针型函数
1.定义: 返回值是指针的函数
2.目的: 把大量的数据从被调函数返回到主调函数中,非指针只能返回一个变量或对象
3.定义形式: 数据类型* 函数名(参数表)
4.注意: 指针的生存周期要和指向对象生存周期一致。返回全局变量地址/主函数自己定义变量地址,不能返回非静态局部变量地址
10.指向函数的指针
1.函数名: 表示函数的代码在内存中的起始地址,也包括函数的返回值类型和参数的个数、类型、排列次序
2.定义: 函数指针是存放函数代码首地址的变量
3.声明: 数据类型 (*函数指针名)(形参表)
4.简化: 使用typedef可以很方便地为复杂类型起别名
5.赋值: 使用前进行赋值 函数指针名 = 函数名(一个声明过,和函数指针具有相同返回类型和相同参数表)
11.对象指针
1.对象指针的一般概念
1.对象的内存空间只存放数据成员,函数成员不在每一个对象中存储副本
2.声明: 类名 *对象指针名;
3.访问对象成员: 对象指针名->成员名 等价于 (*对象指针名).成员名
4.关于类的引用问题
class Fred; //前向引用声明class Barney{ Fred x; //错误;类Fred的定义尚不完善};class Fred{ Barney x; };class Fred; //前向引用声明class Barney{ Fred *x; //正确 };class Fred{ Barney x; };
2.this指针
1.定义: 隐含于每一个类的非静态成员函数中的特殊指针(包括构造函数和析构函数),它指向正在被成员函数操作的对象
2.细节: 实际上是类成员函数的一个隐含参数,目的对象的地址自动作为该参数的值,传递给成员函数.常成员函数是常指针类型
3.提示: 局部作用域中,声明与类成员同名的标识符时,直接引用为声明的标识符,用this指针可以访问该类成员
3.指向类的非静态成员的指针
声明:类型说明符 类名::*指针名; //指向数据成员 类型说明符 (类名::*指针名)(参数表); //指向函数成员
赋值:指针名=&类名::数据成员名;(对类成员取地址时,类的作用域之外不能对它的私有成员取地址) 指针名=&类名::函数成员名;
引用:对象名.*类成员指针名/ 对象指针名->*类成员指针名 (对象名.*类成员指针名)(参数表)/ (对象指针名->*类成员指针名)(参数表)
4.指向类的静态成员的指针
静态成员的访问不依赖于对象,用普通的指针指向和访问静态成员
3.动态内存分配
1.堆对象: 在程序运行过程中申请(建立)和释放(删除)的存储单元
2.建立和删除的运算符: new和delete
1.new: 动态分配内存/动态创建堆对象 new 数据类型(初始化参数列表);
2.内存申请成功,返回一个指向首地址的指针进行访问
3.不希望设定初值,可以把括号省去: new 数据类型;
4.保留括号但不写任何值,表示用0初始化
5.delete 指针名; 删除由new建立的对象,释放内存空间
6.若为对象,调用析构函数,只能使用delete进行一次删除操作,多次使用会运行错误
7.用new分配的空间,必须用delete释放,否则会"内存泄漏"
8.new T 与 new T()
new T: 调用系统生成的隐含的默认构造函数
new T(): 还会为基本数据类型和指针类型用0赋初值,递归的
3.new 类型名[数组长度];
1.new可以创建数组类型的对象,给出数组的结构说明
int *p=new int[10]();
2.数组长度可为变量--任何能够得到正整数值的表达式
3.[]后可以加(): 不加(),相当于执行new T;加了(),相当于执行new T()
4.删除时要在指针名前面加[]删除整个数组: delete[] 指针名;
4.动态数组类
1.普通建立和删除数组繁琐,最好将建立和删除的过程封装起来,形成动态数组类
2.可以在每次访问之前检查下标是否越界
3.assert 断言: 判断一个条件表达式是否为true,否则程序终止,只在调试模式生效,只检查程序本身逻辑错误,不检查用户的不当输入
assert(index>=0 && index<size);
4.问题: 对数组元素的访问形式"points.element(0)"显得啰嗦,可以对[]进行重载
5.创建多维数组
1.第一维可为变量,其他各维数组长度必须是结果为正整数的常量表达式
2.成功后返回一个指针,但不是T类型,而是指向T类型数组的指针,个数为除第一维外各维下标的乘积
3.float (*cp)[25][10]; cp=new float[10][25][10];
cp既可以作为指针使用,也可以像三维数组名使用
4.用vector创建数组对象
1.vector: 被封装的动态数组,具有各种类型,是一个类模板
2.定义动态数组形式: vector<元素类型> 数组对象名(数组长度);
元素类型可容纳任何类型,长度可为变量
3.vector定义的所有元素都会被初始化,需要保证类具有默认构造函数,初值可自己指定,但只能为相同初值
vector<元素类型> 数组对象名(数组长度,元素初值);
4.访问方式与普通数组相同: 数组对象名[下标表达式]
5.注意: vector数组对象的名字就是一个数组对象,不是数组的首地址,因为数组对象不是数组,而是封装了数组的对象
6.重要成员函数size(): 返回数组的大小
7.for循环配合auto遍历举例
5.深复制与浅复制
1.浅复制: 两个指针指向同一个内存地址,并没有形成真正的副本,会相互影响
2.弊端: 两个对象共用同一块内存空间,该空间会被两次释放,导致运行错误
3.补充:移动构造
1.定义: 将要消亡的临时对象的内存转移过来
2.格式: class(class &&n)
3.&&: 右值引用:函数返回的临时变量是右值
6.字符串
1.用字符数组存储和处理字符串
2.string类
1.构造函数的原型:P231
1.字符串常量和用字符数组表示的字符串变量都可以隐含转换为string对象
2.string类的操作符(进行了重载)
1.s+t
2.s[i]
3.常成员函数功能简介:P232
1.append(const char *s): 将字符串s添加在本串尾
2.getline(cin,s2,','): 不以空格作为分隔符,以换行符作为分隔符,允许增加其他分隔符
七、继承与派生
1.类的继承与派生
1.继承关系举例
1.原有的类: 基类或父类 产生的新类: 派生类或子类
2.派生类的定义
1.语法: class 派生类名:继承方式 基类名1,继承方式 基类名2...
2.多继承: 一个派生类,可以同时拥有多个基类
3.继承方式: 规定了如何访问从基类继承的成员,每一个继承方式只限定紧随其后的基类,默认为私有继承
4.类的继承方式: 指定了派生类成员和类外成员对于基类的成员的访问权限
3.派生类生成过程
1.吸收基类成员
1.构造函数和析构函数不能被继承
2.改造基类成员
1.基类成员的访问控制问题
依靠派生类定义时的继承方式控制
2.对基类数据或函数成员的覆盖或隐藏
1.隐藏: 派生类中声明一个和基类完全同名的成员
2.覆盖:
3.重载: 函数成员中参数不同,派生类新成员隐藏了外层同名成员
3.添加新的成员(核心)
1.新的构造和析构函数
2.访问控制
1.公有继承 public
1.派生类中新增成员
公有和保护保持不变,私有不可直接访问
2.派生类外部(对象)
只能访问公有,不能访问保护和私有
2.私有继承 private
1.派生类中新增成员
公有和保护都以私有出现在派生类中,私有不可直接访问
2.派生类外部(对象)
什么都不可以直接访问
进一步派生,全部成员在新的派生类都无法访问,相当于终止了派生,使用少
3.保护继承 protected
1.派生类中新增成员
公有和保护以保护出现在派生类中,私有不可直接访问
2.派生类外部(对象)
什么都不可以直接访问
4.总结
1.私有继承和保护继承: 在直接派生类中,访问属性相同;如果派生类继续派生,就有差别
2.对于类的对象: 私有成员和保护成员一样不可访问
3.对于派生类: 保护成员有可能被它的派生类访问,决不能被其他外部使用者访问
合理利用保护成员,在共享与成员隐藏之间找到一个平衡点
3.类型兼容规则
1.定义
在需要基类对象的任何地方,都可以使用公有派生类的对象代替
2.替换的情形
1.派生类的对象可以隐含转换为基类对象 b1=d1;
2.派生类的对象可以初始化基类的引用 B &rb=d1;
3.派生类的指针可以隐含转换为基类的指针 pb1=&d1;
3.是多态性的重要基础之一
函数形参为基类的对象,实参可以是派生类对象/指针
4.问题
通过"对象名.成员名",即使对象为派生类的,但也只能访问从基类继承的成员,不能访问自己类中的
4.派生类的构造和析构函数
1.构造函数(无虚基类情况)
1.作用
派生类的构造函数只负责对派生类新增成员进行初始化,基类的成员还是由基类的构造函数完成
2.初始化过程
1.首先调用基类的构造函数,初始化它们的数据成员
2.然后按照初始化列表中指定的方式初始化派生类新增的成员对象
3.最后执行派生类构造函数的函数体
2.1 执行的一般次序
1.调用基类构造函数,按照它们被继承时声明的顺序(从左向右)
2.对新增的成员对象初始化,按照它们在类中声明的顺序
3.执行构造函数体中的内容
初始化列表中基类名,对象名之间的次序无关紧要
3.多个基类时
需要给予参数进行初始化的基类,显式给出基类名和参数表,使用默认构造函数的基类,可以不给出类名
4.派生类可以不声明构造函数,全部采用默认构造函数
2.复制构造函数
1.若没有,系统自动生成隐含的复制构造函数,自动调用基类的复制构造函数,对新增成员对象一一执行复制
2.形式: b::b(const b &v):a(v){...}
用派生类对象去初始化基类的引用
3.析构函数
1.只要在函数体中把新增的非对象成员的清理工作做好,系统自动调用其他的析构函数
2.执行次序和构造函数正好完全相反
3.系统会自动为每个类生成默认的析构函数
5.派生类成员的标识与访问
1.作用域分辨符
1.定义: :: 限定要访问的成员所在的类
2.使用形式: 类名::成员名
3.可见性原则
1.外层声明了一个标识符,内层没有再次声明
外层的标识符在内层依然可见
2.如果在内层声明了同名的标识符
外层标识符在内层不可见/内层标识符隐藏了外层同名标识符(隐藏原则)
3.基类为外层,派生类为内层
直接使用成员名只能访问到派生类的成员
4.若派生类声明了同名的新函数,即使函数的参数表不同,从基类继承的同名函数的所有重载形式也都会被隐藏
访问基类只能使用作用域分辨符和基类名
4.多继承情况
1.所有基类都没有上级基类
1.派生类新增同名成员
1.会隐藏所有基类的同名成员
2.通过"对象名.成员名"可以唯一标识和访问派生类成员
2.派生类没有声明同名成员
1.通过"对象名.成员名"不可以唯一标识和访问成员
2.访问基类,必须通过基类名和作用域分辨符来标识成员
3.细节
1.只有在相同的作用域中定义的函数才可以重载
2.子类定义与父类的函数同名,但有不同的参数数量/参数类型,不属于函数重载,为函数隐藏
3.using用法
1.一般功能
将一个作用域中的名字引入到另一个作用域中
2.特殊功能
using用于基类中的函数名,基类的函数不会被隐藏两个重载的函数并存在派生类作用域中
2.部分或全部基类从另一个基类派生
1.在直接基类中,从上一级基类继承来的成员拥有相同名称,派生类产生同名现象
2.也要用作用域分辨符,且必须用直接基类来限定,不能用间接基类限定
3.派生类对象在内存中同时拥有同名成员的两个副本,增加了内存开销,很多情况只需一个
通过虚基类解决
2.虚基类
1.作用
1.解决多继承时可能发生的对同一基类继承多次而产生的二义性问题
2.为最远的派生类提供唯一的基类成员,而不重复产生多次复制
2.用法
1.将共同基类设置为虚基类,从不同路径继承的同名数据成员在内存中就只有一个副本,同一个函数名也只有一个映射
3.语法形式
1.class 派生类名: virtual 继承方式 基类名
2.多继承时,只对紧跟其后的基类起作用
3.虚基类的成员在进一步派生中和派生类一起维护同一个内存数据副本
4.直接使用"对象名.成员名"可以唯一标识和访问这些成员
3.虚基类及其派生类构造函数
1.如果虚基类声明有非默认的构造函数,且没有声明默认构造函数,就麻烦了
2.直接或间接继承虚基类的所有派生类,必须在构造函数的成员初始化列表中列出对虚基类的初始化
3.最远派生类
建立对象时所指定的类
4.初始化原则
1.虚基类的成员由最远派生类的构造函数通过调用虚基类的构造函数初始化
2.只有最远派生类有用,该派生类的其他基类都自动忽略
5.构造一个类的对象的一般顺序
1.该类有直接或间接的虚基类,先执行虚基类的构造函数
2.有其他基类,按照在继承声明列表中出现的次序,分别执行它们的构造函数, 但不再执行它们的虚基类的构造函数
3.然后按照初始化列表中指定的方式初始化派生类新增的成员对象(类类型/基本数据类型)
4.最后执行派生类构造函数的函数体
八、多态性
1.多态性概述
1.多态的类型
1.重载多态
1.普通函数及类的成员函数的重载
2.运算符重载
2.强制多态
1.将一个变元的类型加以变化,符合函数或操作的要求(类型强制转换)
专用多态
3.包含多态
1.类族中定义于不同类中同名成员函数的多态行为(虚函数实现)
4.参数多态
1.类模板,使用时赋予实际的类型才可以实例化
通用多态
2.多态的实现
1.编译时的多态
1.定义: 在编译过程中确定了同名操作的具体操作对象
2.运行时的多态
1.定义: 在程序运行过程中才动态地确定操作所针对的对象
3.绑定
1.定义: 确定操作的具体对象的过程
1.计算机程序自身彼此关联的过程
2.把一个标识符名和一个存储地址联系在一起
3.面向对象术语: 把一条消息和一个对象的方法相结合
2.两种方式
1.静态绑定
1.定义: 绑定工作在编译连接阶段完成(确定某一同名标识到底调用哪一段代码)
2.类型: 重载,强制,参数多态
2.动态绑定
1.定义: 绑定工作在程序运行阶段完成
2.类型: 包含多态
2.运算符重载
0.说明
1.定义
对已有的运算符赋予多重含义,使同一运算符作用于不同类型的数据导致不同的行为
2.实质
函数重载
3.实现过程
1.先把运算表达式转化为对运算符函数的调用
2.将运算对象转化为函数的实参,根据实参的类型确定要调用的函数
编译中完成
1.运算符重载的规则
1.除了少数几个之外,全部可以重载,只能重载C++已有的运算符
1.类属关系运算符 .
2.成员指针运算符 .*
保证访问成员功能的含义不被改变
3.作用域分辨符 ::
操作数是对象,不是普通表达式,不具有重载特性
4.三目运算符 ?:
2.重载之后的优先级和结合性不会改变
3.针对新类型数据,功能相似,不改变操作对象个数,至少有一个操作对象是自定义类型
2.运算符重载为类的非静态成员函数
1.说明
1.双目运算符
1.左操作数是对象本身的数据,由this指针指出,右操作数由参数表来传递
2.单目运算符
1.操作数由this指针指出,不需要任何参数
3.+,-返回值
1.创建一个临时的无名对象作为返回值
4.前置++,--返回值
1.返回引用(左值),可直接操作当前对象,返回*this
5.后置++,--返回值
1.先用old保存*this
2.直接调用前置++(*this); 修改时不易出错
3.返回old(右值),不能改变对象
2.三种运算符
1.双目运算符B
1.只有一个形参,为op2所属的类型
2.op1 B op2 相当于 op1.operate B(op2)
2.前置单目运算符U
1.函数没有形参
2.U op 相当于 op.operate U()
3.后置运算符++,--
1.有一个int形参,不起任何作用,仅用于区别前置与后置
2.op++ 相当于 op.operate ++(0)
3.运算符重载为非成员函数
1.说明
1.不要机械地将非成员函数声明为友元函数,仅在需要访问类的私有成员/保护成员
2.不声明为友元函数
1.仅依赖于类的接口,接口不变化,函数实现就无须变化
2.需要类声明专门的接口
3.声明为友元函数
1.依赖于类的实现,即使接口不变化,只要私有数据成员变化,函数实现也要变化
2.可提高访问效率,不需要声明单独的接口
4.重载<<
1.第一个参数类型为 ostream &out (系统自带输出流)
2.一般重载为实现级联输出
3.最后返回值为引用类型(为了支持级联输出) out
2.三种运算符
1.双目运算符B
1.两个形参op1和op2
2.op1 B op2 相当于 operate B(op1,op2)
2.前置单目运算符U
1.一个形参为op
2.U op 相当于 operate U(op)
3.后置运算符++,--
1.两个形参,一个为op,一个为int形参,不起任何作用,仅用于区别前置与后置
2.op++ 相当于 operate ++(op,0)
4.两种重载形式的比较
1.语法形式(两个一样)
返回类型 operate 运算符(形参表)
2.参数个数
1.成员函数
1.参数个数比原来的操作数个数少一个(后置++,--除外)
2.原因: 第一个操作数作为函数调用的目标对象,无须出现在参数表,由this指针指出 函数体可直接访问第一个操作数的成员
2.非成员函数
1.参数个数与原操作数个数相同
2.原因: 所有操作数必须显式通过参数传递
注: 需要访问类的私有成员时,可声明为类的友元函数
3.各自特点
1.成员函数
1.优点: 重载方式更加方便
2.缺点: 左操作数必须是类类型,不能是其他类型(调用成员函数的目的对象不会被隐含转化)
2.非成员函数
1.优点
1.第一个操作数可以是不可更改的类型(<<的第一个操作数是标准库的类型,不可更改)
2.支持更灵活的类型转换(参数可以隐含转换)
3.虚函数
0.说明
0.定义
1.实现类族中定义于不同类中同名成员函数的多态行为
2.通过基类指针可以访问到派生类的同名成员,产生不同的行为
1.问题由来
1.如何利用一个循环依次处理同一类族中不同的对象,调用不同的同名函数,产生不同的行为
2.虚函数
1.是动态绑定的基础,派生之后,可实现运行过程中的多态
2.必须是非静态的成员函数,它属于对象,而不属于类
1.一般虚函数成员
1.声明语法
virtual 函数类型 函数名(形参表)
2.运行多态需要三个条件
1.类之间满足赋值兼容规则
2.要声明虚函数
3.由成员函数来调用,或通过指针/引用来访问虚函数
不能通过对象名来调用
3.派生类没有显式给出虚函数声明 系统会判断是否为虚函数
1.该函数是否与基类虚函数有相同的名称 参数个数/相同的对应参数类型 返回值/满足赋值兼容的指针/引用的返回值
4.注意
1.虚函数声明只能出现在类定义中的函数原型声明中,不能在成员实现的时候
2.虚函数一般不声明为内联函数
虚函数的调用需要动态绑定,而内联函数的处理是静态的
3.不能用对象名访问虚函数
1.基类的对象名不能表示派生类的对象
2.对象切片: 用派生类对象赋值构造基类对象,新增的数据成员会被舍弃
3.对象的类型很明确,无须动态绑定,在编译过程就进行绑定(静态绑定)
4.派生类的虚函数会覆盖基类的虚函数,还会隐藏基类中同名的所有其他重载形式
5.用指向派生类对象的指针还可以调用基类中被覆盖的成员函数,用基类名::函数名
6.基类构造函数调用虚函数时,不会调用派生类的虚函数
1.基类被构造时,对象还不是一个派生类的对象
2.同样,基类被析构时,对象已经不再是派生类对象
7.只有虚函数是动态绑定的,派生类只能重写虚函数的行为
8.重写虚函数时,如果有默认形参值,不要重新定义不同的值
默认形参值是静态绑定的,只能来自基类的定义
2.虚析构函数
1.声明语法: virtual ~类名();
2.C++中不能声明需构造函数,可以声明虚析构函数,没有类型,也没有参数
3.一个类的析构函数是虚函数,所有子类的析构函数也是虚函数
4.可能通过基类指针调用对象的析构函数(通过delete),就要让基类的析构函数成为虚函数
否则可能无法释放派生类的空间
4.纯虚函数与抽象类
1.纯虚函数
1.定义
1.基类中的一个虚函数,在基类中没有定义具体的操作内容,在派生类中给出各自的定义
2.声明格式
1.virtual 函数类型 函数名(参数表)=0;
2.基类中可以不再给出函数的实现部分
3.基类中仍允许给出实现,但即使给出实现,也必须由派生类覆盖,否则无法实例化
4.在基类中对纯虚函数定义的函数体的调用,必须通过 基类名::函数名
5.若析构函数声明为纯虚函数,必须给出实现,因为派生类的析构函数体执行完后需要调用基类的纯虚函数
3.纯虚函数与结构体为空的虚函数
1.前者没有函数体,后者函数体为空
2.前者所在的类为抽象类,不能直接实例化,后者可以实例化
3.共同特点: 都可以派生出新的类,给出新的实现,具有多态特征
2.抽象类
1.定义
1.带有纯虚函数的类
2.作用
1.为一个类族建立一个公共的接口,更有效发挥多态性
3.特点
1.处于类层次的上层,自身无法实例化,只能通过继承机制,生成非抽象类,再实例化
2.派生类给出所有纯虚函数的函数实现,才不是抽象类,才可以实例化
3.抽象类不能实例化(不能定义对象),但可以定义抽象类的指针和引用
九、群体类和群体数据的组织
1.函数模板与类模板
0.说明
1.参数化程序设计
1.通用代码不受数据类型的影响,自动适应数据类型的变化
2.参数化多态性
1.程序所处理的对象的类型参数化,使一段程序处理多种不同类型的对象
1.函数模板
1.定义形式
template<模板参数表>类型名 函数名(参数表)
template<typename T>T abs(T x)
2.函数模板的实例化
当类型参数的含义确定后,编译器将以函数模板为样板,生成一个函数
3.函数模板与重载
1.函数模板产生的相关函数是同名的,编译器用重载的方法调用相应函数
2.函数模板本身也可以用多种方式重载
4.函数模板与函数
1.函数模板本身在编译时不会生成任何目标代码,只有实例化才会生成目标代码
2.被多个源文件引用时,应当连同函数体一同放在头文件中,不像普通函数只把声明放在头文件中
3.函数指针只能指向模板的实例,不能指向模板本身
2.类模板
1.定义形式
template<模板参数表>class 类名
template<class T>class 类名
2.类模板的实例化
把参数绑定到形式参数可以创建具体的类
3.作用
1.为类定义一种模式,使类中的某些数据成员,成员函数的参数/返回值/局部变量能取任意类型
4.类与类模板
1.类: 对一组对象的公共性质的抽象
2.类模板: 对不同类的公共性质的抽象,属于更高层次的抽象
2.线性群体
1.数组类
1.浅复制与深复制
1.浅复制共用同一段空间,会造成混乱,更严重的是执行析构函数时,会出现严重错误(同一内存空间被释放两次)
2.与众不同的运算符
1.重载的 = 和 [],返回值类型都是对象的引用,通过引用改变对象的值
2.对于常对象,不希望通过[]修改其值,返回类型为常引用
3.返回引用而不返回对象,为了在T复杂类型时,避免创建新对象执行复制构造的开销
3.指针转换运算符的作用
1.当实参与形参不同时,系统会试图进行自动类型转换,但数组对象是自定义类类型,无法实现
2.直接返回当前对象中私有数组的首地址即可,不用指定返回值类型,由私有数组类型确定
3.当对象为常数时,为避免通过指针对数组内容修改,只能将对象转换为常指针
3.群体数据的组织
十、泛型程序设计与C++标准模板库
1.泛型程序设计及STL的结构
2.迭代器
3.容器
4.函数对象
5.算法
十一、流类库与输入输出
1.I/O流的概念及流类库结构
1.I/O流类库
1.是C语言中I/O函数在面向对象中的一个替换产品
2.流
1.定义: 将数据从一个对象到另一个对象的流动抽象为流
2.提取操作: 从流中获取数据
3.插入操作: 向流中添加数据
3.4个预定义的流对象
cin,cout,cerr,clog
2.输出流
0.说明
1.最重要的3个输出流
ostream,ofstream,ostringstream
2.ostream向标准设备输出
1.cout: 标准输出流
2.cerr: 标准错误输出流,没有缓冲,立即输出
区别在于重定向时
1.命令行中用>重定向,cout重定向到文件中,cerr不变
2.命令行中用2>重定向,cerr重定向到文件中,cout不变
3.clog: 类似cerr,但有缓冲,缓冲区满时被输出
默认都输出都屏幕
3.ofstream支持磁盘文件输出
1.构造输出流对象
1.使用情况
1.仅使用预定义的3个对象,不需要构造输出流;要使用文件流输出到文件,需要使用构造函数建立流对象
2.常用方法
1.使用默认构造函数,再用open成员函数打开
ofstream myFile;myFile.open("filename");
2.调用构造函数时指定文件名
ofstream myFile("filename");
3.使用同一个流先后打开不同的文件(同一时刻只有一个打开)
2.使用插入运算符和操纵符
0.说明
1.所有操纵符都在iomanip中,使用时必须包含
2.所有成员函数在iostream中声明
1.输出宽度
1.在流中放入setw()操纵符/调用width()成员函数
2.填充符
1.默认是 空格
2.可用fill('')设置填充字符的值
3.要为同一行的不同数据项分别指定宽度,使用setw操纵符(写在输出内容之前)
4.setw和width都不截断数值
超过指定宽度,显示全部值
5.setw和width仅影响紧随其后的域,一个域输出完后恢复它的默认值
其他流格式选项保持有效直到发生改变
2.对齐方式
1.默认为 右对齐文本
2.设置左对齐
1.setiosflags(ios_base::left);
2.参数ios_base::left是静态常量,引用时必须加前缀
3.影响是持久的,直到resetiosflags(ios_base::left);重新恢复默认值为止
3.精度
1.浮点数的输出精度默认值是 6
2.改变精度
1.用操纵符setprecision()
2.ios_base::fixed
以定点(小数点)格式显示浮点数值
3.ios_base::scientific
以科学格式(指数)显示浮点数值
若设置了,精度值确定小数位数若未设置,精度确定总的有效位数
4.进制
1.hex:十六进制 oct:八进制 dec:十进制(默认进制)
3.文件输出流成员函数
0.3种类型
1.与操纵符等价的成员函数
2.执行非格式化写操作的成员函数
3.修改流状态且不同于操纵符/插入运算符的成员函数
1.输出流的open函数
1.使用输出流,必须在构造函数/open函数中把该流与一个特定的磁盘文件关联起来(参数相同)
2.可指定open_mode标志,用按位或组合
1.ios_base::app
打开一个输出文件在文件尾添加数据
2.ios_base::trunc
打开一个文件,若已存在则删除所有原来内容
3.ios_base::binary
以二进制模式打开一个文件(默认是 文本模式)
2.输出流的close函数
1.虽然析构函数会自动关闭,但需要在同一流对象上打开另外的文件,要用close函数
3.put函数
1.把一个字符写到输出流中(格式化参量不起作用,用<<会起作用)
4.write函数
1.把内存中的一块内容写到一个文件输出流中,长度参数指出写的字节数
2.第一个参数固定为char指针(指向内存数据起始地址) reinterpret_cast<char*>(&a), 第二个参数为所写的字节数 sizeof(a)
3.write函数遇到空字符时并不会停止,可以完整写入类的结构
5.seekp和tellp函数
1.一个文件输出流保存一个内部指针指出下一次写数据的位置
2.seekp设置这个指针,可以随机方式向磁盘文件输出
3.tellp返回这个文件位置指针值
6.错误处理函数
4.二进制输出文件
1.问题
最初流用于文本,但文本文件的行分隔符不大一样,转换时可能会出现问题
2.解决
使用二进制模式输出时,字符时不需要转换的
5.字符串输出流
1.定义
输出流除了可用于向屏幕或文件输出,还可用于生成字符串
2.两个构造函数
1.只有一个形参,表示流的打开模式
与文件输出流的第二个参数功能相同
2.有两个形参: 第一个是string常对象,为字符串流的内容设置初值,第二个表示打开模式
3.成员函数
1.除了专用于文件的open/close,其他都有
2.特有的函数str,返回一个string对象
4.典型用法
1.将一个数值转化为字符串,方便输出
os<<v; return os.str();
2.函数模板tostring可以将各种支持<<插入符的类型的对象转换为字符串
3.输入流
0.说明
1.3个输入流类
1.istream
1.用于顺序文本模式输入
2.很少需要构造istream对象,预定义的对象cin用于完成从标准输入设备(键盘)的输入
2.ifstream
1.支持磁盘文件输入,可指定二进制/文本模式
3.istringstream
1.构造输入流对象
1.仅使用cin对象,不需要构造输入流对象
2.从文件中读取数据,需要构造
1.使用默认构造函数建立对象,再用open成员函数打开
2.调用构造函数时指定文件名和模式
ifstream myFile("filename");
2.使用提取运算符
1.提取运算符>>对于所有标准数据类型都是预先设计好的
2.用于格式化文本输入,以空白符为分隔
3.若输入一端包含空白符的文本,用非格式化成员函数 getline,可读取包含有空格的文本块
3.输入流操纵符
1.最重要的是: 进制操纵符dec/oct/hex
4.输入流相关函数
1.输入流的open函数
1.ios_base::in
打开文件用于输入(默认)
2.ios_base::binary
以二进制模式打开文件(默认是 文本模式)
2.输入流的close函数
1.虽然析构函数会自动关闭,但需要在同一流对象上打开另外的文件,要用close函数
3.get函数
1.与>>功能类似,区别在于get函数包括空白字符(一次读一个字符)
2.当按下Ctrl+Z及回车键时,程序读入的值是EOF,程序结束
4.getline函数
1.成员函数getline
1.允许从输入流中读取多个字符
2.允许指定输入终止字符(默认是 换行字符)
3.完成后,删除该终止字符
4.只能将输入结果存在字符数组中,大小不能自动扩展,不方便
2.非成员函数getline
1.完成相同功能,结果保存在string类型的对象中
2.两个参数: 输入流和string类型的对象,第三个参数可选: 表示终止字符
3.声明在string头文件中
5.read函数
1.功能
1.从一个文件读字节到一个指定的存储器区域,由长度参数确定字节数
2.参数
1.第一个参数固定为char指针(指向存储器的起始地址) reinterpret_cast<char*>(&a), 第二个参数为所读的字节数 sizeof(a)
6.seekg和tellg函数
1.一个文件输入流保存一个内部指针指出下一次读数据的位置
2.seekg设置这个指针,可以实现面向记录的数据管理系统
用固定长度的记录尺寸*记录号=相对于文件末尾的字节位置,然后用get读
3.tellg返回这个文件位置指针值,值是streampos类型
可确定某元素的位置
5.字符串输入流
1.从一个字符串中读取数据
2.典型用法
1.将一个字符串转换为数值
T v; is>>v; return v;
2.函数模板fromString把各种支持>>提取符的字符串转换为该类型的数据
4.输入输出流
1.iostream对象可以是数据的源或目的
2.两个派生类
1.fstream
支持磁盘文件输入和输出,两个逻辑子流的单个流,一个子流输入,一个子流输出
2.stringstream
支持面向字符串的输入和输出,对同一个字符串的内容交替读写
十二、异常处理
1.异常处理的基本思想
2.C++异常处理的实现
3.异常处理中的构造与析构
4.标准程序库异常处理
主题