跳轉到內容

C 程式設計/運算子和型別轉換

來自華夏公益教科書

運算子和賦值

[編輯 | 編輯原始碼]

C 語言擁有廣泛的運算子,使簡單的數學運算易於處理。運算子按優先順序分組的列表如下

基本表示式

[編輯 | 編輯原始碼]

識別符號 是 C 語言中事物的名稱,由字母或下劃線 (_) 組成,後面可以可選地跟字母、數字或下劃線。識別符號(或變數名)是基本表示式,前提是它已宣告為表示物件(在這種情況下,它是一個左值 [可以作為賦值表示式左側的值])或函式(在這種情況下,它是一個函式識別符號)。

常量 是基本表示式。它的型別取決於它的形式和值。常量的型別有字元常量(例如 ' ' 是一個空格)、整型常量(例如 2)、浮點型常量(例如 0.5)和透過 enum 預先定義的列舉常量。

字串字面量 是基本表示式。它由雙引號 (" ).

中的字元序列組成。帶括號的表示式是基本表示式。它由括號 (( )) 中的表示式組成。它的型別和值是括號中非帶括號表示式的型別和值。

在 C11 中,以_Generic開頭的表示式,後面跟著一個初始表示式,以及形式為 type: expression 的值列表,其中 type 是命名的型別或關鍵字 default,它構成一個基本表示式。該值是初始表示式型別之後的表示式,或者如果未找到則為 default。

字尾運算子

[編輯 | 編輯原始碼]

首先,基本表示式也是字尾表示式。以下表達式也是字尾表示式

字尾表示式後面跟著左方括號 ([)、表示式和右方括號 (]) 構成對陣列下標運算子的呼叫。其中一個表示式應具有“指向物件型別的指標”型別,另一個應具有整型;結果型別為型別。連續的陣列下標運算子指定多維陣列的元素。

字尾表示式後面跟著括號或可選的帶括號的引數列表,表示對函式呼叫運算子的呼叫。函式呼叫運算子的值是使用提供的引數呼叫函式的返回值。函式的引數在堆疊上按值複製(或者至少編譯器假裝發生了這種情況;如果程式設計師希望引數按引用複製,那麼更容易透過值傳遞要修改區域的地址,然後被呼叫函式可以透過相應的指標訪問該區域)。編譯器傳遞引數的趨勢是從右到左在堆疊上,但這並非普遍現象。

字尾表示式後面跟著一個點 (.),然後跟著一個識別符號,選擇結構或聯合中的成員;字尾表示式後面跟著一個箭頭 (->),然後跟著一個識別符號,選擇結構或聯合中的成員,該成員由表示式左側的指標指向。

字尾表示式後面跟著自增或自減運算子 (分別為 ++--),表示要對變數進行自增或自減作為副作用。表示式的值為字尾表示式自增或自減之前的值。這些運算子只對整數和指標有效。

一元表示式

[編輯 | 編輯原始碼]

首先,字尾表示式是一元表示式。以下表達式都是一元表示式

自增或自減運算子後面跟著一元表示式是一元表示式。表示式的值為一元表示式自增或自減之後的值。這些運算子只對整數和指標有效。

以下運算子後面跟著強制轉換表示式是一元表示式

Operator     Meaning
========     =======
   &         Address-of; value is the location of the operand
   *         Contents-of; value is what is stored at the location
   -         Negation
   +         Value-of operator
   !         Logical negation ( (!E) is equivalent to (0==E) )
   ~         Bit-wise complement

關鍵字 sizeof 後面跟著一元表示式是一元表示式。該值為表示式型別的位元組大小。表示式不會被求值。

關鍵字 sizeof 後面跟著帶括號的型別名是一元表示式。該值為型別的位元組大小。

強制轉換運算子

[編輯 | 編輯原始碼]

一元表示式也是強制轉換表示式。

帶括號的型別名後面跟著任何表示式,包括字面量,都是強制轉換表示式。帶括號的型別名具有將強制轉換表示式強制轉換為括號中型別名指定的型別的效果。對於算術型別,這要麼不改變表示式的值,要麼如果表示式是整數並且新型別小於以前的型別,則截斷表示式的值。

將 int 強制轉換為 float 的示例

int i = 5;
printf("%f\n", (float) i / 2); // Will print out:  2.500000

乘法和加法運算子

[編輯 | 編輯原始碼]

首先,乘法表達式也是強制轉換表示式,加法表示式也是乘法表達式。這遵循乘法優先於加法的優先順序。

在 C 語言中,簡單的數學運算非常容易處理。存在以下運算子:+(加法)、-(減法)、*(乘法)、/(除法)和%(模);您可能在數學課上學過所有這些運算子——除了模運算。它返回除法的餘數(例如 5 % 2 = 1)。(模運算未定義為浮點數,但math.h 庫有一個fmod 函式。)

模運算必須小心處理,因為它不等於數學模運算:(-5) % 2 不是 1,而是 -1。整數除法將返回一個整數,負整數除以正整數將朝零取整而不是向下取整(例如 (-5) / 3 = -1 而不是 -2)。但是,對於所有整數 a 和非零整數 b,始終為真((a / b) * b) + (a % b) == a.

沒有用於執行求冪的內聯運算子(例如,5 ^ 2 不是 25 [它是 7;^ 是異或運算子],並且 5 ** 2 是錯誤),但有一個冪函式

數學運算順序適用。例如 (2 + 3) * 2 = 10,而 2 + 3 * 2 = 8。乘法運算子優先於加法運算子。

#include <stdio.h>

int main(void)
{
int i = 0, j = 0;

    /* while i is less than 5 AND j is less than 5, loop */
    while( (i < 5) && (j < 5) )
    {
        /* postfix increment, i++
         *     the value of i is read and then incremented
         */
        printf("i: %d\t", i++);

        /*
         * prefix increment, ++j 
         *     the value of j is incremented and then read
         */
        printf("j: %d\n", ++j);
    }

    printf("At the end they have both equal values:\ni: %d\tj: %d\n", i, j);

    getchar(); /* pause */
    return 0;
}

將顯示以下內容

i: 0    j: 1
i: 1    j: 2
i: 2    j: 3
i: 3    j: 4
i: 4    j: 5
At the end they have both equal values:
i: 5    j: 5

移位運算子(可用於位旋轉)

[編輯 | 編輯原始碼]

移位表示式也是加法表示式(這意味著移位運算子的優先順序僅次於加法和減法)。

移位函式通常用於低階 I/O 硬體介面。移位和旋轉函式在密碼學和軟體浮點模擬中被大量使用。除此之外,移位可以用來代替除法或乘以二的冪。許多處理器都有專門的功能模組來加速這些操作 - 參見 微處理器設計/移位和旋轉塊。在擁有此類模組的處理器上,大多數 C 編譯器將移位和旋轉運算子編譯成單個組合語言指令 - 參見 X86 彙編/移位和旋轉

<< 運算子將二進位制表示向左移動,丟棄最高有效位,並在末尾追加零位。結果等效於將整數乘以二的冪。

無符號右移

[編輯 | 編輯原始碼]

無符號右移運算子,有時也稱為邏輯右移運算子。它將二進位制表示向右移動,丟棄最低有效位,並在開頭追加零位。>> 運算子對於無符號整數等效於除以二的冪。

有符號右移

[編輯 | 編輯原始碼]

有符號右移運算子,有時也稱為算術右移運算子。它將二進位制表示向右移動,丟棄最低有效位,但在開頭追加原始符號位的副本。>> 運算子對於有符號整數不等效於除法。

在 C 中,>> 運算子的行為取決於它作用的資料型別。因此,有符號和無符號右移看起來完全一樣,但在某些情況下會產生不同的結果。

右旋轉

[編輯 | 編輯原始碼]

與普遍看法相反,可以編寫 C 程式碼,它可以編譯成“旋轉”組合語言指令(在擁有此類指令的 CPU 上)。

大多數編譯器識別這種慣用法

  unsigned int x;
  unsigned int y;
  /* ... */
  y = (x >> shift) | (x << (32 - shift));

並將其編譯成單個 32 位旋轉指令。 [1] [2]

在某些系統上,這可能被“#define”定義為宏,或在像“bitops.h”這樣的標準標頭檔案中定義為名為“rightrotate32”或“rotr32”或“ror32”的行內函數。 [3]

左旋轉

[編輯 | 編輯原始碼]

大多數編譯器識別這種慣用法

  unsigned int x;
  unsigned int y;
  /* ... */
  y = (x << shift) | (x >> (32 - shift));

並將其編譯成單個 32 位旋轉指令。

在某些系統上,這可能被“#define”定義為宏,或在像“bitops.h”這樣的標頭檔案中定義為名為“leftrotate32”或“rotl32”的行內函數。

關係運算符和相等運算子

[編輯 | 編輯原始碼]

關係表示式也是移位表示式;相等表示式也是關係表示式。

關係二元運算子 <(小於)、>(大於)、<=(小於或等於)和 >=(大於或等於)運算子在操作結果為真時返回 1,為假時返回 0。這些運算子的結果型別為 int

相等二元運算子 ==(等於)和 !=(不等於)運算子類似於關係運算符,只是它們的優先順序較低。它們也返回 1 如果操作結果為真,返回 0 如果結果為假。

浮點數和相等運算子需要注意的一點:由於浮點運算會產生近似值(例如,0.1 在二進位制中是迴圈小數,所以 0.1 * 10.0 幾乎永遠不會是 1.0),因此不建議對浮點數使用 == 運算子。相反,如果 a 和 b 是要比較的數字,則將 fabs (a - b) 與一個模糊因子進行比較。

位運算子

[編輯 | 編輯原始碼]

位運算子是 &(與)、^(異或)和 |(或)。& 運算子的優先順序高於 ^^ 的優先順序高於 |

被操作的值必須是整數;結果是整數。

位運算子的一種用途是模擬位標誌。這些標誌可以用 OR 設定,用 AND 測試,用 XOR 翻轉,用 AND NOT 清除。例如

/* This code is a sample for bitwise operations.  */
#define BITFLAG1    (1)
#define BITFLAG2    (2)
#define BITFLAG3    (4) /* They are powers of 2 */

unsigned bitbucket = 0U;    /* Clear all */
bitbucket |= BITFLAG1;      /* Set bit flag 1 */
bitbucket &= ~BITFLAG2;     /* Clear bit flag 2 */
bitbucket ^= BITFLAG3;      /* Flip the state of bit flag 3 from off to on or
                               vice versa */
if (bitbucket & BITFLAG3) {
  /* bit flag 3 is set */
} else {
  /* bit flag 3 is not set */
}

邏輯運算子

[編輯 | 編輯原始碼]

邏輯運算子是 &&(與)和 ||(或)。這兩個運算子在關係為真時產生 1,在關係為假時產生 0。這兩個運算子都進行短路計算;如果可以從第一個運算元確定表示式的結果,則忽略第二個運算元。&& 運算子的優先順序高於 || 運算子。

&& 用於從左到右評估表示式,如果兩個語句都為真,則返回 1,如果其中任何一個為假,則返回 0。如果第一個表示式為假,則不評估第二個表示式。

  
  int x = 7;
  int y = 5;
  if(x == 7 && y == 5) {
      ...
  }

這裡,&& 運算子檢查最左邊的表示式,然後檢查它右邊的表示式。如果有多個表示式連結在一起(例如 x && y && z),則運算子將檢查x首先,然後檢查 y(如果x不為零),然後繼續向右檢查 z,如果 x 或 y 都不為零。由於兩個語句都返回真,因此 && 運算子返回真,並執行程式碼塊。

  
    if(x == 5 && y == 5) {
        ...
    }

&& 運算子以與以前相同的方式進行檢查,發現第一個表示式為假。&& 運算子在發現一個語句為假時停止評估,並返回一個假。


|| 用於從左到右評估表示式,如果兩個表示式中任何一個為真,則返回 1,如果兩個表示式都為假,則返回 0。如果第一個表示式為真,則不評估第二個表示式。

      
    /* Use the same variables as before. */
    if(x == 2 || y == 5) { // the || statement checks both expressions, finds that the latter is true, and returns true
        ...
    }

這裡的 || 運算子檢查最左邊的表示式,發現它為假,但繼續評估下一個表示式。它發現下一個表示式返回真,停止並返回 1。與 && 運算子在發現一個表示式返回假時停止一樣,|| 運算子在發現一個表示式返回真時停止。

值得注意的是,C 沒有其他語言中常見的布林值(真和假)。它將 0 解釋為假,並將任何非零值解釋為真。

條件運算子

[編輯 | 編輯原始碼]

三元 ?: 運算子是條件運算子。表示式 (x ? y : z) 如果 x 不為零,則值為 y,否則值為 z

示例

  
int x = 0;
int y;
y = (x ? 10 : 6); /* The parentheses are technically not necessary as assignment
                     has a lower precedence than the conditional operator, but
                     it's there for clarity.  */

表示式 x 評估為 0。三元運算子然後查詢“如果為假”的值,在本例中是 6。它返回該值,因此 y 等於 6。如果 x 為非零,則表示式將返回 10。

賦值運算子

[編輯 | 編輯原始碼]

賦值運算子包括 =*=/=%=+=-=<<=>>=&=^=|== 運算子將右運算元的值儲存到左運算元確定的位置,左運算元必須是 左值(具有地址的值,因此可以對其進行賦值)。

對於其他運算子,x op= yx = x op (y) 的簡寫形式。因此,以下表達式是等效的

    1. x += y     -     x = x+y
    2. x -= y     -     x = x-y
    3. x *= y     -     x = x*y
    4. x /= y     -     x = x/y
    5. x %= y     -     x = x%y

賦值表示式的值為賦值後左運算元的值。因此,賦值可以進行鏈式操作;例如,表示式 a = b = c = 0; 將為所有三個變數賦值為零。

逗號運算子

[edit | edit source]

優先順序最低的運算子是逗號運算子。表示式 x, y 的值將計算 xy ,但提供 y 的值。

此運算子對於在一個語句中包含多個操作(例如在 for 迴圈條件內)很有用。

以下是一個關於逗號運算子的簡單示例

int i, x;      /* Declares two ints, i and x, in one declaration.
                  Technically, this is not the comma operator. */

/* this loop initializes x and i to 0, then runs the loop */
for (x = 0, i = 0; i <= 6; i++) {
    printf("x = %d, and i = %d\n", x, i);
}

參考資料

[edit | edit source]
  1. GCC: "Optimize common rotate constructs"
  2. "Cleanups in ROTL/ROTR DAG combiner code" mentions that this code supports the "rotate" instruction in the CellSPU
  3. "replace private copy of bit rotation routines" -- recommends including "bitops.h" and using its rol32 and ror32 rather than copy-and-paste into a new program.


華夏公益教科書