4 表達式 | Expression

表達式

REF

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

  1. 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
    
  2. 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_caststatic_cast合法灼卢,則行為與命名一致练对,否則執(zhí)行的是reinterpret_cast

運算符優(yōu)先級

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末税灌,一起剝皮案震驚了整個濱河市均函,隨后出現的幾起案子,更是在濱河造成了極大的恐慌菱涤,老刑警劉巖苞也,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異粘秆,居然都是意外死亡如迟,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門攻走,熙熙樓的掌柜王于貴愁眉苦臉地迎上來殷勘,“玉大人,你說我怎么就攤上這事昔搂×嵯” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵巩趁,是天一觀的道長痒玩。 經常有香客問我,道長议慰,這世上最難降的妖魔是什么蠢古? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮别凹,結果婚禮上草讶,老公的妹妹穿的比我還像新娘。我一直安慰自己炉菲,他們只是感情好堕战,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布坤溃。 她就那樣靜靜地躺著,像睡著了一般嘱丢。 火紅的嫁衣襯著肌膚如雪薪介。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天越驻,我揣著相機與錄音汁政,去河邊找鬼。 笑死缀旁,一個胖子當著我的面吹牛记劈,可吹牛的內容都是我干的。 我是一名探鬼主播并巍,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼目木,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了懊渡?” 一聲冷哼從身側響起刽射,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎距贷,沒想到半個月后柄冲,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡忠蝗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了漓拾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阁最。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖骇两,靈堂內的尸體忽然破棺而出速种,到底是詐尸還是另有隱情,我是刑警寧澤低千,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布配阵,位于F島的核電站,受9級特大地震影響示血,放射性物質發(fā)生泄漏棋傍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一难审、第九天 我趴在偏房一處隱蔽的房頂上張望瘫拣。 院中可真熱鬧,春花似錦告喊、人聲如沸麸拄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拢切。三九已至蒂萎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間淮椰,已是汗流浹背五慈。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留实苞,地道東北人豺撑。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像黔牵,于是被迫代替她去往敵國和親聪轿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內容

  • C++ lambda表達式與函數對象 lambda表達式是C++11中引入的一項新技術猾浦,利用lambda表達式可以...
    小白將閱讀 85,247評論 15 118
  • 本文按照 cppreference[https://en.cppreference.com/w/] 列出的特性列表...
    401閱讀 21,059評論 2 18
  • C++基礎 (1)C和C++的區(qū)別 C++分為: C部分(區(qū)塊陆错、語句、預處理器金赦、內置數據類型音瓷、數組、指針等)夹抗; 面...
    iyytdeed閱讀 553評論 0 0
  • #1.基礎1.1 基本概念1.2 優(yōu)先級和結合律1.3 求值順序 #2.算術運算符 #3.邏輯和關系運算符 #4....
    MrDecoder閱讀 328評論 0 0
  • 第4章:表達式 4.1 基礎 基本內容: 一元運算符绳慎、二元運算符、三元運算符漠烧、函數調用(特殊的運算符)杏愤。 運算符的...
    北冥有魚wyh閱讀 92評論 0 1