首页 > 编程笔记 > C语言笔记 阅读:13

C语言位运算详解(附带实例)

C 语言既具有高级语言的特点,又具有低级语言的功能,C 语言完全支持按位运算,而且能像汇编语言一样编写系统程序。

本节将介绍 C 语言如何在位一级进行运算。按位运算也就是对字节或字中的实际位进行检测、设置或移位。下表为 C语言提供的 6 类位运算符。

表:位运算符
位运算符 含义 位运算符 含义
& 按位与 ^ 按位异或
| 按位或 << 左移
~ 取反 >> 右移

C语言按位与运算符

按位与运算符“&”是双目运算符,功能是使参与运算的两个数对应的二进位相与,即对应的两个二进位均为 1 时,结果为 1,否则为 0,如下表所示。

表:按位与运算符
a b a&b
0 0 0
0 1 0
1 0 0
1 1 1

例如,89&38 的计算过程如下:


可以发现,按位与运算的一个用途就是清零,即要想原数中为 1 的位置为 0,只需使其与对应位置为 0 的数相与即可。另一个用途是取一个数中的某些特定位。例如,要取 22 的后 5 位,使其与后 5 位均是 1 的数进行相与即可。同样,要取后 4 位,就与后 4 位都是 1 的数相与即可。

【实例】年龄值进行与运算。本实例中,定义两个变量,分别代表两个人的年龄,将这两个变量进行相与运算,具体代码如下:
#include<stdio.h>
void main()
{
    unsigned result; /* 定义无符号变量 */
    int age1, age2; /* 定义变量 */
    printf("请输入第一个人年龄 age1:"); /* 提示输入年龄 1 */
    scanf("%d", &age1); /* 输入年龄 1 */
    printf("请输入第二个人年龄 age2:"); /* 提示输入年龄 2 */
    scanf("%d", &age2); /* 输入年龄 2 */
    printf("age1=%d, age2=%d", age1, age2); /* 显示年龄 */
    result = age1 & age2; /* 计算相与运算的结果 */
    printf("\nage1&age2=%u\n", result); /* 输出计算结果 */
}
程序运行结果及两个年龄的相与运算过程如下图所示:


图 2 年龄相与运算

初学者很容易将“&”与“&&”运算符混淆,下面总结一下它们的区别:

C语言按位或运算符

按位或运算符“|”是双目运算符,功能是使参与运算的两个数对应的二进位相或,即只要对应的两个二进位有一个为 1,结果就为 1,如下表所示。

表:“或”运算符
a b a|b
0 0 0
0 1 1
1 0 1
1 1 1

例如,17|31 的计算过程如下:


可以发现,十进制数 17 对应的二进制数的后 5 位是 10001,十进制数 31 对应的二进制数的后 5 位是 11111,将这两个数执行按位或运算之后,得到的结果是 31,也就是 17 对应二进制数的后 5 位中 0 变成了 1。

因此,可以总结出这样一个规律:要想使一个数的后 6 位全为 1,只需和 63 进行按位或运算即可;同理,要想使一个数的后 5 位全为 1,只需和 31 进行按位运算即可。

【实例】将十六进制数 0xEFCA 与本身进行按位或运算,代码如下:
#include<stdio.h>
int main()
{
    int a=0xEFCA, result; /* 定义变量 */
    result = a|a; /* 计算或运算的结果 */
    printf("a|a=%X\n", result); /* 输出结果 */
    return 0; /* 程序结束 */
}
程序运行结果和按位或计算过程如下图所示。为了方便观察,这里只给出每个数据的后 16 位。


图 4 按位或运算

初学者也很容易将“|”与“||”运算符混淆,下面总结一下它们的区别:

C语言按位取反运算符

取反运算符“~”为单目运算符,具有右结合性。其功能是对参与运算的数的各二进位按位求反,即将 0 变成 1,1 变成 0。

例如,~86 是对 86 进行按位求反,计算过程如下:


【实例】输入一个数并赋给变量 a,计算 ~a 的值,最后以八进制形式输出。代码如下:
#include<stdio.h>
main()
{
    unsigned result; /* 定义无符号变量 */
    int a;
    printf("please input a:"); /* 提示输入a */
    scanf("%d", &a); /* 输入a */
    printf("a=%d", a); /* 显示a的值 */
    result = ~a; /* 求a的反 */
    printf("\n~a=%o\n", result); /* 输出a的反的值 */
}
程序运行结果和取反计算过程如下图所示:


图 6 按位取反运算

注意,这里最后是以八进制的形式输出的。

C语言按位异或运算符

按位异或运算符“^”是双目运算符。其功能是对参与运算的两个数对应的二进位相异或,即当对应的两个二进位数相异时结果为 1,否则结果为 0,如下表所示。

表:“异或”运算符
a b a^b
0 0 0
0 1 1
1 0 1
1 1 0

例如,107^127 的计算过程为:


从上面算式可以看出,异或操作的一个主要用途就是能使特定的位翻转。例如,要想将 107 的后 7 位翻转,只需与一个后 7 位都是 1 的数进行异或操作即可。

异或操作的另一个主要用途,就是在不使用临时变量的情况下实现两个变量值的互换。

例如,x=9,y=4,将 x 和 y 的值互换可用如下方法实现:
x=x^y;
y=y^x;
x=x^y;
其具体运算过程如下:


【实例】求两个数异或值。要求输入两个数,分别赋予变量 a 和 b,计算 a^b 的值。代码如下:
#include<stdio.h>
main()
{
    unsigned result; /* 定义无符号变量 */
    int a, b;
    printf("please input a:"); /* 提示输入a */
    scanf("%d", &a); /* 输入a */
    printf("please input b:"); /* 提示输入b */
    scanf("%d", &b); /* 输入b */
    printf("a=%d,b=%d", a, b); /* 显示a和b的值 */
    result = a ^ b; /* 求a与b按位异或的结果 */
    printf("\n a^b=%u\n", result); /* 输出a与b按位异或的结果 */
}
程序运行结果和计算过程如下图所示:


图 9 按位异或运算

按位异或运算经常被用到一些简单的加密算法中。

C语言左移运算符

左移运算符“<<”是双目运算符,其功能是把“<<”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补 0。

例如,假设 a=39,那么 a 在内存中的存储情况如下图所示:


图 10 39在内存中的存储情况

a<<2 表示把 a 的各二进位向左移动两位,此时内存中的存储情况如下图所示。可见,a 左移两位后由原来的 39 变成了 156:


图 11 39左移两位后

仔细观察可发现,将 a 左移一位相当于 a 乘以 2,将 a 左移两位相当于 a 乘以 4,但这种简捷计算只限于移出位不包含 1 的情况。若是将十进制数 64 左移两位(假设 64 以一个字节即 8 位存储),则移位后的结果将为 0(01000000→00000000),这是因为 64 在左移两位时将 1 移出了。

【实例】将 15 先左移两位,将其左移后的结果输出,在这个结果的基础上再左移 3 位,并将结果输出,代码如下:
#include<stdio.h>
main()
{
    int x=15;
    x=x<<2;        /* x 左移 2 位 */
    printf("左移 2 位的结果:%d\n",x);
    x=x<<3;        /* x 左移 3 位 */
    printf("再左移 3 位的结果:%d\n",x);
}
程序运行结果如下图所示:

左移 2 位的结果:60
再左移 3 位的结果:480

来分析下程序中的移位过程。首先,15 在内存中的存储情况如下图所示:


图 12 15在内存中的存储情况

15 左移两位后变为 60,其存储情况如下图所示:


图 13 15 左移两位后变为 60

60 左移 3 位变成 480,其存储情况如下图所示:


图 14 60左移3位后变为480

C++右移运算符

右移运算符“>>”是双目运算符,其功能是把“>>”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。

例如,a>>2 即把 a 的各二进位向右移动两位,假设 a=00000110,右移两位后为 00000001,a 由原来的 6 变成了 1。

对于有符号数,右移时需要注意符号位的问题。当为正数时,最高位一般补 0;当为负数时,最高位是补 0 还是补 1,取决于编译系统的规定。移入 0 的称为“逻辑右移”,移入 1 的称为“算术右移”。

【实例】将 30 和 −30 分别右移 3 位,将所得结果输出,在所得结果的基础上再分别右移两位,并将结果输出。
#include<stdio.h>
int main()
{
    int x=30, y=-30;
    x=x>>3;        /* x 右移 3 位 */
    y=y>>3;        /* y 右移 3 位 */
    printf("x 右移 3 位,y 右移 3 位的结果:%d,%d\n", x, y);
    x=x>>2;        /* x 右移 2 位 */
    y=y>>2;        /* y 右移 2 位 */
    printf("x 再右移两位,y 再右移两位:%d,%d\n", x, y);
    return 0;
}
程序运行结果为:

x 右移 3 位,y 右移 3 位的结果:3,-4
x 再右移两位,y 再右移两位:0,-1

一起分析下程序中的移位过程。首先,30 在内存中的存储情况如下图所示:


图 15 30在内存中的存储情况

−30 在内存中的存储情况如下图所示:


图 16 −30在内存中的存储情况

30 右移 3 位变成 3,其存储情况如下图所示:


图 17 30右移3位变为3

−30 右移 3 位变成 −4,其存储情况如下图所示:


图 18 −30右移3位变为−4

3 右移两位变成 0,而 −4 右移两位则变成 −1,如下图所示:


图 19 −4右移两位变为−1

从上面的过程中可以发现,在 Visual C++ 6.0 中进行的负数右移,实质上就是算术右移。

相关文章