C++參照的詳解

2020-08-12 16:09:32

參照的詳解

參照的應用:
1、參照作爲參數
  參照的一個重要作用就是作爲函數的參數。以前的C語言中函數參數傳遞是值傳遞,如果有大塊數據作爲參數傳遞的時候,採用的方案往往是指針,因爲這樣可以避免將整塊數據全部壓棧,可以提高程式的效率。但是現在(C++中)又增加了一種同樣有效率的選擇(在某些特殊情況下又是必須的選擇),就是參照。
  (1)使用參照傳遞函數的參數,在記憶體中並沒有產生實參的副本,它是直接對實參操作;而使用一般變數傳遞函數的參數,當發生函數呼叫時,需要給形參分配儲存單元,形參變數是實參變數的副本;如果傳遞的是物件,還將呼叫拷貝建構函式。因此,當參數傳遞的數據較大時,用參照比用一般變數傳遞參數的效率和所佔空間都好。
  (2)使用指針作爲函數的參數雖然也能達到與使用參照的效果,但是,在被調函數中同樣要給形參分配儲存單元,且需要重複使用"*指針變數名"的形式進行運算,這很容易產生錯誤且程式的閱讀性較差;另一方面,在主調函數的呼叫點處,必須用變數的地址作爲實參。而參照更容易使用,更清晰。
  如果既要利用參照提高程式的效率,又要保護傳遞給函數的數據不在函數中被改變,就應使用常參照。
2、常參照
  常參照宣告方式:const 型別識別符號 &參照名 = 目標變數名;
  用這種方式宣告的參照,不能通過參照對目標變數的值進行修改,從而使參照的目標成爲const,達到了參照的安全性。

#include <stdio.h>
#include <iostream>
using namespace std;
void test_const();

int main(void){

   test_const();
   return 0;
}

void test_const(){
   int a=1;
   int &b=a;
   b=2;
   cout<<"a="<<a<<endl;//2
   int c=1;
   const int &d=c;
  // d=2;//編譯錯誤 error: assignment of read_only reference 'd'
    c=2;//正確
    cout<<"c="<<c<<endl;
}

3、參照作爲返回值
  要以參照返回函數值,則函數定義時要按以下格式:
  型別識別符號 &函數名 (形參列表及型別說明)
    { 函數體 }
  說明:
  (1)以參照返回函數值,定義函數時需要在函數名前加&
  (2)用參照返回一個函數值的最大好處是,在記憶體中不產生被返回值的副本。
  以下程式中定義了一個普通的函數fn1(它用返回值的方法返回函數值),另外一個函數fn2,它以參照的方法返回函數值。

#include <iostream>
using namespace std;
float temp;//定義全域性變數temp
float fn1(float r);//宣告函數fn1
float &fn2(float r);//宣告函數fn2 

float fn1(float r){//定義函數fn1,它以返回值的方法返回函數值
    temp=(float)(r*r*3.14);
    return temp;
 }

float &fn2(float r){//定義函數fn2,它以參照方式返回函數值
    temp=(float)(r*r*3.14);
    return temp;
}
int main(){
  	float e=10.0;
    float a=fn1(10.0);//第1種情況,系統生成要返回值的副本(即臨時變數)
    // float &b=fn1(10.0); //第2種情況,可能會出錯(不同 C++系統有不同規定)
   /*error: invalid initialization of non-const reference of type 'float&' from an rvalue of type 'float'
   */
     //不能從被調函數中返回一個臨時變數或區域性變數的參照
     float c=fn2(10.0);//第3種情況,系統不生成返回值的副本
     //可以從被調函數中返回一個全域性變數的參照
     float &d=fn2(10.0); //第4種情況,系統不生成返回值的副本
     e=d;

     cout<<"a="<<a<<",c="<<c<<",d="<<d<<",e="<<e<<endl;
     //a=314,c=314,d=314
     return 0;
 }

參照作爲返回值,必須遵守以下規則:
  (1)不能返回區域性變數的參照。這條可以參照Effective C++[1]的Item 31。主要原因是區域性變數會在函數返回後被銷燬,因此被返回的參照就成爲了"無所指"的參照,程式會進入未知狀態。如【例5】中的第2種情況出現編譯錯誤。
  (2)不能返回函數內部new分配的記憶體的參照。這條可以參照Effective C++[1]的Item 31。雖然不存在區域性變數的被動銷燬問題,可對於這種情況(返回函數內部new分配記憶體的參照),又面臨其它尷尬局面。例如,被函數返回的參照只是作爲一個臨時變數出現,而沒有被賦予一個實際的變數,那麼這個參照所指向的空間(由new分配)就無法釋放,造成memory leak。
  (3)可以返回類成員的參照,但最好是const。這條原則可以參照Effective C++[1]的Item 30。主要原因是當物件的屬性是與某種業務規則(business rule)相關聯的時候,其賦值常常與某些其它屬性或者物件的狀態有關,因此有必要將賦值操作封裝在一個業務規則當中。如果其它物件可以獲得該屬性的非常數參照(或指針),那麼對該屬性的單純賦值就會破壞業務規則的完整性。
  (4)參照與一些操作符的過載:流操作符<<和>>,這兩個操作符常常希望被連續使用,例如:cout << 「hello」 << endl; 因此這兩個操作符的返回值應該是一個仍然支援這兩個操作符的流參照。可選的其它方案包括:返回一個流物件和返回一個流物件指針。但是對於返回一個流物件,程式必須重新(拷貝)構造一個新的流物件,也就是說,連續的兩個<<操作符實際上是針對不同對象的!這無法讓人接受。對於返回一個流指針則不能連續使用<<操作符。因此,返回一個流物件參照是惟一選擇。這個唯一選擇很關鍵,它說明了參照的重要性以及無可替代性,也許這就是C++語言中引入參照這個概唸的原因吧。 賦值操作符=。這個操作符象流操作符一樣,是可以連續使用的,例如:x = j = 10;或者(x=10)=100;賦值操作符的返回值必須是一個左值,以便可以被繼續賦值。因此參照成了這個操作符的惟一返回值選擇。
4、參照和多型
  參照是除指針外另一個可以產生多型效果的手段。這意味着,一個基礎類別的參照可以指向它的派生類範例。
例子: 
class A;
class B:public A{ … … }
  B b;
  A &Ref = b;//用派生類物件初始化基礎類別物件的參照
  Ref 只能用來存取派生類物件中從基礎類別繼承下來的成員,是基礎類別參照指向派生類。如果A類中定義有虛擬函式,並且在B類中重寫了這個虛擬函式,就可以通過Ref產生多型效果。
  
參照總結
  (1)在參照的使用中,單純給某個變數取個別名是毫無意義的,參照的目的主要用於在函數參數傳遞中,解決大塊數據或物件的傳遞效率和空間不如意的問題。
  (2)用參照傳遞函數的參數,能保證參數傳遞中不產生副本,提高傳遞的效率,且通過const的使用,保證了參照傳遞的安全性。
  (3)參照與指針的區別是,指針通過某個指針變數指向一個物件後,對它所指向的變數間接操作。程式中使用指針,程式的可讀性差;而參照本身就是目標變數的別名,對參照的操作就是對目標變數的操作。
  (4)使用參照的時機。流操作符<<和>>、賦值操作符=的返回值、拷貝建構函式的參數、賦值操作符=的參數、其它情況都推薦使用參照。