346 lines
7.9 KiB
Markdown
346 lines
7.9 KiB
Markdown
<!--
|
||
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
|
||
* @Date: 2025-05-12 13:19:46
|
||
* @LastEditors: error: git config user.name & please set dead value or install git
|
||
* @LastEditTime: 2025-05-12 14:10:25
|
||
-->
|
||
## 4.阅读代码,并按要求练习。
|
||
``` cpp
|
||
class A
|
||
{
|
||
public:
|
||
A(int num):data1(num) {}
|
||
~A(){
|
||
cout<<" Destory A"<<endl;
|
||
}
|
||
void f() const{
|
||
cout<<" Excute A::f() ";
|
||
cout<<" Data1="<<data1<<endl;
|
||
}
|
||
void g()
|
||
{
|
||
cout<<" Excute A::g() "<<endl;
|
||
}
|
||
private:
|
||
int data1;
|
||
};
|
||
|
||
class B : public A
|
||
{
|
||
public:
|
||
B(int num1,int num2):A(num1),data2(num2) {}
|
||
~B(){
|
||
cout<<" Destory B"<<endl;
|
||
}
|
||
void f( ) const{
|
||
cout<<" Excute B::f() ";
|
||
cout<<" Data1="<< data1;
|
||
cout<<" Data2="<<data2<<endl;
|
||
}
|
||
void f(int n) const{
|
||
cout<<" Excute B::f(int) ";
|
||
cout<<" n="<<n;
|
||
cout<<" Data1="<< data1;
|
||
cout<<" Data2="<<data2<<endl;
|
||
}
|
||
void h(){
|
||
cout<<" Excute B::h() "<<endl;
|
||
}
|
||
private:
|
||
int data2;
|
||
};
|
||
```
|
||
|
||
## 下面是问题
|
||
- ##### 1)完成 B 类的构造函数,使得参数 num1 对应 data1,num2 对应 data2;
|
||
|
||
✅已完成:
|
||
|
||
```cpp
|
||
B(int num1, int num2) : A(num1), data2(num2) {}
|
||
```
|
||
|
||
这正确地将 `num1` 传递给基类 A,`num2` 初始化 `data2`。✅
|
||
|
||
---
|
||
|
||
- ##### 2)尝试在 main 函数中使用这两个类,编译程序看是否有编译错误?指出错误的原因。
|
||
|
||
|
||
✅已完成:
|
||
|
||
```cpp
|
||
B b(1, 2);
|
||
b.f();
|
||
b.g();
|
||
b.f(3);
|
||
b.h();
|
||
```
|
||
|
||
若此时 A 中 `data1` 是 `private`,则:
|
||
|
||
* ✅ `b.f()`、`b.g()`、`b.f(3)`、`b.h()` 都能编译通过。
|
||
* ⚠️ 但 `B::f()` 中访问 `data1` 会报错(因为 `B` 不能访问 `A::data1` 的 `private` 成员)。
|
||
---
|
||
|
||
- ##### 3)将基类中的 private 改为 protected,再编译。理解 protected 访问权限,在public 继承方式下的可访问性。
|
||
|
||
|
||
|
||
✅ 将 A 的成员 `private → protected` 后
|
||
|
||
* `B` 就可以访问 `A::data1`。
|
||
* 所以 `B::f()` 和 `f(int)` 中访问 `data1` 合法。
|
||
* `protected` 在 `public` 继承下,**对子类可见,对外仍然不可见**。
|
||
|
||
---
|
||
- ##### 4)修改 main 函数,如下所示,看看哪些语句合法?为什么?执行的是基类的实现,还是派生类的实现?
|
||
``` cpp
|
||
int main()
|
||
{
|
||
B b(1,2);
|
||
b.f();
|
||
b.g();
|
||
b.f(3);
|
||
b.h();
|
||
return 0;
|
||
}
|
||
```
|
||
✅已完成:
|
||
|
||
```cpp
|
||
int main() {
|
||
B b(1, 2);
|
||
b.f(); // ✅ 调用 B::f()
|
||
b.g(); // ✅ 调用 A::g()
|
||
b.f(3); // ✅ 调用 B::f(int)
|
||
b.h(); // ✅ 调用 B::h()
|
||
}
|
||
```
|
||
|
||
**全部合法**,执行的都是定义在类中的相应成员函数,优先使用派生类的版本。
|
||
|
||
---
|
||
- ##### 5)将继承 A 类的继承方式改为 private,编译能通过吗?再执行 4)中的main 函数,看看哪些语句变得不合法了?为什么?
|
||
✅ 改为 `private` 继承后:
|
||
|
||
```cpp
|
||
class B : private A
|
||
```
|
||
|
||
现在,`A` 的成员都变成 `B` 的私有成员。
|
||
|
||
* `main()` 中 `b.g();` 不合法(`A::g()` 是 `private` 了)
|
||
* `b.f();` 也不合法,因为 `A::f()` 被隐藏
|
||
* ❌ **除了 `b.f(3)` 和 `b.h()` 还行,其余都报错**
|
||
|
||
---
|
||
|
||
- ##### 6)将继承 A 类的继承方式改回 public,并实现 B 类自定义的拷贝构造和赋值函数。
|
||
|
||
✅已构造A类的operator+
|
||
|
||
```cpp
|
||
class B : public A {
|
||
// ...
|
||
public:
|
||
B(const B& other) : A(other), data2(other.data2) {}
|
||
B& operator=(const B& other) {
|
||
if (this != &other) {
|
||
A::operator=(other);
|
||
//或者
|
||
this->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 | 虚析构函数,资源释放 |
|
||
|
||
---
|
||
|