C语言提供了位运算操作符,使程序可以直接对内存进行操作。
位运算是指对数据的二进制位进行处理的运算。
操作系统中的各种类型的数值都由若干字节组成,字节也是数据存取和数值计算的基本单元。
字节由位组成,1字节由8个位组成,每个位的值为0或1。
在操作系统中,数值的存储不是直接以其二进制进行存储的,而是以补码来存储的。
一个数值的二进制值可以称其为原码,存储时会将原码表示为补码。补码的最高位为符号位。
使用补码计算时,可以将符号位和其他位统一处理。在相加时,如果最高位有进位,则进位舍去。
取反操作符的作用是让0变成1,1变成0。
范例1
输出一个整数的补码形式
#include
int main(){
int a = 0;
printf("Please input a number:");
scanf("%d",&a);
if(a < 0)
printf("%d: [%08x]\n",a,-(~a + 1));
else
printf("%d: [%08x]\n",a,a);
return 0;
}
位或操作符将两个操作数逐位进行位或运算。
位或运算:只要有一个数为1,则结果为1。
int a = 39;
a = a | 0x08;
if(~0 == a | ~(0x04))
printf("a的第3位为1");
else
printf("a的第3位为0");
其中 ~0 是取得二进制位全为1的数值; ~(0x04) 表示为除第3位外,其余位都为1,将该值与a进行位或操作,可以将除第3位外的所有位都置为1,因此再与~0进行比较,如果第3位为1,则相等,否则不等。
范例2
打印整数的二进制形式
#include
const int ALL1 = ~0; //得到4字节的全1码
void print_bin(const int a){
int flag = 0x80; //定义标志位,第8位为1
while(flag != 0){
printf("%d",ALL1 == (a | ~flag));//获得a中与flag中非0位对应的位值
flag /= 2; //将flag中的1右移一位
}
}
int main(){
int a = 0;
int b = 0;
printf("Input two integers(less than 256):");
scanf("%d %d",&a,&b);
printf("\t%d\t",a);
print_bin(a); //打印其二进制
printf("\n");
printf("|\t%d\t",b);
print_bin(b);
printf("\n");
printf("------------------------\n");
printf("\t%d\t",a|b);
print_bin(a|b);
printf("\n");
return 0;
}
位与操作符将两个操作数逐位进行位与运算。
位或运算:如果都为1,则结果为1,否则为0。
int a = 39;
a = a & 0;
int a = 39;
int b;
···
b = a & 0x0f;
要获取数的哪几位,就将这个数与上一个这几位都为1的数。
范例3
使用位与操作符输出整数的二进制形式
#include
int main(void){
unsigned a = 0;
int i = 0;
int flag = 0x80;
//使用循环结果,要求输入确定范围的数
do{
printf("Please input a number(0~255):");
scanf("%u",&a);
}while(a > 255);
//按位从高到低输出
while(flag != 0){
//输出flag指定的位
if(0 == (a & flag))
printf("0");
else
printf("1");
flag /= 2;
}
printf("\n");
return 0;
}
范例4
计算输入整数的二进制形式含有数字1的个数
#include
int count_one(const int data){
int tmp = data;
int count = 0;
//计算data中有几个1
while(tmp != 0){
tmp = tmp & (tmp - 1); //消去最后一个1
++count;
}
return count;
}
int main(void){
int data = 0;
printf("Please input a number:");
scanf("%d",&data);
printf("There are %d ones in %d.\n",count_one(data),data);
return 0;
}
01110000 & 01101111 -> 01100000
00110100 & 00110011 -> 00110000
一直通过减1来计数,直到减到没有1了为止。
如果两个数一样,则值为0;如果不一样,值为1。
int a = 39;
a = a ^ 0x7f;
范例5
使用异或操作符实现两个数的交换
#include
int main(){
int a = 0;
int b = 0;
printf("Please input a number:\n");
printf("a = ");
scanf("%d",&a);
printf("b = ");
scanf("%d",&b);
//交换两个数
a = a ^ b;
b = b ^ a;
a = a ^ b;
printf("After changing:\n");
printf("a = %d, b = %d\n",a,b);
return 0;
}
右移操作符是将一个数的各个二进制值全部右移若干位。
int a = 39;
a = a >> 2;
该表达式将a的二进制值右移2位,移到右端的低位被舍弃,高位补入的数值由符号位决定。如果无符号数,高位补0。
如果高位补入0,为“逻辑右移”;如果补入原符号位,为“算数右移”。
例如对于00011101:
逻辑右移:00000111
算数右移:01000111
范例6
检测右移方式
#include
int main(){
int a = -2;
int b = a >> 1;
if(b < 0)
printf("Arithmettical right shift.\n");//算术右移
else
printf("Logical right shift.\n");//逻辑右移
return 0;
}
左移操作符是将一个数的各个二进制值全部左移若干位。移到左端的高位被舍弃,而低位补入的数值为0。
范例7
将4个单字节无符号整数转换为一个unsigned
#include
typedef unsigned int uint32_t; //声明类型的别名
typedef unsigned char uint8_t; //声明类型的别名
#define INT_SIZE 4
//将4个1字节整数拼为1个4字节整数
uint32_t uint8_to_uint32(uint8_t *p){
return (uint32_t)(p[0] | //获取1~4位
(p[1] << 8) | //获取5~8位
(p[2] << 16) | //获取9~12位
(p[3] << 24)); //获取13~16位
}
//输出整数的二进制形式
void print_bin(const uint32_t a,const uint32_t size){
//把1左移7位,正好移到第8位
uint32_t flag = 1 << (size * 8 - 1);//将flag置为第size*8位为1的数
while(flag != 0){
printf("%d",0 != (a&flag));//取出a中与flag中非0位对应的位
flag = flag >> 1;
}
}
int main(){
uint8_t str[INT_SIZE] = {
0x20,0x30,0x40,0x22};
uint32_t data =0;
int i = 0;
for(i = INT_SIZE -1;i>=0;--i){
printf("---------------\n");
print_bin(str[i],1);//输出第i个元素的二进制形式
printf("\ni = %d",i);
printf("\n0x%x\n",str[i]);
}
data = uint8_to_uint32(str);//将4个1字节整数拼为1个4字节整数
printf("\n%d:\n",data);
print_bin(data,8);
printf("\n");
return 0;
}
循环移位与一般移位不同的是,移位时没有数位的丢失。
例如,要对a(无符号数)循环右移三位,可以采用以下方案:
(1)把将要移出右端的三个位左移(8-3位)(假定a占8个位)后保存在b中。
(2)将a右移3位,右端3位被舍弃,又由于a为无符号数,左端添加3个0。
(3)将a与b位或,由于0和任意值位或不改变该值,因此,得到yyyxxxxx。
范例8
循环向右移位
#include
//输出二进制形式
void print_bin(const int a,const int size){
unsigned flag = 1 << (size * 8 - 1);
while(flag != 0){
printf("%d",0 != (a & flag));
flag >>= 1;
}
}
//实现循环右移
unsigned cycclic_right_shift(unsigned a,int n){
unsigned b = 0;
int size = sizeof(unsigned)*8;
n %= size; //得到需要右移的最小位数n = n % size
printf("-----------------------\n");
b = a << (size - n); //暂存右边的部分
printf("b=");
print_bin(b,sizeof(unsigned));
printf("\n");
a = a >> n; //将左边部分右移
printf("a=");
print_bin(a,sizeof(unsigned));
printf("\n");
return a | b; //合并两部分
}
int main(void){
int size = 0;
int a = 0;
int n = 0;
printf("a = ");
scanf("%d",&a);
printf("n = ");
scanf("%d",&n);
printf("Shift before.\n");
print_bin(a,sizeof(unsigned));
printf("\n");
a = cycclic_right_shift(a,n);
printf("-----------------------\n");
printf("Shift after.\n");
print_bin(a,sizeof(unsigned));
printf("\n");
return 0;
}
子网掩码是一个32位地址,用于屏蔽IP地址的一部分以区别网络标识和主机标识。
如果一个IP地址与子网掩码相与的结果与一个子网地址相同,那么,这个IP地址便属于该子网。
范例9
该例子是子网掩码的使用
#include
const unsigned NET_MASK = 0xffffff00;
const unsigned SUBNET_A = 0x03b40c800; //59.64.200.0
const unsigned SUBNET_B = 0x03b40c900; //59.64.201.0
int main(void){
unsigned ip_1 = 0x03b40c812; //59.64.200.18
unsigned ip_2 = 0x03b40c914; //59.64.201.20
if((ip_1 & NET_MASK) == SUBNET_A)
printf("ip_1 belong to SUBNET_A.\n");
else if((ip_1 & NET_MASK) == SUBNET_B)
printf("ip_1 belong to SUBNET_B.\n");
else
printf("ip_1 belong to other.\n");
if((ip_2 & NET_MASK) == SUBNET_A)
printf("ip_2 belong to SUBNET_A.\n");
else if((ip_2 & NET_MASK) == SUBNET_B)
printf("ip_2 belong to SUBNET_B.\n");
else
printf("ip_2 belong to other.\n");
return 0;
}
void change(char *str){
for(int i = 0,j = strlen(str)-1;i<j;i++,j--){
str[i] ^= str[j] ^= str[i] ^= str[j];
}
}
练习2
将16进制表示的IP地址以标准字符串形式输出
#include
int main(void){
unsigned ip = 0x3b40c812;
printf("%8x:\t",ip);
printf("%d.",(ip >> 24) & 0xff); //输出第4字节
printf("%d.",(ip >> 16) & 0xff); //输出第3字节
printf("%d.",(ip >> 8) & 0xff); //输出第2字节
printf("%d.\n",ip & 0xff); //输出第1字节
return 0;
}