C++之const關鍵字
const作用
const關鍵字在C++中真是無處不在,無論是函式引數,還是函式返回值,還是函式末尾都經常會看到const關鍵字,這表明C++中的const關鍵字是非常靈活的,
合理地使用const關鍵字能大大提高我們程式的健壯性。
被const修飾的即表明是常量性的、只讀性的,不可隨意修改的。因為const物件一旦建立後其值就不能再改變,所以const物件必須初始化。
const在C和C++中的區別
雖然在C中const也表示不可修改的意思,但是它的校驗卻沒有C++中那麼嚴格,比如在C中被const修改的變數我們可以通過間接繞過的方式修改,但是在C++中卻是不行的,例如:
main.c int main() { const int a = 100; a = 50; // 編譯錯誤 int *p = &a; *p = 50; // 編譯成功,可以修改a的值 return 0; } main.cpp int main() { const int a = 100; a = 50; // 編譯錯誤,a的值無法修改 int *p = &a; // 無法編譯通過 const int *p = &a; // 可以編譯通過 *p = 50; // 無法編譯通過,不能通過指標修改a的值 return 0; }
const到底修飾誰
const在不同的位置修飾的內容不一樣,對於const到底修飾的是誰,誰才是不可變的這個問題有一個簡單的規則就是const離誰近,誰就不能被修改;。
對於const修飾的變數從右往左看,const修飾誰就表示誰不可變 (注意const不能修飾*)。也就是const離變數名近就是用來修飾指標變數的,便是這個變數不可變,離變數名遠就是用來修飾這個資料的,表示這個資料不可改變。我們通過下面的例子來分析下const到底修飾的是誰:
main.cpp int main() { int i = 10; const int *p1; // const距離int比較近,說明const修飾的是指標p1指向的資料不可變 *p1 = 100; // 錯誤 int const *p2; // 從右往左看,因為const不可以修飾星號,所以const修飾的是int,因此這種寫法與p1是一致的 *p2 = 100; // 錯誤 int * const p3 = &i; // 這裡const距離p3比較近,所以修飾的是p3變數,所以必須初始化 *p3 = 100; // 可以,雖然p3不可以變,但是其指向內容是可變的 int j = 20; p3 = &j; //錯誤,p3被const修飾,不可重新指向 const int * const p4 = &i; // 雙重修飾,p4不可變,p4指向的記憶體也不可變 p4 = &j; // 錯誤 *p4 = 100; //錯誤 return 0; }
const與函式之間的那點事
先說下知識點:
1、const可以構成函式過載,如果const構成函式過載,const物件只能呼叫const函式,非const物件優先呼叫非const函式。
2、const放在函式末端const修飾類的成員函式表示在本函式內部不會修改類內的資料成員,不會呼叫其它非const成員函式
3、const函式只能呼叫const函式,const物件,只能呼叫const成員函式,非const函式可以呼叫const函式。
4、const修飾函式的返回值,如果該函式是以值的方式返回,則使用const修飾是沒有意義的,如果該函式返回的是指標,則表示返回的指標的內容是不可修改的,需要使用const修飾來接收。
下面是具體的例子:
using namespace std;
class Person{
public:
Person(int a):age(a),name(""){
}
virtual ~Person(){
}
int getAge() const{
name = "hell0"; // 錯誤,末端帶const修飾,表示承諾在該函式內不會改變this物件的內容
std::cout << "getAge const" << std::endl;
return age;
}
int getAge(){
// 與帶const的構成函式過載
std::cout << "getAge" << std::endl;
return age;
}
string getName(){
return name;
}
public:
const int age; // 因為有const成員變數的存在,所以這個變數必須要初始化,所以需要自定義建構函式
string name;
};
void printConst(const Person& person){
std::cout << "printConst" << std::endl;
person.name = "hello"; // 錯誤,不可以修改person
}
void print(Person& person){
std::cout << "print const" << std::endl;
person.name = "hello"; // 正確
}
// 這裡加的const是沒有價值的
const Person getPerson1(){
return Person(1);
}
// 返回的指標內容不可修改,需要用const接收
const Person* getPerson2(){
return new Person(2);
}
int main() {
Person person1 = getPerson1();
const Person *person2 = getPerson2(); // 因為getPerson2的返回值被const修飾,所以返回的指標的內容是不可修改的,所以不能去掉const
person1.getAge(); // 呼叫不帶const的getAge()
person2->getAge(); // 呼叫帶有const的getAge()
print(*person2); // 錯誤const物件只能呼叫const函式
printConst(*person2); // 正確,const物件只能呼叫const函式
person1.getName(); // 可以
person2->getName(); // 錯誤,const物件只能呼叫const函式
return 0;
}
相信結合程式碼註釋和以上四點知識點應該還是挺好理解的,紙上得來終覺淺,要想真正掌握還是需要實打實地自己敲敲實踐一把。
const與mutable
mutable可以說是與const為敵的一個關鍵字了,如果使用mutable關鍵字修飾類的成員變數,那麼在那些被const修飾的函式體內,也是可以修改這個成員變數的,例如:
using namespace std;
class Person {
public:
Person(int a) : age(a), name("") {
}
virtual ~Person() {
}
int getAge() const {
name = "hell0"; // 可以,因為name被mutable修飾了
age = 20; // 不行,age沒有被mutable修飾
std::cout << "getAge const" << std::endl;
return age;
}
public:
const int age; // 因為有const成員變數的存在,所以這個變數必須要初始化,所以需要自定義建構函式
mutable string name;
};
void printConst(const Person &person) {
std::cout << "printConst" << std::endl;
person.name = "hello"; // 可以name被mutable修飾了
}
int main() {
Person person(1);
person.getAge();
printConst(person);
return 0;
}
什麼時候該用const
對於這個問題,筆者的意見是不管三七二十一,先把能用const修飾的地方都用上const,等等真正使用這些變數或者函式提示無法編譯通過的時候再去詳細斟酌是需要刪除const修飾呢。還是自己的程式設計呼叫有問題。
相信很多C/C++都使用過巨集,特別是一些C/C++高手更是把巨集玩得出神入化,但是如果一個巨集比較複雜,自己又對巨集的掌握不是那麼深入的話,往往會程式設計師們產生一些意想不到的困擾,在《Effective C++》一書的第第二和第三條款作者就提倡:
Item 2: 用 const, enums 和 inlines 取代 #defines
Item 3: 只要可能就用 const
關注我,一起進步,人生不止coding!!!
公粽號:思想覺悟