总结
这一章讲了异常处理。
- 首先你得有个抛出异常,用throw表达式,可以throw整数、字符串、异常类类对象,可以定义异常类
- 然后需要捕获异常,用catch子句,catch{//…}异常对象的类型是会和捕获并处理异常的catch子句比对的,看哪个catch子句能处理这种类型的异常对象,catch子句可以重新抛出异常交由其他catch子句处理,就在catch子句最后写个throw;就行。
- 捕获任何类型异常,写catch(…){//…}
- 然后讲了提炼异常,介绍try块,try{//…}和catch子句搭配,catch子句紧接try{}之后,当try块内的程序代码执行时,如果try块有任何异常抛出,捕获catch()内指定的异常。
- 从提炼异常引申到了异常处理机制,如果异常不能在某个地方被处理(某个函数里),就会终止这个函数,向调用这个函数的地方寻求可处理异常的catch子句(可能会不断解开“函数调用链”)
- 如果main()函数还找不到能处理异常的catch子句,则调用C++标准库的terminate()函数来终结main()函数(整个程序)的运行。
- 如果异常能在某个地方被处理(比如某个函数里),则该地方(函数)的剩余代码可继续被执行
- 接下来是局部资源管理,C++保证函数中的所有局部对象的析构函数都会被调用。
- 介绍了auto_ptr,使用前包含memory头文件,auto_ptr将*和->运算符重载,以便我们可以像用一般指针一样用auto_ptr对象。
- 最后是介绍了标准异常。首先说明new表达式的具体工作步骤。然后介绍bad_alloc异常类。接着是异常类体系exception类,bad_alloc派生类派生自exception基类。
- 自己编写的异常类也可以继承于exception基类之下,好处是可以让捕获抽象基类exception的程序代码(catch子句)所捕获。使用/提到exception基类的程序代码必须包含头文件exception
- 抛出自己编写的异常,打印自编的异常类里的what()函数(继承自exception基类)里输出的内容。
- 最后介绍了ostringstream类和istringstream类,使用/提到这些类的程序代码必须包含头文件sstream
- ostringstream类内提供内存内的输出操作,可以把不同类型的数据格式化为字符串并输出到一个string对象上(非字符型/string型数据格式化为string型),而istringstream类相反,作用是将表示非字符数据的字符串(string型)转换为其实际的类型,
- ostringstream类和istringstream类作用都体现在其类对象<<" “(或非” "对象上)
- ostringstream类有一个str()成员函数,用来返回转换成字符串(string型)的对象
- string类有一个成员函数c_str(),把string型字符串转换为const char
*
型,并返回const char*
型字符串。
习题
7.1
- ifstream类的构造函数形参类型是const char*,如果有以下操作:
int *alloc_and_init(string file_name)
{
ifstream infile(file_name);
if(!infile)
{
//处理打开文件失败的操作(处理异常)
}
//...
}
是不可以的。ifstream infile(file_name);
要改为ifstream infile(file_name.c_str());
如果有以下操作:
int elem_cnt;
infile>>elem_cnt;
一定在读取文件的内容进elem_cnt后检查一下读取的到底是个什么东西,如果文件是个空文件或者字符/字符串文件,infile>>elem_cnt;
的操作就会失败,就要在这行代码下加if(!infile){//...}
来处理如果读取空文件或者读取的是非数值内容(这种情况下会读取失败,infile=0)的这种异常操作。如果读取失败,infile会表现出infile前一个操作执行后的状态(infile=0)
一定注意指针的指向是不是实际的对象而非0或者不知道指向了哪。比如以下操作:
int *pi=allocate_array(elem_cnt);
if(!pi)
{
//allocate_array()没有分配到足够内存,也就没法让指针指向拟分配的内存空间
}
如果allocate_array()函数没有分配到足够内存,pi指针就会被设置为0(空指针),所以在进行pi指针操作前一定要写if(!pi){//…}来检查pi是不是0
7.2
下列函数被上题的alloc_and_ini()调用,执行失败时会发出异常:
allocate_array()//发出异常noMem
sort_array()//发出异常int
register_data()//发出异常string
写一个/多个try块,以及相应的catch子句,来处理这些异常,相应的catch字句只需要打印错误信息。
用一个try块里给三个相应的catch子句来处理三个相应的异常:
int *alloc_and_init(string file_name)
{
ifstream infile(file_name.c_str());
if(!infile)
{
return 0;
}
int elem_cnt;
infile>>elem_cnt;
if(!infile)
{
return 0;
}
try
{
int *pi=allocate_array(elem_cnt);
int elem;
int index=0;
while(infile>>elem&&index<elem_cnt)
{
pi[index++]=elem;
}
sort_array(pi,elem_cnt);
register_data(pi);
}
catch(const noMem &menFail)//noMem是一个类
{
cerr<<"错误信息";
return 0;//终止*alloc_and_init函数运行
}
catch(const &sortFail)
{
cerr<<"错误信息";
return 0;
}
catch(string ®isterFail)
{
cerr<<"错误信息";
return 0;
}
return pi;//没有任何异常被抛出,程序执行于此(意味着如果有抛出异常的情况
//相应的catch子句会接收异常并处理,然后终止该函数的运行
}
学习一下这种处理方式!
7.3
在Stack类体系里加入两个异常类型,处理”空栈弹出元素“和”满栈压入元素“这两种异常情况。
-
因为会定义处理这两种情况的异常类,供pop()和push()这两个Stack类的成员函数抛出异常,这样这两个成员函数就不用返回代表成功或失败的值(也就是不用再写
if(栈空/栈满){cerr<<.....;}
如此的操作了,pop()和push()这两个Stack类的成员函数可以从bool返回类型改为void返回类型了
这就是有异常类的好处。 -
为了让这两个Stack异常可以被完全不知情的其他组件捕获,他们应该派生自StackException(自己定义的异常类)类继承体系中,StackException异常类派生自C++标准库所提供的logic_error类详情查看这个文章,关于logic_error逻辑错误异常类的介绍
-
而logic_error异常类派生自exception这个标准库中所有异常类继承体系最根本的抽象基类。
-
exception异常类类继承体系有个名为what()的虚函数,返回const char*,用以表示为什么会有异常,而其子类(派生类)会继承what()函数并加以改造称为子类自己的what()函数。
-
举个例子,关于自己定义的异常类StackException、PopOnEmpty、PushOnFull和其基类logic_error和exception标准库里的异常类
class StackException:public logic_error{
public:
StackException(const char* what):_what(what){}
const char* what()const{return _what.c_str();}
protected:
string _what;
};
class PopOnEmpty:public StackException{
public:
PopOnEmpty():StackException("Pop on Empty Stack");
//这个属于父类只声明了带参数的构造函数
//在这种情况下,子类中的构造函数
//必须显式调用父类的带参数的构造函数!!
};
class PushOnFull:public StackException{
public:
PushOnFull():StackException("Push on Full Stack");
};
//以下任何一个catch子句都可以处理比如PushOnFull这种异常
//(栈满还要往栈里压栈)
catch(const PushOnFull &pof)
{
log(pof.what());
return;
}
catch(const StackException &stke)
{
log(stke.what());
return;
}
catch(const logic_error &lge)
{
log(lge.what());
return;
}
catch(const Exception &ex)
{
log(ex.what());
return;
}
为什么任何一个catch子句都可以处理比如PushOnFull这种异常?
因为类继承体系的关系,PushOnFull继承自StackException,StackException继承自logic_error,logic_error继承自exception。
只要抛出了PushOnFull异常,可以被四种catch子句捕获并用对应的四种异常类类对象来处理异常+终止函数运行。
踩雷清单
class StackException:public logic_error{
public:
StackException(const char* what):_what(what){}
const char* what()const{return _what.c_str();}
protected:
string _what;
};
class PopOnEmpty:public StackException{
public:
PopOnEmpty():StackException(“Pop on Empty Stack”);//👈
//这个属于父类只声明了带参数的构造函数
//在这种情况下,子类中的构造函数
//必须显式调用父类的带参数的构造函数!!!切记
};
至此,EssentialC++完结!总结这本书的学习
前期记笔记感觉还好,到后面越来越难,记笔记来让自己巩固知识点似乎变得很困难,不过还是坚持了下来。
这本书翻译稍微有点问题,有的地方翻译的感觉不是那回事,有的地方很绕。不过大体内容还不错,层层递进!(数列打天下)
我也大致了解了C++语言的类、继承、多态、模板、STL、重载、异常等特性(后续要跟上C++ primer再学一遍,给它学明白!)