一、虚函数表
- 在C++的类中,一旦成员函数中有虚函数,此类中就会多一个虚函数表指针,指向一个虚函数表,记录的是类中所有的虚函数
- 当这个类被继承,它的子类中也会有一个虚函数表
- 如果子类的成员函数中有函数签名与父类的虚函数一样,就会用子类中的函数替换它的虚函数的位置,这样就达到了覆盖的效果
- 当通过类指针或引用调用函数时,会根据对象中实际的虚函数表记录来调用函数,这样就达到了多态的效果
二、虚析构
- 当使用
delete
释放一个父类指针时,不管实际指向的对象时子类还是父类,都只会调用父类的析构函数(多态肯定会出现的问题) - 如果子类的析构函数有需要负责释放的内存,就会造成内存泄漏
- 为了解决这个问题,可以把父类的析构函数设置为虚函数(析构函数进行覆盖时,不会比较函数名)
- 当父类的析构为虚函数时,通过父类指针或引用释放子类对象时,会自动调用子类的析构函数,子类的析构函数执行完成后也会调用父类的析构函数
- 注意:析构函数可以使用虚函数,但构造函数不可以是虚函数
三、强制类型转换
- C++中为了兼容C语言,(目标类型)源类型依然可以继续使用,但C语言的强制类型转换安全性差,因此建议使用C++中的强制类型转换
- C++之父认为如果代码设计的完善,根本不需要用到强制类型转换,而C++的强制类型转换之所以设计的很复杂,是为了让程序员多关注代码本身的设计,尽量少使用
- C++中的强制类型转换可以保证基本没有安全隐患
static_cast<目标类型>(源类型)
:编译器会对源类型和目标类型进行兼容性检查,如果冲突则报错dynamic_cast<目标类型>(源类型)
:编译器会对源类型和目标类型检查是否同为指针或引用,并且是否存在多态型的继承关系const_cast<目标类型>(源类型)
:编译器会对源类型和目标类型检测是否同为指针或引用,除了常属性外其他部分必须完全相同否则转换失败reinterpret_cast<目标类型>(源类型)
:编译器会对源类型和目标类型检测是否为指针或整数,可以把指针转换成整数,也可以把整数转换成指针
四、静态编译
- 指针或引用的目标是确定的,在编译时期就经过了所有的类型检查以及函数调用
五、动态编译
- 指针或引用的目标是不确定的(多态),只有函数调用时才确定具体是哪个子类
六、I/O流
ios::in
:以读权限打开文件,不存在则失败,存在不清空
ios::out
:以写权限打开文件,不存在则创建,存在则清空
ios::app
:打开文件,用于追加,不存在则创建,存在不清空,在文件头读取,在文件末尾写入
ios::binary
:以二进制模式进行读写
ios::ate
:打开时定位到文件末尾
ios::trunc
:打开文件时清空
fstream/ifstream/ofstream
:
- 用于进行文件操作
open
用于打开文件(成员函数或构造函数)good
检查流是否可用(成员函数)eof
用于输入流是否结束(成员函数)<<
用于输入数据到文件(操作符)>>
用于从文件中读取数据到变量(操作符)
IO流有一系列格式化控制函数,类似:左对齐、右对齐、宽度、填充、小数点位数 …
二进制读写:
read(char_type *__s,streamsize __n)
gcount
:可以获取上次流出的二进制读操作的字节数(成员函数)
write(char_type *__s,streamsize __n)
good
:可以获取到写操作是否成功(成员函数)
随机读写:
seekp(off_type,ios_base::seekdir)
- 功能:设置文件的位置指针
off_type
:偏移值(正值向右,负值向左)seekdir
:基础位置ios::beg
:文件开头ios::cur
:当前位置ios::end
:文件末尾
获取文件位置指针:tellp()
(无参数)
- 该成员函数返回当前文件流的位置指针(字节数)
- 也可以借助此函数获取文件大小
练习:使用C++标准IO,实现带覆盖检测的cp命令(./cp src dest)
七、类型信息 typeid
- 用于获取数据的类型信息,返回
type_info
类型临时对象- 成员函数
name
,可以获取类型的名字,内建类型名字使用缩写 - 同时还支持
== / !=
用来比较是否是同一种类型
- 成员函数
- 如果用于判断父子类的指针或引用,它不能准确判断出实际的对象类型
- 但可以判断具有多态继承关系的父子类的指针或引用,实际类型的对象
七、异常处理
抛异常:
throw 数据
抛异常对象:
- 可以抛的是:基本类型
- 不能抛的是:局部对象的指针或引用
- 如果异常没有被捕获并处理,程序就会停止
捕获异常:
try{
可能抛出异常的代码
}
处理异常:
catch(类型 变量名)// 根据数据类型捕获
{
处理异常,如果无法处理,可以继续抛出异常
}
//捕获异常的顺序是自上而下的,而不是最精准的匹配,针对子类异常捕获时要放在父类的前面
函数的异常声明:
返回值类型 函数名(参数)throw(类型1,类型2,...)
- 注意:如果不写异常声明表示什么类型的异常都可能抛出
- 注意:如果写了异常声明表示只抛出某些类型的异常,一旦超出异常声明的范围,程序会直接运行,无法捕获
- 注意:函数的异常声明
throw()
表示什么类型都会抛出
设计异常类:
class Error
{
int errno;
char errmsg[255];
public:
Error(int errno = -1,const char* msg="未知错误")
{
this->errno = errno;
strcpy(errmsg,msg);
}
int getError(void)
{
return errno;
}
const char* getErrmsg(void)
{
return errmsg;
}
};