友元类、友元成员函数和嵌套类是在其他类中声明的类;新增的特性:异常、运行阶段类型识别(RTTI)和改进后的类型转换控制。C++异常处理提供了处理特殊情况的机制,如果不对其进行处理,将导致程序终止。RTTI是一种确定对象类型的机制。新的类型转换运算符提高了类型转换的安全性。
友元
可以将类作为友元,在这种情况下,友元类的所有方法都可以访问原始类的私有成员和保护成员。??也可以只将特定的成员函数指定为另一个类的友元。
当函数需要访问两个类的私有数据时,这就需要使用友元。
例如,假定有一个Probe类和一个Analyzer类,前者表示某种可编程的测量设备,后者表示某种可编程的分析设备。这两个类都有内部时钟,且希望它们能够同步,则应包含下述代码行:
class Analyzer;
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);
…
};
inline void sync(Analyzer &a, const Probe &p)
{
…
}
inline void sync (Probe &p, const Analyzer &a)
{
…
}
嵌套类
概念
在C++中,可以将类声明放在另一个类中。在另一个类中声明的类被称为嵌套类(nested class),它通过提供新的类型类作用域来避免名称混乱。
包含类的成员函数可以创建和使用被嵌套类的对象;而仅当声明位于公有部分,才能在包含类的外面使用嵌套类,而且必须使用作用域解析运算符。
对类进行嵌套与包含并不相同。包含意味着将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明中的类有效。
嵌套类的声明位置决定了嵌套类的作用域,即它决定了程序的哪些部分可以创建这种类的对象。和其它类一样,嵌套类的共有部分、保护部分和私有部分控制了对类成员的访问。在哪些地方可以使用嵌套类以及如何使用嵌套类,取决于作用域和访问控制。
作用域
由于嵌套类的作用域为包含它的类,因此在外部世界使用它时,必须使用类限定符。嵌套结构和枚举的作用域与此相同,很多程序员都使用公有枚举来提供可供客户程序员使用的类常数。
声明位置 | 包含它的类是否可以使用它 | 从包含它的类派生而来的类是否可以使用它 | 在外部是否可以使用 |
---|
私有部分 | 是 | 否 | 否 | 保护部分 | 是 | 是 | 否 | 公有部分 | 是 | 是 | 是,通过类限定符来使用 |
访问控制
类声明的位置决定了类的作用域或可见性。类可见后,访问控制规则(公有、保护、私有、友元)将决定程序对嵌套类成员的访问权限。
异常
调用abort()
abort() 函数的原型位于头文件cstdlib(or stdlib.h) 中,其典型实现是向标准错误流(即cerr使用的错误流)发送消息abnormal program termination(程序异常终止),然后终止程序。它还返回一个随实现而异的值,告诉操作系统(若程序是由另一个程序调用的,则告诉父进程),处理失败。abort() 是否刷新文件缓冲区(用与存储读写到文件中的数据的内存区域)取决于实现。
返回错误码
可使用指针参数或引用参数来将值返回给调用程序,并使用函数的返回值来指出成功还是失败。
异常机制
C++异常是对程序运行过程中发生的异常情况的一种响应。异常提供了将控制权从程序的一个部分传递到另一个部分的途径。对异常的处理有3个组成部分:
程序使用异常处理程序来捕获异常,异常处理程序位于要处理问题的程序中。catch 关键字表示捕获异常。处理程序以关键字catch 开头,随后是位于括号中的类型声明,它指出了异常处理程序要响应的异常类型;然后是一个用花括号括起的代码块,指出要采取的措施。catch 关键字和异常类型用作标签,指出当异常被引发时,程序应跳到这个位置执行。异常处理程序也被称为catch 块。
try 块标识其中特定的异常可能被激活的代码块,它后面跟一个或多个catch 块。try 块是由关键字try 指示的,关键字try 的后面是一个由花括号括起的代码块,表明需要注意这些代码引发的异常。如:
#include<iostream>
double hmean2(double a, double b);
void testErr3()
{
double x, y, z;
std::cout << "Enter two numbers: ";
while (std::cin >> x >> y)
{
try{
z = hmean2(x, y);
}
catch (const char* s)
{
std::cout << s << std::endl;
std::cout << "Enter a new pair of numbers: ";
continue;
}
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";
}
double hmean2(double a, double b)
{
if (a == -b)
throw "bad hmean() arguments: a=-b not allowed";
return 2.0 * a * b / (a + b);
}
异常类型可以是字符串或其它C++类型(通常为类类型);执行throw 语句类似于执行返回语句,因为它也将终止函数的执行;但throw 不是将控制权返回给调用程序,而是导致程序沿着函数调用序列后退,直到找到包含try 块的函数。
catch 块点类似于函数定义,但并不是函数定义。关键字catch 表明这是一个处理程序,而char*s 则表明该处理程序与字符串异常匹配。执行完try 块中的语句后如果没有引发任何异常,则程序跳过try 块后面的catch 块,直接执行处理程序后面的第一条语句。
在默认情况下,如果函数引发了异常,而没有try 块或没有匹配的处理程序时,程序最终将调用abort() 函数。
将对象用作异常类型
通常,引发异常的函数将传递一个对象,这可以使用不同的异常类型来区分不同的函数在不同的情况下引发的异常。另外,对象可以携带信息,程序员可以根据这些信息来确定引发异常的原因。 C++11支持一种特殊的异常规范:您可以使用新增的关键字noexcept 指出函数不会引发异常: double marm() noexcept; //marm() don’t throw an exception 还有运算符noexcept() ,它判断其操作数是否会引发异常。
栈解退
假设try 块没有直接调用引发异常的函数,而是调用了对引发异常的函数进行调用的函数,则程序流程将从引发异常的函数跳到包含try 块和处理程序的函数。
函数调用的处理
C++ 通常通过将信息放在栈中来处理函数调用,即程序将调用函数的指令的地址(返回地址)放到栈中。当被调用的函数执行完毕后,程序将使用该地址来确定从哪里开始继续执行。另外,函数调用将函数参数放到栈中。在栈中,这些函数参数被视为自动变量。如果被调用函数创建了新的自动变量,则这些变量也将被添加到栈中。如果被调用函数调用了另一个函数,则后者的信息将被添加到栈中,以此类推。当函数结束时,程序流程将跳转到该函数被调用时存储的地址处,同时栈顶的元素被释放。因此,函数通常都返回到调用它的函数,依此类推,同时每个函数都在结束时自动释放其自动变量。如果自动变量是类对象,则类的析构函数(如果有的话)将被调用。
栈解退
现在假设函数由于出现异常(而不是由于返回)而终止,则程序也将释放栈中的内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try 块中的返回地址。
随后,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句。这个过程被称为栈解退。
引发机制的一个重要特性是,和函数返回一样,对于栈中的自动类对象,类的析构函数将被调用。然而,函数返回仅仅处理该函数放在栈中的对象,而throw 语句则处理try 块和throw 之间整个函数调用序列放在栈中的对象。如果没有栈解退这种特性,则引发异常后,对于中间函数调用放在栈中的自动类对象,其析构函数将不会被调用。
如果有一个异常类继承层次结构,应这样排列catch 块:将捕获位于层次结构最下面的异常类的catch 语句放在最前面,将捕获基类异常的catch 语句放在最后面。
exception 类
C++异常的主要目的是为设计容错程序提供语言级支持,即异常使得在程序设计中包含错误处理功能更容易,异常的灵活性和相对方便性激励着程序员在条件允许的情况下在程序设计中加入错误处理功能。
exception头文件定义了exception 类,C++可以把它用作其他异常类的基类。有一个名为what() 的虚拟成员函数,它返回一个字符串,该字符串的特征随实现而异。由于这是一个虚方法,因此可以在从exception派生而来的类中重新定义它:
#include<exception>
class bad_hmean : public std::exception
{
public:
const char* what( ) { return “bad arguments to hmean()” ; }
…
};
如果不想以不同的方式处理这些派生而来的异常,也可在同一个基类处理程序中捕获它们:
try {
…
}
catch(std::exception & e)
{
cout << e.what() <<endl;
…
}
C++库定义了很多基于exception的异常类型。
stdexcept 异常类
头文件stdexcept 定义了其他几个异常类。该文件定义了logic_error 和runtime_error 类,这两个类被用作两个派生类系列的基类。它们都是以公有方式从exception 派生而来:
class logic_error: public exception{
public:
explicit logic_error( const string & what_arg);
…
};
这些类的构造函数接受一个string对象作为参数,该参数提供了方法what() 以C-风格字符串方式返回的字符数据。
-
异常类系列logic_error 描述了典型的逻辑错误,每个类名指出了它报告的错误类型: domain_error ; //函数参数不在定义域 invalid_argument ; //指出给函数传递了一个错误值 length_error ; //指出没有足够的空间来执行所需的操作 out_of_bounds ; //用于指示索引错误 -
runtime_error 异常系列描述了可能在运行期间发生但难以预计和防范的错误: range_error ; //计算结果可能不在函数允许的范围之内,但没有发生上溢或下溢错误,可使用range_error overflow_error ; //上溢错误 underflow_error ; //下溢错误在浮点数计算中(计算结果比浮点类型可以表示的最小非零值还小时)
bad_alloc 异常和new
对于使用new 导致的内存分配问题,C++处理方式是让new 引发bad_alloc 异常。头文件new 包含bad_alloc 类的声明,它是从exception 类公有派生而来。 例如:
#include<iostream>
#include<new>
#include<cstdlib>
using namespace std;
struct Big
{
double stuff[20000];
};
void testErr6()
{
Big* pb;
try {
cout << "Trying to get a big block of memory:\n";
pb = new Big[10000];
cout << "Got past the new request: \n";
}
catch (bad_alloc& ba)
{
cout << "Caught the exception!\n";
cout << ba.what() << endl;
exit(EXIT_FAILURE);
}
cout << "Memory successfully allocated\n";
pb[0].stuff[0] = 4;
cout << pb[0].stuff[0] << endl;
delete[]pb;
}
空指针和new
C++标准提供了一种在失败时返回空指针的new,用法如下:
int *pi = new (std::nothrow) int;
int *pa = new (std::nowthrow) int[500];
使用这种new,可将上面程序的核心代码改为:
Big *pb;
pb = new (std::nothrow) Big[10000];
if(pb==0)
{
cout<<”Could not allocate memory. Bye\n”;
exit (EXIT_FAILURE);
}
-
异常、类和继承以三种方式相互关联。可以像标准C++库那样,从一个异常类派生出另一个;可以在类定义中嵌套异常类声明来组合异常;这种异常嵌套声明本身可被继承,还可用作基类。 -
异常被引发后,如果它是在带异常规范的函数中引发的,则必须与规范列表中的某种异常匹配(在继承层次结构中,类类型与这个类及其派生类的对象匹配),否则称为意外异常。在默认情况下,这将导致程序异常终止;如果不是在函数中引发的(或者函数没有异常规范),则必须捕获它。如果没被捕获(在没有try 块或没有匹配到catch 块时将出现这种情况),则异常被称为未捕获异常。在默认情况下,这将导致程序异常终止。 未捕获异常不会导致程序立刻异常终止。相反,程序将先调用函数terminate() ,在默认情况下,terminate() 调用abort() 函数。 -
unexpected_handler 函数可以:
- 通过调用
terminate() (默认行为)、abort() 或exit() 来终止程序 - 引发异常
引发异常的结果取决于unexpected_handler函数所引发的异常以及引发意外异常的函数的异常规范: - 如果新引发的异常与原来的异常规范匹配,则程序将从那里开始进行正常处理,即寻找与新引发的异常匹配的catch块。基本上,这种方法将用预期的异常取代意外异常。
- 如果新引发的异常与原来的异常规范不匹配,且异常规范中没有包括std::bad_exception类型,则程序将调用terminate()。bad_exception是从exception派生而来的,其声明位于头文件exception中;
- 如果新引发的异常与原来的异常规范不匹配,且原来的异常规范中包含了std::bad_exception类型,则不匹配的异常将被std::bad_exception异常所取代。
总之,要捕获所有的异常,则可以这样做: 首先确保异常头文件的声明可用: #include<exception> using namespace std; 然后,设计一个替代函数,将意外异常转换为bad_exception 异常,该函数原型如下:
void myUnexpected()
{
throw std::bad_exception();
}
仅使用throw ,而不指定异常将导致重新引发原来的异常。如果异常规范中包含了这种类型,则该异常将被bad_exception 对象所取代。接下来在程序的开始位置,将意外异常操作指定为调用该函数: 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)
{
…
}
RTTI(运行阶段识别,Runtime Type Identification)
RTTI旨在为程序在运行阶段确定对象的类型提供一种标准方式。
RTTI的工作原理
C++有3个支持RTTI的元素
- 如果可能的话,
dynamic_cast 运算符将使用一个指向基类的指针来生成一个指向派生类的指针;否则,该运算符返回0—空指针。 typeid 运算符返回一个指出对象的类型的值type_info 结构存储了有关特定类型的信息 只能将RTTI用于包含虚函数的类层次结构,原因在于只有对于这种类层次结构,才应该将派生对象的地址赋给基类指针。
通常,如果指向的对象(*pt )的类型为Type或者是从Type直接或间接派生而来的类型,则下面的表达式将指针pt转换为Type类型的指针: dynamic_cast<Type *> (pt) 否则,结果为0,即空指针。
typeid 运算符和type_info 类
typeid 运算符使得能够确定两个对象是否为同种类型,它与sizeof 有些相像,可以接受两种参数:类名、结果为对象的表达式; typeid 运算符返回一个对type_info 对象的引用,type_info 是在头文件typeinfo 中定义的一个类。type_info 类重载了== 和!= 运算符,以便可以使用这些运算符来对类型进行比较。
类型转换运算符
4个类型转换运算符:
dynamic_cast
语法: dynamic_cast <type-name> (expression) ,这使得能够在类层次结构中进行向上转换,而不允许其他转换。
const_cast
const_cast 运算符用于执行只有一种用途的类型转换,即改变值为const 或volatile ,其语法与dynamic_cast 运算符相同:const_cast<type-name>(expression) 。除了const 或volatile 特征可以不同外,type_name和expression的类型必须相同。
static_cast
static_cast 运算符的语法与其他类型转换运算符相同:static_cast<type-name>(expression) ,仅当type_name可被隐式转换成expression所属的类型或expression可被隐式转换为tyep_name所属的类型时,上述转换才是合法的。
reinterpret_cast
reinterpret_cast 运算符用于天生危险的类型转换。它不允许删除const。语法: reinterpret_cast<type-name>(expression)
|