第十五章.友元,异常和其他(P603-654)

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:有关结构体类型修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值