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++從文法機制上對常對象的保護。
- 常成員函數不能更新對象的資料成員,也不能調用該類中的普通成員函數,這就保證了在常函數中絕對不會更新資料成員的值。