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
中没这个函数签名。