C语言每日一练 —— 第20天:位运算

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言每日一练 —— 第20天:位运算相关的知识,希望对你有一定的参考价值。

一、前言

  今天主要内容是聊一聊二进制和位运算。
  对应视频教程如下:位运算视频教程

二、再谈二进制

  我们在学习 光天化日学C语言(06)- 进制转换入门 的时候,曾经提到过二进制。
  二进制就是逢二进一,计算机中的存储采用的就是二进制。在计算机中,非零即一。

1、二进制数值表示

  例如,在计算机中,我们可以用单纯的 0 和 1 来表示数字。

1、101、1100011、100101010101 都是二进制数。
123、423424324、101020102101AF 则不是,因为有 0 和 1 以外的数字位。

  一般为了不产生二义性,我们会在数字的右下角写上它的进制,例如: 101 0 ( 10 ) 1010_(10) 1010(10)  代表的是十进制下的 1010,也就是十进制下的 “一千零一十”。 101 0 ( 2 ) 1010_(2) 1010(2)  代表的是二进制下的 1010,也就是十进制下的 “十”。

2、二进制加法

  二进制加法采用从低到高的位依次相加,当相加的和为2时,则向高位进位。

  例如,在二进制中,加法如下: 1 ( 2 ) + 1 ( 2 ) = 1 0 ( 2 ) 1 ( 2 ) + 0 ( 2 ) = 1 ( 2 ) 0 ( 2 ) + 1 ( 2 ) = 1 ( 2 ) 0 ( 2 ) + 0 ( 2 ) = 0 ( 2 ) 1_(2) + 1_(2) = 10_(2) \\\\ 1_(2) + 0_(2) = 1_(2) \\\\ 0_(2) + 1_(2) = 1_(2) \\\\ 0_(2) + 0_(2) = 0_(2) 1(2)+1(2)=10(2)1(2)+0(2)=1(2)0(2)+1(2)=1(2)0(2)+0(2)=0(2)

3、二进制减法

  二进制减法采用从低到高的位依次相减,当遇到 0 减 1 的情况,则向高位借位。

  例如,在二进制中:减法如下: 1 ( 2 ) − 1 ( 2 ) = 0 ( 2 ) 1 ( 2 ) − 0 ( 2 ) = 1 ( 2 ) 1 0 ( 2 ) − 1 ( 2 ) = 1 ( 2 ) 0 ( 2 ) − 0 ( 2 ) = 0 ( 2 ) 1_(2) - 1_(2) = 0_(2) \\\\ 1_(2) - 0_(2) = 1_(2) \\\\ 10_(2) - 1_(2) = 1_(2) \\\\ 0_(2) - 0_(2) = 0_(2) 1(2)1(2)=0(2)1(2)0(2)=1(2)10(2)1(2)=1(2)0(2)0(2)=0(2)  而我们今天要讲的位运算正是基于二进制展开的。

三、位运算简介

  位运算可以理解成对二进制数字上的每一个位进行操作的运算。位运算分为 逻辑(布尔)位运算符 和 移位位运算符。
  逻辑位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。
  如图所示:

1、位与的定义

  位与运算符是一个二元的位运算符,也就是有两个操作数,表示为x & y
  位与运算会对操作数的每一位按照如下表格进行运算,对于每一位只有 0 或 1 两种情况,所以组合出来总共 2 2 = 4 2^2 = 4 22=4 种情况。

左操作数右操作数结果
000
010
100
111

  通过这个表,我们得出一些结论:
  1)无论是 0 或 1,只要位与上 1,还是它本身;
  2)无论是 0 或 1,只要位与上 0,就变成 0;

#include <stdio.h>
int main() 
    int a = 0b1010;           // (1)
    int b = 0b0110;           // (2)
    printf("%d\\n", (a & b) ); // (3)
    return 0;

  • ( 1 ) (1) (1) 在C语言中,以0b作为前缀,表示这是一个二进制数。那么a的实际值就是 ( 1010 ) 2 (1010)_2 (1010)2
  • ( 2 ) (2) (2) 同样的,b的实际值就是 ( 0110 ) 2 (0110)_2 (0110)2
  • ( 3 ) (3) (3) 那么这里a & b就是对 ( 1010 ) 2 (1010)_2 (1010)2 ( 0110 ) 2 (0110)_2 (0110)2 的每一位做表格中的&运算。

  所以最后输出结果为:2
  因为输出的是十进制数,它的二进制表示为: ( 0010 ) 2 (0010)_2 (0010)2。注意:这里的 前导零 可有可无,作者写上前导零只是为了对齐以及让读者更加清楚位与的运算方式。

2、位与运算符的简单应用

1)奇偶性判定

  我们判断一个数是奇数还是偶数,往往是通过取模%来判断的,如下:

#include <stdio.h>
int main() 
    if(5 % 2 == 1) 
        printf("5是奇数\\n");
    
    if(6 % 2 == 0) 
        printf("6是偶数\\n");
    
    return 0;
 

  然而,我们也可以这么写:

#include <stdio.h>
int main() 
    if(5 & 1) 
        printf("5是奇数\\n");
    
    if( (6 & 1) == 0 ) 
        printf("6是偶数\\n");
    
    return 0;
 

  这是利用了奇数和偶数分别的二进制数的特性,如下表所示:

-二进制末尾位
奇数1
偶数0

  所以,我们对任何一个数,通过将它和 0b1进行位与,结果为零,则必然这个数的二进制末尾位为0,根据以上表就能得出它是偶数了;否则,就是奇数。

2)取末五位

给定一个数,求它的二进制表示的末五位,以十进制输出即可。

  这个问题的核心就是:我们只需要末五位,剩下的位我们是不需要的,所以可以将给定的数 位与上0b11111,这样一来就直接得到末五位的值了。代码实现如下:

#include <stdio.h>
int main() 
    int x;
    scanf("%d", &x);
    printf("%d\\n", (x & 0b11111) );
    return 0;
 

3)消除末尾五位

给定一个 32 位整数,要求消除它的末五位。

  还是根据位与的性质,消除末五位的含义,有两层:
  1)末五位,要全变成零;
  2)剩下的位不变;
  那么,根据位运算的性质,我们需要数,它的高27位都为1,低五位都为 0,则这个数就是: ( 11111111111111111111111111100000 ) 2 (11111111111111111111111111100000)_2 (11111111111111111111111111100000)2  但是如果要这么写,代码不疯掉,人也会疯掉,所以一般我们把它转成十六进制,每四个二进制位可以转成一个十六进制数,所以得到十六进制数为0xffffffe0。代码实现如下:

#include <stdio.h>
int main() 
    int x;
    scanf("%d", &x);
    printf("%d\\n", (x & 0xffffffe0) );
    return 0;
 

4)2的幂判定

请用一句话,判断一个正数是不是2的幂。

  如果一个数是 2 的幂,它的二进制表示必然为以下形式: 1 00...00 ⏟ k 1\\underbrace00...00_\\rm k 1k 00...00 这个数的十进制值为 2 k 2^k 2k。那么我们将它减一,即 2 k − 1 2^k-1 2k1 的二进制表示如下(参考二进制减法的借位): 0 11...11 ⏟ k 0\\underbrace11...11_\\rm k 0k 11...11于是 这两个数位与的结果为零,于是我们就知道

以上是关于C语言每日一练 —— 第20天:位运算的主要内容,如果未能解决你的问题,请参考以下文章

C语言每日一练——第126天:佩奇借书问题

C语言每日一练——第70天:24点问题

C语言每日一练——第161天:冒泡排序算法

C语言每日一练——第154天:牛顿迭代法求方程根

C语言每日一练——第105天:杨辉三角形

C语言每日一练——第90天:青蛙跳台阶(升级版)