C++Primer学习笔记:第4章 表达式
-
表達(dá)式由一個或多個運算對象組成,對表達(dá)式求值將得到一個結(jié)果。字面值和變量是最簡單的表達(dá)式,其結(jié)果就是字面值和變量的值。把一個運算符和一個或多個運算對象組合起來可以生成較復(fù)雜的表達(dá)式。
-
重載運算符包括運算對象的類型和返回值的類型,都是由該運算符定義的;但是運算對象的個數(shù)、運算符的優(yōu)先級和結(jié)合律都是無法改變的
-
當(dāng)一個對象被用作右值的時候,用的是對象的值(內(nèi)容);當(dāng)對象被用作左值的時候,用的是對象的身份(在內(nèi)存中的位置)。需要右值的地方可以用左值來代替,但是不能把右值當(dāng)成左值使用。
- 賦值運算符需要一個(非常量)左值作為其左側(cè)運算對象,得到的結(jié)果也仍然是一個左值
- 取地址符作用于一個左值運算對象,返回一個指向該運算對象的指針,這個指針是一個右值
- 內(nèi)置解引用運算符、下標(biāo)運算符、迭代器解引用運算符、string和vector的下標(biāo)運算符的求值結(jié)果都是左值
- 內(nèi)置類型和迭代器的遞增遞減運算符作用于左值運算對象,其前置版本所得到的結(jié)果也是左值
-
如果表達(dá)式的求值結(jié)果是左值,decltype作用于該表達(dá)式得到一個引用類型
int a = 1; int *p = nullptr; decltype(*p) b = a; //b是int &類型 decltype(&p) c = nullptr; //c是int **類型 -
優(yōu)先級規(guī)定了運算對象的組合方式,但是沒有說明運算對象按照什么順序求值,在大多數(shù)情況下,不會明確指定求值的順序。對于表達(dá)式int i = f1() * f2(),我們知道f1()和f2()一定在執(zhí)行乘法之前被調(diào)用,但是我們無法直到到底f1和f2的執(zhí)行先后順序。對于那些沒有指定執(zhí)行順序的運算符來說,如果表達(dá)式指向并修改了同一個對象將會引發(fā)錯誤并產(chǎn)生未定義行為(UB)。因為<<運算符沒有明確規(guī)定何時以及如何對運算對象求值,因此cout << i << " " << ++i << endl是未定義的。
-
有四種運算符明確規(guī)定了運算對象的求值順序:邏輯與&&、邏輯或||、條件(三元)運算符?:、逗號運算符,
-
運算對象的求值順序與優(yōu)先級和結(jié)合律無關(guān),對每個運算對象的運算結(jié)果的計算順序是通過優(yōu)先級和結(jié)合律決定,但是對運算對象的運算順序是不確定的。如果在一個表達(dá)式中有多個運算對象涉及對同一個對象的運算,那么很容易產(chǎn)生未定義的行為。例如f() + g() * h() + j()中,對這些函數(shù)的返回值的運算順序是確定的,但是對這些函數(shù)的運算順序是不確定的。
-
書寫復(fù)合表達(dá)式的準(zhǔn)則:
- 拿不準(zhǔn)的時候最好使用括號來強制讓表達(dá)式的組合關(guān)系復(fù)合程序邏輯的要求
- 如果改變了某個對象的值,在表達(dá)式的其他地方不要再使用這個運算對象。這個其他地方是不包括當(dāng)改變運算對象的子表達(dá)式本身就是另外一個子表達(dá)式的運算對象。例如:*++iter
-
算數(shù)運算符的運算對象和求值結(jié)果都是右值。
-
一元正號運算符、加法運算符和減法運算符都能作用于指針。當(dāng)一元正號運算符作用于一個指針或者算術(shù)值時,返回運算對象的一個(提升后的)副本
bool b = true; bool b2 = -b; //相當(dāng)于b2 = -1,所以b2為真 -
整數(shù)相除結(jié)果還是整數(shù),參與取余運算的運算對象必須是整數(shù)類型
-
C++11新標(biāo)準(zhǔn)規(guī)定商一律向0取整(即直接切除小數(shù)部分)
-
根據(jù)取余運算的定義,如果m和n是整數(shù)且n非0,則表達(dá)式(m/n)*n+m%n的值和m相同。在C++11新標(biāo)準(zhǔn)中,除了-m導(dǎo)致溢出的特殊情況,其他時候(-m)/n和m/(-n)都等于-(m/n),m%(-n)等于m%n,(-m)%n等于-(m%n)
-
邏輯運算符作用于任何能轉(zhuǎn)換成布爾值的類型。邏輯運算符和關(guān)系運算符的返回值都是布爾類型。運算對象和求值結(jié)果都是右值
-
邏輯與與邏輯或都采用短路求值。可以用左側(cè)運算對象來保證右側(cè)運算對象求值過程的正確性和安全性
-
使用范圍for循環(huán)時盡可能聲明成引用類型,能夠避免對元素的拷貝
-
進(jìn)行比較運算時除非比較的對象是布爾類型,否則不要使用布爾字面值true和false作為運算對象
-
char *cp; if (cp && *cp) {}表示判斷指針cp所指向的數(shù)組是否為空,如果C字符串?dāng)?shù)組為空,則數(shù)組中只有一個空字符\0
-
復(fù)制運算的結(jié)果是它左側(cè)的運算對象,并且是一個左值。如果賦值運算符左右兩個運算對象類型不同,則右側(cè)運算對象將轉(zhuǎn)換成左側(cè)運算對象的類型。
-
C++11新標(biāo)準(zhǔn)允許使用花括號括起來的初始值列表作為賦值語句的右側(cè)運算對象。如果左側(cè)運算對象是內(nèi)置類型,那么初始值列表最多只能包含一個值,而且該值不能有丟失信息的風(fēng)險(所占用的空間不應(yīng)該大于目標(biāo)類型的空間)。對于類類型來說,賦值運算的細(xì)節(jié)由類本身決定。vector模板重載了賦值運算符而且可以接收初始值列表,當(dāng)賦值發(fā)生時用右側(cè)運算對象的元素替換左側(cè)運算對象的元素。無論左側(cè)運算對象的類型是什么,初始值列表都可以為空,此時編譯器創(chuàng)建一個值初始化的臨時量并將其賦給左側(cè)運算對象(我認(rèn)為沒有默認(rèn)構(gòu)造函數(shù)的類可能不行)
vector<int> a; a = {1, 2, 3, 4}; a = {1}; a = {1, 2, 3, 4, 5, 6, 7}; -
賦值運算滿足右結(jié)合律。因為賦值運算符的優(yōu)先級低于關(guān)系運算符的優(yōu)先級,所以在條件語句中,賦值部分通常應(yīng)該加上括號
int i; while(1 == (i = getValue())) {// } -
使用復(fù)合運算符只求值一次,使用普通的運算符則求值兩次(一次計算一次賦值)。因此盡量使用賦值運算符。
-
++和--可以用于迭代器,很多迭代器本身不支持算術(shù)運算。前置版本將對象本身作為左值返回,后置版本則將對象原始值的副本作為右值返回。如果不需要保存未修改版本的值,盡量使用前置版本不使用后置版本。cout << *iter++ << endl;是一種被廣泛使用的有效的寫法。C++程序追求簡潔、摒棄冗長,因此C++程序員應(yīng)該習(xí)慣于這種寫法。
-
因為大多數(shù)的運算符都沒有規(guī)定求值順序,因此同一條表達(dá)式中最好在一個地方修改對象的值(且在改變這個對象以后就不要再進(jìn)行使用),否則很容易產(chǎn)生未定義的行為(&& || , ?:除外)
-
ptr->item等價于(*ptr).item,.運算符的優(yōu)先級更高,因此括號必不可少。箭頭運算符作用于一個指針, 結(jié)果是一個左值。點運算符作用于左值則結(jié)果是左值,作用于右值則結(jié)果是右值
-
條件運算符cond ? expr1 : expr2,其中cond是判斷條件的表達(dá)式,而expr1和expr2是兩個類型相同或者可能轉(zhuǎn)換為某個公共類型的表達(dá)式。條件運算符是有求值順序的,而且條件運算符只會對expr1和expr2中的一個求值。當(dāng)條件運算符的兩個表達(dá)式都是左值或者能夠轉(zhuǎn)換成同一種左值類型時,運算的結(jié)果是左值,否則運算結(jié)果是右值
-
條件運算符滿足右結(jié)合律,意味著運算對象一般按照從右往左的順序結(jié)合。條件運算的嵌套最好別超過二到三層。
sting final_grade = (grade > 90) ? "high pass": (grade < 60) ? "fail" : "pass";條件運算符的優(yōu)先級比較低,因此最好在復(fù)合表達(dá)式中使用條件運算符的時候加上括號
-
位運算符作用于整數(shù)類型的運算對象,并把運算對象看成是二進(jìn)制位的集合(bitset可以表示任意大小的二進(jìn)制位集合,也可以使用位運算符)
~ 位求反 ~expr << 左移 expr1 << epxr2 >> 右移 & 位與 ^ 位異或 | 位或如果運算對象是“小整型”,則它的值會被自動提升成較大的整數(shù)類型,如果運算對象是帶符號的,有可能產(chǎn)生未定義的行為(如果有可能為負(fù)數(shù)),因此建議將位運算用于處理無符號類型
-
移位運算符將經(jīng)過移動的(可能進(jìn)行了提升)左側(cè)運算對象的拷貝作為球直接過。其中右側(cè)的運算對象一定不能為負(fù),而且值必須嚴(yán)格小于結(jié)果的位數(shù),否則就會產(chǎn)生未定義的行為。二進(jìn)制位或者向左移(<<)或者向右移(>>),移出邊界外的位就被舍棄掉了。<<在右側(cè)插入值為0的二進(jìn)制位,>>對無符號數(shù)來講是 在左側(cè)添加0,但是對帶符號類型依賴環(huán)境
-
unsigned char在位運算中會被提升為unsigned int,unsigned long在任何機(jī)器上都至少擁有32位。1UL << x制造一個第x為1,其他位都為0的數(shù)字,對這個數(shù)字取反可以得到第x位為0,其他位都為1的數(shù)字。然后通過|或&進(jìn)行操作
-
sizeof運算符返回一條表達(dá)式或一個類型名字所占的字節(jié)數(shù)。sizeof運算符滿足又結(jié)合律,得到的是一個size_t類型的常量表達(dá)式。運算符的運算對象有兩種形式:
sizeof (type) sizeof expr在第二種類型中,sizeof返回的是表達(dá)式結(jié)果類型的大小,與眾不同的一點是sizeof并不實際計算其運算對象的值。因此解引一個無效指針仍然是一種安全的行為,因為指針實際上沒有被真正使用。sizeof不需要真的解引用指針也能知道它所指向?qū)ο蟮念愋汀?/p> Sales_data data, *p; sizeof *p; //返回Sales_data的空間大小 sizeof data.revenue sizeof Sales_data::revenue //同上
在C++11新標(biāo)準(zhǔn)勻速我們使用作用域運算符來獲取類成員的大小。通常情況下只有通過類的對象才能訪問到類的成員,但是sizeof運算符無需我們提供一個具體的對象
-
對數(shù)組執(zhí)行sizeof運算得到整個數(shù)組所占空間的大小,等價于對數(shù)組中所有元素執(zhí)行一次sizeof運算并將所得的結(jié)果求和。注意,sizeof運算符不會把數(shù)組轉(zhuǎn)換成指針來處理。因此可以用數(shù)組的大小除以單個元素的大小得到數(shù)組中元素的個數(shù)
constexpr size_t sz = sizeof(ia)/sizeof(*ia); int arr2[sz]; //arr2的大小和ia一樣 -
對string對象或vector對象執(zhí)行sizeof運算只返回該類型固定部分的大小,不會計算對象中的元素占用了多少空間。實測了一下發(fā)現(xiàn)對vector(string應(yīng)該也一樣)使用sizeof返回固定的大小,對于vector<int>返回24
-
逗號運算符有兩個運算對象,按照從左往右的順序依次求值(同&& || ?:一樣規(guī)定了運算順序)。先對左側(cè)的運算對象求值,然后將求值結(jié)果拋棄,真正的運算結(jié)果是右側(cè)表達(dá)式的值,如果右側(cè)運算對象是左值,則最終的求值結(jié)果也是左值。
-
隱式類型轉(zhuǎn)換:
- 大多數(shù)表達(dá)式中,比int類型小的整型值首先提升為較大的整數(shù)類型
- 在條件中,非布爾值轉(zhuǎn)換為布爾值
- 初始化和賦值語句中,右側(cè)運算對象轉(zhuǎn)換成左側(cè)運算對象的類型
- 如果算術(shù)運算或關(guān)系運算的運算對象有多種類型,需要轉(zhuǎn)換為同一種類型
-
整型提升負(fù)責(zé)把小整數(shù)類型轉(zhuǎn)換成較大的整數(shù)類型,整數(shù)類型中比int小的類型如果參與運算,如果可以放到int里就會提升成int,否則提升成unsigned int
-
隱式類型轉(zhuǎn)換:
- 數(shù)組轉(zhuǎn)換成指針:在大多數(shù)用到數(shù)組的表達(dá)式中,數(shù)組自動轉(zhuǎn)換成指向數(shù)組首元素的指針。當(dāng)數(shù)組被用作decltype關(guān)鍵字的參數(shù),或者作為取地址符&、sizeof以及typeid等運算符的運算對象時,以及用一個引用來初始化數(shù)組時,上述轉(zhuǎn)換不會發(fā)生
- 常量整數(shù)值0或者字面值nullptr能夠轉(zhuǎn)換為任意指針類型,指向任意非常量的指針能夠轉(zhuǎn)化成void*,指向任意對象的指針能夠轉(zhuǎn)化為const void*
-
如果有的轉(zhuǎn)換不會隱式自動轉(zhuǎn)換就需要強制類型轉(zhuǎn)換,雖然有時候不得不使用強制類型轉(zhuǎn)換,但是這種方法本質(zhì)上是非常危險的。
-
一個命名的強制類型轉(zhuǎn)換具有如下形式:
cast-name<type>(expression);type是要轉(zhuǎn)換的類型,expression是要轉(zhuǎn)換的值,如果type是引用類型,則結(jié)果是左值。
cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一種
-
static_cast:任何具有明確定義的類型轉(zhuǎn)換,只要不包含底層const,就可以使用static_cast。
-
把較大的算數(shù)類型賦給較小的類型
double slope = static_cast<double>(j) -
無法自動執(zhí)行的類型轉(zhuǎn)換
void *p = &d; double *dp = static_cast<double*>(p);
-
-
const_cast:只能改變運算對象的底層const,用于將常量對象轉(zhuǎn)換為非常量對象
const char *pc; char *p = const_cast<char*>(pc);只有const_cast可以改變表達(dá)式的常量屬性
const_cast常用于有函數(shù)重載的上下文中
-
reinterpret_cast:通常為運算對象的位模式提供較低層次上的重新解釋,不太好用,不建議使用
-
-
應(yīng)該盡量避免使用強制類型轉(zhuǎn)換,且不推薦使用舊式類型轉(zhuǎn)換:與命名的強制類型轉(zhuǎn)換相比,舊式的強制類型轉(zhuǎn)換從表現(xiàn)形式上來說不那么清晰明了,容易被看漏。
int x; char y = (char)x;
總結(jié)
以上是生活随笔為你收集整理的C++Primer学习笔记:第4章 表达式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++Primer学习笔记:第3章 字符
- 下一篇: C++Primer学习笔记:第5章 语句