<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 第?16?章?運算符詳解 **目錄** + [1\. 位運算](ch16s01.html) + [1.1\. 按位與、或、異或、取反運算](ch16s01.html#id2761062) + [1.2\. 移位運算](ch16s01.html#id2761805) + [1.3\. 掩碼](ch16s01.html#id2761995) + [1.4\. 異或運算的一些特性](ch16s01.html#id2762114) + [2\. 其它運算符](ch16s02.html) + [2.1\. 復合賦值運算符](ch16s02.html#id2762352) + [2.2\. 條件運算符](ch16s02.html#id2762537) + [2.3\. 逗號運算符](ch16s02.html#id2762598) + [2.4\. sizeof運算符與typedef類型聲明](ch16s02.html#id2762676) + [3\. Side Effect與Sequence Point](ch16s03.html) + [4\. 運算符總結](ch16s04.html) 本章介紹很多前面沒有講過的運算符,重點是位運算,然后引出一個重要的概念Sequence Point,在最后一節總結C語言各種運算符的優先級和結合性。 ## 1.?位運算 整數在計算機中用二進制的位來表示,C語言提供一些運算符可以直接操作整數中的位,稱為位運算,這些運算符的操作數都必須是整型的。在以后的學習中你會發現,有些信息利用整數中的某幾個位來存儲,要訪問這些位,僅僅有對整數的操作是不夠的,必須借助位運算,例如[第?2?節 “Unicode和UTF-8”](apas02.html#app-encoding.utf8)介紹的UTF-8編碼就是如此,學完本節之后你應該能自己寫出UTF-8的編碼和解碼程序。本節首先介紹各種位運算符,然后介紹與位運算有關的編程技巧。 ### 1.1.?按位與、或、異或、取反運算 在[第?3?節 “布爾代數”](ch04s03.html#cond.bool)講過邏輯與、或、非運算,并列出了真值表,對于整數中的位也可以做與、或、非運算,C語言提供了按位與(Bitwise AND)運算符&、按位或(Bitwise OR)運算符|和按位取反(Bitwise NOT)運算符~,此外還有按位異或(Bitwise XOR)運算符^,我們在[第?1?節 “為什么計算機用二進制計數”](ch14s01.html#number.binary)講過異或運算。下面用二進制的形式舉幾個例子。 **圖?16.1.?位運算** ![位運算](https://box.kancloud.cn/2016-04-02_56ff80d34a0a8.png) 注意,&、|、^運算符都是要做Usual Arithmetic Conversion的(其中有一步是Integer Promotion),~運算符也要做Integer Promotion,所以在C語言中其實并不存在8位整數的位運算,操作數在做位運算之前都至少被提升為`int`型了,上面用8位整數舉例只是為了書寫方便。比如: ``` unsigned char c = 0xfc; unsigned int i = ~c; ``` 計算過程是這樣的:常量0xfc是`int`型的,賦給`c`要轉成`unsigned char`,值不變;`c`的十六進制表示是fc,計算`~c`時先提升為整型(000000fc)然后取反,最后結果是ffffff03。注意,如果把`~c`看成是8位整數的取反,最后結果就得3了,這就錯了。為了避免出錯,一是盡量避免不同類型之間的賦值,二是每一步計算都要按上一章講的類型轉換規則仔細檢查。 ### 1.2.?移位運算 移位運算符(Bitwise Shift)包括左移&lt;&lt;和右移&gt;&gt;。左移將一個整數的各二進制位全部左移若干位,例如0xcfffffff3&lt;&lt;2得到0x3fffffcc: **圖?16.2.?左移運算** ![左移運算](https://box.kancloud.cn/2016-04-02_56ff80d35a22f.png) 最高兩位的11被移出去了,最低兩位又補了兩個0,其它位依次左移兩位。但要注意,移動的位數必須小于左操作數的總位數,比如上面的例子,左邊是`unsigned int`型,如果左移的位數大于等于32位,則結果是Undefined。移位運算符不同于+ - * / ==等運算符,兩邊操作數的類型不要求一致,但兩邊操作數都要做Integer Promotion,整個表達式的類型和左操作數提升后的類型相同。 復習一下[第?2?節 “不同進制之間的換算”](ch14s02.html#number.convert)講過的知識可以得出結論,_在一定的取值范圍內,將一個整數左移1位相當于乘以2_。比如二進制11(十進制3)左移一位變成110,就是6,再左移一位變成1100,就是12。讀者可以自己驗證這條規律對有符號數和無符號數都成立,對負數也成立。當然,如果左移改變了最高位(符號位),那么結果肯定不是乘以2了,所以我加了個前提“在一定的取值范圍內”。由于計算機做移位比做乘法快得多,編譯器可以利用這一點做優化,比如看到源代碼中有`i * 8`,可以編譯成移位指令而不是乘法指令。 當操作數是無符號數時,右移運算的規則和左移類似,例如0xcfffffff3&gt;&gt;2得到0x33fffffc: **圖?16.3.?右移運算** ![右移運算](https://box.kancloud.cn/2016-04-02_56ff80d36c65e.png) 最低兩位的11被移出去了,最高兩位又補了兩個0,其它位依次右移兩位。和左移類似,移動的位數也必須小于左操作數的總位數,否則結果是Undefined。在一定的取值范圍內,將一個整數右移1位相當于除以2,小數部分截掉。 當操作數是有符號數時,右移運算的規則比較復雜: * 如果是正數,那么高位移入0 * 如果是負數,那么高位移入1還是0不一定,這是Implementation-defined的。對于x86平臺的`gcc`編譯器,最高位移入1,也就是仍保持負數的符號位,這種處理方式對負數仍然保持了“右移1位相當于除以2”的性質。 綜上所述,由于類型轉換和移位等問題,用有符號數做位運算是很不方便的,所以,_建議只對無符號數做位運算,以減少出錯的可能_。 #### 習題 1、下面兩行`printf`打印的結果有何不同?請讀者比較分析一下。`%x`轉換說明的含義詳見[第?2.9?節 “格式化I/O函數”](ch25s02.html#stdlib.formatio)。 ``` int i = 0xcffffff3; printf("%x\n", 0xcffffff3>>2); printf("%x\n", i>>2); ``` ### 1.3.?掩碼 如果要對一個整數中的某些位進行操作,怎樣表示這些位在整數中的位置呢?可以用掩碼(Mask)來表示。比如掩碼0x0000ff00表示對一個32位整數的8~15位進行操作,舉例如下。 1、取出8~15位。 ``` unsigned int a, b, mask = 0x0000ff00; a = 0x12345678; b = (a & mask) >> 8; /* 0x00000056 */ ``` 這樣也可以達到同樣的效果: ``` b = (a >> 8) & ~(~0U << 8); ``` 2、將8~15位清0。 ``` unsigned int a, b, mask = 0x0000ff00; a = 0x12345678; b = a & ~mask; /* 0x12340078 */ ``` 3、將8~15位置1。 ``` unsigned int a, b, mask = 0x0000ff00; a = 0x12345678; b = a | mask; /* 0x1234ff78 */ ``` #### 習題 1、統計一個無符號整數的二進制表示中1的個數,函數原型是`int countbit(unsigned int x);`。 2、用位操作實現無符號整數的乘法運算,函數原型是`unsigned int multiply(unsigned int x, unsigned int y);`。例如:(11011)<sub>2</sub>×(10010)<sub>2</sub>=((11011)<sub>2</sub>&lt;&lt;1)+((11011)<sub>2</sub>&lt;&lt;4)。 3、對一個32位無符號整數做循環右移,函數原型是`unsigned int rotate_right(unsigned int x);`。所謂循環右移就是把低位移出去的部分再補到高位上去,例如`rotate_right(0xdeadbeef, 16)`的值應該是0xefdeadbe。 ### 1.4.?異或運算的一些特性 1、一個數和自己做異或的結果是0。如果需要一個常數0,x86平臺的編譯器可能會生成這樣的指令:`xorl %eax, %eax`。不管`eax`寄存器里的值原來是多少,做異或運算都能得到0,這條指令比同樣效果的`movl $0, %eax`指令快,因為前者只需要在CPU內部計算,而后者需要訪問內存,在下一章[第?5?節 “Memory Hierarchy”](ch17s05.html#arch.memh)詳細介紹。 2、從異或的真值表可以看出,不管是0還是1,和0做異或保持原值不變,和1做異或得到原值的相反值。可以利用這個特性配合掩碼實現某些位的翻轉,例如: ``` unsigned int a, b, mask = 1U << 6; a = 0x12345678; b = a ^ mask; /* flip the 6th bit */ ``` 3、如果a<sub>1</sub> ^ a<sub>2</sub> ^ a<sub>3</sub> ^ ... ^ a<sub>n</sub>的結果是1,則表示a<sub>1</sub>、a<sub>2</sub>、a<sub>3</sub>...a<sub>n</sub>之中1的個數為奇數個,否則為偶數個。這條性質可用于奇偶校驗(Parity Check),比如在串口通信過程中,每個字節的數據都計算一個校驗位,數據和校驗位一起發送出去,這樣接收方可以根據校驗位粗略地判斷接收到的數據是否有誤。 4、x ^ x ^ y == y,因為x ^ x == 0,0 ^ y == y。這個性質有什么用呢?我們來看這樣一個問題:交換兩個變量的值,不得借助額外的存儲空間,所以就不能采用`temp = a; a = b; b = temp;`的辦法了。利用位運算可以這樣做交換: ``` a = a ^ b; b = b ^ a; a = a ^ b; ``` 分析一下這個過程。為了避免混淆,把a和b的初值分別記為a<sub>0</sub>和b<sub>0</sub>。第一行,`a = a<sub>0</sub> ^ b<sub>0</sub>`;第二行,把a的新值代入,得到`b = b<sub>0</sub> ^ a<sub>0</sub> ^ b<sub>0</sub>`,等號右邊的b<sub>0</sub>相當于上面公式中的x,a<sub>0</sub>相當于y,所以結果為a<sub>0</sub>;第三行,把a和b的新值代入,得到`a = a<sub>0</sub> ^ b<sub>0</sub> ^ a<sub>0</sub>`,結果為b<sub>0</sub>。注意這個過程不能把同一個變量自己跟自己交換,而利用中間變量`temp`則可以交換。 #### 習題 1、請在網上查找有關RAID(Redundant Array of Independent Disks,獨立磁盤冗余陣列)的資料,理解其實現原理,其實就是利用了本節的性質3和4。 2、交換兩個變量的值,不得借助額外的存儲空間,除了本節講的方法之外你還能想出什么方法?本節講的方法不能把同一個變量自己跟自己交換,你的方法有沒有什么局限性? ## 2.?其它運算符 ### 2.1.?復合賦值運算符 復合賦值運算符(Compound Assignment Operator)包括`*=` `/=` `%=` `+=` `-=` `&lt;&lt;=` `&gt;&gt;=` `&=` `^=` `|=`,一邊做運算一邊賦值。例如`a += 1`相當于`a = a + 1`。但有一點細微的差別,前者對表達式`a`只求值一次,而后者求值兩次,如果`a`是一個復雜的表達式,求值一次和求值兩次的效率是不同的,例如`a[i+j] += 1`和`a[i+j] = a[i+j] + 1`。那么僅僅是效率上的差別嗎?對于沒有Side Effect的表達式,求值一次和求值兩次的結果是一樣的,但對于有Side Effect的表達式則不一定,例如`a[foo()] += 1`和`a[foo()] = a[foo()] + 1`,如果`foo()`函數調用有Side Effect,比如會打印一條消息,那么前者只打印一次,而后者打印兩次。 在[第?3?節 “for語句”](ch06s03.html#iter.for)講自增、自減運算符時說`++i`相當于`i = i + 1`,其實更準確地說應該是等價于`i += 1`,表達式`i`只求值一次,而`--i`等價于`i -= 1`。 ### 2.2.?條件運算符 條件運算符(Conditional Operator)是C語言中唯一一個三目運算符(Ternary Operator),帶三個操作數,它的形式是`表達式1 ? 表達式2 : 表達式3`,這個運算符所組成的整個表達式的值等于表達式2或表達式3的值,取決于表達式1的值是否為真,可以把它想像成這樣的函數: ``` if (表達式1) return 表達式2; else return 表達式3; ``` 表達式1相當于`if`語句的控制表達式,因此它的值必須是標量類型,而表達式2和3相當于同一個函數在不同情況下的返回值,因此它們的類型要求一致,也要做Usual Arithmetic Conversion。 下面舉個例子,定義一個函數求兩個參數中較大的一個。 ``` int max(int a, int b) { return (a > b) ? a : b; } ``` ### 2.3.?逗號運算符 逗號運算符(Comma Operator)也是一種雙目運算符,它的形式是`表達式1, 表達式2`,兩個表達式不要求類型一致,左邊的表達式1先求值,求完了直接把值丟掉,再求右邊表達式2的值作為整個表達式的值。逗號運算符是左結合的,類似于+ - * /運算符,根據組合規則可以寫出`表達式1, 表達式2, 表達式3, ..., 表達式n`這種形式,`表達式1, 表達式2`可以看作一個子表達式,先求表達式1的值,然后求表達式2的值作為這個子表達式的值,然后這個值再和表達式3組成一個更大的表達式,求表達式3的值作為這個更大的表達式的值,依此類推,整個計算過程就是從左到右依次求值,最后一個表達式的值成為整個表達式的值。 注意,函數調用時各實參之間也是用逗號隔開,這種逗號是分隔符而不是逗號運算符。但可以這樣使用逗號運算符: ``` f(a, (t=3, t+2), c) ``` 傳給函數`f`的參數有三個,其中第二個參數的值是表達式`t+2`的值。 ### 2.4.?sizeof運算符與typedef類型聲明 `sizeof`是一個很特殊的運算符,它有兩種形式:“sizeof 表達式”和“sizeof(類型名)”。這個運算符很特殊,“sizeof 表達式”中的子表達式并不求值,而只是根據類型轉換規則求得子表達式的類型,然后把這種類型所占的字節數作為整個表達式的值。有些人喜歡寫成“sizeof(表達式)”的形式也可以,這里的括號和`return(1);`的括號一樣,不起任何作用。但另外一種形式“sizeof(類型名)”的括號則是必須寫的,整個表達式的值也是這種類型所占的字節數。 比如用`sizeof`運算符求一個數組的長度: ``` int a[12]; printf("%d\n", sizeof a/sizeof a[0]); ``` 在上面這個例子中,由于`sizeof 表達式`中的子表達式不需要求值,所以不需要到運行時才計算,事實上在編譯時就知道`sizeof a`的值是48,`sizeof a[0]`的值是4,所以在編譯時就已經把`sizeof a/sizeof a[0]`替換成常量12了,這是一個常量表達式。 `sizeof`運算符的結果是`size_t`類型的,這個類型定義在`stddef.h`頭文件中,不過你的代碼中只要不出現`size_t`這個類型名就不用包含這個頭文件,比如像上面的例子就不用包含這個頭文件。C標準規定`size_t`是一種無符號整型,編譯器可以用`typedef`做一個類型聲明: ``` typedef unsigned long size_t; ``` 那么`size_t`就代表`unsigned long`型。不同平臺的編譯器可能會根據自己平臺的具體情況定義`size_t`所代表的類型,比如有的平臺定義為`unsigned long`型,有的平臺定義為`unsigned long long`型,C標準規定`size_t`這個名字就是為了隱藏這些細節,使代碼具有可移植性。所以注意不要把`size_t`類型和它所代表的真實類型混用,例如: ``` unsigned long x; size_t y; x = y; ``` 如果在一種ILP32平臺上定義`size_t`代表`unsigned long long`型,這段代碼把`y`賦給`x`時就把高位截掉了,結果可能是錯的。 `typedef`這個關鍵字用于給某種類型起個新名字,比如上面的`typedef`聲明可以這么看:去掉`typedef`就成了一個變量聲明`unsigned long size_t;`,`size_t`是一個變量名,類型是`unsigned long`,那么加上`typedef`之后,`size_t`就是一個類型名,就代表`unsigned long`類型。再舉個例子: ``` typedef char array_t[10]; array_t a; ``` 這相當于聲明`char a[10];`。類型名也遵循標識符的命名規則,并且通常加個`_t`后綴表示Type。 ## 3.?Side Effect與Sequence Point 如果你只想規規矩矩地寫代碼,那么基本用不著看這一節。本節的內容基本上是鉆牛角尖兒的,除了Short-circuit比較實用,其它寫法都應該避免使用。但沒辦法,有時候不是你想鉆牛角尖兒,而是有人逼你去鉆牛角尖兒。這是我們的學員在找工作筆試時碰到的問題: ``` int a=0; a = (++a)+(++a)+(++a)+(++a); ``` 據我了解,似乎很多公司都有出這種筆試題的惡趣味。答案應該是Undefined,我甚至有些懷疑出題人是否真的知道答案。下面我來解釋為什么是Undefined。 我們知道,調用一個函數可能產生Side Effect,使用某些運算符(++ -- = 復合賦值)也會產生Side Effect,如果一個表達式中隱含著多個Side Effect,究竟哪個先發生哪個后發生呢?C標準規定代碼中的某些點是Sequence Point,當執行到一個Sequence Point時,在此之前的Side Effect必須全部作用完畢,在此之后的Side Effect必須一個都沒發生。至于兩個Sequence Point之間的多個Side Effect哪個先發生哪個后發生則沒有規定,編譯器可以任意選擇各Side Effect的作用順序。下面詳細解釋各種Sequence Point。 1、調用一個函數時,在所有準備工作做完之后、函數調用開始之前是Sequence Point。比如調用`foo(f(), g())`時,`foo`、`f()`、`g()`這三個表達式哪個先求值哪個后求值是Unspecified,但是必須都求值完了才能做最后的函數調用,所以`f()`和`g()`的Side Effect按什么順序發生不一定,但必定在這些Side Effect全部作用完之后才開始調用`foo`函數。 2、條件運算符?:、逗號運算符、邏輯與&&、邏輯或||的第一個操作數求值之后是Sequence Point。我們剛講過條件運算符和逗號運算符,條件運算符要根據表達式1的值是否為真決定下一步求表達式2還是表達式3的值,如果決定求表達式2的值,表達式3就不會被求值了,反之也一樣,逗號運算符也是這樣,表達式1求值結束才繼續求表達式2的值。 邏輯與和邏輯或早在[第?3?節 “布爾代數”](ch04s03.html#cond.bool)就講了,但在初學階段我一直回避它們的操作數求值順序問題。這兩個運算符和條件運算符類似,先求左操作數的值,然后根據這個值是否為真,右操作數可能被求值,也可能不被求值。比如[例?8.5 “剪刀石頭布”](ch08s05.html#array.scissor)這個程序中的這幾句: ``` ret = scanf("%d", &man); if (ret != 1 || man < 0 || man > 2) { printf("Invalid input! Please input 0, 1 or 2.\n"); continue; } ``` 其實可以寫得更簡單(類似于[[K&R]](bi01.html#bibli.kr "The C Programming Language")的簡潔風格): ``` if (scanf("%d", &man) != 1 || man < 0 || man > 2) { printf("Invalid input! Please input 0, 1 or 2.\n"); continue; } ``` 這個控制表達式的求值順序是:先求`scanf("%d", &man) = 1`的值,如果`scanf`調用失敗,則返回值不等于1成立,||運算有一個操作數為真則整個表達式為真,這時直接執行下一句`printf`,根本不會再去求`man &lt; 0`或`man &gt; 2`的值;如果`scanf`調用成功,則讀入的數保存在變量`man`中,并且返回值等于1,那么說它不等于1就不成立了,第一個||運算的左操作數為假,就會去求右操作數`man &lt; 0`的值作為整個表達式的值,這時變量`man`的值正是`scanf`讀上來的值,我們判斷它是否在[0, 2]之間,如果`man &lt; 0`不成立,則整個表達式`scanf("%d", &man) != 1 || man &lt; 0` 的值為假,也就是第二個||運算的左操作數為假,所以最后求右操作數`man &gt; 2`的值作為整個表達式的值。 &&運算與此類似,`a && b`的計算過程是:首先求表達式`a`的值,如果`a`的值是假則整個表達式的值是假,不會再去求`b`的值;如果`a`的值是真,則下一步求`b`的值作為整個表達式的值。所以,`a && b`相當于“if a then b”,而`a || b`相當于“if not a then b”。這種特性稱為Short-circuit,很多人喜歡利用Short-circuit特性簡化代碼。 3、在一個完整的聲明末尾是Sequence Point,所謂完整的聲明是指這個聲明不是另外一個聲明的一部分。比如聲明`int a[10], b[20];`,在`a[10]`末尾是Sequence Point,在`b[20]`末尾也是。 4、在一個完整的表達式末尾是Sequence Point,所謂完整的表達式是指這個表達式不是另外一個表達式的一部分。所以如果有`f(); g();`這樣兩條語句,`f()`和`g()`是兩個完整的表達式,`f()`的Side Effect必定在`g()`之前發生。 5、在庫函數即將返回時是Sequence Point。這條規則似乎可以包含在上一條規則里面,因為函數返回時必然會結束掉一個完整的表達式。而事實上很多庫函數是以宏定義的形式實現的([第?2.1?節 “函數式宏定義”](ch21s02.html#prep.funcmacro)),并不是真正的函數,所以才需要有這條規則。 還有兩種Sequence Point和某些C標準庫函數的執行過程相關,此處從略,有興趣的讀者可參考[[C99]](bi01.html#bibli.c99 "ISO/IEC 9899: Programming Languages - C")的Annex C。 現在可以分析一下本節開頭的例子了。`a = (++a)+(++a)+(++a)+(++a);`的結果之所以是Undefined,因為在這個表達式中有五個Side Effect都在改變`a`的值,這些Side Effect按什么順序發生不一定,只知道在整個表達式求值結束時一定都發生了。比如現在求第二個`++a`的值,這時第一個、第三個、第四個`++a`的Side Effect發生了沒有,`a`的值被加過幾次了,這些都不確定,所以第二個`++a`的值也不確定。這行代碼用不同平臺的不同編譯器來編譯結果是不同的,甚至在同一平臺上用同一編譯器的不同版本來編譯也可能不同。 寫表達式應遵循的原則一:_在兩個Sequence Point之間,同一個變量的值只允許被改變一次_。僅有這一條原則還不夠,例如`a[i++] = i;`的變量`i`只改變了一次,但結果仍是Undefined,因為等號左邊改`i`的值,等號右邊讀`i`的值,到底是先改還是先讀?這個讀寫順序是不確定的。但為什么`i = i + 1;`就沒有歧義呢?雖然也是等號左邊改`i`的值,等號右邊讀`i`的值,但你不讀出`i`的值就沒法計算`i + 1`,那拿什么去改`i`的值呢?所以這個讀寫順序是確定的。寫表達式應遵循的原則二:_如果在兩個Sequence Point之間既要讀一個變量的值又要改它的值,只有在讀寫順序確定的情況下才可以這么寫_。 ## 4.?運算符總結 到此為止,除了和指針相關的運算符還沒講之外,其它運算符都講過了,是時候做一個總結了。 運算符+ - * / % &gt; &lt; &gt;= &lt;= == != & | ^ 以及各種復合賦值運算符要求兩邊的操作數類型一致,條件運算符?:要求后兩個操作數類型一致,這些運算符在計算之前都需要做Usual Arithmetic Conversion。 下面按優先級從高到低的順序總結一下C語言的運算符,每一條所列的各運算符具有相同的優先級,對于同一優先級的多個運算符按什么順序計算也有說明,雙目運算符就簡單地用“左結合”或“右結合”來說明了。和指針有關的運算符* & -&gt;也在這里列出來了,到[第?23?章 _指針_](ch23.html#pointer)再詳細解釋。 1、標識符、常量、字符串和用()括號套起來的表達式是組成表達式的最基本單元,在運算中做操作數,優先級最高。 2、后綴運算符,包括數組取下標[]、函數調用()、結構體取成員“.”、指向結構體的指針取成員-&gt;、后綴自增++、后綴自減--。如果一個操作數后面有多個后綴,按照離操作數從近到遠的順序(也就是從左到右)依次計算,比如`a.name++`,先算`a.name`,再++,這里的`.name`應該看成`a`的一個后綴,而不是把`.`看成雙目運算符。 3、單目運算符,包括前綴自增++、前綴自減--、`sizeof`、類型轉換()、取地址運算&、指針間接尋址*、正號+、負號-、按位取反~、邏輯非!。如果一個操作數前面有多個前綴,按照離操作數從近到遠的順序(也就是從右到左)依次計算,比如`!~a`,先算`~a`,再求!。 4、乘*、除/、模%運算符。這三個運算符是左結合的。 5、加+、減-運算符。左結合。 6、移位運算符&lt;&lt;和&gt;&gt;。左結合。 7、關系運算符&lt; &gt; &lt;= &gt;=。左結合。 8、相等性運算符==和!=。左結合。 9、按位與&。左結合。 10、按位異或^。左結合。 11、按位或|。左結合。 12、邏輯與&&。左結合。 13、邏輯或||。左結合。 14、條件運算符:?。在[第?2?節 “if/else語句”](ch04s02.html#cond.ifelse)講過Dangling-else問題,條件運算符也有類似的問題。例如`a ? b : c ? d : e`是看成`(a ? b : c) ? d : e`還是`a ? b : (c ? d : e)`呢?C語言規定是后者。 15、賦值=和各種復合賦值(`*=` `/=` `%=` `+=` `-=` `&lt;&lt;=` `&gt;&gt;=` `&=` `^=` `|=`)。在雙目運算符中只有賦值和復合賦值是右結合的。 16、逗號運算符。左結合。 [[K&R]](bi01.html#bibli.kr "The C Programming Language")第2章也有這樣一個列表,但是對于結合性解釋得不夠清楚。左結合和右結合這兩個概念只對雙目運算符有意義,對于前綴、后綴和三目運算符我單獨做了說明。C語言表達式的詳細語法規則可以參考[[C99]](bi01.html#bibli.c99 "ISO/IEC 9899: Programming Languages - C")的Annex A.2,其實語法規則并不是用優先級和結合性這兩個概念來表述的,有一些細節用優先級和結合性是表達不了的,只有看C99才能了解完整的語法規則。 ### 習題 1、以下代碼得到的`sum`是0xffff,對嗎? ``` int i = 0; unsigned int sum = 0; for (; i < 16; i++) sum = sum + 1U<<i; ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看