表達(dá)式
An expression is a sequence of operators and their operands, that specifies a computation.
Expression evaluation may produce a result (e.g., evaluation of 2+2 produces the result 4) and may generate side-effects (e.g. evaluation of std::printf("%d",4) prints the character '4' on the standard output).
基本概念
分類
根據(jù)運(yùn)算對(duì)象數(shù)量可分為一元運(yùn)算符 unary operator、二元運(yùn)算符binary operator等,函數(shù)調(diào)用也是一種特殊的運(yùn)算符。
類型轉(zhuǎn)換
在不同類型對(duì)象進(jìn)行運(yùn)算時(shí),常常會(huì)發(fā)生類型轉(zhuǎn)換。如小整數(shù)類型一般會(huì)被 提升 promote 成較大整數(shù)類型。
重載運(yùn)算符 overloaded operator
當(dāng)用戶對(duì)類類型的對(duì)象進(jìn)行運(yùn)算時(shí),可以自定運(yùn)算符及其含義,稱為 重載運(yùn)算符 overloaded operator 。IO庫zhongde'>>'和'<<',以及vector和string對(duì)象中的許多運(yùn)算符都是重載的運(yùn)算符。
左右值
C++表達(dá)式可以分為:
- lvalue and rvalue
- glvalue and prvalue
其中:
lvalue+xvalue=glvalue;prvalue+xvlaue=rvalue
簡單來說,左值用對(duì)象的身份,右值用對(duì)象的數(shù)值。
詳見 https://en.cppreference.com/w/cpp/language/value_category
優(yōu)先級(jí) precedence、結(jié)合律 associativity、求值順序 order of evaluation
優(yōu)先級(jí)和結(jié)合律
https://en.cppreference.com/w/cpp/language/operator_precedence
求值順序
參見:https://en.cppreference.com/w/cpp/language/eval_order
若某個(gè)語句沒有指定執(zhí)行順序,但表達(dá)式指向并修改了同一個(gè)對(duì)象,則無法明確何時(shí)、如何對(duì)該對(duì)象求值,會(huì)引發(fā)錯(cuò)誤產(chǎn)生未定義的行為 undefined behavior。
- If a side effect on a scalar object is unsequenced relative to another side effect on the same scalar object, the behavior is undefined.
i = ++i + 2; // undefined behavior until C++11 i = i++ + 2; // undefined behavior until C++17 f(i = -2, i = -2); // undefined behavior until C++17 f(++i, ++i); // undefined behavior until C++17, unspecified after C++17 i = ++i + i++; // undefined behavior - If a side effect on a scalar object is unsequenced relative to a value computation using the value of the same scalar object, the behavior is undefined.
cout << i << i++; // undefined behavior until C++17 a[i] = i++; // undefined behavior until C++17 n = ++i + i; // undefined behavior
算術(shù)運(yùn)算符 arithmetic operators
+a
-a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b
邏輯logical 和 比較comparison 運(yùn)算符
//logical operator
!a
a && b
a || b
//comparison operator
a == b
a != b
a < b
a > b
a <= b
a >= b
a <=> b
短路求值 short-circuit evaluation
邏輯運(yùn)算符都是從左往右求值,直到無法確定后再從右開始。
-
||當(dāng)且僅當(dāng)左側(cè)為假才向右計(jì)算 -
&&當(dāng)且僅當(dāng)左側(cè)為真才向右計(jì)算 - 使用以上規(guī)則可以規(guī)避某些溢出,如:
該式若改變index != s.size() && !isspace(s[index])&&兩端順序,則isspace首先因下標(biāo)越界而出錯(cuò)。
賦值運(yùn)算符 assignment operator
賦值運(yùn)算符左側(cè)對(duì)象必須是一個(gè)可修改的左值。結(jié)果是左側(cè)運(yùn)算對(duì)象,且是一個(gè)左值。
a = b
a += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b
右結(jié)合律
int a1, a2;
a1 = a2 = 0; //正確,從右往左
int a3, *a4;
a3 = a4 = 0; //錯(cuò)誤,類型不同且無法轉(zhuǎn)換
復(fù)合賦值運(yùn)算符
假設(shè)算術(shù)運(yùn)算符形如 X ,則:
a = a X b; 等價(jià)于 a X= b;,而左邊求值兩次,右邊一次。
遞增/遞減運(yùn)算符 increment/decrement operator
- 前置版本
++i:首先將運(yùn)算對(duì)象加1,然后將改變后的對(duì)象作為結(jié)果繼續(xù)運(yùn)算。更廣泛使用,能避免不必要的語句,如for循環(huán)中會(huì)少進(jìn)行一次判斷。 - 后置版本
i++: 將運(yùn)算對(duì)象加1,但求值結(jié)果是運(yùn)算對(duì)象改變之前的值的副本。較少使用,需要先儲(chǔ)存副本,增加了不必要的運(yùn)算。
后置自增和解引用混用
*p++ 是一種常用的避免無法輸出首字母的表達(dá)式。由于遞增遞減的優(yōu)先級(jí)高于解引用,該式等價(jià)于*(p++)。
auto* p = v.begin();
while (p != v.end())
cout<< *p++ <<endl;
用前置自增改寫會(huì)增加代碼而變得不簡潔:
auto* p = v.begin();
while (p != v.end()) {
cout<< *p <<endl;
++p;
}
使用遞增遞減應(yīng)避免未定義行為
成員訪問運(yùn)算符 member access operator
a[b]
*a
&a
a->b
a.b
a->*b
a.*b
https://en.cppreference.com/w/cpp/language/operator_precedence
a->b等價(jià)于(*a).b而不是*a.b。后者相當(dāng)于a本身是一個(gè)指針,試圖訪問該指針的b成員,自然出錯(cuò)。
條件運(yùn)算符
cond ? expr1 : expr2 :先求cond的值,若真則對(duì)expr1求值并返回,否則對(duì)expr2求值并返回。
條件運(yùn)算符嵌套
const int A = 90;
const int B = 80;
const int C = 70;
const int D = 60;
int main(){
int grade = 0;
cin>>grade;
string str_grade = (grade>=A)? "A" :
(grade>=B)? "B" :
(grade>=C)? "C" :
(grade>=D)? "D" : "F";
cout<<str_grade<<endl;
}
相較于switch和if等語句,由于會(huì)逐漸向后計(jì)算,使用該方式可減少判斷條件的長度;但由于前式都需要計(jì)算,可能會(huì)略微減慢。
位運(yùn)算符
用于整數(shù)類型的運(yùn)算對(duì)象并將其視為二進(jìn)制位的集合,并提供檢查和設(shè)置二進(jìn)制位的功能。位運(yùn)算符同樣可用于bitset的標(biāo)準(zhǔn)庫類型。
如果運(yùn)算對(duì)象類型是小整型,會(huì)被自動(dòng)提升promote成較大整型。此外,由于不同編譯器位運(yùn)算對(duì)最前面符號(hào)位的處理方式不同,可能出現(xiàn)未定義undefined結(jié)果,因此避免為有符號(hào)數(shù)進(jìn)行位運(yùn)算。
~a
a & b
a | b
a ^ b
a << b
a >> b
-
若是短類型,先進(jìn)行提升:短類型前補(bǔ)滿0轉(zhuǎn)為長類型;
- 無論是否帶符號(hào),提升后默認(rèn)是有符號(hào)值。
-
若超限,則:
- 超上限:減去容量(char: 256=0400);
- 超下限:加上容量。
無符號(hào)數(shù)按位取反:容量減去當(dāng)前值;
-
有符號(hào)數(shù)按位取反(提升并使容量合法時(shí)):
- 所有位按位取反;
- 若取反后符號(hào)位是1,顯示的值是再(對(duì)絕對(duì)值)按位取反并+1。相當(dāng)于 ~正->負(fù):絕對(duì)值 +1 變符號(hào);
- 若取反后符號(hào)位是0,顯示的值是再(對(duì)絕對(duì)值)按位取反并-1。相當(dāng)于 ~負(fù)->正:絕對(duì)值 -1 變符號(hào)。
//char: int8_t;
//sizeof(unsigned char): 1byte=8bits; 3bit/num;
//char-max = 0B11'111'111 = 0377 = 255 = 0xFF = unsigned char -1;
unsigned char c1o = 0227;
//unsigned char 0b10'010;111 = 151
//to unsigned int 0b00000000'00000000'00000000'10010111
unsigned int i1o = 0227;
//unsigned int 0b00000000'00000000'00000000'10010111
auto c1o_(~c1o);
//int 0b11111111'11111111'11111111'01101000 =>
//int -(0b00000000'00000000'00000000'10010111+1) => int -152
auto i1o_(~i1o);
//unsigned int 0b11111111'11111111'11111111'01101000
//to unsigned int 4294967144
cout<<c1o_<<"\n"<<i1o_;
不超限的短類型負(fù)數(shù):
signed char c1o = -027;
//注意char可能為unsigned也可能為signed
int x = -0b00000000'00000000'00000000'00010111;
int xx = 0b11111111'11111111'11111111'11101001;
cout<<~c1o<<'\t'<<~xx<<'\t'<<~x<<endl;
//三變量結(jié)果相等
負(fù)號(hào)和按位取反的區(qū)別:
int main(){
int a = ~0b00000000'00000000'00000000'00000100;
//int a = ~5;
int b = 0b11111111'11111111'11111111'11111011;
//int b = -5;
int c = -0b00000000'00000000'00000000'00000101;
//int c = -5;
cout<<a<<'\t'<<~a<<endl //-5 4
<<b<<'\t'<<~b<<endl //-5 4
<<c<<'\t'<<~c<<endl; //-5 4
}
負(fù)號(hào)返回值增加了一個(gè)取補(bǔ)碼的過程,在表達(dá)式中的結(jié)果同樣也是提符號(hào)-取補(bǔ)碼作為絕對(duì)值-進(jìn)行輸出。而按位操作單純對(duì)位操作,不取補(bǔ)碼。二者單獨(dú)使用均可逆,但混用時(shí)注意流向。
移位運(yùn)算符(io運(yùn)算符)滿足左結(jié)合律。
sizeof運(yùn)算符
返回的是某表達(dá)式或類型名所占的字節(jié)數(shù)。該運(yùn)算符滿足右結(jié)合律,所得的值是一個(gè)size_t類型的常量表達(dá)式。sizeof并不實(shí)際計(jì)算表達(dá)式的值而是直接返回字節(jié)數(shù)。運(yùn)算符的運(yùn)算對(duì)象形式如下:
sizeof (type);
sizeof expr;
sizeof *p比較特殊。首先,由于sizeof()滿足右結(jié)合律并且優(yōu)先級(jí)等于*運(yùn)算符,按照從右向左的順序組合,等價(jià)于sizeof(*p)。此外,由于不會(huì)實(shí)際求運(yùn)算對(duì)象的值,即使是無效指針也可以安全獲取結(jié)果。
一些規(guī)則:
-
sizeof(char): 1 - 對(duì)string或vector執(zhí)行sizeof只返回該類型固定部分的大小而不是對(duì)象中元素占了多少空間。
- 若
ia是個(gè)數(shù)組,可以使用sizeof(ia)/sizeof(ia[0])或者sizeof(ia)/sizeof(*ia)得到數(shù)組大小。- 注意不要使用指向數(shù)組的指針:
sizeof(p)/sizeof(*p)
- 注意不要使用指向數(shù)組的指針:
-
sizeof的返回值是一個(gè)常量表達(dá)式,因此可以用該結(jié)果聲明數(shù)組維度。
逗號(hào)運(yùn)算符 comma operator
- 用在for循環(huán)中,實(shí)現(xiàn)多個(gè)變量的自增/自減
- 用在同一基本類型的連續(xù)賦值中
類型轉(zhuǎn)換
如果兩種類型有關(guān)聯(lián),則當(dāng)程序需要其中一種類型的運(yùn)算對(duì)象時(shí),可以用另一種關(guān)聯(lián)類型的對(duì)象或值來替代:若兩種類型可以相互轉(zhuǎn)換conversion,則二者就是關(guān)聯(lián)的。
隱式轉(zhuǎn)換 implicit conversion:自動(dòng)執(zhí)行,無需人為介入。如下列情況:
- 比int小的整型提升時(shí);
- 非布爾轉(zhuǎn)為布爾;
- 初始化的初始值轉(zhuǎn)為變量的類型;賦值中右側(cè)類型轉(zhuǎn)為左側(cè)類型
- 多種類型進(jìn)行混合算術(shù)運(yùn)算;
- 函數(shù)調(diào)用時(shí)的類型轉(zhuǎn)換。
算數(shù)轉(zhuǎn)換 arithmetic conversion
把一種算術(shù)類型轉(zhuǎn)換成另一種算術(shù)類型。運(yùn)算符的運(yùn)算對(duì)象將轉(zhuǎn)換成最寬的類型。例如有整型和浮點(diǎn)就都換為浮點(diǎn);含有l(wèi)ong double,則都換為long double。
整型提升integral promotion
- 對(duì)于
bool, char, signed char, unsigned char, short, unsigned short,只要它們所有可能的值都能存在int里,它們就會(huì)提升成int類型,否則提升成unsigned int類型。 - 對(duì)于
wchar_t, char16_t, char32_t,提升為能夠容納它們的int, unsigned int, long, unsigned long, long long, unsigned long long中最小的一種類型。
無符號(hào)類型
在進(jìn)行整型提升后,再?zèng)Q定是否是帶符號(hào)的類型。
具體規(guī)則如下:
- 若只同時(shí)存在有符號(hào)或同時(shí)存在無符號(hào),則轉(zhuǎn)為容量較大的類型(無論正負(fù))。
-
若無符號(hào)類型 >= 有符號(hào)類型,均轉(zhuǎn)為無符號(hào)類型:
signed char a = -10; unsigned int b = 1; unsigned long c = 3.0; auto d = a+b+c; //unsigned long -
若有符號(hào)類型 > 無符號(hào)類型:
- 若無符號(hào)類型的所有值都可以存在有符號(hào)類型中,則轉(zhuǎn)為帶符號(hào)類型
- 若不能,則轉(zhuǎn)為無符號(hào)類型
- 主要依賴于系統(tǒng)或編譯器對(duì)類型大小的定義。
其他隱式類型轉(zhuǎn)換
https://en.cppreference.com/w/cpp/language/implicit_conversion
特殊類型
某些特殊類型如size_t, size_type, ptrdiff_t, wchar_t都可以隱式轉(zhuǎn)換為相應(yīng)的合理類型(主要由操作系統(tǒng)和編譯器決定)。為了程序的可移植性,應(yīng)多使用這些類型。
數(shù)組轉(zhuǎn)為指針
int ia[10];
int* ip = ia; //右邊隱式轉(zhuǎn)換為指向首元素的指針
int* ip = std::begin(ia);
上述轉(zhuǎn)換不會(huì)在decltype, &, sizeof, typeid中發(fā)生。用引用初始化數(shù)組也不會(huì)發(fā)生。
指針的轉(zhuǎn)換
- 常量整數(shù)值的指針或者nullptr能轉(zhuǎn)為任意指針類型;
- 指向任意非常量的指針能轉(zhuǎn)換為void*;
- 指向任意對(duì)象的指針能轉(zhuǎn)換成const void*。
- 再有繼承關(guān)系的類中還有其他轉(zhuǎn)換方式。
指向常量的指針
允許將指向非常量類型的指針轉(zhuǎn)換成指向相應(yīng)常量類型的指針(增加底層const),反之由于試圖刪除底層const,無法實(shí)現(xiàn)。
int a = 1;
const int& ra = a;
const int* pa = &a;
int& rr = ra; //錯(cuò)誤
int* pp = pa; //錯(cuò)誤
int* const ppc = pa; //錯(cuò)誤,沒有底層,頂層自然不一致
類類型定義的轉(zhuǎn)換
顯式轉(zhuǎn)換
強(qiáng)制類型轉(zhuǎn)換 cast
具體參見:
https://en.cppreference.com/w/cpp/language/explicit_cast
命名的強(qiáng)制類型轉(zhuǎn)換
形式如:
cast-name<type>(expression)
- type:目標(biāo)類型。若是引用類型,則結(jié)果是左值。
- expression:要轉(zhuǎn)換的表達(dá)式
- cast_name:包括
static_cast, dynamic_cast, const_cast, reinterpret_cast指定了轉(zhuǎn)換方式。
注意:不可用于左值,使用cast后的值進(jìn)行初始化等操作時(shí)可與auto配合使用以減少類型名的重復(fù)編寫。
static_cast
任何具有明確定義的類型轉(zhuǎn)換,只要不包含底層const就可以使用。
static_cast不能轉(zhuǎn)換掉expression的const、volatile、或者_(dá)_unaligned屬性。
int a = 1;
float b = static_cast<float>(a);
//兩種舊式轉(zhuǎn)換方式
float c = float(a);
float d = (float)a;
int* const pc = &a;
int* p = static_cast<int* const>(pc);
int* pp = pc; //而且非底層const不必static_cast可以直接轉(zhuǎn)換
const_cast
只能改變運(yùn)算對(duì)象的底層const,這種行為稱為 去掉const性質(zhì) | cast away the const。注意必須具有底層const。若對(duì)象是個(gè)常量(具有頂層const)則
int a = 1;
const int* pa = &a;
int* p = const_cast<int*>(pa);
const int& ra = a;
int& r = const_cast<int&>(ra);
reinterpret_cast
reinterpret_cast通常為運(yùn)算對(duì)象的位模式提供較低層次上的重新解釋,即以二進(jìn)制存在形式的重新解釋
可將int*轉(zhuǎn)為char*等。使用此強(qiáng)制轉(zhuǎn)換較為危險(xiǎn)。
int* const pp = &b;
unsigned int* qq = reinterpret_cast<unsigned int*>(pp);
unsigned int* qq = reinterpret_cast<unsigned int* const>(pp);
https://en.cppreference.com/w/cpp/language/reinterpret_cast
dynamic_cast
https://en.cppreference.com/w/cpp/language/dynamic_cast
pointer_cast
https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast
std::static_pointer_cast, std::dynamic_pointer_cast, std::const_pointer_cast, std::reinterpret_pointer_cast
舊型強(qiáng)制轉(zhuǎn)換
type(expr) //函數(shù)形式
(type)expr //C語言風(fēng)格
根據(jù)涉及類型不同,可以具有與 const_cast, static_cast, reinterpret_cast 任意一種相似的行為。若替換為const_cast和static_cast合法,則行為與命名一致,否則執(zhí)行的是reinterpret_cast。