在我們寫代碼的過程中,添加合適恰當的相應修飾符去告訴編譯器該怎麼做,限制編譯器在我們背後偷偷的做一些出乎我們意料的行爲,這樣方能提高程序的魯棒性。作爲程序員,我們是主宰,應該由我們來明確告訴編譯器,我們希望它做什麼,不希望它做什麼,而不是等發生錯誤的時候才花大量的時間去debug。合理應用各種修飾符,就像我們爲程序開鑿了一條河牀,讓它按照我們的意願流淌,何樂而不爲?
語言有很多修飾符,我們在這裏只分析const, static,enum, inline,#define的用法以及他們的關係。
const的用法可以概括爲下面幾個方向:
1. 修飾指針,迭代器。
char str[] = "Hello World";
const char *pStr = str; //常量指針,指針指向的對象是一個常量,無法通過指針修改對象的值,
//但是可以改變指針的指向
*pStr = 'h'; //error
pStr++; //right
char* const pStr = str; //指針常量, 常量的內容爲一個指針,我們無法改變指針本身的值,
//但是可以改變指針所指向對象的值
*pStr = 'h'; //right
pStr++; //error
const char* const pStr = str; //指針本身已經指針所指向的值均不能改變,
//可以理解爲完全的常量
//迭代器
vector<int>::const_iterator IteVecInt; //常量迭代器, 同義與常量指針
const vector<int>::iterator IteVecInt; //迭代器常量, 同義與指針常量
2. const 修飾local 變量
const int allocNum = 1000;
int array[allocNum];
3. const 修飾函數參數以及返回值
這應該是用得最多的用法。修飾函數參數,可以告訴編譯器該參數在函數作用域內不會改變, 請您放心做優化, 編譯器還可以根據這個發現一些書寫錯誤, 如下:
void functionA(const int &a) {
int b;
b = 10;
if(a = b) { //應該爲 a == b, 書寫錯誤
...
}
...
}
只要我們認爲這些參數在函數內不會發生改變, 特別是指針, 迭代器,引用或者數組, 我們應該斟酌着加上合適的const修飾符, 可以避免上述類似錯誤,因爲編譯器會發出錯誤警告。 否則我們只能進入徹夜的調試大會。
const修飾函數的返回值, 我們的初衷是不希望函數的返回值被賦值, 返回值爲只讀, 不可寫。
const int& functionB(const int &a) {
int b;
b = 10;
if(a = b) { //應該爲 a == b, 書寫錯誤
...
}
...
return a;
}
int num = 10;
int numBack = functionB(num); //right
functionB(num) = 5; //error;
4. const 修飾類成員函數
在C++中,const 修飾類成員函數非常有用,通過const修飾成員函數,一方面提高函數接口可能性,讀者閱讀代碼可以很清楚的知道在這個函數內部不會改變成員變量,易於明白作者的意圖;另一方面可以操作相關的const對象;還有一點,我們經常會忽略的是,兩個成員函數如果只是常量性不同,可以被重載。
class book {
public:
....
const char& operator[](std::size_t position) const
{ return bookText[position];}
char& operator[](std::size_t position)
{ return bookText[position]; }
...
private:
std::string bookText;
}
上述代碼有兩個需要注意的地方, 一是const修飾的成員函數返回值應該採用const, const修飾的成員函數我們要做到不僅在函數內部成員變量不被改變,與此同時也不能在返回值留下被修改的漏洞。
二是const屬性不同的成員函數重載,如下:
book bookA("C++ primer");
std::cout<<bookA[0]; //調用char& operator[](std::size_t position)
bookA[0] = 'B'; // "B++ primer" is right
book bookB("C++ primer");
std::cout<<bookB[0]; //調用const char& operator[] (std::size_t position) const
bookA[0] = 'B'; // error, 返回值爲const,不能修改
static的用法:
static在c語言中的用法有兩方面:
1. 修飾函數以及全局變量, 使被修飾的對象的可見性僅限於被定義的文件中
// calNum.cpp
static int num = 10;
static int calNum(int );
//可見性均只限於calNum.cpp, 在其他文件中不可見
上述static的用法在C代碼中比較常見,但是在C++代碼中,我們可以用更加有效的方法替代, 那就是無名namesapce, 既然namespace沒有名字, 你在其他文件當然是無法訪問的,也就是說無名namespace的作用域只能在定義文件內。代碼如下:
namespace {
int number = 10;
int calNum(int num) { return num+1;}
};
int main()
{
number = 11;
int newNum = calNum(number);
return 0;
}
2.修飾local變量
static修飾local變量,使得local變量的生命週期上升到跟全局變量一樣,但是不會改變local變量的作用域。 static修飾的local變量會被存儲在全局變量/靜態變量區內, 而不是在棧中。
int calTimes(void )
{
static int times = 0;
return ++times;
}
int num1 = calTImes(); // 1
int num2 = calTimes(); // 2
3. 修飾類成員變量以及成員函數
在c++中,static新增的用法是修飾成員變量以及成員函數。 static修飾的成員變量是隸屬於整個類別所有成員所共享, 不是僅僅屬於某一個類對象。static修飾的成員函數不能操作非static成員變量。這是因爲普通成員函數會有個默認的形參爲this指針, this指針所指向對象爲具體的某個類對象, static修飾的成員函數不存在this指針。
class textBook {
public:
static int getNum() {return num;} //right
static int getNum1() {return num1;} //Error
private:
int num1;
static int num;
};
inline的用法
引用《effective c++》的一句話: inline函數看起來像函數,動作像函數,卻不是函數,比#define表現要好,調用inline函數不會蒙受調用普通函數的額外開銷。
下面我們分別對上面的話進行解析。
inline函數在書寫的時候完全是按照函數的格式, 所以看起來像函數; 動作像函數,主要是指調用inline函數時,會像普通函數一樣進行形參檢查,而宏不會對參數進行檢查,所以有時候一不小心就會出錯(即使你的本意不是這樣)。
inline函數的本意是在每一個調用inline函數的地方都會以inline函數的本體替換之,所以不會帶來額外的普通函數調用開銷。普通函數的調用開銷一般有保存返回地址,保存實參,保存某些寄存器值,而被調用函數不一定跟調用函數在同一個內存頁,因此可能還會涉及到內存頁面置換帶來的額外開銷。
上述所說的inline函數那麼多優點,是不是我們可以無所顧忌的使用inline函數呢?不是的。過度熱衷inline 函數會導致代碼膨脹,進而造成更多的內存換頁行爲,還會降低cache的命中率,直接造成運行效率降低。反之,如果函數的本體很小,那麼inline直接替換以後的函數本體可能比未替換的更小。運行效率不僅沒有下降,反而有提高。
一般來說,函數本體只有幾行,且沒有循環遞歸等較爲複雜邏輯運算的均可以定義爲inline函數。此外,並不是我們用inline去聲明一個函數或者將一個函數定義在class內,它就一定爲inline函數。請記住,inline只是我們對編譯器的一個請求,最終是不是得看編譯器的決定。
inline函數一般被定義於頭文件。因爲大部分的編譯器都是在編譯的時候對inline函數進行函數本體替換,這個時候編譯器需要在每一個調用inline函數的地方都看到inline的函數本體。
virtual函數被定義爲inline函數的願望一般不能實現。因爲inline函數在編譯的時候就要進行函數替換,需要知道調用的具體函數是哪一個,此過程發生在鏈接與運行之前。而virtual函數意味着,不知道調用的是哪一個函數,直到函數運行到此。
在工程實現的初期,爲了便於測試代碼debug,我們先不要定義任何inline函數,否則無法設定斷點之類的debug手段。在測試成功,進行優化時,我們可以再考慮將哪些函數進行inline。還有一點需要注意的是,因爲inline函數是在每一個調用處以本體替換,因此只要inline函數本體發生變化,每一個調用它的文件都要重新編譯鏈接,可能會消耗更多的時間。如果是非inline函數只要一個文件重新編譯,其它文件鏈接即可。這是在inline函數時要考慮的。
define的用法: