跳轉到內容

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

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

[edit | edit source]

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

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

左移

[edit | edit source]

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

無符號右移

[edit | edit source]

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

有符號右移

[edit | edit source]

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

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

右旋轉

[edit | edit source]

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

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

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

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

在某些系統上,這可能被 "#define" 作為宏,或定義為一個行內函數,在標準標頭檔案(如 "bitops.h")中稱為 "rightrotate32" 或 "rotr32" 或 "ror32"。 [3]

左旋轉

[edit | edit source]

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

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

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

在某些系統上,這可能被 "#define" 作為宏,或定義為一個行內函數,在標頭檔案(如 "bitops.h")中稱為 "leftrotate32" 或 "rotl32"。

關係運算符和相等運算子

[edit | edit source]

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

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

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

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

位運算子

[edit | edit source]

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

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

位運算子的一種用途是模擬位標誌。這些標誌可以用 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 */
}

邏輯運算子

[edit | edit source]

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

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

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

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

  
    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 解釋為假,將任何非零值解釋為真。

條件運算子

[edit | edit source]

三元 ?: 運算子是條件運算子。表示式 (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。然後三元運算子查詢“if-false”值,在本例中為 6。它返回該值,所以 y 等於 6。如果 x 為非零,則表示式將返回 10。

賦值運算子

[edit | edit source]

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

對於其他運算子,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.


華夏公益教科書