15.1 友元
友元类
之前将友元函数用于类的扩展接口中,类并非只能拥有友元函数,也可以将类作为友元,友元类的所有方法都可以访问原始类的私有成员和保护成员。:
class TV{
private:
...
public:
friend class Remote; //Remote是TV类的友元类
...
}
class Remote{
public:
void set_chan(TV &t, int n){t.channel = c}
}
```c++
这样在Remote类中就可以访问TV类的私有数据。在现实中就是不仅可以在电视机上更改数据,音量,频道等等,使用遥控器也能直接更改这些信息。
### 友元成员函数
在Remote类中,可能只有一两个成员函数会直接访问TV类的成员,其他函数对调用TV类的共有接口,这种情况下,只需要**将特定的类成员成为另一个类的友元,而不必让整个类成为类的友元.:
```c++
class TV{
friend void Remote::set_chan(TV &, int);
}
然而,要使编译器能够处理这条语句,他必须知道Remote的定义,所以Remote的定义要放在TV定义的前面,而Remote方法提到了TV,所以TV要在Remote的前面,为了避免这种循环依赖,使用前向声明,在Remote的定义前加入:
class TV;
class Remote{...};
class TV{...};
其顺序不能颠倒,因为编译器在TV类的声明中看在Remote的一个方法被声明为Tv的友元之前,应该先看到Remote类的声明的set_chan的声明。
还有Remote中许多成员函数虽然不是Tv的友元,但却调用了TV的公共接口,但TV这些成员函数定义却在Remote的后面,所以,只能在Remote中声明这些使用TV公共接口的函数,然后放在TV类定义的后面,并且因为是内联函数,所以前面加上关键字inline:
class TV;
class Remote{...};
class TV{...};
//then put Remote method definitions here
其他友元关系
两个类是彼此的友元
共同友元
即一个函数是两个类的友元,需要访问两个类的数据,只需要在两个类中都声明该函数为友元函数即可。
嵌套类
将类声明放在另一个类中,在另一个类中声明的类被称为嵌套类。而在类中定义另一个类对象为数据成员称为类包含,两者进行区分。
例如,在模拟队列使,结点之前是使用结构体的,现在就可以将队列声明为模板类,然后在模板类中重新声明一个类表示结点。
异常
在编写程序时,会出现许多错误,要选择合适的方法处理这些错误。
调用abort()
例如在一个函数中,其中计算时,分母有x-y,编译时无错误,但在用户输入x,y值时,如果x=y,分母为0,会产生无穷大的数,编译器会用inf表示无穷大,但不会报错。因此,我们要使编译器在错误时,停止运行,就要调用abort函数:
#include<iostream>
#include<cstdlib> //包含头文件
using namespace std;
double aver(double a, double b);
int main(){
double x,y,z;
cout<<"please enter two numbers,i will give your the average of them:";
while(cin>>x>>y){
if((x-y)==0){ //判断x,y合法性
cout<<"the fault number!!!"<<endl;
abort(); //不合法,输出错误语句,然后结束运行。
}
else
z = aver(x,y);
cout<<"average of "<<x<<" and "<<y<<" is "<<z<<endl;
cout<<"please enter next two numbers:";
}
return 0;
}
double aver(double a, double b){
return (a+b)/(a-b);
}
返回错误码
上述异常终止可以解决问题,但有时希望运行继续,返回错误(布尔类型)即可,让用户重新输入:
#include<iostream>
#include<cstdlib>
using namespace std;
bool aver(double a, double b, double *ans);
int main(){
double x,y,z;
cout<<"please enter two numbers,i will give your the average of them:";
while(cin>>x>>y){
if(aver(x,y,&z)){
cout<<"average of "<<x<<" and "<<y<<" is "<<z<<endl;
}
else
cout<<"the flaut number!"<<endl;
cout<<"please enter next two numbers:";
}
return 0;
}
bool aver(double a, double b, double *ans){
if((a-b)==0)
return false;
else{
*ans = (a+b)/(a-b);
return true;
}
}
异常机制
异常提供了将控制权从程序的一个部分传递到另一部分的途径,对异常的处理有三部分组成:
- 引发异常
- 使用处理程序捕获异常
- 使用try块
程序出现问题时引发异常,throw关键字表示引发异常,后面的值指出了异常的特征。异常类型通常为类类型,或者字符串,或者其他
程序使用异常处理程序来捕获异常,异常处理程序要位于要处理问题的程序中。catch关键字表示捕获异常,随后是位于括号中的类型说明,异常特征的类型,然后花括号,指出要采取的措施,当异常被触发时,程序将根据异常特征匹配到该位置执行。
try表示可能发生异常的代码块,后面跟多个或一个catch块。
#include<iostream>
#include<cstdlib>
using namespace std;
double aver(double a, double b);
int main(){
double x,y,z;
cout<<"please enter two numbers,i will give your the average of them:";
while(cin>>x>>y){
try{
z = aver(x,y);
}
catch(const char *s){ //捕获异常,然后执行相应代码
cout<<s<<endl;
cout<<"please enter the right numbers:";
continue;
}
cout<<"average of "<<x<<" and "<<y<<" is "<<z<<endl;
cout<<"please enter next two numbers:";
}
return 0;
}
double aver(double a, double b){
if((a-b)==0)
throw "bad input!,(x-y) must not is the 0!!! "; //引发异常,类型为字符串
return (a+b)/(a-b);
}
- 如果函数引发异常,但没有try块或者匹配的捕获类型,程序将自动调用abort()函数。
异常类型最主要是类对象,这样处理方便,这样catch捕获异常时参数是类的引用就好。
栈解退
假如try块没有直接调用引发异常的函数,而是调用了对引发异常的函数进行调用的函数,则程序流程将从引发异常的函数跳到包含try块和处理程序的函数。
如果出现异常,程序将释放栈中的内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,知道找到一个位于try块中的返回地址,随后,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句,这个过程------栈解退
其他异常特性
throw-catch机制类似与函数参数和函数返回机制,但有不同之处,函数的返回语句将控制权返回到调用该函数的函数,但throw语句将控制权向上返回到第一个这样的函数:包括能够捕获相应异常的try-catch。
虽然throw语句生成副本,但代码中也要使用引用:基类引用可以执行派生类对象。因此就算有很多个通过继承得到的异常类型,列出一个基类引用,就可以全部匹配。并且捕捉的catch语句顺序要与throw语句相反,因为如果通过继承关联起来的异常类型,一如基类的catch捕获按照顺序放在第一个,那所有的异常满足条件,都会被第一个捕获,所以需要反顺序,层层筛选。
exception类
exception定义了exception类,可以将他用作其他异常类的基类,代码可以引发exception异常,有一个名为what()的虚拟成员函数,它返回一个字符串,这是一个虚方法,可以在派生的异常类中重新定义他
stdexcept异常类:头文件stdexcept定义了其他几个异常类,logic_error和runtime_error类,都是从exception派生而来的
bad_alloc和new:使用new导致的内存分配问题,之前,当无法分配请求的内存量时,new返回一个空指针,经过这个异常类,可以返回某个字符串
总之,异常与类和继承可以相互结合,构成一个完整的系统。
异常何时会迷失方向
引发异常,但没有相匹配的捕获类型,称为意外异常。
异常不是在函数中引发的,则必须捕获它,如果没有被捕获,则称为未捕获异常,会导致程序终止。
RTTI
基类的指针可以指向其派生类的对象,RTTI可让指针明确所指向的对象类别。
- dynamic_cast运算符将使用一个指针基类的指针生成一个指向派生类的指针,否则,该运算符返回一个空指针
- typeid运算符返回一个指出对象的类型的值
- type_info结构存储了有关特定的类型的值
A *pm = dynamic_cast<A *>(pi); //如果可以将pi指针类型转换为指向A的指针类型
这种只能向上转换,换为基类。
typeid(A)==typeid(*pi); //判断pi指针的类型
类型转换运算符
第一种就是上面的 dynamic_cast运算符;
第二种,const_cast:可以将变量的const属性去掉,即在大部分情况下都是不可修改的,但某个特殊情况,使用该运算符将const去掉,使其可以修改。
第三种,static_cast:也是指针类型,不过与第一种不同,此时使用可以向下转换。
最后一种,reinterpret_cast:有关结构体类型修改。