一、操作符
包括:算術操作符,移位操作符,位操作符,賦值操作符,單目操作符,關系操作符,邏輯操作符,條件操作符,下標引用,函數調用,結構體成員調用。
1、算術操作符
加、減、乘、除、取模(%)
取模的兩個操作數只能是整型值。
2、移位操作符
左移位:<< 最左邊幾位丟棄,最右邊幾位補0;
右移位:>> (1)邏輯移位:最右邊幾位丟棄,左邊補0;
(2)算術移位:最右邊幾位丟棄,左邊符號位為0,則全移入0,為1則全移入1。
標準規定無符號數都執行邏輯移位,而有符號數執行的是邏輯移位還是算術移位由編譯器決定,若移位的位數超過操作數的位數,則具體情況由編譯器決定。
所以出現有符號數移位操作的程序是不可移植的。
3、位操作符
& | ^ (與,或,異或)
通常用于位操作,修改變量指定位的值。
舉例:
value的第bit_number位置1:
value |= 1 << bit_number;
value的第bit_number位置0:
value &= ~ (1 << bit_number);
測試指定位是否為1,為1則表達式非0:
value &= 1 << bit_number;
4、賦值操作符
賦值是表達式的一種,而不是某種類型的語句,所以只要允許出現表達式的地方都可以進行賦值。
舉例:
a = x = y + 3;
a和x被賦的值并不是同一個值
若x的長度不夠,則x被賦予的y+3值會被截短,再存儲于x中,之后這個被截短的值再被賦予到a中。
復合賦值符:+=,-=,|=等等,多使用可以使代碼更簡潔。
5、單目操作符
!; ++; -- ; + ;-; ~ ;& ;* ;sizeof ; (類型)
- sizeof():判斷操作數的類型長度,以字節為單位。
sizeof (int):必須加括號;
sizeof x:可以不加括號;
舉例:
16位機器中:int arry[10];
則sizeof(arry)=20
sizeof(arry[0])=2
sizeof(arry)/sizeof(arry[0])=10,即數組中元素的個數。
sizeof(a = b + 3):sizeof可以判斷表達式的長度,此時不需要求表達式的值,所以a并沒有被賦值;表達式返回a的大小。
(類型):強制類型轉換(cast),具有很高的優先級,所以注意:把強制類型轉換放在表達式的前面只會改變第一個項目的類型,要操作整個表達式的話,就要加括號。
float(a)
:獲得a對應的浮點數值。增值++和減值--操作符。
前綴操作符在變量被使用之前改變變量的值;
后綴操作符在變量被使用之后改變變量的值。
舉例:
c = ++a; d=a++;
其中,c得到a增加之前的值,d得到a增加之后的值。
前綴和后綴形式的增值操作都復制了一份變量值的拷貝,用于周圍表達式的值是這份拷貝來的值(賦值表達式);
前綴形式,在變量值增加之后復制,后綴形式,在變量值增加之前復制。
因此這些操作符的結果不是被他們修改的變量,而是變量值的拷貝。
++a = 10;
這條語句時錯誤的,++a的結果是變量值的拷貝,不是變量a本身,因此無法向一個值進行賦值。
6、關系操作符
>;>=;<;<=;!=;==
關系操作符的結果是一個整型值,而不是布爾值,可以將結果賦值給整型變量。(C語言中沒有布爾類型,所以用整數來代替,非0位真,0為假)
7、邏輯操作符
邏輯與&&,邏輯或||,都會控制表達式的順序執行:
從左到右依次執行,&&若左操作數為假,則后續右操作數不再求值,整個表達式為假,||邏輯或同理。這個行為被稱為“短路求值”(short-circuited evaluation)。
舉例:
if(a<b && c>d)
if(a<b & c>d)
和
if(a && b)
if(a & b)
第一組語句的結果一樣,因為關系操作符的結果只能是0或1,而第二組語句結果不一樣,因為若a,b非0,則第一個一定為真,而a,b按位與的值并不一定非0,所以不一定為真。
8、條件操作符
expression1 ? expression2 : expression3
會控制子表達式的求值順序,expression2和expression3只會執行其中一個。
b = a > 5 ? 3 : -20;
a > 5則b = 3,否則b = -20。
條件操作符的作用有時類似于if,else,且比它更簡潔。
9、逗號操作符
expression1,expression2,expression3,......,expressionN
將多個表達式用逗號分開,并從左到右依次執行,整個表達式的值為最后那個表達式的值。
while(a = get_value(), count_value( a ), a>0)
{
expression;
}
因為依次執行,所以用在循環語句的測試表達式中時,獲得下一個測試值語句只出現一次,修改時只需要在一個地方修改,方便程序的維護。
二、表達式求值
- 表達式的求值順序由所包含操作符的優先級和結合性決定;
- 求值過程中的類型轉換。
1、隱式類型轉換(implicit conversion)
-
發生的情況:
- 算術表達式或邏輯表達式的操作數類型不相同時;(執行常用算術轉換,即usual arithmetic conversion)
- 操作符兩邊的變量類型不相同時;
- 函數調用時,實參與形參不匹配時;
- return語句中表達式類型和函數返回值類型不匹配時。
隱式轉換規則
long double
double
float
unsigned long int
long int
unsigned int
int
排名較低的操作數首先轉換為另一個操作數的類型,即低精度數像高精度數轉換。
但是,在32位機器上,int類型和long字長相同,這時unsigned int的精度就比long精度高。整型運算符的精度至少是缺省整型類型,則運算過程中,字符型和短整型在使用之前被轉換成普通整型,這種轉換過程被稱為整型提升(integral promotion)。
提升精度往往無害,但降低精度可能會導致問題,低精度類型可能不夠大,不能容納高精度的完整數據。
(1)算術轉換
舉例:
char a,b,c;
statement;
a = b + c;
首先,b和c的值被提升為整型,然后,執行加法運算,最后,將結果截短后賦值并存儲于a中。
當然,也可以使用強制類型轉換執行顯示轉換(explicit conversion):(int)a = b + c;
(2)顯示轉換
舉例:
int a = 5000;
int b = 25;
long c = a * b;
在32位機器上,int和long int都是32位,這段代碼運行起來沒有問題;但在16位機器上,int是16位,long是32位,a*b的值應該是int型,但由于數據類型不夠大(max = 2^16 - 1 = 65535),所以發生溢出,c會被賦一個無意義的值,因此需要進行強制類型轉換:
long c = (long)a*b;
注意:
long c = (long)(a*b);
是錯誤的,因為溢出在強制轉換之前就已經發生了,所以這樣做不會有任何改變。-
數據溢出:
- 有符號數的數據溢出是未定義的;
- 無符號數的數據溢出:溢出后的數以2^(8*sizeof(type))作模運算。比如用unsigned char型變量存儲258,其實存進去的是258-2^8=2。
(3)操作符兩邊表達式的轉換
舉例:
unsigned int a = 6;
int b = -20;
int c = (a + b) > 6 ? 1 : 2;
在加法運算中a和b類型不一致,會發生隱式轉換,將int型轉換為unsigned int型,b=-20轉換為無符號數會變成一個很大的整數,因為無符號負數轉換為的正數是用負數的補碼所表示的,正數的源碼,反碼,和補碼相同。所以,程序輸出1,而不是2。
舉例:
if(strlen(arry) < 10)
if(strlen(arry) - 10 < 0)
這兩條語句不等價,因為strlen函數返回值是unsigned int型,兩個無符號數運算所得的結果仍為無符號數,而無符號數肯定大于0。所以,要盡量避免使用第二個表達式。
2、操作符的優先級
表達式的運算規則:
兩個相鄰操作符的執行順序由他們的優先級決定,若優先級相同,則由他們的結合性決定。
結合性:當多個相同優先級的運算符出現在表達式中時,先執行左邊的叫具有左結合性,先執行右邊的叫具有右結合性。
- 舉例:
a*b + c*d + e*f
相鄰的加法和乘法運算符中,乘法運算符先執行;兩個加法運算,根據加法的左結合性,是左邊的加法運算先執行。但對于哪個乘法運算先執行,以及是否在所有乘法執行完后再執行加法運算,這些都由編譯器決定,所以表達式就會有多種執行順序。
c + --c
相鄰的+和--操作符是--先執行,但對于表達式c和--c并不知道哪個先執行,又因為--c具有副作用,所以這兩個表達式的執行順序會對結果有影響。
- 編譯器只要不違背優先級和結合性規則,就可以任意決定復雜表達式的取值順序。