C語言入門系列之12.位運算

天下事有難易乎?爲之,則難者亦易矣;不爲,則易者亦難矣。人之爲學有難易乎?學之,則難者亦易矣;不學,則易者亦難矣。
天下的事情有困難和容易的區別嗎?只要肯做,那麼困難的事情也變得容易了;如果不做,那麼容易的事情也變得困難了。人們做學問有困難和容易的區別嗎?只要肯學,那麼困難的學問也變得容易了;如果不學,那麼容易的學問也變得困難了。

爲學一首示子侄

一、位運算符和位運算

1.基本概念

位運算是指按二進制位進行的運算,這是因爲在系統軟件中,常要處理二進制位的問題。
例如,將一個存儲單元中的各二進制位左移或右移一位,兩個數按位相加等。

C語言提供位運算的功能,與其他高級語言(如PASCAL)相比,具有很大的優越性。

2.位運算符

常見位運算符及含義如下:

運算符 含義
& 按位與
| 按位或
^ 按位異或
~ 取反
<< 左移
>> 右移

位運算符中除~以外,均爲二目(元)運算符,即要求兩側各有一個運算量;
運算量只能是整型或字符型的數據,不能爲實型數據。

按位與運算符&

含義:
參加運算的兩個數據,按二進制位進行與運算
如果兩個相應的二進制位都爲1,則該位的結果值爲1,否則爲0。

例如:

0 & 0 = 0, 0 & 1 = 0,
1 & 0 = 0, 1 & 1 = 1

3 & 5並不等於8,而是二進制按位與運算,如下:
3&5
如果參加&運算的是負數(如-3&-5),則要以補碼形式表示爲二進制數,然後再按位進行與運算。

按位與運算的用途
(1)清零
若想對一個存儲單元清零,即使其全部二進制位爲0,只要找一個二進制數,其中各個位符合以下條件:
原來的數中爲1的位,新數中相應位爲0。
然後使二者進行&運算,即可達到清零目的。
例如,要將二進制數11100101的第2位清零,則可以另找一個數11100001,兩者進行與運算即可。
(2)取一個數中某些指定位
例如:我們需要對一個字型數據取出其低8位的值時,我們可以如下:
與運算用途2

按位或操作符|

兩個相應的二進制位中只要有一個爲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進行^運算,即:
異或運算符應用1

(2)與0相^,保留原值
例如:
異或運算符應用2
因爲原數中的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 & ba <<= 2相當於a = a << 2

二、位運算舉例

  1. 練習:
    取一個字符型變量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

  1. 練習:
    要求將a進行右循環移位,如下:
    位運算舉例2實現思路
    代碼如下:
#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) 位段可以在數值表達式中引用,它會被系統自動地轉換成整型數

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章