第15章:友元、异常和其他

探索C++的高级特性:程序的“秘密伙伴”、错误处理的“防弹衣”与运行时的“身份扫描器”。

🤝

秘密伙伴:友元

学习如何通过友元(friend)打破封装,实现类与类之间的高效协作。

🛡️

程序防弹衣:异常

掌握 try/catch 机制,优雅地处理程序运行时可能出现的各种错误。

🔍

身份扫描器:RTTI

了解如何在程序运行时识别对象的真实类型,实现更安全的动态操作。

环节一:友元的家族和嵌套类

有时候,我们需要给特定的类或函数一把“万能钥匙”,让它们能访问一个类的私有成员。

👨‍👩‍👧‍👦 友元类:家族特权

如果 B 类是 A 类的友元,那么 B 类的所有方法都能访问 A 类的私有成员。这就像给了整个家族进入私人花园的许可。

🔑 友元成员函数:点对点授权

更精确的授权!只让 B 类的某个特定方法成为 A 类的友元,就像只给一个人一把特定的门钥匙,而不是整个大门的钥匙。

🧰 嵌套类:随身工具箱

在一个类内部定义另一个类。这个“内部类”的作用域被限制在外部类中,有助于组织代码,避免命名冲突。

代码示例:友元授权

#include <iostream> using namespace std; class TV; // 前置声明 (Forward Declaration) // 遥控器类 class Remote { public: // 这个方法想改变TV的音量 void change_vol(TV & t, int v); }; // 电视类 class TV { private: int volume = 5; public: TV() : volume(5) {} // 授权整个 Remote 类为我的友元 friend class Remote; }; // 现在 Remote 的方法可以访问 TV 的私有成员了 void Remote::change_vol(TV & t, int v) { if (v >= 0 && v <= 100) { t.volume = v; // 合法!因为是友元 cout << "音量已修改为: " << t.volume << endl; } } int main() { TV my_tv; Remote my_remote; my_remote.change_vol(my_tv, 20); return 0; }

环节二:异常机制——错误的紧急处理

程序出错时怎么办?直接崩溃?不!我们可以用“异常”来优雅地处理问题。

除法计算器:异常演示

/

错误!

三要素:尝试、抛出、捕获

try (尝试): 将可能出错的代码包裹起来,像一个安全测试区。

throw (抛出): 当错误发生时,立刻“抛出”一个异常信号,中断正常流程。

catch (捕获): 准备一个“捕手”,专门接收特定类型的异常信号并处理它。

代码示例:处理除零错误

#include <iostream> #include <string> using namespace std; double safe_divide(double a, double b) { if (b == 0) { // 抛出一个 string 对象作为异常信号 throw string("错误: 除数不能为零!"); } return a / b; } int main() { try { cout << safe_divide(10.0, 2.0) << endl; cout << safe_divide(10.0, 0.0) << endl; // 这里会抛出异常 } catch (string s) { // 捕获 string 类型的异常 cout << "捕获到异常: " << s << endl; } return 0; }

环节四:RTTI——运行时识别对象身份

当基类指针指向不同派生类对象时,我们如何知道它的“真实身份”?

身份扫描器:`dynamic_cast` 演示

指针

Animal* p

实际对象 1

🐱

Cat

实际对象 2

🐾

Animal

点击按钮,查看 dynamic_cast 结果...
#include <iostream> #include <typeinfo> using namespace std; class Base { virtual void dummy() {} }; // 必须有虚函数 class Derived : public Base {}; void test_rtti(Base* ptr) { // 尝试安全地向下转型 Derived* d_ptr = dynamic_cast<Derived*>(ptr); if (d_ptr) { // 如果转换成功 cout << "成功转换: 实际类型是 Derived" << endl; } else { // 如果转换失败,返回 nullptr cout << "转换失败: 实际类型不是 Derived" << endl; } }

环节五:类型转换运算符的安全替代

告别粗暴的C风格强转,拥抱更安全、意图更明确的C++转换运算符。

static_cast

比喻:理性的转换。 用于良性的、编译时就能确定的转换,如 int 转 double。它是最常用的转换符。

dynamic_cast

比喻:多态转换。 专用于处理多态类型(有虚函数的类)的向下转换,进行运行时安全检查。

const_cast

比喻:删除只读锁。 唯一能移除变量 const 属性的转换符。使用时需格外小心,通常用于与旧API交互。

reinterpret_cast

比喻:危险的重新解释。 最不安全的转换,它仅仅是重新解释内存中的位模式。极少使用,通常用于底层编程。

环节六:总结与编程挑战

你已经掌握了C++的高级特性!现在来试试这些挑战任务吧。

练习 1:除法异常处理

任务:

编写一个函数 `div_safe(int a, int b)`。如果除数 `b` 为零,抛出一个整数 `(-1)` 作为异常。在 `main` 函数中,使用 `try/catch` 块捕获这个异常,并打印出错误信息。

点击查看参考答案
#include <iostream> using namespace std; int div_safe(int a, int b) { if (b == 0) { throw -1; // 抛出整数异常 } return a / b; } int main() { int num1 = 10, num2 = 0; cout << "开始计算..." << endl; try { int result = div_safe(num1, num2); cout << "结果: " << result << endl; } catch (int e) { // 捕获 int 类型的异常 if (e == -1) { cout << "错误捕获: 运算失败,除数为零。" << endl; } } cout << "程序继续执行。" << endl; return 0; }
练习 2:多态与安全转换

任务:

  1. 定义基类 `Animal` (带虚函数) 和派生类 `Cat`。
  2. 编写函数 `process_animal(Animal* p)`,用 `dynamic_cast` 尝试将其转为 `Cat*`。如果成功,调用 `Cat` 的特有方法;如果失败,则调用基类方法。
  3. 在 `main` 中,用 `static_cast` 将 `int` 转换为 `double` 并进行除法运算,以演示其用途。
点击查看参考答案
#include <iostream> #include <typeinfo> using namespace std; class Animal { public: virtual void speak() const { cout << "Animal speaks." << endl; } }; class Cat : public Animal { public: void meow() const { cout << "Cat meows." << endl; } void speak() const override { cout << "Cat speaks." << endl; } }; void process_animal(Animal* p) { Cat* c_ptr = dynamic_cast(p); if (c_ptr) { cout << "成功识别为 Cat: "; c_ptr->meow(); } else { cout << "无法转换为 Cat: "; p->speak(); } } int main() { Cat my_cat; Animal generic_animal; process_animal(&my_cat); process_animal(&generic_animal); int integer_val = 5; double double_val = static_cast(integer_val) / 2; cout << "\n5 / 2.0 结果: " << double_val << endl; return 0; }