15.1 友元
类并非只能拥有友元函数,也可以将类作为友元。
15.1.1 友元类
友元类可以位于公有、私有或保护部分,其所在位置无关紧要。友元类的成员函数与全局函数能访问该类的私有成员。
class TV
{
public:
friend class Remote;
...
};
15.1.2 友元成员函数
若只需要类中某一个或两个函数访问另一个类的私有成员,则只需将此类函数设置为另一个的友元函数。
class TV
{
friend void Remote::set_chan(TV& t, int c);
...
};
然而,要使编译器能够处理上述语句,它必须知道Remote的定义。否则,它无法知道Remote是一个类,而set_chan是这个类的方法。这意味着应将Remote的定义放在Tv的定义前面。Remote的方法提到了TV对象,而这意味着TV定义应当位于Remote定义前面。避开这种循环依赖的方法是,使用前向声明(forward declaration)。代码如下:
class Tv; // forward declaration
class Remote {...}; // definition
class Tv {...}; // definition
内联函数的链接性是内部的,意味着函数定义必须在使用函数的文件中。即将内联函数定义与函数声明放在同一个文件中。
15.1.3 其他友元关系
若让类成为彼此的友元。需要记住一点,对于使用Remote对象的Tv方法,其原型可在Remote类声明之前声明,但必须在Remote类声明之后定义,以便编译器有足够的信息来编译方法。
class Tv
{
friend class Remote;
public:
void buzz(Remote& r);
...
};
class Remote
{
friend class Tv;
public:
void volup(Tv& t){...};
...
};
inline void Tv::buzz(Remote& r)
{
...
}
有Remote的声明位于Tv声明的后面,所以可以咋类声明中定义Remote::volup(),但在Tv::buzz()方法必须在Tv声明的外部定义,使其位于Remote声明的后面。
15.1.4 共同的友元
需要使用友元的另一种情况是,函数需要访问两个类的私有数据。
class Analyzer;
class Probe
{
friend void sync(Analyzer& a, const Probe& p);
friend void sync(const Probe& p, Analyzer& a);
...
};
class Analyzer
{
friend void sync(Analyzer& a, const Probe& p);
friend void sync(const Probe& p, Analyzer& a);
...
};
inline void sync(Analyzer& a, const Probe& p)
{
...
}
inline void sync(const Probe& p, Analyzer& a)
{
...
}
15.2 嵌套类
在C++中,可以将类声明放在另一个类。在另一个类中声明的类被称为嵌套类(nested class),它通过提供新的类型类作用域来避免名称混乱。包含类的成员函数可以创建和使用被嵌套类的对象;而仅当声明位于公有部分,才能在包含类的外面使用嵌套类,而且必须使用作用域解析符。
对类嵌套和包含不一样。包含意味着将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明的类中有效。
class Queue
{
private:
// nested struct
struct Node
{
Item item;
Node* next;
};
...
};
15.2.1 嵌套类的访问权限
声明位置 | 包含它的类 | 包含它的类的派生类 | 外部 |
私有部分 | 是 | 否 | 否 |
保护部分 | 是 | 是 | 否 |
公有部分 | 是 | 是 | 是 |
15.3 异常
15.3.3 异常机制
引发异常;
使用处理程序捕获异常;
使用try块。
// error4.cpp - using exception classes
#include<iostream>
#include<cmath>
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";
}
// function prototype
double hmean(double a, double b)
{
if (a == -b)
throw bad_hmean(a, b); // 抛出异常
return 2.0 * a * b / (a + b);
}
int main()
{
using std::cout;
using std::cin;
using std::endl;
double x, y, z;
cout << "Enter two numbers";
while (cin >> x >> y)
{
try // 将测试代码放入try代码块中
{
z = hmean(x, y);
cout << "Harmonic mean of " << x << " and " << y << " is " << z << endl;
}
catch (bad_hmean& bg) // 根据throw抛出对象的类型接收
{
bh.mesg();
cout << "Try again";
continue;
}
}
return 0;
}
15.3.8 exception类
logic_error:
domain_error: 定义域或值域取值范围超过。例如sin函数之类
invalid_argument:给函数传递意料外的参数
length_error:没有足够的空间来执行所需操作。例如字符拼接时
out_of_bounds:索引越界
runtime_error:
range_over:计算结果不在函数允许的范围之内,但没有发生上溢或下溢;
overflow_error:比类型可表示范围的最大数值还大;
underflow_error:比类型可表示范围的最小数值还大;
15.4 RTTI(Runtime Type Identification)
dynamic_cast运算符用于将派生类指针转换为基类指针,其主要用途确保安全地调用虚函数。
dynamic_cast<Base*>(Derived*);
const_cast运算符改变值为const或volatile,提供该运算符的原因是,有时候可能需要这样一个值,它在大多数时候是常量,而有时又是可以修改的。
class High {...};
High bar;
const High* pbar = &bar;
High* pb = const_cast<High*>(pbar);
static_cast用法是,仅当type_name可被隐式转换为expression所属的类型或expression可被隐式的转换为type_name所属类型时,下述转换才合法。
static_cast<type-name>(expression);
reinterpret_cast运算符用于天生危险的类型转换。依赖于底层编程技术,是不可移植的。
Typied运算符返回一个type_info对象。可以对两个typied的返回值进行比较,以确保对象是否为特定类型。
typied(pt1) == typied(pt2);