(一)C++ - virtual
1. 虚函数:virtual关键字修饰的函数,用于被子类具有相同函数签名的同名函数所覆盖。
注:虚函数是具有函数体(函数实现)的。
class A{
public:
virtual void func(){
cout << "A" << endl;
}
};
class B: public A{
public:
void func(){
cout << "B" << endl;
}
};
int main(){
A *a = new B();
a->func(); // B
}
2. 纯虚函数:纯虚函数在类中是没有函数体(函数实现)的,只能以“ = 0; ”结尾(同样是用virtual修饰)。
注:纯虚函数是可以在抽象类外实现函数体的,不过我觉得这样做并没有什么意义,具体原因可以参看抽象类的特性。
class A{
public:
virtual void func() = 0;
};
void A::func(){
cout << "A" << endl;
}
3. 抽象类:含有纯虚函数的类。
(1)抽象类不可实例化。
(2)继承抽象类的子类必须覆盖(重写)虚函数。
class A{
public:
virtual void func() = 0;
};
void A::func(){
cout << "A" << endl; // 这个实现没有意义
}
class B: public A{
};
int main(){
A a;
a.func(); // object of abstract class type "A" is not allowed: -- function "A::func" is a pure virtual function
B b;
b.func(); // object of abstract class type "B" is not allowed: -- pure virtual function "A::func" has no overrider
}
4. 虚函数表
(1)建立时期:编译期(表格中的virtual functions地址是如何被建构起来的?在C++中,virtual functions(可经由其class object被调用)可以在编译时期获知。此外,这一组地址是固定不变的,执行期不可能新增或替换之。由于程序执行时,表格的大小和内容都不会改变,所以其建构和存取皆可以由编译器完全掌控,不需要执行期的任何介入。——《深度探索C++对象模型》)
(2)关于虚函数表的具体分析可参考:C++ 虚函数表解析
5. virtual关键字还有一个无关抽象的用法——虚基类
(1)虚基类:virtual关键字声明继承的父类,即便该基类在多条链路上被一个子类继承,但是该子类中只包含一个该虚基类的备份,虚基类主要用来解决继承中的二义性问题(菱形继承),这就是虚基类的作用所在。
(2)在每个子类的构造函数中必须显式调用该虚基类的构造函数,不管该虚基类是不是直接的父类。
(3)虚基类的构造函数的调用早于其他非虚基类的构造函数的调用。
(4)注:任何的类都可以作为虚基类。
#include <iostream>
using namespace std;
class Base{
public:
int a;
public:
Base(int a){
this->a = a;
cout << "Base constructor! " << endl;
}
~Base(){
cout << "Base deconstructor! " << endl;
}
};
//派生类1(声明Base为虚基类)
class Derive1: virtual public Base{
public:
Derive1(int a):Base(a){
cout << "Derive1 constructor! " << endl;
}
~Derive1(){
cout << "Derive1 deconstructor! " << endl;
}
int GetA(){
return a;
}
};
//派生类2(声明Base为虚基类)
class Derive2: virtual public Base{
public:
Derive2(int a): Base(a){
cout << "Derive2 constructor! " << endl;
}
~Derive2(){
cout << "Derive2 deconstructor! " << endl;
}
int GetA(){
return a;
}
};
//子派生类
class Derive12: public Derive1, public Derive2{
public:
Derive12(int a1, int a2, int a3): Derive1(a1), Derive2(a2), Base(a3){
cout << "Derive12 constructor! " << endl;
}
~Derive12(){
cout << "Derive12 deconstructor! " << endl;
}
};
int main()
{
Derive12 obj(1, 2, 3);
cout << "Derive12:a = " << obj.a << endl;
//得到从CDerive1继承的值
cout << "Derive1:a = " << obj.Derive1::GetA() << endl;
//得到从CDerive2继承的值
cout << "Derive2:a = " << obj.Derive2::GetA() << endl;
}
运行结果:
Base constructor!
Derive1 constructor!
Derive2 constructor!
Derive12 constructor!
Derive12:a = 3
Derive1:a = 3
Derive2:a = 3
Derive12 deconstructor!
Derive2 deconstructor!
Derive1 deconstructor!
Base deconstructor!
Derive12中的a不是来自Derive1或Derive2,而是来自一份从Base直接过来的拷贝,Derive12实例只有Base基类的一个备份。
(5)如果只有Derive2是对Base虚继承的话,那么Derive12是不会具有a成员变量的,Derive1的a是自己保持的,Derive2的a是从Base拷贝过来的。不过继承的两个Base类的备份的构造/析构都是最先/最迟调用的。
int main()
{
Derive12 obj(1, 2, 3);
// cout << "Base:a = " << obj.a << endl; // error: request for member 'a' is ambiguous
//得到从CDerive1继承的值
cout << "Derive1:a = " << obj.Derive1::GetA() << endl;
//得到从CDerive2继承的值
cout << "Derive2:a = " << obj.Derive2::GetA() << endl;
}
运行结果:
Base constructor!
Base constructor!
Derive1 constructor!
Derive2 constructor!
Derive12 constructor!
Derive1:a = 1
Derive2:a = 3
Derive12 deconstructor!
Derive2 deconstructor!
Derive1 deconstructor!
Base deconstructor!
Base deconstructor!
(6)如果Derive1和Derive2都不是对Base虚继承的话,那么Derive12就无法调用Base的构造函数,Derive1和Derive2各自保持自己的a成员变量,构造/析构也是要先/后调用Base类的构造/析构函数。
int main()
{
Derive12 obj(1, 2);
//cout << "Base:a = " << obj.a << endl; // error: request for member 'a' is ambiguous
//得到从CDerive1继承的值
cout << "Derive1:a = " << obj.Derive1::GetA() << endl;
//得到从CDerive2继承的值
cout << "Derive2:a = " << obj.Derive2::GetA() << endl;
}
运行结果:
Base constructor!
Derive1 constructor!
Base constructor!
Derive2 constructor!
Derive12 constructor!
Derive1:a = 1
Derive2:a = 2
Derive12 deconstructor!
Derive2 deconstructor!
Base deconstructor!
Derive1 deconstructor!
Base deconstructor!
(二)Java - 抽象类与接口
1. 抽象方法:abstract关键字修饰的方法,仅有声明而没有实现。
2. 抽象类&接口:
(1)抽象类:含有抽象方法的类
(2)接口:所有方法都是抽象方法的类
(3)抽象类与接口的区别:
- 抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract方法(但Java 8提供了接口的default方法,该方法可以在接口中实现);
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
- 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法(但Java 8提供了接口的静态方法并可以在接口中实现);
- 一个类只能继承(extends)一个抽象类,而一个类却可以实现(implements)多个接口。
参考文献:
【2】C++ 虚基类
【3】java抽象类和抽象方法