请注意,等边三角形类的初始化程序具有三个不同的步骤:
- 设置子类声明的属性值。
- 调用超类的初始化器。
- 更改超类定义的属性值。任何使用方法、getter 或 setter 的附加设置工作也可以在此时完成。
然后我在 Swift 和 C++ 中尝试下面的示例:
class Base {
init() {
print("Enter Base");
setUp();
print("Leave Base");
}
func setUp() -> Void {
print("base setUp()")
}
}
class Derived: Base {
let number: Int;
override init () {
number = 5;
}
override func setUp() -> Void {
print("derived setUp() \(number)")
}
}
let d = Derived()
let b = Base()
Enter Base
derived setUp() 5
Leave Base
Enter Base
base setUp()
Leave Base
#include <iostream>
#include <string>
class Base {
public:
Base() {
std::cout << "Enter Base" << std::endl;
setUp();
std::cout << "Leave Base" << std::endl;
}
virtual void setUp() {
std::cout << "base setUp()" << std::endl;
}
};
class Derived: public Base {
int number;
public:
Derived () {
number = 5;
}
void setUp() override {
std::cout << "derived setUp() " << number << std::endl;
}
};
int main()
{
Derived d = Derived();
Base b = Base();
}
Enter Base
base setUp()
Leave Base
Enter Base
base setUp()
Leave Base
根据上面的两个输出,很明显 C++ 和 Swift 在继承理念方面存在差异。有人可以解释为什么两种语言之间的行为不同(为什么 Swift 允许超类访问子类方法)?
我已经了解了C++中的vtable及其机制,但我对Swift一无所知,所以如果你能比较一下两种语言之间底层实现的差异就好了。
在 C++ 中,基类在派生类之前完全初始化。调用超类构造函数是派生类构造函数首先要做的事情之一。这意味着,如果您要在基类构造函数中调用派生类的
setUp
,setUp
将观察到未初始化的 number
。这是不可取的,因为编写 setUp
的人可能会假设所有字段都已初始化(尽管在调用该方法时不太可能发生这种情况 setUp
)。因此,构造函数中的虚拟方法调用被设计为静态分派。另请参阅这篇文章了解更多详细信息。
另一方面,Swift 以相反的顺序初始化类。首先初始化派生类。
super.init
只能在初始化派生类中声明的所有存储属性后才能调用。
class Derived: Base {
let number: Int
override init() {
super.init() // this is not allowed!
number = 5
}
}
因此,在基类初始化器中调用虚方法是安全的。当它被调用时,所有派生类存储的属性都保证被初始化。