c++面向對象: public 繼承, 虛方法, 動態(tài)綁定


title: 'c++面向對象: public 繼承, 虛方法, 動態(tài)綁定'
date: 2018-11-04 13:00:34
tags:

  • oop
  • public inheritance
  • virtual methods
  • dynamic binding
    categories: cpp
    blog: https://withas.me

學了java中的面向對象后再來學習c++的面向對象, 對兩者設計理念上的差異有不少的體會: c++更多地考慮了程序的運行效率(默認靜態(tài)綁定, 沒有垃圾回收等), 把很多繁瑣的操作留給了使用者; 而java是貫徹了面向對象的思維, 有一種徹頭徹尾的面向對象的感覺, 而把一些背后的機制向使用者隱藏了. 這篇blog主要記錄c++中public繼承相關的內容.

Public Inheritance

c++中有public, protected, private三種繼承方式, 其中public是最常用的.

public繼承描述的是一種is-a的關系, derived-class是base-class的一個子集且一般是真子集. 比如從Student類可以派生出Cadre類, Cadre一定是Student, 而Student不一定是Cadre. 這種關系是單向的, 不具有對稱性.

class Student {
private:
   int id;
   string name;
public:
   int getId() const { return id; }

   void setId(int id) { Student::id = id; }

   const string &getName() const { return name; }

   void setName(const string &name) { Student::name = name; }
};

class Cadre : public Student {
private:
   string duty;
public:
   const string &getDuty() const { return duty; }

   void setDuty(const string &duty) { Cadre::duty = duty; }
};

Cadre由Student派生, 則Cadre直接繼承了Student的所有public成員. Private成員也成為了derived-class的一部分, 但是只能通過base-class的public和protected方法訪問. 例如Cadre從Student那里繼承了name, 但是不能直接訪問, 只能通過Student::getName訪問.

class Cadre : public Student {
    ...
public:
    ...
    void showInfo() const {
        cout << Student::getName() << endl;
        cout << Student::getId() << endl;
        cout << duty << endl;
    }
};

Constructos and Destructos

class Student {
private:
    int id;
    string name;
public:
    Student(int id, const string &name) : id(id), name(name) {}
    ...
};

class Cadre : public Student {
private:
    string duty;
public:
    Cadre(int id, const string &name, const string &duty) : Student(id, name), duty(duty) {}
    ...
};

Derived-class不能直接直接訪問繼承自base-class的private成員, 所以不能在constructors里直接對它們初始化, 必須借助base-class的constructors. 并且base-class必須先于derived-class被構造. 如果要使用base-class的constructor有參數(shù)則必須在derived-class的constructor中以初始化參數(shù)列表的方式調用, 如上例. 如果不在derived-class的constructor中顯式地調用base-class的constructor, 則默認使用base-class的不帶參數(shù)的constructor(不存在則編譯會產生錯誤).

Base-class會先于derived-class被構造. 析構的方向恰與之相反, derived-class的destructor先于base-class被執(zhí)行.

Is-a

前面提到過public繼承是一種is-a的關系, 所以base-class的指針可以指向derived-class的對象, base-class的引用可以引用derived-class的對象.

    Student *pc = new Cadre(1, "J", "monitor");
    Student &rc = *pc;

但是通過base-class的指針和引用只能使用base-class存在的屬性, 如:

    rc.getId();//ok
    rc.getDuty();//error

Virtual Methods

Redefine

有時候為了讓derived-class有不同的功能我們可能需要重寫base-class的方法.

class Student {
private:
    int id;
    string name;
public:
    void showInfo() const {
        cout << name << endl;
        cout << id << endl;
    }

};

class Cadre : public Student {
private:
    string duty;
public:
    void showInfo() const {
        cout << Student::getName() << endl;
        cout << Student::getId() << endl;
        cout << duty << endl;
    }
};

這樣我們對Student對象和Cadre的對象分別調用showInfo的時候就會輸出不同的結果.

但是這樣會產生一個問題:

    Cadre *pc = new Cadre(1, "J", "monitor");
    Student &rc = *pc;
    pc->showInfo();
    cout << endl;
    rc.showInfo();
/*output:
J
1
monitor

J
1*/

同一個Cadre對象, 用它自己的指針調用和用Student的引用調用時結果不一樣. 查看輸出結果可以知道用Student的引用調用的時候運行的是Student::showInfo, 而非在Cadre中重寫的方法.

Virtual methods

為了讓base-class引用的derived-class可以調用derived-class重寫的方法, 我們可以使用virtual.

class Student {
public:
    ...
    virtual void showInfo() const {
        cout << name << "\t" << id;
    }
};

class Cadre : public Student {
public:
    ...
    virtual void showInfo() const {
        Student::showInfo();//reusing code 
        cout << "\t" << duty;
    }
};

再來看看輸出結果:

    Cadre *pc = new Cadre(1, "J", "monitor");
    Student &rc = *pc;//Student reference
    pc->showInfo();
    cout << endl;
    rc.showInfo();//invoke Cadre::showInfo() instead of Student::showInfo()
/*output:
J       1       monitor
J       1       monitor
*/

如果不使用virtual, 調用的方法取決于引用或者指針的類型; 使用, 則由引用或者指向的對象本身決定, 也就是說可以使用base-class的引用或者指針調用在derived-class的重寫的方法.

當base-class中的方法被標記為virtual時, derived-class中重寫的方法也被自動標記為virtual. 不過一般為了方便閱讀, 也將derived-class中重寫的方法標記為virtual.

對于base-class中在derived-class被重寫的方法一般都會被標記為virtual. 另外有derived-class的base-class的destructors最好被標記為virtual.

class A {
private:
    string *a;
public:
    A() { a = new string("Hello"); }

    ~A() { delete a; }
};

class B : public A {
private:
    string *b;
public:
    B() : A() { b = new string("world"); }

    ~B() { delete b; }
};

int main() {
    A *pb = new B();
    delete pb;
}

delete pb時會直接調用A::~A(), B中b的內存沒有成功被釋放. 所以為了內存的正確釋放最好把base-class的destructor標記為virtual.

Static and Dynamic Binding

綁定是指調用哪個具體的方法. 例如上面不加virtual關鍵詞時rc.showInfo會調用引用類型的showInfo, 即Student::showInfo; 而加上virtual會調用Cadre::showInfo.

    Cadre *pc = new Cadre(1, "J", "monitor");
    Student &rc = *pc;//Student reference
    rc.showInfo();

static binding是指在編譯的時候就決定了調用的方法, 而dynamic binding是在程序運行過程中才能決定(因為對于一個base-class的指針可能指向base-class也可能指向derived-class). 這種dynamic binding的特性被稱為多態(tài).

c++默認的綁定方式是static binding. 對于dynamic binding對象內部會增加一個virtual function table (vtbl), 它負責記錄這個對象應該調用的方法. 盡管dynamic binding看上去有更多的好處, 然而c++默認的綁定方式是static binding, 這樣程序的運行效率更高. 與之對比, java中沒有virtual function, 它默認的就是動態(tài)綁定.

dynamic binding產生的多態(tài)有什么好處呢? 看下面這個例子:

class Class {
private:
    vector<Student *> students;
public:
    void addStudent(Student *student) {
        students.push_back(student);
    }

    void listStudent() {
        for (auto &s: students) {
            s->showInfo();
            cout << endl;
        }
    }
};
//
Class cla;
Student a = Student(1, "Y");
Cadre b = Cadre(2, "Z", "monitor");
Student c = Student(3, "W");
cla.addStudent(&a);
cla.addStudent(&b);
cla.addStudent(&c);
cla.listStudent();
/*output
Y   1
Z   2   monitor
W   3
*/

在Class中我們全都儲存的是Student的指針, 在打印Student List的時候就可以根據(jù)指向的具體對象輸出相應的信息.

Code

完整演示代碼:

#include <iostream>
#include <vector>

using namespace std;

class Student {
private:
    int id;
    string name;
public:
    Student(int id, const string &name) : id(id), name(name) {}

    int getId() const { return id; }

    void setId(int id) { Student::id = id; }

    const string &getName() const { return name; }

    void setName(const string &name) { Student::name = name; }

    virtual void showInfo() const {
        cout << name << "\t" << id;
    }

    virtual ~Student() {}
};

class Cadre : public Student {
private:
    string duty;
public:
    Cadre(int id, const string &name, const string &duty) : duty(duty), Student(id, name) {}

    const string &getDuty() const { return duty; }

    void setDuty(const string &duty) { Cadre::duty = duty; }

    virtual void showInfo() const {
        Student::showInfo();
        cout << "\t" << duty;
    }

};

class Class {
private:
    vector<Student *> students;
public:
    void addStudent(Student *student) {
        students.push_back(student);
    }

    void listStudent() {
        for (auto &s: students) {
            s->showInfo();
            cout << endl;
        }
    }
};

int main() {
    Cadre *pc = new Cadre(1, "J", "monitor");
    Student &rc = *pc;
    pc->showInfo();
    cout << endl;
    rc.showInfo();
    delete pc;
    cout << endl;
    Class cla;
    Student a = Student(1, "Y");
    Cadre b = Cadre(2, "Z", "monitor");
    Student c = Student(3, "W");
    cla.addStudent(&a);
    cla.addStudent(&b);
    cla.addStudent(&c);
    cla.listStudent();
    return 0;
}

懶惰的我終于又開始更新了.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末宵喂,一起剝皮案震驚了整個濱河市烧董,隨后出現(xiàn)的幾起案子拟枚,更是在濱河造成了極大的恐慌,老刑警劉巖蒙幻,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡阀捅,警方通過查閱死者的電腦和手機宿饱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門熏瞄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谬以,你說我怎么就攤上這事强饮。” “怎么了为黎?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵邮丰,是天一觀的道長。 經常有香客問我铭乾,道長剪廉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任片橡,我火速辦了婚禮妈经,結果婚禮上,老公的妹妹穿的比我還像新娘捧书。我一直安慰自己吹泡,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布经瓷。 她就那樣靜靜地躺著爆哑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舆吮。 梳的紋絲不亂的頭發(fā)上揭朝,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天队贱,我揣著相機與錄音,去河邊找鬼潭袱。 笑死柱嫌,一個胖子當著我的面吹牛,可吹牛的內容都是我干的屯换。 我是一名探鬼主播编丘,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彤悔!你這毒婦竟也來了嘉抓?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤晕窑,失蹤者是張志新(化名)和其女友劉穎抑片,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杨赤,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡敞斋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了望拖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渺尘。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖说敏,靈堂內的尸體忽然破棺而出鸥跟,到底是詐尸還是另有隱情,我是刑警寧澤盔沫,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布医咨,位于F島的核電站,受9級特大地震影響架诞,放射性物質發(fā)生泄漏拟淮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一谴忧、第九天 我趴在偏房一處隱蔽的房頂上張望很泊。 院中可真熱鬧,春花似錦沾谓、人聲如沸委造。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昏兆。三九已至,卻和暖如春妇穴,著一層夾襖步出監(jiān)牢的瞬間爬虱,已是汗流浹背隶债。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留跑筝,地道東北人死讹。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像曲梗,于是被迫代替她去往敵國和親回俐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容