至此,我们关于 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