4.2 KiB
1)去掉(1)中的 virtual,比较执行结果。
virtual ~A() {...}
如果去掉 virtual,在通过 A* p = new B; 创建对象并用 delete p; 释放时:
- 原代码:会先调用
B::~B(),再调用A::~A()(因为析构函数是虚函数,发生动态绑定,析构顺序正确)。 - 去掉
virtual后:只会调用A::~A(),不会调用B::~B(),可能导致子类资源未释放,产生内存泄漏或行为异常。
✅ 结论:基类应将析构函数设为 virtual,以确保通过基类指针删除时能调用子类析构函数。
2)去掉(6)中的 const,可以吗?
virtual int Func1() const;
不可以。因为你在基类 A 中定义的是:
virtual int Func1() const = 0;
这是一个纯虚函数,要求派生类重写时函数签名必须完全一致。如果你去掉 const,则签名不同,不会重写该函数,导致类 B 仍然是抽象类,不能实例化。
3)(3) 和 (7) 中各自定义了参数的缺省值,(12) 执行时匹配的是哪个缺省值?为什么?
// A 类中:
virtual void Func2(int = 500) = 0;
// B 类中:
virtual void Func2(int = 1000)
语句 (12) 是:
p->Func2(); // 没传参数
此时 p 是 A* 类型。函数的缺省参数在编译时根据指针静态类型决定,所以用的是 A::Func2(int=500) 中的默认值。
✅ 结论:
- 缺省参数值是静态绑定的,跟对象的动态类型无关。
- 编译时看的是指针类型
A*,所以用了500而不是1000。
4)(4) 中的 protected 改为 private 可以吗?
protected:
static int lineno;
如果改为 private,派生类 B 就不能访问 lineno,会导致编译错误(如构造函数中 ++lineno)。
✅ 结论:基类希望子类访问成员变量,应使用 protected;private 不可被子类访问。
5)去掉 (5) 中的 virtual,对结果有影响吗?
virtual ~B() { ... }
如果 ~A() 是虚函数,那么即使 ~B() 不是虚函数,也会通过动态绑定调用 ~B()。
✅ 结论:去掉 virtual 不影响结果(只要基类析构函数是虚函数),但建议保留以提高可读性和一致性。
6)(9) 的作用是什么?
int A::lineno = 0;
这是静态成员变量 lineno 的定义和初始化,必须在类外初始化,否则会链接错误。
✅ 作用:为类的所有对象共享一份 lineno 计数器,用来输出行号。
7)(10) 中改为 A* p = new A; 可以吗?
A * p = new A; // 错误
A 是抽象类(包含纯虚函数),不能实例化。会导致编译错误。
✅ 结论:抽象类不能直接创建对象,只能用于指针或引用指向派生类。
8)去掉(3)中的 virtual,结果会有什么改变?
void Func2(int = 500) = 0; // 去掉 virtual -> 变成普通纯函数?
这在语义上是错误的。纯虚函数必须是虚函数,否则语法不合法。
✅ 结论:不能去掉,纯虚函数必须是 virtual。
9)(14) 为什么不对?将 (8) 改为 virtual 后,(14) 可以了吗?
// (14) p->Func3(100);
p 是 A*,而 Func3() 并未在 A 中声明,因此不能通过 A* 调用,即使 Func3() 是 virtual。
✅ 结论:
- 静态类型决定能否调用某函数。
- 即使改成虚函数,也需要
A中声明它,才能通过A*调用。
10)(15) 为什么不对?理解编译时静态类型和运行时动态类型的含义。
// p->Func1(100); // 错误
在 B 中有两个重载版本:
int Func1(int n) const;
int Func1() const;
但 A* p 只能看到 A::Func1() const 版本(无参数)。你尝试调用的是 Func1(int),这在 A 中根本没有定义,编译时报错。
✅ 结论:
- 编译时由静态类型决定调用哪个函数(是否存在该函数签名)。
- 运行时由动态类型决定调用哪个版本(多态行为)。
- 所以
p->Func1(100)不合法,因为A中没这个函数签名。