1、资料清单

资料 内容 入口
教材 《C++语言程序设计(第4版)》Chap8,原书页码 322-361 抽取 Markdown 下载
课件 Chap8 课件,位于本地郑莉 C++ 资料目录 本地资料夹
例题源代码 C++V5 源代码 下载
VS2019 工程 C++V5 VS2019 solution 下载

Chap8 的主线是重载 → 虚函数 → 动态绑定 → 虚析构函数 → 纯虚函数与抽象类。这一章把 Chap7 的继承结构真正变成“同一个接口,不同对象表现不同”的程序设计能力。

2、颜色标注

颜色 含义 使用位置
蓝色 核心术语、定义 多态、虚函数、动态绑定、抽象类
绿色 推荐写法、主线 override、虚析构函数、基类指针/引用
黄色 易错点、边界条件 重载匹配、非虚函数静态绑定、对象切片
红色 不建议做法 用无虚析构基类指针删除派生类对象、滥用运算符重载

3、学习目标

目标 要会到什么程度
多态概念 能区分编译时多态和运行时多态
函数重载 知道函数名相同、参数列表不同,返回值不能单独构成重载
运算符重载 会写成员函数版本和友元/非成员函数版本
虚函数 理解基类指针或引用调用虚函数时发生动态绑定
虚析构函数 知道有多态使用需求的基类析构函数应为 virtual
纯虚函数 会写 = 0,知道它只规定接口,不规定具体行为
抽象类 知道抽象类不能直接实例化,只能作为统一接口使用

4、章节目录

小节 内容 本节关键词
8.1 多态性概述 编译时多态、运行时多态
8.2 运算符重载 operator、成员函数、非成员函数
8.3 虚函数 virtual、动态绑定、override
8.4 纯虚函数与抽象类 = 0、接口、抽象基类
8.5 变步长梯形积分实例 函数对象、多态计算
8.6 银行账户程序改进 账户族、统一接口

5、多态性概述

多态就是:同样的消息发送给不同类型的对象,产生不同的行为。

比如同样调用 speak()

1
2
3
4
5
6
7
8
9
10
11
12
13
class Mammal {
public:
virtual void speak() const {
std::cout << "Mammal speak\n";
}
};

class Dog : public Mammal {
public:
void speak() const override {
std::cout << "Woof\n";
}
};

当基类指针实际指向 Dog 对象时:

1
2
3
Mammal* p = new Dog;
p->speak();
delete p;

如果 speak() 是虚函数,调用的是 Dog::speak()

类型 发生时机 C++ 中的典型方式
编译时多态 编译阶段决定调用哪个函数 函数重载、运算符重载、函数模板
运行时多态 程序运行阶段根据对象真实类型决定调用哪个函数 虚函数、基类指针、基类引用

一句话复盘:重载看参数,虚函数看对象真实类型

6、函数重载

函数重载是最基础的编译时多态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

int twice(int x) {
return x * 2;
}

double twice(double x) {
return x * 2;
}

int main() {
std::cout << twice(3) << '\n';
std::cout << twice(3.14) << '\n';
return 0;
}

重载判断依据是函数签名中的参数列表。

可以构成重载 不能单独构成重载
参数个数不同 只有返回值不同
参数类型不同 只有形参名不同
参数顺序不同 只有默认值不同

如果两个函数调用都可能匹配,编译器会尝试选择最合适的版本;如果无法判定,就会产生二义性。

7、运算符重载

运算符重载让自定义类型也能使用类似内置类型的表达方式。

1
Counter c3 = c1 + c2;

这背后可以写成:

1
Counter c3 = c1.operator+(c2);

或者非成员函数:

1
Counter c3 = operator+(c1, c2);

7.1 基本规则

规则 说明
至少一个操作数是自定义类型 不能给两个 int 重新定义 +
不能创造新运算符 只能重载 C++ 已有运算符
不能改变优先级和结合性 + 还是原来的优先级
不能改变操作数个数 一元还是一元,二元还是二元
保持语义自然 + 最好仍然表达加法、合并、拼接这类含义

7.2 成员函数重载

成员函数版本的左操作数默认是当前对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Counter {
public:
explicit Counter(unsigned value = 0) : value_(value) {}

unsigned value() const {
return value_;
}

Counter operator+(const Counter& rhs) const {
return Counter(value_ + rhs.value_);
}

private:
unsigned value_;
};

调用:

1
2
3
Counter a(2);
Counter b(4);
Counter c = a + b;

7.3 非成员函数重载

如果左操作数不是当前类对象,常用非成员函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Point {
public:
Point(int x = 0, int y = 0) : x_(x), y_(y) {}

int x() const { return x_; }
int y() const { return y_; }

friend Point operator+(const Point& point, int offset);
friend Point operator+(int offset, const Point& point);

private:
int x_;
int y_;
};

Point operator+(const Point& point, int offset) {
return Point(point.x_ + offset, point.y_ + offset);
}

Point operator+(int offset, const Point& point) {
return point + offset;
}

这样 point + 55 + point 都能成立。

8、虚函数

虚函数用于运行时多态。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Base {
public:
virtual void print() const {
std::cout << "Base\n";
}
};

class Derived : public Base {
public:
void print() const override {
std::cout << "Derived\n";
}
};

调用:

1
2
3
Derived d;
Base* p = &d;
p->print();

输出:

1
Derived

这里指针类型是 Base*,但对象真实类型是 Derived。由于 print() 是虚函数,所以调用发生动态绑定。

8.1 虚函数的使用条件

条件 说明
基类成员函数声明为 virtual 派生类重写后才能通过基类接口动态调用
通过基类指针或基类引用调用 直接对象调用通常不体现运行时多态
派生类函数签名一致 推荐使用 override 让编译器检查
1
2
3
4
class Derived : public Base {
public:
void print() const override;
};

现代 C++ 里,重写虚函数时尽量写 override。它不是为了运行,而是为了让编译器帮你检查“是不是确实重写了基类虚函数”。

8.2 虚函数与非虚函数对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>

class Base {
public:
virtual void fn1() {
std::cout << "Base::fn1\n";
}

void fn2() {
std::cout << "Base::fn2\n";
}
};

class Derived : public Base {
public:
void fn1() override {
std::cout << "Derived::fn1\n";
}

void fn2() {
std::cout << "Derived::fn2\n";
}
};

int main() {
Derived object;
Base* base = &object;
Derived* derived = &object;

base->fn1();
base->fn2();
derived->fn1();
derived->fn2();

return 0;
}

输出:

1
2
3
4
Derived::fn1
Base::fn2
Derived::fn1
Derived::fn2

重点是:虚函数按对象真实类型绑定,非虚函数按指针静态类型绑定

9、虚析构函数

只要一个类准备用作多态基类,就应该把析构函数写成虚函数。

1
2
3
4
class Base {
public:
virtual ~Base() = default;
};

原因是:可能会通过基类指针删除派生类对象。

1
2
Base* p = new Derived;
delete p;

如果 Base 的析构函数不是虚函数,delete p 可能只调用 Base 的析构函数,派生类资源得不到正确释放。

完整例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

class Base {
public:
virtual ~Base() {
std::cout << "~Base\n";
}
};

class Derived : public Base {
public:
~Derived() override {
std::cout << "~Derived\n";
}
};

int main() {
Base* p = new Derived;
delete p;
return 0;
}

输出:

1
2
~Derived
~Base

不能声明虚构造函数。构造函数执行时对象还没有完整形成,虚函数机制依赖的动态类型还不能按“派生类对象”来工作。

10、纯虚函数与抽象类

纯虚函数用于声明“所有派生类都应该提供这个能力”,但基类自己不给出完整实现。

1
2
3
4
5
6
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
virtual double perimeter() const = 0;
};

含有纯虚函数的类叫抽象类。抽象类不能直接创建对象:

1
Shape s; // 错误

但可以定义指针或引用:

1
Shape* p = nullptr;

派生类必须实现所有纯虚函数后,才可以实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Circle : public Shape {
public:
explicit Circle(double radius) : radius_(radius) {}

double area() const override {
return 3.1415926 * radius_ * radius_;
}

double perimeter() const override {
return 2 * 3.1415926 * radius_;
}

private:
double radius_;
};

抽象类最适合作为“接口层”:它规定大家都要会什么,具体怎么做交给派生类。

11、本章综合代码

这个例子把抽象类、虚函数、虚析构函数和动态绑定连在一起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <iostream>
#include <memory>
#include <vector>

class Shape {
public:
virtual ~Shape() = default;

virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual const char* name() const = 0;
};

class Rectangle : public Shape {
public:
Rectangle(double width, double height)
: width_(width), height_(height) {}

double area() const override {
return width_ * height_;
}

double perimeter() const override {
return 2 * (width_ + height_);
}

const char* name() const override {
return "Rectangle";
}

private:
double width_;
double height_;
};

class Circle : public Shape {
public:
explicit Circle(double radius) : radius_(radius) {}

double area() const override {
constexpr double pi = 3.141592653589793;
return pi * radius_ * radius_;
}

double perimeter() const override {
constexpr double pi = 3.141592653589793;
return 2 * pi * radius_;
}

const char* name() const override {
return "Circle";
}

private:
double radius_;
};

void printShape(const Shape& shape) {
std::cout << shape.name()
<< " area = " << shape.area()
<< ", perimeter = " << shape.perimeter()
<< '\n';
}

int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(4.0, 6.0));

for (const auto& shape : shapes) {
printShape(*shape);
}

return 0;
}

复盘重点:

代码位置 对应概念
Shape 抽象基类
area() = 0 纯虚函数
virtual ~Shape() = default 多态基类的虚析构函数
override 派生类重写虚函数
printShape(const Shape& shape) 通过基类引用触发动态绑定
vector<unique_ptr<Shape>> 用统一容器管理不同派生类对象

12、课后习题答案

8-1 什么叫做多态性?C++ 中是如何实现多态的?

多态是指同样的消息被不同类型的对象接收时,会产生不同的行为。

C++ 中主要有两类实现方式:

类型 实现方式
编译时多态 函数重载、运算符重载、模板
运行时多态 继承、虚函数、基类指针或引用

本章最核心的是运行时多态:把派生类对象当作基类对象使用,通过虚函数在运行时决定真正调用哪个函数。

8-2 什么叫做抽象类?抽象类有何作用?抽象类的派生类是否一定要给出纯虚函数的实现?

带有纯虚函数的类叫抽象类。

抽象类的作用:

作用 说明
建立公共接口 让一组派生类共享统一调用方式
支持运行时多态 通过基类指针或引用调用派生类行为
分离接口和实现 基类规定“要做什么”,派生类决定“怎么做”

派生类不一定必须实现所有纯虚函数。如果派生类没有实现全部纯虚函数,那么这个派生类仍然是抽象类,不能直接创建对象。

8-3 声明一个参数为整型、无返回值、名为 fn1 的虚函数。

1
virtual void fn1(int);

8-4 能否声明虚构造函数?能否声明虚析构函数?

不能声明虚构造函数。构造函数负责创建对象,而虚函数机制依赖对象已经形成后的动态类型,因此虚构造函数没有意义。

可以声明虚析构函数。用途是通过基类指针删除派生类对象时,保证先调用派生类析构函数,再调用基类析构函数。

1
2
3
4
class Base {
public:
virtual ~Base() = default;
};

如果一个类有虚函数,或者准备作为多态基类使用,通常应当给它一个虚析构函数。

8-5 实现重载函数 Double(x)

要求:返回值为输入参数的两倍;参数分别为 intlongfloatdouble,返回值类型与参数一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>

int Double(int value) {
std::cout << "In Double(int)\n";
return value * 2;
}

long Double(long value) {
std::cout << "In Double(long)\n";
return value * 2;
}

float Double(float value) {
std::cout << "In Double(float)\n";
return value * 2;
}

double Double(double value) {
std::cout << "In Double(double)\n";
return value * 2;
}

int main() {
int myInt = 6500;
long myLong = 65000;
float myFloat = 6.5F;
double myDouble = 6.5e20;

std::cout << "myInt: " << myInt << '\n';
std::cout << "myLong: " << myLong << '\n';
std::cout << "myFloat: " << myFloat << '\n';
std::cout << "myDouble: " << myDouble << '\n';

std::cout << "doubledInt: " << Double(myInt) << '\n';
std::cout << "doubledLong: " << Double(myLong) << '\n';
std::cout << "doubledFloat: " << Double(myFloat) << '\n';
std::cout << "doubledDouble: " << Double(myDouble) << '\n';

return 0;
}

参考输出:

1
2
3
4
5
6
7
8
9
10
11
12
myInt: 6500
myLong: 65000
myFloat: 6.5
myDouble: 6.5e+20
In Double(int)
doubledInt: 13000
In Double(long)
doubledLong: 130000
In Double(float)
doubledFloat: 13
In Double(double)
doubledDouble: 1.3e+21

复盘:这里是编译时多态,编译器根据实参类型选择函数版本。

8-6 定义 Rectangle 类并重载构造函数

要求:有长 itsLength、宽 itsWidth,重载 Rectangle()Rectangle(int width, int length)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>

class Rectangle {
public:
Rectangle() : itsWidth_(5), itsLength_(10) {}

Rectangle(int width, int length)
: itsWidth_(width), itsLength_(length) {}

int width() const {
return itsWidth_;
}

int length() const {
return itsLength_;
}

private:
int itsWidth_;
int itsLength_;
};

int main() {
Rectangle rect1;
std::cout << "rect1 width: " << rect1.width() << '\n';
std::cout << "rect1 length: " << rect1.length() << '\n';

Rectangle rect2(20, 50);
std::cout << "rect2 width: " << rect2.width() << '\n';
std::cout << "rect2 length: " << rect2.length() << '\n';

return 0;
}

输出:

1
2
3
4
rect1 width: 5
rect1 length: 10
rect2 width: 20
rect2 length: 50

8-7 定义计数器 Counter 类,重载运算符 +

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>

class Counter {
public:
explicit Counter(unsigned value = 0) : value_(value) {}

unsigned value() const {
return value_;
}

void setValue(unsigned value) {
value_ = value;
}

Counter operator+(const Counter& rhs) const {
return Counter(value_ + rhs.value_);
}

private:
unsigned value_;
};

int main() {
Counter varOne(2);
Counter varTwo(4);
Counter varThree = varOne + varTwo;

std::cout << "varOne: " << varOne.value() << '\n';
std::cout << "varTwo: " << varTwo.value() << '\n';
std::cout << "varThree: " << varThree.value() << '\n';

return 0;
}

输出:

1
2
3
varOne: 2
varTwo: 4
varThree: 6

8-8 定义 MammalDog,观察虚函数调用结果

要求:基类 Mammal 和派生类 Dog 都定义 Speak(),基类中定义为虚函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>

class Mammal {
public:
Mammal() {
std::cout << "Mammal constructor...\n";
}

virtual ~Mammal() {
std::cout << "Mammal destructor...\n";
}

virtual void Speak() const {
std::cout << "Mammal speak!\n";
}
};

class Dog : public Mammal {
public:
Dog() {
std::cout << "Dog constructor...\n";
}

~Dog() override {
std::cout << "Dog destructor...\n";
}

void Speak() const override {
std::cout << "Woof!\n";
}
};

int main() {
Mammal* animal = new Dog;
animal->Speak();
delete animal;

return 0;
}

输出:

1
2
3
4
5
Mammal constructor...
Dog constructor...
Woof!
Dog destructor...
Mammal destructor...

复盘:

调用 原因
animal->Speak() 输出 Woof! Speak() 是虚函数,实际对象是 Dog
delete animal 先析构 Dog 基类析构函数是虚函数

8-9 定义 Shape 抽象类,派生出 RectangleCircle

要求:RectangleCircle 都有 GetArea()GetPerim()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <memory>

class Shape {
public:
virtual ~Shape() = default;
virtual double GetArea() const = 0;
virtual double GetPerim() const = 0;
};

class Circle : public Shape {
public:
explicit Circle(double radius) : radius_(radius) {}

double GetArea() const override {
constexpr double pi = 3.14;
return pi * radius_ * radius_;
}

double GetPerim() const override {
constexpr double pi = 3.14;
return 2 * pi * radius_;
}

private:
double radius_;
};

class Rectangle : public Shape {
public:
Rectangle(double length, double width)
: length_(length), width_(width) {}

double GetArea() const override {
return length_ * width_;
}

double GetPerim() const override {
return 2 * (length_ + width_);
}

private:
double length_;
double width_;
};

void print(const Shape& shape) {
std::cout << "area: " << shape.GetArea() << '\n';
std::cout << "perimeter: " << shape.GetPerim() << '\n';
}

int main() {
std::unique_ptr<Shape> circle = std::make_unique<Circle>(5);
std::unique_ptr<Shape> rectangle = std::make_unique<Rectangle>(4, 6);

std::cout << "Circle\n";
print(*circle);

std::cout << "Rectangle\n";
print(*rectangle);

return 0;
}

输出:

1
2
3
4
5
6
Circle
area: 78.5
perimeter: 31.4
Rectangle
area: 24
perimeter: 20

8-10 对 Point 类重载 ++--

要求:分别实现前置自增、后置自增、前置自减、后置自减。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>

class Point {
public:
Point(int x = 0, int y = 0) : x_(x), y_(y) {}

Point& operator++() {
++x_;
++y_;
return *this;
}

Point operator++(int) {
Point old = *this;
++(*this);
return old;
}

Point& operator--() {
--x_;
--y_;
return *this;
}

Point operator--(int) {
Point old = *this;
--(*this);
return old;
}

int x() const {
return x_;
}

int y() const {
return y_;
}

private:
int x_;
int y_;
};

void print(const Point& point) {
std::cout << "Point(" << point.x() << ", " << point.y() << ")\n";
}

int main() {
Point a;
print(a);

a++;
print(a);

++a;
print(a);

a--;
print(a);

--a;
print(a);

return 0;
}

输出:

1
2
3
4
5
Point(0, 0)
Point(1, 1)
Point(2, 2)
Point(1, 1)
Point(0, 0)

复盘:

写法 函数声明 返回值特点
前置 ++a Point& operator++() 返回自增后的当前对象引用
后置 a++ Point operator++(int) 返回自增前的旧值
前置 --a Point& operator--() 返回自减后的当前对象引用
后置 a-- Point operator--(int) 返回自减前的旧值

8-11 定义 BaseClassDerivedClass,观察虚函数与非虚函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>

class BaseClass {
public:
virtual void fn1() {
std::cout << "调用基类的虚函数 fn1()" << '\n';
}

void fn2() {
std::cout << "调用基类的非虚函数 fn2()" << '\n';
}
};

class DerivedClass : public BaseClass {
public:
void fn1() override {
std::cout << "调用派生类的函数 fn1()" << '\n';
}

void fn2() {
std::cout << "调用派生类的函数 fn2()" << '\n';
}
};

int main() {
DerivedClass object;
BaseClass* basePointer = &object;
DerivedClass* derivedPointer = &object;

basePointer->fn1();
basePointer->fn2();
derivedPointer->fn1();
derivedPointer->fn2();

return 0;
}

输出:

1
2
3
4
调用派生类的函数 fn1()
调用基类的非虚函数 fn2()
调用派生类的函数 fn1()
调用派生类的函数 fn2()

解释:

调用语句 调用结果 原因
basePointer->fn1() 派生类 fn1() fn1() 是虚函数,动态绑定
basePointer->fn2() 基类 fn2() fn2() 不是虚函数,静态绑定
derivedPointer->fn1() 派生类 fn1() 指针类型和对象类型都是派生类
derivedPointer->fn2() 派生类 fn2() 指针类型是派生类

8-12 定义虚析构函数,观察析构过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

class BaseClass {
public:
virtual ~BaseClass() {
std::cout << "~BaseClass()" << '\n';
}
};

class DerivedClass : public BaseClass {
public:
~DerivedClass() override {
std::cout << "~DerivedClass()" << '\n';
}
};

int main() {
BaseClass* pointer = new DerivedClass;
delete pointer;
return 0;
}

输出:

1
2
~DerivedClass()
~BaseClass()

复盘:析构顺序和构造顺序相反。通过基类指针删除派生类对象时,基类析构函数必须是虚函数,才能正确触发完整析构链。

8-13 定义 Point 类,用友元函数重载 +

要求:成员变量 XY,用友元函数实现 Point + intint + Point

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>

class Point {
public:
Point(int x = 0, int y = 0) : x_(x), y_(y) {}

int x() const {
return x_;
}

int y() const {
return y_;
}

void print() const {
std::cout << "Point(" << x_ << ", " << y_ << ")\n";
}

friend Point operator+(const Point& point, int offset);
friend Point operator+(int offset, const Point& point);

private:
int x_;
int y_;
};

Point operator+(const Point& point, int offset) {
return Point(point.x_ + offset, point.y_ + offset);
}

Point operator+(int offset, const Point& point) {
return point + offset;
}

int main() {
Point point(10, 10);
point.print();

point = point + 5;
point.print();

point = 10 + point;
point.print();

return 0;
}

输出:

1
2
3
Point(10, 10)
Point(15, 15)
Point(25, 25)

复盘:如果只写成员函数 Point::operator+(int),只能支持 point + 5;要支持 5 + point,需要非成员函数或友元函数。

8-14 到 8-16 MFC 应用题整理

原答案后半部分是 Visual C++ / MFC 框架实验,主要包括:

题号 内容 复盘方式
8-14 人事管理系统,涉及 CEmployee、文档序列化、视图绘制和记录增删改查 理解对象序列化与 GUI 文档/视图结构,不作为本章核心 C++ 语法题
8-15 基于对话框的彩色吹泡泡程序 理解对话框、消息响应、绘图区域和颜色选择
8-16 计算器程序 理解按钮事件、状态变量和基本运算流程

这些题更像 MFC 图形界面课程实验,和本章核心的“多态、虚函数、抽象类”关系不强。后续如果要整理 Windows GUI 或 MFC,可以单独开应用程序设计笔记;当前 C++ 复盘只保留题意、结构和旧代码入口。

本章核心复盘顺序:先写会 DoubleCounterPoint 这些重载题,再把 Mammal/DogShape/Circle/Rectangle 这些虚函数和抽象类题反复敲熟。

13、本章复盘清单

检查点 是否掌握
能说清编译时多态和运行时多态的区别 待复盘
能写函数重载和运算符重载 待复盘
能解释为什么重写虚函数要加 override 待复盘
能解释虚函数动态绑定发生在基类指针或引用调用时 待复盘
能解释为什么多态基类需要虚析构函数 待复盘
能写 Shape 抽象类和派生类面积周长例子 待复盘
能区分前置 ++ 和后置 ++ 的重载写法 待复盘

最容易混的地方:函数重载是编译器按参数选函数,虚函数是运行时按对象真实类型选函数。这句话记住,本章大部分题就顺了。