C++引用

1、引用的使用

  • 引用必須在定義時候初始化,後期綁定不能更改 ,可以相當於 * const
int rats=101;
int & rodents=rats;  //等價於 int * const rodents =&rats;   
int rats2 =102;

rodents =rats2; //此時只是將 rodents的值設置爲 rats2 並非更改引用綁定。
  • 函數參數的引用 ,在聲明和原型中指明引用, 但是從調用上看不出來
void swap1(int &a, int &b);  //傳遞引用
void swap2(int a , int b );  //傳遞拷貝
void swap3(int *a, int *b);  //傳遞指針
...

int x=1,y=2;

swap1(x, y);      //不能顯示看出是引用還是拷貝型
swap2(x, y); 
swap3(&x, &y);    //明顯可見是指針調用類型  
  • 臨時匿名變量生成的情況。在實參並不是實際存在內存空間中的一個變量的時候,編譯器在編譯期間將會生成一個匿名臨時變量,並讓引用n引用指向這個變量,函數結束後便刪除臨時變量。
double caln(const double &n)
{
    return n*n;
}
double a =3.0;

double out1 =double(5.0);
double out2 =double(a+3);

若形參和實參不配,則考慮兩種情況

  1. 普通引用調用類型,顯示實參類型不匹配

double recal2( double & ra)
{
    ra = ra * 3;
    return ra; 
}

double recal3(double & ra)
{
    return  ra * 3;
}
...

int a = 9;
cout << "result :" << recal2(a) << endl;
cout << "result :" << recal3(a) << endl;

image

  1. const 類型的引用調用,由於只是傳參數調用,並不會修改原值 ,故編譯器生成一個拷貝使用
double recal(const double & ra)
{
    return ra * 2;
}
...
int a = 9;
cout << "result :" << recal(a) << endl;

image

  • 所以如果只是簡單的傳值調用,可以考慮用上const 引用以防止出現這個錯誤

  • 若不修改變量的值,應該儘可能使用const

    1. const 可以避免無意中修改數據的編程錯誤
    2. const 形參可以處理const 和非const 實參 ,否則只能接受非const 的數據
    3. 使用const 引用使函數能夠正確生成並使用臨時變量
  • 函數中返回引用,返回變量 ,接受返回變量,接受引用。以下是測試和測試結果:

    1. 在引用中修改引用值,變量改變,從out1,out3 可以看出,不用多說。
    2. 外部定義變量接受函數返回值,返回的時候臨時變量還在,外部變量af,bf,cf,df正確賦值。從地址上看,雖然1 2函數返回類型爲變量,3 4函數返回類型爲引用,但是,af -df 確實都是新的變量和原始的 a b c d 沒有關係。
    3. out1 out2 返回類型並非引用類型,但是用引用類型接收,出現如下錯誤:
      4. ![image](G:\mark\C++\1112\4.jpg).
  • 必須爲左值也就是這個要是可以被賦值的,比如 a變量,因爲左值有固定內存地址,可以制定引用的地址。一會兒可以測試一下。

  • 函數內return 爲原外部變量引用的,out3 可以明顯看出和c 是一個地址 ,正確引用。
  • 函數內 return 爲內部臨時變量的,從地址上看確實還是臨時使用變量的地址,但是由於在函數調用結束後,變量空間被釋放。期間其他程序可以使用這個空間,導致內存值變化。所以不應該用臨時變量作爲引用的返回值。

看如下的測試程序:

int out1(int & a){
    a += 10;
    return a;
}

int out2(int & b){
    int m1 = b + 20;
    return m1;
}

int & out3(int & c){
    c += 30;
    return c;
}

int &out4(int & d){
    int m2 = d+ 40;
    cout << " address m2 :" << &m2 << endl;
    return m2;
}
int main()
{
    int a = 0, b = 0, c = 0, d = 0;

    cout << " address: a:" << &a << " a :" << a <<endl;
    cout << " address: b:" << &b << " b :" << b << endl;
    cout << " address: c:" << &c << " c :" << c << endl;
    cout << " address: d:" << &d << " d :" << d << endl <<endl;

    int af = out1(a);
    int bf = out2(b);
    int cf = out3(c);
    int df = out4(d);

    //int &ay = out1(a);
    //int &by = out2(b);
    int &cy = out3(c);
    int &dy = out4(d);
    cout << " 測試函數調用對原變量的影響: " << endl;
    cout << " address: a:" << &a << " a :" << a << endl;
    cout << " address: b:" << &b << " b :" << b << endl;
    cout << " address: c:" << &c << " c :" << c << endl;
    cout << " address: d:" << &d << " d :" << d << endl<<endl;

    cout << " 測試使用普通變量接受返回值: " << endl;
    cout << " address: af:" << &af << " af :" << af << endl;
    cout << " address: bf:" << &bf << " bf :" << bf << endl;
    cout << " address: cf:" << &cf << " cf :" << cf << endl;
    cout << " address: df:" << &df << " df :" << df << endl<<endl;

    cout << " 測試使用引用接受返回值: " << endl;
    //cout << " address: ay:" << &af << " ay :" << ay << endl;
    //cout << " address: by:" << &bf << " by :" << by << endl;
    cout << " address: cy:" << &cy << " cy :" << cy<< endl;
    cout << " address: dy:" << &dy << " dy :" << dy << endl;

    cin.get();
    return 0;
}

image

  • 以下是對函數返回的測試
//cout << "address :out1 " << &(out1(a)) << &(1)<<endl;
//cout << "address :out2 " << &out2(b) << endl;
cout << "address :out3 " << &out3(c) << endl;
cout << "address :out4 " << &out4(d) << endl;

這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
- 前面兩個報錯,註釋掉。錯誤的原因從兩個對比可以看出,得到是一個左值。而取地址只能取一個左值的地址,也就是一個變量的地址,因爲變量在內存空間中有固定的地址。而單獨的數字,如圖中的數字1,沒有指定內存空間,不能取地址。普通的函數返回值在臨時內存塊內,運行到下一句可能就不存在。具體解釋看後面介紹。
- 從運行結果來看,out3 out4返回的都是引用類型,編譯器開闢空間存儲引用,故是存在具體的內存空間的,可以取地址。

  • 返回引用使用堆存儲數據

    之所以函數內返回臨時變量的引用會出問題的原因在於,臨時變量存儲在棧上,生命週期只是當前的 塊內,就是花括號內 {}。只要能夠存在生命週期更長的地方就沒問題了。不妨試一下存在堆內會怎麼樣。

int &out5(int &e)
{
    int *p = new int[1];
    p=&e;
    *p += 50;
    return *p;
}

...
int e=0;
cout << " address e:     " << &e << "   result : " << e << endl;
cout << " address out5:  " << &out5(e) << "   result : " << out5(e) << endl;

image
- 結果符合預期估計,因爲申請的堆空間沒有被釋放,存儲的內容就不會丟失。

若是如下釋放對應的空間,再次去訪問空間,則會卡死奔潰


int e=0;
int *fp1 = &out5(e);
int *fp2 = fp1; 
cout << " fp1  :  " << fp1 << "  fp2 : " << fp2 << endl;
delete fp1;
cout << " address e:     " << &e << "   result : " << e << endl;
cout << " address out5:  " << fp2<< "   result : " << *fp2 << endl;

image

  • 關鍵性的一點 C++ Primer Plus P268頁
    函數的引用返回值是左值,可以用於取地址
    函數的常規返回類型是右值,不能用於取地址
    以下測試一下這一點

image

可以後面兩個是返回引用函數,看到確實沒有報錯

關於什麼時候適合使用引用

  1. 程序員能夠修改調用函數中的數據對象。
  2. 通過傳遞引用而不是整個數據對象,提高程序的運行速度。

傳值 、指針、引用的選擇

  1. 如果數據很小,內置數據類型或者小型結構 ,傳值操作
  2. 如果對象是數組,那麼指針是唯一的方式
  3. 如果對象是較大的結構,那麼使用引用或者指針能夠節省比較大的時間和空間開銷。
  4. 如果數據對象是類的話,使用引用的操作方式則是標準操作方式。
  5. 如果將在操作中修改傳的數據值,使用指針會更加直觀。swap(&a,&b);能夠明顯看出意圖。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章