日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > c/c++ >内容正文

c/c++

《C++ Primer 5th》笔记(4 / 19):表达式

發(fā)布時(shí)間:2023/12/13 c/c++ 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《C++ Primer 5th》笔记(4 / 19):表达式 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

    • 基礎(chǔ)
      • 基本概念
        • 組合運(yùn)算符和運(yùn)算對(duì)象
        • 運(yùn)算對(duì)象轉(zhuǎn)換
        • 重載運(yùn)算符
        • 左值和右值
      • 優(yōu)先級(jí)與結(jié)合律
        • 括號(hào)無(wú)視優(yōu)先級(jí)與結(jié)合律
        • 優(yōu)先級(jí)與結(jié)合律有何影響
      • 求值順序
        • 求值順序、優(yōu)先級(jí)、結(jié)合律
        • 建議:處理復(fù)合表達(dá)式
    • 算術(shù)運(yùn)算符
      • 一元正負(fù)號(hào)
      • 加、減、乘、除、求余
      • 提示:溢出和其他算術(shù)運(yùn)算異常
    • 邏輯和關(guān)系運(yùn)算符
      • 邏輯與和邏輯或運(yùn)算符
      • 邏輯非運(yùn)算符
      • 關(guān)系運(yùn)算符
      • 相等性測(cè)試與布爾字面值
    • 賦值運(yùn)算符
      • 賦值運(yùn)算滿足右結(jié)合律
      • 賦值運(yùn)算優(yōu)先級(jí)較低
      • 切勿混淆相等運(yùn)算符和賦值運(yùn)算符
      • 復(fù)合賦值運(yùn)算符
    • 遞增和遞減運(yùn)算符
      • 建議:除非必須,否則不用遞增遞減運(yùn)算符的后置版本
      • 在一條語(yǔ)句中混用解引用和遞增運(yùn)算符
      • 建議:簡(jiǎn)潔可以成為一種美德
      • 運(yùn)算對(duì)象可按任意順序求值
    • 成員訪問(wèn)運(yùn)算符
    • 條件運(yùn)算符
      • 嵌套條件運(yùn)算符
      • 在輸出表達(dá)式中使用條件運(yùn)算符
    • 位運(yùn)算符
      • 移位運(yùn)算符
      • 位求反運(yùn)算符
      • 位與、位或、位異或運(yùn)算符
      • 使用位運(yùn)算符
      • 移位運(yùn)算符(又叫IO運(yùn)算符)滿足左結(jié)合律
    • sizeof運(yùn)算符
    • 逗號(hào)運(yùn)算符
    • 類型轉(zhuǎn)換
      • 何時(shí)發(fā)生隱式類型轉(zhuǎn)換
      • 算術(shù)轉(zhuǎn)換
        • 整型提升
        • 無(wú)符號(hào)類型的運(yùn)算對(duì)象
        • 理解算術(shù)轉(zhuǎn)換
      • 其他隱式顯示轉(zhuǎn)換
      • 顯示轉(zhuǎn)換
        • 命名的強(qiáng)制類型轉(zhuǎn)換
        • static_cast
        • const_cast
        • reinterpret_cast
        • 建議:避免強(qiáng)制類型轉(zhuǎn)換
        • 舊式的強(qiáng)制類型轉(zhuǎn)換
    • 運(yùn)算符優(yōu)先級(jí)表

表達(dá)式(expression)由一個(gè)或多個(gè)運(yùn)算對(duì)象(operand)組成,對(duì)表達(dá)式求值將得到一個(gè)結(jié)果(result)。

  • 字面值和變量是最簡(jiǎn)單的表達(dá)式,其結(jié)果就是字面值和變量的值。

  • 把一個(gè)運(yùn)算符( operator)和一個(gè)或多個(gè)運(yùn)算對(duì)象組合起來(lái)可以生成較復(fù)雜的表達(dá)式

基礎(chǔ)

有幾個(gè)基礎(chǔ)概念對(duì)表達(dá)式的求值過(guò)程有影響,它們涉及大多數(shù)(甚至全部)表達(dá)式。

基本概念

C++定義了一元運(yùn)算符(unary operator)和二元運(yùn)算符(binary operator)。

  • 作用于一個(gè)運(yùn)算對(duì)象的運(yùn)算符是一元運(yùn)算符,如取地址符(&)和解引用符(*);
  • 作用于兩個(gè)運(yùn)算對(duì)象的運(yùn)算符是二元運(yùn)算符,如相等運(yùn)算符(==)和乘法運(yùn)算符(*)。

除此之外,還有一個(gè)作用于三個(gè)運(yùn)算對(duì)象的三元運(yùn)算符(?:)。函數(shù)調(diào)用也是一種特殊的運(yùn)算符,它對(duì)運(yùn)算對(duì)象的數(shù)量沒(méi)有限制。

(一符多意)一些符號(hào)既能作為一元運(yùn)算符也能作為二元運(yùn)算符。以符號(hào)*為例,作為一元運(yùn)算符時(shí)執(zhí)行解引用操作,作為二元運(yùn)算符時(shí)執(zhí)行乘法操作。一個(gè)符號(hào)到底是一元運(yùn)算符還是二元運(yùn)算符由它的上下文決定。對(duì)于這類符號(hào)來(lái)說(shuō),它的兩種用法互不相干,完全可以當(dāng)成兩個(gè)不同的符號(hào)。

組合運(yùn)算符和運(yùn)算對(duì)象

對(duì)于含有多個(gè)運(yùn)算符的復(fù)雜表達(dá)式來(lái)說(shuō),要想理解它的含義首先要理解運(yùn)算符的:

  • 優(yōu)先級(jí)(precedence)
  • 結(jié)合律(associativity)
  • 運(yùn)算對(duì)象的求值順序(order of evaluation)

例如,下面這條表達(dá)式的求值結(jié)果依賴于表達(dá)式中運(yùn)算符和運(yùn)算對(duì)象的組合方式:

5 + 10 * 20 / 2;

乘法運(yùn)算符(*)是一個(gè)二元運(yùn)算符,它的運(yùn)算對(duì)象有4種可能: 10和20、10和20/2、15和20、15和20/2。下一節(jié)將介紹如何理解這樣一條表達(dá)式。

運(yùn)算對(duì)象轉(zhuǎn)換

在表達(dá)式求值的過(guò)程中,運(yùn)算對(duì)象常常由一種類型轉(zhuǎn)換成另外一種類型。

例如,盡管一般的二元運(yùn)算符都要求兩個(gè)運(yùn)算對(duì)象的類型相同,但是很多時(shí)候即使運(yùn)算對(duì)象的類型不相同也沒(méi)有關(guān)系,只要它們能被轉(zhuǎn)換成同一種類型即可。

類型轉(zhuǎn)換的規(guī)則雖然有點(diǎn)復(fù)雜,但大多數(shù)都合乎情理、容易理解。

例如,整數(shù)能轉(zhuǎn)換成浮點(diǎn)數(shù),浮點(diǎn)數(shù)也能轉(zhuǎn)換成整數(shù),但是指針不能轉(zhuǎn)換成浮點(diǎn)數(shù)。讓人稍微有點(diǎn)意外的是,小整數(shù)類型(如bool、char、short等)通常會(huì)被提升(promoted)成較大的整數(shù)類型,主要是int。接下來(lái)將會(huì)詳細(xì)介紹類型轉(zhuǎn)換的細(xì)節(jié)。

重載運(yùn)算符

C++語(yǔ)言定義了運(yùn)算符作用于內(nèi)置類型和復(fù)合類型的運(yùn)算對(duì)象時(shí)所執(zhí)行的操作。

當(dāng)運(yùn)算符作用于類類型的運(yùn)算對(duì)象時(shí),用戶可以自行定義其含義。因?yàn)檫@種自定義的過(guò)程事實(shí)上是為已存在的運(yùn)算符賦予了另外一層含義,所以稱之為重載運(yùn)算符( overloaded operator)。IO庫(kù)的>>和<<運(yùn)算符以及string對(duì)象、vector對(duì)象和迭代器使用的運(yùn)算符都是重載的運(yùn)算符。

我們使用重載運(yùn)算符時(shí),其包括運(yùn)算對(duì)象的類型和返回值的類型,都是由該運(yùn)算符定義的。但是運(yùn)算對(duì)象的個(gè)數(shù)、運(yùn)算符的優(yōu)先級(jí)和結(jié)合律都是無(wú)法改變的。

左值和右值

C++的表達(dá)式要不然是右值(rvalue,讀作“are-value"),要不然就是左值(lvalue,讀作“ell-value”)。這兩個(gè)名詞是從C語(yǔ)言繼承過(guò)來(lái)的,原本是為了幫助記憶:左值可以位于賦值語(yǔ)句的左側(cè),右值則不能。

在C++語(yǔ)言中,二者的區(qū)別就沒(méi)那么簡(jiǎn)單了。

一個(gè)左值表達(dá)式的求值結(jié)果是一個(gè)對(duì)象或者一個(gè)函數(shù),然而以常量對(duì)象為代表的某些左值實(shí)際上不能作為賦值語(yǔ)句的左側(cè)運(yùn)算對(duì)象。此外,雖然某些表達(dá)式的求值結(jié)果是對(duì)象,但它們是右值而非左值。

可以做一個(gè)簡(jiǎn)單的歸納

  • 當(dāng)一個(gè)對(duì)象被用作右值的時(shí)候,用的是對(duì)象的值(內(nèi)容);
  • 當(dāng)對(duì)象被用作左值的時(shí)候,用的是對(duì)象的身份(在內(nèi)存中的位置)。

不同的運(yùn)算符對(duì)運(yùn)算對(duì)象的要求各不相同,

  • 有的需要左值運(yùn)算對(duì)象、
  • 有的需要右值運(yùn)算對(duì)象;

返回值也有差異,

  • 有的得到左值結(jié)果、
  • 有的得到右值結(jié)果。

一個(gè)重要的原則是在需要右值的地方可以用左值來(lái)代替,但是不能把右值當(dāng)成左值(也就是位置)使用。當(dāng)一個(gè)左值被當(dāng)成右值使用時(shí),實(shí)際使用的是它的內(nèi)容(值)。到目前為止,已經(jīng)有幾種我們熟悉的運(yùn)算符是要用到左值的。(右能被左替,對(duì)換不能替)

  • 賦值運(yùn)算符需要一個(gè)(非常量nonconst)左值作為其左側(cè)運(yùn)算對(duì)象,得到的結(jié)果也仍然是一個(gè)左值。(a = b = 1)

  • 取地址符作用于一個(gè)左值運(yùn)算對(duì)象,返回一個(gè)指向該運(yùn)算對(duì)象的指針,這個(gè)指針是一個(gè)右值。(int a = 1;int *p = &a;)

  • 內(nèi)置解引用運(yùn)算符、下標(biāo)運(yùn)算符、迭代器解引用運(yùn)算符、string和 vector的下標(biāo)運(yùn)算符的求值結(jié)果都是左值。

  • 內(nèi)置類型和迭代器的遞增遞減運(yùn)算符作用于左值運(yùn)算對(duì)象,其前置版本(本書之前章節(jié)所用的形式)所得的結(jié)果也是左值。

接下來(lái)在介紹運(yùn)算符的時(shí)候,我們將會(huì)注明該運(yùn)算符的運(yùn)算對(duì)象是否必須是左值以及其求值結(jié)果是否是左值。

使用關(guān)鍵字decltype的時(shí)候,左值和右值也有所不同。如果表達(dá)式的求值結(jié)果是左值,decltype作用于該表達(dá)式(不是變量)得到一個(gè)引用類型

舉個(gè)例子,假定p的類型是int*,因?yàn)榻庖眠\(yùn)算符生成左值,所以decltype(p)的結(jié)果是int&。另一方面,因?yàn)槿〉刂愤\(yùn)算符生成右值,所以 decltype (&p)的結(jié)果是int*,也就是說(shuō),結(jié)果是一個(gè)指向整型指針的指針。

優(yōu)先級(jí)與結(jié)合律

復(fù)合表達(dá)式(compound expression)是指含有兩個(gè)或多個(gè)運(yùn)算符的表達(dá)式。求復(fù)合表達(dá)式的值需要首先將運(yùn)算符和運(yùn)算對(duì)象合理地組合在一起,優(yōu)先級(jí)結(jié)合律決定了運(yùn)算對(duì)象組合的方式。也就是說(shuō),它們決定了表達(dá)式中每個(gè)運(yùn)算符對(duì)應(yīng)的運(yùn)算對(duì)象來(lái)自表達(dá)式的哪一部分。表達(dá)式中的括號(hào)無(wú)視上述規(guī)則,程序員可以使用括號(hào)將表達(dá)式的某個(gè)局部括起來(lái)使其得到優(yōu)先運(yùn)算。

一般來(lái)說(shuō),表達(dá)式最終的值依賴于其子表達(dá)式的組合方式。高優(yōu)先級(jí)運(yùn)算符的運(yùn)算對(duì)象要比低優(yōu)先級(jí)運(yùn)算符的運(yùn)算對(duì)象更為緊密地組合在一起。如果優(yōu)先級(jí)相同,則其組合規(guī)則由結(jié)合律確定。例如,乘法和除法的優(yōu)先級(jí)相同且都高于加法的優(yōu)先級(jí)。因此,乘法和除法的運(yùn)算對(duì)象會(huì)首先組合在一起,然后才能輪到加法和減法的運(yùn)算對(duì)象。算術(shù)運(yùn)算符滿足左結(jié)合律,意味著如果運(yùn)算符的優(yōu)先級(jí)相同,將按照從左向右的順序組合運(yùn)算對(duì)象:

  • 根據(jù)運(yùn)算符的優(yōu)先級(jí),表達(dá)式3+4*5的值是23,不是35。

  • 根據(jù)運(yùn)算符的結(jié)合律,表達(dá)式20-15-3的值是2,不是8。

舉一個(gè)稍微復(fù)雜一點(diǎn)的例子,如果完全按照從左向右的順序求值,下面的表達(dá)式將得到20:

6 + 3 * 4 / 2 + 2

也有一些人會(huì)計(jì)算得到9、14或者36,然而在C++語(yǔ)言中真實(shí)的計(jì)算結(jié)果應(yīng)該是14。這是因?yàn)檫@條表達(dá)式事實(shí)上與下述表達(dá)式等價(jià):

// parentheses in this expression match default precedence and associativity ((6 + ((3 * 4) / 2)) + 2)

(Note:如果優(yōu)先級(jí)相同,則其組合規(guī)則由結(jié)合律確定。

(Note:左結(jié)合律 -> 按照從左向右的順序組合運(yùn)算對(duì)象)

括號(hào)無(wú)視優(yōu)先級(jí)與結(jié)合律

括號(hào)無(wú)視普通的組合規(guī)則,表達(dá)式中括號(hào)括起來(lái)的部分被當(dāng)成一個(gè)單元來(lái)求值,然后再與其他部分一起按照優(yōu)先級(jí)組合。例如,對(duì)上面這條表達(dá)式按照不同方式加上括號(hào)就能得到4種不同的結(jié)果:

// parentheses result in alternative groupings cout << (6 + 3) * (4 / 2 + 2) << endl; // prints 36 cout << ((6 + 3) * 4) / 2 + 2 << endl; // prints 20 cout << 6 + 3 * 4 / (2 + 2) << endl; // prints 9

優(yōu)先級(jí)與結(jié)合律有何影響

由前面的例子可以看出,優(yōu)先級(jí)會(huì)影響程序的正確性,這一點(diǎn)在介紹的解引用和指針運(yùn)算中也有所體現(xiàn):

int ia[] = {0,2,4,6,8}; // array with five elements of type int int last = *(ia + 4); // initializes last to 8, the value of ia [4] last = *ia + 4; // last = 4, equivalent to ia [0] + 4

如果想訪問(wèn)ia+4位置的元素,那么加法運(yùn)算兩端的括號(hào)必不可少。一旦去掉這對(duì)括號(hào),*ia就會(huì)首先組合在一起,然后4再與*ia的值相加。

結(jié)合律對(duì)表達(dá)式產(chǎn)生影響的一個(gè)典型示例是輸入輸出運(yùn)算,將要介紹IO相關(guān)的運(yùn)算符滿足左結(jié)合律。這一規(guī)則意味著我們可以把幾個(gè)IO運(yùn)算組合在一條表達(dá)式當(dāng)中:

cin >> v1 >> v2; // read into v1 and then into v2

最后部分會(huì)羅列出了全部的運(yùn)算符,并用雙橫線將它們分割成若干組。

同一組內(nèi)的運(yùn)算符優(yōu)先級(jí)相同,組的位置越靠前組內(nèi)的運(yùn)算符優(yōu)先級(jí)越高。例如,前置遞增運(yùn)算符和解引用運(yùn)算符的優(yōu)先級(jí)相同并且都比算術(shù)運(yùn)算符的優(yōu)先級(jí)高。

求值順序

優(yōu)先級(jí)規(guī)定了運(yùn)算對(duì)象的組合方式,但是沒(méi)有說(shuō)明運(yùn)算對(duì)象按照什么順序求值。在大多數(shù)情況下,不會(huì)明確指定求值的順序。對(duì)于如下的表達(dá)式:

int i = f1() * f2();

我們知道f1和f2一定會(huì)在執(zhí)行乘法之前被調(diào)用,因?yàn)楫吘瓜喑说氖沁@兩個(gè)函數(shù)的返回值。但是我們無(wú)法知道到底f1在f2之前調(diào)用還是f2在f1之前調(diào)用

對(duì)于那些沒(méi)有指定執(zhí)行順序的運(yùn)算符來(lái)說(shuō),如果表達(dá)式指向并修改了同一個(gè)對(duì)象,將會(huì)引發(fā)錯(cuò)誤并產(chǎn)生未定義的行為。舉個(gè)簡(jiǎn)單的例子,<<運(yùn)算符沒(méi)有明確規(guī)定何時(shí)以及如何對(duì)運(yùn)算對(duì)象求值,因此下面的輸出表達(dá)式是未定義的:

int i = 0; cout << i << " " << ++i << endl; // undefined

因?yàn)槌绦蚴俏炊x的,所以我們無(wú)法推斷它的行為。

  • 編譯器可能先求++i的值再求i的值,此時(shí)輸出結(jié)果是1 1;

  • 也可能先求i的值再求++i的值,輸出結(jié)果是0 1;

甚至編譯器還可能做完全不同的操作。因?yàn)榇吮磉_(dá)式的行為不可預(yù)知,因此不論編譯器生成什么樣的代碼程序都是錯(cuò)誤的。

有4種運(yùn)算符明確規(guī)定了運(yùn)算對(duì)象的求值順序

  • 邏輯與(&&)運(yùn)算符,它規(guī)定先求左側(cè)運(yùn)算對(duì)象的值,只有當(dāng)左側(cè)運(yùn)算對(duì)象的值為真時(shí)才繼續(xù)求右側(cè)運(yùn)算對(duì)象的值。
  • 邏輯或(|| )運(yùn)算符、
  • 條件(? :)運(yùn)算符
  • 逗號(hào)(,)運(yùn)算符。
  • 求值順序、優(yōu)先級(jí)、結(jié)合律

    運(yùn)算對(duì)象的求值順序與優(yōu)先級(jí)和結(jié)合律無(wú)關(guān),在一條形如f( )+g( ) *h()+j()的表達(dá)式中:

    • 優(yōu)先級(jí)規(guī)定,g()的返回值和h ()的返回值相乘。
    • 結(jié)合律規(guī)定,f()的返回值先與g ()和 h ()的乘積相加,所得結(jié)果再與j()的返回值相加。
    • 對(duì)于這些函數(shù)的調(diào)用順序沒(méi)有明確規(guī)定。

    如果f、g、h和j是無(wú)關(guān)函數(shù)independent functions ,它們既不會(huì)改變同一對(duì)象的狀態(tài)也不執(zhí)行IO任務(wù),那么函數(shù)的調(diào)用順序不受限制。反之,如果其中某幾個(gè)函數(shù)影響同一對(duì)象,則它是一條錯(cuò)誤的表達(dá)式,將產(chǎn)生未定義的行為。

    建議:處理復(fù)合表達(dá)式

    以下兩條經(jīng)驗(yàn)準(zhǔn)則對(duì)書寫復(fù)合表達(dá)式有益:

  • 拿不準(zhǔn)的時(shí)候最好用括號(hào)來(lái)強(qiáng)制讓表達(dá)式的組合關(guān)系符合程序邏輯的要求。
  • 如果改變了某個(gè)運(yùn)算對(duì)象的值,在表達(dá)式的其他地方不要再使用這個(gè)運(yùn)算對(duì)象。
  • 第2條規(guī)則有一個(gè)重要例外,當(dāng)改變運(yùn)算對(duì)象的子表達(dá)式本身就是另外一個(gè)子表達(dá)式的運(yùn)算對(duì)象時(shí)該規(guī)則無(wú)效。

    例如,在表達(dá)式*++iter中,遞增運(yùn)算符改變 iter的值,iter(已經(jīng)改變)的值又是解引用運(yùn)算符的運(yùn)算對(duì)象。此時(shí)(或類似的情況下),求值的順序不會(huì)成為問(wèn)題,因?yàn)檫f增運(yùn)算(即改變運(yùn)算對(duì)象的子表達(dá)式)必須先求值,然后才輪到解引用運(yùn)算。顯然,這是一種很常見的用法,不會(huì)造成什么問(wèn)題。

    算術(shù)運(yùn)算符

    左結(jié)合律

    運(yùn)算符功能用法
    +
    -
    一元正號(hào)
    一元負(fù)號(hào)
    + expr
    - expr
    *
    /
    %
    乘法
    除法
    求余
    expr * expr
    expr / expr
    expr % expr
    +
    -
    加法
    減法
    expr + expr
    expr - expr

    上表(以及后面章節(jié)的運(yùn)算符表)按照運(yùn)算符的優(yōu)先級(jí)將其分組。

    一元運(yùn)算符的優(yōu)先級(jí)最高,接下來(lái)是乘法和除法,優(yōu)先級(jí)最低的是加法和減法。優(yōu)先級(jí)高的運(yùn)算符比優(yōu)先級(jí)低的運(yùn)算符組合得更緊密。上面的所有運(yùn)算符都滿足左結(jié)合律,意味著當(dāng)優(yōu)先級(jí)相同時(shí)按照從左向右的順序進(jìn)行組合。

    除非另做特殊說(shuō)明,算術(shù)運(yùn)算符都能作用于任意算術(shù)類型以及任意能轉(zhuǎn)換為算術(shù)類型的類型。

    算術(shù)運(yùn)算符的運(yùn)算對(duì)象和求值結(jié)果都是右值。(當(dāng)一個(gè)對(duì)象被用作右值的時(shí)候,用的是對(duì)象的值(內(nèi)容);)

    在表達(dá)式求值之前,小整數(shù)類型的運(yùn)算對(duì)象被提升成較大的整數(shù)類型,所有運(yùn)算對(duì)象最終會(huì)轉(zhuǎn)換成同一類型。

    一元正負(fù)號(hào)

    一元正號(hào)運(yùn)算符、加法運(yùn)算符和減法運(yùn)算符都能作用于指針。上一章節(jié)已經(jīng)介紹過(guò)二元加法和減法運(yùn)算符作用于指針的情況。當(dāng)一元正號(hào)運(yùn)算符作用于一個(gè)指針或者算術(shù)值時(shí),返回運(yùn)算對(duì)象值的一個(gè)(提升后的)副本。

    (TODO:一元正號(hào)運(yùn)算符作用于一個(gè)指針?查查有哪些用途。)

    一元負(fù)號(hào)運(yùn)算符對(duì)運(yùn)算對(duì)象值取負(fù)后,返回其(提升后的)副本:

    int i = 1024; int k = -i; // i is -1024 bool b = true; bool b2 = -b; // b2 is true!

    我們指出布爾值不應(yīng)該參與運(yùn)算,-b就是一個(gè)很好的例子。

    對(duì)大多數(shù)運(yùn)算符來(lái)說(shuō),布爾類型的運(yùn)算對(duì)象將被提升為 int 類型。如上所示,布爾變量b的值為真,參與運(yùn)算時(shí)將被提升成整數(shù)值1,對(duì)它求負(fù)后的結(jié)果是-1。將-1再轉(zhuǎn)換回布爾值并將其作為b2的初始值,顯然這個(gè)初始值不等于0轉(zhuǎn)換成布爾值后應(yīng)該為1。所以,b2的值是真!(似非而是的結(jié)果)

    加、減、乘、除、求余

    當(dāng)作用于算術(shù)類型的對(duì)象時(shí),算術(shù)運(yùn)算符+、-、*、/的含義分別是加法、減法、乘法和除法。整數(shù)相除結(jié)果還是整數(shù),也就是說(shuō),如果商含有小數(shù)部分,直接棄除:

    int ival1 = 21/6; // ival1 is 3; result is truncated; remainder is discarded int ival2 = 21/7; // ival2 is 3; no remainder; result is an integral value

    運(yùn)算符%俗稱“取余”或“取模”運(yùn)算符,負(fù)責(zé)計(jì)算兩個(gè)整數(shù)相除所得的余數(shù),參與取余運(yùn)算的運(yùn)算對(duì)象必須是整數(shù)類:

    int ival = 42; double dval = 3.14; ival % 12; // ok: result is 6 ival % dval; // error: floating-point operand

    在除法運(yùn)算中,如果兩個(gè)運(yùn)算對(duì)象的符號(hào)相同則商為正(如果不為0的話),否則商為負(fù)。C++語(yǔ)言的早期版本允許結(jié)果為負(fù)值的商向上或向下取整,C++11新標(biāo)準(zhǔn)則規(guī)定商一律向0取整(即直接切除小數(shù)部分)。

    根據(jù)取余運(yùn)算的定義,如果m和 n是整數(shù)且n非0,則表達(dá)式(m/n)*n+m%n的求值結(jié)果與m相等。隱含的意思是,如果m%n不等于0,則它的符號(hào)和m相同。C++語(yǔ)言的早期版本允許 m%n 的符號(hào)匹配n的符號(hào),而且商向負(fù)無(wú)窮一側(cè)取整,這一方式在新標(biāo)準(zhǔn)中已經(jīng)被禁止使用了。除了-m 導(dǎo)致溢出的特殊情況,其他時(shí)候(-m)/n和 m/(-n)都等于- (m/ n),m% (-n)等于m%n,(-m)%n等于-(m%n)。具體示例如下:

    21 % 6; /* result is 3 */ 21 / 6; /* result is 3 */21 % 7; /* result is 0 */ 21 / 7; /* result is 3 */-21 % -8; /* result is -5 */ -21 / -8; /* result is 2 */21 % -5; /* result is 1 */ c21 / -5; /* result is -4 */

    提示:溢出和其他算術(shù)運(yùn)算異常

    算術(shù)表達(dá)式有可能產(chǎn)生未定義的結(jié)果。

    • 一部分原因是數(shù)學(xué)性質(zhì)本身例如除數(shù)是0的情況;
    • 另外一部分則源于計(jì)算機(jī)的特點(diǎn):例如溢出,當(dāng)計(jì)算的結(jié)果超出該類型所能表示的范圍時(shí)就會(huì)產(chǎn)生溢出。

    假設(shè)某個(gè)機(jī)器的short類型占16位,則最大的short數(shù)值是32767。在這樣一臺(tái)機(jī)器上,下面的復(fù)合賦值語(yǔ)句將產(chǎn)生溢出:

    short short_value = 32767; // max value if shorts are 16 bits short_value += 1; // this calculation overflows cout << "short_value: " << short_value << endl;

    給short_value賦值的語(yǔ)句是未定義的,這是因?yàn)楸硎疽粋€(gè)帶符號(hào)數(shù)32768需要17位,但是short類型只有16位。很多系統(tǒng)在編譯和運(yùn)行時(shí)都不報(bào)溢出錯(cuò)誤,像其他未定義的行為一樣,溢出的結(jié)果是不可預(yù)知的。在我們的系統(tǒng)中,程序的輸出結(jié)果是:

    short_value: -32768

    該值發(fā)生了“環(huán)繞(wrapped around)”,符號(hào)位本來(lái)是0,由于溢出被改成了1,于是結(jié)果變成一個(gè)負(fù)值。在別的系統(tǒng)中也許會(huì)有其他結(jié)果,程序的行為可能不同甚至直接崩潰。

    邏輯和關(guān)系運(yùn)算符

    關(guān)系運(yùn)算符作用于算術(shù)類型或指針類型,邏輯運(yùn)算符作用于任意能轉(zhuǎn)換成布爾值的類型。

    邏輯運(yùn)算符和關(guān)系運(yùn)算符的返回值都是布爾類型。

    值為0的運(yùn)算對(duì)象(算術(shù)類型或指針類型)表示假,否則表示真。

    對(duì)于這兩類運(yùn)算符來(lái)說(shuō),運(yùn)算對(duì)象和求值結(jié)果都是右值。

    結(jié)合律運(yùn)算符功能用法
    !邏輯非!expr



    <
    <=
    >
    >=
    小于
    小于等于
    大于
    大于等于
    expr < expr
    expr <= expr
    expr > expr
    expr >= expr

    ==
    !=
    相等
    不相等
    expr == expr
    expr != expr
    &&邏輯與expr &&expr
    ||邏輯或expr||expr

    邏輯與和邏輯或運(yùn)算符

    對(duì)于邏輯與運(yùn)算符(&&)來(lái)說(shuō),當(dāng)且僅當(dāng)兩個(gè)運(yùn)算對(duì)象都為真時(shí)結(jié)果為真。對(duì)于邏輯或運(yùn)算符(||)來(lái)說(shuō),只要兩個(gè)運(yùn)算對(duì)象中的一個(gè)為真結(jié)果就為真。

    邏輯與運(yùn)算符和邏輯或運(yùn)算符都是先求左側(cè)運(yùn)算對(duì)象的值再求右側(cè)運(yùn)算對(duì)象的值,當(dāng)且僅當(dāng)左側(cè)運(yùn)算對(duì)象無(wú)法確定表達(dá)式的結(jié)果時(shí)才會(huì)計(jì)算右側(cè)運(yùn)算對(duì)象的值。這種策略稱為短路求值(short-circuit evaluation)。

    • 對(duì)于邏輯與運(yùn)算符來(lái)說(shuō),當(dāng)且僅當(dāng)左側(cè)運(yùn)算對(duì)象為真時(shí)才對(duì)右側(cè)運(yùn)算對(duì)象求值。

    • 對(duì)于邏輯或運(yùn)算符來(lái)說(shuō),當(dāng)且僅當(dāng)左側(cè)運(yùn)算對(duì)象為假時(shí)才對(duì)右側(cè)運(yùn)算對(duì)象求值。

    上一章的幾個(gè)程序用到了邏輯與運(yùn)算符,它們的左側(cè)運(yùn)算對(duì)象是為了確保右側(cè)運(yùn)算對(duì)象求值過(guò)程的正確性和安全性。如:

    index != s.size() && !isspace(s[index])

    首先檢查index是否到達(dá)string對(duì)象的末尾,以此確保只有當(dāng)index在合理范圍之內(nèi)時(shí)才會(huì)計(jì)算右側(cè)運(yùn)算對(duì)象的值。

    舉一個(gè)使用邏輯或運(yùn)算符的例子,假定有一個(gè)存儲(chǔ)著若干string對(duì)象的vector對(duì)象,要求輸出string對(duì)象的內(nèi)容并且在遇到空字符串或者以句號(hào)結(jié)束的字符串時(shí)進(jìn)行換行。使用基于范圍的for循環(huán)處理string對(duì)象中的每個(gè)元素:

    // note s as a reference to const; the elements aren't copied and can't be changed for (const auto &s : text) { // for each element in textcout << s; // print the current element// blank lines and those that end with a period get a newlineif (s.empty() || s[s.size() - 1] == '.')cout << endl;elsecout << " "; // otherwise just separate with a space }

    輸出當(dāng)前元素后檢查是否需要換行。if語(yǔ)句的條件部分首先檢查s 是否是一個(gè)空string,如果是,則不論右側(cè)運(yùn)算對(duì)象的值如何都應(yīng)該換行(短路求值體現(xiàn))。只有當(dāng)string對(duì)象非空時(shí)才需要求第二個(gè)運(yùn)算對(duì)象的值,也就是檢查string對(duì)象是否是以句號(hào)結(jié)束的。在這條表達(dá)式中,利用邏輯或運(yùn)算符的短路求值策略確保只有當(dāng)s非空時(shí)才會(huì)用下標(biāo)運(yùn)算符去訪問(wèn)它。

    值得注意的是,s被聲明成了對(duì)常量的引用。因?yàn)?text的元素是string對(duì)象,可能非常大,所以將s聲明成引用類型可以避免對(duì)元素的拷貝。又因?yàn)椴恍枰獙?duì)string對(duì)象做寫操作,所以s被聲明成對(duì)常量的引用

    邏輯非運(yùn)算符

    邏輯非運(yùn)算符(!)將運(yùn)算對(duì)象的值取反后返回。下面再舉一個(gè)例子,假設(shè)vec是一個(gè)整數(shù)類型的vector對(duì)象,可以使用邏輯非運(yùn)算符將empty函數(shù)的返回值取反從而檢查vec是否含有元素:

    // print the first element in vec if there is one if (!vec.empty())cout << vec[0];

    子表達(dá)式

    !vec.empty()//vec.empty()==false的簡(jiǎn)寫

    當(dāng)empty函數(shù)返回假時(shí)結(jié)果為真。

    關(guān)系運(yùn)算符

    顧名思義,關(guān)系運(yùn)算符比較運(yùn)算對(duì)象的大小關(guān)系并返回布爾值。關(guān)系運(yùn)算符都滿足左結(jié)合律。

    因?yàn)殛P(guān)系運(yùn)算符的求值結(jié)果是布爾值,所以將幾個(gè)關(guān)系運(yùn)算符連寫在一起會(huì)產(chǎn)生意想不到的結(jié)果:

    // oops! this condition compares k to the bool result of i < j if (i < j < k) // true if k is greater than 1! error

    if語(yǔ)句的條件部分首先把i、j和第一個(gè)<運(yùn)算符組合在一起,其返回的布爾值再作為第二個(gè)<運(yùn)算符的左側(cè)運(yùn)算對(duì)象。也就是說(shuō),k 比較的對(duì)象是第一次比較得到的那個(gè)或真或假的結(jié)果!要想實(shí)現(xiàn)我們的目的,其實(shí)應(yīng)該使用下面的表達(dá)式:

    // ok: condition is true if i is smaller than j and j is smaller than k if (i < j && j < k) { /* ... */ }

    相等性測(cè)試與布爾字面值

    如果想測(cè)試一個(gè)算術(shù)對(duì)象或指針對(duì)象的真值,最直接的方法就是將其作為if語(yǔ)句的條件:

    if (val) { /* ... */ } // true if val is any nonzero value if (!val) { /* ... */ } // true if val is zero

    在上面的兩個(gè)條件中,編譯器都將val轉(zhuǎn)換成布爾值。如果val非0則第一個(gè)條件為真,如果val的值為О則第二個(gè)條件為真。

    有時(shí)會(huì)試圖將上面的真值測(cè)試寫成如下形式:

    if (val == true) { /* ... */ } // true only if val is equal to 1!

    但是這種寫法存在兩個(gè)問(wèn)題:首先,與之前的代碼相比,上面這種寫法較長(zhǎng)而且不太直接(盡管大家都認(rèn)為縮寫的形式對(duì)初學(xué)者來(lái)說(shuō)有點(diǎn)難理解);更重要的一點(diǎn)是,如果val不是布爾值,這樣的比較就失去了原來(lái)的意義。

    如果val不是布爾值,那么進(jìn)行比較之前會(huì)首先把 true 轉(zhuǎn)換成val的類型。也就是說(shuō),如果val不是布爾值,則代碼可以改寫成如下形式:

    if (val == 1) { /* ... */ }

    正如我們已經(jīng)非常熟悉的那樣,當(dāng)布爾值轉(zhuǎn)換成其他算術(shù)類型時(shí),false轉(zhuǎn)換成0而true轉(zhuǎn)換成1。如果真想知道val的值是否是1,應(yīng)該直接寫出1這個(gè)數(shù)值來(lái),而不要與true 比較。

    進(jìn)行比較運(yùn)算時(shí)除非比較的對(duì)象是布爾類型,否則不要使用布爾字面值true和false作為運(yùn)算對(duì)象。

    賦值運(yùn)算符

    賦值運(yùn)算符的左側(cè)運(yùn)算對(duì)象必須是一個(gè)可修改的左值。如果給定

    int i = 0, j = 0, k = 0; // initializations, not assignment const int ci = i; // initialization, not assignment

    則下面的賦值語(yǔ)句都是非法的:

    1024 = k; // error: literals are rvalues i + j = k; // error: arithmetic expressions are rvalues ci = k; // error: ci is a const (nonmodifiable) lvalue

    賦值運(yùn)算的結(jié)果是它的左側(cè)運(yùn)算對(duì)象,并且是一個(gè)左值。相應(yīng)的,結(jié)果的類型就是左側(cè)運(yùn)算對(duì)象的類型。如果賦值運(yùn)算符的左右兩個(gè)運(yùn)算對(duì)象類型不同,則右側(cè)運(yùn)算對(duì)象將轉(zhuǎn)換成左側(cè)運(yùn)算對(duì)象的類型:

    k = 0; // result: type int, value 0 k = 3.14159; // result: type int, value 3

    C++11新標(biāo)準(zhǔn)允許使用花括號(hào)括起來(lái)的初始值列表作為賦值語(yǔ)句的右側(cè)運(yùn)算對(duì)象:

    k = {3.14}; // error: narrowing conversion vector<int> vi; // initially empty vi = {0,1,2,3,4,5,6,7,8,9}; // vi now has ten elements, values 0 through 9

    如果左側(cè)運(yùn)算對(duì)象是內(nèi)置類型,那么初始值列表最多只能包含一個(gè)值,而且該值即使轉(zhuǎn)換的話其所占空間也不應(yīng)該大于目標(biāo)類型的空間。

    對(duì)于類類型來(lái)說(shuō),賦值運(yùn)算的細(xì)節(jié)由類本身決定。對(duì)于vector來(lái)說(shuō),vector模板重載了賦值運(yùn)算符并且可以接收初始值列表,當(dāng)賦值發(fā)生時(shí)用右側(cè)運(yùn)算對(duì)象的元素替換左側(cè)運(yùn)算對(duì)象的元素。

    無(wú)論左側(cè)運(yùn)算對(duì)象的類型是什么,初始值列表都可以為空。此時(shí),編譯器創(chuàng)建一個(gè)值初始化的臨時(shí)量并將其賦給左側(cè)運(yùn)算對(duì)象。

    賦值運(yùn)算滿足右結(jié)合律

    賦值運(yùn)算符滿足右結(jié)合律,這一點(diǎn)與其他二元運(yùn)算符不太一樣:

    int ival, jval; ival = jval = 0; // ok: each assigned 0

    因?yàn)橘x值運(yùn)算符滿足右結(jié)合律,所以靠右的賦值運(yùn)算jval=0 作為靠左的賦值運(yùn)算符的右側(cè)運(yùn)算對(duì)象。又因?yàn)橘x值運(yùn)算返回的是其左側(cè)運(yùn)算對(duì)象,所以靠右的賦值運(yùn)算的結(jié)果(即jval)被賦給了ival。

    對(duì)于多重賦值語(yǔ)句中的每一個(gè)對(duì)象,它的類型或者與右邊對(duì)象的類型相同、或者可由右邊對(duì)象的類型轉(zhuǎn)換得到:

    int ival, *pval; // ival is an int; pval is a pointer to int ival = pval = 0; // error: cannot assign the value of a pointer to an int string s1, s2; s1 = s2 = "OK"; // string literal "OK" converted to string

    因?yàn)閕val和pval的類型不同,而且 pval 的類型(int*)無(wú)法轉(zhuǎn)換成ival 的類型(int),所以盡管0這個(gè)值能賦給任何對(duì)象,但是第一條賦值語(yǔ)句仍然是非法的。

    與之相反,第二條賦值語(yǔ)句是合法的。這是因?yàn)樽址置嬷悼梢赞D(zhuǎn)換成string對(duì)象并賦給s2,而s2和s1的類型相同,所以s2的值可以繼續(xù)賦給s1。

    賦值運(yùn)算優(yōu)先級(jí)較低

    賦值語(yǔ)句經(jīng)常會(huì)出現(xiàn)在條件當(dāng)中。因?yàn)橘x值運(yùn)算的優(yōu)先級(jí)相對(duì)較低,所以通常需要給賦值部分加上括號(hào)使其符合我們的原意。下面這個(gè)循環(huán)說(shuō)明了把賦值語(yǔ)句放在條件當(dāng)中有什么用處,它的目的是反復(fù)調(diào)用一個(gè)函數(shù)直到返回期望的值(比如42)為止:

    // a verbose and therefore more error-prone way to write this loop int i = get_value(); // get the first value while (i != 42) {// do something ...i = get_value(); // get remaining values }

    在這段代碼中,首先調(diào)用get_value函數(shù)得到一個(gè)值,然后循環(huán)部分使用該值作為條件。在循環(huán)體內(nèi)部,最后一條語(yǔ)句會(huì)再次調(diào)用get_value函數(shù)并不斷重復(fù)循環(huán)。可以將上述代碼以更簡(jiǎn)單直接的形式表達(dá)出來(lái):

    int i; // a better way to write our loop---what the condition does is now clearer while ((i = get_value()) != 42) {// do something ... }

    這個(gè)版本的while條件更容易表達(dá)我們的真實(shí)意圖:不斷循環(huán)讀取數(shù)據(jù)直至遇到42為止。其處理過(guò)程是首先將get_value函數(shù)的返回值賦給i,然后比較i和42是否相等。

    如果不加括號(hào)的話含義會(huì)有很大變化,比較運(yùn)算符!=的運(yùn)算對(duì)象將是get_value函數(shù)的返回值及42,比較的結(jié)果不論真假將以布爾值的形式賦值給i,這顯然不是我們期望的結(jié)果。

    因?yàn)橘x值運(yùn)算符的優(yōu)先級(jí)低于關(guān)系運(yùn)算符的優(yōu)先級(jí),所以在條件語(yǔ)句中,賦值部分通常應(yīng)該加上括號(hào)。

    切勿混淆相等運(yùn)算符和賦值運(yùn)算符

    C++語(yǔ)言允許用賦值運(yùn)算作為條件,但是這一特性可能帶來(lái)意想不到的后果:

    if (i = j)

    此時(shí),if語(yǔ)句的條件部分把j的值賦給i,然后檢查賦值的結(jié)果是否為真。如果j不為0,條件將為真。然而程序員的初衷很可能是想判斷i和j是否相等:

    if (i == j)

    程序的這種缺陷顯然很難被發(fā)現(xiàn),好在一部分編譯器會(huì)對(duì)類似的代碼給出警告信息。

    復(fù)合賦值運(yùn)算符

    我們經(jīng)常需要對(duì)對(duì)象施以某種運(yùn)算,然后把計(jì)算的結(jié)果再賦給該對(duì)象。舉個(gè)例子,考慮1.4.2節(jié)(第11頁(yè))的求和程序:

    int sum = 0; // sum values from 1 through 10 inclusive for (int val = 1; val <= 10; ++val)sum += val; // equivalent to sum = sum + val

    這種復(fù)合操作不僅對(duì)加法來(lái)說(shuō)很常見,而且也常常應(yīng)用于其他算術(shù)運(yùn)算符或者將要介紹的位運(yùn)算符。每種運(yùn)算符都有相應(yīng)的復(fù)合賦值形式:

    += -= *= /= %= // arithmetic operators <<= >>= &= ^= |= // bitwise operators

    任意一種復(fù)合運(yùn)算符都完全等價(jià)于

    a = a op b;

    唯一的區(qū)別是左側(cè)運(yùn)算對(duì)象的求值次數(shù):使用復(fù)合運(yùn)算符只求值一次,使用普通的運(yùn)算符則求值兩次。這兩次包括:一次是作為右邊子表達(dá)式的一部分求值,另一次是作為賦值運(yùn)算的左側(cè)運(yùn)算對(duì)象求值。其實(shí)在很多地方,這種區(qū)別除了對(duì)程序性能有些許影響外幾乎可以忽略不計(jì)。

    遞增和遞減運(yùn)算符

    遞增運(yùn)算符(++)和遞減運(yùn)算符(–)為對(duì)象的加1和減1操作提供了一種簡(jiǎn)潔的書寫形式。這兩個(gè)運(yùn)算符還可應(yīng)用于迭代器,因?yàn)楹芏嗟鞅旧聿恢С炙阈g(shù)運(yùn)算,所以此時(shí)遞增和遞減運(yùn)算符除了書寫簡(jiǎn)潔外還是必須的。

    遞增和遞減運(yùn)算符有兩種形式:前置版本和后置版本。

    • 到目前為止,之前的程序使用的都是前置版本,這種形式的運(yùn)算符首先將運(yùn)算對(duì)象加1(或減1),然后將改變后的對(duì)象作為求值結(jié)果。
    • 后置版本也會(huì)將運(yùn)算對(duì)象加1(或減1),但是求值結(jié)果是運(yùn)算對(duì)象改變之前那個(gè)值的副本:
    int i = 0, j; j = ++i; // j = 1, i = 1: prefix yields the incremented value j = i++; // j = 1, i = 2: postfix yields the unincremented value

    這兩種運(yùn)算符必須作用于左值運(yùn)算對(duì)象。

    • 前置版本將對(duì)象本身作為左值返回,
    • 后置版本則將對(duì)象原始值的副本作為右值返回。

    建議:除非必須,否則不用遞增遞減運(yùn)算符的后置版本

    有C語(yǔ)言背景的讀者可能對(duì)優(yōu)先使用前置版本遞增運(yùn)算符有所疑問(wèn),其實(shí)原因非常簡(jiǎn)單:前置版本的遞增運(yùn)算符避免了不必要的工作,它把值加1后直接返回改變了的運(yùn)算對(duì)象。與之相比,后置版本需要將原始值存儲(chǔ)下來(lái)以便于返回這個(gè)未修改的內(nèi)容。如果我們不需要修改前的值,那么后置版本的操作就是一種浪費(fèi)。

    對(duì)于整數(shù)和指針類型來(lái)說(shuō),編譯器可能對(duì)這種額外的工作進(jìn)行一定的優(yōu)化,但是對(duì)于相對(duì)復(fù)雜的迭代器類型,這種額外的工作就消耗巨大了。建議養(yǎng)成使用前置版本的習(xí)慣,這樣不僅不需要擔(dān)心性能的問(wèn)題,而且更重要的是寫出的代碼會(huì)更符合編程的初衷。

    在一條語(yǔ)句中混用解引用和遞增運(yùn)算符

    如果我們想在一條復(fù)合表達(dá)式中既將變量加1或減1又能使用它原來(lái)的值,這時(shí)就可以使用遞增和遞減運(yùn)算符的后置版本

    舉個(gè)例子,可以使用后置的遞增運(yùn)算符來(lái)控制循環(huán)輸出一個(gè)vector對(duì)象內(nèi)容直至遇到(但不包括)第一個(gè)負(fù)值為止:

    auto pbeg = v.begin(); // print elements up to the first negative value while (pbeg != v.end() && *beg >= 0)cout << *pbeg++ << endl; // print the current value and advance pbeg

    對(duì)于剛接觸C++和C的程序員來(lái)說(shuō),*pbeg++不太容易理解。其實(shí)這種寫法非常普遍,所以程序員一定要理解其含義

    后置遞增運(yùn)算符的優(yōu)先級(jí)高于解引用運(yùn)算符,因此*pbeg++等價(jià)于*(pbeg++)。pbeg++把pbeg 的值加1,然后返回pbeg的初始值的副本作為其求值結(jié)果,此時(shí)解引用運(yùn)算符的運(yùn)算對(duì)象是pbeg未增加之前的值。最終,這條語(yǔ)句輸出pbeg開始時(shí)指向的那個(gè)元素,并將指針向前移動(dòng)一個(gè)位置。

    這種用法完全是基于一個(gè)事實(shí),即后置遞增運(yùn)算符返回初始的未加1的值。如果返回的是加1之后的值,解引用該值將產(chǎn)生錯(cuò)誤的結(jié)果。不但無(wú)法輸出第一個(gè)元素,而且更糟糕的是如果序列中沒(méi)有負(fù)值,程序?qū)⒖赡茉噲D解引用一個(gè)根本不存在的元素。

    建議:簡(jiǎn)潔可以成為一種美德

    形如*pbeg++的表達(dá)式一開始可能不太容易理解,但其實(shí)這是一種被廣泛使用的、有效的寫法。當(dāng)對(duì)這種形式熟悉之后,書寫

    cout << *iter++ << endl;

    要比書寫下面的等價(jià)語(yǔ)句更簡(jiǎn)潔、也更少出錯(cuò)

    cout << *iter << endl; ++iter;

    不斷研究這樣的例子直到對(duì)它們的含義一目了然。大多數(shù)C++程序追求簡(jiǎn)潔、摒棄冗長(zhǎng),因此C++程序員應(yīng)該習(xí)慣于這種寫法。而且,一旦熟練掌握了這種寫法后,程序出錯(cuò)的可能性也會(huì)降低。

    運(yùn)算對(duì)象可按任意順序求值

    大多數(shù)運(yùn)算符都沒(méi)有規(guī)定運(yùn)算對(duì)象的求值順序,這在一般情況下不會(huì)有什么影響。

    然而,如果一條子表達(dá)式改變了某個(gè)運(yùn)算對(duì)象的值,另一條子表達(dá)式又要使用該值的話,運(yùn)算對(duì)象的求值順序就很關(guān)鍵了。因?yàn)檫f增運(yùn)算符和遞減運(yùn)算符會(huì)改變運(yùn)算對(duì)象的值,所以要提防在復(fù)合表達(dá)式中錯(cuò)用這兩個(gè)運(yùn)算符。

    為了說(shuō)明這一問(wèn)題,使用for循環(huán)將輸入的第一個(gè)單詞改成大寫形式:

    for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)*it = toupper(*it); // capitalize the current character

    在上述程序中,我們把解引用it 和遞增it 兩項(xiàng)任務(wù)分開來(lái)完成。如果用一個(gè)看似等價(jià)的while循環(huán)進(jìn)行代替

    // the behavior of the following loop is undefined! while (beg != s.end() && !isspace(*beg))*beg = toupper(*beg++); // error: this assignment is undefined

    將產(chǎn)生未定義的行為。問(wèn)題在于:賦值運(yùn)算符左右兩端的運(yùn)算對(duì)象都用到了beg,并且右側(cè)的運(yùn)算對(duì)象還改變了beg 的值,所以該賦值語(yǔ)句是未定義的。

    編譯器可能按照下面的任意一種思路處理該表達(dá)式

    *beg = toupper(*beg); // execution if left-hand side is evaluated first *(beg + 1) = toupper(*beg); // execution if right-hand side is evaluated first

    也可能采取別的什么方式處理它。

    成員訪問(wèn)運(yùn)算符

    點(diǎn)運(yùn)算符和箭頭運(yùn)算符都可用于訪問(wèn)成員,其中,點(diǎn)運(yùn)算符獲取類對(duì)象的一個(gè)成員,箭頭運(yùn)算符與點(diǎn)運(yùn)算符有關(guān),表達(dá)式ptr->mem等價(jià)于(*ptr).mem:

    string s1 = "a string", *p = &s1; auto n = s1.size(); // run the size member of the string s1 n = (*p).size(); // run size on the object to which p points n = p->size(); // equivalent to (*p).size()

    因?yàn)榻庖眠\(yùn)算符的優(yōu)先級(jí)低于點(diǎn)運(yùn)算符,所以執(zhí)行解引用運(yùn)算的子表達(dá)式兩端必須加上括號(hào)。如果沒(méi)加括號(hào),代碼的含義就大不相同了:

    // run the size member of p, then dereference the result! *p.size(); // error: p is a pointer and has no member named size

    這條表達(dá)式試圖訪問(wèn)對(duì)象p的size成員,但是p本身是一個(gè)指針且不包含任何成員,所以上述語(yǔ)句無(wú)法通過(guò)編譯。

    箭頭運(yùn)算符作用于一個(gè)指針類型的運(yùn)算對(duì)象,結(jié)果是一個(gè)左值。

    點(diǎn)運(yùn)算符分成兩種情況:

  • 如果成員所屬的對(duì)象是左值,那么結(jié)果是左值;
  • 反之,如果成員所屬的對(duì)象是右值,那么結(jié)果是右值。
  • 條件運(yùn)算符

    條件運(yùn)算符(?:)允許我們把簡(jiǎn)單的if-else邏輯嵌入到單個(gè)表達(dá)式當(dāng)中,條件運(yùn)算符按照如下形式使用:

    cond ? expr1 : expr2;

    其中 cond是判斷條件的表達(dá)式,而expr1和expr2是兩個(gè)類型相同或可能轉(zhuǎn)換為某個(gè)公共類型的表達(dá)式。條件運(yùn)算符的執(zhí)行過(guò)程是:首先求cond 的值,如果條件為真對(duì)expr1求值并返回該值,否則對(duì)expr2求值并返回該值。舉個(gè)例子,我們可以使用條件運(yùn)算符判斷成績(jī)是否合格:

    string finalgrade = (grade < 60) ? "fail" : "pass";

    條件部分判斷成績(jī)是否小于60。如果小于,表達(dá)式的結(jié)果是"fail",否則結(jié)果是"pass"。有點(diǎn)類似于邏輯與運(yùn)算符和邏輯或運(yùn)算符( &&和||),條件運(yùn)算符只對(duì)exprl和expr2中的一個(gè)求值。

    當(dāng)條件運(yùn)算符的兩個(gè)表達(dá)式都是左值或者能轉(zhuǎn)換成同一種左值類型時(shí),運(yùn)算的結(jié)果是左值:否則運(yùn)算的結(jié)果是右值。

    嵌套條件運(yùn)算符

    允許在條件運(yùn)算符的內(nèi)部嵌套另外一個(gè)條件運(yùn)算符。也就是說(shuō),條件表達(dá)式可以作為另外一個(gè)條件運(yùn)算符的cond或expr。舉個(gè)例子,使用一對(duì)嵌套的條件運(yùn)算符可以將成績(jī)分成三檔:優(yōu)秀(high pass)、合格( pass)和不合格( fail ):

    finalgrade = (grade > 90) ? "high pass": (grade < 60) ? "fail" : "pass";

    第一個(gè)條件檢查成績(jī)是否在90分以上,如果是,執(zhí)行符號(hào)?后面的表達(dá)式,得到"highpass";如果否,執(zhí)行符號(hào):后面的分支。這個(gè)分支本身又是一個(gè)條件表達(dá)式,它檢查成績(jī)是否在60分以下,如果是,得到"fail";否則得到"pass"。

    條件運(yùn)算符滿足右結(jié)合律,意味著運(yùn)算對(duì)象(一般)按照從右向左的順序組合。因此在上面的代碼中,靠右邊的條件運(yùn)算(比較成績(jī)是否小于60)構(gòu)成了靠左邊的條件運(yùn)算的:分支。

    隨著條件運(yùn)算嵌套層數(shù)的增加,代碼的可讀性急劇下降。因此,條件運(yùn)算的嵌套最好別超過(guò)兩到三層。

    在輸出表達(dá)式中使用條件運(yùn)算符

    條件運(yùn)算符的優(yōu)先級(jí)非常低,因此當(dāng)一條長(zhǎng)表達(dá)式中嵌套了條件運(yùn)算子表達(dá)式時(shí),通常需要在它兩端加上括號(hào)。例如,有時(shí)需要根據(jù)條件值輸出兩個(gè)對(duì)象中的一個(gè),如果寫這條語(yǔ)句時(shí)沒(méi)把括號(hào)寫全就有可能產(chǎn)生意想不到的結(jié)果:

    cout << ((grade < 60) ? "fail" : "pass"); // prints pass or fail cout << (grade < 60) ? "fail" : "pass"; // prints 1 or 0! cout << grade < 60 ? "fail" : "pass"; // error: compares cout to 60

    在第二條表達(dá)式中, grade和60的比較結(jié)果是<<運(yùn)算符的運(yùn)算對(duì)象,因此如果grade<60為真輸出1,否則輸出0。<<運(yùn)算符的返回值是cout,接下來(lái)cout作為條件運(yùn)算符的條件。也就是說(shuō),第二條表達(dá)式等價(jià)于

    cout << (grade < 60); // prints 1 or 0 cout ? "fail" : "pass"; // test cout and then yield one of the two literals// depending on whether cout is true or false

    因?yàn)榈谌龡l表達(dá)式等價(jià)于下面的語(yǔ)句,所以它是錯(cuò)誤的:

    cout << grade; // less-than has lower precedence than shift, so print grade first cout < 60 ? "fail" : "pass"; // then compare cout to 60!

    位運(yùn)算符

    位運(yùn)算符作用于整數(shù)類型的運(yùn)算對(duì)象,并把運(yùn)算對(duì)象看成是二進(jìn)制位的集合。位運(yùn)算符提供檢查和設(shè)置二進(jìn)制位的功能,將會(huì)要介紹的,一種名為bitset的標(biāo)準(zhǔn)庫(kù)類型也可以表示任意大小的二進(jìn)制位集合,所以位運(yùn)算符同樣能用于bitset類型。

    左結(jié)合律

    運(yùn)算符功能用法
    ~位求反expr
    <<
    >>
    左移
    右移
    expr1 << expr2
    expr1 >> expr2
    &位與expr & expr
    ^位異或expr ^ expr
    |位或expr | expr

    一般來(lái)說(shuō),如果運(yùn)算對(duì)象是“小整型”,則它的值會(huì)被自動(dòng)提升成較大的整數(shù)類型。運(yùn)算對(duì)象可以是帶符號(hào)的,也可以是無(wú)符號(hào)的。如果運(yùn)算對(duì)象是帶符號(hào)的且它的值為負(fù),那么位運(yùn)算符如何處理運(yùn)算對(duì)象的“符號(hào)位”依賴于機(jī)器。而且,此時(shí)的左移操作可能會(huì)改變符號(hào)位的值,因此是一種未定義的行為。(不像Java那樣,用>>>和>>來(lái)處理負(fù)號(hào)位)

    關(guān)于符號(hào)位如何處理沒(méi)有明確的規(guī)定,所以強(qiáng)烈建議僅將位運(yùn)算符用于處理無(wú)符號(hào)類型。

    移位運(yùn)算符

    之前在處理輸入和輸出操作時(shí),我們已經(jīng)使用過(guò)標(biāo)準(zhǔn)IO庫(kù)定義的<<運(yùn)算符和>>運(yùn)算符的重載版本。

    這兩種運(yùn)算符的內(nèi)置含義是對(duì)其運(yùn)算對(duì)象執(zhí)行基于二進(jìn)制位的移動(dòng)操作,

  • 首先令左側(cè)運(yùn)算對(duì)象的內(nèi)容按照右側(cè)運(yùn)算對(duì)象的要求移動(dòng)指定位數(shù),
  • 然后將經(jīng)過(guò)移動(dòng)的(可能還進(jìn)行了提升)左側(cè)運(yùn)算對(duì)象的拷貝作為求值結(jié)果。
  • 其中,右側(cè)的運(yùn)算對(duì)象一定不能為負(fù),而且值必須嚴(yán)格小于結(jié)果的位數(shù),否則就會(huì)產(chǎn)生未定義的行為。二進(jìn)制位或者向左移(<<)或者向右移(>>),移出邊界之外的位就被舍棄掉了:

    在下面的例子中右側(cè)為最低位并且假定char占8位、int占32位。

    unsigned char bits = 0233;//0233是八進(jìn)制的字面值 1 0 0 1 1 0 1 1

    bits << 8 //bits提升成int類型,然后向左移動(dòng)8位 0 0 0 0 0 0 0 00 0 0 0 0 0 0 01 0 0 1 1 0 1 10 0 0 0 0 0 0 0

    bits << 31 //向左移動(dòng)31位,左邊超出邊界的位丟棄掉了 1 0 0 0 0 0 0 00 0 0 0 0 0 0 00 0 0 0 0 0 0 00 0 0 0 0 0 0 0

    bits >> 3 //向右移動(dòng)3位,最右邊的3位丟棄掉了 0 0 0 0 0 0 0 00 0 0 0 0 0 0 00 0 0 0 0 0 0 00 0 0 1 0 0 1 1

    左移運(yùn)算符(<<)在右側(cè)插入值為0的二進(jìn)制位。右移運(yùn)算符(>>)的行為則依賴于其左側(cè)運(yùn)算對(duì)象的類型:

    • 如果該運(yùn)算對(duì)象是無(wú)符號(hào)類型,在左側(cè)插入值為0的二進(jìn)制位;
    • 如果該運(yùn)算對(duì)象是帶符號(hào)類型,在左側(cè)插入符號(hào)位的副本或值為0的二進(jìn)制位,如何選擇要視具體環(huán)境而定。

    位求反運(yùn)算符

    位求反運(yùn)算符(~)將運(yùn)算對(duì)象逐位求反后生成一個(gè)新值,將1置為0、將0置為1:

    unsigned char bits = 0233;//0233是八進(jìn)制的字面值 1 0 0 1 0 1 1 1

    ~bits 1 1 1 1 1 1 1 11 1 1 1 1 1 1 11 1 1 1 1 1 1 10 1 1 0 1 0 0 0

    char類型的運(yùn)算對(duì)象首先提升成int類型,提升時(shí)運(yùn)算對(duì)象原來(lái)的位保持不變,往高位(high order position)添加0即可。因此在本例中,首先將bits提升成int類型,增加24個(gè)高位0,隨后將提升后的值逐位求反。

    位與、位或、位異或運(yùn)算符

    與(&)、或(|)、異或(^)運(yùn)算符在兩個(gè)運(yùn)算對(duì)象上逐位執(zhí)行相應(yīng)的邏輯操作:

    代碼高二十四位低八位
    unsigned char b1 = 0145;-0 1 1 0 0 1 0 1
    unsigned char b2 = 0257;-1 0 1 0 1 1 1 1
    b1 & b224個(gè)高階位都是00 0 1 0 0 1 0 1
    b1 | b224個(gè)高階位都是01 1 1 0 1 1 1 1
    b1 ^ b224個(gè)高階位都是01 1 0 0 1 0 1 0
    • 對(duì)于位與運(yùn)算符(&)來(lái)說(shuō),如果兩個(gè)運(yùn)算對(duì)象的對(duì)應(yīng)位置都是1則運(yùn)算結(jié)果中該位為1,否則為0。

    • 對(duì)于位或運(yùn)算符(|)來(lái)說(shuō),如果兩個(gè)運(yùn)算對(duì)象的對(duì)應(yīng)位置至少有一個(gè)為1則運(yùn)算結(jié)果中該位為1,否則為0。

    • 對(duì)于位異或運(yùn)算符(^)來(lái)說(shuō),如果兩個(gè)運(yùn)算對(duì)象的對(duì)應(yīng)位置有且只有一個(gè)為1則運(yùn)算結(jié)果中該位為1,否則為0。

    一種常見的錯(cuò)誤是把位運(yùn)算符和邏輯運(yùn)算符搞混了,比如位與(&)和邏輯與(&&)、位或(I)和邏輯或(1l)、位求反(~)和邏輯非(!)

    使用位運(yùn)算符

    (位圖法)

    我們舉一個(gè)使用位運(yùn)算符的例子:假設(shè)班級(jí)中有30個(gè)學(xué)生,老師每周都會(huì)對(duì)學(xué)生進(jìn)行一次小測(cè)驗(yàn),測(cè)驗(yàn)的結(jié)果只有通過(guò)和不通過(guò)兩種。為了更好地追蹤測(cè)驗(yàn)的結(jié)果,我們用一個(gè)二進(jìn)制位代表某個(gè)學(xué)生在一次測(cè)驗(yàn)中是否通過(guò),顯然全班的測(cè)驗(yàn)結(jié)果可以用一個(gè)無(wú)符號(hào)整數(shù)來(lái)表示:

    unsigned long quiz1 = 0; //我們把這個(gè)值當(dāng)成是位的集合來(lái)使用

    定義quiz1的類型是unsigned long,這樣,quiz1在任何機(jī)器上都將至少擁有32位;給quiz1賦一個(gè)明確的初始值,使得它的每一位在開始時(shí)都有統(tǒng)一且固定的值。

    教師必須有權(quán)設(shè)置并檢查每一個(gè)二進(jìn)制位。例如,我們需要對(duì)序號(hào)為27的學(xué)生對(duì)應(yīng)的位進(jìn)行設(shè)置,以表示他通過(guò)了測(cè)驗(yàn)。為了達(dá)到這一目的,首先創(chuàng)建一個(gè)值,該值只有第27位是1其他位都是0,然后將這個(gè)值與quiz1進(jìn)行位或運(yùn)算,這樣就能強(qiáng)行將quiz1的第27位設(shè)置為1,其他位都保持不變。

    為了實(shí)現(xiàn)本例的目的,我們將quiz1的低階位賦值為0、下一位賦值為1,以此類推,最后統(tǒng)計(jì)quiz1各個(gè)位的情況。

    使用左移運(yùn)算符和一個(gè)unsigned long類型的整數(shù)字面值1就能得到一個(gè)表示學(xué)生27通過(guò)了測(cè)驗(yàn)的數(shù)值:

    1UL << 27 //生成一個(gè)值,該值只有第27位為1

    指定數(shù)位置1

    1U 的低階位上有一個(gè)1,除此之外(至少)還有31個(gè)值為0的位。之所以使用unsignedlong類型,是因?yàn)閕nt類型只能確保占用16位,而我們至少需要27位。上面這條表達(dá)式通過(guò)在值為1的那個(gè)二進(jìn)制位后面添加0,使得它向左移動(dòng)了27位。

    接下來(lái)將所得的值與quiz1進(jìn)行位或運(yùn)算。為了同時(shí)更新quiz1的值,使用一條復(fù)合賦值語(yǔ)句:

    quiz1 |= 1UL<< 27; //表示學(xué)生27通過(guò)了測(cè)驗(yàn)

    |-運(yùn)算符的工作原理和+=非常相似,它等價(jià)于

    quiz1 = quiz1 | 1UL << 27; //等價(jià)于quiz1 l= 1UL << 27;

    指定數(shù)位置0

    假定教師在重新核對(duì)測(cè)驗(yàn)結(jié)果時(shí)發(fā)現(xiàn)學(xué)生27實(shí)際上并沒(méi)有通過(guò)測(cè)驗(yàn),他必須要把第27位的值置為0。此時(shí)我們需要使用一個(gè)特殊的整數(shù),它的第27位是0、其他所有位都是1。將這個(gè)值與quiz1進(jìn)行位與運(yùn)算就能實(shí)現(xiàn)目的了:

    quiz1 &= ~(1UL << 27); //學(xué)生27沒(méi)有通過(guò)測(cè)驗(yàn)

    通過(guò)將之前的值按位求反得到一個(gè)新值,除了第27位外都是1,只有第27位的值是0。隨后將該值與quiz1進(jìn)行位與運(yùn)算,所得結(jié)果除了第27位外都保持不變。

    查指定數(shù)位

    最后,我們?cè)噲D檢查學(xué)生27測(cè)驗(yàn)的情況到底怎么樣:

    bool status = quiz1 & (1UL << 27);// 學(xué)生27是否通過(guò)了測(cè)驗(yàn)?

    我們將quiz1和一個(gè)只有第27位是1的值按位求與,如果quiz1的第27位是1,計(jì)算的結(jié)果就是非0(真);否則結(jié)果是0。

    小結(jié)

  • 指定數(shù)位置1:用 |
  • 指定數(shù)位置0:用 ~ &
  • 查指定數(shù)位 :用 &
  • 移位運(yùn)算符(又叫IO運(yùn)算符)滿足左結(jié)合律

    盡管很多程序員從未直接用過(guò)位運(yùn)算符,但是幾乎所有人都用過(guò)它們的重載版本來(lái)進(jìn)行IO操作。重載運(yùn)算符的優(yōu)先級(jí)和結(jié)合律都與它的內(nèi)置版本一樣,因此即使程序員用不到移位運(yùn)算符的內(nèi)置含義,也仍然有必要理解其優(yōu)先級(jí)和結(jié)合律。

    因?yàn)橐莆贿\(yùn)算符滿足左結(jié)合律,所以表達(dá)式

    cout<< "hi" << " there" << endl;

    的執(zhí)行過(guò)程實(shí)際上等同于

    ((cout <<"hi") << " there" ) << endl;

    在這條語(yǔ)句中,運(yùn)算對(duì)象"hi"和第一個(gè)<<組合在一起,它的結(jié)果和第二個(gè)<<組合在一起,接下來(lái)的結(jié)果再和第三個(gè)<<組合在一起。

    移位運(yùn)算符的優(yōu)先級(jí)不高不低,介于中間:比算術(shù)運(yùn)算符的優(yōu)先級(jí)低,但比關(guān)系運(yùn)算符、賦值運(yùn)算符和條件運(yùn)算符的優(yōu)先級(jí)高。因此在一次使用多個(gè)運(yùn)算符時(shí),有必要在適當(dāng)?shù)牡胤郊由侠ㄌ?hào)使其滿足我們的要求。

    cout << 42 + 10; //正確:+的優(yōu)先級(jí)更高,因此輸出求和結(jié)果 cout << (10 < 42); //正確:括號(hào)使運(yùn)算對(duì)象按照我們的期望組合在一起,輸出1 cout << 10 < 42; //錯(cuò)誤:試圖比較cout和42!

    最后一個(gè)cout的含義其實(shí)是

    (cout << 10) < 42;

    也就是“把數(shù)字10寫到cout,然后將結(jié)果(即 cout)與42進(jìn)行比較”。

    sizeof運(yùn)算符

    sizeof運(yùn)算符返回一條表達(dá)式或一個(gè)類型名字所占的字節(jié)數(shù)。sizeof運(yùn)算符滿足右結(jié)合律,其所得的值是一個(gè)size_t類型的常量表達(dá)式。運(yùn)算符的運(yùn)算對(duì)象有兩種形式:

    sizeof (type) sizeof expr

    在第二種形式中,sizeof返回的是表達(dá)式結(jié)果類型的大小。與眾不同的一點(diǎn)是,sizeof并不實(shí)際計(jì)算其運(yùn)算對(duì)象的值:

    Sales_data data, *p; sizeof(Sales_data); // size required to hold an object of type Sales_data sizeof data; // size of data's type, i.e., sizeof(Sales_data) sizeof p; // size of a pointer sizeof *p; // size of the type to which p points, i.e., sizeof(Sales_data) sizeof data.revenue; // size of the type of Sales_data's revenue member sizeof Sales_data::revenue; // alternative way to get the size of revenue

    這些例子中最有趣的一個(gè)是sizeof *p。

    • 首先,因?yàn)閟izeof滿足右結(jié)合律并且與*運(yùn)算符的優(yōu)先級(jí)一樣,所以表達(dá)式按照從右向左的順序組合。也就是說(shuō),它等價(jià)于sizeof(*p)。

    • 其次,因?yàn)閟izeof不會(huì)實(shí)際求運(yùn)算對(duì)象的值,所以即使p是一個(gè)無(wú)效(即未初始化)的指針也不會(huì)有什么影響。在sizeof的運(yùn)算對(duì)象中解引用一個(gè)無(wú)效指針仍然是一種安全的行為,因?yàn)橹羔槍?shí)際上并沒(méi)有被真正使用。sizeof不需要真的解引用指針也能知道它所指對(duì)象的類型。

    C++ 11新標(biāo)準(zhǔn)允許我們使用作用域運(yùn)算符來(lái)獲取類成員的大小。通常情況下只有通過(guò)類的對(duì)象才能訪問(wèn)到類的成員,但是sizeof運(yùn)算符無(wú)須我們提供一個(gè)具體的對(duì)象,因?yàn)橐胫李惓蓡T的大小無(wú)須真的獲取該成員。

    sizeof運(yùn)算符的結(jié)果部分地依賴于其作用的類型:

    • 對(duì)char或者類型為char的表達(dá)式執(zhí)行sizeof運(yùn)算,結(jié)果得1。

    • 對(duì)引用類型執(zhí)行sizeof運(yùn)算得到被引用對(duì)象所占空間的大小。

    • 對(duì)指針執(zhí)行sizeof運(yùn)算得到指針本身所占空間的大小。

    • 對(duì)解引用指針執(zhí)行sizeof運(yùn)算得到指針指向的對(duì)象所占空間的大小,指針不需有效。

    • 對(duì)數(shù)組執(zhí)行sizeof運(yùn)算得到整個(gè)數(shù)組所占空間的大小,等價(jià)于對(duì)數(shù)組中所有的元素各執(zhí)行一次sizeof運(yùn)算并將所得結(jié)果求和。注意,sizeof運(yùn)算不會(huì)把數(shù)組轉(zhuǎn)換成指針來(lái)處理。

    • 對(duì)string對(duì)象或vector對(duì)象執(zhí)行sizeof運(yùn)算只返回該類型固定部分的大小,不會(huì)計(jì)算對(duì)象中的元素占用了多少空間。(這個(gè)要注意)

    因?yàn)閳?zhí)行sizeof運(yùn)算能得到整個(gè)數(shù)組的大小,所以可以用數(shù)組的大小除以單個(gè)元素的大小得到數(shù)組中元素的個(gè)數(shù):

    // sizeof(ia)/sizeof(*ia) returns the number of elements in ia constexpr size_t sz = sizeof(ia) / sizeof(*ia); int arr2[sz]; // ok sizeof returns a constant expression

    因?yàn)閟izeof的返回值是一個(gè)常量表達(dá)式,所以我們可以用sizeof的結(jié)果聲明數(shù)組的維度。

    逗號(hào)運(yùn)算符

    逗號(hào)運(yùn)算符(comma operator)含有兩個(gè)運(yùn)算對(duì)象,按照從左向右的順序依次求值。和邏輯與、邏輯或以及條件運(yùn)算符一樣,逗號(hào)運(yùn)算符也規(guī)定了運(yùn)算對(duì)象求值的順序。

    對(duì)于逗號(hào)運(yùn)算符來(lái)說(shuō),首先對(duì)左側(cè)的表達(dá)式求值,然后將求值結(jié)果丟棄掉。逗號(hào)運(yùn)算符真正的結(jié)果是右側(cè)表達(dá)式的值。如果右側(cè)運(yùn)算對(duì)象是左值,那么最終的求值結(jié)果也是左值。

    逗號(hào)運(yùn)算符經(jīng)常被用在for循環(huán)當(dāng)中:

    vector<int>::size_type cnt = ivec.size(); // assign values from size... 1 to the elements in ivec for(vector<int>::size_type ix = 0; ix != ivec.size(); ++ix, --cnt)ivec[ix] = cnt;

    這個(gè)循環(huán)在for語(yǔ)句的表達(dá)式中遞增ix、遞減cnt,每次循環(huán)迭代ix和cnt相應(yīng)改變。只要ix滿足條件,我們就把當(dāng)前元素設(shè)成cnt的當(dāng)前值。

    類型轉(zhuǎn)換

    在C++語(yǔ)言中,某些類型之間有關(guān)聯(lián)。如果兩種類型有關(guān)聯(lián),那么當(dāng)程序需要其中一種類型的運(yùn)算對(duì)象時(shí),可以用另一種關(guān)聯(lián)類型的對(duì)象或值來(lái)替代。換句話說(shuō),如果兩種類型可以相互轉(zhuǎn)換(conversion),那么它們就是關(guān)聯(lián)的。

    舉個(gè)例子,考慮下面這條表達(dá)式,它的目的是將ival初始化為6:

    int ival = 3.541 + 3;//編譯器可能會(huì)警告該運(yùn)算損失了精度

    加法的兩個(gè)運(yùn)算對(duì)象類型不同:3.541的類型是double,3的類型是int。C++語(yǔ)言不會(huì)直接將兩個(gè)不同類型的值相加,而是先根據(jù)類型轉(zhuǎn)換規(guī)則設(shè)法將運(yùn)算對(duì)象的類型統(tǒng)一后再求值。上述的類型轉(zhuǎn)換是自動(dòng)執(zhí)行的,無(wú)須程序員的介入,有時(shí)甚至不需要程序員了解。因此,它們被稱作隱式轉(zhuǎn)換(implicit conversion)。

    算術(shù)類型之間的隱式轉(zhuǎn)換被設(shè)計(jì)得盡可能避免損失精度。很多時(shí)候,如果表達(dá)式中既有整數(shù)類型的運(yùn)算對(duì)象也有浮點(diǎn)數(shù)類型的運(yùn)算對(duì)象,整型會(huì)轉(zhuǎn)換成浮點(diǎn)型。在上面的例子中,3轉(zhuǎn)換成double類型,然后執(zhí)行浮點(diǎn)數(shù)加法,所得結(jié)果的類型是double。

    接下來(lái)就要完成初始化的任務(wù)了。在初始化過(guò)程中,因?yàn)楸怀跏蓟膶?duì)象的類型無(wú)法改變,所以初始值被轉(zhuǎn)換成該對(duì)象的類型。仍以這個(gè)例子說(shuō)明,加法運(yùn)算得到的double類型的結(jié)果轉(zhuǎn)換成int類型的值,這個(gè)值被用來(lái)初始化ival。由 double向 int 轉(zhuǎn)換時(shí)忽略掉了小數(shù)部分,上面的表達(dá)式中,數(shù)值6被賦給了ival。

    何時(shí)發(fā)生隱式類型轉(zhuǎn)換

    在下面這些情況下,編譯器會(huì)自動(dòng)地轉(zhuǎn)換運(yùn)算對(duì)象的類型:

    • 在大多數(shù)表達(dá)式中,比int類型小的整型值首先提升為較大的整數(shù)類型。
    • 在條件中,非布爾值轉(zhuǎn)換成布爾類型。
    • 初始化過(guò)程中,初始值轉(zhuǎn)換成變量的類型;在賦值語(yǔ)句中,右側(cè)運(yùn)算對(duì)象轉(zhuǎn)換成左側(cè)運(yùn)算對(duì)象的類型。
    • 如果算術(shù)運(yùn)算或關(guān)系運(yùn)算的運(yùn)算對(duì)象有多種類型,需要轉(zhuǎn)換成同一種類型。
    • 函數(shù)調(diào)用時(shí)也會(huì)發(fā)生類型轉(zhuǎn)換。(未來(lái)會(huì)介紹)

    算術(shù)轉(zhuǎn)換

    算術(shù)轉(zhuǎn)換(arithmetic conversion)的含義是把一種算術(shù)類型轉(zhuǎn)換成另外一種算術(shù)類型,前些章節(jié)有介紹。算術(shù)轉(zhuǎn)換的規(guī)則定義了一套類型轉(zhuǎn)換的層次,其中運(yùn)算符的運(yùn)算對(duì)象將轉(zhuǎn)換成最寬的類型

    例如,

    • 如果一個(gè)運(yùn)算對(duì)象的類型是 long double,那么不論另外一個(gè)運(yùn)算對(duì)象的類型是什么都會(huì)轉(zhuǎn)換成long double。
    • 還有一種更普遍的情況,當(dāng)表達(dá)式中既有浮點(diǎn)類型也有整數(shù)類型時(shí),整數(shù)值將轉(zhuǎn)換成相應(yīng)的浮點(diǎn)類型。

    整型提升

    整型提升(integral promotion)負(fù)責(zé)把小整數(shù)類型轉(zhuǎn)換成較大的整數(shù)類型。

    對(duì)于bool、char、signed char、 unsigned char、short和 unsigned short等類型來(lái)說(shuō),只要它們所有可能的值都能存在 int 里,它們就會(huì)提升成int 類型;否則,提升成unsigned int類型。就如我們所熟知的,布爾值false提升成0、true提升成1。

    較大的char類型(wchar_t、char16_t、char32_t)提升成int、unsigned int、long、unsigned long、long long和 unsigned long long中最小的一種類型,前提是轉(zhuǎn)換后的類型要能容納原類型所有可能的值。

    無(wú)符號(hào)類型的運(yùn)算對(duì)象

    如果某個(gè)運(yùn)算符的運(yùn)算對(duì)象類型不一致,這些運(yùn)算對(duì)象將轉(zhuǎn)換成同一種類型。但是如果某個(gè)運(yùn)算對(duì)象的類型是無(wú)符號(hào)類型,那么轉(zhuǎn)換的結(jié)果就要依賴于機(jī)器中各個(gè)整數(shù)類型的相對(duì)大小了。

    像往常一樣,首先執(zhí)行整型提升。如果結(jié)果的類型匹配,無(wú)須進(jìn)行進(jìn)一步的轉(zhuǎn)換。如果兩個(gè)(提升后的)運(yùn)算對(duì)象的類型要么都是帶符號(hào)的、要么都是無(wú)符號(hào)的,則小類型的運(yùn)算對(duì)象轉(zhuǎn)換成較大的類型。

    如果一個(gè)運(yùn)算對(duì)象是無(wú)符號(hào)類型、另外一個(gè)運(yùn)算對(duì)象是帶符號(hào)類型:

    • 其中的無(wú)符號(hào)類型不小于(>=)帶符號(hào)類型,那么帶符號(hào)的運(yùn)算對(duì)象轉(zhuǎn)換成無(wú)符號(hào)的。

      • 例如,假設(shè)兩個(gè)類型分別是unsigned int和 int,則 int類型的運(yùn)算對(duì)象轉(zhuǎn)換成unsigned int類型。
      • 需要注意的是,如果int型的值恰好為負(fù)值,其結(jié)果感人(具體轉(zhuǎn)換方法回看第二章筆記),并帶來(lái)該節(jié)描述的所有副作用。
    • 剩下的一種情況是帶符號(hào)類型大于(>)無(wú)符號(hào)類型,此時(shí)轉(zhuǎn)換的結(jié)果依賴于機(jī)器。

      • 如果無(wú)符號(hào)類型的所有值都能存在該帶符號(hào)類型中,則無(wú)符號(hào)類型的運(yùn)算對(duì)象轉(zhuǎn)換成帶符號(hào)類型。
      • 如果不能,那么帶符號(hào)類型的運(yùn)算對(duì)象轉(zhuǎn)換成無(wú)符號(hào)類型。
      • 例如,
        • 如果兩個(gè)運(yùn)算對(duì)象的類型分別是long和 unsigned int,并且int和 long的大小相同,則long類型的運(yùn)算對(duì)象轉(zhuǎn)換成unsigned int 類型;(帶符號(hào)的運(yùn)算對(duì)象轉(zhuǎn)換成無(wú)符號(hào)的)
        • 如果 long類型占用的空間比 int更多,則unsigned int類型的運(yùn)算對(duì)象轉(zhuǎn)換成long類型。

    (Note:好麻煩)

    理解算術(shù)轉(zhuǎn)換

    要想理解算術(shù)轉(zhuǎn)換,辦法之一就是研究大量的例子:

    bool flag; char cval; short sval; unsigned short usval; int ival; unsigned int uival; long lval; unsigned long ulval; float fval; double dval; 3.14159L + 'a'; //1. 'a' promoted to int, then that int converted to long double dval + ival; // ival converted to double dval + fval; // fval converted to double ival = dval; // dval converted (by truncation) to int flag = dval; // if dval is 0, then flag is false, otherwise true cval + fval; // cval promoted to int, then that int converted to float sval + cval; // sval and cval promoted to int cval + lval; // cval converted to long ival + ulval; // ival converted to unsigned long usval + ival; //2. promotion depends on the size of unsigned short and int uival + lval; // conversion depends on the size of unsigned int and long
  • 在第一個(gè)加法運(yùn)算中,小寫字母’a’是char型的字符常量,它其實(shí)能表示一個(gè)數(shù)字值。到底這個(gè)數(shù)字值是多少完全依賴于機(jī)器上的字符集,在我們的環(huán)境中,'a’對(duì)應(yīng)的數(shù)字值是97。當(dāng)把’a’和一個(gè) long double類型的數(shù)相加時(shí),char類型的值首先提升成int類型,然后int類型的值再轉(zhuǎn)換成long double類型。最終我們把這個(gè)轉(zhuǎn)換后的值與那個(gè)字面值相加。

  • 最后的兩個(gè)含有無(wú)符號(hào)類型值的表達(dá)式也比較有趣,它們的結(jié)果依賴于機(jī)器。(Note:有趣?Excuse me.)

  • 其他隱式顯示轉(zhuǎn)換

    除了算術(shù)轉(zhuǎn)換之外還有幾種隱式類型轉(zhuǎn)換,包括如下幾種。

    數(shù)組轉(zhuǎn)換成指針:在大多數(shù)用到數(shù)組的表達(dá)式中,數(shù)組自動(dòng)轉(zhuǎn)換成指向數(shù)組首元素的指針:

    int ia[10]; // array of ten ints int* ip = ia; // convert ia to a pointer to the first element

    當(dāng)數(shù)組被用作decltype關(guān)鍵字的參數(shù),或者作為取地址符(&)、sizeof及typeid等運(yùn)算符的運(yùn)算對(duì)象時(shí),上述轉(zhuǎn)換不會(huì)發(fā)生。同樣的,如果用一個(gè)引用來(lái)初始化數(shù)組,上述轉(zhuǎn)換也不會(huì)發(fā)生。在將來(lái)會(huì)看到,當(dāng)在表達(dá)式中使用函數(shù)類型時(shí)會(huì)發(fā)生類似的指針轉(zhuǎn)換。

    指針的轉(zhuǎn)換:C++還規(guī)定了幾種其他的指針轉(zhuǎn)換方式,包括

    • 常量整數(shù)值0或者字面值nullptr能轉(zhuǎn)換成任意指針類型;
    • 指向任意非常量的指針能轉(zhuǎn)換成void*;
    • 指向任意對(duì)象的指針能轉(zhuǎn)換成const void*。
    • 未來(lái)將要介紹,在有繼承關(guān)系的類型間還有另外一種指針轉(zhuǎn)換的方式。

    轉(zhuǎn)換成布爾類型:存在一種從算術(shù)類型或指針類型向布爾類型自動(dòng)轉(zhuǎn)換的機(jī)制。如果指針或算術(shù)類型的值為0,轉(zhuǎn)換結(jié)果是false;否則轉(zhuǎn)換結(jié)果是true:

    char *cp = get_string(); if (cp) /* ... */ // true if the pointer cp is not zero while (*cp) /* ... */ // true if *cp is not the null character

    轉(zhuǎn)換成常量:允許將指向非常量類型的指針轉(zhuǎn)換成指向相應(yīng)的常量類型的指針,對(duì)于引用也是這樣。也就是說(shuō),如果T是一種類型,我們就能將指向T的指針或引用分別轉(zhuǎn)換成指向const T的指針或引用:

    int i; const int &j = i; // convert a nonconst to a reference to const int const int *p = &i; // convert address of a nonconst to the address of a const int &r = j, *q = p; // error: conversion from const to nonconst not allowed

    相反的轉(zhuǎn)換并不存在,因?yàn)樗噲D刪除掉底層const。

    類類型定義的轉(zhuǎn)換:類類型能定義由編譯器自動(dòng)執(zhí)行的轉(zhuǎn)換,不過(guò)編譯器每次只能執(zhí)行一種類類型的轉(zhuǎn)換。在第7章,我們將看到一個(gè)例子,如果同時(shí)提出多個(gè)轉(zhuǎn)換請(qǐng)求,這些請(qǐng)求將被拒絕。

    我們之前的程序已經(jīng)使用過(guò)類類型轉(zhuǎn)換:一處是在需要標(biāo)準(zhǔn)庫(kù)string類型的地方使用C風(fēng)格字符串;另一處是在條件部分讀入istream:

    string s, t = "a value"; // character string literal converted to type string while (cin >> s) // while condition converts cin to bool

    條件 (cin>>s) 讀入cin的內(nèi)容并將cin作為其求值結(jié)果。條件部分本來(lái)需要一個(gè)布爾類型的值,但是這里實(shí)際檢查的是istream類型的值。

    幸好,IO庫(kù)定義了從istream向布爾值轉(zhuǎn)換的規(guī)則,根據(jù)這一規(guī)則,cin自動(dòng)地轉(zhuǎn)換成布爾值。所得的布爾值到底是什么由輸入流的狀態(tài)決定,如果最后一次讀入成功,轉(zhuǎn)換得到的布爾值是 true;相反,如果最后一次讀入不成功,轉(zhuǎn)換得到的布爾值是false。

    顯示轉(zhuǎn)換

    有時(shí)我們希望顯式地將對(duì)象強(qiáng)制轉(zhuǎn)換成另外一種類型。例如,如果想在下面的代碼中執(zhí)行浮點(diǎn)數(shù)除法:

    int i,j; double slope = i/j;

    就要使用某種方法將i和/或j顯式地轉(zhuǎn)換成double,這種方法稱作強(qiáng)制類型轉(zhuǎn)換(cast)。

    WARNING:雖然有時(shí)不得不使用強(qiáng)制類型轉(zhuǎn)換,但這種方法本質(zhì)上是非常危險(xiǎn)的。

    命名的強(qiáng)制類型轉(zhuǎn)換

    一個(gè)命名的強(qiáng)制類型轉(zhuǎn)換具有如下形式:

    cast-name<type>(expression);

    其中,type是轉(zhuǎn)換的目標(biāo)類型而expression是要轉(zhuǎn)換的值。如果type是引用類型,則結(jié)果是左值。cast-name是 static_cast、dynamic_cast、const_cast和reinterpret_cast中的一種。cast-name指定了執(zhí)行的是哪種轉(zhuǎn)換。

    dynamic_cast將在最后一章介紹。

    static_cast

    任何具有明確定義的類型轉(zhuǎn)換,只要不包含底層const,都可以使用static_cast。例如,通過(guò)將一個(gè)運(yùn)算對(duì)象強(qiáng)制轉(zhuǎn)換成double類型就能使表達(dá)式執(zhí)行浮點(diǎn)數(shù)除法:

    //進(jìn)行強(qiáng)制類型轉(zhuǎn)換以便執(zhí)行浮點(diǎn)數(shù)除法 double slope = static_cast<double>(j) / i;

    當(dāng)需要把一個(gè)較大的算術(shù)類型賦值給較小的類型時(shí),static_cast非常有用。此時(shí),強(qiáng)制類型轉(zhuǎn)換告訴程序的讀者和編譯器:我們知道并且不在乎潛在的精度損失。一般來(lái)說(shuō),如果編譯器發(fā)現(xiàn)一個(gè)較大的算術(shù)類型試圖賦值給較小的類型,就會(huì)給出警告信息;但是當(dāng)我們執(zhí)行了顯式的類型轉(zhuǎn)換后,警告信息就會(huì)被關(guān)閉了。

    static_cast對(duì)于編譯器無(wú)法自動(dòng)執(zhí)行的類型轉(zhuǎn)換也非常有用。例如,我們可以使用static_cast找回存在于void*指針中的值:

    void* p = &d;//正確:任何非常量對(duì)象的地址都能存入void* //正確:將void*轉(zhuǎn)換回初始的指針類型 double *dp = static_cast<double*>(p) ;

    當(dāng)我們把指針存放在void*中,并且使用static_cast將其強(qiáng)制轉(zhuǎn)換回原來(lái)的類型時(shí),應(yīng)該確保指針的值保持不變。也就是說(shuō),強(qiáng)制轉(zhuǎn)換的結(jié)果將與原始的地址值相等,因此我們必須確保轉(zhuǎn)換后所得的類型就是指針?biāo)傅念愋汀?/p>

    類型一旦不符,將產(chǎn)生未定義的后果。

    (Note:Java的類轉(zhuǎn)換錯(cuò)誤會(huì)拋出ClassCastException)

    const_cast

    const_cast只能改變運(yùn)算對(duì)象的底層const(第二章內(nèi)容):

    const char *pc; char *p = const_cast<char*>(pc);// 正確:但是通過(guò)p 寫值是未定義的行為

    對(duì)于將常量對(duì)象轉(zhuǎn)換成非常量對(duì)象的行為,我們一般稱其為“去掉const性質(zhì)(cast away the const)”。一旦我們?nèi)サ袅四硞€(gè)對(duì)象的const性質(zhì),編譯器就不再阻止我們對(duì)該對(duì)象進(jìn)行寫操作了。如果對(duì)象本身不是一個(gè)常量,使用強(qiáng)制類型轉(zhuǎn)換獲得寫權(quán)限是合法的行為。

    然而如果對(duì)象是一個(gè)常量,再使用const_cast執(zhí)行寫操作就會(huì)產(chǎn)生未定義的后果。

    只有const_cast能改變表達(dá)式的常量屬性,使用其他形式的命名強(qiáng)制類型轉(zhuǎn)換改變表達(dá)式的常量屬性都將引發(fā)編譯器錯(cuò)誤。同樣的,也不能用const_cast改變表達(dá)式的類型:

    const char *cp; //錯(cuò)誤: static_cast不能轉(zhuǎn)換掉const性質(zhì) char *q = static_cast<char*> (cp) ;static_cast<string>(cp);//正確:字符串字面值轉(zhuǎn)換成string類型const_cast<string>(cp);//錯(cuò)誤:const_cast只改變常量屬性,下行才對(duì) const_cast<char *>(cp);

    const_cast常常用于有函數(shù)重載的上下文中,關(guān)于函數(shù)重載將在第六章進(jìn)行詳細(xì)介紹。

    reinterpret_cast

    reinterpret_cast通常為運(yùn)算對(duì)象的位模式提供較低層次上的重新解釋。舉個(gè)例
    子,假設(shè)有如下的轉(zhuǎn)換

    int *ip; char *pc = reinterpret_cast<char*>(ip);

    我們必須牢記pc所指的真實(shí)對(duì)象是一個(gè)int而非字符,如果把pc當(dāng)成普通的字符指針使用就可能在運(yùn)行時(shí)發(fā)生錯(cuò)誤。例如:

    string str(pc);

    可能導(dǎo)致異常的運(yùn)行時(shí)行為。

    使用reinterpret_cast是非常危險(xiǎn)的,用pc初始化str的例子很好地證明了這一點(diǎn)。其中的關(guān)鍵問(wèn)題是類型改變了,但編譯器沒(méi)有給出任何警告或者錯(cuò)誤的提示信息。當(dāng)我們用一個(gè)int的地址初始化pc時(shí),由于顯式地聲稱這種轉(zhuǎn)換合法,所以編譯器不會(huì)發(fā)出任何警告或錯(cuò)誤信息。接下來(lái)再使用pc時(shí)就會(huì)認(rèn)定它的值是char*類型,編譯器沒(méi)法知道它實(shí)際存放的是指向int的指針

    最終的結(jié)果就是,在上面的例子中雖然用pc初始化 str沒(méi)什么實(shí)際意義,甚至還可能引發(fā)更糟糕的后果,但僅從語(yǔ)法上而言這種操作無(wú)可指摘。查找這類問(wèn)題的原因非常困難,如果將ip強(qiáng)制轉(zhuǎn)換成pc的語(yǔ)句和用pc初始化string對(duì)象的語(yǔ)句分屬不同文件就更是如此。Thus, the initialization of str with pc is absolutely correct—albeit盡管 in this case meaningless or worse! Tracking down the cause of this sort of problem can prove extremely difficult, especially if the cast of ip to pc occurs in a file separate from the one in which pc is used to initialize a string.

    無(wú)可指摘

    指沒(méi)有什么可以指責(zé)的。表示做得妥當(dāng)。

    link

    WARNING:reinterpret_cast本質(zhì)上依賴于機(jī)器,要想安全地使用reinterpret_cast必須對(duì)涉及的類型和編譯器實(shí)現(xiàn)轉(zhuǎn)換的過(guò)程都非常了解。

    (Note:沒(méi)看懂reinterpret_cast作用,個(gè)人理解為"強(qiáng)人所難的強(qiáng)制類型轉(zhuǎn)換",TODO:查查它的作用)

    建議:避免強(qiáng)制類型轉(zhuǎn)換

    強(qiáng)制類型轉(zhuǎn)換干擾了正常的類型檢查,因此我們強(qiáng)烈建議程序員避免使用強(qiáng)制類型轉(zhuǎn)換。

    這個(gè)建議對(duì)于reinterpret_cast尤其適用,因?yàn)榇祟愵愋娃D(zhuǎn)換總是充滿了風(fēng)險(xiǎn)。

    在有重載函數(shù)的上下文中使用const_cast無(wú)可厚非,關(guān)于這一點(diǎn)將在第六章中詳細(xì)介紹;但是在其他情況下使用const_cast也就意味著程序存在某種設(shè)計(jì)缺陷。其他強(qiáng)制類型轉(zhuǎn)換,比如 static_cast 和dynamic_cast,都不應(yīng)該頻繁使用。

    每次書寫了一條強(qiáng)制類型轉(zhuǎn)換語(yǔ)句,都應(yīng)該反復(fù)斟酌能否以其他方式實(shí)現(xiàn)相同的目標(biāo)。就算實(shí)在無(wú)法避免,也應(yīng)該盡量限制類型轉(zhuǎn)換值的作用域,并且記錄對(duì)相關(guān)類型的所有假定,這樣可以減少錯(cuò)誤發(fā)生的機(jī)會(huì)。

    舊式的強(qiáng)制類型轉(zhuǎn)換

    在早期版本的C++語(yǔ)言中,顯式地進(jìn)行強(qiáng)制類型轉(zhuǎn)換包含兩種形式:

    type (expr); // function-style cast notation (type) expr; // C-language-style cast notation

    根據(jù)所涉及的類型不同,舊式的強(qiáng)制類型轉(zhuǎn)換分別具有與const_cast 、static_cast或reinterpret_cast相似的行為。當(dāng)我們?cè)谀程巿?zhí)行舊式的強(qiáng)制類型轉(zhuǎn)換時(shí),如果換成const_cast和 static_cast也合法,則其行為與對(duì)應(yīng)的命名轉(zhuǎn)換一致。

    如果替換后不合法,則舊式強(qiáng)制類型轉(zhuǎn)換執(zhí)行與reinterpret_cast類似的功能:

    int *ip; char *pc = (char*) ip; // ip is a pointer to int

    的效果與使用reinterpret_cast一樣。

    WARNING:與命名的強(qiáng)制類型轉(zhuǎn)換相比,舊式的強(qiáng)制類型轉(zhuǎn)換從表現(xiàn)形式上來(lái)說(shuō)不那么清晰明了,容易被看漏,所以一旦轉(zhuǎn)換過(guò)程出現(xiàn)問(wèn)題,追蹤起來(lái)也更加困難。

    (Note:有新的就用新的,舊的擱置吧)

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

    結(jié)合律運(yùn)算符功能用法


    ::
    ::
    ::
    全局作用域
    類作用域
    命名空間作用域
    ::name
    class::name
    namespace::name




    .
    ->
    []
    ()
    ()
    成員選擇
    成員選擇
    下標(biāo)
    函數(shù)調(diào)用
    類型構(gòu)造
    object.member
    pointer->member
    expr[expr]
    name(expr_list)
    type(expr_list)




    +_

    typeid
    typeid
    explicit cast
    后置遞增運(yùn)算
    后置遞減運(yùn)算
    類型ID
    運(yùn)行時(shí)類型ID
    類型轉(zhuǎn)換
    lvalue++
    lvalue–
    typeid(type)
    typeid(expr)
    cast_name<type>(expr)
















    ++

    ~
    !
    -
    +
    *
    &
    ()
    sizeof
    sizeof
    sizeof…
    new
    new[]
    delete
    delete[]
    noexcept
    前置遞增運(yùn)算
    前置遞減運(yùn)算
    位求反
    邏輯非
    一元負(fù)號(hào)
    一元正號(hào)
    解引用
    取地址
    類型轉(zhuǎn)換
    對(duì)象的大小
    類型的大小
    參數(shù)包的大小
    創(chuàng)建對(duì)象
    創(chuàng)建數(shù)組
    釋放對(duì)象
    釋放數(shù)組
    能否拋出異常
    ++lvalue
    –lvalue
    ~expr
    !expr
    -expr
    +expr
    *expr
    &lvalue
    (type)expr
    sizeof expr
    sizeof(type)
    sizeof–(name)
    new type
    new type[size]
    delete expr
    delete[] expr
    noexcept(expr)

    ->*
    .*
    指向成員選擇的指針
    指向成員選擇的指針
    ptr->*ptr_to_member
    obj.*ptr_to_member


    *
    /
    %
    乘法
    除法
    取模(取余)
    expr * expr
    expr / expr
    expr % expr

    +
    -
    加法
    減法
    expr + expr
    expr - expr

    <<
    >>
    向左移位
    向右移位
    expr << expr
    expr >> expr



    <
    <=
    >
    >=
    小于
    小于等于
    大于
    大于等于
    expr < expr
    expr <= expr
    expr > expr
    expr >= expr

    ==
    !=
    相等
    不想等
    expr == expr
    expr != expr
    &位與expr & expr
    ^位異或expr ^ expr
    |位或expr | expr
    &&邏輯與expr && expr
    ||邏輯或expr || expr
    ? :條件expr ? expr : expr
    =賦值lvalue = expr



    *=,/=,%=
    +=,-=
    <<=,>>=
    &=,|=,^=
    復(fù)合賦值lvalue += expr等
    throw拋出異常throw expr
    逗號(hào)expr, expr

    說(shuō)明:

  • 結(jié)合律中,“左”表示從左到右,“右”表示從右到左。
  • 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)

    總結(jié)

    以上是生活随笔為你收集整理的《C++ Primer 5th》笔记(4 / 19):表达式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。