C语言位运算详解(附带实例)
C 语言既具有高级语言的特点,又具有低级语言的功能,C 语言完全支持按位运算,而且能像汇编语言一样编写系统程序。
本节将介绍 C 语言如何在位一级进行运算。按位运算也就是对字节或字中的实际位进行检测、设置或移位。下表为 C语言提供的 6 类位运算符。
例如,89&38 的计算过程如下:
可以发现,按位与运算的一个用途就是清零,即要想原数中为 1 的位置为 0,只需使其与对应位置为 0 的数相与即可。另一个用途是取一个数中的某些特定位。例如,要取 22 的后 5 位,使其与后 5 位均是 1 的数进行相与即可。同样,要取后 4 位,就与后 4 位都是 1 的数相与即可。
【实例】年龄值进行与运算。本实例中,定义两个变量,分别代表两个人的年龄,将这两个变量进行相与运算,具体代码如下:

图 2 年龄相与运算
初学者很容易将“&”与“&&”运算符混淆,下面总结一下它们的区别:
例如,17|31 的计算过程如下:
可以发现,十进制数 17 对应的二进制数的后 5 位是 10001,十进制数 31 对应的二进制数的后 5 位是 11111,将这两个数执行按位或运算之后,得到的结果是 31,也就是 17 对应二进制数的后 5 位中 0 变成了 1。
因此,可以总结出这样一个规律:要想使一个数的后 6 位全为 1,只需和 63 进行按位或运算即可;同理,要想使一个数的后 5 位全为 1,只需和 31 进行按位运算即可。
【实例】将十六进制数 0xEFCA 与本身进行按位或运算,代码如下:

图 4 按位或运算
初学者也很容易将“|”与“||”运算符混淆,下面总结一下它们的区别:
例如,~86 是对 86 进行按位求反,计算过程如下:
【实例】输入一个数并赋给变量 a,计算 ~a 的值,最后以八进制形式输出。代码如下:

图 6 按位取反运算
例如,107^127 的计算过程为:
从上面算式可以看出,异或操作的一个主要用途就是能使特定的位翻转。例如,要想将 107 的后 7 位翻转,只需与一个后 7 位都是 1 的数进行异或操作即可。
异或操作的另一个主要用途,就是在不使用临时变量的情况下实现两个变量值的互换。
例如,x=9,y=4,将 x 和 y 的值互换可用如下方法实现:
【实例】求两个数异或值。要求输入两个数,分别赋予变量 a 和 b,计算 a^b 的值。代码如下:

图 9 按位异或运算
按位异或运算经常被用到一些简单的加密算法中。
例如,假设 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 位,并将结果输出,代码如下:

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

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

图 14 60左移3位后变为480
例如,a>>2 即把 a 的各二进位向右移动两位,假设 a=00000110,右移两位后为 00000001,a 由原来的 6 变成了 1。
对于有符号数,右移时需要注意符号位的问题。当为正数时,最高位一般补 0;当为负数时,最高位是补 0 还是补 1,取决于编译系统的规定。移入 0 的称为“逻辑右移”,移入 1 的称为“算术右移”。
【实例】将 30 和 −30 分别右移 3 位,将所得结果输出,在所得结果的基础上再分别右移两位,并将结果输出。

图 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 中进行的负数右移,实质上就是算术右移。
本节将介绍 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 年龄相与运算
初学者很容易将“&”与“&&”运算符混淆,下面总结一下它们的区别:
- “&&”是逻辑与运算符,相当于生活中的“与”,如我与你。其含义为:两个条件同时成立,逻辑与运算的结果为真,否则为假;
- “&”是位运算符,且为双目运算符,当两个位都为 1 时,结果才为 1,否则为 0。
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 按位或运算
初学者也很容易将“|”与“||”运算符混淆,下面总结一下它们的区别:
- “||”是逻辑运算符,相当于生活中的“或者”,如我或者你。其含义为:两个条件中有一个成立,逻辑或运算的结果就是“真”;
- “|”是位运算符,且为双目运算符,当两个位都为 0 时,结果才为 0,否则为 1。
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

图 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

图 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 中进行的负数右移,实质上就是算术右移。