當形參是const時,必須要注意關於頂層const的討論。如前所述,頂層const的作用於對象本身:
const int ci=42; //不能改變ci,const是頂層的
int i=ci; //正確:當拷貝ci時,忽略了它的頂層const
int *const p=&i; //const是頂層的,不能給p賦值
*p=0; //正確:通過p改變對象的內容是允許的,現在i變成了0
和其他初始化過程一樣,當用實參初始化形參時會忽略掉頂層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) {/*....*/}//錯誤:重復定義了fdn(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; //錯誤:不能用字面值初始化一個非常量引用
將同樣的初始化規則應用到參數傳遞上可得如下形式:
int i=0;
const int ci=i;
string::size_type ctr=0;
void reset(int &i);
reset(&i); //調用形參類型是int *的reset函數
reset(&ci); //錯誤:不能用指向const int對象的指針初始化int *
reset(i); //調用參數類型是int&的reset函數
reset(ci); //錯誤:不能把普通引用綁定到const對象ci上
reset(42); //錯誤:不能把普通引用綁定到字面值上
reset(ctr); //錯誤:類型不匹配,ctr是無符號類型
//find_char的第一個形參是對常量的引用
find_char("hello world",'o',ctr);//可以綁定到字面值常量上
要想調用引用版本的reset,只能使用int類型的對象,而不能使用字面值、求值結果為int的表達式、需要轉換的對象或者const int類型的對象。類似的,要想調用指針版本的reset只能使用int*。
另一方面,我們能傳遞一個字符串字面值作為find_char的第一個實參,這是因為改函數的引用形參是常量引用,而C++允許我們用字面值初始化常量引用。
盡量使用常量引用
把函數不會改變的形參定義成(普通的)引用是一種比較常見的錯誤,這么做帶來給函數的調用者一種誤導,即函數可以修改它的實參的值。此外,使用引用而非常量引用也會極大地限制函數所能接受的實參類型。就像剛剛看到的,我們不能把const對象、字面值或者需要類型轉換的對象傳遞給普通的引用形參。