1. 程式人生 > 其它 >c++語法拾遺,一些細節與特性

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