C的位运算符系统梳理

概述
C语言的位运算符十分常用也十分好用。使C区别于其他高级语言的的特性之一是访问整数中的个别位的能力。它十分接近底层硬件,这个特性通常是程序与硬件设备和操作系统相连接的关键!
下面我就系统的梳理下C语言中关于位运算符的使用和功能,希望大家也能从中感受的C的魅力!
*一、基本运算符的使用(~、&、|、^)*
这里主要是按位取反、按位与、按位或、按位异或的使用,它们基本的运算方式简要说一下,从名字也能看出来,他们是对一位进行运算(int是32位即4个字节组成),而每位只有两个可能取值0或1。
~(按位取反):0变1,1变0;
&(按位与):全都是1的时候结果才是1,否则是0;
|(按位或):只要有一个是1结果即为1,否则是0;
^(按位异或):相异为1(就是只有1和0这种搭配结果才是1),相同为0;
其实上面就是逻辑代数的基础概念,十分实用的一些运算。下面的代码展示了这些运算符对int的位操作,相信结合注释,并没有什么障碍!

/*
*C语言中通过位操作符,对二进制各位操作
*/
#include <stdio.h>
#include <stdlib.h>

int main()
{
    //基本位操作符使用
    /*
    *char在内存中占1 byte(即8位)所以下面的计算过程为:
    *按位取反,即0变成1,1变成0
    *~(10011010)                           //表达式 154
    * (01100101)                            //结果值 101
    */
    unsigned char val = 154;
    unsigned char anti_val = ~(val);       //val的值并不改变
    printf("%d", anti_val);                 //十进制输出
    printf("\n");

    /*
    *按位与,对于每个位中只有两个数都为1结果才是1
    *(10010011) & (00111101)                //表达式 147 & 61
    *(00010001)                             //结果值 17
    */
    unsigned char val_2 = 147, val_3 = 61;
    unsigned char and_val = val_2 & val_3;
    printf("%d", and_val);
    printf("\n");

    /*
    *按位或,对于每个位有一个为1即是1
    *(10010011) | (00111101)                //表达式
    *(10111111)                             //结果值 191
    */
    unsigned char val_4 = 147, val_5 = 61;
    unsigned char or_val = val_4 | val_5;
    printf("%d", or_val);
    printf("\n");

    /*
    *位异或,对于每个位,相同为0,相异为1
    *(10010011) ^ (00111101)                //表达式
    *(10101110)                             //结果值 174
    */
    unsigned char val_6 = 147, val_7 = 61;
    unsigned char exc_val = val_6 ^ val_7;
    printf("%d", exc_val);
    printf("\n");

    /*
    * & | ^ 存在操作符-赋值表示(类似于+= -= ...)
    *如 &= |= ^=,不存在~=
    */

    system("pause");
    return 0;
}

二、位运算符的基本功能总结
通过上述几个运算符可以实现一些有趣的功能,这里涉及到几个概念,首先应该阐明一下。
掩码:就是一个一个“遮掩膜”的功能。一串数字,通过与掩码做&运算可以把不想要的位“隐藏掉”,想要的位“保留”下来。下面看剖析图:
这里写图片描述
其根本上还是利用了&的运算性质!
打开位:这是比较形象的说法,可以这样理解,把每位重点的两个状态看成开关,像可以让1表示开状态,0表示关状态(反过来的话属于‘逆逻辑’)。这里就是把指定的位打开(置1)。
关闭位:这当然就时把指定的位置0.
转置位:这个从名字也可以看出来,就是原来是0现在就变1,原来是1现在就变0.也许你立刻想到了前面的一个运算符,没错!就是按位取反(~),不过你只才对了一般,他的功能的确类似,但是~是对整个数据进行操作,如果想把指定的一位转置呢?看代码吧!
查看一位的值就是看看指定的位,是0还是1.这个功能挺实用的,想象要把一个十进制转换成二进制的表示形式,他是不是就派上用场了!
结合上面的简明阐述,下面的代码应该没有障碍:

/*
*各个位操作符的基本用法及功能
*如,掩码、打开位、关闭位、置换位等
*/
#include <stdio.h>
#include <stdlib.h>

#define MASK 2

int main()
{
    /*
    *掩码示例
    *表达式为 10010110 & MASK(00000010)
    *结果值为 00000010(即为十进制2)
    */
    unsigned char val = 150;
    unsigned char after_mask = 0;
    after_mask = val & MASK;
    printf("%d", after_mask);
    printf("\n");

    /*
    *掩码的常用方式
    *使用0xFF掩码只留下被掩盖值得最后8位,将其余设为0
    *无论最初的value是8位16位或者更多,最终的值都修正到一个字节中
    */
    int val_2 = 514;                    //514的二进制形式为0…,00000001,00000010
    unsigned char mask = 0xFF;          //0xFF是二进制11111111的十六进制表示形式
    int after_mask_2 = val_2 & mask;    //掩码使用
    printf("%d", after_mask_2);         //输出结果是2(即是0…0,00000010)
    printf("\n");

    /*
    *‘打开位’示例
    *表达式为 10010100 | open_bit(00000010)
    *结果值为 10010110
    */
    int val_3 = 148;
    unsigned char open_bit = 2;
    int after_open_bit = val_3 | open_bit;  //将右数第二位打开,相当于原数加2
    printf("%d", after_open_bit);           //输出结果为150 = 148 + 2
    printf("\n");

    /*
    *‘关闭位’示例
    *表达式为 10010110 & close_bit(11111101)
    *结果值为 10010100
    */
    int val_4 = 150;
    unsigned char close_bit = ~open_bit;   //open_bit按位取反即可达到目的
    int after_close_bit = val_4 & close_bit;//将右数第二位关闭,相当于原数减2
    printf("%d", after_close_bit);          //输出结果为148 = 150 - 2
    printf("\n");

    /*
    *‘转置位’示例
    *表达式为 10010110 ^ tran_bit(00000010)
    *结果值 10010100
    */
    int val_5 = 150;
    unsigned char tran_bit = 2;
    int after_tran_bit = val_5 ^ tran_bit;//将右数第二位转置(0变1,1变0),这里相当于原数减2
    printf("%d", after_tran_bit);         //输出结果为148 = 150 - 2
    printf("\n");

    /*
    *‘查看一位的值’示例 
    *实现查看val_6右数第二位的值的功能
    */
    mask = 2;
    unsigned char val_6 = 150;              //val_6的二进制表示为10010110
    unsigned char each_bit = val_6 & mask;  //屏蔽val_6的其他位
    if (each_bit == mask) {                 //查看并输出结果
        printf("val_6右数第二位是1");     
    }
    else {
        printf("val_6右数第二位非1");
    }
    printf("\n");

    system("pause");
    return 0;
}

三、强大的左移<<与右移>>运算符
这两个运算符真的很神奇!他们竟然可以到一个整数的内部来具体选中某一个位。这也是C最迷人的地方,是如此的接近底层。但是有一个应该注意的地方:<<向左移动的位数由其右操作数指定,空出的位用0填充,并且丢弃移出左侧操作数末端的位;>>对于unsigned数与<<的处理过程完全一致,但是在针对signed数填充的值却依赖于具体的机器!这表面运用这个操作符的代码很难移植!应该牢记这一点!
下面结合代码的注释看看他们对数据的基本操作方式:

/*
*通过左移<<与右移>>运算符实现对数据的位操作
*/
#include <stdio.h>
#include <stdlib.h>

int main()
{
    //左移运算符使用举例
    unsigned char val_1 = 138;          //其二进制表示形式为 10001010
    unsigned char after_left_move;
    after_left_move = val_1 << 2;       //左移两位,丢弃末端为,空位用0填充
    printf("%d", after_left_move);      //输出结果是40,二进制表达形式为 00101000
    printf("\n");

    //使用运算符-赋值格式,会改变val_1的值s
    val_1 <<= 2;
    printf("%d", val_1);
    printf("\n");

    //右移运算符的使用举例
    unsigned char val_3 = 138;          //无符号数,二进制表达形式 10001010
    unsigned char after_right_move;
    after_right_move = val_3 >> 2;      //右移两位,丢弃末端位,填充值0
    printf("%d", after_right_move);     //输出为34,二进制表达形式为 00100010
    printf("\n");

    signed char val_4 = 138;            //...
    val_4 >>= 2;
    printf("%d", val_4);                //末端位丢弃,填充值依赖于操作系统
    printf("\n");                       //我的机器上此处输出结果是-30,也就是补1

    system("pause");
    return 0;
}

四、<<与>>运算符的基本功能
主要介绍3个使用的功能:
(1)提供快捷、高效的2次幂的乘法和除法。具体规则看下表:(注意非负)
这里写图片描述
(2)从较大单位中提取多组比特位(如:RGB颜色编码中不同颜色分量的提取)
(3)实现十进制转换成二进制表示
下面的代码中,每个功能通过函数实现出来,并不难懂:

/*
*移位运算符的功能介绍,主要包括下面3部分
*(1)对2的幂快速乘除法
*(2)在较大单位中提取多组bit位
*(3)实现十进制转换成二进制表示
*/
#include <stdio.h>
#include <stdlib.h>
#define MASK 0xFF                                   //仅保留8位的掩码
#define MASK_LAST 1                                 //仅保留末位的掩码

//函数声明
int MultiByBitMove(int power, int value);           //使用移位运算实现快速乘法
int DivisByBitMove(int power,unsigned int value);   //使用移位运算实现快速除法
int PickUpBit(int value, int cnt);                  //从数据value中提取cnt开始的8位
void TransToBinary(int value, int *str);            //十进制转二进制表示
void DisplayByBinary(int *str);                     //输出二进制字符串

int main()
{
    int val_1 = 100, val_2 = 512, val_3 = 512;
    int binary[sizeof(int) * 8];
    printf("%d\n", MultiByBitMove(2, val_1));       //输出400 = 100 * 2^2
    printf("%d\n", DivisByBitMove(2, val_1));       //输出25 = 100 、 2^2

    printf("%d", PickUpBit(val_2, 9));              //从右树第8位开始向左提取8位
    printf("\n");                                   //512装换成二进制后,对应输出结果是1,也就是…00000001

    TransToBinary(val_3, binary);
    DisplayByBinary(binary);
    printf("\n");

    system("pause");
    return 0;
}

//函数定义
int MultiByBitMove(int power, int value) {
    //左移位数就是数据乘的2的幂数
    return (value <<= power);
}

int DivisByBitMove(int power, unsigned int value) {
    //右移位数就是数据除的2的幂数
    return (value >>= power);
}

int PickUpBit(int value, int cnt) {
    //通过掩码及移位运算符提取数据中的bits
    return (value >> cnt) & MASK;
}

void TransToBinary(int value, int *str) {
    //通过掩码及右移运算符将十进制保存为二进制字符串
    for (int i = 0; i < sizeof(int) * 8; i ++) {
        str[i] = (value >> i) & MASK_LAST;
    }
}

void DisplayByBinary(int *str) {
    //每8 bits分割输出二进制字符串
    for (int i = sizeof(int) * 8 - 1; i >= 0; i--) {
        if (0 == (i + 1) % 8 && (i + 1) != sizeof(int)* 8) {
            printf(",");
        }
        printf("%d", str[i]);
    }
}

对位操作的核心概念大概就这些,有时候用位操作可以实现各种功能和算法,并且可以实现对一些硬件的直接控制(如单片机等)。总之非常值得花时间学习和使用。也希望通过文章大家对C的位操作符内容有一个更清晰的认识!

猜你喜欢

转载自blog.csdn.net/weixin_37818081/article/details/78596703
今日推荐