C++面向對象的繼承與多態(tài)

[toc]

繼承

? 繼承,可以讓子類擁有父類的所有成員(變量\函數(shù))

image-20210403150553278

對象的內存布局

struct Person {

};

struct Student : Person {
    int m_no;
};

struct GoodStudent : Student {
    int m_money;
};

? 父類的成員變量在前涉兽,子類的成員變量在后

image-20210403151255004

成員訪問權限

? 成員訪問權限锣咒、繼承方式有3種

  • public:公共的,任何地方都可以訪問(struct默認)

  • protected:子類內部胁后、當前類內部可以訪問

  • private:私有的挟阻,只有當前類內部可以訪問(class默認)

? 子類內部訪問父類成員的權限琼娘,是以下2項中權限最小的那個

  • 成員本身的訪問權限

  • 上一級父類的繼承方式

? 開發(fā)中用的最多的繼承方式是public,這樣能保留父類原來的成員訪問權限

? 訪問權限不影響對象的內存布局

struct Person {
private:
   int m_age;

public:
   void setAge(int age) {
      m_age = age;
   }

   int getAge() {
      return m_age;
   }
};

struct Student : public Person {

};

struct GoodStudent : public Student {
   void work() {

   }
};
class Person {
private:
   int m_age;
public:
   void run() {

   }
};

struct Student : public Person {

};

class GoodStudent : public Student {

};

初始化列表

? 特點

  • 一種便捷的初始化成員變量的方式

  • 只能用在構造函數(shù)中

  • 初始化順序只跟成員變量的聲明順序有關

struct Person {
   int m_age;
   int m_height;

   /*Person(int age, int height) {
      m_age = age;
      m_height = height;
   }*/

   // 語法糖
   Person(int age = 0, int height = 0) :m_age(age), m_height(height) {}
};
int myAge() {
    cout << "myAge()" << endl;
    return 10;
}
int myHeight() {
    cout << "myHeight()" << endl;
    return 10;
}
struct Person {
    int m_age;
    int m_height;
    Person(int age , int height):m_age(myAge()), m_height(myHeight()){
        
    }
};
int main() {
    Person person(18, 180);

    cout << person.m_age << endl;
    cout << person.m_height << endl;
}

myAge()
myHeight()
10
10

  • 初始化順序只跟成員變量的聲明順序有關
image-20210405132725878
image-20210405132742522

說明輸出和定義在類中成員的順序有關附鸽,和代碼Person(int age , int height):m_age(myAge()), m_height(myHeight())定義的順序無關脱拼。

初始化列表與默認參數(shù)配合使用

? 如果函數(shù)聲明和實現(xiàn)是分離的

  • 初始化列表只能寫在函數(shù)的實現(xiàn)中

  • 默認參數(shù)只能寫在函數(shù)的聲明中

struct Person {
   int m_age;
   int m_height;
   Person(int age = 0, int height = 0);
};

Person::Person(int age, int height) :m_age(age), m_height(height) {

}
int main() {
    Person person1;
    Person person2(17);
    Person person(18, 180);
}

構造函數(shù)的互相調用

image-20210405134229541
struct Person {
    int m_age;
    int m_height;

    /*Person() :Person(10, 20) {

    }*/

    Person() :Person(10, 20) {
        // 創(chuàng)建了一個臨時的Person對象
        // Person(10, 20);
        /*Person person;
        person.m_age = 10;
        person.m_height = 20;*/
    }

    Person(int age, int height) {
        this->m_age = age;
        this->m_height = height;
    }
};

int main() {

    Person person;
    cout << person.m_age << endl;
    cout << person.m_height << endl;

    return 0;
}

父類的構造函數(shù)

? 子類的構造函數(shù)默認會調用父類的無參構造函數(shù)

? 如果子類的構造函數(shù)顯式地調用了父類的有參構造函數(shù),就不會再去默認調用父類的無參構造函數(shù)

? 如果父類缺少無參構造函數(shù)坷备,子類的構造函數(shù)必須顯式調用父類的有參構造函數(shù)

struct Person {
   Person() {
      cout << "Person::Person()" << endl;
   }
   ~Person() {
      cout << "Person::~Person()" << endl;
   }
};

struct Student : Person {
   Student() {
      // call Person::Person
      cout << "Student::Student()" << endl;
   }
   ~Student() {
      cout << "Student::~Student()" << endl;
      // call Person::~Person
   }
};
int main() {

   {
      Student student;
   }

   // Student student(18, 34);

   return 0;
}
/*
Person::Person()
Student::Student()
Student::~Student()
Person::~Person()
*/
  • 子類的構造函數(shù)默認會調用父類的無參構造函數(shù)
image-20210405135555425

構造熄浓、析構順序

image-20210405140202765

多態(tài)

? 默認情況下,編譯器只會根據(jù)指針類型調用對應的函數(shù)省撑,不存在多態(tài)

? 多態(tài)是面向對象非常重要的一個特性

  • 同一操作作用于不同的對象赌蔑,可以有不同的解釋,產生不同的執(zhí)行結果

  • 在運行時竟秫,可以識別出真正的對象類型娃惯,調用對應子類中的函數(shù)

? 多態(tài)的要素

  • 子類重寫父類的成員函數(shù)(override

  • 父類指針指向子類對象

  • 利用父類指針調用重寫的成員函數(shù)

父類指針、子類指針

image-20210405140630028
struct Person {
   int m_age;
};

struct Student : public Person {
   int m_score;
};

void test() {
   // 父類指針 指向 子類對象
// Person *p = new Student();
// p->m_age = 10;

   Student *p = (Student *) new Person();
   p->m_age = 10;
   p->m_score = 100;//危險鸿摇,動了age后面的4個字節(jié),修改了非自己內存的數(shù)據(jù)
}
struct Animal {
   virtual void speak() {
      cout << "Animal::speak()" << endl;
   }
   virtual void run() {
      cout << "Animal::run()" << endl;
   }
};

struct Dog : Animal {
   // 重寫(覆寫劈猿、覆蓋拙吉、override)
   void speak() {
      cout << "Dog::speak()" << endl;
   }
   void run() {
      cout << "Dog::run()" << endl;
   }
};

struct Cat : Animal {
   void speak() {
      cout << "Cat::speak()" << endl;
   }
   void run() {
      cout << "Cat::run()" << endl;
   }
};

struct Pig : Animal {
   void speak() {
      cout << "Pig::speak()" << endl;
   }
   void run() {
      cout << "Pig::run()" << endl;
   }
};
  • 同一操作作用于不同的對象潮孽,可以有不同的解釋,產生不同的執(zhí)行結果
//同一操作作用于不同的對象筷黔,可以有不同的解釋往史,產生不同的執(zhí)行結果
void liu(Animal *p) {
   p->speak();
   p->run();
}

int main() {

    liu(new Dog());
    liu(new Cat());
    liu(new Pig());
    

    return 0;
}
/*
Dog::speak()
Dog::run()
Cat::speak()
Cat::run()
Pig::speak()
Pig::run()
*/
  • 默認情況下,編譯器只會根據(jù)指針類型調用對應的函數(shù)佛舱,不存在多態(tài)
struct Animal {
   void speak() {
      cout << "Animal::speak()" << endl;
   }
   void run() {
      cout << "Animal::run()" << endl;
   }
};
/*
Animal::speak()
Animal::run()
Animal::speak()
Animal::run()
Animal::speak()
Animal::run()
*/
**不存在多態(tài)**

虛函數(shù)

? C++中的多態(tài)通過虛函數(shù)(virtual function)來實現(xiàn)

? 虛函數(shù):被virtual修飾的成員函數(shù)

? 只要在父類中聲明為虛函數(shù)椎例,子類中重寫的函數(shù)也自動變成虛函數(shù)(也就是說子類中可以省略virtual關鍵字

struct Animal {
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
};
int main() {
   Animal *p = new Pig();
   p->speak();
   p->run();
}
/*
Pig::speak()
Pig::run()
*/
虛函數(shù)

虛表

? 虛函數(shù)的實現(xiàn)原理是虛表,這個虛表里面存儲著最終==需要調用的虛函數(shù)地址==请祖,這個虛表也叫虛函數(shù)表

image-20210405144422323
struct Animal {
    int m_age;
    void speak() {
        cout << "Animal::speak()" << endl;
    }
    void run() {
        cout << "Animal::run()" << endl;
    }
};

struct Cat : Animal {
    int m_life;
    void speak() {
        cout << "Cat::speak()" << endl;
    }
    void run() {
        cout << "Cat::run()" << endl;
    }
};

int main() {

    cout<< sizeof (Cat)<<endl;
}
    

8

struct Animal {
    int m_age;
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
    void run() {
        cout << "Animal::run()" << endl;
    }
};

virtual void speak()

cout<< sizeof (Cat)<<endl;

16

image-20210405145641286

虛表里面存儲著最終需要調用的虛函數(shù)地址订歪,這個虛表也叫虛函數(shù)表

(x86環(huán)境的圖)

x86環(huán)境的圖
int main() {


    Animal *cat = new Cat();
    cat->m_age = 20;
    cat->speak();
    cat->run();
}

下面我們通過上述代碼的內存數(shù)據(jù)及匯編指令,來窺探虛表的原理

cat堆內存數(shù)據(jù)

image-20210405152135545
image-20210405152143497
image-20210405152904006
image-20210405152915125
image-20210405154238056
image-20210405154515650

總結一下Animal類由于存在虛函數(shù)virtual void speak()virtual void run() 所以Animal對象創(chuàng)建完成后肆捕,前8個字節(jié)是一個地址指針刷晋,指向了一個虛表的地址

image-20210405154943917

由于有兩個虛函數(shù),所以指向的地址慎陵,前8位speak函數(shù)的地址眼虱,后8個字節(jié)為run函數(shù)的地址。

Animal 虛表地址之后席纽,跟著為屬性成員的地址捏悬,movl $0x14, 0x8(%rax) 修改m_age的內存地址的值為20.

0x10fe79035 <+85>: callq *(%rcx)0x10fe79041 <+97>: callq *0x8(%rcx)都是間接調用,先找到虛表地址润梯,從內存地址中过牙,取出函數(shù)地址間接調用到對應類對象真實實現(xiàn)函數(shù)的地址上。

調用父類的成員函數(shù)實現(xiàn)

image-20210405162029567
image-20210405162356201
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末仆救,一起剝皮案震驚了整個濱河市抒和,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彤蔽,老刑警劉巖摧莽,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異顿痪,居然都是意外死亡镊辕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門蚁袭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來征懈,“玉大人,你說我怎么就攤上這事揩悄÷舭ィ” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長亏娜。 經常有香客問我焕窝,道長,這世上最難降的妖魔是什么维贺? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任它掂,我火速辦了婚禮,結果婚禮上溯泣,老公的妹妹穿的比我還像新娘虐秋。我一直安慰自己,他們只是感情好垃沦,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布客给。 她就那樣靜靜地躺著,像睡著了一般栏尚。 火紅的嫁衣襯著肌膚如雪起愈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天译仗,我揣著相機與錄音抬虽,去河邊找鬼。 笑死纵菌,一個胖子當著我的面吹牛阐污,可吹牛的內容都是我干的。 我是一名探鬼主播咱圆,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼笛辟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了序苏?” 一聲冷哼從身側響起手幢,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忱详,沒想到半個月后围来,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡匈睁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年监透,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片航唆。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡胀蛮,死狀恐怖,靈堂內的尸體忽然破棺而出糯钙,到底是詐尸還是另有隱情粪狼,我是刑警寧澤退腥,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站再榄,受9級特大地震影響阅虫,放射性物質發(fā)生泄漏。R本人自食惡果不足惜不跟,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望米碰。 院中可真熱鬧窝革,春花似錦、人聲如沸吕座。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吴趴。三九已至漆诽,卻和暖如春乒裆,著一層夾襖步出監(jiān)牢的瞬間而涉,已是汗流浹背糯俗。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工疯淫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留族购,地道東北人劈伴。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓豫柬,卻偏偏與公主長得像八堡,于是被迫代替她去往敵國和親陨闹。 傳聞我的和親對象是個殘疾皇子楞捂,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內容