2. 其它運算符

2.1. 復合賦值運算符

復合賦值運算符(Compound Assignment Operator)包括*= /= %= += -= <<= >>= &= ^= |=,一邊做運算一邊賦值。例如a += 1相當於a = a + 1。但有一點細微的差別,前者對錶達式a只求值一次,而後者求值兩次,如果a是一個複雜的表達式,求值一次和求值兩次的效率是不同的,例如a[i+j] += 1a[i+j] = a[i+j] + 1。那麼僅僅是效率上的差別嗎?對於沒有Side Effect的表達式,求值一次和求值兩次的結果是一樣的,但對於有Side Effect的表達式則不一定,例如a[foo()] += 1a[foo()] = a[foo()] + 1,如果foo()函數調用有Side Effect,比如會打印一條消息,那麼前者只打印一次,而後者打印兩次。

第 3 節 “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。