1. 程式人生 > 實用技巧 >const形參和實參

const形參和實參

頂層const作用於物件本身:

const int ci = 42;       // 不能改變ci,const是頂層的
int i = ci;              // 正確:當拷貝ci時,忽略頂層const
int * const p = &i;      // const是頂層的,不能給p賦值
*p = 0;                  // 正確:通過p改變物件的內容是允許的

和其他初始化過程一樣,當用實參初始化形參時會忽略掉頂層 const。

換句話說,形參的頂層 const 被忽略掉了。當形參有頂層const時,傳給它常量物件或者非常量物件都是可以的∶

void fcn(const int i){} // fcn能讀取i,但不能向i寫值

呼叫 fcn 函式時,既可以傳入 const int 也可以傳入 int。

忽略掉形參的頂層 const可能產生意想不到的結果:

void fcn(const int i){} // fcn能讀取i,但不能向i寫值
void fcn(int i){}       // 錯誤:重新定義了fcn(int)

在 C++語言中,允許我們定義若干具有相同名字的函式,不過前提是不同函式的形參列表應該有明顯的區別。

因為頂層 const被忽略掉了,所以在上面的程式碼中傳入兩個fcn 函式的引數可以完全一樣。

因此第二個 fcn 是錯誤的,儘管形式上有差異,但實際上它的形參和第一個 fcn 的形參沒什麼不同。

指標或引用形參與const

形參的初始化方式和變數的初始化方式是一樣的。

我們可以使用非常量初始化一個底層 const 物件,但是反過來不行;同時一個普通的引用必須用同類型的物件初始化。

int i = 42;
const int *cp = &i;    // 正確:但是cp不能改變i
const int &r = i;      // 正確:但是r不能改變i
const int &r2 = 42;    // 正確
int *p = cp;           // 錯誤:p和cp的型別不匹配
int &r3 = r;           // 錯誤:r3和r的型別不匹配
int &r4 = 42;          // 錯誤:不能用字面值初始化一個非常量引用

將同樣的初始化規則應用到引數傳遞上可得如下形式:

string::size_type find_char(const string &s, char c, string::size_type &occurs);
void reset(int *i);                       // 指標版本
void reset(int &i);                       // 引用版本

int i = 0;
const int ci = i;
string::sizel_type ctr = 0;
reset(&i);                                // 呼叫形參型別是int*的reset函式
reset(&ci);                               // 錯誤:不能用指向const int物件的指標初始化int*

reset(i);                                 // 呼叫形參型別是int&的reset函式
reset(ci);                                // 錯誤:不能把普通引用繫結到const物件ci上
reset(42);                                // 錯誤;不能把普通引用繫結到字面值上
reset(ctr);                               // 錯誤:型別不匹配
// 正確:find_char的第一個形參是對常量的引用
find_char("Hello world!", 'o', ctr);

要想呼叫引用版本的reset,只能使用int型別的物件,而不能使用字面值、求值結果為 int 的表示式、需要轉換的物件或者 const int 型別的物件。

類似的,要想呼叫指標版本的 reset只能使用 int*。

另一方面,我們能傳遞一個字串字面值作為find char的第一個實參,這是因為該函式的引用形參是常量引用,而C++允許我們用字面值初始化常量引用。

儘量使用常量引用

把函式不會改變的形參定義成(普通的)引用是一種比較常見的錯誤,這麼做帶給函式的呼叫者一種誤導,即函式可以修改它的實參的值。

此外,使用引用而非常量引用也會極大地限制函式所能接受的實參型別。就像剛剛看到的,我們不能把 const 物件、字面值或者需要型別轉換的物件傳遞給普通的引用形參。

這種錯誤絕不像看起來那麼簡單,它可能造成出人意料的後果。以 find_char 函式為例,函式(正確地)將它的string型別的形參定義成常量引用。假如我們把它定義成普通的 string&∶

string::size_type find_char(string &s, char c, string::size_type &occurs);

則只能將find_char函式作用於string物件。類似以下這樣的呼叫

find_char("Hello world!", 'o', ctr);

將在編譯時發生錯誤。

還有一個更難察覺的問題,假如其他函式(正確地)將它們的形參定義成常量引用,那麼第二個版本的find char無法在此類函式中正常使用。舉個例子,我們希望在一個判斷 string 物件是否是句子的函式中使用 find_char∶s.

bool is_sentence(const string &s){
    // 如果s的末尾有且只有一個句號,則s是一個句子。
    string::size_type ctr = 0;
    return find_char(s, '.', str) == s.size() - 1 && ctr == 1;
}

如果 find_char的第一個形參型別是 string&,那麼上面這條呼叫find_char的語句將在編譯時發生錯誤。

原因在於s是常量引用,但find_char 被(不正確地)定義成只能接受普通引用。

解決該問題的一種思路是修改is_sentence 的形參型別,但是這麼做只不過轉移了錯誤而已,結果是 is_sentence 函式的呼叫者只能接受非常量 string 物件了。

正確的修改思路是改正 find_char 函式的形參。如果實在不能修改find_char,就在 is_sentence 內部定義一個 string型別的變數,令其為s的副本,然後把這個 string 物件傳遞給 find_char。