一、虚函数表
什么是虚函数表:在C++的类中,一旦成员函数中有虚函数,这个类中就会多一个虚函数表指针,这个指针指向一个虚函数表,表里面记录了这个类中所有的虚函数。当这个类被继承,它的子类中也会有一个虚函数表(不管子类中有没有虚函数),如果子类的成员函数中有函数签名与父类的虚函数一样,就会用子类中的函数替换它在虚函数表中的位置,这样就达到了覆盖的效果 函数还是在的,代码段中
当通过类指针或引用调用函数时,会根据对象中实际的虚函数表记录来调用函数(每个对象有独自的虚函数表,根据虚函数表中的指针,找到父类的虚函数,替换它),这样就达到了多态的效果
类的对象内部会有指向类内部的虚表地址的指针。通过这个指针调用虚函数。
虚函数的地址存放于虚函数表之中
Test* p = new Test; 定义一个指针指向对象,再指向虚函数表,再找每一个函数,调用
int* p1 = (int*)*(int*)p;
((funcp)(*(p1+1)))();
二、虚析构
当使用delete释放一个父类指针时,不管实际指向的对象是子类还是父类都只会调用父类的析构函数(多态肯定会出现的问题)
如果子类的析构函数有需要负责释放的内存,就会造成内存泄漏
为了解决这个问题,可以把父类的析构函数设置为虚函数,析构函数进行覆盖时不会比较函数名
父类的析构函数被子类的析构函数覆盖,子类先释放,释放完了,就没覆盖了,父类释放
当父类的析构为虚函数时,通过父类指针或引用释放子类对象时,会自动调用子类的析构函数,子类的析构函数执行完成后也会调用父类的析构函数
注意:析构函数可以是虚函数,但构造函数不可以
构造函数,父类先执行,会发现子类覆盖,变成子类执行,子类会让父类先执行,构成死循环
三、强制类型转换
注意:C++中为了兼容C语言,(目标类型)源类型 依然可以继续使用,但C语言的强制类型转换安全性差,因此建议使用C++中的强制类型转换
注意:C++之父认为如果代码设计的完善,根据不需要用到强制类型转换,而C++的强制类型转换之所以设计得很复杂,是为了让程序员多关注代码本身的设计,尽量少使用
C++中的强制类型转换保证没有很大安全隐患
static_cast<目标类型>(源类型) short* sp=static_cast<short*>(p) 编译器会对源类型和目标类型做兼容性检查,不通过则错
整型与整型 、指针与指针 一个体系内的转换
void* p = NULL;
short* sp = static_cast<short*>(p);
dynamic_cast<目标类型>(源类型) 编译器会对源类型和目标类型是否同为指针或引用,并且存在多态型的继承关系
多态,父子类 (虚函数)
const_cast<目标类型>(源类型)编译器会对源类型和目标类型是否同为指针或引用,除了常属性外其它必须完全相同否则报错 const
reinterpret_cast<目标类型>(源类型)编译器会对源类型和目标类型是否同为指针或整数进行检查,也就是说把整数转换成指针或把指针转换成整数
动态链接,静态链接 (动态编译,静态编译)
1、静态库,动态库
2、多态
静态编译:指针或引用的目标是确定,在编译时间就确定所有类型检查、函数调用
动态编译:指针或引用的目标是不确定的(多态),只有运行时才确定,具体是哪个子类
四、I/O流
I/O流的打开模式:
ios::app 打开文件用于追加,不存在则创建,存在不清空 写在文件尾,读在文件头
ios::ate 打开时定义到文件末尾
ios::binary 以二进制模式进行读写
ios::in 以读权限打开文件,不存在则失败,存在不清空 从文件中到终端 >>
ios::out 以写权限打开文件,不存在则创建,存在则清空 从终端到文件中 <<
ios::trunc 打开文件时清空
fstream/ifstream/ofstream 类用于进行文件操作
构造函数或成员函数 open 用于打开文件
good成员函数检查流是否可用
eof成员函数用于输入流是否结束;使用eof()函数检测文件是否读结束
>>操作符用于从文件中读取数据到变量
<<操作符用于输出数据到文件
IO流有一系列格式化控制函数,类似:左对齐、右对齐、宽度、填充、小数点位数。
二进制读写:成员函数 read/write
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
该成员函数返回当前文件流的位置指针(字节数)
也可以借助此函数获取文件的大小
// 调整文件的位置指针到末尾
fs.seekp(0,ios::end);
cout << "文件的总字节数:" << fs.tellp() << endl;
五、类型信息 typeid
用于获取数据的类型信息,返回type_info类型临时对象
name成员函数,可以获取类型的名字,内建类型名字使用缩写 4Base 5Basea
同时还支持 == != 用来比较是否是同一种类型
如果用于判断父子类的指针或引用,它不能准确判断出实际的对象类型
但可以判断具有多态继承关系的父子类的指针或引用,它的实际对象
Base b;
(typeid(b) == typeid(Base))-----1
#include <iostream>
#include <typeinfo>
using namespace std;
class Base
{
public:
virtual ~Base(void)
{
}
};
class Test:public Base
{
};
int main()
{
Base b;
Test t;
cout << (typeid(b) == typeid(t)) << endl;//----------------0
cout << (typeid(b) == typeid(Test)) << endl;//------------0
cout << (typeid(t) == typeid(Base)) << endl;//------------0
Base* p = new Test;
cout << (typeid(p) == typeid(Test*)) << endl;//-----------0
cout << (typeid(*p) == typeid(Test)) << endl;//------------在有虚函数是1,没有为0
}
六、异常处理
抛异常
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;
}
};