類的三大特性

一、封裝:
方法:
1、使用類將數據成員和函數方法綁定在一起。
2、使用訪問限制符進行限制封裝。

作用:信息隱藏的作用,只提供一些必要的接口,而隱藏那些不必要的接口。避免受到外界的干擾和誤用,從而確保安全
破壞封裝:友元類可訪問該類中的所有數據和函數。破化了類的封裝性

二、繼承
繼承:
1、作用:通過子類(派生類)繼承父類(基類)的成員,共享不同的東西,實現各自不同的東西,達到代碼複用的作用。
繼承是類型之間的建模
子類是父類的的一部分,子類可繼承父類中的非static成員。
3、繼承方式:
訪問修飾符:
public——公有(所有權限)
protected—保護(僅在類中可訪問,子類繼承父類的public成員後,爲protected屬性,子類可訪問)
private—–私有(僅在類中可訪問,類外及子類中不可見(繼承但不可訪問))
成員屬性和繼承方式最終取權限小的:
即:
成員屬性 繼承方式 子類繼承後的屬性
public public–protected—private public–protected—private
protected public–protected—private protected–protected—private
private public–protected—private private—private—private
子類和父類屬於不同的作用域,可出現同名的成員,但會構成重定義(隱藏),隱藏父類的成員,屏蔽對父類該成員的直接訪問。若要調用父類的成員,則需加類名指定
注:在實際使用中最好不要定義同名的成員,防止出錯。
public繼承是一種is–a的關係,即子類是父類的一部分,子類可繼承使用父類中所有的public成員。
4、賦值兼容規則:
可直接將子類對象賦給父類,即切片(將父類的部分切分給父類),此方法不是一種類型轉換。可使用引用的方法接收驗證
父類對象不可賦給子類,但可通過強轉將父類的指針或引用賦給子類,但使用時還是會出現錯誤
5、構造函數問題:
注:子類由複合構成,初始化子類中父類的數據成員時,必須顯示的調用父類的構造函數,
且當父類無缺省的帶參構造函數時,必須在子類構造函的初始化列表中顯示調用父類的構造函數,進行初始化父類的成員。
因爲:調用子類的構造函數時,會在子類的初始化列表處,調用父類的構造函數,
若此處不顯示調用父類的構造函數,則編譯器也會尋找父類中無參的構造函數,但父類中已定義帶參的構造函數,所以會因找不到匹配的構造函數出錯。
缺省的帶參函數 = 無參函數+帶參函數。
6、拷貝構造問題:
子類初始化父類時,可直接將子類對象傳給父類,父類對象會進行切片,將屬於父類的部分切分下來進行初始化父類。
7、賦值運算符重載:
注:由於父類的賦值運算符重載函數,與子類的該函數屬於同名函數,所以兩個函數構成了重定義,子類的該函數隱藏了父類的函數。從而會默認調用子類的賦值函數(就近原則)。
所以在調用父類的賦值函數時,必須加上類域指定調用父類的賦值函數,否則會默認調用子類的賦值函數,從而構成遞歸。
8、析構函數:
子類和父類的析構函數名雖然表面上看到的相同,但在底層上實際被處理成同名的函數,兩者構成了重定義。
所以顯示調用父類的析構函數時,必須加上類域指定爲父類的,否則會默認的調用子類的析構函數。
調用析構函數時,會先調用子類析構函數,後自動調用父類析構函數。
因爲:構造函數時,先調用的是父類構造函數,後構造子類部分,根據棧的先進後出特性,所以不可先析構父類。
protected/private繼承是一種has-a關係,類外不可訪問,
對於私有繼承,子類內不可訪問父類成員,
保護繼承,子類內可訪問父類非私有成員。
列:
AA a;//父類對象
BB b; //子類對象
a = b; //子類對象賦給父類
不是類型轉化:
b = a; //父類對象賦給子類,錯誤
AA & aa = b //可引用,說明未發生類型轉換
9、如何定義一個不可繼承的類?
解1:將類的構造函數定義成私有的。
原因:1、類的私有成員,外部不可見,子類也不可見。
2、子類是合成的,調用子類的構造函數時,會先調用父類的構造函數,但父類的構造函數不可見,所以無法實例化處對象。所以該類不可繼承。
注:若類外要實例化出父類對象,則還需在父類中定義一個static接口函數,類外通過類域指定調用。才能實例化出對象。由於static成員是不繼承的,所以子類還是無法調用父類的構造。
解2:將類定義成抽象類,抽象類無法實例化出對象。
分類:
1、單繼承:子類只有一個直接父類的繼承
2、多繼承:子類有多個直接父類的繼承方式
3、菱形繼承:單繼承和多繼承的複合。
列:菱形繼承

   A
  / \
 B   C
  \ /
   D

A類中的數據被B,C類繼承,B,C再被D繼承,從而導致D類中有多個A中的數據成員,從而導致數據二義性和數據沉於問題。
虛繼承:
方法:通過在繼承方式修飾符前加virtual 關鍵字進行修飾,即爲虛繼承。
1、虛繼承是爲了解決菱形繼承產生的二義性和數據沉於問題。
非虛繼承時:同名的成員的數據成員相互獨立,數值不同,使用時必須指定是哪個父類,否則會造成二義性問題,運行不通過,使用類與指定後可解決二義性問題,但無法處理數據沉於問題(類中有多個該數據成員)。
虛繼承:對同名的數據成員操作,實際是對他們公共父類中的該數據成員操作,兩父類的該數據成員值相同。從而可以解決數據二義性問題和數據沉於問題,類中有1個該數據成員
2、虛繼承會產生虛基表,用於保存到父類的偏移量(地址)。
調用父類(B/C)中繼承的成員時,先通過虛基表中的偏移量找到父類(A),然後再找到該成員,而不是直接在父類(B/C)中找到該成員(A類成員)。
當繼承的父,子類中存在同名的虛函數時,且參數相同,返回類型相同(協變除外)則就構成了多態。
注:子類可不用加virtual關鍵字,因爲子類會繼承父類的虛函數特性
協變—返回類型不相同,但必須是類的指針或引用。

10、代碼實例:

1、繼承方式:

using namespace std;

class A
{
public:
    A()
    {}

protected:
    void print()
    {
        cout << "輸出" <<endl;
    }

private:
    int _a;
};

class B : public A
{
public:
    B()
    {}

    void change()
    {
        //this->_a  = 1;//錯誤父類私用成員,子類內不可見(不可訪問)
        print();//代碼段不屬於某一個類,不使用this指針調用,父類的保護成員,子類內部可訪問
    }

private:
    int _b;
};

void test()
{
    A a;
    B b;

    //b._a = 1;錯誤,父類私用成員不可見(不可訪問)
    //b.print();//錯誤,保護成員外部不可見(不可訪問)

    a = b;
    A & aa = b;//支持,說明未發生隱式類型轉換
    //b = a;//父類對象不可賦給子類

    size_t d = 0;
    //int & t = d;//引用不支持,發生隱式類型轉換
}

2、繼承的構造函數問題:

//繼承
#include<iostream>
using namespace std;

class A
{
public:


    //A(const int& a)
    //  :_a(new int(a))
    //{}

    //缺省構造函數 = 無參構造+有參構造
    A(const int& a = 0)
    :_a(new int(a))
    {
        cout << "構造A" << endl;
    }

    ~A()
    {
        cout <<"A"<< endl;
        delete _a;
    }

protected:
    int* _a;
};

class B : public A
{
public:

    //A類中無缺省的構造函數
    //B(const int& a,const int& b)
    //  :A(a) //正確
    //  ,_b(new int(b))
    //{}

    //B(const int& a,const int& b)
    //{
    //  A(a);//錯誤,在初始化列表時,會調用A類中的無參構造函數,但A類中無無參的構造函數,所以會報無默認的構造函數出錯
    //  _b = new int(b);
    //}


    //A類有帶缺省的構造函數時

    //B(const int& a,const int& b)
    //{
    //  A(a);//錯誤,在初始化列表時,會調用A類中的參構造函數,無參調用,實例化出a,此處再在調用父類構造,帶參調用,又實例化出一個a,導致重定義問題
    //  _b = new int(b);
    //}

    //B(const int& a,const int& b)
    //{
    //  _a = new int(a); //錯誤,在初始化列表時,會調用A類中的參構造函數,無參調用,實例化處_a後,已對_a開闢空間,此處不可再開闢空間
    //  _b = new int(b);
    //  cout << "構造B" << endl;
    //}

    //B(const int& a,const int& b)//正確
    //{
    //  *_a = 1; //正確,在初始化列表時,會調用A類中的參構造函數,無參調用,實例化處_a後,此處進行初始化
    //  _b = new int(b);
    //  cout << "構造B" << endl;
    //}

    B(const int& a,const int& b)//正確
        :A(a)
        ,_b(new int(b))
    {
        cout << "構造B" << endl;
    }

    //~B()//自動先析構子類,後析構父類
    //{
    //  ~A();//錯誤,子類和父類析構函數,在底層上是同名的函數,即構成了重定義,子類隱藏了父類,導致遞歸出錯。
    //  cout <<"B"<< endl;
    //  delete _b;
    //}

    //~B()//自動先析構子類,後析構父類
    //{
    //  A::~A();//錯誤,顯示析構父類後,再析構子類,子類析構後會自動再析構父類,導致出錯
    //  cout <<"B"<< endl;
    //  delete _b;
    //}

    ~B()//自動先析構子類,後析構父類---正確
    {
        cout <<"B"<< endl;
        delete _b;
    }

protected:
    int* _b;
};

void test3()
{
    B bb(0,0);
}

3、菱形繼承

//菱形繼承
#include<iostream>
using namespace std;

class A
{
public:
    A()
    {}

    void Funa()
    {
        cout << "Funa()" << endl;
    }

public:
    int _a;
};

class B : public A //公有繼承A
{
public:
    B()
    {}

    void Funb()
    {
        cout << "Funb()" << endl;
    }

public:
    int _b;
};


class C : public A //單繼承
{
public:
    C()
    {}

    void Func()
    {
        cout << "Func()" << endl;
    }

public:
    int _c;
};

class D : public B,public C //多繼承
{
public:
    D()
    {}

    void Fund()
    {
        cout << "Fund()" << endl;
    }

public:
    int _d;
};

void test2()
{
    A aa;
    B bb;
    C cc;
    D dd;

    cout << "類的大小" << endl; //函數(代碼段)和靜態成員(靜態區)不屬於某一個對象,是所有對象共有的,所以不算做對象的大小。
    cout << "aa = " << sizeof(aa) << endl; //4
    cout << "bb = " << sizeof(bb) << endl; //4+4
    cout << "cc = " << sizeof(cc) << endl; //4+4
    cout << "dd = " << sizeof(dd) << endl; //8+8+4

    aa._a = 0;
    //dd._a = 10;//錯誤,D類繼承有多個數據_a,存在二義性問題
    //dd.A::_a  = 1; //錯誤,A不是D的直接父類,所以A不算是D的父類,不可直接訪問A類成員
    dd.B::_a = 1; //通過類域,指定訪問B類中的_a成員,可解決二義性問題,但D類中有多個_a成員,存在數據冗餘問題。
    dd.C::_a = 2; //D類中的兩個_a成員數據不同
    dd._b = 3;
    dd._c = 4;
    dd._d = 5;
    cout << dd.B::_a << endl;
    cout << dd.C::_a << endl;
    cout << aa._a << endl;
}

4、菱形虛繼承

//菱形虛繼承

#include<iostream>
using namespace std;

class A
{
public:
    A()
    {}

    void Funa()
    {
        cout << "Funa()" << endl;
    }

public:
    int _a;
};

class B : virtual public A //公有繼承A
{
public:
    B()
    {}

    void Funb()
    {
        cout << "Funb()" << endl;
    }

public:
    int _b;
};

//注意virtual關鍵字添加的位置,添加在繼承公共父類處
class C : virtual public A //單繼承
{
public:
    C()
    {}

    void Func()
    {
        cout << "Func()" << endl;
    }

public:
    int _c;
};

class D : public B,public C //多繼承
{
public:
    D()
    {}

    void Fund()
    {
        cout << "Fund()" << endl;
    }

public:
    int _d;
};

void test2()
{
    A aa;
    B bb;
    C cc;
    D dd;
    //函數(代碼段)和靜態成員(靜態區)不屬於某一個對象,是所有對象共有的,所以不算做對象的大小。
    cout << "類的大小" << endl; 
    cout << "aa = " << sizeof(aa) << endl; //4
    cout << "bb = " << sizeof(bb) << endl; //4+4+4(虛基表)
    cout << "cc = " << sizeof(cc) << endl; //4+4+4(虛基表)
    cout << "dd = " << sizeof(dd) << endl; //12+12+4-4(A類大小),

    dd.B::_a = 0; //通過類域,指定訪問B類中的_a成員,
    dd.C::_a = 1; //通過類域,指定訪問C類中的_a成員,
    dd._a = 2;   //但實際都是直接對同一個父類A操作,結果值相同
    dd._b = 3;
    dd._c = 4;
    dd._d = 5;

    //注:B,C不直接繼承A類,而是通過虛基表表找到A類,所以B,C共用的是一個A類(原A類),而不是各種獨有。所以減去多統計的A類大小
    dd._a = 10;//正確,D類繼承父類B,C,兩個父類根據虛基表找到他們的公共父類A,他們都是直接對同一個父類A操作。所以D類實際只有在一個_a成員,從而解決二義性和數據冗餘問題
    //dd.A::_a  = 1; //錯誤,A不是D的直接父類,所以A不算是D的父類,不可直接訪問A類成員
    dd.B::_a = 1; //通過類域,指定訪問B類中的_a成員,
    dd.C::_a = 2; //通過類域,指定訪問C類中的_a成員,但實際都是直接對同一個父類A操作,結果值相同
    dd._b = 3;
    dd._c = 4;
    dd._d = 5;
    //對公共繼承的父類數據操作,即直接對原父類中的數據操作,數據相同
    cout << dd.B::_a << endl;
    cout << dd.C::_a << endl;
    cout << aa._a << endl;//對aa對象操作,屬於不同對象,所以數值不同
}

繼承圖解:
這裏寫圖片描述

//5、無法繼承的類
#include<iostream>
using namespace std;

class A
{
private: //將A的構造函數定義成私有的,使類不可被繼承
    A()
    {
        cout << "構造A" << endl;
    }

public:
    static A* Get()
    {
        A();
    }

protected:
    int _a;
};

class B : public A
{
public:
    B() //不可調用A的構造函數,無法實例化出對象
    {
        cout << "構造B" << endl;
    }

    int add()
    {
        _a = 10;
        _b = 20;
        return _a+_b;
    }

protected:
    int _b;
};


void test4()
{

    B bb;
    A* a = A::Get(); //通過接口實例化對象
}

三、多態

一、先介紹一下虛函數:
1、實現:在類的成員函數前加關鍵字virtual,則這個成員函數變爲虛函數
2、虛函數的重寫:在子類中定義一個與父類完全相同的虛函數(函數名相同,參數相同,返回類型相同,協變除外),則子類會覆蓋掉父類中的這個虛函數,稱爲重寫
注:1、父類必須加關鍵字virtual,子類可不用加virtual關鍵字,因爲子類會繼承父類的特性
2、協變—-返回類型是對應類的的指針或引用(同爲指針或引用)
3、靜態成員函數不能定義的虛函數
4、只有類的成員函數才能定義成虛函數
5、類外定義不可加virtual關鍵字,類內聲明時可加virtual關鍵字
6、構造函數不可爲虛函數,析構函數最好定義成虛函數
原因:構造函數定義成虛函數,調用構造函數時會調用虛表,在虛表中找構造函數,但由於對象還未實例化,所以還未創建續表,導致找不到構造函數出錯。

7、析構函數定義虛函數:父類的指針有可能指向的是子類的對象,若子類的析構函數不是多態,則只調用父類的析構,而不會調用子類的析構,導致子類的空間未釋放,造成內存泄漏。
列:A * aa = new B; //父類指針指向子類的對象。
delete aa; //析構只調用父類的析構,導致子類內存泄露。
若將析構函數定義成虛函數,由於父類和子類的析構函數名實際是相同的,所以會構成重寫。析構時根據對象調用對應的析構函數,從而避免內存泄漏問題。

8、內聯函數不可定義成虛函數?
內聯函數是在編譯時進行展開的,虛函數的調用是在運行時動態綁定的,所以不可定義成虛函數。

9、靜態成員函數不可定義成虛函數?
靜態成員函數屬於類的,不屬於某個對象,是所有對象的共有,所以沒必要在運行時聯編,沒必要定義成虛函數。

10、友元函數不可定義成虛函數?
友元函數不可被繼承,不可繼承的函數不可定義成虛函數。

其它:
1、含有虛函數的類:會創建一個虛表,用於存儲虛函數的地址,虛表實際主要就是一個函數指針數組。
2、子類會繼承父類的虛函數,會重寫所有父類中相同的虛函數,但只將自己的虛函數寫在最先繼承的父類的虛表中。
3、類中不存虛表,而是存指向這個虛表的的指針。虛表存在代碼段中,同類的所有對象共用一個虛表。

二、純虛函數:
1、方法:在聲明時將成員函數的虛函數後寫 = 0,則該成員函數即爲純虛函數。
注:寫 = 0;的意思是將函數的地址初始化爲0,告訴編譯器不爲該函數編制,從而阻止該類的實例化。

2、含有純虛函數的類稱爲抽象類。抽象類不可實例化出對象,純虛函數在派生類中重新定以後,派生類才能實例化出對象,父類還是不可實例化出對象。
目的:讓子類繼承並實現他的接口方法
作用:接口與實現分離,不僅把數據成員隱藏,還把實現完全隱藏,只留一些接口給外部調用。從而即使將來實現改變,接口可以保持不變,向後兼容。

這種徹底的封裝思想,體現了面向對象的動態綁定技術,主要引用於COM,CORBA等體系結構。
函數都是public的純虛函數的類稱爲接口類。

三、多態:

1、多態就是多種形態,C++的多態分爲靜態多態和動態多態
靜態多態:如重載,在編譯時進行決議(聯編),將函數地址放到函數調用處。
動態多態:通過繼承重寫基類的虛函數實現的多態,在運行時進行決議(聯編)。
多態的虛函數存放在虛表中,虛表實際就是一個函數指針數組,由於數組下標只有在運行時才能確定。所以重寫是動態多態。

2、作用:同一接口實現不同的功能,調用結果與對象有關。根據對象調用各自對應的方法,子對象調用子類的,父類對象調用父類的。
不是多態時,調用與類型有關。根據類調用對應的方法。

3、當使用基類的指針或引用調用重寫的虛函數時,指向父類調用的就是父類的虛函數,指向子類調用的就是子類的虛函數。

必要條件:
1、調用的必須是子類重寫了父類的虛函數
2、必須是父類的指針或引用。(根據所指向的類或引用的對象調用,子類對象調用子類的,父類對象調用父類的。)

繼承體系中同名的函數:
一、重載:
1、在同一個作用域
2、函數名相同,參數不同
3、返回值可不同

二、重寫(覆蓋)
1、在不同的作用域(分別在子類和父類中)
2、函數名相同,參數相同,返回值類型相同(協變除外)
3、訪問修飾符可不同
4、基類必須有virtual關鍵字修飾

三、重定義(隱藏)
1、不在同一作用域(分別在子類和父類中)
2、函數名相同
3、在子類和父類中,不構成重寫即爲重定義

代碼實例:
1、菱形虛繼承+虛函數

//菱形虛繼承+虛函數

#include<iostream>
using namespace std;

class A
{
public:

    A()
    {
        cout <<"A構造"<< endl;
    }

    ~A()
    {
        cout <<"B析構"<< endl;
    }
    virtual void Fun1()
    {
        cout << "A.Fun()" << endl;
    }

    void Funa()
    {
        cout << "a.Funa()" << endl;
    }
public:
    int _a;
};

class B : virtual public A
{
public:
    B()
    {
        cout <<"B構造"<< endl;
    }

    virtual void Fun2()
    {
        cout << "B.Fun()" << endl;
    }

    void Funb()
    {
        cout << " b.Funb()" << endl;
    }

    ~B()
    {
        cout <<"B析構"<< endl;
    }

public:
    int _b;
};


class C : virtual public A
{
public:
    C()
    {
        cout <<"C構造"<< endl;
    }

    virtual void Fun3()
    {
        cout << "c.Fun()" << endl;
    }

    void Funb()
    {
        cout << " c.Funb()" << endl;
    }

    ~C()
    {
        cout <<"C析構"<< endl;
    }

public:
    int _c;
};

class D : public B,public C
{
public:
    D()
    {
        cout <<"D構造"<< endl;
    }

    virtual void Fun()
    {
        cout << "D.Fun4()" << endl;
    }

    void Funb()
    {
        cout << " d.Funb()" << endl;
    }

    ~D()
    {
        cout <<"D析構"<< endl;
    }

public:
    int _d;
};

void test5()
{
    A aa;
    B bb;
    C cc;
    D dd;
    dd._a = 1;
    dd._b = 2;
    dd._c = 3;
    dd._d = 4;

    cout << sizeof(aa) << endl; //8
    cout << sizeof(bb) << endl; //20
    cout << sizeof(cc) << endl; //20
    cout << sizeof(dd) << endl; //36 = 20+20-8+4 ,虛繼承B/C佔用同一個父類A,-8
}

圖解:
這裏寫圖片描述

2、菱形虛繼承+多態

//菱形虛繼承+多態

#include<iostream>
using namespace std;

class A
{
public:

    A()
    {
        cout <<"A構造"<< endl;
    }

    ~A()
    {
        cout <<"B析構"<< endl;
    }

    virtual void Fun()
    {
        cout << "A.Fun()" << endl;
    }

    virtual void Fun1()
    {
        cout << "A.Fun1()" << endl;
    }


    void Funa()
    {
        cout << "a.Funa()" << endl;
    }
public:
    int _a;
};

class B : virtual public A
{
public:
    B()
    {
        cout <<"B構造"<< endl;
    }

    virtual void Fun()
    {
        cout << "B.Fun()" << endl;
    }

    virtual void Fun2()
    {
        cout << "B.Fun()" << endl;
    }

    void Funb()
    {
        cout << " B.Funb()" << endl;
    }

    ~B()
    {
        cout <<"B析構"<< endl;
    }

public:
    int _b;
};


class C : virtual public A
{
public:
    C()
    {
        cout <<"C構造"<< endl;
    }

    virtual void Fun()
    {
        cout << "C.Fun()" << endl;
    }

    virtual void Fun3()
    {
        cout << "C.Fun()" << endl;
    }

    void Funb()
    {
        cout << " c.Funb()" << endl;
    }

    ~C()
    {
        cout <<"C析構"<< endl;
    }

public:
    int _c;
};

class D : public B,public C
{
public:
    D()
    {
        cout <<"D構造"<< endl;
    }

    virtual void Fun()
    {
        cout << "D.Fun()" << endl;
    }

    virtual void Fun4()
    {
        cout << "D.Fun4()" << endl;
    }

    void Funb()
    {
        cout << " d.Funb()" << endl;
    }

    ~D()
    {
        cout <<"D析構"<< endl;
    }

public:
    int _d;
};

void test5()
{
    A aa;
    B bb;
    C cc;
    D dd;
    dd._a = 1;
    dd._b = 2;
    dd._c = 3;
    dd._d = 4;

    cout << sizeof(aa) << endl; //8
    cout << sizeof(bb) << endl; //24
    cout << sizeof(cc) << endl; //24
    cout << sizeof(dd) << endl; //40 = 24+24-8-4+4 //虛繼承共用一個父類A,所以-8,父類B,C用同一個佔位地址-4。
}

圖解:
這裏寫圖片描述

四、三大特性總結
封裝:
1、封裝:二中方法(1、類將數據和函數捆綁,2、訪問限制符限制)
2、作用:信息隱藏,只提供必要接口,防止外部干擾

繼承:
1、方式:public(is-a) protected,private(has-a)
2、作用:通過子類繼承父類實現代碼複用。
分類:
單繼承—只有一個直接父類
多繼承—有多個直接父類
菱形繼承—單繼承和多繼承的複合
缺陷:數據二義性,數據冗餘
解決:虛繼承
虛基表
1、存放到虛表的的偏移量
2、存放到父類的偏移量

多態:
分類:靜態多態,動態多態
作用:一個接口實現多種功能,根據對象調用對應的實現函數
必要條件:
1、父,子類中必須有完全相同的虛函數。
2、必須是父類的指針或引用。
虛函數:
使用關鍵字virtual修飾的函數
虛表
存放虛函數的地址(函數指針數組)
父類有虛表時不創建虛表,有多大個父類時,每個父類都會形成多態,但只將自己的虛函數放在最先繼承的父類中
最好爲虛函數:析構函數
不可爲虛函數:構造,靜態,友元,內聯。
純虛函數:在虛函數後寫 = 0;函數地址初始爲0,告訴編譯器不爲該函數編址從而使類不可實例化出對象。
含有純虛函數的類稱爲抽象類,若繼承方式爲public的類稱爲接口類。

如何定義一個不可被繼承的類?
解:將類的構造函數定義成私有的。
1、類的私有成員只能在類內可訪問,子類不可見。
2、子類是合成的,調用子類的構造函數時會先調用父類的構造函數。

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