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