原文:https://www.cnblogs.com/tp-16b/p/8619813.html
引用與指針
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. 無參的構造函數和全缺省值的構造函數都認為是缺省構造函數,並且缺省的構造函數只能有一個。
【構造函數初始化列表】
我們一般都是在構造函數體內來初始化數據成員,但這並不是真正意義上的初始化,而是賦值。但因為構造函數是創建一個對象時自動調用的第一個成員函數,所以我們就把它體內的賦值語句當成初始化來看待。
真正的初始化是使用“初始化列表”來進行的。初始化列表以一個冒號開始,接着一個逗號分隔數據列表,每個數據成員都放在一個括號中進行初始化。如下:
class B(int b1, int b2) : _b1(b1) , _b2(b2) {//可以保留原構造賦值部分} private: int _b1; int _b2; };
它位於構造函數參數列表后,在函數體{}
之前,這說明該列表里的初始化工作發生在函數體內的任何代碼被執行之前。
盡量使用初始化列表進行初始化,因為它更高效。
大部分時候構造函數既可以使用初始化列表又能在函數體內賦值初始化,但有幾種情況必須使用初始化列表。
- 類的非靜態 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