l 友元
类并非只能拥有友元函数,也可以将类作为友元
eg:
class Tv
{
public:
friend class Remote;
…
};
class Remove
{…};
友元声明可以位于公有、私有或保护部分,其所在位置无关紧要。
也可以选择仅让特定的类成员成为另一个类的友元。
eg:
class Tv
{
friend void Remote::set_chan(Tv &t,int c);
…
};
这时候类头文件,要使用前向声明
class Tv;
class Remote{…};
class Tv{…};
并且在Remote类方法中,只声明原型,定义要放在class Tv之后。
两个类可以互相成为对方的友元。也可以拥有共同的友元函数。
l 嵌套类
一个类的声明可以放在另一个类中,在另一个类中声明的类被称为嵌套类。
对类进行嵌套与包含不现。包含意味着将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义一种类型。该类型仅在包含嵌套类声明的类中有效。
对类进行嵌套通常是为了帮助实现另一个类,并避免名称冲突。
eg
class Queue
{
private:
class Node
{…};
…
};
l 异常
程序有时会遇到运行阶段,导致程序无法正常运行下去。
可以调用abort();
abort()函数原型位于头文件cstdlib(stdlib.h)中。共典型实现是向标准错误流发送消息abort program termination,然后终止程序。
C++异常是对程序运行过程中发生的异常情况的一种响应。异常提供了将控制权从程序从程序的一个部分传递到另一部分的途径。对异常的处理有3个组成部分
n 引发异常
n 捕获异常
n 使用try块
eg.
#include <iostream>
double hmean(double a, double b);
int main()
{
double x, y, z;
std::cout << "Enter two numbers: ";
while (std::cin >> x >> y)
{
try { // start of try block
z = hmean(x,y);
} // end of try block
catch (const char * s) // start of exception handler
{
std::cout << s << std::endl;
std::cout << "Enter a new pair of numbers: ";
continue;
} // end of handler
std::cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << std::endl;
std::cout << "Enter next set of numbers <q to quit>: ";
}
std::cout << "Bye!\n";
return 0;
}
double hmean(double a, double b)
{
if (a == -b)
throw "bad hmean() arguments: a = -b not allowed";
return 2.0 * a * b / (a + b);
}
try块内为测试模块, throw引发异常,catch捕获异常。
如果函数引发了异常,而 没有try块或没有匹配的处理程序时,将会发生什么情况。在默认情况下,程序最终调用abort()函数,但可以修改这种行为。
将对象用作异常类型
通常,引发异常的函数将传递一个对象。
eg:
#include <iostream>
class bad_hmean
{
private:
double v1;
double v2;
public:
bad_hmean(double a = 0, double b = 0) : v1(a), v2(b){}
void mesg();
};
inline void bad_hmean::mesg()
{
std::cout << "hmean(" << v1 << ", " << v2 <<"): "
<< "invalid arguments: a = -b\n";
}
class bad_gmean
{
public:
double v1;
double v2;
bad_gmean(double a = 0, double b = 0) : v1(a), v2(b){}
const char * mesg();
};
inline const char * bad_gmean::mesg()
{
return "gmean() arguments should be >= 0\n";
}
//error4.cpp ?using exception classes
#include <iostream>
#include <cmath> // or math.h, unix users may need -lm flag
#include "exc_mean.h"
// function prototypes
double hmean(double a, double b) throw(bad_hmean);
double gmean(double a, double b) throw(bad_gmean);
int main()
{
using std::cout;
using std::cin;
using std::endl;
double x, y, z;
cout << "Enter two numbers: ";
while (cin >> x >> y)
{
try { // start of try block
z = hmean(x,y);
cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << endl;
cout << "Geometric mean of " << x << " and " << y
<< " is " << gmean(x,y) << endl;
cout << "Enter next set of numbers <q to quit>: ";
}// end of try block
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
cout << "Try again.\n";
continue;
}
catch (bad_gmean & hg)
{
cout << hg.mesg();
cout << "Values used: " << hg.v1 << ", "
<< hg.v2 << endl;
cout << "Sorry, you don't get to play any more.\n";
break;
} // end of catch block
}
cout << "Bye!\n";
return 0;
}
double hmean(double a, double b) throw(bad_hmean)
{
if (a == -b)
throw bad_hmean(a,b);
return 2.0 * a * b / (a + b);
}
double gmean(double a, double b) throw(bad_gmean)
{
if (a < 0 || b < 0)
throw bad_gmean(a,b);
return std::sqrt(a * b);
}
堆栈解退
程序调用函数时,将返回地址放到堆栈中。函数的参数,和函数创建的新的自动变量也将添加到堆栈中。当函数结束时,程序跳到该函数被调用时存储的地址处,同时堆栈顶端的元素被释放。函数通常返回到调用它的函数,同时每个函数都在结束时释放其自动变量。如果自动变量是类对象,则类的析构函数将被调用。
假设函数出现了异常。则程序也将释放堆栈中的内存。但不会在释放堆栈的第一个返回地址后停止,而是继续释放堆栈,直到找到一个位于try块中的返回地址。随后控制权转到块尾的异常处理程序,而不是函数调用后面的第一条语句。这个过程被称为堆栈解退。
有了堆栈解退这种特性,则引发异常后,对于中间函数调用放在堆栈中的自动类对象,其析构函数被调用。
eg
class problem {…};
void super() throw(problem)
{
…
if(oh_no)
{
problem oops;
throw oops;
…
}
…
}
try{
super();
}
catch(problem &p)
{}
当发生异常时,编译器总是临时创建一个临时拷贝,即使异常规范和catch块中指定的是引用。在此处catch()选择引用的原因不是为了提高效率,而是可以用problem的引用指向以problem为基类的派生类对象。
exception
exception头文件定义了exception类,它有一个名为what()的虚拟成员函数,用来返回一个字符串。
头文件stdexcept定义了其它几个异常类。它定义了logic_error和runtime_error类,它们都以公有方式从exception派生而来。
以logic_error为基类派生出以下类
domain_error;invalid_argument;length_error;out_of_bounds
发生的以上错误是逻辑错误,可以通过合理的编程避免。
以runtime_error为基类派生出以下类
range_error;overflow_error;underflow_error;
runtime_error异常描述了可能在运行期间发生但难以预计和防范的错误。
使用new可能出现内存分配的问题。当new在无法满足内存请求时返回一个空指针,或引发bad_alloc异常。头文件new中包含了bad_alloc类的声明,它是从exception类的公有派生而来的。
异常何时会迷失方向
异常被引发后,两种情况会引发问题:意外异常和未捕获异常
意外异常:在异常规范的函数中引发,但与规范列表中的某种异常不匹配。它会导致程序异常终止。
因为没有try块或没有匹配的catch块,将出现未捕获异常。
未捕获异常首先调用terminate(),默认情况下,terminate()调用abort(). 可以调用set_terminate()函数修改默认的调用函数。set_terminate()和terminate()都在头文件exception中声明的。
typedef void(*terminate_handler)();
terminate_handler set_terminate(terminate_handler f) throw();
void terminate();
意外异常默认调用unexpected()函数,这个函数调用terminate()函数。可以用set_unexpected()函数修改调用的默认函数。
unexpected_handler可以调用terminate(),abort()或exit()来终止程序,或引发异常。
引发的异常就是bad_exception。
异常注意事项
异常和动态内存不能总是协同工作。
如果动态分配的内存是在堆栈内,则内存可以被正确的管理,这归功于堆栈解退。
如果内存是由new分配在堆内,则引发异常后,内存的指针被释放,但内存并不会释放。这需要在catch()模块中做一些处理。
应有节制地使用异常特性。
l RTTI
RTTI(Runtime Type Identification),是运行阶段类型识别的简称。RTTI旨在为程序在运行阶段确定对象的类型提供一种标准方式。
dynamic_cast操作符是最常用的RTTI组件,它可以判断是否能安全地将对象的地址赋给选定类型的指针。
eg:
class Grand{…};
class Superb:public Grand{…};
class Magnificent:public Superb{…};
Grand *pg=new Grand;
Superb *pm=dynamic_cast<Superb *>(pg);
如果指针pg可以安全地转换为Superb*,操作符将返回对象的地址,否则返回一个空指针。
用法:dynamic_cast<Type *>(pt)
pt为指针
也可以将dynamic_cast用于引用,其用法稍微有点不同。
dynamic_cast<Type &>(pt)
pt为类对象
因为没有空指针对应的引用值,因此无法使用特殊的引用值来指示失败。当请求不正确时,dynamic_cast将引发类型为bad_cast的异常。这种异常从exception类派生而来的。它在头文件typeinfo中定义
eg:
#include <typeinfo>
…
try{
Superb &rs=dynamic_cast<Superb &>(rg);
…
}
catch(bad_cast &){
…
};
typeid操作符使得能够确定两个对象是否为同种类型
eg:
typeid(Magnificent)==typeid(*pg)
如果pg是一个空指针,程序将引发bad_typeid异常。该异常类型是从exception类派生而来的,在头文件typeinfo中声明的。
typeid操作符返回一个对type_info对象的引用,其中,type_info是在头文件typeinfo中定义的一个类。
type_info类包含一个name()成员,该函数通常返回一类的名称。
eg:
cout<<”Now processing type”<<typeid(*pg).name()<<”.\n”;
l 类型转换操作符
4个类型转换操作符
u dynamic_cast
u const_cast
u static_cast
u reinterpret_cast
const_cast操作符用于执行只有一种用途的类型转换,即改变值为const或volatile.句法:
const_cast<type-name>(expression)
eg:
High bar;
const High *pbar=&bar;
High *pb=const_cast<High*>(pbar);
pb和pbar只有const或volatile特征不同,其它的均应相同。
static_cast用于类型转换
只有能进行转换的类调用static_cast才能转换,否则出错
假设high是low的基类
High bar;
Low blow;
High *pb=static_cast<High *>(&blow);上行转换
Low *pl=static_cast<Low *>(&bar);下行转换
reinterpret_cast,它的转换依赖于底层编程技术,是不可移植的。