原文:https://www.cnblogs.com/tp-16b/p/8619813.html
引用與指針
C++進行傳值更傾向於使用引用。引用實質就是給已經定義的變量起一個別名,函數經過這個別名來完成對應的功能。html
【引用特色】安全
①一變量可取多個別名;函數
②引用必須初始化,同時只能在初始化時被引用,且只能被引用一次;優化
【使用引用注意幾種狀況】url
(1)什麼時候添加const進行修飾spa
①防止引用變量被修改操作系統
咱們知道在變量前加const 表示這是個常變量,不能被修改。那麼在引用前加上const是同樣的道理,例如: int a = 2; const int& d = a; 這樣的形式防止變量a的別名d 對值‘2’進行修改。.net
②引用的爲常量 如:指針
常量是具備常性的,因此必須在前面加上const使其保持常性。code
③引用參數存在隱式類型轉換
(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