C++继承与虚函数:章节总结与测验

至此,我们关于 C++ 继承与虚函数的旅程已画上句号。亲爱的读者,请勿惆怅,后续仍有无数 C++ 领域等待我们深入探索。

章节小结

• C++ 允许将基类指针或引用绑定到派生类对象。此特性在编写可处理任意派生类对象的函数或数组时尤为有用。
• 若未使用虚函数,通过基类指针或引用访问派生类对象时,仅能使用基类的成员变量及函数版本。
• 虚函数是一种特殊函数,在运行时会解析为基类与派生类之间最派生的版本(称为覆盖)。要成为覆盖,派生类函数必须与基类虚函数拥有完全相同的签名及返回类型。协变返回类型是唯一的例外:若基类函数返回基类指针或引用,覆盖函数可返回派生类指针或引用。
• 若某函数意在成为覆盖,应使用 override 说明符,以保证其确实构成覆盖。
• 可使用 final 说明符阻止某函数被进一步覆盖,或阻止某类被继续继承。
• 若打算使用继承,应将析构函数设为虚函数,以确保通过基类指针删除对象时,能够调用正确的析构函数。
• 可通过作用域解析运算符显式忽略虚函数动态绑定,直接指定所需类版本的函数,例如 base.Base::getName()。
• 早绑定(静态绑定)发生于编译器遇到直接函数调用之时,编译器或链接器可在编译期直接解析。晚绑定(动态绑定)发生于通过函数指针调用函数之时,此时直至运行期方能确定调用哪个函数。虚函数采用晚绑定,并借助虚函数表决定最终调用的函数版本。
• 使用虚函数需付出代价:虚函数调用开销更大,且因需维护虚函数表,每个含有虚函数的对象都会额外增加一个指针大小。
• 在虚函数原型末尾添加 “= 0” 可将其声明为纯虚函数(抽象函数)。含纯虚函数的类称为抽象类,不可实例化。继承纯虚函数的派生类必须给出具体定义,否则亦为抽象类。纯虚函数亦可拥有函数体,但仍被视为抽象。
• 接口类是指不含成员变量、仅含纯虚函数的类,通常以大写字母 I 开头命名。
• 虚基类保证无论被继承多少次,派生类对象中仅包含该基类的一个实例。
• 将派生类对象赋给基类对象时,基类仅复制派生类中的基类子对象,此现象称为对象切片(object slicing)。
• dynamic_cast 可将基类指针转换为派生类指针,此过程称为向下转型(downcasting)。若转型失败,则返回空指针。
• 为继承体系重载 operator« 的最简方法:为最基类编写重载版本,并在其中调用虚成员函数完成实际输出。

测验时间

下列每个程序均存在某种缺陷。请仅通过目测(勿编译)找出问题所在。各程序预期输出均为 “Derived”。

1a)

#include <iostream>

class Base
{
protected:
    int m_value;

public:
    Base(int value)
        : m_value{ value }
    {
    }

    const char* getName() const { return "Base"; }
};

class Derived : public Base
{
public:
    Derived(int value)
        : Base{ value }
    {
    }

    const char* getName() const { return "Derived"; }
};

int main()
{
    Derived d{ 5 };
    Base& b{ d };
    std::cout << b.getName() << '\n';

    return 0;
}

1b)

#include <iostream>

class Base
{
protected:
    int m_value;

public:
    Base(int value)
        : m_value{ value }
    {
    }

    virtual const char* getName() { return "Base"; }
};

class Derived : public Base
{
public:
    Derived(int value)
        : Base{ value }
    {
    }

    virtual const char* getName() const { return "Derived"; }
};

int main()
{
    Derived d{ 5 };
    Base& b{ d };
    std::cout << b.getName() << '\n';

    return 0;
}

1c)

#include <iostream>

class Base
{
protected:
    int m_value;

public:
    Base(int value)
        : m_value{ value }
    {
    }

    virtual const char* getName() { return "Base"; }
};

class Derived : public Base
{
public:
    Derived(int value)
        : Base{ value }
    {
    }

    const char* getName() override { return "Derived"; }
};

int main()
{
    Derived d{ 5 };
    Base b{ d };
    std::cout << b.getName() << '\n';

    return 0;
}

1d)

#include <iostream>

class Base final
{
protected:
    int m_value;

public:
    Base(int value)
        : m_value{ value }
    {
    }

    virtual const char* getName() { return "Base"; }
};

class Derived : public Base
{
public:
    Derived(int value)
        : Base{ value }
    {
    }

    const char* getName() override { return "Derived"; }
};

int main()
{
    Derived d{ 5 };
    Base& b{ d };
    std::cout << b.getName() << '\n';

    return 0;
}

1e)

#include <iostream>

class Base
{
protected:
    int m_value;

public:
    Base(int value)
        : m_value{ value }
    {
    }

    virtual const char* getName() { return "Base"; }
};

class Derived : public Base
{
public:
    Derived(int value)
        : Base{ value }
    {
    }

    virtual const char* getName() = 0;
};

const char* Derived::getName()
{
    return "Derived";
}

int main()
{
    Derived d{ 5 };
    Base& b{ d };
    std::cout << b.getName() << '\n';

    return 0;
}

1f)

#include <iostream>

class Base
{
protected:
    int m_value;

public:
    Base(int value)
        : m_value{ value }
    {
    }

    virtual const char* getName() { return "Base"; }
};

class Derived : public Base
{
public:
    Derived(int value)
        : Base{ value }
    {
    }

    virtual const char* getName() { return "Derived"; }
};

int main()
{
    auto* d{ new Derived(5) };
    Base* b{ d };
    std::cout << b->getName() << '\n';
    delete b;

    return 0;
}

2a) 创建一个名为 Shape 的抽象类。该类应包含三个函数:一个接收并返回 std::ostream& 的纯虚函数 print、重载的 operator« 以及一个空的虚析构函数。

2b) 从 Shape 派生出两个类:Triangle 与 Circle。Triangle 应包含 3 个 Point 成员;Circle 应包含一个中心 Point 及一个整型半径。重写 print() 函数,使以下程序得以运行:

int main()
{
    Circle c{ Point{ 1, 2 }, 7 };
    std::cout << c << '\n';

    Triangle t{Point{ 1, 2 }, Point{ 3, 4 }, Point{ 5, 6 }};
    std::cout << t << '\n';

    return 0;
}

预期输出:

Circle(Point(1, 2), radius 7)  
Triangle(Point(1, 2), Point(3, 4), Point(5, 6))  

可使用如下 Point 类:

class Point
{
private:
    int m_x{};
    int m_y{};

public:
    Point(int x, int y)
        : m_x{ x }, m_y{ y }
    {

    }

    friend std::ostream& operator<<(std::ostream& out, const Point& p)
    {
        return out << "Point(" << p.m_x << ", " << p.m_y << ")";
    }
};

2c) 基于上述类(Point、Shape、Circle、Triangle),补全以下程序:

#include <vector>
#include <iostream>

int main()
{
    std::vector<Shape*> v{
      new Circle{Point{ 1, 2 }, 7},
      new Triangle{Point{ 1, 2 }, Point{ 3, 4 }, Point{ 5, 6 }},
      new Circle{Point{ 7, 8 }, 3}
    };

    // 在此逐行打印 vector v 中的每个图形

    std::cout << "The largest radius is: " << getLargestRadius(v) << '\n'; // 请实现此函数

    // 在此释放 vector 中的每个元素

    return 0;
}

程序应输出:

Circle(Point(1, 2), radius 7)  
Triangle(Point(1, 2), Point(3, 4), Point(5, 6))  
Circle(Point(7, 8), radius 3)  
The largest radius is: 7  

提示:需为 Circle 添加 getRadius() 函数,并将 Shape* 向下转型为 Circle* 以访问半径。

2d) 附加题:将前述解答更新为使用 std::vector<std::unique_ptr>。请注意 std::unique_ptr 不可复制。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"