Files
OOP-Cpp/oop_hw5/hw4/题目以及答案文字部分.md
2025-08-11 00:01:30 +08:00

7.9 KiB
Raw Blame History

4.阅读代码,并按要求练习。

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 对应 data1num2 对应 data2

已完成:

B(int num1, int num2) : A(num1), data2(num2) {}

这正确地将 num1 传递给基类 Anum2 初始化 data2


  • 2)尝试在 main 函数中使用这两个类,编译程序看是否有编译错误?指出错误的原因。

已完成:

B b(1, 2);
b.f();
b.g();
b.f(3);
b.h();

若此时 A 中 data1private,则:

  • b.f()b.g()b.f(3)b.h() 都能编译通过。
  • ⚠️B::f() 中访问 data1 会报错(因为 B 不能访问 A::data1private 成员)。

  • 3)将基类中的 private 改为 protected再编译。理解 protected 访问权限在public 继承方式下的可访问性。

将 A 的成员 private → protected

  • B 就可以访问 A::data1
  • 所以 B::f()f(int) 中访问 data1 合法。
  • protectedpublic 继承下,对子类可见,对外仍然不可见

  • 4)修改 main 函数,如下所示,看看哪些语句合法?为什么?执行的是基类的实现,还是派生类的实现?
int main()
{
B b(1,2);
b.f();
b.g();
b.f(3);
b.h();
return 0;
}

已完成:

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 继承后:

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+

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(),请问哪些可以通过编译,执行结果如何?

调用:

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);,理解向上类型转换的安全性。

已完成:

A* p = new B(1, 2);

安全称为“向上转型”upcastingB 是 A 的子类。安全的主要原因: “向上类型转换的安全性”是面向对象编程中一个很关键但也容易被忽略的点,尤其在 C++ 中。


什么是向上类型转换Upcasting

向上类型转换:将派生类对象的指针或引用转换为基类类型的指针或引用

比如:

class A { };
class B : public A { };

B b;
A* pa = &b;  // 向上类型转换Upcasting

安全性的本质

向上转换是安全的、隐式允许的

因为:

  • 每个派生类对象本质上包含一个完整的基类子对象部分
  • 所以把 B* 转为 A* 是有意义的:A 的部分总是存在的。

换句话说,你永远可以“安全地把一个更具体的对象视为一个更抽象的对象”。


示例

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() 非虚函数):

p->f();  // 输出 A::f()

若你希望执行的是 B::f()应将 A 的 f() 声明为 virtual


  • 10)在 8)的基础上,执行 p->f(1),能通过编译吗?为什么?

编译?

p->f(1);  // ❌ 报错A 类中无 f(int)

即使实际对象是 B编译器只看指针类型A而 A 没有 f(int)


  • 11)在 8)的基础上,执行 p->g()和 p->h(),能行吗?为什么?

编译?

p->f(1);  // ❌ 报错A 类中无 f(int)

即使实际对象是 B编译器只看指针类型A而 A 没有 f(int)


  • 12)在 8)的基础上,执行 delete p;输出是什么B 类的析构函数执行了吗?

输出:

如果 A 的析构函数不是 virtual,那么:

delete p;  // 只会调用 A 的析构函数,❌ 不会调用 B 的析构函数

输出:

Destory A

⚠️ 此时会发生资源泄漏,因为 B 中资源未释放!

正确写法是:

class A {
public:
    virtual ~A() {
        cout << "Destory A" << endl;
    }
    // ...
};

这样 delete p; 才会调用 B 的析构函数 → 输出顺序:

Destory B
Destory A

总结重点

问题 核心知识
23 访问权限private vs protected
45 继承方式public/private影响成员访问
6 拷贝构造函数 & 赋值操作符
711 多态、向上转型、成员函数隐藏
12 虚析构函数,资源释放