4 表達(dá)式 | Expression

表達(dá)式

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).

基本概念

分類

根據(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=glvalueprvalue+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。

  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
    

算術(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)
  • 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_caststatic_cast合法,則行為與命名一致,否則執(zhí)行的是reinterpret_cast。

運(yùn)算符優(yōu)先級(jí)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • C++ lambda表達(dá)式與函數(shù)對(duì)象 lambda表達(dá)式是C++11中引入的一項(xiàng)新技術(shù),利用lambda表達(dá)式可以...
    小白將閱讀 85,672評(píng)論 15 117
  • 本文按照 cppreference[https://en.cppreference.com/w/] 列出的特性列表...
    401閱讀 22,219評(píng)論 2 18
  • C++基礎(chǔ) (1)C和C++的區(qū)別 C++分為: C部分(區(qū)塊、語句、預(yù)處理器、內(nèi)置數(shù)據(jù)類型、數(shù)組、指針等); 面...
    iyytdeed閱讀 735評(píng)論 0 0
  • #1.基礎(chǔ)1.1 基本概念1.2 優(yōu)先級(jí)和結(jié)合律1.3 求值順序 #2.算術(shù)運(yùn)算符 #3.邏輯和關(guān)系運(yùn)算符 #4....
    MrDecoder閱讀 401評(píng)論 0 0
  • 第4章:表達(dá)式 4.1 基礎(chǔ) 基本內(nèi)容: 一元運(yùn)算符、二元運(yùn)算符、三元運(yùn)算符、函數(shù)調(diào)用(特殊的運(yùn)算符)。 運(yùn)算符的...
    北冥有魚wyh閱讀 123評(píng)論 0 1

友情鏈接更多精彩內(nèi)容