c++引用類型(c++引用的定義)

    本文主要介紹c引用類型(c引用的定義),下面一起看看c引用類型(c引用的定義)相關資訊。
    -
    引用、繼承和派生引用的介紹首先要知道,參數的傳遞本質上是一個賦值的過程,賦值就是復制記憶。所謂內存復制是指將數據從一個內存復制到另一個內存,對于聚合類型(復雜類型,如結構和類)可能會消耗大量內存。
    一個引用可以看作是一個數據的別名,通過這個別名和原來的名字(指向同一個內存)可以找到這個數據。
    注意:
    引用必須在定義的同時初始化,以后會一直使用,不能引用其他數據。這有點類似于常量(const variable)引用,定義的時候需要添加,使用的時候不能添加。使用的時候,加法就是取地址int a = 99int r = a;cout a , r endl一般來說,在c中,引用作為函數參數來代替指針的作用,同樣可以改變數據內容,非常實用。
    同時,在c中,引用可以作為函數返回值,但是!!!對本地數據(如本地變量、本地對象、本地數組等)的引用。)無法返回,因為函數調用完成后本地數據會被破壞,下次數據可能就不存在了。
    int plus 10(int r){ int m = r 10;返回m;//返回對本地數據的引用}//對于某些編譯器,會報錯int num 3 = plus 10(num 1);int num 4 = plus 10(num 3);//但是有些編譯器是可以運行的,比如gcc,但是num3和num4的值是一樣的,因為函數是在棧上運行的,運行之后會放棄所有本地數據的管理權,后面的函數調用會覆蓋前面函數的本地數據,這兩點會改成最后一個值;語錄的精髓:
    其實引用只是指針的簡單封裝,它的底層還是通過指針來實現的。引用占用的內存和指針占用的內存是一樣的,32位環境下是4字節,64位環境下是8字節。之所以無法獲得引用的地址,是因為編譯器進行了內部轉換:
    int a = 99int r = a;r = 18coutrendl//在編譯時,會轉換成如下形式:int a = 99int * r = a;* r = 18coutrendl當r取地址時,編譯器會隱式轉換代碼,使代碼輸出r的內容(a的地址)而不是r的地址,這也是無法獲取引用的原因。變量地址的原因。也就是說,不是變量r不占用內存,而是編譯器不允許它獲取自己的地址。
    指針和引用之間的其他區別:
    引用必須在定義的時候初始化,以后也一直如此,不能再指向其他數據;指針不 我沒有這個限制。指針不 它們在定義時不必賦值,將來可以指向任意數據。
    你可以有常量指針,但是沒有常量引用,r可以 不要改變重點,添加常量是不必要的。
    指針可以有多級,引用只能有一級(如果你學了引用折疊,你可以想想它是不是正確的)。比如int **p是合法的,int r是非法的(c 11加了右值引用,是合法的),下面這個就可以了。
    int a = 10int r = a;int rr = r;//指向a的兩個地址指針與引用的遞增和遞減(-)操作的含義不同。使用指針表示它指向下一個數據,使用引用表示它所引用的數據本身加1。
    引用通常不能綁定臨時數據:
    指針和引用只能指向內存,不能指向寄存器或硬盤,因為寄存器和硬盤是不能尋址的。
    定義的變量、創建的對象、字符串常量、函數參數、函數體本身、new或malloc分配的內存等。,所有這些都可以用來獲取地址。
    什么數據可以 t被使用,它將被記錄在:
    基本類型的數據,如int、double、bool、char,通常小于8個字節,可以存放在一兩個寄存器中,所以這些類型的臨時數據通常放在寄存器中;但是,對象和結構變量是用戶定義的數據類型,它們的大小是不可預測的,所以這些類型的臨時數據通常放在內存中。int * p2 =(n 100);//不會,n 100會在寄存器里,常量表達式也會在寄存器里;s1 = {23,45 };s s2 = {90,75 };s * p1 =(s1 s2);//在visual c中是,s1 s2在內存中,但是!!!!gcc can t,因為gcc可以 不要引用任何臨時變量!!!bool iso dd(int n){ if(n % 2 = = 0){ return false;}else{返回true} } iso dd(a);//更正iso dd(a9);//錯誤,有時它 向它傳遞臨時數據很容易。去看看吧
    常量引用綁定臨時數據:
    常量引用:編譯器會為臨時數據創建一個新的,沒有名字的臨時變量,把臨時數據放到臨時變量里,然后把引用綁定到臨時變量上。
    把它改成常規引用就行了,因為為常規引用創建臨時變量是沒有意義的。創建和修改的臨時變量只是臨時變量中的數據,不會影響原始數據,意義不大。對于頻繁引用,我們只能通過const引用讀取數據的值,但是可以 t修改它的值,所以我們不 不用考慮同步更新的問題,我們不會 t產生兩個不同的數據。
    bool isodd(const int n){ //更改為頻繁引用if(n/2 = = 0){ r《整數在內存中是如何存儲的》《小數在內存中是如何存儲的》);因為引用的本質也是指針,所以引用的類型轉換也是錯誤的。
    int n = 100int * p1 = n;//正確的float * p2 = n;//error int r1 = n;//修正浮點r2 = n;//錯了但是!!!通過添加頻繁引用可以進行類型轉換。
    原則:當引用和數據的類型不一致時,如果它們的類型相似,則 數據類型的自動轉換,那么編譯器會創建一個臨時變量,將數據賦給這個臨時變量(此時會發生自動類型轉換),然后將引用綁定到這個臨時變量,這與 綁定對臨時數據的常量引用。
    int n = 100int r1 = n;//正確的常量float r2 = n;//正確總結:如果函數不要求改變引用值,函數參數應該使用const盡可能;第一,避免臨時數據;第二,避免不同類型;第三,常量和非常量參數都是可以接受的;
    double volume(const double len,const double width,const double hei){ return len * width * 2 len * hei * 2 width * hei * 2;}double v4 = volume(a 12.5,b 23.4,16.78);double v5 =音量(a b,a c,b c);繼承和派生繼承介紹了被繼承的類稱為父類或基類,被繼承的類稱為子類或派生類。 子類 和 父類和通常被召集在一起,而 基類和和 派生類 通常被召集在一起。意思是一樣的。
    什么時候?等待繼承:類之間必須有很大的相關性,有很多共同的成員函數和成員變量。
    繼承的成員可以通過子類對象訪問,就像它們自己的一樣。
    繼承格式:
    類派生類名稱:[繼承方法]基類名稱{派生類新添加的成員};
    三種繼承
    公共繼承模式
    基類中的公共成員-派生類中成為基類中公共屬性的受保護成員-派生類中的私有成員或基類中的受保護屬性-不能在派生類中使用的不可見的受保護繼承方法。
    基類中的公共成員——派生類中的受保護成員成為基類中的受保護成員——派生類中的私有成員或基類中的受保護成員——派生類中不能使用的不可見私有繼承方法。
    在基類中,public\protected-成為派生類中的私有屬性。在派生類中,只有基類中的私有成員才能在類中使用——不能在派生類中使用,不可見的protected屬性只能在派生類(類代碼)中訪問;沒別的;
    不難發現:
    1)繼承模式中的public、protected、private是用來指明基類成員在派生類中的最高訪問權限,不能超過,即即使基類是公共成員屬性,派生類采用受保護的繼承,公共成員屬性也只能變成受保護的;
    2)基類中的私有成員永遠不能在派生類中使用,但是可以通過public的set和get函數使用(訪問派生類中私有成員的唯一方法是使用基類的非私有成員函數)。
    3)如果希望基類的成員被派生類繼承,無障礙使用,那么這些成員只能聲明為public或者protected。
    4)如果您希望基類的成員不公開(不能通過對象訪問),但也在派生類中使用,則只能將它們聲明為protected。
    注意,我們這里說的是基類的私有成員不能在派生類中使用,而不是基類的私有成員不能被繼承。其實基類的私有成員是可以繼承的,(成員變量)會占用派生類對象的內存,但是在派生類中是不可見的,這就使得它不可用。
    若要更改訪問權限,請使用using關鍵字。
    使用只能更改基類中公共和受保護成員的訪問權限,而不能更改私有成員的訪問權限。因為基類中的私有成員在派生類中是不可見的,不能被寫入;
    //基類people class people {public: void show;protect: char * m _ name;int m _ age};虛空人: :秀{ cout m _ nam:公人{public: void learning;public:使用people ::m _ name;//將protected改為public使用人: : _ age;//將protected改為public float m _ scor:用people :: show;//將public改為private };繼承時的名稱屏蔽
    對于函數來說,無論函數的參數是什么,只要名稱相同,就會被遮擋,不會出現重載。如果要調用,使用域名和域解析器;
    對于成員變量,只要名稱相同,派生類就會覆蓋基類,但是基類的成員變量同時存在。此時,從基類繼承的get和set方法都是對基類中同名變量的操作,而不是對派生類中同名變量的操作。
    類繼承的作用域嵌套和對象內存模型假設base是基類,derived是派生類,那么它們的作用域嵌套關系將如下:
    編譯器將從內向外查找以下類的范圍:
    通過obj (c類對象c)訪問成員變量n時,可以在c類的作用域中找到n這個名字,雖然a類和b類都有n這個名字,但是編譯器不會在它們的作用域中查找,所以是不可見的,也就是派生類中的n遮住了基類中的n。
    通過obj訪問成員函數func時,在c類的作用域中沒有找到名字func,b也沒有 我找不到它。然后我們繼續在a類的范圍內搜索,結果找到了func這個名字。當搜索結束時,編譯器決定調用類a來做這件事。在域中使用func函數(這個過程叫做名稱搜索,而且都是通過名稱搜索,除非直接通過域名和域解析器搜索,否則不會有這個過程);
    對象內存模型:
    沒有繼承的時候,簡單,變量存在于堆或者棧區,函數存在于代碼段;
    當繼承存在時:
    所有變量都連續存在于堆區或棧區(成員變量按照派生的層次順序排列,新加入的成員變量總是在最后,私有和隱藏變量也會在內存中),函數存在于代碼區(所有對象都是共享的,但能否使用取決于權限,私有的可以 必要時不使用)。
    示例:
    obj_a是基類對象,obj_b是派生類對象。假設obj_a的起始地址為0x1000,其內存分布如下圖所示:
    假設obj_b的起始地址是0x1100,a類中的m_b是私有的,那么它的內存分布如下圖所示:
    假設obj_c的起始地址是0x1300,并且有屏蔽,其內存分布如下圖所示:
    總結:在派生類的對象模型中,會包含基類的所有成員變量。這種設計方案的優點是訪問效率高,可以在派生類對象中直接訪問基類變量,不需要幾層間接計算。
    在設計基類和派生類的構造函數/析構函數時,派生類的構造函數也要對繼承的成員變量進行初始化,但大多數基類都有帶有私有屬性的成員變量,在派生類中是的,更不用說被派生類的構造函數初始化了。解決這個問題的思路是在派生類的構造函數中調用基類的構造函數。
    # includeiostreamusing命名空間std//基類people class people{protect: char * m _ name;int m _ agepublic:人(char*,int);};人: :人(char * nam: m _ nam:公有p: float m _ score;public:學生(char *name,int age,float score);空白display;};//people(姓名,年齡)是調用基類的構造函數,student : : student(char * name,intage,float scor: people(姓名,年齡),m_score(分數){ } void student : : display{ coutm _ name 有著 的年齡年齡 得分為 m _ score 。 endl} int main{ student stu( 小明 , 16, 90.5);stu.display。返回0;}people(name,age)是調用基類的構造函數并將名字和年齡作為實參傳遞給它,m_score(score)是派生類的參數初始化表。其次,不存在m_score(score)放在前面的問題,它會遵循先調用基類構造函數,再初始化參數初始化表中其他成員變量的原則。
    構造函數調用順序:
    當類a-b-c按照a類構造函數-b類構造函數-c類構造函數的順序執行時,其中a是c的間接基類,b是c的直接基類;派生類的構造函數只能調用直接基類的構造函數,而不能調用間接基類的構造函數,因為c丟棄了b類的構造函數,b會先調用a類的構造函數,相當于c間接(或隱式)調用a的構造函數,如果在c中再次顯式調用a的構造函數,a的構造函數就會被調用兩次,相應的,初始化工作也會做兩次,不僅多余,而且沒有必要。
    基類構造函數調用規則:
    通過派生類創建對象時,必須調用基類的構造函數,這是語法規則。換句話說,在定義派生類構造函數時,最好參考明基類構造函數;如果沒有指定,調用基類的默認構造函數(不帶參數的構造函數);如果沒有默認構造函數。
    #使用命名空間std包含iostreamusing//基類people class people{public:人;//基類的默認構造函數people(char *name,int age);protect: char * m _ nam::p: m _ name( xxx ),m _ age(0){ } people ::people(char * nam: m _ nam:公立people{public:學生;student(char*,int,float);public:虛空顯示;privat:浮點m _ scor::stud: m _ score(0.0){ }//派生類默認構造函數student: :學生(char * name,intage,float scor:人(姓名,年齡),m _ score(分數){ } void student: : display{ coutm _ name 有著 的年齡年齡 得分為 m _ score 。 endl} int main{ student stu 1;stu 1 . display;學生stu 2( 小明 , 16, 90.5);stu 2 . display;返回0;}在創建對象stu1時,執行派生類的構造函數student: : student,該函數不指定調用基類的哪個構造函數。從運行結果中可以明顯看出,系統默認調用不帶參數的構造函數,即people : :人。
    創建對象stu2時,執行派生類的構造函數student: :學生(char * name,intage,f)。loat score),它指示基類的構造函數。
    對于析構函數
    析構函數也不能被繼承。與構造函數不同的是,在派生類的析構函數中不需要顯式調用基類的析構函數,因為每個類只有一個析構函數,編譯器知道如何選擇,不需要我們的干預。
    析構函數和構造函數的執行順序正好相反:
    創建派生類對象時,構造函數的執行順序與繼承順序相同,即先執行基類構造函數,再執行派生類構造函數。當派生類對象被銷毀時,析構函數的執行順序與繼承順序相反,即先執行派生類析構函數,再執行基類析構函數。多繼承多繼承對象內存模型c不僅有單繼承,還有多繼承。
    d:類公共a、私有b、受保護c {//d類的新成員}
    d是一個具有多種繼承形式的派生類。它以公共繼承a類,以私有繼承b類,以保護繼承c類。d .根據不同的繼承方法,得到a、b、c中的成員,確定其在派生類中的訪問權限。
    多重繼承下的構造:
    基類構造函數的調用順序與它們在派生類的構造函數中出現的順序無關,但與聲明的派生類的時基類出現的順序相同(類似于類中的變量)。和上面的例子一樣,先構造a,再構造b,再構造c,最后構造d;
    命名:
    當兩個或多個基類中存在同名的成員(成員變量或成員函數)時,如果直接訪問該成員,就會發生命名,編譯器不知道使用基類的哪個成員。此時,需要在成員名前添加類名和域解析器::,以便明確指出使用哪個類成員,消除歧義。
    內存模型:
    直接上例:
    #include cstdiousing命名空間std//基類a class a{public: a(int a,int b);protect:國際機場;int m _ b;};a:: a(int a,int b): m _ a(a),m _ b(b){ }//基類bclass b{public: b(int b,int c);protect: int m _ b;int m _ c;};b::b(intb,int c): m _ b(b),m _ c(c){ }//派生類c c:公共a,公共b{public: c(int a,int b,int c,int d);public:虛空顯示;privat:國際機場;int m _ c;int m _ d;};c: :c(int a,int b,int c,int d): a(a,b),b(b,c),m_a(a),m_c(c),m _ d(d){ } void c: : display{ printf( a: :m _ a = % d,a: :m _ b = % d \ n ,a::m_a,a: :m _ b);printf( b: :m _ b = % d,b: :m _ c = % d \ n ,b::m_b,b: :m _ c);printf( c: :m _ a = % d,c: :m _ c = % d,c: :m _ d = % d \ n ,c::m_a,c::m_c,m _ d);}int main{ c obj_c(10,20,30,40);obj _ c . display;返回0;}借助指針突破訪問權限:
    認為指針指向內存地址,對象指針指向對象的內存地址,通過內存模型可以知道private也在連續內存中,所以!!!就用手指pin偏移量可以強制訪問私有成員變量;例如:
    圖中假設obj對象的起始地址為0x1000,m_a(public)、m_b(public)和m_c (private)分別與對象的開頭相隔0、4、8個字節。我們稱之為距離偏移。
    你知道:
    int b = p-m _ b;
    會轉換成:int b = *(int *)((int)p sizeof(int));
    實際上是:int b = *(int *)((int)p4);
    有:
    所以:int c = *(int *)((int)p sizeof(int)* 2);//it ;就這么簡單。
    虛擬繼承1。什么是虛擬繼承和虛擬基類?
    多重繼承容易出現命名:經典鉆石繼承
    第一個問題:在派生類中保存間接基類成員的多個副本,雖然不同的數據可以存儲在不同的成員變量中,但在大多數情況下是多余的,因為保存多個成員變量不僅占用更多的存儲空間,而且容易導致命名。如果a類有一個成員變量a,那么在d類中直接訪問a會導致二義性,而編譯器沒有 我不知道它是來自路徑a-b-d還是來自路徑a-c-d
    為了消除歧義,我們可以在m_a前面注明來自哪個類(按域處理)。
    但是內存中仍然有兩個間接基類,很消耗內存,所以為了解決多重繼承中的命名和冗余數據問題,提出了虛擬繼承,使得派生類中只保留間接基類的一個成員。
    //間接基類aclass a{ //虛擬基類protect: int m _ a;};//直接基類b class b:虛公共a {//虛繼承protect: int m _ b;};//直接基類c類c:虛公共a {//虛繼承protect: int m _ c;};//派生類d類d:公共b,公共c{public: void seta(inta){ m _ a = a;}//更正void setb(int b){ m _ b = b;}//更正void setc(int c){ m_c= c;}//更正void s: int m _ d;};int main{ d d;返回0;虛擬繼承的目的是讓一個類聲明它愿意共享它的基類。其中,這個共享基類稱為虛擬基類。
    我們可以看到一個問題:虛擬派生的運算必須在虛擬派生的真實需求出現之前完成,也就是在d的需求出現之前,要將b和c設置為虛擬繼承;
    即虛擬派生只影響指定虛擬基類的派生類進一步派生的類(繼承bc的類,比如e繼承b,f繼承c會有影響),不會影響派生類本身(bc);
    在實際開發中,中間層的基類一般會將其繼承聲明為虛擬繼承,不會帶來任何問題。使用虛擬繼承的類層次結構是由一個人或一個項目組一次性設計的,因為沒有考慮到后期的需求,所以不需要修改中間函數;c庫iostream只是采用了虛繼承。
    2.虛擬基類成員的可見性
    以鉆石繼承為例,假設a定義了一個名為x的成員變量,當我們在d中直接訪問x時,有三種可能:
    如果b和c中都沒有x的定義,那么x會被解釋為a的成員,此時不存在歧義。如果b或c中的某個類定義了x,就不會有二義性,派生類的x比虛基類的x優先級高。如果x在b和c中都有定義,那么直接訪問x會造成二義性。第二類問題:bc包含相同優先級的變量。此時,只能使用領域分析來消除歧義。
    3.虛擬繼承的構造函數和內存模型;
    與繼承的構造過程不同,派生類的構造函數必須調用虛基類的構造函數。
    #使用命名空間std包含iostreamusing//虛擬基類a class a{public: a(int a);protect:國際機場;};a: : a(int a): m _ a(a){ }//直接從b class b:虛擬公共a {公共echo。2-@.com b(int a,int b);public:虛空顯示;protect: int m _ b;};b: :b(int a,int b): a(a),m _ b(b){ } void b: : display{ cout m _ a = 并購 ,m _ b = m _ bendl}//直接派生類c類c:虛公共a{public: c(int a,int c);public:虛空顯示;protect: int m _ c;};c: :c(int a,int c): a(a),m _ c(c){ } void c: : display{ cout m _ a = 并購 ,m _ c = m _ c:公共b,公共c{public: d(int a,int b,int c,int d);public:虛空顯示;privat:國際機場;};de cho 2-@ . com :d(int a,int b,int c,int d): a(a),b(90,b),c(100,c),m _ d(d){ } void de cho 2-@ . com : display{ cout m _ a = 并購 ,m _ b = m _ b ,m _ c = m _ c ,m _ d = m _ dendl}在最終派生類d的構造函數中,除了b和c的構造函數外,還調用了a的構造函數。因為現在a在最終的派生類d中只有一份內存,如果是b\c構造的,編譯器會很困惑,不會 我不知道該初始化哪一個。
    虛擬繼承時間構造函數的執行順序與普通繼承不同:在最終派生類的構造函數調用列表中,不管每個構造函數出現的順序如何,編譯器總是先調用虛基類的構造函數,然后按照出現的順序調用其他構造函數;對于普通的繼承,按照構造函數出現的順序調用它。
    對于普通繼承,基類子對象總是在派生類對象的前面(即基類成員變量總是在派生類成員變量的前面),無論繼承級別有多深,它距派生類對象頂部的偏移量都是固定的(固定位置)。請看下面的例子:
    a{protect: int m _ a1級;int m _ a2};b:公共a{protect: int b1級;int b2};c:公共b{protect: int c1級;int c2};d:公共c{protect: int d1級;int d2};int main{ a obj _ a;b obj _ bc obj _ cd obj _ d返回0;} a類所在的內存位置永遠在前面。
    1)修改上面的代碼,使a成為b的虛擬基類:
    b:虛擬公共a類
    a將移動到后面
    2)假設a是b and b的虛基類,是c的虛基類
    從上面兩張圖可以看出,虛擬繼承時的派生類對象分為兩部分:
    沒有陰影的部分有固定的偏移量,不會隨著繼承級別的增加而改變,稱為固定部分;陰影部分是虛擬基類的一個子類,偏移量會隨著繼承級別的增加而變化,這部分稱為共享部分。有一個問題:如何計算分攤部分的偏移量?
    對于虛擬繼承,派生類分為固定部分和共享部分,共享部分放在最后。幾乎所有的編纂者都在這一點上達成了共識。主要區別在于如何計算分攤部分的抵消,沒有統一的標準。
    以下是vs的解決方案示例:
    vc引入了虛擬基類表。如果一個派生類有一個或多個虛擬基類,編譯器將在派生類對象中插入一個指針,指向虛擬基類表。虛擬基類表實際上是一個數組,數組中的元素存儲每個虛擬基類的偏移量字節。
    假設a是b and b的虛基類,c是c的虛基類,那么每個對象的內存該模型如下圖所示:
    虛擬繼承表存儲了所有虛擬基類(包括直接繼承和間接繼承)相對于當前對象的偏移量,這樣在通過派生類指針訪問虛擬基類的成員變量時,無論繼承級別有多深,都只需要一次間接轉換。
    這種方案還可以避免在有多個虛擬基類的情況下,使派生類對象承載太多指針,只需要承載一個指針。例如,假設類a、b、c和d的繼承關系是:
    內存模型是:
    當派生類被賦給基類,數據類型被轉換,int類型的數據被賦給float類型的變量時,編譯器會先把int類型的數據轉換成float類型再賦值;同樣,類也可以有數據類型轉換,也是數據類型;
    但這種轉換只在基類和派生類之間有意義,只能將派生類賦給基類,包括將派生類對象賦給基類對象,將派生類指針賦給基類指針,將派生類引用賦給基類引用,這在c中稱為向上造型,相應地,將基類賦給派生類稱為向下造型。
    過渡期間非常安全。
    分配的本質是將現有數據寫入分配的內存。對象的內存只包含成員變量,所以對象之間的賦值就是成員變量的賦值,不存在成員函數的賦值問題。
    這種轉換關系是不可逆的,只能用派生類對象給基類對象賦值,不能給派生類對象賦值。(因為基類不包含派生類的成員變量,所以不能給派生類的成員變量賦值。同樣,不能在同一基類的不同派生類對象之間進行賦值)。
    #使用命名空間std包含iostreamusing//基類class a{public: a(int a);public:虛空顯示;public:國際機場;};a: :a(int a): m _ a(a){ } void a: : display{ cout a: m _ a級= m _ a:公共a{public: b(int a,int b);public:虛空顯示;public: int m _ b;};b: :b(int a,int b): a(a),m _ b(b){ } void b: : display{ cout b: m _ a級= 并購 ,m _ b = m _ bendl} int main{ a a(10);b b(66,99);//賦值前,a . display;b .顯示器;cout - endl//賦值后,a = b;//此時a.m_a變成66;a .顯示器;b .顯示器;返回0;}除了將派生類對象賦給類基類對象,還可以將派生類指針賦給基類指針:
    以下繼承關系:
    #使用命名空間std包含iostreamusing//基類a class a{public: a(int a);public:虛空顯示;protect:國際機場;};a: :a(int a): m _ a(a){ } void a: : display{ cout a: m _ a級= m _ aendl}//中間派生類b class b:公共a{public: b(int a,int b);public:虛空顯示;protect: int m _ b;};b: :b(int a,int b): a(a),m _ b(b){ } void b: : display{ cout b: m _ a級= 并購 ,m _ b = m _ bendl}//基類c類c{public: c(int c);public:虛空顯示;protect: int m _ c;};c::c(int c): m _ c(c){ } void c:: display{ cout c:級m _ c = m _ c: public b,public c{public: d(int a,int b,int c,int d);public:虛空顯示;privat:國際機場;};de cho 2-@ . com :d(int a,int b,int c,int d): b(a,b),c(c),m _ d(d){ } void de cho 2-@ . com : display{ cout d: m _ a級= 并購 ,m _ b = m _ b ,m _ c = m _ c ,m _ d = m _ dendl} int main{ a * pa = new a(1);b *pb = new b(2,20);c *pc =新c(3);d *pd =新d(4,40,400,4000);pa = pd//將pd的指針賦給pa;pa 的地址也是pd s地址,pa-display;//但是函數還是用了pa的函數,和指針類型pb = pd有關;p b- display;pc = pdpc -顯示器;cout - endlcout pa = paendlcout pb = pbendlcout pc = pcendlcout pd = pdendl返回0;可以看出,與對象變量之間的賦值不同,對象指針之間的賦值并不復制對象的成員,也不修改對象本身的數據,只是改變指針的方向。
    第一個問題:為什么pa的地址變了,指向pd?為什么被調用的函數仍然屬于pa?
    輸出值的解釋:pa本來是指向基類a的指針,現在指向了派生類d的對象,使得隱式指針發生了這種變化,也指向了類d的對象,所以最終在display內部使用了類d的成員變量也就不難理解了。
    函數調用解釋:編譯器雖然通過指針訪問成員變量,但并不通過指針訪問成員函數:編譯器通過指針類型訪問成員函數。對于pa來說,它的類型是a,無論指向哪個對象,都使用a類的成員函數..具體原因在前面的注釋中已經詳細解釋過了:c函數的編譯原理和成員函數的實現;
    第二個問題:為什么不是 pc的地址和pd的地址不一樣,而pa,pb和pd是一樣的?
    我們通常認為賦值就是把一個變量的值賦予另一個變量。雖然這種想法是正確的,但是需要注意的是,編譯器可能會在賦值之前處理現有的值。比如將double類型的值賦給int類型的變量,編譯器會直接擦除小數部分,導致賦值運算符兩邊的變量值不相等。
    將派生類的指針賦給基類的指針時也是如此,編譯器也可能在賦值前對其進行處理:
    對象的指針必須指向對象的起始位置。對于a類和b類,其子對象的起始地址與d類相同,所以在給pa和pb賦值pd時不需要做任何調整,直接傳遞已有的值即可;但是,c類子對象與d類對象的開頭有一定的偏移。在給pc賦值pd時要加上這個偏移量,這樣pc就可以指向c類子對象的起始位置,即執行pc = pd語句,編譯器調整了pd的值,導致pc和pd的值不同。
    內存模型:
    將派生類引用分配給基類引用
    引用只是封裝了指針,本質上沒有區別,所以我猜測把派生類引用賦給基類引用的效果應該和指針一樣。
    //更改上面main函數中的content int main {d d d (4,40,400,4000);a ra = d;b rb = d;c rc = d;ra . display;rb.display。rc . display;返回0;}果不其然,運行結果是:a:類m _ a = 4 b:類m _ a = 4,m _ b = 40 cecho2-@.co類。m m c = 400
    具體分析同指針;
    你可以去看看這個博客來增強對向上轉化的理解-。
    標簽:
    函數派生類
    了解更多c引用類型(c引用的定義)相關內容請關注本站點。

    芝麻認證初始化失敗(端外)是什么意思(芝麻認證初始化失敗 公積金提取)
    裝機必備軟件有哪些win7操作系統版本(電腦裝機必備軟件清單win7)
    聯想電腦開不開機怎么辦一直黑屏
    海外版抖音tik tok下載(抖音海外版tiktok下載安裝)
    安卓手機裝windows軟件(手機安裝windows教程)
    c++引用類型(c++引用的定義)
    怎么設置無朋友圈入口(怎么設置無朋友圈背景)
    20tb硬盤,存儲服務器需要20T的硬盤空間RAID1單塊2T需要多少20塊硬盤
    加了個固態硬盤怎么設置,安裝固態硬盤bios里怎么設置
    咸魚芝麻信用未授權的賣家可信嗎(閑魚上賣家芝麻信用未授權的)
    ios懸浮球怎么用,iphone懸浮球怎么用
    蘋果手機如何轉發語音聊天記錄(如何轉發語音聊天記錄QQ)
    磁盤分區會不會丟失文件,硬盤分區會丟失文件嗎
    惠普打印機維修點,庫爾勒惠普打印機維修點
    固態硬盤可以用來存儲數據嗎,固態硬盤和機械硬盤都能存儲數據嗎
    手機sd卡根目錄是什么東西(手機存儲的根目錄)
    1000到2000的游戲本
    榮耀7升級132怎么樣,華為榮耀7怎么樣
    樂視盒子怎么聯網(樂視盒子怎么用)
    表格每次打開都提示保存(表格保存提示)
    主站蜘蛛池模板: 综合久久一区二区三区| 日韩免费一区二区三区在线 | 无码精品久久一区二区三区 | 国产精品va无码一区二区| 一区二区三区在线|欧| 国产福利一区二区| 无码aⅴ精品一区二区三区浪潮| 亚洲国产AV无码一区二区三区| 亚洲乱码一区二区三区在线观看| 亚洲国产一区在线观看| 国产伦精品一区二区三区视频金莲| 亚洲男女一区二区三区| 国产免费播放一区二区| 人妻互换精品一区二区| 麻豆一区二区99久久久久| 久久无码精品一区二区三区| 亚洲国产欧美日韩精品一区二区三区| 日韩精品无码一区二区视频| 北岛玲在线一区二区| 在线播放一区二区| 久久久久成人精品一区二区| 一区二区三区亚洲视频| 欧美日韩精品一区二区在线观看 | 国产福利无码一区在线| 国产精品视频一区二区猎奇| 国内精品一区二区三区东京| 国产乱码一区二区三区四| 亚洲熟妇AV一区二区三区宅男| 亚洲国产精品一区二区三区在线观看| 精品无码国产一区二区三区AV| 亚洲一区二区中文| 久久一区二区三区精华液使用方法| 日本一区二区三区不卡视频| 精品女同一区二区三区在线| 一区视频在线播放| 久久久久无码国产精品一区| 亚洲丶国产丶欧美一区二区三区| 一本一道波多野结衣一区| 国产在线精品观看一区| 在线视频一区二区三区三区不卡| 人妻无码一区二区不卡无码av|