表達式
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).
基本概念
分類
根據運算對象數量可分為一元運算符 unary operator德崭、二元運算符binary operator等绊序,函數調用也是一種特殊的運算符。
類型轉換
在不同類型對象進行運算時,常常會發(fā)生類型轉換达布。如小整數類型一般會被 提升 promote 成較大整數類型历谍。
重載運算符 overloaded operator
當用戶對類類型的對象進行運算時,可以自定運算符及其含義,稱為 重載運算符 overloaded operator 爆班。IO庫zhongde'>>'和'<<',以及vector和string對象中的許多運算符都是重載的運算符辱姨。
左右值
C++表達式可以分為:
- lvalue and rvalue
- glvalue and prvalue
其中:
lvalue+xvalue=glvalue柿菩;prvalue+xvlaue=rvalue
簡單來說,左值用對象的身份雨涛,右值用對象的數值枢舶。
詳見 https://en.cppreference.com/w/cpp/language/value_category
優(yōu)先級 precedence、結合律 associativity替久、求值順序 order of evaluation
優(yōu)先級和結合律
https://en.cppreference.com/w/cpp/language/operator_precedence
求值順序
參見:https://en.cppreference.com/w/cpp/language/eval_order
若某個語句沒有指定執(zhí)行順序凉泄,但表達式指向并修改了同一個對象,則無法明確何時蚯根、如何對該對象求值后众,會引發(fā)錯誤產生未定義的行為 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
算術運算符 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 運算符
//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
邏輯運算符都是從左往右求值稼锅,直到無法確定后再從右開始吼具。
-
||
當且僅當左側為假才向右計算 -
&&
當且僅當左側為真才向右計算 - 使用以上規(guī)則可以規(guī)避某些溢出,如:
該式若改變index != s.size() && !isspace(s[index])
&&
兩端順序矩距,則isspace
首先因下標越界而出錯拗盒。
賦值運算符 assignment operator
賦值運算符左側對象必須是一個可修改的左值。結果是左側運算對象锥债,且是一個左值陡蝇。
a = b
a += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b
右結合律
int a1, a2;
a1 = a2 = 0; //正確,從右往左
int a3, *a4;
a3 = a4 = 0; //錯誤哮肚,類型不同且無法轉換
復合賦值運算符
假設算術運算符形如 X
登夫,則:
a = a X b;
等價于 a X= b;
,而左邊求值兩次允趟,右邊一次恼策。
遞增/遞減運算符 increment/decrement operator
- 前置版本
++i
:首先將運算對象加1,然后將改變后的對象作為結果繼續(xù)運算潮剪。更廣泛使用涣楷,能避免不必要的語句,如for循環(huán)中會少進行一次判斷抗碰。 - 后置版本
i++
: 將運算對象加1狮斗,但求值結果是運算對象改變之前的值的副本。較少使用弧蝇,需要先儲存副本碳褒,增加了不必要的運算折砸。
后置自增和解引用混用
*p++
是一種常用的避免無法輸出首字母的表達式。由于遞增遞減的優(yōu)先級高于解引用沙峻,該式等價于*(p++)
睦授。
auto* p = v.begin();
while (p != v.end())
cout<< *p++ <<endl;
用前置自增改寫會增加代碼而變得不簡潔:
auto* p = v.begin();
while (p != v.end()) {
cout<< *p <<endl;
++p;
}
使用遞增遞減應避免未定義行為
成員訪問運算符 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
等價于(*a).b
而不是*a.b
。后者相當于a本身是一個指針专酗,試圖訪問該指針的b成員睹逃,自然出錯盗扇。
條件運算符
cond ? expr1 : expr2
:先求cond的值祷肯,若真則對expr1求值并返回,否則對expr2求值并返回疗隶。
條件運算符嵌套
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等語句佑笋,由于會逐漸向后計算,使用該方式可減少判斷條件的長度斑鼻;但由于前式都需要計算蒋纬,可能會略微減慢。
位運算符
用于整數類型的運算對象并將其視為二進制位的集合坚弱,并提供檢查和設置二進制位的功能蜀备。位運算符同樣可用于bitset
的標準庫類型。
如果運算對象類型是小整型荒叶,會被自動提升promote成較大整型碾阁。此外,由于不同編譯器位運算對最前面符號位的處理方式不同些楣,可能出現未定義undefined結果脂凶,因此避免為有符號數進行位運算。
~a
a & b
a | b
a ^ b
a << b
a >> b
-
若是短類型愁茁,先進行提升:短類型前補滿0轉為長類型蚕钦;
- 無論是否帶符號,提升后默認是有符號值鹅很。
-
若超限嘶居,則:
- 超上限:減去容量(char: 256=0400);
- 超下限:加上容量促煮。
無符號數按位取反:容量減去當前值邮屁;
-
有符號數按位取反(提升并使容量合法時):
- 所有位按位取反;
- 若取反后符號位是1污茵,顯示的值是再(對絕對值)按位取反并+1樱报。相當于 ~正->負:絕對值 +1 變符號;
- 若取反后符號位是0泞当,顯示的值是再(對絕對值)按位取反并-1迹蛤。相當于 ~負->正:絕對值 -1 變符號。
//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_;
不超限的短類型負數:
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;
//三變量結果相等
負號和按位取反的區(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
}
負號返回值增加了一個取補碼的過程,在表達式中的結果同樣也是提符號-取補碼作為絕對值-進行輸出盗飒。而按位操作單純對位操作嚷量,不取補碼。二者單獨使用均可逆逆趣,但混用時注意流向蝶溶。
移位運算符(io運算符)滿足左結合律。
sizeof運算符
返回的是某表達式或類型名所占的字節(jié)數宣渗。該運算符滿足右結合律抖所,所得的值是一個size_t類型的常量表達式。sizeof
并不實際計算表達式的值而是直接返回字節(jié)數痕囱。運算符的運算對象形式如下:
sizeof (type);
sizeof expr;
sizeof *p
比較特殊田轧。首先,由于sizeof()
滿足右結合律并且優(yōu)先級等于*
運算符鞍恢,按照從右向左的順序組合傻粘,等價于sizeof(*p)
。此外帮掉,由于不會實際求運算對象的值弦悉,即使是無效指針也可以安全獲取結果。
一些規(guī)則:
-
sizeof(char)
: 1 - 對string或vector執(zhí)行sizeof只返回該類型固定部分的大小而不是對象中元素占了多少空間蟆炊。
- 若
ia
是個數組稽莉,可以使用sizeof(ia)/sizeof(ia[0])
或者sizeof(ia)/sizeof(*ia)
得到數組大小。- 注意不要使用指向數組的指針:
sizeof(p)/sizeof(*p)
- 注意不要使用指向數組的指針:
-
sizeof
的返回值是一個常量表達式盅称,因此可以用該結果聲明數組維度肩祥。
逗號運算符 comma operator
- 用在for循環(huán)中,實現多個變量的自增/自減
- 用在同一基本類型的連續(xù)賦值中
類型轉換
如果兩種類型有關聯(lián)缩膝,則當程序需要其中一種類型的運算對象時混狠,可以用另一種關聯(lián)類型的對象或值來替代:若兩種類型可以相互轉換conversion,則二者就是關聯(lián)的疾层。
隱式轉換 implicit conversion:自動執(zhí)行将饺,無需人為介入。如下列情況:
- 比int小的整型提升時痛黎;
- 非布爾轉為布爾予弧;
- 初始化的初始值轉為變量的類型;賦值中右側類型轉為左側類型
- 多種類型進行混合算術運算湖饱;
- 函數調用時的類型轉換掖蛤。
算數轉換 arithmetic conversion
把一種算術類型轉換成另一種算術類型。運算符的運算對象將轉換成最寬的類型井厌。例如有整型和浮點就都換為浮點蚓庭;含有l(wèi)ong double致讥,則都換為long double。
整型提升integral promotion
- 對于
bool, char, signed char, unsigned char, short, unsigned short
器赞,只要它們所有可能的值都能存在int里垢袱,它們就會提升成int類型,否則提升成unsigned int類型港柜。 - 對于
wchar_t, char16_t, char32_t
请契,提升為能夠容納它們的int, unsigned int, long, unsigned long, long long, unsigned long long
中最小的一種類型。
無符號類型
在進行整型提升后夏醉,再決定是否是帶符號的類型爽锥。
具體規(guī)則如下:
- 若只同時存在有符號或同時存在無符號,則轉為容量較大的類型(無論正負)授舟。
-
若無符號類型 >= 有符號類型救恨,均轉為無符號類型:
signed char a = -10; unsigned int b = 1; unsigned long c = 3.0; auto d = a+b+c; //unsigned long
-
若有符號類型 > 無符號類型:
- 若無符號類型的所有值都可以存在有符號類型中,則轉為帶符號類型
- 若不能释树,則轉為無符號類型
- 主要依賴于系統(tǒng)或編譯器對類型大小的定義。
其他隱式類型轉換
https://en.cppreference.com/w/cpp/language/implicit_conversion
特殊類型
某些特殊類型如size_t, size_type, ptrdiff_t, wchar_t
都可以隱式轉換為相應的合理類型(主要由操作系統(tǒng)和編譯器決定)擎淤。為了程序的可移植性奢啥,應多使用這些類型嘴拢。
數組轉為指針
int ia[10];
int* ip = ia; //右邊隱式轉換為指向首元素的指針
int* ip = std::begin(ia);
上述轉換不會在decltype, &, sizeof, typeid
中發(fā)生桩盲。用引用初始化數組也不會發(fā)生。
指針的轉換
- 常量整數值的指針或者nullptr能轉為任意指針類型席吴;
- 指向任意非常量的指針能轉換為void*赌结;
- 指向任意對象的指針能轉換成const void*。
- 再有繼承關系的類中還有其他轉換方式孝冒。
指向常量的指針
允許將指向非常量類型的指針轉換成指向相應常量類型的指針(增加底層const)柬姚,反之由于試圖刪除底層const,無法實現庄涡。
int a = 1;
const int& ra = a;
const int* pa = &a;
int& rr = ra; //錯誤
int* pp = pa; //錯誤
int* const ppc = pa; //錯誤量承,沒有底層,頂層自然不一致
類類型定義的轉換
顯式轉換
強制類型轉換 cast
具體參見:
https://en.cppreference.com/w/cpp/language/explicit_cast
命名的強制類型轉換
形式如:
cast-name<type>(expression)
- type:目標類型穴店。若是引用類型撕捍,則結果是左值。
- expression:要轉換的表達式
- cast_name:包括
static_cast, dynamic_cast, const_cast, reinterpret_cast
指定了轉換方式泣洞。
注意:不可用于左值忧风,使用cast后的值進行初始化等操作時可與auto配合使用以減少類型名的重復編寫。
static_cast
任何具有明確定義的類型轉換球凰,只要不包含底層const就可以使用狮腿。
static_cast不能轉換掉expression的const该窗、volatile、或者__unaligned屬性蚤霞。
int a = 1;
float b = static_cast<float>(a);
//兩種舊式轉換方式
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可以直接轉換
const_cast
只能改變運算對象的底層const酗失,這種行為稱為 去掉const性質 | cast away the const。注意必須具有底層const昧绣。若對象是個常量(具有頂層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通常為運算對象的位模式提供較低層次上的重新解釋规肴,即以二進制存在形式的重新解釋
可將int*
轉為char*
等。使用此強制轉換較為危險夜畴。
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
舊型強制轉換
type(expr) //函數形式
(type)expr //C語言風格
根據涉及類型不同卒煞,可以具有與 const_cast, static_cast, reinterpret_cast
任意一種相似的行為。若替換為const_cast
和static_cast
合法灼卢,則行為與命名一致练对,否則執(zhí)行的是reinterpret_cast
。