您好,登錄后才能下訂單哦!
C++中的封裝繼承和多態(tài)理解是什么,針對這個(gè)問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡單易行的方法。
封裝(encapsulation):就是將抽象得到的數(shù)據(jù)和行為(或功能)相結(jié)合,形成一個(gè)有機(jī)的整體,也就是將數(shù)據(jù)與操作數(shù)據(jù)的源代碼進(jìn)行有機(jī)的結(jié)合,形成”類”,其中數(shù)據(jù)和函數(shù)都是類的成員。封裝的目的是增強(qiáng)安全性和簡化編程,使用者不必了解具體的實(shí)現(xiàn)細(xì)節(jié),而只是要通過外部接口,特定的訪問權(quán)限來使用類的成員。封裝可以隱藏實(shí)現(xiàn)細(xì)節(jié),使得代碼模塊化。
繼承(inheritance):C++通過類派生機(jī)制來支持繼承。被繼承的類型稱為基類或超類,新產(chǎn)生的類為派生類或子類。保持已有類的特性而構(gòu)造新類的過程稱為繼承。在已有類的基礎(chǔ)上新增自己的特性而產(chǎn)生新類的過程稱為派生。繼承和派生的目的是保持已有類的特性并構(gòu)造新類。繼承的目的:實(shí)現(xiàn)代碼重用。派生的目的:實(shí)現(xiàn)代碼擴(kuò)充。三種繼承方式:public、protected、private。
繼承時(shí)的構(gòu)造函數(shù):(1)、基類的構(gòu)造函數(shù)不能被繼承,派生類中需要聲明自己的構(gòu)造函數(shù);(2)、聲明構(gòu)造函數(shù)時(shí),只需要對本類中新增成員進(jìn)行初始化,對繼承來的基類成員的初始化,自動調(diào)用基類構(gòu)造函數(shù)完成;(3)、派生類的構(gòu)造函數(shù)需要給基類的構(gòu)造函數(shù)傳遞參數(shù);(4)、單一繼承時(shí)的構(gòu)造函數(shù):派生類名::派生類名(基類所需的形參,本類成員所需的形參):基類名(參數(shù)表) {本類成員初始化賦值語句;};(5)、當(dāng)基類中聲明有默認(rèn)形式的構(gòu)造函數(shù)或未聲明構(gòu)造函數(shù)時(shí),派生類構(gòu)造函數(shù)可以不向基類構(gòu)造函數(shù)傳遞參數(shù);(6)、若基類中未聲明構(gòu)造函數(shù),派生類中也可以不聲明,全采用缺省形式構(gòu)造函數(shù);(7)、當(dāng)基類聲明有帶形參的構(gòu)造函數(shù)時(shí),派生類也應(yīng)聲明帶形參的構(gòu)造函數(shù),并將參數(shù)傳遞給基類構(gòu)造函數(shù);(8)、構(gòu)造函數(shù)的調(diào)用次序:A、調(diào)用基類構(gòu)造函數(shù),調(diào)用順序按照它們被繼承時(shí)聲明的順序(從左向右);B、調(diào)用成員對象的構(gòu)造函數(shù),調(diào)用順序按照它們在類中的聲明的順序;C、派生類的構(gòu)造函數(shù)體中的內(nèi)容。
繼承時(shí)的析構(gòu)函數(shù):(1)、析構(gòu)函數(shù)也不被繼承,派生類自行聲明;(2)、聲明方法與一般(無繼承關(guān)系時(shí))類的析構(gòu)函數(shù)相同;(3)、不需要顯示地調(diào)用基類的析構(gòu)函數(shù),系統(tǒng)會自動隱式調(diào)用;(4)、析構(gòu)函數(shù)的調(diào)用次序與構(gòu)造函數(shù)相反。
同名隱藏規(guī)則:當(dāng)派生類與基類中有相同成員時(shí):(1)、若未強(qiáng)行指名,則通過派生類對象使用的是派生類中的同名成員;(2)、如要通過派生類對象訪問基類中被覆蓋的同名成員,應(yīng)使用基類名限定:基類名::數(shù)據(jù)成員名。
虛基類:作用:(1)、主要用來解決多繼承時(shí)可能發(fā)生的對同一基類繼承多次而產(chǎn)生的二義性問題;(2)、為最遠(yuǎn)的派生類提供唯一的基類成員,而不重復(fù)產(chǎn)生多次拷貝。
繼承、組合:組合是將其它類的對象作為成員使用,繼承是子類可以使用父類的成員方法。(1)、A繼承B,說明A是B的一種,并且B的所有行為對A都有意義;(2)、若在邏輯上A是B的“一部分”,則不允許B從A派生,而是要用A和其它東西組合出B;(3)、繼承屬于”白盒”復(fù)用,組合屬于”黑盒”復(fù)用。
多態(tài)(Polymorphic)性可以簡單地概括為“一個(gè)接口,多種方法”,程序在運(yùn)行時(shí)才決定調(diào)用的函數(shù)。C++多態(tài)性是通過虛函數(shù)來實(shí)現(xiàn)的,虛函數(shù)允許子類重新定義成員函數(shù),而子類重新定義父類的做法稱為覆蓋或者稱為重寫。而重載則是允許有多個(gè)同名的函數(shù),而這些函數(shù)的參數(shù)列表不同,允許參數(shù)個(gè)數(shù)不同,參數(shù)類型不同,或者兩者都不同。關(guān)于多態(tài),簡而言之就是用父類型別的指針指向其子類的實(shí)例,然后通過父類的指針調(diào)用實(shí)際子類的成員函數(shù)。
多態(tài)與非多態(tài)的實(shí)質(zhì)區(qū)別就是函數(shù)地址是早綁定還是晚綁定。如果函數(shù)的調(diào)用,在編譯器編譯期間就可以確定函數(shù)的調(diào)用地址,并產(chǎn)生代碼,是靜態(tài)的,就是說地址是早綁定的。而如果函數(shù)調(diào)用的地址不能在編譯期間確定,需要在運(yùn)行時(shí)才確定,這就是屬于晚綁定。
封裝可以使得代碼模塊化,繼承可以擴(kuò)展已存在的代碼,它們的目的都是為了代碼重用。而多態(tài)的目的則是為了接口重用。也就是說不論傳遞過來的究竟是哪個(gè)類的對象,函數(shù)都能夠通過同一個(gè)接口調(diào)用到適應(yīng)各自對象的實(shí)現(xiàn)方法。
最常見的用法就是聲明基類的指針,利用該指針指向任意一個(gè)子類對象,調(diào)用相應(yīng)的虛函數(shù),可以根據(jù)指向的子類的不同而實(shí)現(xiàn)不同的方法。如果沒有使用虛函數(shù)的話,即沒有利用C++多態(tài)性,則利用基類指針調(diào)用相應(yīng)的函數(shù)的時(shí)候,將總被限制在基類函數(shù)本身,而無法調(diào)用到子類中被重寫過的函數(shù)。因?yàn)闆]有多態(tài)性,函數(shù)調(diào)用的地址將是一定的,而固定的地址將始終調(diào)用到同一個(gè)函數(shù),這就無法實(shí)現(xiàn)一個(gè)接口,多種方法的目的了。
純虛函數(shù)是在基類中聲明的虛函數(shù),它在基類中沒有定義,但要求任何派生類都要定義自己的實(shí)現(xiàn)方法。在基類中實(shí)現(xiàn)純虛函數(shù)的方法是在函數(shù)原型后加“= 0”。為了方便使用多態(tài)特性,常常需要在基類中定義虛函數(shù),在很多情況下,基類本身生成對象是不合情理的。為了解決這些問題,引入了純虛函數(shù)的概念,將函數(shù)定義為純虛函數(shù),則編譯器要求在派生類中必須予以重寫以實(shí)現(xiàn)多態(tài)性。同時(shí)含有純虛函數(shù)的類稱為抽象類,它不能生成對象。由于純虛函數(shù)所在的類中沒有它的定義,在該類的構(gòu)造函數(shù)和析構(gòu)函數(shù)中不允許調(diào)用純虛函數(shù),否則會導(dǎo)致程序運(yùn)行錯(cuò)誤,但其它成員函數(shù)可以調(diào)用純虛函數(shù)。
C++支持兩種多態(tài)性:(1)、編譯時(shí)多態(tài)性(靜態(tài)多態(tài),在編譯時(shí)就可以確定對象使用的形式):通過重載函數(shù)實(shí)現(xiàn);(2)、運(yùn)行時(shí)多態(tài)性(動態(tài)多態(tài),其具體引用的對象在運(yùn)行時(shí)才能確定):通過虛函數(shù)實(shí)現(xiàn)。
C++中,實(shí)現(xiàn)多態(tài)有以下方法:虛函數(shù)、抽象類、重載、覆蓋、模板。
函數(shù)重載(Overload):指在相同作用域里(如同一類中),函數(shù)同名不同參,返回值則不用理會,不同參可以是不同個(gè)數(shù),也可以是不同類型。效果:根據(jù)實(shí)參的個(gè)數(shù)和類型調(diào)用對應(yīng)的函數(shù)體。
函數(shù)覆蓋(Override)(函數(shù)重寫):指派生類中的函數(shù)覆蓋基類中的同名同參虛函數(shù),因此作用域不同。效果:基類指針或引用訪問虛函數(shù)時(shí)會根據(jù)實(shí)例的類型調(diào)用對應(yīng)的函數(shù)。
函數(shù)隱藏(Hide):對于子類中與基類同名的函數(shù),如果不是覆蓋那就成了隱藏。兩種情況:(1)、同名不同參;(2)、同名同參但基類不是virtual函數(shù)。
派生類的構(gòu)造函數(shù)使用說明:(1)、在派生類構(gòu)造函數(shù)中,只要基類不是僅使用無參的默認(rèn)構(gòu)造函數(shù),都要顯示的給出基類名稱參數(shù)表;(2)、基類沒有定義構(gòu)造函數(shù),派生類也可以不定義,使用默認(rèn)構(gòu)造函數(shù);(3)、基類有帶參構(gòu)造函數(shù),派生類必須定義構(gòu)造函數(shù)。
虛函數(shù)的重載函數(shù)仍是虛函數(shù)。在派生類重定義虛函數(shù)時(shí)必須有相同的函數(shù)原型,包括返回類型、函數(shù)名、參數(shù)個(gè)數(shù)、參數(shù)類型的順序必須相同。虛函數(shù)必須是類的成員函數(shù),不能為全局函數(shù),也不能為靜態(tài)函數(shù)。不能將友員說明為虛函數(shù),但虛函數(shù)可以是另一個(gè)類的友員。析構(gòu)函數(shù)可以是虛函數(shù),但構(gòu)造函數(shù)不能為虛函數(shù)。一般地講,若某類中定義有虛函數(shù),則其析構(gòu)函數(shù)也應(yīng)當(dāng)說明為虛函數(shù)。特別是在析構(gòu)函數(shù)需要完成一些有意義的操作,比如釋放內(nèi)存時(shí),尤其應(yīng)當(dāng)如此。在類系統(tǒng)中訪問一個(gè)虛函數(shù)時(shí),應(yīng)使用指向基類類型的指針或?qū)愵愋偷囊?,以滿足運(yùn)行時(shí)多態(tài)性的要求。當(dāng)然也可以像調(diào)用普通成員函數(shù)那樣利用對象名來調(diào)用一個(gè)函數(shù)。若在派生類中沒有重新定義虛函數(shù),則該類的對象將使用其基類中的虛函數(shù)代碼。
抽象類:如果一個(gè)類中至少有一個(gè)純虛函數(shù),那么這個(gè)類被稱為抽象類。抽象類不僅包括純虛函數(shù),也可包括虛函數(shù)。抽象類中的純虛函數(shù)可能是在抽象類中定義的,也可能是從它的抽象基類中繼承下來且重定義的。抽象類有一個(gè)重要特點(diǎn),即抽象類必須用作派生其它類的基類,而不能用于直接創(chuàng)建對象實(shí)例。抽象類不能直接創(chuàng)建對象的原因是其中有一個(gè)或多個(gè)函數(shù)沒有定義,但仍可使用指向抽象類的指針支持運(yùn)行時(shí)多態(tài)性。派生類中必須重載基類中的純虛函數(shù),否則它仍將被看作一個(gè)抽象類。從基類繼承來的純虛函數(shù),在派生類中仍是虛函數(shù)。
虛函數(shù)表:虛函數(shù)是通過一張?zhí)摵瘮?shù)表來實(shí)現(xiàn)的。簡稱為V-Table,在這個(gè)表中,主要是一個(gè)類的虛函數(shù)的地址表,這張表解決了繼承、覆蓋的問題,保證其真實(shí)反應(yīng)實(shí)際的函數(shù)。這樣,在有虛函數(shù)的類的實(shí)例中這個(gè)表被分配在了這個(gè)實(shí)例的內(nèi)存中,所以,當(dāng)我們用父類的指針來操作一個(gè)子類的時(shí)候,這張?zhí)摵瘮?shù)表就顯得有無重要了,它就像一個(gè)地圖一樣,指明了實(shí)際所應(yīng)該調(diào)用的函數(shù)。
一個(gè)多態(tài)的例子:
#include <iostream>using namespace std; class A{public:void foo(){printf("1\n");} virtual void fun(){printf("2\n");}}; class B : public A{public:void foo(){printf("3\n");} void fun(){printf("4\n");}}; int main(void){A a;B b; A* p = &a;p->foo();//1p->fun();//2 p = &b;p->foo();//1p->fun();//4 B* ptr = (B*)&a;ptr->foo();//3ptr->fun();//2 return 0;}
另一個(gè)例子:
#include <iostream>using namespace std; int main(void){class CA {public:virtual ~CA() {cout<<"delete CA"<<endl;}virtual int GetValue() {return 1;}}; class CB : public CA{public:~CB() {cout<<"delete CB"<<endl;}virtual int GetValue() {return 2;}}; CA* pA = new CB;cout<<pA->GetValue()<<endl;delete pA; /* result:2delete CBdelete CA*//*若父類CA中沒有將析構(gòu)函數(shù)定義為虛函數(shù),則result:2delete CA由結(jié)果看出,如果不將父類CA的析構(gòu)函數(shù)定義為虛函數(shù),則不會調(diào)用到子類的析構(gòu)函數(shù)*//*若父類CA中的成員函數(shù)GetValue沒有定義為虛函數(shù),則result:1delete CA*/ return 0;}
對C++繼承,封裝,多態(tài)的理解
用了C++一段時(shí)間,感覺對C++慢慢有了一點(diǎn)認(rèn)識,在這和大家分享一下。C++是一款面向?qū)ο蟮恼Z言,擁有面向?qū)ο笳Z言的三大核心特性:繼承,封裝,多態(tài)。每一個(gè)特性的良好理解與使用都會為我們的編程帶來莫大的幫助。下面我就這三個(gè)特性講一下我對C++的理解。
繼承
學(xué)過面向?qū)ο笳Z言的人基本都可以理解什么是繼承,但我們?yōu)槭裁匆褂美^承? 很多人說繼承可以使代碼得到良好的復(fù)用,當(dāng)然這個(gè)是繼承的一個(gè)優(yōu)點(diǎn),但代碼復(fù)用的方法除了繼承還有很多,而且有些比繼承更好。我認(rèn)為使用繼承最重要的原因是繼承可以使整個(gè)程序設(shè)計(jì)更符合人們的邏輯,從而方便的設(shè)計(jì)出想要表達(dá)的意思。比如我們要設(shè)計(jì)一堆蘋果,橘子,梨等水果類,使用面向?qū)ο蟮姆椒?,我們首先會抽象出一個(gè)水果的基類,而后繼承這個(gè)基類,派生出具體的水果類。如果要設(shè)計(jì)的水果很多,我們還可以在水果基類基礎(chǔ)上,繼續(xù)生成新的基類,比如熱帶水果類,溫帶水果類,寒帶水果類等,而后再繼承這些基類。這樣的設(shè)計(jì)思想就相當(dāng)于人類的分類思想,簡單易懂,而且設(shè)計(jì)出來的程序?qū)哟畏置?,容易掌握? 既然繼承這么好,那該如何使用繼承? 繼承雖好但不能濫用,否則設(shè)計(jì)出來的程序會雜亂不堪。根據(jù)上面的介紹,可以發(fā)現(xiàn)繼承主要用來定義一個(gè)東西是什么,比如熱帶水果是水果,菠蘿是熱帶水果等,即繼承主要用來設(shè)計(jì)一個(gè)程序的類的框架,將所要設(shè)計(jì)的東西用繼承來設(shè)立一個(gè)基本結(jié)構(gòu)。如果想為一個(gè)類添加一個(gè)行為或格外的功能,最好是使用組合的方式。如果想了解組合的方式,可以看一下比較著名的策略模式。
封裝
封裝是什么? 在C++中,比較狹隘的解釋就是將數(shù)據(jù)與操作數(shù)據(jù)的方法放在一個(gè)類中,而后給每個(gè)成員設(shè)置相應(yīng)的權(quán)限。從大一點(diǎn)的角度來說,封裝就是將完成一個(gè)功能所需要的所有東西放在一起,對外部只開放調(diào)用它的接口。 為什么要封裝? 我認(rèn)為模塊化設(shè)計(jì)是封裝的本質(zhì)原因。 對軟件設(shè)計(jì)或其他工程設(shè)計(jì),特別是比較復(fù)雜的工程,一般都是模塊化設(shè)計(jì)。模塊化設(shè)計(jì)的好處就是可以將一個(gè)復(fù)雜的系統(tǒng)拆分成不同的模塊。每一個(gè)模塊又可以獨(dú)立的設(shè)計(jì),調(diào)試,這就讓多人一起做一個(gè)復(fù)雜的工程成為現(xiàn)實(shí)。如果想做到模塊化設(shè)計(jì),封裝是不可缺少的一部分。一個(gè)好的模塊,比如一塊inter的CPU芯片,它有強(qiáng)大的功能,雖然我們不知道它內(nèi)部是如何實(shí)現(xiàn)的,但卻可以很好的使用它。
多態(tài)
什么是多態(tài)? 多態(tài)簡單的說就是“一個(gè)函數(shù),多種實(shí)現(xiàn)”,或是“一個(gè)接口,多種方法”。多態(tài)性表現(xiàn)在程序運(yùn)行時(shí)根據(jù)傳入的對象調(diào)用不同的函數(shù)。 C++的多態(tài)是通過虛函數(shù)來實(shí)現(xiàn)的,在基類中定義一個(gè)函數(shù)為虛函數(shù),該函數(shù)就可以在運(yùn)行時(shí),根據(jù)傳入的對象調(diào)用不同的實(shí)現(xiàn)方法。而如果該函數(shù)不設(shè)為虛函數(shù),則在調(diào)用的過程中調(diào)用的函數(shù)就是固定的。比如下面一個(gè)例子
////定義一個(gè)Duck基類,而后繼承Duck派生出一個(gè)RedHandDuck類。//其中display()方法,第一次運(yùn)行設(shè)為普通函數(shù),第二次設(shè)為虛函數(shù) #include "iostream" class Duck { public:Duck(){}~Duck(){} //定義一個(gè)虛函數(shù)displayvirtual void display(){ std::cout<<" I am a Duck !"<<std::endl;}}; class RedHandDuck:public Duck{ public:RedHandDuck(){}~RedHandDuck(){} //重寫displayvoid display(){ std::cout<<" I am a RedHandDuck !"<<std::endl;}}; int main(){ RedHandDuck* duck1 = new RedHandDuck();Duck* duck2 = duck1; duck1->display();duck2->display(); std::getchar();}
第一次運(yùn)行結(jié)果(不使用虛函數(shù)):
第二次運(yùn)行結(jié)果(使用虛函數(shù)):
由結(jié)果可以看到,由于虛函數(shù)的使用,Duck對象(可以理解為接口),調(diào)用的display()方法是根據(jù)傳入的對象決定的。這就實(shí)現(xiàn)了“一個(gè)接口,多種方法”。
從網(wǎng)上看到一個(gè)關(guān)于多態(tài)的介紹,非常精辟,分享給大家
多態(tài)與非多態(tài)的實(shí)質(zhì)區(qū)別就是函數(shù)地址是早綁定還是晚綁定。如果函數(shù)的調(diào)用,在編譯器編譯期間就可以確定函數(shù)的調(diào)用地址,并生產(chǎn)代碼,是靜態(tài)的,就是說地址是早綁定的。而如果函數(shù)調(diào)用的地址不能在編譯器期間確定,需要在運(yùn)行時(shí)才確定,這就屬于晚綁定。
關(guān)于C++中的封裝繼承和多態(tài)理解是什么問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。