近來回轉 C++的學習,腦子又被搞得了一團迷(簡直不要忘得太快..... )
過后靜下來想一想,還是因為有些東西沒搞清楚導致,所以理了理兩個容易搞迷糊的地方。
引用與指針
C++進行傳值更傾向於使用引用。引用實質就是給已經定義的變量起一個別名,函數通過這個別名來完成對應的功能。
【引用特點】
①一變量可取多個別名
②引用必須初始化,同時只能在初始化時被引用,
③只能被引用同一變量,從一而終
【使用引用注意幾種情況】
(1)何時添加const進行修飾
①防止引用變量被修改
我們知道在變量前加const 表示這是個常變量,不能被修改。那么在引用前加上const是一樣的道理,例如: int a = 2; const int& d = a; 這樣的形式防止變量a的別名d 對值‘2’進行修改。
②引用的為常量 如:
常量是具有常性的,所以必須在前面加上const使其保持常性。
③引用參數存在隱式類型轉換
(2)函數傳引用作返回值
①不要返回臨時變量的引用
例如
int& Add(int d1, int d2) //臨時變量的引用作返回值 { int ret = d1 + d2; return ret; } void Test() { int& sum = Add(1, 2); //獲取返回值ret的別名 cout<<"占用位置"<<endl; cout<<sum<<endl; }
結果:
分析:ret是隸屬於Add函數棧幀,ret的引用作返回值,返回的其實是ret變量的地址;而當Add函數調用完畢后,該處被操作系統收回權限,若再通過返回的地址訪問該處就是非法的(結果便成了上圖隨機值)。這與傳值返回有着很大的差別,
(傳值返回&引用返回 匯編代碼)
②當返回的對象出了函數作用域依舊存在,最好使用引用作返回,因為它更高效。
因為引用返回僅僅是一個別名(其實是保存在寄存器eax中的地址),而若是傳值返回,且返回的ret是一個對象,便會產生臨時對象,這個臨時對象用ret拷貝構造初始化(拷貝構造請往下看),而這個臨時對象底層是在返回值接收方的函數中提前開辟好的,在函數接受方接收完成后,還需調用析構函數來清理該臨時對象,進一步增大了開銷;所以用引用返回會更高效。
注:返回值優化參考http://www.cnblogs.com/hazir/archive/2012/04/19/2456840.html
然后就是注意它和指針的區別(比較重要)。
【引用和指針區別】
* 引用只能在定義時初始化一次,之后不能改變去指向其它變量(從一而終);指針變量的值可變
* 引用必須指向有效的變量,指針可以為空;用指針傳參,函數棧額外開空間來拷貝一份參數地址,引用傳參則不會。
* sizeof指針對象和引用對象的意義不一樣。sizeof引用得到的是所指向的變量的大小,而sizeof指針是對象地址的大小。
* 指針和引用自增(++)自減(--)意義不一樣。
總之, 相對而言,引用比指針更安全,指針更靈活。
構造函數
首先,它是用來初始化類里面的成員變量的公有成員函數,有且僅在定義對象時自動執行一次。
它有如下基本特征:
1. 函數名與類名相同。
2. 無返回值。
3. 對象實例化時系統自動調用對應的構造函數。
4. 構造函數可以重載。
5. 構造函數可以在類中實現,也可以在類外實現(類外加上類限定符)
6. 如果類定義中沒有給出構造函數,則C++編譯器自動產生一個缺省的構造函數,但只要我們定義了一個構造函數,系統就不會自動生成缺省的構造函數。(半缺省時,只能從最右邊往左連續着缺省,如:Date(int year, int month =1, int year = 1 ),這和參數入棧規則有關)
7. 無參的構造函數和全缺省值的構造函數都認為是缺省構造函數,並且缺省的構造函數只能有一個。
【構造函數初始化列表】
我們一般都是在構造函數體內來初始化數據成員,但這並不是真正意義上的初始化,而是賦值;初始化發生時間更早,是在數據成員的defalut(缺省)構造函數被自動調用的時候,內置類型除外。defalut構造函數先為這些數據成員設初值,然后自定義實現的構造函數再對它們賦予新值。於是這樣便把default構造函數的一切作為給白白浪費了,效率有待提高。
所以一般初始化是使用“初始化列表”來進行的。初始化列表以一個冒號開始,接着一個逗號分隔數據列表,每個數據成員都放在一個括號中進行初始化。如下:

class B(int b1, int b2) : _b1(b1) , _b2(b2) {//可以保留原構造賦值部分} private: int _b1; int _b2; };
它位於構造函數參數列表后,在函數體{}
之前,這說明該列表里的初始化工作發生在函數體內的任何代碼被執行之前。該列表中針對各個成員變量而設的參數,直接當實參被拿去作初值(或傳進另一拷貝構造函數中來初始化該成員變量),這樣也就避免前面提到的“白白浪費”了,這也正是 effectiveC++所提到的觀點。
所以盡量使用初始化列表進行初始化,因為它是更高效的。
大部分時候構造函數既可以使用初始化列表又能在函數體內初始化,但有幾種情況必須使用初始化列表。
- 類的非靜態的const 數據成員 (成員被const修飾,於是被定義出來之后,構造函數來初始化,此時已不能被修改)
- 為引用的數據成員 (引用從一而終)
- 沒有缺省構造函數的類的成員變量 ()
注意!!
成員變量按聲明順序依次初始化,而非初始化列表出現的順序 (看如下例子)
class A { public: A(int n) : _a2(n) , _a1(_a2) {} void Show() { cout<<"a1 "<<_a1<<endl<<"a2 "<<_a2<<endl; } private: int _a1; int _a2; }; int main() { A a(1); a.Show(); return 0; }
不難看出在構造函數中是先跟變量_a2賦值,然后再用_a2來初始化_a1,結果就應該是 1 和 1;但是結果卻是
因為成員變量按聲明順序依次初始化,而非初始化列表出現的順序。這里按變量定義順序,應該先初始化 _a1 ,再初始化 _a2 ,但初始化列表里, _a1 是使用 _a2 的值來初始化的,但此時 _a2還沒有被初始化,於是_a1就不會被初始化了。
接下來說說一個特殊的構造函數
【拷貝構造函數】
創建對象時使用同類對象來進行初始化(相當於復制對象),這時所用的構造函數稱為拷貝構造函數(Copy Constructor),拷貝構造函數是特殊的構造函數。

class Date { public : Date() {} // 拷貝構造函數
Date (const Date& d) { _year = d ._year; _month = d ._month; _day = d ._day; } private : int _year ; int _month ; int _day ; };
特征:
1. 拷貝構造函數其實是一個構造函數的重載。
2. 拷貝構造函數的參數必須使用引用傳參,使用傳值方式會引發無窮遞歸調用(調拷貝構造函數會傳參,傳參過程又會調用拷貝構造,以此往復...無窮遞歸)
3. 若未顯示定義,系統會生成默認缺省的拷貝構造函數。缺省的拷貝構造函數會依次拷貝類成員進行初始化
何時調用拷貝函數:
- 一個對象以值傳遞的方式傳入函數體
- 一個對象以值傳遞的方式從函數返回(與返回值優化密切相關)
- 一個對象需要通過另一個對象進行初始化
關於拷貝構造函數還有一類很熱的問題,構造函數拷貝賦值函數的N種調用情況
即判斷下面每種情況都調用了多少次構造、拷貝構造...
//1.Date 對象做參數傳值 void fun1 (Date d) //void fun1(Date& d) {} // 2.Date 對象做返回值傳值 Date fun2 () // Date& fun2() { Date d ; return d ; } // 3.Date 對象做臨時返回值傳值 (編譯器優化問題) Date fun3 () // Date& fun3() { return Date (); } int main () { // 場景 Date d1; fun1(d1); // 場景 Date d2 = fun2(); // 場景 Date d3 ; d3 = fun3 (); return 0; }
這其實和前面所說的傳值返回也有着緊密的聯系,同時還涉及編譯器的優化,如果有興趣可以參考:
http://www.cnblogs.com/hazir/archive/2012/04/19/2456840.html