天天看點

【C++ Primer】第十三章 類繼承

一,類繼承

        定義:從已有的類派生出新的類,而派生類繼承了原有類的特征,包括方法。

        目标:提供可重用的代碼

二,一個簡單的基類

<strong><span style="font-size:18px;">#include <iostream>

#include <cstring>

using namespace std;

class student      //基類

{

private:

char name[20];

int num;

int age;

public:

student(const char *m_name,const int m_num,const int m_age);

~student();

virtual void display();//派生類有重載 故采用虛函數形式

};

class graduate:public student  //派生類    注意寫法

char major[20];

int thesis;

graduate(const char *m_major,const int m_thesis,student &st);

~graduate();

void display();

void setThesis(int i);  

student::student(const char *m_name,const int m_num,const int m_age)

strcpy(name,m_name);

num=m_num;

age=m_age;

}

student::~student()

cout<<"student is over"<<endl;

void student::display()

cout<<"name:"<<name<<"\nnum:"<<num<<"\nage"<<age<<endl;

graduate::graduate(const char* m_major, const int m_thesis, student& st):student(st)

{ //特别注意:由于派生類不能直接使用基類私有成員,是以必須使用基類構造函數

st.display();

strcpy(major,m_major);

thesis=m_thesis;

graduate::~graduate()

cout<<"graduate is over"<<endl;

void graduate::display()

cout<<"major:"<<major<<"\nthesis:"<<thesis<<endl;

void graduate::setThesis(int i)

thesis=i;

int main()

student st("tianshuai",1,18);

cout<<"**************\n";

graduate gd("compuater",5,st);

gd.display();

return 0;

</span></strong>輸出:<strong><span style="font-size:18px;">name:tianshuainum:1

age18

**************

name:tianshuai

num:1

major:compuater

thesis:5

graduate is over

student is over

</span></strong>

【簡介】1)繼承類 繼承了基類的私有成員,但是不能通過繼承類的對象直接通路私有成員

               2)派生類不能直接通路基類私有成員,派生類構造函數必須使用基類構造函數

               3)派生類構造函數特點:基類對象首先被建立  建立graduate   gd(); student 對象先被建立

                                                           建立派生類對象時,先調用基類構造函數,再調用派生類構造函數

                                                            派生類對象過期時,先析構派生類,再析構基類

              4)不能講基類對象和位址賦給派生類 

                        student st;

                        graduate  &gd=st;  //  Not  Allow

                        graduate  *gd=st;   //  Not  Allow

                    如果允許的話,則基類可以通路派生類成員,但通路基類沒有的成員,對于基類對象來說是沒有意義的

                        st.setThesis(int i)   //因為基類沒有該方法

三,繼承----- is-a 關系

        繼承方式:共有繼承、私有繼承、保護繼承

        共有繼承建立一種is-a關系:派生類對象也是一個基類對象,可以對基類對象執行的任何操作,也可以對派生類對象執行

        例如:Fruit 是水果類,有重量和熱量。Banana是派生類,包含重量和熱量外,還添加專門香蕉成員,這些成員通常不用于Fruit.

四,多态共有繼承

        兩種實作機制:1>在派生類中重新定義基類方法。2>使用虛方法

        不存在虛拟構造函數,但基類的虛拟析構函數是必要的。請看下例:<strong><span style="font-size:18px;">class ClxBase{public:

ClxBase(){cout << "Output from the con ClxBase!" << endl;};

virtual ~ClxBase(){cout << "Output from the destructor of class ClxBase!" << endl;};

virtual void DoSomething(){ cout << "Do something in class ClxBase!" << endl; };

class ClxDerived : public ClxBase{

ClxDerived() {cout << "Output from the con ClxDerived!" << endl;};

~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };

void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };

void main()

<span style="color:#ff0000;">ClxBase</span> *pTest = new <span style="color:#ff0000;">ClxDerived</span>; //基類的指針 引用派生類對象

pTest->DoSomething(); //調用派生類的方法

delete pTest;

}</span></strong>

輸出:Do something in class ClxDerived!

           Output from the destructor of class ClxDerived!

這個很簡單,非常好了解。

但是,如果把類ClxBase析構函數前的virtual去掉,那輸出結果就是下面的樣子了:

           Do something in class ClxDerived!

    也就是說,類ClxDerived的析構函數根本沒有被調用!一般情況下類的析構函數裡面都是釋放記憶體資源,而析構函數不被調用的話就會造成記憶體洩漏。我想所有的C++程式員都知道這樣的危險性。當然,如果在析構函數中做了其他工作的話,那你的所有努力也都是白費力氣。

    當然,并不是要把所有類的析構函數都寫成虛函數。因為當類裡面有虛函數的時候,編譯器會給類添加一個虛函數表,裡面來存放虛函數指針,這樣就會增加類的存儲空間。是以,隻有當一個類被用來作為基類的時候,才把析構函數寫成虛函數。

五,靜态聯編和動态聯編

        函數名聯編:将源代碼中函數調用解釋為執行特定的函數代碼塊

        但是在C++中由于函數重載,編譯器必須檢視函數參數以及函數名才能确定使用哪個函數

        靜态聯編:在編譯過程中進行聯編

        但是虛函數使這項工作變得更困難,使用哪一個函數不能在編譯時确定

        動态聯編:程式運作時選擇正确虛方法的代碼

        1)為什麼有兩種類型聯編,為什麼 靜态聯編為預設

              動态聯編讓您能夠重新定義類方法,而靜态聯編則很差,但靜态聯編效率高。

              如果派生類不重新定義基類任何方法,則不需要動态聯編。僅僅将那些派生類需要重新定義的函數定義為虛拟的

        2)虛函數工作原理

              編譯器給每個對象添加一個隐藏成員,其中儲存一個該函數位址的指針,這種數組成為虛函數表。

              調用虛函數時,檢視存儲在對象中虛拟函數表位址,然後轉向相應函數位址表,如果使用類聲明中定義的第一個虛函數,則程式将使用數組中的第一個函數位址,并執行具有該位址的函數。

       3)注意事項

              如果重新定義繼承的方法,則應確定與原來的原型完全相同。

              如果基類聲明被重載了,則應在派生類中重新定義所有基類版本

六,抽象基類ABC(abstract base class)

        說明:某幾個子類的所公有的資料和方法抽象出來組成一個抽象基類,然後從抽象基類中派生出這幾個子類,可以通過基類指針數組同時管理這幾個子類。對于每個子類中的不同方法,可以将該方法在抽象基類中定義為純虛函數的方式,同時在各子類中将該方法定義為虛函數。     

         #include <iostream>using namespace std;

const double pi=3.14;

class BaseEllipse //抽象基類 含有圓跟橢圓的公共成員

double x;

double y;

BaseEllipse(double t_x=0,double t_y=0)

{

x=t_x;

y=t_y;

}

virtual ~BaseEllipse(){}

void Move(int nx,int ny)

x=nx;

y=ny;

cout<<"中心坐标為:"<<"("<<x<<","<<y<<")"<<endl;

<strong><span style="font-size:18px;"></span></strong><pre name="code" ><span style="color:#ff0000;">virtual void Area()const=0;//純虛函數結尾處為 “=0” 在類中可以隻定義,不用實作</span>};class Circle:public BaseEllipse //圓{private: double r;public: Circle(double t_x=0,double

t_y=0,double t_r=0); //半徑和中心坐标 Circle(const BaseEllipse & ba,double t_r=0); void Area()const;};class Ellipse:public BaseEllipse //橢圓{private: double a; double b;public: Ellipse(double t_x=0,double t_y=0,double t_a=0,double t_b=0); Ellipse(const BaseEllipse

& p,double t_a=0,double t_b=0); void Area()const;};/*圓*/Circle::Circle(double t_x,double t_y,double t_r):BaseEllipse(t_x,t_y){ r=t_r;}Circle::Circle(const BaseEllipse & ba,double t_r):BaseEllipse(ba){ r=t_r;}void Circle::Area()const{ cout<<pi*r*r<<endl;}/*橢圓*/Ellipse::Ellipse(double

t_x,double t_y,double t_a,double t_b):BaseEllipse(t_x,t_y){ a=t_a; b=t_b;}Ellipse::Ellipse(const BaseEllipse & ba,double t_a,double t_b):BaseEllipse(ba){ a=t_a; b=t_b;}void Ellipse::Area()const{ cout<<0.5*a*b<<endl;}int main(){ Circle c1(0,0,5); c1.Move(1,2);

c1.Area(); Ellipse e1(0,0,7,8); e1.Move(3,4); e1.Area(); return 0;}

繼續閱讀