欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
C++編程思想重點(diǎn)筆記

  

2014-12-01 14:49 3306人閱讀  
FROM  http://blog.csdn.net/lanxuezaipiao/article/details/41647333

 

  1. C和C++指針的最重要的區別在于:C++是一種類(lèi)型要求更強的語(yǔ)言。void *而言,這一點(diǎn)表現得更加突出。C雖然不允許隨便地把一個(gè)類(lèi)型的指針指派給另一個(gè)類(lèi)型,但允許通過(guò)void *來(lái)實(shí)現。例如:

    1. bird* b;
    2. rock* r;
    3. void* v;
    4. v = r;
    5. b = v;

    C++不允許這樣做,其編譯器將會(huì )給出一個(gè)出錯信息。如果真的想這樣做,必須顯式地使用映射,通知編譯器和讀者。

  2. 參數傳遞準則 
    當給函數傳遞參數時(shí),人們習慣上應該是通過(guò)常量引用來(lái)傳遞,這種簡(jiǎn)單習慣可以大大提高效率:傳值方式需要調用構造函數和析構函數,然而如果不想改變參數,則可通過(guò)常量引用傳遞,它僅需要將地址壓棧。 事實(shí)上,只有一種情況不適合用傳遞地址方式,這就是當傳值是唯一安全的途徑,否則將會(huì )破壞對象(而不是修改外部對象,這不是調用者通常期望的)。

  3. C++訪(fǎng)問(wèn)權限控制:public、private、protected 
    其中protected只有在繼承中才有不同含義,否則與private相同,也就是說(shuō)兩者只有一點(diǎn)不同:繼承的結構可以訪(fǎng)問(wèn)protected成員,但不能訪(fǎng)問(wèn)private成員。

  4. 前置聲明注意

    1. struct X; // Declaration(incomplete type spec)
    2. struct Y
    3. {
    4. void f(X *memx);
    5. void g(X memx); // not allowed, the size of X is unknown.
    6. };

    這里f(X*)引用了一個(gè)X對象的地址,這是沒(méi)有任何問(wèn)題的,但如果是void g(X memx);就不行了,編譯器會(huì )報錯。這一點(diǎn)很關(guān)鍵,因為編譯器知道如何傳遞一個(gè)地址,這一地址大小是一定的,而不用管被傳遞的對象類(lèi)型大小。如果試圖傳遞整個(gè)對象,編譯器就必須知道X的全部定義以確定它的大小以及如何傳遞它,這就使程序員無(wú)法聲明一個(gè)類(lèi)似于Y :: g(X) 的函數。

  5. C++是純的嗎? 
    如果某個(gè)類(lèi)的一個(gè)函數被聲明為friend,就意味著(zhù)它不是這個(gè)類(lèi)的成員函數,但卻可以修改類(lèi)的私有成員, 而且它必須被列在類(lèi)的定義中,因此我們可以認為它是一個(gè)特權函數。這種類(lèi)的定義提供了有關(guān)權限的信息,我們可以知道哪些函數可以改變類(lèi)的私有部分。 因此,C++不是完全的面向對象語(yǔ)言,它只是一個(gè)混合產(chǎn)品。friend關(guān)鍵字就是用來(lái)解決部分的突發(fā)問(wèn)題。它也說(shuō)明了這種語(yǔ)言是不純的。畢竟C + +語(yǔ)言的設計是為了實(shí)用,而不是追求理想的抽象。

  6. C++輸入輸出流的操縱算子(manipulator)有:endl、flush、ws、hex等。

    1. cout<<flush; // 清空流
    2. cout << hex << "0x" << i; // 輸出16進(jìn)制
    3. cin>>ws; // 跳過(guò)空格

    iostream.h還包括以下的操縱算子:

    操縱算子作用
    showbase/noshowbase在打印一整數值時(shí),標明數字基數(十進(jìn)制,八進(jìn)制和十六進(jìn)制);所用的格式能被C++編譯器讀出
    showpos/noshowpos顯示正值符號加(+)
    uppercase/nouppercase顯示代表十六進(jìn)制值的大寫(xiě)字母A - F以及科學(xué)記數法中的E
    showpoint/noshowpoint表明浮點(diǎn)數值的小數點(diǎn)和后面的零
    skipws/noskipws跳過(guò)輸入中的空白字符
    left左對齊,右填充
    right右對齊,左填充
    internal在引導符或基數指示符和值之間填充
    scientific使用科學(xué)記數法
    fixedsetprecision()或ios::precision()設置小數點(diǎn)后面的位數

    如何建立我們自己的操縱算子? 
    我們可能想建立自己的操縱算子,這是相當簡(jiǎn)單的。一個(gè)像endl這樣的不帶參數的操縱算子只是一個(gè)函數,這個(gè)函數把一個(gè)ostream引用作為它的參數。對endl的聲明是:

    1. ostream& endl(ostream&);

    例子:產(chǎn)生一個(gè)換行而不刷新這個(gè)流。人們認為nl比使用endl要好,因為后者總是清空輸出流,這可能引起執行故障。

    1. ostream& nl(ostream& os) {
    2. return os << "\n";
    3. }
    4. int main() {
    5. cout << "newlines" << nl << "between" << nl << "each" << nl << "word" << nl;
    6. return 0;
    7. }
  7. C語(yǔ)言中const與C++中const的區別: 
    常量引進(jìn)是在早期的C++版本中,當時(shí)標準C規范正在制訂。那時(shí),常量被看作是一個(gè)好的思想而被包含在C中。但是,C中的const意思是“一個(gè)不能被改變的普通變量”,在C中,它總是占用存儲而且它的名字是全局符。C編譯器不能把const看成一個(gè)編譯期間的常量。在C中, 如果寫(xiě):

    1. const bufsize=100;
    2. char buf[bufsize];

    盡管看起來(lái)好像做了一件合理的事,但這將得到一個(gè)錯誤結果。因為bufsize占用存儲的某個(gè)地方,所以C編譯器不知道它在編譯時(shí)的值。在C語(yǔ)言中可以選擇這樣書(shū)寫(xiě):

    1. const bufsize;

    這樣寫(xiě)在C++中是不對的,而C編譯器則把它作為一個(gè)聲明,這個(gè)聲明指明在別的地方有存儲分配。因為C默認const是外部連接的,C++默認cosnt是內部連接的,這樣,如果在C++中想完成與C中同樣的事情,必須用extern把連接改成外部連接:

    1. extern const bufsize;//declaration only

    這種方法也可用在C語(yǔ)言中。 
    注意:在C語(yǔ)言中使用限定符const不是很有用,即使是在常數表達式里(必須在編譯期間被求出);想使用一個(gè)已命名的值,使用const也不是很有用的。C迫使程序員在預處理器里使用#define。

  8. 類(lèi)里的const和enum 
    下面的寫(xiě)法有什么問(wèn)題嗎?:

    1. class bob {
    2. const size = 100; // illegal
    3. int array[size]; // illegal
    4. }

    結果當然是編譯不通過(guò)。why?因為const在類(lèi)對象里進(jìn)行了存儲空間分配,編譯器不能知道const的內容是什么,所以不能把它用作編譯期間的常量。這意味著(zhù)對于類(lèi)里的常數表達式來(lái)說(shuō),const就像它在C中一樣沒(méi)有作用。

    在類(lèi)里的const意思是“在這個(gè)特定對象的壽命期內,而不是對于整個(gè)類(lèi)來(lái)說(shuō),這個(gè)值是不變的”。那么怎樣建立一個(gè)可以用在常數表達式里的類(lèi)常量呢? 
    一個(gè)普通的辦法是使用一個(gè)不帶實(shí)例的無(wú)標記的enum。枚舉的所有值必須在編譯時(shí)建立,它對類(lèi)來(lái)說(shuō)是局部的,但常數表達式能得到它的值,這樣,我們一般會(huì )看到:

    1. class bob {
    2. enum { size = 100 }; // legal
    3. int array[size]; // legal
    4. }

    使用enum是不會(huì )占用對象中的存儲空間的,枚舉常量在編譯時(shí)被全部求值。我們也可以明確地建立枚舉常量的值:enum { one=1,two=2,three};

  9. 類(lèi)里面的const成員函數

    1. class X {
    2. int i;
    3. public:
    4. int f() const;
    5. }

    這里f()是const成員函數,表示只能const類(lèi)對象調用這個(gè)函數(const對象不能調用非const成員函數),如果我們改變對象中的任何一個(gè)成員或調用一個(gè)非const成員函數,編譯器將發(fā)出一個(gè)出錯信息。 
    關(guān)鍵字const必須用同樣的方式重復出現在定義里,否則編譯器把它看成一個(gè)不同的函數:

    1. int X::f() const { return i;}

    任何不修改成員數據的函數應該聲明為const函數,這樣它可以由const對象使用。 
    注意:構造函數和析構函數都不是const成員函數,因為它們在初始化和清理時(shí),總是對對象作些修改。


    引申:如何在const成員函數里修改成員 —— 按位和與按成員const

    如果我們想要建立一個(gè)const成員函數,但仍然想在對象里改變某些數據,這時(shí)該怎么辦呢?這關(guān)系到按位const和按成員const的區別。按位const意思是對象中的每個(gè)位是固定的,所以對象的每個(gè)位映像從不改變。按成員const意思是,雖然整個(gè)對象從概念上講是不變的,但是某個(gè)成員可能有變化。當編譯器被告知一個(gè)對象是const對象時(shí),它將保護這個(gè)對象。

    這里我們要介紹在const成員函數里改變數據成員的兩種方法。

    • 第一種方法已成為過(guò)去,稱(chēng)為“強制轉換const”。它以相當奇怪的方式執行。取this(這個(gè)關(guān)鍵字產(chǎn)生當前對象的地址)并把它強制轉換成指向當前類(lèi)型對象的指針??磥?lái)this已經(jīng)是我們所需的指針,但它是一個(gè)const指針,所以,還應把它強制轉換成一個(gè)普通指針,這樣就可以在運算中去掉常量性。下面是一個(gè)例子:

      1. class Y {
      2. int i, j;
      3. public:
      4. Y() { i = j = 0; }
      5. void f() const;
      6. };
      7. void Y::f() const {
      8. //! i++; // error
      9. ((Y*)this)->j++; // ok , cast away const feature.
      10. }

      這種方法可行,在過(guò)去的程序代碼里可以看到這種用法,但這不是首選的技術(shù)。問(wèn)題是:this沒(méi)有用const修飾,這在一個(gè)對象的成員函數里被隱藏,這樣,如果用戶(hù)不能見(jiàn)到源代碼(并找到用這種方法的地方),就不知道發(fā)生了什么。

    • 第二種方法也是推薦的方法,就是在類(lèi)聲明里使用關(guān)鍵字mutable,以指定一個(gè)特定的數據成員可以在一個(gè)const對象里被改變。 
      1. class Y {
      2. int i;
      3. mutable int j;
      4. public:
      5. Y() { i = j = 0; }
      6. void f() const;
      7. };
      8. void Y::f() const {
      9. //! i++; // error
      10. ((Y*)this)->j++; // ok , mutable.
      11. }
  10. volatile關(guān)鍵字 
    volatile的語(yǔ)法與const是一樣的,但是volatile的意思是“在編譯器認識的范圍外,這個(gè)數據可以被改變”。不知何故,環(huán)境正在改變數據(可能通過(guò)多任務(wù)處理),所以,volatile告訴編譯器不要擅自做出有關(guān)數據的任何假定—在優(yōu)化期間這是特別重要的。如果編譯器說(shuō):“我已經(jīng)把數據讀進(jìn)寄存器,而且再沒(méi)有與寄存器接觸”。一般情況下,它不需要再讀這個(gè)數據。但是,如果數據是volatile修飾的,編譯器不能作出這樣的假定,因為可能被其他進(jìn)程改變了, 它必須重讀這個(gè)數據而不是優(yōu)化這個(gè)代碼。

    注意:

    • 就像建立const對象一樣,程序員也可以建立volatile對象,甚至還可以建立const volatile對 象,這個(gè)對象不能被程序員改變,但可通過(guò)外面的工具改變。
    • 就像const一樣,我們可以對數據成員、成員函數和對象本身使用volatile,可以并且也只能為volatile對象調用volatile成員函數。
    • volatile的語(yǔ)法與const是一樣的,所以經(jīng)常把它們倆放在一起討論。為表示可以選擇兩個(gè)中的任何一個(gè),它們倆通稱(chēng)為c-v限定詞。

  11. 宏的好處與壞處

    • 宏的好處:#與##的使用

      三個(gè)有用的特征:字符串定義、字符串串聯(lián)和標志粘貼。

      字符串定義的完成是用#指示,它容許設一個(gè)標識符并把它轉化為字符串,然而字符串串聯(lián)發(fā)生在當兩個(gè)相鄰的字符串沒(méi)有分隔符時(shí),在這種情況下字符串組合在一起。在寫(xiě)調試代碼時(shí),這兩個(gè)特征是非常有效的。

      1. #define DEBUG(X) cout<<#X " = " << X << endl

      上面的這個(gè)定義可以打印任何變量的值。 
      我們也可以得到一個(gè)跟蹤信息,在此信息里打印出它們執行的語(yǔ)句。

      1. #define TRACE(S) cout << #S << endl; S

      #S定義了要輸出的語(yǔ)句。第2個(gè)S重申了語(yǔ)句,所以這個(gè)語(yǔ)句被執行。當然,這可能會(huì )產(chǎn)生問(wèn)題,尤其是在一行for循環(huán)中。

      1. for (int i = 0 ; i < 100 ; i++ )
      2. TRACE(f(i)) ;

      因為在TRACE( )宏里實(shí)際上有兩個(gè)語(yǔ)句,所以一行for循環(huán)只執行第一個(gè)。

      1. for (int i = 0 ; i < 100 ; i++ )
      2. cout << "f(i)" << endl;
      3. f(i); // 第二條語(yǔ)句脫離了for循環(huán),因此執行不到

      解決方法是在宏中用逗號代替分號。

      標志粘貼在寫(xiě)代碼時(shí)是非常有用的,用##表示。它讓我們設兩個(gè)標識符并把它們粘貼在一起自動(dòng)產(chǎn)生一個(gè)新的標識符。例如:

      1. #define FIELD(A) char *A##_string;int A##_size

      此時(shí)下面的代碼:

      1. class record{
      2. FIELD(one);
      3. FIELD(two);
      4. FIELD(three);
      5. //...
      6. };

      就相當于下面的代碼:

      1. class record{
      2. char *one_string,int one_size;
      3. char *two_string,int two_size;
      4. char *three_string,int three_size;
      5. //...
      6. };
    • 宏的不好:容易出錯 
      下面舉個(gè)例子即可說(shuō)明:

      1. #define band(x) (((x)>5 && (x)<10) ? (x) :0)
      2. int main() {
      3. for(int i = 4; i < 11; i++) {
      4. int a = i;
      5. cout << "a = " << a << "\t";
      6. cout << "band(++a)" << band(++a) << "\t";
      7. cout << "a = " << a << endl;
      8. }
      9. return 0;
      10. }

      輸出:

      a = 4 band(++a)0 a = 5 
      a = 5 band(++a)8 a = 8 
      a = 6 band(++a)9 a = 9 
      a = 7 band(++a)10 a = 10 
      a = 8 band(++a)0 a = 10 
      a = 9 band(++a)0 a = 11 
      a = 10 band(++a)0 a = 12

  12. 存儲類(lèi)型指定符 
    常用的有staticextern。 
    不常用的有兩個(gè):一是auto,人們幾乎不用它,因為它告訴編譯器這是一個(gè)局部變量,實(shí)際上編譯器總是可以從 變量定義時(shí)的上下文中判斷出這是一個(gè)局部變量。所以auto是多余的。還有一個(gè)是register,它也是局部變量,但它告訴編譯器這個(gè)特殊的變量要經(jīng)常用到,所以編譯器應該盡可能地讓它保存在寄存器中。它用于優(yōu)化代碼。各種編譯器對這種類(lèi)型的變量處理方式也不盡相同,它們有時(shí)會(huì )忽略這種存儲類(lèi)型的指定。一般,如果要用到這個(gè)變量的地址, register指定符通常都會(huì )被忽略。應該避免用register類(lèi)型,因為編譯器在優(yōu)化代碼方面通常比我們做得更好。

  13. 位拷貝(bitcopy)與值拷貝的區別(很重要) 
    由1個(gè)例子來(lái)說(shuō)明:一個(gè)類(lèi)在任何時(shí)候知道它存在多少個(gè)對象,可以通過(guò)包含一個(gè)static成員來(lái)做到,如下代碼所示:

    1. #include <iostream>
    2. using namespace std;
    3. class test {
    4. static int object_count;
    5. public:
    6. test() {
    7. object_count++;
    8. print("test()");
    9. }
    10. static void print(const char *msg = 0) {
    11. if(msg) cout << msg << ": ";
    12. cout << "object_count = " << object_count << endl;
    13. }
    14. ~test() {
    15. object_count--;
    16. print("~test()");
    17. }
    18. };
    19. int test::object_count = 0;
    20. // pass and return by value.
    21. test f(test x) {
    22. x.print("x argument inside f()");
    23. return x;
    24. }
    25. int main() {
    26. test h;
    27. test::print("after construction of h");
    28. test h2 = f(h);
    29. test::print("after call to f()");
    30. return 0;
    31. }

    然而輸出并不是我們期望的那樣:

    test(): object_count = 1 
    after construction of h: object_count = 1 
    x argument inside f(): object_count = 1 
    ~test(): object_count = 0 
    after call to f(): object_count = 0 
    ~test(): object_count = -1 
    ~test(): object_count = -2

    在h生成以后,對象數是1,這是對的。我們希望在f()調用后對象數是2,因為h2也在范圍內。然而,對象數是0,這意味著(zhù)發(fā)生了嚴重的錯誤。這從結尾兩個(gè)析構函數執行后使得對象數變?yōu)樨摂档氖聦?shí)得到確認,有些事根本就不應該發(fā)生。

    讓我們來(lái)看一下函數f()通過(guò)傳值方式傳入參數那一處。原來(lái)的對象h存在于函數框架之外,同時(shí)在函數體內又增加了一個(gè)對象,這個(gè)對象是傳值方式傳入的對象的拷貝,這屬于位拷貝,調用的是默認拷貝構造函數,而不是調用構造函數。然而,參數的傳遞是使用C的原始的位拷貝的概念,但test類(lèi)需要真正的初始化來(lái)維護它的完整性。所以,缺省的位拷貝不能達到預期的效果。

    當局部對象出了調用的函數f()范圍時(shí),析構函數就被調用,析構函數使object_count減小。 所以,在函數外面, object_count等于0。h2對象的創(chuàng )建也是用位拷貝產(chǎn)生的(也是調用默認拷貝構造函數),所以,構造函數在這里也沒(méi)有調用。當對象h和h2出了它們的作用范圍時(shí),它們的析構函數又使object_count值變?yōu)樨撝怠?/p>

    總結: 

    • 位拷貝拷貝的是地址(也叫淺拷貝),而值拷貝則拷貝的是內容(深拷貝)。
    • 深拷貝和淺拷貝可以簡(jiǎn)單理解為:如果一個(gè)類(lèi)擁有資源,當這個(gè)類(lèi)的對象發(fā)生復制過(guò)程的時(shí)候,資源重新分配,這個(gè)過(guò)程就是深拷貝,反之,沒(méi)有重新分配資源,就是淺拷貝。
    • 默認的拷貝構造函數”和“缺省的賦值函數”均采用“位拷貝”而非“值拷貝”的方式來(lái)實(shí)現,倘若類(lèi)中含有指針變量,這兩個(gè)函數注定將出錯。

    關(guān)于位拷貝和值拷貝的深入理解可以參考這篇文章:C++中的位拷貝與值拷貝淺談

  14. 為了達到我們期望的效果,我們必須自己定義拷貝構造函數:
    1. test(const test& t) {
    2. object_count++;
    3. print("test(const test&)");
    4. }

    這樣輸出才正確:

    test(): object_count = 1 
    after construction of h: object_count = 1 
    test(const test&): object_count = 2 
    x argument inside f(): object_count = 2 
    test(const test&): object_count = 3 
    ~test(): object_count = 2 
    after call to f(): object_count = 2 
    ~test(): object_count = 1 
    ~test(): object_count = 0


    引申

    • 如果在main中加一句“f(h);”,即忽略返回值,那么返回的時(shí)候還會(huì )調用拷貝構造函數嗎? 
      答案是:會(huì )調用。這時(shí)候會(huì )產(chǎn)生一個(gè)臨時(shí)對象,由于該臨時(shí)對象沒(méi)有用處,因此會(huì )馬上調用析構函數銷(xiāo)毀掉。這時(shí)候輸出就會(huì )像下面這樣:

      test(): object_count = 1 
      after construction of h: object_count = 1 
      test(const test&): object_count = 2 
      x argument inside f(): object_count = 2 
      test(const test&): object_count = 3 
      ~test(): object_count = 2 
      after call to f(): object_count = 2 
      test(const test&): object_count = 3 
      x argument inside f(): object_count = 3 
      test(const test&): object_count = 4 
      ~test(): object_count = 3 
      ~test(): object_count = 2 
      ~test(): object_count = 1 
      ~test(): object_count = 0

    • 如果一個(gè)類(lèi)由其它幾個(gè)類(lèi)的對象組合而成,如果此時(shí)該類(lèi)沒(méi)有自定義拷貝構造函數,那么編譯器遞歸地為所有的成員對象和基本類(lèi)調用拷貝構造函數。如果成員對象也含有別的對象,那么后者的拷貝構造函數也將被調用。

    • 怎樣避免調用拷貝構造函數??jì)H當準備用傳值的方式傳遞類(lèi)對象時(shí),才需要拷貝構造函數。有兩種解決方法:

      • 防止傳值方法傳遞 
        有一個(gè)簡(jiǎn)單的技術(shù)防止通過(guò)傳值方式傳遞:聲明一個(gè)私有private拷貝構造函數。我們甚至不必去定義它,除非我們的成員函數或友元函數需要執行傳值方式的傳遞。如果用戶(hù)試圖用傳值方式傳遞或返回對象,編譯器將會(huì )發(fā)出一個(gè)出錯信息。這是因為拷貝構造函數是私有的。因為我們已顯式地聲明我們接管了這項工作,所以編譯器不再創(chuàng )建缺省的拷貝構造函數。

        1. class noCC {
        2. int i;
        3. noCC(const noCC&); // private and no definition
        4. public:
        5. noCC(int I = 0) : i(I) {}
        6. };
        7. void f(noCC);
        8. main() {
        9. noCC n;
        10. //! f(n); // error: copy-constructor called
        11. //! noCC n2 = n; // error: c-c called
        12. //! noCC n3(n); // error: c-c called
        13. }

        注意這里n2 = n也調用拷貝構造函數,注意這里要和賦值函數區分。

      • 改變外部對象的函數 
        使用引用傳遞:比如void get(const Slice&);
  15. 非自動(dòng)繼承的函數 
    構造函數、析構函數和賦值函數(operator=)不能被繼承。

  16. 私有繼承的目的 
    private繼承的目的是什么,因為在類(lèi)中選擇創(chuàng )建一個(gè)private對象似乎更合適。將private繼承包含在該語(yǔ)言中只是為了語(yǔ)言的完整性。但是,如果沒(méi)有其他理由,則應當減少混淆,所以通常建議用private成員而不是private繼承。 
    然而,private繼承也不是一無(wú)用處。 
    這里可能偶然有這種情況,即可能想產(chǎn)生像基類(lèi)接口一樣的接口,而不允許處理該對象像處理基類(lèi)對象一樣。private繼承提供了這個(gè)功能。


    引申

    能對私有繼承成員公有化嗎? 
    當私有繼承時(shí),基類(lèi)的所有public成員都變成了private。如果希望它們中的任何一個(gè)是可視的,可以辦到嗎?答案是可以的,只要用派生類(lèi)的public選項聲明它們的名字即可(新的標準中使用using關(guān)鍵字)。

    1. #include <iostream>
    2. class base {
    3. public:
    4. char f() const { return 'a'; }
    5. int g() const { return 2; }
    6. float h() const { return 3.0; }
    7. };
    8. class derived : base {
    9. public:
    10. using base::f; // Name publicizes member
    11. using base::h;
    12. };
    13. int main() {
    14. derived d;
    15. d.f();
    16. d.h();
    17. // d.g(); // error -- private function
    18. return 0;
    19. }

    這樣,如果想要隱藏這個(gè)類(lèi)的基類(lèi)部分的功能,則private繼承是有用的。

  17. 多重繼承注意向上映射的二義性。比如base(有個(gè)f()方法)有兩個(gè)子對象d1和d2,且都重寫(xiě)了base的f()方法,此時(shí)子類(lèi)dd如果也有f()方法則不能同時(shí)繼承自d1和d2,因為f()方法存在二義性,不知道該繼承哪個(gè)f()方法。 
    解決方法是對dd類(lèi)中的f()方法重新定義以消除二義性,比如明確指定使用d1的f()方法。 
    當然也不能將dd類(lèi)向上映射為base類(lèi),這可以通過(guò)使用虛繼承解決,關(guān)鍵字virtual,base中的f()方法改成虛函數且d1和d2的繼承都改為虛繼承,當然dd繼承d1和d2用public繼承即可。

  18. C語(yǔ)言中如何關(guān)閉assert斷言功能? 
    頭文件:<assert.h>或<cassert> 
    在開(kāi)發(fā)過(guò)程中,使用它們,完成后用#define NDEBUG使之失效,以便推出產(chǎn)品,注意必須在頭文件之前關(guān)閉才有效。

    1. #define NDEBUG
    2. #include <cassert>
  19. C++如何實(shí)現動(dòng)態(tài)捆綁?—即多態(tài)的實(shí)現(很重要) 
    C++中為了實(shí)現多態(tài),編譯器對每個(gè)包含虛函數的類(lèi)創(chuàng )建一個(gè)表(稱(chēng)為VTABLE,虛表)。在 VTABLE中,編譯器放置特定類(lèi)的虛函數地址。在每個(gè)帶有虛函數的類(lèi)中,編譯器秘密地置一指針,稱(chēng)為vpointer(縮寫(xiě)為VPTR),指向這個(gè)對象的VTABLE。通過(guò)基類(lèi)指針做虛函數調用時(shí)(也就是做多態(tài)調用時(shí)),編譯器靜態(tài)地插入取得這個(gè)VPTR,并在VTABLE表中查找函數地址的代碼,這樣就能調用正確的函數使晚捆綁發(fā)生。 
    為每個(gè)類(lèi)設置VTABLE、初始化VPTR、為虛函數調用插入代碼,所有這些都是自動(dòng)發(fā)生的,所以我們不必擔心這些。利用虛函數,這個(gè)對象的合適的函數就能被調用,哪怕在編譯器還不知道這個(gè)對象的特定類(lèi)型的情況下。

    在vtable表中,編譯器放置了在這個(gè)類(lèi)中或在它的基類(lèi)中所有已聲明為virtual的函數的地址。如果在這個(gè)派生類(lèi)中沒(méi)有對在基類(lèi)中聲明為virtual的函數進(jìn)行重新定義,編譯器就使用基類(lèi)的這個(gè)虛函數地址。 
    下面舉個(gè)例子說(shuō)明:

    1. #include <iostream>
    2. enum note { middleC, Csharp, Cflat };
    3. class instrument {
    4. public:
    5. virtual void play(note) const {
    6. cout << "instrument::play" << endl;
    7. }
    8. virtual char* what() const {
    9. return "instrument";
    10. }
    11. // assume this will modify the object:
    12. virtual void adjust(int) {}
    13. };
    14. class wind : public instrument {
    15. public:
    16. void play(note) const {
    17. cout << "wind::play" << endl;
    18. }
    19. char* what() const {
    20. return "wind";
    21. }
    22. void adjust(int) {}
    23. };
    24. class percussion : public instrument {
    25. public:
    26. void play(note) const {
    27. cout << "percussion::play" << endl;
    28. }
    29. char* what() const {
    30. return "percussion";
    31. }
    32. void adjust(int) {}
    33. };
    34. class string : public instrument {
    35. public:
    36. void play(note) const {
    37. cout << "string::play" << endl;
    38. }
    39. char* what() const {
    40. return "string";
    41. }
    42. void adjust(int) {}
    43. };
    44. class brass : public wind {
    45. public:
    46. void play(note) const {
    47. cout << "brass::play" << endl;
    48. }
    49. char* what() const {
    50. return "brass";
    51. }
    52. };
    53. class woodwind : public wind {
    54. public:
    55. void play(note) const {
    56. cout << "woodwind::play" << endl;
    57. }
    58. char* what() const {
    59. return "woodwind";
    60. }
    61. };
    62. instrument *A[] = {
    63. new wind,
    64. new percussion,
    65. new string,
    66. new brass
    67. };

    下圖畫(huà)的是指針數組A[]。


    下面看到的是通過(guò)instrument指針對于brass調用adjust()。instrument引用產(chǎn)生如下結果:


    編譯器從這個(gè)instrument指針開(kāi)始,這個(gè)指針指向這個(gè)對象的起始地址。所有的instrument對象或由instrument派生的對象都有它們的VPTR,它在對象的相同的位置(常常在對象的開(kāi)頭),所以編譯器能夠取出這個(gè)對象的VPTR。VPTR指向VTABLE的開(kāi)始地址。所有的VTABLE有相同的順序,不管何種類(lèi)型的對象。 play()是第一個(gè),what()是第二個(gè),adjust()是第三個(gè)。所以編譯器知道adjust()函數必在VPTR + 2處。這樣,不是“以instrument :: adjust地址調用這個(gè)函數”(這是早捆綁,是錯誤活動(dòng)),而是產(chǎn)生代碼,“在VPTR + 2處調用這個(gè)函數”。因為VPTR的效果和實(shí)際函數地址的確定發(fā)生在運行時(shí),所以這樣就得到了所希望的晚捆綁。向這個(gè)對象發(fā)送消息,這個(gè)對象能斷定它應當做什么。


    引申 — 對象切片

    當多態(tài)地處理對象時(shí),傳地址與傳值有明顯的不同。所有在這里已經(jīng)看到的例子和將會(huì )看到的例子都是傳地址的,而不是傳值的。這是因為地址都有相同的長(cháng)度,傳派生類(lèi)型(它通常稍大一些)對象的地址和傳基類(lèi)(它通常小一點(diǎn))對象的地址是相同的。如前面解釋的,使用多態(tài)的目的是讓對基類(lèi)對象操作的代碼也能操作派生類(lèi)對象。 
    如果使用對象而不是使用地址或引用進(jìn)行向上映射,發(fā)生的事情會(huì )使我們吃驚:這個(gè)對象 被“切片”,直到所剩下來(lái)的是適合于目的的子對象。在下面例子中可以看到通過(guò)檢查這個(gè)對象的長(cháng)度切片剩下來(lái)的部分。

    1. #include <iostream>
    2. using namespace std;
    3. class base {
    4. int i;
    5. public:
    6. base(int I = 0) : i(I) {}
    7. virtual int sum() const { return i; }
    8. };
    9. class derived : public base {
    10. int j;
    11. public:
    12. derived(int I = 0, int J = 0) : base(I), j(J) {}
    13. virtual int sum() const { return base::sum() + j; }
    14. };
    15. void call(base b) {
    16. cout << "sum = " << b.sum() << endl;
    17. }
    18. main() {
    19. base b(10);
    20. derived d(10, 47);
    21. call(b);
    22. call(d);
    23. }

    函數call( )通過(guò)傳值傳遞一個(gè)類(lèi)型為base的對象。然后對于這base對象調用虛函數sum( )。 我們可能希望第一次調用產(chǎn)生10,第二次調用產(chǎn)生57。實(shí)際上,兩次都產(chǎn)生10。 在這個(gè)程序中,有兩件事情發(fā)生了。

    • 第一,call( )接受的只是一個(gè)base對象,所以所有在這個(gè)函數體內的代碼都將只操作與base相關(guān)的數。 對call( )的任何調用都將引起一個(gè)與base大小相同的對象壓棧并在調用后清除。這意味著(zhù),如果一個(gè)由base派生來(lái)類(lèi)對象被傳給call,編譯器接受它,但只拷貝這個(gè)對象對應于base的部分,切除這個(gè)對象的派生部分,如圖: 

      現在,我們可能對這個(gè)虛函數調用感到奇怪:這里,這個(gè)虛函數既使用了base(它仍存在), 又使用了derived的部分(derived不再存在了,因為它被切片)。 其實(shí)我們已經(jīng)從災難中被解救出來(lái),這個(gè)對象正安全地以值傳遞。因為這時(shí)編譯器認為它知道這個(gè)對象的確切的類(lèi)型(這個(gè)對象的額外特征有用的任何信息都已經(jīng)失去)。
    • 另外,用值傳遞時(shí),它對base對象使用拷貝構造函數,該構造函數初始化vptr指向base vtable,并且只拷貝這個(gè)對象的base部分。這里沒(méi)有顯式的拷貝構造函數,所以編譯器自動(dòng)地為我們合成一個(gè)。由于上述諸原因,這個(gè)對象在切片期間變成了一個(gè)base對象。

    對象切片實(shí)際上是去掉了對象的一部分,而不是象使用指針或引用那樣簡(jiǎn)單地改變地址的內容。因此,對象向上映射不常做,事實(shí)上,通常要提防或防止這種操作。我們可以通過(guò)在基 類(lèi)中放置純虛函數來(lái)防止對象切片。這時(shí)如果進(jìn)行對象切片就將引起編譯時(shí)的出錯信息。

    最后注意:虛機制在構造函數中不工作。即在構造函數中調用虛函數沒(méi)有結果。

  20. RTTI—運行時(shí)類(lèi)型識別(很重要)

    • 概念

      運行時(shí)類(lèi)型識別(Run-time type identification, RTTI)是在我們只有一個(gè)指向基類(lèi)的指針或引用時(shí)確定一個(gè)對象的準確類(lèi)型。

    • 使用方法 
      一般情況下,我們并不需要知道一個(gè)類(lèi)的確切類(lèi)型,虛函數機制可以實(shí)現那種類(lèi)型的正確行為。但是有些時(shí)候,我們有指向某個(gè)對象的基類(lèi)指針,確定該對象的準確類(lèi)型是很有用的。 
      RTTI與異常一樣,依賴(lài)駐留在虛函數表中的類(lèi)型信息。如果試圖在一個(gè)沒(méi)有虛函數的類(lèi)上用RTTI,就得不到預期的結果。

      RTTI的兩種使用方法

      • 第一種使用typeid(),就像sizeof()一樣,看上都像一個(gè)函數。但實(shí)際上它是由編譯器實(shí)現的。typeid()帶有一個(gè)參數,它可以是一個(gè)對象引用或指針,返回全局typeinfo類(lèi)的常量對象的一個(gè)引用??梢杂眠\算符“==”和“!=”來(lái)互相比較這些對象。也可以用name()來(lái)獲得類(lèi)型的名稱(chēng)。注意,如果給typeid( )傳遞一個(gè)shape*型參數,它會(huì )認為類(lèi)型為shape*,所以如果想知道一個(gè)指針所指對象的精確類(lèi)型,我們必須逆向引用這個(gè)指針。比如,s是個(gè)shape*, 那么:

        1. cout << typeid(*s).name()<<endl;

        將顯示出s所指向的對象類(lèi)型。 
        也可以用before(typeinfo&)查詢(xún)一個(gè)typeinfo對象是否在另一個(gè)typeinfo對象的前面(以定義實(shí)現的排列順序),它將返回true或false。如果寫(xiě):

        1. if(typeid(me).before(typeid(you))) //...

        那么表示我們正在查詢(xún)me在排列順序中是否在you之前。

      • RTTI的第二個(gè)用法叫“安全類(lèi)型向下映射”。使用dynamic_cast<>模板。

      兩種方法的使用舉例如下:

      1. #include <iostream>
      2. #include <typeinfo>
      3. using namespace std;
      4. class base {
      5. int i;
      6. public:
      7. base(int I = 0) : i(I) {}
      8. virtual int sum() const { return i; }
      9. };
      10. class derived : public base {
      11. int j;
      12. public:
      13. derived(int I = 0, int J = 0) : base(I), j(J) {}
      14. virtual int sum() const { return base::sum() + j; }
      15. };
      16. main() {
      17. base *b = new derived(10, 47);
      18. // rtti method1
      19. cout << typeid(b).name() << endl; // P4base
      20. cout << typeid(*b).name() << endl; // 7derived
      21. if(typeid(b).before(typeid(*b)))
      22. cout << "b is before *b" << endl;
      23. else
      24. cout << "*b is before b" << endl;
      25. // rtti method2
      26. derived *d = dynamic_cast<derived*>(d);
      27. if(d) cout << "cast successful" << endl;
      28. }

      注意1:這里如果沒(méi)有多態(tài)機制,則RTTI可能運行的結果不是我們想要的,比如如果沒(méi)有虛函數,則這里兩個(gè)都顯示base,一般希望RTTI用于多態(tài)類(lèi)。 
      注意2:運行時(shí)類(lèi)型的識別對一個(gè)void型指針不起作用。void *確實(shí)意味著(zhù)“根本沒(méi)有類(lèi)型信息”。

      1. void *v = new stimpy;
      2. stimpy* s = dynamic_cast<stimpy*>(v); // error
      3. cout << typeid(*v).name() << endl; // error
    • RTTI的實(shí)現 
      **典型的RTTI是通過(guò)在VTABLE中放一個(gè)額外的指針來(lái)實(shí)現的。這個(gè)指針指向一個(gè)描述該特定類(lèi)型的typeinfo結構(每個(gè)新類(lèi)只產(chǎn)生一個(gè)typeinfo的實(shí)例),所以typeid( )表達式的作用實(shí)際上很簡(jiǎn)單。**VPTR用來(lái)取typeinfo的指針,然后產(chǎn)生一個(gè)結果typeinfo結構的一個(gè)引用—這是一個(gè)決定性的步驟—我們已經(jīng)知道它要花多少時(shí)間。

      對于dynamic_cast<目標* > <源指針>,多數情況下是很容易的,先恢復源指針的RTTI信息再取出目標*的類(lèi)型RTTI信息,然后調用庫中的一個(gè)例程判斷源指針是否與目標*相同或者是目標*類(lèi)型的基類(lèi)。它可能對返回的指針做了一點(diǎn)小的改動(dòng),因為目的指針類(lèi)可能存在多重繼承的情況,而源指針類(lèi)型并不是派生類(lèi)的第一個(gè)基類(lèi)。在多重繼承時(shí)情況會(huì )變得復雜些,因為一個(gè)基類(lèi)在繼承層次中可能出現一次以上,并且可能有虛基類(lèi)。 
      用于動(dòng)態(tài)映射的庫例程必須檢查一串長(cháng)長(cháng)的基類(lèi)列表,所以動(dòng)態(tài)映射的開(kāi)銷(xiāo)比typeid()要大(當然我們得到的信息也不同,這對于我們的問(wèn)題來(lái)說(shuō)可能很關(guān)鍵),并且這是非確定性的,因為查找一個(gè)基類(lèi)要比查找一個(gè)派生類(lèi)花更多的時(shí)間。另外動(dòng)態(tài)映射允許我們比較任何類(lèi)型,不限于在同一個(gè)繼承層次中比較兩個(gè)類(lèi)。這使得動(dòng)態(tài)映射調用的庫例程開(kāi)銷(xiāo)更高了。

      映射類(lèi)型含義
      static_cast為了“行為良好”和“行為較好”而使用的映射,包括一些我們可能現在不用的映射(如向上映射和自動(dòng)類(lèi)型轉換)
      const_cast用于映射常量和變量(const和volatile)
      const_cast為了安全類(lèi)型的向下映射(本章前面已經(jīng)介紹)
      reinterpret_cast為了映射到一個(gè)完全不同的意思。這個(gè)關(guān)鍵詞在我們需要把類(lèi)型映射回原有類(lèi)型時(shí)要用到它。我們映射到的類(lèi)型僅僅是為了故弄玄虛和其他目的。這是所有映射中最危險的

      注意:如果想把一個(gè)const 轉換為非const,或把一個(gè)volatile轉換成一個(gè)非volatile(勿遺忘這種情況),就要用到const_cast。這是可以用const_cast的唯一轉換。如果還有其他的轉換牽涉進(jìn)來(lái),它必須分開(kāi)來(lái)指定,否則會(huì )有一個(gè)編譯錯誤。

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
C++拷貝構造函數詳解(轉)
C 學(xué)習筆記——繼承和組合
C++核心編程
c++的拷貝構造函數中直接使用私有變量
第十章 C++11新特性
C++語(yǔ)言特性的性能分析
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久