《C++primer(第五版)》學習之路-第四章:運算式,c語言
【 聲明:著作權,轉載請標明出處,請勿用於商業用途。 聯絡信箱:libin493073668@sina.com】
4.1 基礎
1.運算式由一個或多個運算對象(operand)組成,對錶達式求值將得到一個結果(result)。字面值和變數是最簡單的運算式(expression),其結果就是字面值和變數的值。把一個運算子(operator)和一個或多個運算對象組合起來可以產生較複雜的運算式。
2.C++定義了一元運算子(unary operator)和二元運算子(binary operator)。作用於一個運算對象的運算子是一元運算子,如取地址符(&)和解引用符(*);作用於兩個運算對象的運算子是二元運算子,如相等運算子(==)和乘法運算子(*)。除此之外,還有一個作用於三個運算對象的三元運算子。函數調用也是一種特殊的運算子,它對運算對象的數量沒有限制。
一些符號既能作為一元運算子也能作為二元運算子。以符號*為例,作為一元運算子時執行解引用操作,作為二元運算子時執行乘法操作。一個符號到底是一元運算子還是二元運算子由它的上下文決定。對於這類符號來說,它的兩種用法互不相干,完全可以當成兩個不同的符號。
3.在運算式求值的過程中,運算對象常常由一種類型轉換成另外一種類型。
4.C++語言定義了運算子作用於內建類型和複合類型的運算對象時所執行的操作。當運算子作用於類類型的運算對象時,使用者可以自行定義其含義。因為這種自訂的過程事實上是為已存在的運算子賦予了另外一層含義,所以稱之為重載運算子(overloaded operator)。IO庫的>>和<<運算子以及string對象、vector對象和迭代器使用的運算子都是重載的運算子。
5.C++的運算式要不然是右值(rvalue,讀作"are-value"),要不然就是左值(lvalue,讀作"ell-value")。
當一個對象被用作右值的時候,用的是對象的值(內容);當對象被用作左值的時候,用的是對象的身份(在記憶體中的位置)。
6.運算式最終的值依賴於其子運算式的組合方式。高優先順序運算子的運算對象要比低優先順序運算子的運算對象更為緊密地組合在一起。如果優先順序相同,則其組合規則由結合律確定。
7.以下兩條經驗準則對書寫複合運算式有益:
1.拿不準的時候最好用括弧來強制讓運算式的組合關係符合程式邏輯的要求。
2.如果改變了某個運算對象的值,在運算式的其他地方不要再使用這個運算對象。
4.2 算術運算子
1.
2.運算子%俗稱"取餘"或"模數"運算子,負責計算兩個整數相除所得的餘數,參與取餘運算的運算對象必須是整數類型。C++11新標準則規定商一律向0取整(即直接切除小數部分)。
除了 m導致溢出的特殊情況,其他時候(-m)/n和m/(-n)都等於-(m/n),m%(-n)等於m%n,(-m)%n等於-(m%n)。
4.3 邏輯和關係運算子
1.
2.對於邏輯與運算子來說,若且唯若左側運算對象為真時才對右側運算對象求值。
對於邏輯或運算子來說,若且唯若左側運算對象為假時才對右側運算對象求值。
3.進行比較運算時除非比較的對象是布爾類型,否則不要使用布爾字面值true和false作為運算對象。
4.4 賦值運算子
1.每種運算子都有相應的複合賦值形式:
+= -= *= /= %= //算術運算子 <<= >>= &= ^= |= //位元運算符
4.5 遞增和遞減運算子
1.增運算子(++)和遞減運算子( )為對象的加1和減1操作提供了一種簡潔的書寫形式。
2.建議:除非必須,否則不用遞增遞減運算子的後置版本
有C語言背景的讀者可能對優先使用前置版本遞增運算子有所疑問,其實原因非常簡單:前置版本的遞增運算子避免了不必要的工作,它把值加1後直接返回改變了的運算對象。與之相比,後置版本需要將原始值儲存下來以便於返回這個未修改的內容。如果我們不需要修改前的值,那麼後置版本的操作就是一種浪費。
對於整數和指標類型來說,編譯器可能對這種額外的工作進行一定的最佳化;但是對於相對複雜的迭代器類型,這種額外的工作就消耗巨大了。建議養成使用前置版本的習慣,這樣不僅不需要擔心效能的問題,而且更重要的是寫出的代碼會更符合編程的初衷。
4.6 成員訪問運算子
1.點運算子和箭頭運算子都可用於訪問成員,其中,點運算子擷取類對象的一個成員;箭頭運算子與點運算子有關,運算式ptr->mem等價於(*ptr).mem。
2.箭頭運算子作用於一個指標類型的運算對象,結果是一個左值。點運算子分成兩種情況:如果成員所屬的對象是左值,那麼結果是左值;反之,如果成員所屬的對象是右值,那麼結果是右值。
4.7 條件運算子
1.條件運算子(? :)允許我們把簡單的if-else邏輯嵌入到單個運算式當中,條件運算子按照如下形式使用:
cond ? expr1 : expr2;
其中cond是判斷條件的運算式,而expr1和expr2是兩個類型相同或可能轉為某個公用類型的運算式。條件運算子的執行過程是:首先求cond的值,如果條件為真對expr1求值並返回該值,否則執行expr2。
4.8 位元運算符
1.
2.對於位與運算子(&)來說,如果兩個運算對象的對應位置都是1則運算結果中該位為1,否則為0。對於位或運算子(|)來說,如果兩個運算對象的對應位置至少有一個為1則運算結果中該位為1,否則為0。對於位異或運算子(^)來說,如果兩個運算對象的對應位置有且只有一個為1則運算結果中該位為1,否則為0。
4.9 sizeof運算子
1.sizeof運算子返回一條運算式或一個類型名字所佔的位元組數。sizeof運算子滿足右結合律,其所得的值是一個size_t類型的常量運算式。
2.sizeof運算子的結果部分地依賴於其作用的類型:
對char或者類型為char的運算式執行sizeof運算,結果得1。
對參考型別執行sizeof運算得到被引用對象所佔空間的大小。
對指標執行sizeof運算得到指標本身所佔空間的大小。
對解引用指標執行sizeof運算得到指標指向的對象所佔空間的大小,指標不需有效。
對數組執行sizeof運算得到整個數組所佔空間的大小,等價於對數組中所有的元素各執行一次sizeof運算並將所得結果求和。注意,sizeof運算不會把數群組轉換成指標來處理。
對string對象或vector對象執行sizeof運算只返回該類型固定部分的大小,不會計算對象中的元素佔用了多少空間。
4.10 逗號運算子
1.逗號運算子(comma operator)含有兩個運算對象,按照從左向右的順序依次求值。和邏輯與、邏輯或以及條件運算子一樣,逗號運算子也規定了運算對象求值的順序。
對於逗號運算子來說,首先對左側的運算式求值,然後將求值結果丟棄掉。逗號運算子真正的結果是右側運算式的值。如果右側運算對象是左值,那麼最終的求值結果也是左值。
4.11 類型轉換
1.在下面這些情況下,編譯器會自動地轉換運算對象的類型:
在大多數運算式中,比int類型小的整型值首先提升為較大的整數類型。
在條件中,非布爾值轉換成布爾類型。
初始化過程中,初始值轉換成變數的類型;在賦值語句中,右側運算對象轉換成左側運算對象的類型。
如果算術運算或關係運算的運算對象有多種類型,需要轉換成同一種類型。
函數調用時也會發生類型轉換。
2.一個命名的強制類型轉換具有如下形式:
cast-name<type>(expression);
其中,type是轉換的目標類型而expression是要轉換的值。如果type是參考型別,則結果是左值。cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一種。dynamic_cast支援運行時類型識別。cast-name指定了執行的是哪種轉換。
3.任何具有明確定義的類型轉換,只要不包含底層const,都可以使用static_cast。
const_cast只能改變運算對象的底層const。
reinterpret_cast通常為運算對象的位元模式提供較低層次上的重新解釋。
4.12 運算子優先順序表
PS:部分練習答案
練習4.6
i % 2 == 0 ? "even" : "odd"
練習4.10
int i = 0;while(cin >> i && i != 42)
練習4.11
a > b && b > c && c > d
練習4.21
#include <iostream>#include <vector>using std::cin;using std::cout;using std::endl;using std::vector;int main(){vector<int> num {1,2,3,4,5,6,7,8,9,10};for(auto &c:num){if(c%2)c = c*2;}for(auto c:num)cout<<c<<endl;}
練習4.23
string pl = s + (s[s.size() - 1] == 's' ? "" : "s") ;
練習4.25
結果的二進位為1111 1111 1111 1111 1110 0011 1000 0000
十進位表示為-7296
練習4.28
#include <iostream>using std::cout;using std::endl;int main(){cout << "bool\t\tis " << sizeof(bool) << "bytes." << endl;cout << "char\t\tis " << sizeof(char) << "bytes." << endl;cout << "wchar_t\t\tis " << sizeof(wchar_t) << "bytes." << endl;cout << "char16_t\tis " << sizeof(char16_t) << "bytes." << endl;cout << "char32_t\tis " << sizeof(char32_t) << "bytes." << endl;cout << "short\t\tis " << sizeof(short) << "bytes." << endl;cout << "int\t\tis " << sizeof(int) << "bytes." << endl;cout << "long\t\tis " << sizeof(long) << "bytes." << endl;cout << "long long\tis " << sizeof(long long) << "bytes." << endl;cout << "float\t\tis " << sizeof(float) << "bytes." << endl;cout << "double\t\tis " << sizeof(double) << "bytes." << endl;cout << "long double\tis " << sizeof(long double) << "bytes." << endl;cout << endl;return 0;}
練習4.36
i *= static_cast<int>(d);
練習4.37
int i; double d; const string *ps; char *pc; void *pv;pv = (void*)ps; // pv = const_cast<string*>(ps); or pv = static_cast<void*>(const_cast<string*>(ps));i = int(*pc); // i = static_cast<int>(*pc);pv = &d; // pv = static_cast<void*>(&d);pc = (char*)pv; // pc = reinterpret_cast<char*>(pv);
著作權聲明:本文為博主原創文章,如果轉載,請註明出處