由C++的多態想到 C++與Golang的一點區別

  1. 多態

有種說法,Golang沒有多態/繼承,它叫組合,比如:

type Base struct {
}

func (b *Base) sayHello() {
	b.Hello()
}

func (b *Base) Hello() {
	fmt.Println("Base Hello")
}

type Deverived struct {
	Base
}

func (d *Deverived) Hello() {
	fmt.Println("Deverived Hello")
}

func testDeverived() {
	d := new(Deverived)
	d.sayHello()
}

func main() {
	testDeverived()
}

將輸出,

Base Hello

爲什麼呢?因爲在testDeverived函數中,d的類型雖然是Deverived,但Deverived沒有實現sayHello方法,而在基類Base的sayHello方法中,此時類型是*Base,因此調用的會是基類的Hello方法。

  1. C++與Golang的map的區別

golang map是hash map, 而C++ map是red-black-tree map;
golang map是無序的, 且每次遍歷不穩定一致, 而C++ map是有序的, 且每次遍歷穩定一致,這個可以從代碼運行情況看出:

func testMap() {
	m := map[string]int{"hello": 1, "world": 2}
	for key, _ := range m {
		fmt.Println(key, m[key])
	}
}

以上代碼多次運行的結果有可能不一樣。

C++中的多態需要幾個條件:

  1. 基類函數前加上virtual;
  2. 子類實現同名函數,包括函數返回值,函數參數類型和參數個數都要一致;

虛函數是動態多態(好像有相對的靜態多態),是在運行時才確定的行爲。

// base.h
#ifndef BASE_H
#define BASE_H

class Base
{
public:
    Base();

    virtual void sayHi();
    void func();
};

#endif // BASE_H

// base.cpp
#include "base.h"
#include <iostream>
using namespace std;

Base::Base()
{

}

void Base::sayHi()
{
    cout<<"I'm Base"<<endl;
}

void Base::func()
{
    cout<<"Base::func()"<<endl;
}

// deverived.h
#ifndef DEVERIVED_H
#define DEVERIVED_H

#include "base.h"

class Deverived : public Base
{
public:
    Deverived();
    ~Deverived();

    void sayHi();
    void func();
};
#endif // DEVERIVED_H

// deverived.cpp
#include "deverived.h"
#include <iostream>
using namespace std;

Deverived::Deverived()
{

}

Deverived::~Deverived()
{
    cout<<__func__<<endl;
}

void Deverived::sayHi()
{
    cout<<"I'm Deverived"<<endl;
}

void Deverived::func()
{
    cout<<"Deverived::func()"<<endl;
}

以上代碼中,sayHi有用virtual修飾,是虛函數,而func沒有用virtual修飾,不是虛函數,func的輸出取決於調用對象的類型,觀察如下代碼輸出:

Base *b = new Deverived();
b->sayHi();
b->func();

Deverived *d = new Deverived();
d->sayHi();
d->func();
I'm Deverived
Base::func()
I'm Deverived
Deverived::func()

參考

Q:類構造函數可以是虛函數嗎?
A:不可以,虛函數的調用需要虛函數表指針,而該指針存放在對象的內容空間中;若構造函數聲明爲虛函數,那麼由於對象還未創建,還沒有內存空間,更沒有虛函數表地址用來調用虛函數——構造函數了。

Q:爲什麼析構函數可以爲虛函數,如果不設爲虛函數可能會存在什麼問題?
A:首先析構函數可以爲虛函數,而且當要使用基類指針或引用調用子類時,最好將基類的析構函數聲明爲虛函數,否則可以存在內存泄露的問題。
舉例說明:
子類B繼承自基類A;A *p = new B; delete p;
1) 此時,如果類A的析構函數不是虛函數,那麼delete p;將會僅僅調用A的析構函數,只釋放了B對象中的A部分,而派生出的新的部分未釋放掉。
2) 如果類A的析構函數是虛函數,delete p; 將會先調用B的析構函數,再調用A的析構函數,釋放B對象的所有空間。
補充: B *p = new B; delete p;時也是先調用B的析構函數,再調用A的析構函數。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章