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.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 + 5 和 5 + 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();
|
输出:
这里指针类型是 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; }
|
输出:
不能声明虚构造函数。构造函数执行时对象还没有完整形成,虚函数机制依赖的动态类型还不能按“派生类对象”来工作。
10、纯虚函数与抽象类
纯虚函数用于声明“所有派生类都应该提供这个能力”,但基类自己不给出完整实现。
1 2 3 4 5 6
| class Shape { public: virtual ~Shape() = default; virtual double area() const = 0; virtual double perimeter() const = 0; };
|
含有纯虚函数的类叫抽象类。抽象类不能直接创建对象:
但可以定义指针或引用:
派生类必须实现所有纯虚函数后,才可以实例化。
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 的虚函数。
8-4 能否声明虚构造函数?能否声明虚析构函数?
不能声明虚构造函数。构造函数负责创建对象,而虚函数机制依赖对象已经形成后的动态类型,因此虚构造函数没有意义。
可以声明虚析构函数。用途是通过基类指针删除派生类对象时,保证先调用派生类析构函数,再调用基类析构函数。
1 2 3 4
| class Base { public: virtual ~Base() = default; };
|
如果一个类有虚函数,或者准备作为多态基类使用,通常应当给它一个虚析构函数。
8-5 实现重载函数 Double(x)
要求:返回值为输入参数的两倍;参数分别为 int、long、float、double,返回值类型与参数一致。
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 定义 Mammal 和 Dog,观察虚函数调用结果
要求:基类 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 抽象类,派生出 Rectangle 和 Circle
要求:Rectangle 和 Circle 都有 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 定义 BaseClass 和 DerivedClass,观察虚函数与非虚函数调用
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 类,用友元函数重载 +
要求:成员变量 X、Y,用友元函数实现 Point + int 和 int + 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++ 复盘只保留题意、结构和旧代码入口。
本章核心复盘顺序:先写会 Double、Counter、Point 这些重载题,再把 Mammal/Dog、Shape/Circle/Rectangle 这些虚函数和抽象类题反复敲熟。
13、本章复盘清单
| 检查点 |
是否掌握 |
| 能说清编译时多态和运行时多态的区别 |
待复盘 |
| 能写函数重载和运算符重载 |
待复盘 |
能解释为什么重写虚函数要加 override |
待复盘 |
| 能解释虚函数动态绑定发生在基类指针或引用调用时 |
待复盘 |
| 能解释为什么多态基类需要虚析构函数 |
待复盘 |
能写 Shape 抽象类和派生类面积周长例子 |
待复盘 |
能区分前置 ++ 和后置 ++ 的重载写法 |
待复盘 |
最容易混的地方:函数重载是编译器按参数选函数,虚函数是运行时按对象真实类型选函数。这句话记住,本章大部分题就顺了。