C++ Primer Plus 15章 友元、异常
1.友元
前面介绍过友元成员函数,这里再补充一下友元类。
我们应该先明确一下,友元类的作用是干什么的,前面说继承的时候,继承分为公有继承,私有继承,保护继承。其中公有继承是is-a的关系,剩下两个是has-a的关系。但是总有些分类,它既不属于is-a,也不属于has-a。这个时候就要采用友元类的关系来表示它们。比如电视机和遥控器,他们既有共同的部分,又有相同的部分,我们不能用继承的方法去表示这两个物体,但可以使用友元类去表示它们,更为的合理。
定义声明:
class Tv
{
public:
friend class Remote;//这里使用friend关键字去声明Remote为友元类
....
}
class Remote
{
....
}
但有时候将整个类作为友元又太为臃肿,可能我们只需要其中某个接口或者某个实现方法,此时只需要将其中需要的函数声明为友元函数即可。但是这里将会涉及到一些声明顺序的问题,要尤其小心。
如下:
class Tv
{
friend void Remote::set_chan(Tv & t, int c);//要使这句话通过,则必须知道Remote的定义
};
//但是Remote中又有Tv对象,所以此时需要前向声明
class Tv; //forward declaration
class Remote{...};
class Tv{...};
友元关系还可以分为两种
(1)互为友元
class Tv
{
friend class Remote;
public:
void buzz(Remote & r);
...
};
class Remote
{
friend class Tv;
public:
void Bool volup(Tv & t){...}
...
};
inline void Tv::buzz(Remote & r)
{
...
}
(2)共同友元
class Analyzer; //forward declaration
class Probe
{
friend void sync(Analyzer & a, const Probe & p);
friend void sync(Probe & p, const Analyzer & a);
}
class Analyzer
{
friend void sync(Analyzer & a, const Probe & p);
friend void sync(Probe & p, const Analyzer & a);
}
2.嵌套类
从字面上很好理解,就是在一个类中,声明了另外一个类。
要注意类嵌套和前面的所述的类包含是不一样的。
包含意味着将类对象作为另一个类的成员;而对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明的类中有效。
示例:
class Queue
{
class Node
{
public:
Item item;
Node * next;
Node(const Item & i):item(i),next(0){}
};
....
}
作用域:
如果嵌套类是在另一个类的私有部分声明的,则只有后者知道它。
如果嵌套类是在另一个类的保护部分声明的,则它对于后者来说是可见的,但对于外部世界则是不可见的。派生类将知道嵌套类,并可以直接创建这种类型的对象。
如果嵌套类是在另一个类的公有部分声明的,则允许后者、后者的派生类以及外部世界使用它。
3.异常
这是C++的特性,有好处,有坏处。
(1)调用abort()
直接异常终止程序
(2)返回错误码
程序如果出错,手动返回错误
(3)异常机制
引发异常
使用处理程序捕获异常
使用try块
示例:
#include<iostream>
double hmean(double a,double b);
int main()
{
double x,y,z;
std::cout<<"Enter two numbers: ";
while(std::cin>>x>>y)
{
//从下面开始,都是程序的异常处理部分
//如果发生了异常,先跳转到throw处,处理完成后,则执行catch模块里面的代码;如果没有,则跳过
//catch执行完后将返回到while循环的开始位置
try{
z = hmean(x,y);
}
catch(const char * s)
{
std::cout<<s<<std::endl;
std::cout<<"Enter a new pair of numbers: ";
continue;
}
....
}
}
double hmean(double a,double b)
{
if(a == -b)
{
throw "bad hmean() arguments:a = -b not allowed";
}
return 2.0 * a * b/(a+b);
}
(4)将对象用作异常类型
其实和函数的使用差不多,只是代码的处理更为多一点而已。这里的代码太多,就不展示了。
(5)栈解退
现在假设函数由于出现异常而终止,则程序也将释放栈中的内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,知道找到一个位于try块中的返回地址。随后,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句。这个过程被称为栈解退。
(6)exception类
专门用于异常类的头文件
下面是几种不同类型的错误的标准异常处理类型
domain_error 值域出问题
invalid_argument 不是有效值
length_error 没有足够的空间来执行所需的操作
out_of_bounds 指示索引错误
range_error
overflow_error
underflow_error
(7)异常迷失方向
第一种情况:由异常规范引发,必须与规范列表中的某种异常匹配,如果不匹配,则发生意外异常。
第二种情况:如果异常不是在函数中引发的,则必须捕获它,如果没被捕获,则成为未捕获异常。
要确保捕获异常,可以做以下措施:
一:确保异常头文件的声明可用
#include
using namespace std;
二:设计一个替代函数,将意外异常转为bad_exception异常
void myUnexpection()
{
throw std::bad_expection();
}
三:在程序的开始位置,将意外异常操作指定为调用该函数
set_unexpected(myUnexpected);
四:将bad_exception类型包括在异常规范中,并添加如下catch块序列
double Argh(double, double) throw(out_of_bounds, bad_exception);
...
try{
x = Argh(a,b);
}
catch(out_of_bounds & ex)
{
...
}
catch(bad_exception & ex)
{
...
}
(8)异常的注意事项:动态内存分配导致的异常
示例:
void test2(int n)
{
double * ar = new double[n];
...
if(oh_no)
throw exception();
...
delete [] ar;
return;
}
由于栈解退,throw 会调用析构函数,从而释放了栈中的变量ar,导致指针未被正确释放,从而内存泄露。
4.RTTI
给程序在运行阶段确定对象的类型提供一种标准方式。
只适用于包含虚函数的类,因为虚函数可以重定义,从而确定使用同名字,但不用类型的函数。
这就是RTTI的功能。
dynamic_cast运算符
typeid运算符和type_info类
5.类型转换运算符
dynamic_cast运算符
基类向上转换
const_cast运算符
同类型转换
static_cast运算符
隐式转换
reinterpret_cast运算符
危险类型转换