天天看點

const 和引用作為形參,傳回類型,函數類型詳解

const作用:const 聲明的變量、(常)函數或者傳回類型其主要作用就是不可改變(相當于常量),即變量值不能改變,傳回值不能改變,(常)函數定義内部參數不能改變。下面依次介紹。

1.臨時變量、引用參數和 const聲明的形參

在參數傳遞過程中,如果實參與引用參數不比對,C++将生成臨時變量。目前,僅當參數為 const 引用時,C++才允許這麼做,但以前不是這樣。如果引用參數是 const,則編譯器将在下面兩種情況下生成臨時變量:

  • 實參的類型正确,但不是左值;
  • 實參的類型不正确,但可以轉換為正确的類型。

注:左值參數是可以被引用的資料對象,例如,變量、數組元素、結構成員、引用和解除引用的指針都是左值(可以通過位址通路)。非左值包括字面常量(如 10.0)和包含多項的表達式(如 x + y)。

将引用參數聲明為常量資料的引用特點:

  • 使用 const 可以避免無意中修改資料的程式設計錯誤;
  • 使用 const 使函數能夠處理 const 和非 const 實參,否則将隻能接受非 const 資料;
  • 使用 const 引用使函數能夠正确生成并使用臨時變量。

是以,應盡量将引用類型形參聲明為 const 類型

double refcube(const double &ra)
{ return ra * ra * ra; }

//有如下調用方式
double side = 3.0;
double *pd = &side;
double &rd = side;
long edge = 5L;
double lens[4] = { 2.0, 5.0, 10.0, 20.0 };
double c1 = refcube(side);          //ra is side
double c2 = refcube(*pd);           //ra is *pd is side
double c3 = refcube(rd);            //ra is rd is side
double c4 = refcube(lens[2]);       //ra is lens[2]
double c5 = refcube(edge);          //ra is temporary(臨時的) variable
double c6 = refcube(7.0);           //ra is temporary variable
double c7 = refcube(side + 10.0);   //ra is temporary variable
           

參數 side 、len[2] 、 rd 和 *pd 都是有名稱的、double 類型的資料對象,是以可以為其建立引用,而不需要臨時變量。然而 edge 雖然是變量,但是類型卻不正确,double 引用不能指向 long。另一方面,參數 7.0 和 side + 10 的類型都正确,但沒有名稱,在這種情況下,編譯器将生成一個臨時匿名變量,并讓 ra 指向它。這些臨時變量隻在函數調用期間存在,此後編譯器便可以随意将其删除。那為什麼對于 const引用這種行為是可行的,而對于普通的引用是不行的呢?

void swapr(int &a, int &b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}

//執行下面語句
long a = 3, b = 5;
swapr(a, b);
           

如果順利通過編譯(有些優秀的編譯器會提示錯誤),則 a 和 b  的值沒有發生交換。因為這裡的類型不比對,編譯器将建立兩個臨時 int 變量,将他們初始化為 3 和 5 ,然後交換臨時變量的内容,而a 和 b 保持不變。如果接受引用參數的函數的意圖是修改作為參數傳遞的變量,則建立臨時變量将阻止這種意圖的實作。解決的方法是,禁止建立臨時變量,現在的 C++編譯器一般都是這樣的,禁止建立臨時變量。

對于開始寫的 refcube() 函數。該函數的目的隻是使用傳遞的值,而不是修改它們,是以臨時變量不會造成任何不利的影響,反而會使函數在處理的參數種類方面更便利。是以,如果聲明将引用指定為 const ,C++将在必要時生成臨時變量。實際上,對于形參為 const引用的 C++函數,如果實參不比對,則其行為類似于按值傳遞,為確定原始資料不被修改,将使用臨時變量來存儲值。

 2.傳回引用和傳回 const 引用

引用主要應用在結構和類的參數傳遞中,主要原因是可以節省記憶體開銷,節省了了傳遞時拷貝構造函數和資料備份的時間和記憶體空間。注意,應避免傳回函數終止時不再存在的記憶體單元引用,即傳回臨時變量的引用是不允許的。

//結構體聲明
struct free_throws
{
    std::string name;
    int made;
    int attempts;
    float percent;
}

//列印函數定義
void display(const free_throws &ft)
{
    using std::cout;
    cout << "name: " << ft.name << '\n';
    cout << " Made: " << ft.made << '\t';
    cout << "Attempts: " << ft.attempts << '\t';
    cout << "Percent: " << ft.percent << '\n';
}

//某計算函數定義
free_throws& accumulate(free_throws& target, const free_throws& source)
{
    target.attempts += source.attempts;
    target.made += source.made;
    set_pc(target);  //根據 made 和 attempts 值計算出 percent
    return target;
}
           

有以下幾條調用:

1.display(accumulate(team, two));

首先,将結構對象 team 作為第一個參數傳遞給了 accumulate()。這意味着,在 accumulate()中,target 指向的是 team。函數 accumulate()修改 team,再傳回指向它的引用。注意到傳回語句

return target;

光看這條語句并不能知道傳回的是引用,但函數頭和原型指出了這一點:

free_throws& accumulate(free_throws& target, const free_throws& source)

如果傳回類型被聲明為 free_throws 而不是 free_throws& ,上述語句将傳回 target(也就是 team)的拷貝。但傳回類型為引用,這意味着傳回的是最初傳遞給 accumulate()的 team 對象。

接下來,将 accumulate()的傳回值作為參數傳遞給了display()。display()的參數為引用,這意味着 display()中的 ft 指向的是 team,是以将顯示 team 的内容。是以下述代碼:

display(accumulate(team, two));

與下面的代碼等效:

accumulate(team, two);

display(team);

2.accumulate(dup, five) = four;

這條語句将指派給函數調用,這是可行的,因為函數的傳回值是一個引用。其效果如下:首先 five 的資料添加到 dup 中,再使用 four 的内容覆寫 dup 的内容。在指派語句中,左邊必須是可修改的左值,也就是說,在指派表達式中,左邊的子表達式必須辨別一個可修改的記憶體塊。在這裡,函數傳回指向 dup 的引用,它确實辨別的是一個這樣的記憶體塊,是以這條語句是合法的。如果函數 accumulate()按值傳回,這條語句将不能通過編譯。 為什麼正常函數傳回值是右值呢?這是因為這種傳回值位于臨時記憶體單元中,運作到下一條語句時,它們可能不再存在。

由于傳回的是 dup 的引用,是以上述代碼與下面的代碼等效:

accumulate(dup, five);

dup = four;

其中第二條語句消除了第一條語句所做的工作,是以在原始指派語句使用 accumulate()的方式并不友好。假設我們想使用引用傳回值,但又不允許執行像給 accumulate()指派這樣的操作,隻需将傳回類型聲明為 const 的引用:

const free_throws&
    accumulate(free_throws& target, const free_throws& source);
           

現在傳回類型是 const,是不可修改的左值,是以下面的指派語句不合法:

accumulate(dup, five) = four;

但下面的語句是合法的:

display(accumulate(team, two));

這是因為 display()的形參也是 const free_throws& 類型。但下面的語句不合法,因為 accumulate()的第一個形參不是 const:

accumulate(accumulate(team, three), four);

這影響大嗎?這裡而言是不大的,因為可以這樣使用:

accumulate(team, three);

accumulate(team, four);

另外,還可以在指派語句右邊使用 accumulate()。

3.常成員函數

在類中使用關鍵字 const 說明的函數為常成員函數,常成員函數的說明格式如下:

類型  函數名(參數表)  const;

const 是函數類型的一個組成部分,是以在聲明函數和定義函數時都要有關鍵字 const。 在調用時不必加 const。同時 const 也可以作為函數重載的區分标志。

  • 常成員函數可以通路常資料成員,也可以通路普通資料成員。常資料成員可以被常成員函數通路,也可以被普通成員函數通路。具體情況如下表:

普通成員函數和常成員函數的通路特性比較

函數類型 普通資料成員 常資料成員 常對象的資料成員
普通成員函數 可以通路,也可以改變值 可以通路,但不可以改變值 不允許通路和改變值 
常成員函數 可以通路,但不可以改變值 可以通路,但不可以改變值 可以通路,但不可以改變值
  • 如果将一個對象說明為常對象,則通過該對象隻能調用它的常成員函數,而不能調用普通成員函數。常成員函數是常對象唯一的對外接口,這是 C++從文法機制上對常對象的保護。
  • 常成員函數不能更新對象的資料成員,也不能調用該類中的普通成員函數,這就保證了在常函數中絕對不會更新資料成員的值。

繼續閱讀