復合賦值運算符(Compound Assignment Operator)包括*=
/=
%=
+=
-=
<<=
>>=
&=
^=
|=
,一邊做運算一邊賦值。例如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語句”講自增、自減運算符時說++i
相當於i = i + 1
,其實更準確地說應該是等價于i += 1
,表達式i
只求值一次,而--i
等價于i -= 1
。
條件運算符(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; }
逗號運算符(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
的值。
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。