C語言中的按位移動及其簡單應用
在C語言中按位左移用”<<”表示,按位右移用”>>”表示。
按位左移和按位右移運算經常被用來替換乘二和除二運算,但是要注意,這兩者之間並不完全等價。下面就分析一下:
首先明確,按位移動分為邏輯移動和算術移動,具體就是:邏輯左移、算術左移;邏輯右移、算術右移。
算術左移、算術右移、邏輯左移、邏輯右移的操作如下圖:
可以看到,邏輯左移=算術左移:都是左移然后右邊補零
算術右移和邏輯右移有所區別:邏輯右移是右移並且左邊補0,而算術左移是右移並且左邊補1(也就是補符號位)
在C語言中:左移采用的邏輯左移(和算術左移的效果相同)和算術右移。由於算術左移和邏輯左移的效果相同,所以我們可以認為,C語言中采用的是算術移動(對於有符號數而言)。
那么算術左移、算術右移和乘二、除二運算結果是否一致呢?
先來看左移:
先給出結論:無論是正數還是負數,只要結果不溢出(也就是不超過取值范圍)結果就是乘2。
比如:
輸出為:
對於結果溢出的(也就是超出取值范圍的),比如:
輸出為:
發現沒,兩個結果都不對,因為char是8位的,表示范圍為-128~127,結果產生溢出了。那為什么會產生這樣的結果呢?
a = 65. 二進制位:0100 0001
邏輯左移一位結果為: 1000 0010 ,我們發現,這個數的符號位變成了1,也就是負數了,就是-126。細心的人也會發現,理想結果130、實際結果 -126、27,這三者之間貌似有某種聯系,的確有聯系,這里就不深究了。
再來看看右移:
先給結論:首先,右移,並不會造成結果溢出的情況。對於正數,放心大膽地去右移吧,結果都相當於除二;但是對於負數,結果不一定相等,最好不要用右移代替除二。
比如:對於正數
輸出為:
結果都是正確的。
但是對於負數,比如:
輸出為:
可以看到對於偶數的結果是正確的。但是對於奇數,結果的絕對值比除二的結果的絕對值大1。
那么能否實現邏輯右移呢?
上面提到了C語言中采用的是算術右移,那么有沒有辦法實現邏輯右移呢?可以的。
我們需要注意一下無符號數,無符號數並沒有符號位,所以對無符號數進行的位移都是邏輯位移。對於一個有符號數,如果我們想對他進行邏輯右移,那么我們可以先將該數轉換成對應對的無符號數,然后再進行右移操作。
比如對於char a = -6。
二進制位 1111 1010
如果是算術右移,結果應該為: 1111 1101 也就是-3
如果是邏輯右移,結果應該為: 0111 110 也就是125
下面我們驗證一下:
輸出為:
正確
位移運算的簡單應用
有這樣一個題,要求是將一個int型的數據轉換成二進制和16進制的字符串輸出。
比如對於整型量456789
我們要輸出:(空格不用了)
00000000 00000110 11111000 01010101
0x0006F855
對於求二進制,該數在內存中就是二進制的表示,我們只需要把每一位變成字符然后存到字符數組中即可,方法就是按位與和位移相結合,比如我們想得到第一個位(最高位),那么我們可以先按位與上10000000 00000000 00000000 00000000 然后將結果按位右移31位(需要注意,應該是采用邏輯右移,而不是算術右移,也就是前面要補0,而不是補1,方法是上面提到的,先將該數轉換成無符號數),就得到了第一位的值,之后轉換成字符即可。
對於求16進制,我們則需要以四個二進制位為一個單位,方法類似,思路就是想辦法把當前要轉換的四個二進制位移動到最低四位,因此我們可以用一個char來保存8位二進制,然后想辦法把高四位置為0,把待轉換的四位放到第四位。
比如說,我們現在想轉換標紅的部分
00000000 00000110 11111000 01010101
應該是轉換成F,那么怎么轉換呢?
首先我們先將該數左移16位,也就是4*4位,改數就變成了
11111000 01010101 00000000 00000000
然后我們在右移28位,也就是32-4位(特別注意,這里應該是邏輯右移,而不是算術右移,也就是要保證前面補0,而不是補1,方法就是上面提到的,先將該數轉換成無符號數,然后再右移)
結果為:
00000000 00000000 00000000 00001111
最后在用char類型將數據的低8位截斷,並轉換成字符F即可。
源代碼如下:
#include <stdio.h> #include <string.h> #include <stdlib.h> char *get_bin_string(int num) { unsigned int u_num = (unsigned int)num; char *buffer = (char *)malloc(33); if(buffer == NULL) return NULL; buffer[32] = '\0'; int i = 0; for(;i < 32;i++) { unsigned int temp = u_num &(1<<(31 - i)); temp = temp>>(31-i); buffer[i] = temp == 0 ? '0':'1'; } return buffer; } char *get_hex_string(int num) { unsigned int u_num = (unsigned int)num; char *buffer = (char *)malloc(11); if(buffer == NULL) return NULL; //填寫固定的部分 buffer[0] = '0'; buffer[1] = 'x'; buffer[10] = '\0'; char *temp = buffer + 2; int i = 0; for(;i < 8;i++) { unsigned int af = u_num<<(4 * i); af = af >> 28; temp[i] = (char)(u_num<<(4 * i)>>28); temp[i] = temp[i] < 10 ? temp[i] + 48 :temp[i] + 55; } return buffer; } int main() { int num = 456789; char *bin = get_bin_string(num); char *hex = get_hex_string(num); printf("二進制為:%s\n",bin); printf("十六進制為:%s\n",hex); free(hex); free(bin); return 0; }
最后再強調一下,右移的時候一定要考慮清楚,我們是想要前面補0還是補1,也就是采用邏輯右移還是算術右移,如果是采用邏輯右移,要先將該數轉換成無符號數。
就到這里了。
最后附上word文件和源代碼文件
鏈接:http://pan.baidu.com/s/1nuYBXjj 密碼:5huf
如果你覺得對你有用,請贊一個吧~~~