c++語法拾遺,一些細節與特性
寫了2年多的C+STL的acmer,在學習《C++ primer》時總結的一些少見的語法特性與細節。總體還是和題目說的一樣這是一篇 c++ 拾遺。
1 變數和基本型別
1.1 基本型別
1.1.1 字面常量
0123 表示的不是帶有前導0的數字123,而是代表8進位制數字123。
1.2 常量
1.2.1 constexpr
constexpr變數能自動判別賦值的表示式是否是常量表達式,若不是則會報錯。
constexpr int mf=20; //20是常量表達式 constexpr int limit=mf+1; //mf+1是常量表達式 constexpr int sz=size(); //當size是一個constexpr 函式的時候是是常量表達式 int a=20; constexpr int b=a+1; //報錯
1.2 處理型別
1.2.1 decltype
decltype能和auto類似的自動判別型別。
decltype(f()) a=b; //這裡的a的型別是f()返回值的型別
編譯器並不實際呼叫函式f,只是將返回值作為a的型別。
實際上decltype(exp) exp是一個表示式所以可以這麼寫
int a = 0;
decltype(a) b = 1; //b 被推導成了 int
decltype(10.8) x = 5.5; //x 被推導成了 double
decltype(x + 100) y; //y 被推導成了 double
- 如果 exp 是一個不被括號( )包圍的表示式,或者是一個類成員訪問表示式,或者是一個單獨的變數,那麼 decltype(exp) 的型別就和 exp 一致。
- 如果 exp 是函式呼叫,那麼 decltype(exp) 的型別就和函式返回值的型別一致。
- 如果 exp 是一個左值,或者被括號( )包圍,那麼 decltype(exp) 的型別就是 exp 的引用。
2 表示式
2.1 基礎
2.1.1 左值
簡單的一句話說,左值就是記憶體。
2.1.2 右值
簡單的一句話說,右值就是資料。
2.1.3 求值順序
c++ 中沒有定義表示式的求值順序,如:
int cnt=0; int f(){ return ++cnt; } int main(){ int a=f()-f(); //a的結果可能會因為編譯器不同而不同 //因為兩個f函式的呼叫順序沒有定義 return 0; }
int a=1;
int b=a+a++; //同樣,i的自增和i的呼叫順序沒定義
int c=a++ + ++a; //同樣無定義
int *d= new int[2];
*d=f(*d++); //無定義
另外java是有定義表示式的求值順序的,從左到右。c++不定義是為了編譯器啟用更多優化。
3 函式
3.1 可變引數的函式
3.1.1 initializer_list 形參
int sum(initializer_list<int> li){
int s=0;
for(auto i : li)
s+=i;
return s;
}
// 呼叫方法
sum({1,2,3,4,5});
sum({1,1,1,1});
initializer_lsit 物件中的元素永遠是常量型別。
可使用begin、end、size方法進行操作。
3.1.2 省略符形參
void f(...);
void f(cnt,...);
不建議使用,僅為方便c++訪問某些c程式碼設定。
3.2 函式返回
3.2.1 值如何被返回的
值返回的時候回撥用拷貝建構函式,建立一個臨時物件。
3.2.2 列表返回值
C++11 新標準規定,函式可以返回花括號包圍的值的列表。
vector<int> f(){
return {1,2,3,4};
}
實際上就是花括號賦值,因為值返回的時候回撥用拷貝建構函式,如果拷貝建構函式支援花括號,那就可以作為返回值。
3.2.3 返回陣列指標
int (*f)()[size]; //表示函式f返回長度為size的陣列指標
//特別的是這裡陣列表示是後置的
3.2.4 尾值返回型別
在C++11標準中添加了一種尾置返回型別的定義。
auto f()->int; //同等於 int f();
auto f()->int *[size]; //同等於 int (*f)()[size];
3.3 過載
3.3.1過載與const 形參
頂層的const無法和另一個沒有頂層的const的引數區分,但底層的const可以區分
void f(int);
void f(const int); //重複宣告,報錯
void f(int *)
void f(int *const) //重複宣告
void f1(int*);
void f1(const int*); //底層const,新函式。
void f1(int&);
void f1(const int&); //底層const,新函式。
3.3.2 過載與作用域
當在作用域內重複定義識別符號,作用域內的識別符號將會直接覆蓋外部的識別符號,包括過載的。
void f(int); //函式1
void f(char); //函式2
void f(double); //函式3
void foo(){
void f(double); //函式4
f(1.2); //正確呼叫函式4
f(1); //正確呼叫函式4,傳入值為1.0
f('c') //錯誤,沒定義
}
3.4 特殊用途語言特性
3.4.1 預設實參
可給函式的引數設定預設實參,但預設的呼叫時,會預設賦值。
void f(int a,int b,int c=1,int d=2,int e=3); //宣告
//呼叫
f(1,2); //實際為f(1,2,1,2,3);
f(1,2,3); //實際為f(1,2,3,2,3);
f(1,2,3,4,5); //實際為f(1,2,3,4,5);
設定預設實參只能從右到左,並且不能留空。
void f(int a=1,int b=2); //正確
void f(int a=1,int b); //錯誤
void f(int a,int b=1,int c); //錯誤
預設值可賦函式、變數或者表示式。
int x=1;
int v();
void f(int a=x,int b=v());
// 將在呼叫f時,呼叫v,為函式引數賦值
3.4.2 行內函數 inline
在函式的返回型別前面加上關鍵字inline,就可以將函式宣告為行內函數。行內函數有點類似c的巨集函式,它會在每一個呼叫的位置,內聯的展開,從而節省函式呼叫和返回的開銷。但行內函數宣告只是向編譯器發出的請求,編譯器可以選擇忽略。
inline void f();
行內函數定義通常放在標頭檔案中
3.4.3 constexpr 函式
該函式相當於一個常量表達式。
constexpr int f();
constexpr 函式定義通常放在標頭檔案中
3.5 除錯幫助
3.5.1 assert 預處理巨集
assert(expr);
當expr為假時輸出資訊並終止程式。
3.5.2 NDEBUG 預處理變數
當定義NDEBUG後,表示關閉除錯。
自定義除錯
#ifndef NDEBUG
//除錯程式碼
#endif
對除錯有幫助的識別符號
- __func__ 當前函式名 字串
- __FILE__ 檔名 字串
- __LINE__ 當前行號 整型
- __TIME__ 編譯時間 字串
- __DATE__ 編譯日期 字串