天下事有難易乎?爲之,則難者亦易矣;不爲,則易者亦難矣。人之爲學有難易乎?學之,則難者亦易矣;不學,則易者亦難矣。
天下的事情有困難和容易的區別嗎?只要肯做,那麼困難的事情也變得容易了;如果不做,那麼容易的事情也變得困難了。人們做學問有困難和容易的區別嗎?只要肯學,那麼困難的學問也變得容易了;如果不學,那麼容易的學問也變得困難了。
一、位運算符和位運算
1.基本概念
位運算是指按二進制位進行的運算,這是因爲在系統軟件中,常要處理二進制位的問題。
例如,將一個存儲單元中的各二進制位左移或右移一位,兩個數按位相加等。
C語言提供位運算的功能,與其他高級語言(如PASCAL)相比,具有很大的優越性。
2.位運算符
常見位運算符及含義如下:
運算符 | 含義 |
---|---|
& | 按位與 |
| | 按位或 |
^ | 按位異或 |
~ | 取反 |
<< | 左移 |
>> | 右移 |
位運算符中除~以外,均爲二目(元)運算符,即要求兩側各有一個運算量;
運算量只能是整型或字符型的數據,不能爲實型數據。
按位與運算符&
含義:
參加運算的兩個數據,按二進制位進行與運算。
如果兩個相應的二進制位都爲1,則該位的結果值爲1,否則爲0。
例如:
0 & 0 = 0, 0 & 1 = 0,
1 & 0 = 0, 1 & 1 = 1
3 & 5
並不等於8,而是二進制按位與運算,如下:
如果參加&運算的是負數(如-3&-5),則要以補碼形式表示爲二進制數,然後再按位進行與運算。
按位與運算的用途:
(1)清零
若想對一個存儲單元清零,即使其全部二進制位爲0,只要找一個二進制數,其中各個位符合以下條件:
原來的數中爲1的位,新數中相應位爲0。
然後使二者進行&運算,即可達到清零目的。
例如,要將二進制數11100101的第2位清零,則可以另找一個數11100001,兩者進行與運算即可。
(2)取一個數中某些指定位
例如:我們需要對一個字型數據取出其低8位的值時,我們可以如下:
按位或操作符|
兩個相應的二進制位中只要有一個爲1,該位的結果值爲1。
即:
0 | 0 = 0, 0 | 1 = 1,
1 | 0 = 1, 1 | 1 = 1
例如:
練習:
編寫一個小程序,將輸入的大寫字母轉換爲小寫字母,輸入的小寫字母轉換爲大寫字母,要求用位操作完成轉換過程。
實現思路:
對於一個大寫字母和對應的小寫字母的二進制形式,只是第5位不同,小寫字母爲1,大寫字母爲0。
代碼如下:
#include <stdio.h>
int main(){
char ch, temp;
printf("Please input a character:\n");
ch = getchar();
temp = getchar();
while(!(ch >= 'A' && ch <= 'z') || (ch > 'Z' && ch <'a')){
printf("Input Error, please input again:\n");
ch = getchar();
}
if(ch & 32){
ch &= 223;
}
else{
ch |= 32;
}
putchar(ch);
ch = getchar();
putchar(ch);
return 0;
}
打印:
Please input a character:
3
Input Error, please input again:
a
A
異或運算符^
異或運算符^也稱XOR運算符。
它的規則是:
若參加運算的兩個二進制位同號則結果爲0(假),異號則結果爲1(真)。
即:
0 ^ 0 = 0, 0 ^ 1 = 1,
1 ^ 0 = 1, 1 ^ 1 = 0
例如:
異或運算符應用:
(1)使特定位翻轉
設有01111010,想使其低44位翻轉,即1變爲0,0變爲1,可以將它與00001111進行^運算,即:
(2)與0相^,保留原值
例如:
因爲原數中的1與0進行^運算得1,0^0得0,故保留原數。
(3)交換兩個值,不用臨時變量
例如a = 3, b = 4
,現在想將a、b變量的值交換位置,傳統的做法是多定義一個temp變量,現在使用位運算也可以達到同樣的目的:
a = a ^ b;
b = b ^ a;
a = a ^ b;
這是因爲異或運算符可以進行逆運算,這種方法也常應用於加密算法。
取反運算符~
~是一個單目(元)運算符,用來對一個二進制數按位取反,即將0變1,將1變0。
例如,~025是對八進制數25(即二進制數00010101)按位求反。
例如:
左移運算符<<
左移運算符是用來將一個數的各二進制位全部左移若干位。
例如,a = a << 2
表示將a的二進制數左移2位,右邊補0。
若a = 15
,即二進制數00001111,左移2位得00111100(十進制數60);
若高位左移後溢出,捨棄。
左移1位相當於該數乘以2,左移2位相當於該數乘以4,15 << 2 = 60,即乘了4。
但此結論只適用於該數左移時被溢出捨棄的高位中不包含1的情況。
假設以一個字節(8位)存一個整數,若a爲無符號整型變量,則a=64時,左移一位時溢出的是0,而左移2位時,溢出的高位中包含1。
右移運算符>>
右移運算符是a>>2
表示將a的各二進制位右移2位,移到右端的低位被捨棄,對無符號數,高位補0。
例如a=017
時,a的值用二進制形式表示爲00001111, 捨棄低2位11,得到a >> 2 == 00000011
。
可以得到:
右移一位相當於除以2,右移n位相當於除以2n。
有關符號位的注意事項如下:
對無符號數,右移時左邊高位移入0;
對於有符號的值,如果原來符號位爲0(該數爲正),則左邊也是移入0;
如果符號位原來爲1(即負數),則左邊移入0還是1,要取決於所用的計算機系統,有的系統移入0,有的系統移入1。
移入0的稱爲邏輯右移, 即簡單右移;
移入1的稱爲算術右移。
例如,a的值是十進制數-2:
a = 1111 1110
(用二進制形式表示);
無符號數:a>>1 = 0111 1111
(邏輯右移時);
有符號數:a>>1 = 1111 1111
(算術右移時)。
練習如下:
#include <stdio.h>
int main(){
unsigned char a = -2;
char b = -2;
a = a >> 1;
b = b >> 1;
printf("a = %d\nb = %d\n", a, b);
return 0;
}
打印:
a = 127
b = -1
顯然,C語言對於有符號數和無符號數的處理是不同的。
位運算賦值運算符
位運算符與賦值運算符可以組成複合賦值運算符。
例如: &=、|=、>>=、<<=、^=。
所以,a &= b
相當於a = a & b
,a <<= 2
相當於a = a << 2
。
二、位運算舉例
- 練習:
取一個字符型變量a從右端開始的2-5位。
實現思路:
(1)先使a右移2位a >> 2
,目的是使要取出的那幾位移到最右端;
(2)設置一個低4位全爲1,其餘全爲0的數,即~(~0 << 4)
;
(3)將(1)、(2)進行&運算,(a >> 2) & ~(~0 << 4)
。
代碼如下:
#include <stdio.h>
int main(){
char a, b, c, d;
printf("Please input the number:\n");
scanf("%d", &a);
b = a >> 2;
c = ~(~0<<4);
d = b & c;
printf("%d\n");
return 0;
}
打印:
Please input the number:
37
9
- 練習:
要求將a進行右循環移位,如下:
代碼如下:
#include <stdio.h>
int main(){
unsigned a, b, c, d;
int n;
printf("Please input the number:\n");
scanf("%d", &a);
printf("Please enter the number of digits to be moved:\n");
scanf("%d", &n);
b = a << (sizeof(char) * 8 - n);
c = a >> n;
d = b | c;
printf("result = %d\n", d);
return 0;
}
打印:
Please input the number:
25
Please enter the number of digits to be moved:
2
result = 1606
三、位段
信息的存取一般以字節爲單位,但有時存儲一個信息不必用一個或多個字節。
例如,真或假用0或1表示,只需1位即可。
在計算機用於過程控制、參數檢測或數據通信領域時,控制信息往往只佔一個字節中的一個或幾個二進制位,常常在一個字節中放幾個信息。
C語言允許在一個結構體中以位爲單位來指定其成員所佔內存長度,這種以位爲單位的成員稱爲位段或稱位域(bit field),利用位段能夠用較少的位數存儲數據。
如下:
struct packed_data{
unsigned a:2;
unsigned b:6;
unsigned c:4;
unsigned d:4;
int 1;
} data;
位段的定義和引用的說明:
(1)位段成員的類型必須指定爲unsigned或int類型。
(2)若某一位段要從另一個字開始存放,可用以下形式定義:
unsigned a:1;
unsigned b:2; // 一個存儲單元
unsigned :0;
unsigned c:3; // 另一存儲單元
a、b、c應連續存放在一個存儲單元中,由於用了長度爲0的位段,其作用是使下一個位段從下一個存儲單元開始存放。因此,只將a、b存儲在一個存儲單元中,c另存在下一個單元(存儲單元可能是一個字節,也可能是2個字節,視不同的編譯系統而異)。
(3) 一個位段必須存儲在同一存儲單元中,不能跨兩個單元;
如果第一個單元空間不能容納下一個位段,則該空間不用,而從下一個單元起存放該位段。
(4) 可以定義無名位段。
(5) 位段的長度不能大於存儲單元的長度,也不能定義位段數組。
(6) 位段可以用整型格式符輸出。
(7) 位段可以在數值表達式中引用,它會被系統自動地轉換成整型數。