一、虚析构
当使用delete释放一个父类指针时(父类引用),不管指向的是父类对象还是子类对象,都只会调用父类的析构。
当使用多态特性时,如果子类中有需要析构释放的资源,为了避免内存泄漏就需要把父类的析构函数设置为虚函数。
当父类的析构函数为虚函数时,子类的析构会自动覆盖它而不用比较它们的名字,当通过父类或引用来释放子类对象时会自动调用子类的析构函数。
二、文件流
C++把对文件的操作封装到了类中。
ifstream 输入文件流
ofstream 输出文件流
fstream 输入/输出文件流
1、打开文件
使用类的构造函数或open成员函数打开文件,它们的参数一样。
void open( const char *filename, openmode mode );
mode:
ios::app 追加输出
ios::ate 当已打开时寻找到EOF
ios::binary 以二进制模式打开文件
ios::in 为读取打开文件
ios::out 为写入打开文件,
ios::trunc 覆盖存在的文件
ifstream:默认mode
ios::in 为读取打开文件,文件不存在则打开失败
ofstream: 默认mode
ios::out 为写入打开文件,文件不存在则创建
ios::trunc 清空存在的文件
fstream: 默认mode
ios::in 为读取打开文件
ios::out 为写入打开文件
2、关闭流 close成员函数
与标准C的fclose和系统的close功能一样。
3、格式化输入输出
可以完全按照cout/cin的使用方式来读写格式化文件。
在读写类或结构时候可以重载输入输出运算符(<</>>)来提高效率,重载方法与cout/cin的一样。
还可以使用一些格式标志来设置输入输出流的格式,通过flags(), setf(), 和 unsetf() 三个函数来控制。
详细格式志请查看帮助手册。
4、二进制读写
istream &read( char *buffer, streamsize num );
功能:以二进行格式读取数据
buffer:存储数据的缓冲区
num:想要读取的字节数
注意:返回值并不是成功读取到的字节数,而是流对象的引用,可以通过输入流的成员函数gcount获取成功读取到的字节数。
ostream &write( const char *buffer, streamsize num );
功能:以二进制格式写入数据
buffer:待写入的数据的首地址
num:要写入的字节数
注意:返回值不是成功写入的字节数,通过流的good成员函数判断是写入成功。
5、随机读写
istream &seekg( off_type offset, ios::seekdir origin );
功能:以偏移称加基础位置设置文件位置指针
istream &seekg( pos_type position );
功能:以绝对位置设置文件位置指针
pos_type tellg();
功能:获取输入流的文件位置指针
ostream &seekp( off_type offset, ios::seekdir origin );
ostream &seekp( pos_type position );
pos_type tellp();
功能:同上
origin: 基础位置
ios::beg
ios::cur
ios::end
练习1:使用C++文件流实现覆盖检查的cp命令。
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc,const char* argv[])
{
if(3 != argc)
{
cout << "User:cp src dest"<< endl;
return 0;
}
ifstream src(argv[1]);
if(!src.good())
{
cout << "源文件不存在,请检查!" << endl;
return 0;
}
ifstream test(argv[2]);
if(test.good())
{
cout << "目标文件已经存在,是否覆盖(y/n)?";
char cmd;
cin >> cmd;
if('n' == cmd || 'N' == cmd)
{
src.close();
test.close();
return 0;
}
}
ofstream dest(argv[2]);
char buf[1];
while(src.read(buf,sizeof(buf)).gcount() > 0)
{
dest.write(buf,src.gcount());
}
src.close();
dest.close();
}
三、类型信息
typeid 用于获取数据的类型信息,返回type_info类型临时对象。
使用前要加头文件typeinfo,type_info对象成员函数name可以获取到基本的缩写,自定义类型的名字及名字的长度,以P开头的是指针类型。
type_info对象还支持== !=运行符,能分辨出两种个数据是否是同一种类型。
如果用于判断父子类的指针或引用,它不能准备分辨出实现的对象类型,但如果父类有虚函数,就可以分辨出来。
四、异常处理
在C++中当代码出现问题时,不能通过返回值和errno反映的,而是随机可以返回一个未知的数据来表示错误。
抛异常:
throw 数据(基本类型|自定义类)。
注意:不能抛出局部对象的指针或引用,因为当异常抛出后函数就执行结束了,函数的栈内存会被释放,如果返回局部对象的指针或引用则返回的是野指针或悬空引用。
最好抛临时对象,而上层代码用const引用捕获。
异常捕获:
注意:如果抛出的异常没有被捕获,程序就会停止。
try{
可以抛出异常的代码
}
catch(const 类型1& 变量名)
{
处理异常,如果无法处理可以向上层代码继续抛出异常。
}
catch(const 类型2& 变量名)
{
}
…
异常捕获是从上到下匹配,只要合适就算匹配成功,并不是择优匹配。
如果异常类型可能是父子类,那么在捕获时要先子类后父类。
函数的异常声明:
在声明函数时可以函数的末尾声明此函数可能抛出的异常。
void func(void) throw(类型1,类型2,…);
1、如果不写异常声明则表示该函数什么类型的异常都可能抛出。
2、如果抛出声明以外的异常类型,则该异常无法捕获,程序肯定会死亡,因此声明异常时要慎重。
3、throw() 表示什么异常都不会抛出
五、C++标准库异常
C++标准库异常定义了常见的异常,可以直接使用,同时C++标准库中也使用这套异常。
这些异常类都有一个what成员函数,里面记录了异常产生的原因。
class Error
{
int errno;
char* msg;
Error(const Error& that) {}
void operator=(const Error& that) {}
public:
Error(const char* str="未知类型错误!",int errno=-1):errno(errno)
{
msg = new char[strlen(str)+1];
stdrcpy(msg,str);
}
~Error(void)
{
delete[] msg;
}
const char* msg(void)
{
return msg;
}
}