C++編程思想重點筆記(上)

  1. C和C++指針的最重要的區別在於:C++是一種類型要求更強的語言。void *而言,這一點表現得更加突出。C雖然不允許隨便地把一個類型的指針指派給另一個類型,但允許通過void *來實現。例如:

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

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

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

  3. C++訪問權限控制:public、private、protected 
    其中protected只有在繼承中才有不同含義,否則與private相同,也就是說兩者只有一點不同:繼承的結構可以訪問protected成員,但不能訪問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*)引用了一個X對象的地址,這是沒有任何問題的,但如果是void g(X memx);就不行了,編譯器會報錯。這一點很關鍵,因爲編譯器知道如何傳遞一個地址,這一地址大小是一定的,而不用管被傳遞的對象類型大小。如果試圖傳遞整個對象,編譯器就必須知道X的全部定義以確定它的大小以及如何傳遞它,這就使程序員無法聲明一個類似於Y :: g(X) 的函數。

  5. C++是純的嗎? 
    如果某個類的一個函數被聲明爲friend,就意味着它不是這個類的成員函數,但卻可以修改類的私有成員, 而且它必須被列在類的定義中,因此我們可以認爲它是一個特權函數。這種類的定義提供了有關權限的信息,我們可以知道哪些函數可以改變類的私有部分。 因此,C++不是完全的面嚮對象語言,它只是一個混合產品。friend關鍵字就是用來解決部分的突發問題。它也說明了這種語言是不純的。畢竟C + +語言的設計是爲了實用,而不是追求理想的抽象。

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

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

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

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

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

    1. ostream& endl(ostream&);

    例子:產生一個換行而不刷新這個流。人們認爲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語言中const與C++中const的區別: 
    常量引進是在早期的C++版本中,當時標準C規範正在制訂。那時,常量被看作是一個好的思想而被包含在C中。但是,C中的const意思是“一個不能被改變的普通變量”,在C中,它總是佔用存儲而且它的名字是全局符。C編譯器不能把const看成一個編譯期間的常量。在C中, 如果寫:

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

    儘管看起來好像做了一件合理的事,但這將得到一個錯誤結果。因爲bufsize佔用存儲的某個地方,所以C編譯器不知道它在編譯時的值。在C語言中可以選擇這樣書寫:

    1. const bufsize

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

    1. extern const bufsize;//declaration only

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

  8. 類裏的const和enum 
    下面的寫法有什麼問題嗎?:

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

    結果當然是編譯不通過。why?因爲const在類對象裏進行了存儲空間分配,編譯器不能知道const的內容是什麼,所以不能把它用作編譯期間的常量。這意味着對於類裏的常數表達式來說,const就像它在C中一樣沒有作用。

    在類裏的const意思是“在這個特定對象的壽命期內,而不是對於整個類來說,這個值是不變的”。那麼怎樣建立一個可以用在常數表達式裏的類常量呢? 
    一個普通的辦法是使用一個不帶實例的無標記的enum。枚舉的所有值必須在編譯時建立,它對類來說是局部的,但常數表達式能得到它的值,這樣,我們一般會看到:

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

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

  9. 類裏面的const成員函數

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

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

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

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


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

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

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

    • 第一種方法已成爲過去,稱爲“強制轉換const”。它以相當奇怪的方式執行。取this(這個關鍵字產生當前對象的地址)並把它強制轉換成指向當前類型對象的指針。看來this已經是我們所需的指針,但它是一個const指針,所以,還應把它強制轉換成一個普通指針,這樣就可以在運算中去掉常量性。下面是一個例子:

      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. }

      這種方法可行,在過去的程序代碼裏可以看到這種用法,但這不是首選的技術。問題是:this沒有用const修飾,這在一個對象的成員函數裏被隱藏,這樣,如果用戶不能見到源代碼(並找到用這種方法的地方),就不知道發生了什麼。

    • 第二種方法也是推薦的方法,就是在類聲明裏使用關鍵字mutable,以指定一個特定的數據成員可以在一個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關鍵字 
    volatile的語法與const是一樣的,但是volatile的意思是“在編譯器認識的範圍外,這個數據可以被改變”。不知何故,環境正在改變數據(可能通過多任務處理),所以,volatile告訴編譯器不要擅自做出有關數據的任何假定—在優化期間這是特別重要的。如果編譯器說:“我已經把數據讀進寄存器,而且再沒有與寄存器接觸”。一般情況下,它不需要再讀這個數據。但是,如果數據是volatile修飾的,編譯器不能作出這樣的假定,因爲可能被其他進程改變了, 它必須重讀這個數據而不是優化這個代碼。

    注意:

    • 就像建立const對象一樣,程序員也可以建立volatile對象,甚至還可以建立const volatile對 象,這個對象不能被程序員改變,但可通過外面的工具改變。
    • 就像const一樣,我們可以對數據成員、成員函數和對象本身使用volatile,可以並且也只能爲volatile對象調用volatile成員函數。
    • volatile的語法與const是一樣的,所以經常把它們倆放在一起討論。爲表示可以選擇兩個中的任何一個,它們倆通稱爲c-v限定詞
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章