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__ 編譯日期 字元串