## 4.阅读代码,并按要求练习。 ``` cpp class A { public: A(int num):data1(num) {} ~A(){ cout<<" Destory A"<A::operator=(other); //这种写法也可以 data2 = other.data2; } return *this; } }; ``` --- - ##### 7)分别创建 A 和 B 类的两个对象 a 和 b,分别执行 a.f(),b.f(),a.g(),b.g(),a.f(1),b.f(1),a.h(),b.h(),请问哪些可以通过编译,执行结果如何? ✅ 调用: ```cpp A a(1); B b(2, 3); a.f(); // ✅ A::f() b.f(); // ✅ B::f() a.g(); // ✅ A::g() b.g(); // ✅ A::g() 继承而来 a.f(1); // ❌ 无此函数 b.f(1); // ✅ B::f(int) a.h(); // ❌ 无此函数 b.h(); // ✅ B::h() ``` --- - ##### 8)增加代码 A * p=new B(1,2);,理解向上类型转换的安全性。 ✅已完成: ```cpp A* p = new B(1, 2); ``` ✅ 安全,称为“向上转型”(upcasting),**B 是 A 的子类**。安全的主要原因: “向上类型转换的安全性”是面向对象编程中一个很关键但也容易被忽略的点,尤其在 C++ 中。 --- ## ✅ 什么是向上类型转换(Upcasting)? 向上类型转换:**将派生类对象的指针或引用转换为基类类型的指针或引用**。 比如: ```cpp class A { }; class B : public A { }; B b; A* pa = &b; // 向上类型转换(Upcasting) ``` --- ## ✅ 安全性的本质 ### ✅ **向上转换是安全的、隐式允许的** 因为: * 每个派生类对象**本质上包含一个完整的基类子对象部分**。 * 所以把 `B*` 转为 `A*` 是有意义的:`A` 的部分总是存在的。 > 换句话说,你永远可以“安全地把一个更具体的对象视为一个更抽象的对象”。 --- ## ✅ 示例 ```cpp class A { public: void foo() { cout << "A::foo\n"; } }; class B : public A { public: void bar() { cout << "B::bar\n"; } }; int main() { B b; A* pa = &b; // 向上转换,安全 pa->foo(); // ✅合法,调用 A 的函数 // pa->bar(); // ❌错误:A 类型中没有 bar() } ``` --- ## ❗ 注意事项:类型“退化” 向上转换后: * **只能访问基类中的成员**,即便实际指向的是一个派生类对象; * 若函数是非虚函数,则会“静态绑定”到基类版本; * 若函数是虚函数,则会“动态绑定”到派生类重写版本(见多态); --- ## ✅ 与向下转换对比 | 类型转换方式 | 安全性 | 是否隐式 | 风险 | | --------- | ----- | ---- | ---------------- | | 向上转换(B→A) | ✅ 安全 | ✅ 是 | 无 | | 向下转换(A→B) | ❌ 不安全 | ❌ 否 | 若对象本非 B 类型,结果未定义 | --- - ##### 9)在 8)的基础上,执行 p->f(),输出是什么?与 B* p=new B(1,2); p->f();的结果一样吗? ✅执行后: 默认情况下(`f()` 非虚函数): ```cpp p->f(); // 输出 A::f() ``` 若你希望执行的是 `B::f()`,**应将 A 的 `f()` 声明为 `virtual`**。 --- - ##### 10)在 8)的基础上,执行 p->f(1),能通过编译吗?为什么? ✅编译? ```cpp p->f(1); // ❌ 报错:A 类中无 f(int) ``` 即使实际对象是 B,编译器只看指针类型(A),而 A 没有 `f(int)`。 --- - ##### 11)在 8)的基础上,执行 p->g()和 p->h(),能行吗?为什么? ✅编译? ```cpp p->f(1); // ❌ 报错:A 类中无 f(int) ``` 即使实际对象是 B,编译器只看指针类型(A),而 A 没有 `f(int)`。 --- - ##### 12)在 8)的基础上,执行 delete p;,输出是什么?B 类的析构函数执行了吗? ✅输出: 如果 `A` 的析构函数不是 `virtual`,那么: ```cpp delete p; // 只会调用 A 的析构函数,❌ 不会调用 B 的析构函数 ``` 输出: ``` Destory A ``` ⚠️ 此时会发生**资源泄漏**,因为 `B` 中资源未释放! ✅ 正确写法是: ```cpp class A { public: virtual ~A() { cout << "Destory A" << endl; } // ... }; ``` 这样 `delete p;` 才会调用 B 的析构函数 → 输出顺序: ``` Destory B Destory A ``` --- ## ✅ 总结重点 | 问题 | 核心知识 | | ---- | -------------------------- | | 2–3 | 访问权限(private vs protected) | | 4–5 | 继承方式(public/private)影响成员访问 | | 6 | 拷贝构造函数 & 赋值操作符 | | 7–11 | 多态、向上转型、成员函数隐藏 | | 12 | 虚析构函数,资源释放 | ---