C++ primer Plus 第十五章 友元、异常和其他

15.1友元

类并非只有友元函数,还可以有友元类,友元类的所有方法都可以访问原始类的私有成员和保护成员。

下面我们通过一个例子来讲解一下友元类

定义TV类和Rmote类,分别作为电视机和遥控器的类

由于遥控器可以控制电视机,即遥控器可以访问电视机的成员,所以要将Rmote类声明为TV的友元类,但是如果只是将Rmote类的一个成员函数声明为友元函数,则需要知道Rmote类的声明,所以TV类应该在Rmote类后声明,但是在Rmote类的声明中,有些成员函数会引用TV类的对象,这时候需要在Rmote类前对TV类进行名称声明(即声明TV 是一个类),这种做法叫做前向引用声明。

#include<iostream>
using namespace std;
class Tv;
class Remote
{
    int mode;
public:
    enum State{off,on};
    void onoff(Tv &t);
};
class Tv
{
    int state;
public:
    friend void Remote::onoff(Tv &t);//将Remote的onoff函数声明为Tv的友元函数
    enum State{off,on};
    void onoff(){state = (state == on)?off:on;}
};

如果我们希望电视机接受遥控器指令后,会返回信息给遥控器,遥控器会发出嗡嗡声作为回应。

这时候可以将Tv和Rmote类互为友元类,但是要注意如果Tv类声明在前的话,对于Tv类中使用Rmote类对象的方法的定义,需要在Remote声明之后。

例如

这种使用内联函数的,即使用了Tv类的具体成员的内联函数,要使用inline关键字 在Tv类声明的后面定义。

 

15.3异常

 

15.3.1调用abort()

abort函数位于cstdlib头文件里,其典型实现是向标准错误流即cerr发送程序异常终止,然后终止程序。

也可以使用exit(),该函数会刷新文件缓冲区,但不显示消息。

#include<iostream>
#include<cstdlib>
using namespace std;
double heam(double a,double b);
void fun()
{
    double a,b;
    cin>>a>>b;
    double result = heam(a,b);//除运算
    cout<<result<<endl;
}
double heam(double a,double b)
{
    if(b==0)
    {
        cout<<"error"<<endl;
        abort();//也可以用exit(0)
    }
}
int main()
{
    fun();
}

如果b==0的话说明发生错误了,执行abort,会直接结束程序,而不是结束函数。

 

15.3.3异常机制

对异常的处理分为三个部分:

引发异常;

使用处理程序捕获异常;

使用try块;

 

try块标识其中特定的异常可能被激活的代码块,它后面跟一个或多个catch块

catch关键字表示捕获异常,处理程序以关键字catch开头,随后是位于括号中的类型声明,它指出了异常处理程序要响应的异常类型,然后是一个花括号括起来的代码块,指出要采取的措施。

throw 类似于return,它将终止函数的执行,并返回相应的值;但throw不是将控制权返回给调用程序,而是导致程序函数调用序列回退,一直到包含try块的那个函数,并将返回值与catch的异常类型比较,以执行特定的代码块,这个过程叫做栈解退。

例如:

#include<iostream>
#include<cstdlib>
using namespace std;
double heam(double a,double b)
{
    if(b==0)
    {
        throw "Error!";
    }
}
void fun()
{
    double a,b,result;
    cin>>a>>b;
    try
    {
        result = heam(a,b);//除运算
    }
    catch(const char *s)//会将throw返回的那个字符串与s的类型匹配,所以执行这异常处理的代码块
    {
        cout<<s<<endl;
        abort();
    }
    cout<<result<<endl;
}

int main()
{
    fun();
}

15.3.4将对象用作异常的类型

 

我们可以手动定义异常的类,为不同异常创建不同的类,以便于在多个catch里找到应执行的代码块。

15.3.5异常规范

double harm(double a) throw(bad_thing);
double marm(double) throw();

其中throw()部分就是异常规范,它可能是出现在函数原型和函数定义中,可能包含类型列表。

这项功能不推荐使用,仅了解即可。

 

15.3.6栈解退

 

1.在try的语句块内声明的变量在外部是不可以访问的,即使是在catch子句内也不可以访问。 
 
2.栈解退(寻找异常处理(exception handling)代码)

栈解退会沿着嵌套函数的调用链不断查找,知道找到了已抛出的异常匹配的catch子句。如果在最后还是没有找到对应的catch子句的话,则退出主函数后查找过程终止,程序调用标准函数库的terminate()函数,终止该程序的执行

1.当一个exception被抛出的时候,控制权会从函数调用中释放出来,并需找一个可以处理的catch子句
2.对于一个抛出异常的try区段,程序会先检查与该try区段关联的catch子句,如果找到了匹配的catch子句,就使用这个catch子句处理这个异常。
3.没有找到匹配的catch子句,如果这个try区段嵌套在其他try区段中,则继续检查与外层try匹配的catch子句。如果仍然没有找到匹配的catch子句,则退出当前这个主调函数,并在调用了刚刚退出的这个函数的其他函数中寻找。

3. catch子句的查找: 
 
1.catch子句是按照出现的顺序进行匹配的(以例2来说,异常先会匹配catch(exception e)子句,然后在匹配 catch (Exception e)子句,一步一步的栈展开)。在寻找catch子句的过程中,抛出的异常可以进行类型转换,但是比较严格:
2.允许从非常量转换到常量的类型转换(权限缩小)
3.允许从派生类到基类的转换。
4.允许数组被转换成为指向数组(元素)类型的指针,函数被转换成指向该函数类型的指针(降级问题)
5.标准算术类型的转换(比如:把bool型和char型转换成int型)和类类型转换(使用类的类型转换运算符和转换构造函数)。


15.5.7其他异常特性

1.如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止

2.一般在异常抛出后资源可以正常被释放,但注意如果在类的构造函数中抛出异常,系统是不会调用它的析构函数的,处理方法是:如果在构造函数中要抛出异常,则在抛出前要记得删除申请的资源。

3.异常处理仅仅通过类型而不是通过值来匹配的,所以catch块的参数可以没有参数名称,只需要参数类型。

4.函数原型中的异常说明要与实现中的异常说明一致,否则容易引起异常冲突。

5.应该在throw语句后写上异常对象时,throw先通过Copy构造函数构造一个新对象,再把该新对象传递给 catch.  

注:那么当异常抛出后新对象如何释放?

异常处理机制保证:异常抛出的新对象并非创建在函数栈上,而是创建在专用的异常栈上,因此它才可以跨接多个函数而传递到上层,否则在栈清空的过程中就会被销毁。所有从try到throw语句之间构造起来的对象的析构函数将被自动调用。但如果一直上溯到main函数后还没有找到匹配的catch块,那么系统调用terminate()终止整个程序,这种情况下不能保证所有局部对象会被正确地销毁。
6.catch块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常扑获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获。

7.编写异常说明时,要确保派生类成员函数的异常说明和基类成员函数的异常说明一致,即派生类改写的虚函数的异常说明至少要和对应的基类虚函数的异常说明相同,甚至更加严格,更特殊。
 

15.4RTTI以及类型转换运算符

RTTI是运行阶段类型识别的简称。

RTTI包含dynamic_cast、typeid和type_info

类型转换运算符包含 dynamic_case const_cast static_cast  reinterpret_cast.

1.dynamic_cast运算符:最常用的RTTI组件

Superb *pm=dynamic_cast<Superb *>(pg);

若pg的类型能够被安全地转化为Superb*,如果可以,返回对象的地址,否则,返回一个空指针。

dynamic_cast<Type*>(pt);正确,返回Type*,否则,返回0。

2.typeid使得能够确定两个对象是否为同种类型,可接受两个参数:类名和结果为对象的表达式。返回值为type_info对象的引用。

typeid(Magnificent)==typeid(*pg);

pg为空指针会抛出异常bad_typeid。type_info含有name()函数,返回类名。

3.const_cast<type-name> (expression) 转变为const或者volatile类型,typename与expression类型相同

4.static_cast<type-name>(expression) type-name转化为expression或者expression转化为type-name时均可使用。枚举/整型互转,double/int互转。

5.reinterpret_cast<type-name>(expression)

函数指针不可转化为数据指针,指针不能转为整型或者浮点

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章