C++&Java - 抽象

(一)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)多个接口。

 

参考文献:

【1】C++中的虚函数表是什么时期建立的?

【2】C++ 虚基类

【3】java抽象类和抽象方法

【4】深入理解Java的接口和抽象类

【5】Java 8的default方法能做什么?不能做什么?

【6】Java 8 新特性:接口的静态方法和默认方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值