一:左值和右值
這個概念暫時很模糊,打算學完這一章再總結。目前的理解是,左值有名字,可以通過名字訪問內存,右值沒有名字,一般是運算的中間結果或者字面值常量等。
const修飾的變量是常量左值
如果decltype()函數的括號中表達式結果是左值,則得到一個引用類型。例如在下面這段代碼中,b是int類型,c是int*類型
int a;
decltype(a) b;
decltype(a = 0) c = a
二:神奇的求值順序(以前沒注意到耶)
int i = 0;
cout << i << " " << ++i << endl;
輸出是1 1,沒想到吧哈哈。其實1,1這個答案是沒意義的,也就是說,對於運算符"<<",其運算對象的執行順序是不可預測的,像這裏不是先輸出左側的i,而是先執行++i操作。同樣,對於int a = f1() + f2();先調用f1()還是先調用f2()是不固定的,因爲加法沒有規定運算對象的執行順序鴨。
只有四種運算符規定了執行順序:邏輯與(&&)運算符,邏輯或(||)運算符,條件(? :)運算符,逗號( , )運算符。
上圖是再次強調求值順序的陷阱,小心寫出了錯誤的代碼。
編譯器之所以沒有規定求值順序,是爲編譯器的優化提供了餘地。
三:c++11標準規定的除法和取餘規則
設m,n都是整數,則(-m)/n =m/(-n) = - (m/ n); m/n = (-m)/(-n) ; (-m)%n = (-m)%(-n) = -(m%n), m%n = m%(-n)
四:賦值運算符
賦值運算是右結合的。
五:遞增和遞減運算符
++i是前置版本,i++是後置版本。前置版本返回的是修改後的對象,作爲左值,後置版本將對象修改前的副本作爲右值返回。由於後置版本需要保存原始值的副本,所以性能會較低,建議養成使用前置版本的習慣鴨。
int i = 0;
++i = 3;
//i++ = 3;
cout << i << endl;
while (1){}
return 0;
在上面這段代碼中,輸出值是3,這是因爲++i = 3這句代碼將3賦值給了對象i 。但是註釋的部分i++ = 3的語句是不合法的,因爲i++返回的是右值鴨。
假設p是int型的指針。則*p++的意義是:先保存p的副本,然後對p加一,然後對未修改的副本執行解引用操作。這是因爲遞增運算符的優先級高於解引用運算符。
六:成員訪問運算符
p->i 等於 (*p).i 。
七:位運算符
對char類型執行位運算會提升爲int類型。
八:sizeof運算符
char i;
char *p = &i;
char &r = i;
cout << sizeof(char) << endl;
cout << sizeof i << endl;
cout << sizeof p << endl;
cout << sizeof *p << endl;
cout << sizeof r << endl;
這段代碼的輸出如下所示:
1
1
4
1
1
對於類型,sizeof運算符要用括號,對於表達式不需要括號。且可以看出,指針類型佔用四個字節,很厲害的。
九:類型轉換
unsigned int ui = 10;
int i = -11;
unsigned i_ui = i;
cout << i_ui << endl;
cout << ui + i << endl;
對於上面的代碼,輸出如下:
4294967285
4294967295
可以得出,當運算對象一個是無符號類型,一個是帶符號類型且無符號類型不小於帶符號類型時候,先將帶符號類型轉換成無符號類型再運算。
強制類型轉換的表達式爲cast_name<type>(expression);其中type爲要轉換的目標類型,expression爲轉換的表達式,cast_name有四種形式:static_cast, const_cast,reinterpret_cast和dynamic_cast;static_cast是最普通的類型轉換,將一種類型轉換爲另一種類型,例如下面這段代碼輸出0.8
int a = 4, b = 5;
double d = static_cast<double>(a) / static_cast<double>(b);
cout << d << endl;
const_cast改變運算對象的底層const,如下面的代碼所示,將const char*轉換成char*,const_cast不能像static_cast那樣改變表達式類型。
char ch = '胖';
const char *p1 = &ch;
char *p2 = const_cast<char*>(p1);