C語言位運算符:與、或、異或、取反、左移與右移


  位運算是指按二進制進行的運算。在系統軟件中,常常需要處理二進制位的問題。C語言提供了6個位操作運算符,這些運算只能用於整型操作數,即只能用於帶符號或無符號的char、short、int與long類型。浮點數因為浮點型和整型在計算機內的存儲方式大相徑庭,同樣是32位。但是浮點數是1位表示符號位,23位表示數值部分,8位其他表示指數部分。而整型只是單純32位補碼形式存放的,這就是位運算不能用於浮點數的原因。
1、“按位與”運算符(&)
  按位與是指:參加運算的兩個數據,按二進制進行“與”運算。如果兩個相應的二進制位都位1,則該位的結果為1;否則為0。這里的1的可以理解為邏輯中的true,0可以理解為邏輯的false。按位與其實與邏輯上“與”的運算規則一致。邏輯上的“與”,要求運算數全真,結果才為真。若A=true, B=true,則A∩B=true 例如:3&5, 3的二進制編碼是11(2)。(為了區分十進制和其他進制,本文規定,凡是非十進制的數據均在數據后面加上括號,括號中注明其進制,二進制則標記為2,內存儲存數據的基本單位是字節(Byte),一個字節由8個位(bit)所組成。位是用以描述電腦數據量的最小單位。二進制系統中,每個0或1就是一個位。將11(2)補足成一個字節,則是00000011(2)。5的二進制編碼是101(2),將其補足稱一個字節,則00000101(2)。
按位與運算:
0000 0011(2) & 000000101(2) = 00000001(2)
由此可知3&5 = 1。

C語言代碼:

1 #include <stdio.h>
2 
3 int main(void) {
4     int a = 3, b = 5;
5     printf("a and b: %d\n", a & b); //0011 & 0101
6     return 0;
7 }

CPU處理位運算的速度是最快的,所以很多操作我們都可以轉換為位運算,以下是用按位與替換取模運算進行奇偶數判斷。

 1 /*************************************************************************
 2     > File Name: 2.test.c
 3     > Author: yudongqun
 4     > Mail: qq2841015@163.com
 5     > Created Time: Sun 18 Oct 2020 10:19:55 PM CST
 6  ************************************************************************/
 7 #include <stdio.h>   
 8                         
 9 int main(void) {        
10     int n;              
11     printf("please input a integer:");
12     while (scanf("%d", &n) != EOF) {
13         //if(n % 2) {
14         if (n & 1) {                                                                                                                  
15            printf("Odd number\n"); //奇數
16         } else {        
17           printf("Even number\n");//偶數
18         }               
19         printf("please input a integer:");
20     }                   
21     return 0;           
22 } 

編譯運行,並輸入數字來測試,結果如下:

ydqun@VM-0-9-ubuntu 20201017 % g++ 2.test.c                                                                                       [0]
ydqun@VM-0-9-ubuntu 20201017 % ./a.out                                                                                            [0]
please input a integer:3
Odd number
please input a integer:2
Even number
please input a integer:%                                                                                                              
ydqun@VM-0-9-ubuntu 20201017 %    

14行就是用&運算符去代替%運算符實現奇偶數判斷,這樣效率更快。

2.按位或運算符 

兩個相應的二進制位中只要有一個為1,該位的結果值為1。借用邏輯學中或運算的話來說就是,一真為真。

例如 十進制6對應二進制0000 0110與8對應的二進制0000 1000進行或運算,0000 0110 | 0000 1000 = 0000 1110,對應十進制的14。

測試的c語言代碼。

 1 /*************************************************************************
 2     > File Name: 3.test.c
 3     > Author: yudongqun
 4     > Mail: qq2841015@163.com
 5     > Created Time: Mon 19 Oct 2020 05:14:44 PM CST
 6  ************************************************************************/
 7 
 8 #include <stdio.h>
 9 
10 int main(void) {
11     int n = 8, m = 6;
12     printf("%d | %d = %d\n", n, m, n | m);
13     return 0;
14 }

3.異或運算 

    異或是一個數學運算,用於邏輯運算。如果a、b兩個值不同,則異或結果為1,否則結果為0,在C語言中是一種強大的基本運算符,有很多巧妙的應用。

例如, A = 14, B = 10;

A = 14,二進制則為1110,B = 10,二進制為1010.

對二進制數進行異或運算 -> 1110^1010 = 0100,對應十進制就為4。

在二進制數異或過程中,我們可以得知異或運算是一種半加運算。什么意思呢?半加即不帶進位的加法運算。

如上面1110^1010 = 0100,如果在加法中,如下

  

 我們從低位開始加起,首先第0位為0+0=0;第一位1+1=0,如果是加號運算符,則需要進位,但由於是異或運算(半加),故不用進位,第二位為1+0=1;最后一位為1+1=0。最終結果就是0100,這就是半加的過程。

特性

  1.一個數與0進行異或運算,其運算結果是自身;

  2.一個數與自身進行異或運算,其運算結果為0;

  3.異或運算滿足分配律,即 3^4^3與3^3^4的結果一樣,都為4。

異或運算的一些應用

1.異或最常用的一種用法 -- 交換兩個數的值。

  這里直接上代碼。

 1 /*************************************************************************
 2     > File Name: swap.c
 3     > Author: yudongqun
 4     > Mail: qq2841015@163.com
 5     > Created Time: Fri 16 Oct 2020 04:48:54 PM CST
 6  ************************************************************************/
 7 
 8 #include <stdio.h>
 9 
10 int main(void) {
11     int a = 10, b = 20, tmp;
12 
13 #if 0
14     /*用中間值來實現值交換*/
15     tmp = a;
16     a = b;
17     b = tmp;
18 #else
19     /*用異或操作來實現值交換*/
20     a ^= b;
21     b ^= a;
22     a ^= b;
23 #endif
24     printf("a: %d, b: %d\n", a, b);
25 
26     return 0;
27 }
ydqun@VM-0-9-ubuntu operator % gcc swap.c                                                                  [0]
ydqun@VM-0-9-ubuntu operator % ./a.out                                                                     [0]
a: 20, b: 10
ydqun@VM-0-9-ubuntu operator %

這里異或操作實現的值交換的好處是少使用了一個臨時變量,執行效率也比較高。

2,尋找只出現一次的數字。

給定一個非空整數數組,除了某個元素只出現一次以外,其余每個元素均出現兩次。找出那個只出現了一次的元素。

說明:

你的算法應該具有線性時間復雜度。 你可以不使用額外空間來實現嗎?

示例 1:

輸入: [2,2,1]
輸出: 1

示例 2:

輸入: [4,1,2,1,2]
輸出: 4
 1 /*************************************************************************
 2     > File Name: single_number.c
 3     > Author: yudongqun
 4     > Mail: qq2841015@163.com
 5     > Created Time: Fri 16 Oct 2020 05:20:25 PM CST
 6  ************************************************************************/
 7 
 8 #include <stdio.h>
 9 
10 int single_number(int nums[], int n) {
11     int res = 0;
12     for (int index = 0; index < n; index++) {
13         res ^= nums[index];
14     }
15     return res;
16 }
17 
18 int main(void) {
19     int nums[5] = {1, 2, 2, 1, 6};
20     printf("single number is : %d\n", single_number(nums, 5));
21 }
ydqun@VM-0-9-ubuntu operator % g++ single_number.c                                                         [0]
ydqun@VM-0-9-ubuntu operator % ./a.out                                                                     [0]
single number is : 6
ydqun@VM-0-9-ubuntu operator %  

這里是運用了異或的特性2與特性3,1^2^2^1^6 = 1^1^2^2^6 = 0^0^6 = 6。

4.按位取反運算

按位取反運算符是把一個數的二進制照着每個位取反,即值為0的位變為1,值1的位變為0,但是我們要注意的是,要結合二進制數在內存中是以補碼的形式存儲的情況一起分析(不知道補碼概念請看https://www.cnblogs.com/ydqblogs/p/13823206.html),接下來我們以10按位取反為例子。

假設我們有一個整型變量x = 10,在計算機中一個整型樹為4個字節,而一個字節為8位,所以數字10在計算機中存儲占32位,且為補碼存儲在內存中,即

原       碼:00000000 00000000 00000000 00001010

補       碼:00000000 00000000 00000000 00001010

按位取反:11111111 11111111 11111111 11110101

這時候,“~10”的二進制數的最高位是1表示它是一個負數,它是在內存的存儲時的值(補碼),我們需要求回原碼。由補碼求原碼的操作跟由原碼求補碼的操作是一樣的。

接着上述:11111111 11111111 11111111 11110101

反       碼:10000000 00000000 00000000 00001010

補       碼:10000000 00000000 00000000 00001011

二進制10000000 00000000 00000000 00001011對應十進制是-11,這時候我們推導出~10=-11,我們可以寫一個程序測試一下我們的推導結果。

 1 /*************************************************************************
 2     > File Name: bit_reverse.c
 3     > Author: yudongqun
 4     > Mail: qq2841015@163.com
 5     > Created Time: Tue 20 Oct 2020 09:25:50 AM CST
 6  ************************************************************************/
 7 
 8 #include <stdio.h>
 9 
10 int main(void) {
11     printf("%d\n", ~10);
12     return 0;
13 }
ydqun@VM-0-9-ubuntu 20201017 % g++ bit_reverse.c                            [0]
ydqun@VM-0-9-ubuntu 20201017 % ./a.out                                      [0]
~10 = -11

  

由測試程序得出的結果,我們可以驗證我們的推導是正確的,所以我們可以看出一個規律:正整數x,~x = -(x+1)。接下來我們來推導一下負整數按位取反的結果。

假設我們有一個負數-10,我們來推導一下按位取反后的結果。

原        碼:10000000 00000000 00000000 1010

反        碼:11111111 11111111 11111111 0101

補       碼:11111111 11111111 11111111 0110

按位取反:00000000 00000000 00000000 1001

我們發現負數在內存中存儲的值在按位取反,符號位為0,即變為了正數,我們知道正數的補碼和原碼是一樣的,在內存中的補碼就是在程序中實際的原碼,由此得到:對於負數-x,~(-x) = x-1。同樣我們可以寫一個測試程序。

ydqun@VM-0-9-ubuntu 20201017 % g++ bit_reverse.c                            [1]
ydqun@VM-0-9-ubuntu 20201017 % ./a.out                                      [0]
~(-10) = 9  

至此我們可以得出一個結論。

對於正整數x,~x = -(x + 1);

對於負整數-x, ~(-x) = (x - 1)。

5.左移運算符 -- <<

  左移運算符是一個計算機用語,用來將一個數的各二進制位全部左移若干位,移動的位數由右操作數指定,右操作數必須是非負值,其右邊空出的位用0填補,高位左移一處則舍棄該高位。

  接下來,我們來各自分析正數和負數的左移過程。

  假設我們有一個正整型數x,值為2,我們要分析它左移2位后的值為多少,即求 2 << 2。

原       碼:00000000 00000000 00000000 00000010

補       碼:00000000 00000000 00000000 00000010

左移兩位:00000000 00000000 00000000 00001000

  二進制數00000000 00000000 00000000 00001000對應的十進制值為8,由此我們得知2 << 2 = 8,由此,我們能得到對於一個正整數x,每左移一位,x的值就變為原來的2倍。那么負整數呢?左移運算結果又是多少?其實跟正整型數是一樣的結果,只不過我們在左移運算的時候要關心符號位,即左移不影響符號位,移動的都是數據域。

  假設我們有一個十六進制的負整型數0xc0000001,對應十進制值為-2147483646,我們要求左移一位后該數值為多少?

原       碼:11000000 00000000 00000000 00000001
反       碼:10111111 11111111 11111111 11111110
補       碼:10111111 11111111 11111111 11111111
左移一位:11111111 11111111 11111111 11111110
反       碼:10000000 00000000 00000000 00000001
原       碼:10000000 00000000 00000000 00000010

  由此我們得到0xc0000001左移一位后值為0x80000002。我們可以寫一個程序測試一下。

 

 1 /*************************************************************************
 2     > File Name: left_shift.c
 3     > Author: yudongqun
 4     > Mail: qq2841015@163.com
 5     > Created Time: Tue 20 Oct 2020 01:29:00 PM CST
 6  ************************************************************************/
 7 
 8 #include <stdio.h>
 9 
10 int main(void) {
11     int num = 0xc0000001;
12     printf("0x%x << 1 = 0x%x\n", num, num << 1);
13     return 0;
14 }

 

ydqun@VM-0-9-ubuntu 20201017 % ./a.out                                                                   [0]
0xc0000001 << 2 = 0x80000002
ydqun@VM-0-9-ubuntu 20201017 % 

 

  在這里,關於一個數左移一位為什么是原來數的兩倍,我這里有個人的理解,首先,有一個名詞叫位的權值,對於二進制數,每一位的權值為2,其中n表示第幾位,所以我們得知第i位的權值是2,而第i+1為的權值為2i+1,所以往左移動一位,權值變為原來的2倍,則數的值也為原來的兩倍。十進制數也是同樣的道理。

5.右移運算符 -- >>

右移運算符和左移運算符大致一樣,唯一區別就是方向不同,另外對於有符號整型數右移,左邊是補符號位;而對於無符號整型數右移,左邊補0。在這里我們只分析有符號的負整型數。

假設我們有一個x值為-10,我們把其右移兩位。

原       碼:10000000 00000000 00000000 00001010

反       碼:11111111 11111111 11111111 11110101

補       碼:11111111 11111111 11111111 11110110

右移兩位:11111111 11111111 11111111 11111101    -- 因為是有符號且為負數,所以補1。

反       碼:10000000 00000000 00000000 00000010

原       碼:10000000 00000000 00000000 00000011

由上述分析結果得(-10) >> 2 = -3。我們可以寫測試程序測試一下結果。

 

 1 /*************************************************************************
 2     > File Name: right_shift.c
 3     > Author: yudongqun
 4     > Mail: qq2841015@163.com
 5     > Created Time: Tue 20 Oct 2020 04:33:18 PM CST
 6  ************************************************************************/
 7 
 8 #include <stdio.h>
 9 
10 int main(void) {
11     int num = -10;
12     printf("%d >> 2 = %d\n", num, num >> 2);
13     return 0;
14 }

 

ydqun@VM-0-9-ubuntu 20201017 % g++ right_shift.c                                                         [0]
ydqun@VM-0-9-ubuntu 20201017 % ./a.out                                                                   [0]
-10 >> 2 = -3

  到了這里,我們已經學習完C語言的所有位運算符,下面我們來給出一些位運算的綜合運用。

6、位運算的綜合運用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM