1. 程式人生 > >C++語言學習(十)——繼承與派生

C++語言學習(十)——繼承與派生

child mem 公有 char 單繼承 同名成員函數 重定義 重載函數 顯示

C++語言學習(十)——繼承與派生

一、類之間的關系

1、類之間的組合關系

組合關系是整體與部分的關系。
組合關系的特點:
A、將其它類的對象作為當前類的成員使用
B、當前類的對象與成員對象的生命周期相同
C、成員對象在用法上與普通對象相同
Computer類由其它多個部件類組合而成,當Computer銷毀時,其它部件對象同時銷毀。

#include <iostream>

using namespace std;

class Memory
{
public:
    Memory()
    {
        cout << "Memory()" << endl;
    }
    ~Memory()
    {
        cout << "~Memory()" << endl;
    }
};

class Disk
{
public:
    Disk()
    {
        cout << "Disk()" << endl;
    }
    ~Disk()
    {
        cout << "~Disk()" << endl;
    }
};

class CPU
{
public:
    CPU()
    {
        cout << "CPU()" << endl;
    }
    ~CPU()
    {
        cout << "~CPU()" << endl;
    }
};

class MainBoard
{
public:
    MainBoard()
    {
        cout << "MainBoard()" << endl;
    }
    ~MainBoard()
    {
        cout << "~MainBoard()" << endl;
    }
};

class Computer
{
    Memory mMem;
    Disk mDisk;
    CPU mCPU;
    MainBoard mMainBoard;
public:
    Computer()
    {
        cout << "Computer()" << endl;
    }
    void power()
    {
        cout << "power()" << endl;
    }
    void reset()
    {
        cout << "reset()" << endl;
    }
    ~Computer()
    {
        cout << "~Computer()" << endl;
    }
};

int main(int argc, char *argv[])
{
    Computer c;
    c.reset();
    return 0;
}

2、類之間的繼承關系

繼承關系是類之間的父子關系。繼承關系的特點如下:
A、子類擁有父類的所有屬性和行為
B、子類也是一種特殊的父類
C、子類對象可以當父類對象使用
D、子類中可以添加父類沒有的屬性和方法
E、子類對象可以直接初始化為父類對象
F、子類對象可以直接賦值給父類對象
G、繼承是面向對象編程中代碼復用的重要手段

#include <iostream>

using namespace std;

class Parent
{
public:
    Parent(int i = 0)
    {
        member = i;
    }
    void method()
    {
        cout << "member = " << member << endl;
    }
private:
    int member;
};

class Child : public Parent
{
public:
    Child(int i = 0, int j = 0):Parent(i)
    {
        childMember = j;
    }
    void childMethod()
    {
        method();
        cout << "childMember = "<< childMember << endl;
    }
private:
    int childMember;
};

int main(int argc, char *argv[])
{
    Child child(1,2);
    child.method();
    child.childMethod();
    return 0;
}

3、類成員的訪問級別選擇

技術分享圖片
定義類時根據類的設計需求確定成員的訪問級別,規則如下:
A、public修飾的成員可以被外部訪問。
B、protected修飾的成員不可以被外部訪問,但可以被子類訪問。
C、private修飾的成員不可以被外部和子類訪問。

#include <iostream>

using namespace std;

class Parent
{
public:
    Parent(int a = 0, int b = 0, int c = 0)
    {
       pub = a;
       pro = b;
       pri = c;
    }
    void method()
    {
        cout << "pub = " << pub << endl;
        cout << "pro = " << pro << endl;
        cout << "pri = " << pri << endl;
    }
public:
    int pub;
protected:
    int pro;
private:
    int pri;
};

class Child : public Parent
{
public:
    Child(int a = 0, int b = 0):Parent(a, b)
    {

    }
    void childMethod()
    {
        pub = 100;
        pro = 200;
        //pri = 300;//error,子類不可見
    }
};

int main(int argc, char *argv[])
{
    Parent parent(1,2);
    parent.pub = 1000;
    //parent.pro = 2000;//error,外部不可見
    //parent.pri = 3000;//error,外部不可見
    Child child(1,2);
    child.pub = -1000;
    //child.pro = -2000;//error,外部不可見
    //child.pri = -3000;//error,外部不可見
    child.childMethod();
    return 0;
}

二、繼承

1、繼承簡介

在C++編程中,軟件可重用性(software reusability)是通過繼承(inheritance)機制來實現的。類的繼承,是新的類從已有類得到已有的特性。從已有類產生新類的過程就是類的派生。原有的類稱為基類或父類,產生的新類稱為派生類或子類。
繼承是一種封裝模型之間關系的抽象,是不同封裝模型的層次分類。

2、派生類的定義

派生類的聲明:

class 派生類名:[繼承方式] 基類名
{
    派生類成員聲明;
};

如果一個派生類同時有多個基類,稱為多重繼承;如果派生類只有一個基類,稱為單繼承。

3、繼承方式

繼承方式規定了如何訪問基類繼承的成員。繼承方式有public、 private,、protected。繼承方式指定了派生類成員以及類外對象對於從基類繼承來的成員的訪問權限。繼承方式如下:
A、公有繼承
基類的公有成員和保護成員在派生類中保持原有訪問屬性,其私有成員仍為基類的私有成員。
B、私有繼承
基類的公有成員和保護成員在派生類中成了私有成員,其私有成員仍為基類的私有成員。
C、保護繼承
基類的公有成員和保護成員在派生類中成了保護成員,其私有成員仍為基類的私有成員。
不同繼承方式下父類成員的訪問級別如下:
技術分享圖片
繼承成員的訪問屬性 = Max{繼承方式,父類成員的訪問屬性}

4、默認繼承方式

使用class關鍵字定義派生類時,默認繼承方式為private。
使用struct關鍵字定義派生類時,默認繼承方式為public。
C++工程項目中,通常只使用public繼承方式。

5、不同繼承方式示例

#include <iostream>

using namespace std;

class Parent
{
public:
    Parent(int a = 0, int b = 0, int c = 0)
    {
       pub = a;
       pro = b;
       pri = c;
    }
public:
    int pub;
protected:
    int pro;
private:
    int pri;
};

class ChildA : public Parent
{
public:
    ChildA(int a = 0, int b = 0):Parent(a, b)
    {
    }
    void print()
    {
        cout << "pub = " << pub << endl;
        cout << "pro = " << pro << endl;
        //cout << "pri = " << pri << endl;//error,私有成員不可見
    }
};

class ChildB : protected Parent
{
public:
    ChildB(int a = 0, int b = 0):Parent(a, b)
    {

    }
    void print()
    {
        cout << "pub = " << pub << endl;
        cout << "pro = " << pro << endl;
        //cout << "pri = " << pri << endl;//error,私有成員不可見
    }
};

class ChildC : private Parent
{
public:
    ChildC(int a = 0, int b = 0):Parent(a, b)
    {
    }
    void print()
    {
        cout << "pub = " << pub << endl;
        cout << "pro = " << pro << endl;
        //cout << "pri = " << pri << endl;//error,私有成員不可見
    }
};
//默認繼承方式
class ChildD :  Parent
{
public:
    ChildD(int a = 0, int b = 0):Parent(a, b)
    {
    }
    void print()
    {
        cout << "pub = " << pub << endl;
        cout << "pro = " << pro << endl;
        //cout << "pri = " << pri << endl;//error,私有成員不可見
    }
};

int main(int argc, char *argv[])
{
    ChildA childa(1000,2000);
    childa.pub = 0;
    //childa.pro = 2000;//error,外部不可見
    //childa.pri = 3000;//error,外部不可見
    childa.print();

    ChildB childb(1001,2001);
    //childb.pub = 1001;//error,外部不可見
    //childb.pro = 2001;//error,外部不可見
    //childb.pri = 3001;//error,外部不可見
    childb.print();

    ChildC childc(1002,2002);
    //childc.pub = 1002;//error,外部不可見
    //childc.pro = 2002;//error,外部不可見
    //childc.pri = 3002;//error,外部不可見
    childc.print();

    ChildD childd(1003,2003);
    //childd.pub = 1003;//error,外部不可見
    //childd.pro = 2003;//error,外部不可見
    //childd.pri = 3003;//error,外部不可見
    childd.print();

    return 0;
}

三、派生類

1、派生類簡介

派生類中的成員包含兩大部分,一類是從基類繼承過來的,一類是自己增加的成員。從基類繼承的成員表現其共性,而新增的成員體現其個性。
派生類中由基類繼承而來的成員的初始化工作還是由基類的構造函數完成,然後派生類中新增的成員在派生類的構造函數中初始化。派生類沒有繼承基類的構造函數和析構函數。

2、派生類的構造函數

派生類的構造函數語法如下:

派生類名::派生類名(參數總表)
:基類名(參數表),內嵌子對象(參數表)
{
    派生類新增成員的初始化語句; //也可出現在參數列表中
}

子類構造函數必須對繼承來的成員進行初始化,可以直接通過初始化列表或賦值方式進行,也可以調用父類構造函數進行初始化。
父類構造函數在子類中的調用方式:
A、默認調用,適用於無參構造函數和使用默認參數的構造函數
B、顯示調用,適用於所有父類構造函數,通過初始化列表調用

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    Parent()
    {
        cout << "Parent()" << endl;
    }
    Parent(string s)
    {
        cout << "Parent(string s): " << s << endl;
    }
    ~Parent()
    {
        cout << "~Parent()" << endl;
    }
};

class Child : public Parent
{
public:
    //隱式調用父類的無參構造函數或默認參數構造函數
    Child()
    {
        cout << "Child()" << endl;
    }
    //顯示調用父類構造函數,如果不顯示調用,默認調用
    //父類的無參構造函數或默認參數構造函數
    Child(string s):Parent(s)
    {
        name = s;
        cout << "Child(): " << s << endl;
    }
    ~Child()
    {
        cout << "~Child(): " << name << endl;
    }
private:
    string name;
};

int main(int argc, char *argv[])
{

    Child child1("bauer");
    // output:
    // Parent(string s): bauer
    // Child(): bauer
    // ~Child(): bauer
    // ~Parent()

    return 0;
}

構造函數的調用順序不以初始化列表中的調用順序進行,而是根據類中對成員變量聲明的順序進行調用。如果基類中沒有默認構造函數(無參),那麽在派生類的構造函數中必須顯示調用基類構造函數,以初始化基類成員。
派生類對象創建時構造函數的調用順序如下:
A、調用基類構造函數,調用順序按照基類被繼承時聲明的順序(從左到右);
B、調用成員變量的構造函數,調用順序按照成員變量在類中聲明的順序;
C、調用派生類自身的構造函數。
子類構造函數中,要麽顯示的調用父類的構造函數(傳參),要麽隱式的調用。發生隱式調用時,父類要有無參構造函數或是可以包含無參構造函數的默認參數函數。子類構造函數必須對繼承而來的成員進行初始化,可以通過初始化列表或者賦值的方法進行初始化,也可以通過調用父類構造函數進行初始化。
子類對象創建時構造函數的調用順序如下:
A、調用父類的構造函數
B、調用成員變量的構造函數
C、調用類自身的構造函數

#include <iostream>

using namespace std;

class Parent
{
public:
    Parent()
    {
        cout << "Parent()" << endl;
    }
    Parent(string s)
    {
        cout << "Parent(string s): " << s << endl;
    }
    ~Parent()
    {
        cout << "~Parent()" << endl;
    }
};

class Member
{
public:
    Member(int i = 0)
    {
        this->i = i;
        cout << "Member(int i = 0) i = " << i << endl;
    }
    ~Member()
    {
        cout << "~Member() i = " << i << endl;
    }
private:
    int i;
};

class Child : public Parent
{
public:
    //隱式調用父類的無參構造函數或默認參數構造函數
    Child()
    {
        cout << "Child()" << endl;
    }
    //顯示調用父類構造函數,如果不顯示調用,默認調用
    //父類的無參構造函數或默認參數構造函數
    Child(string s, int i):Parent(s),member(i)
    {
        name = s;
        cout << "Child(): " << s << endl;
    }
    ~Child()
    {
        cout << "~Child(): " << name << endl;
    }
private:
    string name;
    Member member;
};

int main(int argc, char *argv[])
{
    Child childA("bauer", 10);
    // output:
    // Parent(string s): bauer
    // Member(int i = 0) i = 10
    // Child(): bauer
    // ~Child(): bauer
    // ~Member() i = 10
    // ~Parent()

    return 0;
}

3、派生類的拷貝構造函數

派生類拷貝構造函數的定義如下:

派生類::派生類(const 派生類& another)
:基類(another),派生類新成員(another.新成員)
{ 
}

派生類中的默認拷貝構造函數會調用父類中默認或自實現拷貝構造函數,若派生類中自實現拷貝構造函數,則必須顯示的調用父類的拷貝構造函數。

#include <iostream>
#include <string>

using namespace std;

class Student
{
public:
    Student(string sn,int n,char s)
    {
        name = sn;
        num = n;
        sex = s;
       cout << "Student(string sn,int n,char s)" << endl;
    }
    Student(const Student& another)
    {
        name = another.name;
        num = another.num;
        sex = another.sex;
    }
    ~Student()
    {
        cout << "~Student()" << endl;
    }
    void print()
    {
        cout << name << endl;
        cout << num << endl;
        cout << sex << endl;
    }
private:
    string name;
    int num;
    char sex;
};

class Graduate : public Student
{
public:
    Graduate(string sn,int in,char cs,int fs):Student(sn, in, cs)
    {
        salary = fs;
        cout << "Graduate(string sn,int in,char cs,float fs)" << endl;
    }
    ~Graduate()
    {
        cout << "~Graduate()" << endl;
    }
    Graduate(const Graduate& another):Student(another)
    {
        salary = another.salary;
    }
    void display()
    {
        print();
        cout<<salary<<endl;
    }
private:
    int salary;
};

int main(int argc, char *argv[])
{
    Graduate s("bauer", 1001,‘M‘,8000);
    s.display();
    Graduate g = s;
    g.display();

    return 0;
}

4、派生類的賦值運算符重載

派生類的賦值操作符重載函數的定義如下:

子類& 子類::operator=(const 子類& another)
{
    if(this != &another)
{
  父類::operator =(another); // 調用父類的賦值運算符重載
    this->salary = another.salary;//子類成員初始化
}
        return * this;
}

派生類的如果沒有實現賦值操作符函數,C++編譯器會提供一個默認賦值操作符重載函數,默認的賦值操作符重載函數會調用父類的賦值操作符重載函數(編譯器提供的默認賦值操作符重載函數或是開發者提供的賦值操作符重載函數)。

#include <iostream>
#include <string>

using namespace std;

class Student
{
public:
    Student(string sn,int n,char s)
    {
        name = sn;
        num = n;
        sex = s;
       cout << "Student(string sn,int n,char s)" << endl;
    }
    Student(const Student& another)
    {
        name = another.name;
        num = another.num;
        sex = another.sex;
    }
    Student& operator = (const Student& another)
    {
        if(this != &another)
        {
            name = another.name;
            num = another.num;
            sex = another.sex;
        }
        return *this;
    }
    ~Student()
    {
        cout << "~Student()" << endl;
    }
    void print()
    {
        cout << name << endl;
        cout << num << endl;
        cout << sex << endl;
    }
private:
    string name;
    int num;
    char sex;
};

class Graduate : public Student
{
public:
    Graduate(string sn,int in,char cs,int fs):Student(sn, in, cs)
    {
        salary = fs;
        cout << "Graduate(string sn,int in,char cs,float fs)" << endl;
    }
    ~Graduate()
    {
        cout << "~Graduate()" << endl;
    }
    Graduate(const Graduate& another):Student(another)
    {
        salary = another.salary;
    }

    void display()
    {
        print();
        cout<<salary<<endl;
    }
private:
    int salary;
};

int main(int argc, char *argv[])
{
    Graduate s("bauer", 1001,‘M‘,8000);
    cout << endl;
    s.display();
    cout << endl;
    Graduate g("lee", 1002, ‘F‘, 9000);
    cout << endl;
    g.display();
    cout << endl;
    g = s;
    g.display();
    cout << endl;
    return 0;
}

派生類如果實現了賦值操作符重載函數,則需在賦值操作符重載函數內顯示地調用父類的賦值操作符重載函數。

#include <iostream>
#include <string>

using namespace std;

class Student
{
public:
    Student(string sn,int n,char s)
    {
        name = sn;
        num = n;
        sex = s;
       cout << "Student(string sn,int n,char s)" << endl;
    }
    Student(const Student& another)
    {
        name = another.name;
        num = another.num;
        sex = another.sex;
    }
    Student& operator = (const Student& another)
    {
        if(this != &another)
        {
            name = another.name;
            num = another.num;
            sex = another.sex;
        }
        return *this;
    }
    ~Student()
    {
        cout << "~Student()" << endl;
    }
    void print()
    {
        cout << name << endl;
        cout << num << endl;
        cout << sex << endl;
    }
private:
    string name;
    int num;
    char sex;
};

class Graduate : public Student
{
public:
    Graduate(string sn,int in,char cs,int fs):Student(sn, in, cs)
    {
        salary = fs;
        cout << "Graduate(string sn,int in,char cs,float fs)" << endl;
    }
    ~Graduate()
    {
        cout << "~Graduate()" << endl;
    }
    Graduate(const Graduate& another):Student(another)
    {
        salary = another.salary;
    }

    Graduate& operator = (const Graduate& another)
    {
        if(this != &another)
        {
            Student::operator =(another);
            salary = another.salary;
        }
        return *this;
    }

    void display()
    {
        print();
        cout<<salary<<endl;
    }
private:
    int salary;
};

int main(int argc, char *argv[])
{
    Graduate s("bauer", 1001,‘M‘,8000);
    cout << endl;
    s.display();
    cout << endl;
    Graduate g("lee", 1002, ‘F‘, 9000);
    cout << endl;
    g.display();
    cout << endl;
    g = s;
    g.display();
    cout << endl;
    return 0;
}

5、派生類的析構函數

派生類的析構函數的功能是在對象銷毀前進行一些必要的清理工作,析構函數沒有類型,也沒有參數。析構函數的調用順序與構造函數相反。
析構函數只有一種,無重載,無默參。
子類對象銷毀時析構函數的調用順序如下:
A、調用類自身的析構函數
B、調用成員變量的析構函數
C、調用父類的析構函數
四、父類與子類的同名覆蓋
子類定義父類中的同名成員時發生同名覆蓋,規則如下:
A、子類可以定義父類中的同名成員(成員變量和成員函數)
B、子類中的成員將隱藏父類中的同名成員
C、父類中的同名成員依然存在於子類中
D通過作用域訪問符訪問父類中的同名成員
如果某派生類的多個基類擁有同名的成員,派生類又新增與基類同名的成員,派生類成員將shadow(隱藏)所有基類的同名成員,需要作用域的調用方式才能調用基類的同名成員。

#include <iostream>

using namespace std;

class Parent
{
public:
    int m_count;
    void print()
    {
        cout << &m_count << endl;
    }
};

class Child : public Parent
{
public:
    int m_count;
    void print()
    {
        cout << &(Parent::m_count) << endl;
        cout << &m_count << endl;
    }

};

int main(int argc, char *argv[])
{
    Parent p;
    p.print();
    cout << &p.m_count << endl;
    Child child;
    child.print();
    //子類對象的父類同名成員變量訪問
    cout << &child.Parent::m_count <<endl;
    //子類對象的父類同名成員函數訪問
    child.Parent::print();
    cout << &child.m_count << endl;

    return 0;
}

函數重載發生在同一作用域 ,父類和子類的同名函數不構成函數重載,屬於同名覆蓋,子類會隱藏父類中的同名成員。
子類可以定義父類中的同名成員,子類中的成員將隱藏父類中的同名成員,父類中的同名成員依然存在子類中,通過作用域分辨符::訪問父類中的同名成員。
子類中的成員函數將隱藏父類中的同名成員函數,子類無法重載父類中同名成員函數,使用作用域分辨符可以訪問父類中的同名成員函數。

#include <iostream>

using namespace std;

class Parent
{
public:
    int mi;
    void add(int i)
    {
        mi += i;
    }
    void add(int a, int b)
    {
        mi += (a + b);
    }
};

class Child : public Parent
{
public:
    int mi;
    void add(int x, int y, int z)
    {
        mi += (x + y + z);
    }
};

int main(int argc, char *argv[])
{
    Parent p;
    p.add(1);
    p.add(1,2);

    Child child;
    child.add(1,2,3);
    //child.add(1);//error
    child.Parent::add(1);
    //child.add(1,2);//error
    child.Parent::add(1,2);

    return 0;
}

五、賦值兼容原則

1、賦值兼容原則簡介

賦值兼容原則是指子類對象可以作為父類對象使用。
賦值兼容原則的特點如下:
A、子類對象可以直接賦值給父類對象
B、子類對象可以直接初始化父類對象
C、父類指針可以直接指向子類對象
D、父類引用可以直接引用子類對象
當使用父類指針指向子類對象、父類引用對子類對象進行引用時,子類對象退化為父類對象,只能訪問父類中定義的成員,因此可以直接訪問被子類覆蓋的同名成員。

#include <iostream>

using namespace std;

class Parent
{
public:
    int mi;
    void add(int i)
    {
        mi += i;
    }
    void add(int a, int b)
    {
        mi += (a + b);
    }
};

class Child : public Parent
{
public:
    int mi;
    void add(int x, int y, int z)
    {
        mi += (x + y + z);
    }
};

int main(int argc, char *argv[])
{
    Child child;
    Parent p = child;
    p.add(1);
    p.add(1,2);
    Parent& rp = child;
    rp.add(1);
    rp.add(1,2);
    //rp.add(1,2,3);//error

    Parent* pp = &child;
    pp->add(1);
    pp->add(1,2);
    //pp->add(1,2,3);//error

    return 0;
}

2、函數重寫

子類中重定義父類中已經存在的成員函數,稱為函數重寫。
函數重寫是同名覆蓋的一種特殊情況。

#include <iostream>

using namespace std;

class Parent
{
public:
    int mi;
    void add(int i)
    {
        mi += i;
    }
    void add(int a, int b)
    {
        mi += (a + b);
    }
    void print()
    {
        cout << "Parent" << endl;
    }
};

class Child : public Parent
{
public:
    int mi;
    //函數重寫
    void add(int x, int y)
    {
        mi += (x + y);
    }
    //函數重寫
    void print()
    {
        cout << "Child" << endl;
    }
};

int main(int argc, char *argv[])
{
    Child child;
    Parent p = child;
    p.add(1);
    p.add(1,2);
    Parent& rp = child;
    rp.add(1);
    rp.add(1,2);
    rp.print();//Parent

    Parent* pp = &child;
    pp->add(1);
    pp->add(1,2);
    pp->print();//Parent

    return 0;
}

C++是一種靜態編譯語言,在編譯期間,C++編譯器只能根據指針的類型判斷所指向的對象。根據賦值兼容原則,C++編譯器認為父類指針指向的是父類對象,父類引用是對父類對象的引用,因此編譯結果只能是調用父類中定義的同名函數。

C++語言學習(十)——繼承與派生