91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C++中單繼承與多繼承如何使用

發布時間:2022-04-24 13:45:31 來源:億速云 閱讀:174 作者:iii 欄目:開發技術

今天小編給大家分享一下C++中單繼承與多繼承如何使用的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

    前言

    C++的繼承機制相對其他語言是比較復雜的一種,不同于java只支持單繼承,C++不僅支持單繼承,也支持多繼承,對于多繼承中的菱形問題會引發一系列的麻煩,C++的兩個重要缺陷,一個是多繼承,一個是垃圾回收器。本文將詳細講解C++的單繼承和多繼承,以及菱形繼承的解決方法及原理。

    1.繼承的概念和定義

    (1)繼承的概念

    繼承是面向對象設計使代碼可以復用的重要手段,它允許程序員在保持原有類的基礎上進行擴展。被擴展的類稱為基類或者父類,擴展生成的類叫做子類或者派生類,繼承是類設計層次的復用。

    繼承的作用是使得子類中既包含父類的成員,也可以包含自己的成員。

    (2)繼承的定義方法

    class Person
    {
    private:
    	string _name;
    	int _age;
    };
    class Student :public Person
    {
    private:
    	int _id;
    };

    看這一段代碼,其中子類Student繼承了父類Person,Student后的public表示的是繼承方式。

    (2)繼承后子類的成員類型

    繼承方式和父類的成員屬性共同決定了子類中的成員屬性。我們用一張表來表示三者之間的關系。

    類成員/繼承方式public繼承protected繼承private繼承
    基類的public成員派生類的public成員派生類的protected成員派生類的private成員
    基類的protected成員派生類的protected成員派生類的protected成員派生類的private成員
    基類的private成員派生類中不可見派生類中不可見派生類中不可見

    我們只需要兩點來記憶這個表格:

    1.基類的private成員在派生類中無論以什么方式繼承都是不可見的。

    2.子類中的成員屬性取繼承方式和父類成員屬性中權限小的那個: public>protected>private

    表格的說明:

    1.不可見的意思不是沒有被繼承,而是不能使用,在底層繼承下來比沒有繼承下來更方便。

    2.在父類中private和protected沒有區別,但是在子類中,protected成員可以在類內訪問,而private不能,因此可以說protected是為了繼承而存在的。

    3.如果不寫繼承方式,如果子類是class定義的,那么默認為private繼承,是struct定義的,默認是public繼承。

    4.不可見與private成員區別:不可見指的是在類內與類外都不能使用,private成員在類內可以使用,在類外不可以使用。

    5.不想給子類訪問的成員我們設成private。

    2.基類與派生類的賦值轉換

    (1)派生類賦值給基類

    C++中單繼承與多繼承如何使用

    我們定義了一個父類person和它的派生類student,以上是它們各自的成員。

    當我們將一個派生類的對象賦值給基類的對象時,發生的過程我們稱之為切片。即只將子類中父類成員賦值過去。當父類中有private成員時,同樣會進行切片,只是不顯示而已,因此繼承中盡量不要定義私有成員。

    注意,這種賦值兼容方式僅限于公有繼承。

    私有繼承不支持切片,這是因為對于父類中的public成員,私有或保護繼承之后會轉變成private/protected類型,而賦值時會發生將派生類對象中的private/protected成員賦值給父類對象中的public成員的現象,但是private/protected成員在類外是不能被訪問的,因此不支持私有繼承。

    	Person b;
    	Student a;
    	b = a;
    	Person* ptr = &a;
    	Person& ref = a;

    注意一個細節,我們可以使用引用賦值,說明這里并不存在類型轉換的行為,因為類型轉換中間會產生臨時變量,需要使用const引用。

    double d;
    const int& r=d;//發生了類型轉換。

    (2)基類給派生類

    先說結論:

    父類對象不可以直接賦值給子類對象。

    這是因為子類對象中有父類不存在的類型,無法進行賦值。也不能通過所謂的強制類型轉換進行賦值。

    但是C++支持指針和引用的賦值:

    	Person b;
    	Student a;
    	a = (Student)b;//不正確
    	Student* ptr = (Student*)&b;//支持
    	Student& ref = (Student&)b;//支持

    雖然指針和引用可以,但是當指針向下訪問的時候超過父類對象的時候會出現問題。

    C++中單繼承與多繼承如何使用

    會出現指向空的情況。

    3.繼承中的作用域

    (1)隱藏的概念

    基類和派生類都有各自獨立的作用域。

    如果不同的域內有同名的成員,我們根據就近原則或者指定作用域的方式來指定成員的位置。

    隱藏:子類與父類中出現同名成員,子類成員將屏蔽父類成員對同名成員進行直接訪問,這種情況叫隱藏,也叫重定向

    注意如果是成員函數的隱藏,只要函數名相同就會構成隱藏,與參數無關。

    舉一個例子:

    class Person
    {
    protected:
    	string _name = "小六子";
    	int _num = 111;
    };
    class Student :public Person
    {
    public:
    	void Print()
    	{
    		cout << "姓名:" << _name << endl;
    		cout << "身份證號:" << Person::_num << endl;
    		cout << "學號:" << _num << endl;
    	}
    protected:
    	int _num = 999;
    };
    int main()
    {
    	Student s1;
    	s1.Print();
    }

    在這段代碼中,Person和Student分別定義了_num,當子類對象中的成員函數直接訪問_num時,根據的是就近原則,訪問的是子類中的_num,當要訪問父類中的_num時,需要使用::來指定類域,就可以進行訪問。父類中的_num與子類中的_num構成隱藏。

    這段代碼打印的結果是:

    C++中單繼承與多繼承如何使用

    (2)例題

    這里有一道小小的題目,是關于函數隱藏的:

    class A
    {
    public:
    	void func()
    	{
    		cout << "func" << endl;
    	}
    };
    class B :public A
    {
    public:
    	void func(int i)
    	{
    		A::func();
    		cout << "func(int i)->" << i << endl;
    	}
    };
    void Test()
    {
    	B b;
    	b.func(10);
    	b.func();
    }

    提問在Test中的兩個函數能否調用成功?

    b.func(10)可以調用成功,因為構成了隱藏。
    b.func()不能調用成功,會發生變異報錯,因為隱藏了調不動。

    4.派生類的默認成員函數

    對于六大默認成員函數我們這里暫時先討論4種重要的,即:構造函數,析構函數,拷貝構造,賦值運算符重載。

    (1)默認生成的成員函數

    當我們不在子類中書寫時,編譯器會默認生成。這里只需要記住一句話:

    繼承下來的成員調用父類的來處理,自己的按基本規則來處理。

    以構造函數舉例:派生類中的父類成員調用父類中的構造函數,自己的成員按照構造函數自動生成的規則來。

    (2)自己寫

    自己寫的情況

    1.父類沒有默認構造函數,需要我們自己寫構造函數。

    2.子類有資源需要釋放,需要我們自己寫析構函數。

    3.如果子類涉及淺拷貝問題,需要自己寫拷貝構造和賦值重載。

    構造函數

    父類成員調用對應的父類構造函數處理。子類成員按普通類處理。

    舉一個例子:

    class Person
    {
    public:
    	Person(string name , int num=2)
    		:_name(name)
    		,_num(num)
    	{}
    protected:
    	string _name ;
    	int _num ;
    };
    class Student :public Person
    {
    public:
    	Student(int num,string _name,int _num)
    		:_num(num)
    		,Person(_name,_num)
    	{}
    protected:
    	int _num;
    };
    int main()
    {
    	Student s1(2,"zhangsan",2);
    }

    看這一段代碼,父類中沒有默認構造函數(注意與默認成員函數區分),因此要初始化父類中的對象需要我們自己書寫子類中的構造函數。在書寫構造函數時,父類對象成員初始化使用父類中的構造函數,子類成員的初始化按正常方式書寫即可。

    拷貝構造和運算符重載函數

    	Student(const Student& s)
    		:Person(s)
    		,_num(s._num)
    	{}
    	Student& operator=(const Student& s)
    	{
    		if (this != &s)
    		{
    			Person::operator=(s);//不指明類域的話會發生自己調自己的情況
    			_num = s._num;
    			return *this;
    		}
    	}
    	int main()
    {
    	Student s1(2,"zhangsan",2);
    	Student s2(s1);
    	Student s3 = s2;
    }

    我們可以通過調試來查看結果:

    C++中單繼承與多繼承如何使用

    析構函數

    析構函數比較特殊,對于父類中的析構函數,我們不需要指定去書寫,就像下面這種情況:

    //父類中的析構
    	~Person()
    	{
    		cout << "~Person" << endl;
    	}
    //子類中的析構
    		~Student()
    	{
    		Person::~Person();
    	}

    注意,析構函數的名字在最后會被統一處理成destructor(),如果不指定類域的話,父類析構函數和子類析構函數會構成隱藏,因此需要指定類域。
    對于上述int中的代碼,需要析構三個子類對象,打印出的結果是:

    C++中單繼承與多繼承如何使用

    我們發現調用了六次父類中的析構函數。這說明每個對象的父類成員都被析構了兩次。如果需要釋放空間,則一定會報錯。

    先說結論:我們自己實現子類構造函數時,不需要顯示調用父類析構函數,我們顯示調用一次,它還會自動調用一次。

    C++中單繼承與多繼承如何使用

    下面簡單說明一下,為什么程序需要自動調用:

    我們知道變量的定義是發生在棧中的,因此就存在構造和析構的順序問題,棧滿足先入后出原則,因此先構造的需要后析構。

    在構造的過程中,我們會先初始化父類成員,再初始化子類成員。因此我們需要先析構子類成員,再析構父類成員。

    C++中單繼承與多繼承如何使用

    如果先析構父類會打亂棧的順序,因此編譯器會自動調用父類的析構函數。

    5.友元與靜態成員

    這個只需要記住兩點:

    1.友元關系不能繼承。

    2.靜態成員會被繼承下來,無論繼承多少,靜態成員只有一個。

    6.多繼承

    (1)概念

    一個類有兩個及以上父類時稱這個繼承關系為多繼承。

    class Student
    {
    public:
    protected:
    	int _id;
    };
    class Teacher
    {
    public:
    protected:
    	int _course;
    };
    class Assistant:public Student,public Teacher
    {
    public:
    protected:
    protected:
    };

    我們使用逗號表示分隔,即繼承多個父類。可以通過調試來觀察子類Assitant的內容:

    C++中單繼承與多繼承如何使用

    (2)復雜的菱形繼承

    菱形繼承是多繼承的一種情況:

    C++中單繼承與多繼承如何使用

    具有這樣的繼承關系的稱為菱形繼承。

    菱形繼承出現的問題:從對象成員模型構造,可以看出菱形繼承有數據冗余和二義性的問題。

    數據冗余指的是類Assistant中會有兩份Person的成員,二義性指的是這兩份成員每一次調用不知道調用的的是哪一個,需要指定類域。

    這段代碼表示的就是菱形繼承的關系:

    class Person
    {
    public:
    	string _name;
    };
    class Student:public Person
    {
    public:
    protected:
    	int _num;
    };
    class Teacher:public Person
    {
    public:
    protected:
    	int _id;
    };
    class Assistant:public Student,public Teacher
    {
    public:
    protected:
    protected:
    	int _course;
    };
    int main()
    {
    	Assistant a;
    }

    我們通過調試可以觀測a中的內容,發現會存在兩份Person中的成員:

    C++中單繼承與多繼承如何使用

    如果要對這兩個Person成員賦值時,需要指定類域。

    	a.Student::_name = "xxx";
    	a.Teacher::_name = "yyy";
    }

    這就是所謂的二義性,在實際中一個人不能有兩個名字,對于冗余性來說,如果Person中有一個很大的數組浪費的空間會很多。

    (3)虛繼承解決菱形繼承問題

    虛繼承可以解決菱形繼承的二義性和數據冗余問題。如上面的繼承關系,在Student和Teacher的繼承Person時使用的虛擬繼承,即可解決問題。需要注意的是,虛擬繼承不要在其他地方去使用。

    class Student:virtual public Person
    {
    public:
    protected:
    	int _num;
    };
    class Teacher:virtual public Person
    {
    public:
    protected:
    	int _id;
    };

    只需要在菱形的腰部兩個父類加入virtual關鍵詞即可。

    注意要在菱形的腰部。

    當加完之后,在Assistant的對象中,Person類的_name成員就只有一個了。無論是否指定類域,更改的變量都只有一個:

    C++中單繼承與多繼承如何使用

    (4)虛繼承的原理

    內存演示

    要研究虛繼承的原理,我們給出一個簡化的菱形繼承結構,再借助內存窗口窗口觀察對象成員的模型。

    class A
    {
    public:
    	int _a;
    };
    class B:public A
    {
    public:
    	int _b;
    };
    class C:public A
    {
    public:
    	int _c;
    };
    class D :public B, public C
    {
    public:
    	int _d;
    };
    int main()
    {
    	D d;
    	d.B::_a = 1;
    	d.C::_a = 2;
    	d._b = 3;
    	d._c = 4;
    	d._d = 5;
    	return 0;
    }

    當沒使用虛繼承(即沒有使用virtual時)

    我們使用內存窗口來觀察內容:C++中單繼承與多繼承如何使用

    通過觀察內存中的布局,我們發現d中的B父類對象和C父類對象中的內容分別是連續存放的,B中有父類A中成員_a的值是1,其自己成員_b的值是3,兩者的內存是挨著的,C同理,對于D類中自己的成員_d,放在了內存的最后。

    確定d中B類對象和C類對象的存儲順序是根據繼承順序決定的。由于上述代碼是class D :public B, public C,因此B類的對象會存在C類的前面。
    而當我們給腰部加上virtual構成虛繼承之后:

    class B:virtual public A
    {
    public:
    	int _b;
    };
    class C:virtual public A
    {
    public:
    	int _c;
    };

    C++中單繼承與多繼承如何使用

    使用virtual之后,我們發現已經將A中對象_a放入在了最后,因此無論指定不指定類域,改變的都是同一個_a的值。

    但同時我們發現內存中多了兩行,那么這兩行是干什么的呢?

    虛基表

    從格式來看,這兩行顯然是都是地址。

    C++中單繼承與多繼承如何使用

    我們再開辟一個內存2,向其中輸入上面地址,我們發現地址中存儲的內容是00 00 00 00,C類對象中同理,這里就不演示了。

    這里00 00 00 00的意義在后面多態中會學習到,注意看它的下一個位置存放的是00 00 00 14

    這里是十六進制,因此表示的是20這個數字。

    再來看內存1:

    C++中單繼承與多繼承如何使用

    兩者的地址之差剛剛好是20個字節。

    因此我們可以知道:在虛繼承中,B類對象和C類對象的內存中新加入的是一個地址,分別用于尋找兩者與A類型變量的偏移量。B類對象與A類對象的偏移量是20,同理可驗證C類對象的偏移量是12。而內存2也有一個專有名詞:虛基表

    總結:A一般叫做虛基類,在D里面,A類成員放在一個公共的位置,有時B要找A,C要找A,就要通過虛基表中的偏移量進行計算。

    比如,當我們再用B類和C類建立兩個變量:

    	B b = d;
    	C c = d;

    此時會發生切片處理,需要將d中的A類對象賦值到b和c中,此時就需要使用到虛基表來尋找。

    再比如:

    	B* pb = &d;
    	pb->_a = 10;

    pb指向了d的首地址,要更改d中的_a的值,指針pb也需要使用虛基表來進行尋找。

    7.繼承與組合

    (1)兩者區別

    首先我們要對繼承和組合進行區分:

    繼承表示的是子類繼承父類,組合表示的是在一個類中定義了另一個類的成員變量。

    //繼承
    class A
    {
    public:
    	int _a;
    };
    class B:public A
    {
    public:
    	int _b;
    };
    //組合
    class C
    {
    public:
    	int _c;
    };
    class D 
    {
    public:
    	int _d;
    	C _obj;
    };

    (2)繼承與組合的區別

    我們需要明確一點:類之間,模塊之間最好是低耦合,高內聚的,因為方便維護。

    低耦合:類之間依賴關系越弱越好。

    高內聚:內部成員關系緊密。

    1.繼承對應于白盒:B可以直接使用A中的公有和保護成員,破壞了封裝性。

    2.組合對應于黑盒:D只能使用C的公有,不能直接使用保護成員。

    舉一個例子:

    如果A中有5個public,5個protected

    對于組合來說,非基類只能使用這5個public,基類中的其他成員隨便修改都不會影響該非基類。

    對于繼承來說,基類中一切的改變都會影響子類。

    那可以拋棄繼承的語法嗎?當然是不行的。

    多態是建立在繼承的基礎上的。

    (3)使用情況

    1.如果B就是一個A,比如Student是一個Person,我們稱這種關系為is-a關系,此時適合使用繼承。

    2.如果D被包含于C,比如head包含eyes,我們稱這種關系為has-a關系,此時適合使用組合。

    3.當遇到特殊情況,is-a和has-a都可以講通時,優先使用組合

    以上就是“C++中單繼承與多繼承如何使用”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    c++
    AI

    九龙县| 乌海市| 西贡区| 泰兴市| 安远县| 乌拉特中旗| 文化| 桃园市| 白水县| 上饶县| 运城市| 宁南县| 尚义县| 昭觉县| 肇州县| 偃师市| 裕民县| 保德县| 徐州市| 景德镇市| 松潘县| 巴塘县| 岳池县| 灵台县| 阳曲县| 宁河县| 卓尼县| 桃园市| 阳朔县| 西峡县| 集贤县| 乌拉特前旗| 共和县| 长沙市| 时尚| 乐昌市| 湘乡市| 清河县| 郁南县| 陈巴尔虎旗| 巧家县|