C语言-基础入门-学习笔记(12):位运算操作符

C语言-基础入门-学习笔记(12):位运算操作符

C语言提供了位运算操作符,使程序可以直接对内存进行操作。

一、位运算概述

位运算是指对数据的二进制位进行处理的运算。

操作系统中的各种类型的数值都由若干字节组成,字节也是数据存取和数值计算的基本单元。
字节由组成,1字节由8个位组成,每个位的值为0或1。

在操作系统中,数值的存储不是直接以其二进制进行存储的,而是以补码来存储的。
一个数值的二进制值可以称其为原码,存储时会将原码表示为补码。补码的最高位为符号位。

  1. 非负数的补码
    非负数的补码与原码相同。 例如:11的原码为00001011,补码为00001011。
  2. 负数的补码
    负数的补码符号位为1,其余位为将该数绝对值的原码按位取反再加1。 例如:-15的补码:为负时符号位为1,整个为10001111;其余7位为-15的绝对值的原码按位取反,即0001111取反后为1110000,再加1为1110001,再加上符号位为11110001。

使用补码计算时,可以将符号位和其他位统一处理。在相加时,如果最高位有进位,则进位舍去。

二、位运算操作符

1. 取反操作符(~)

取反操作符的作用是让0变成1,1变成0。

范例1
输出一个整数的补码形式

#include <stdio.h>

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;
}

在这里插入图片描述
在这里插入图片描述

2. 位或操作符(|)

位或操作符将两个操作数逐位进行位或运算
位或运算:只要有一个数为1,则结果为1。

  1. 对指定二进制位的赋值
    可以实现对数值指定二进制位赋值为1。 例如要将a的第4位置为1:
int a = 39;
a = a | 0x08;
  1. 判定指定二进制位的值
    位或操作符与取反操作符结合在一起可以判断指定数位的值。 例如要判断一个整数a其二进制第3位的值:
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 <stdio.h>

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;
}

在这里插入图片描述

3. 位与操作符(&)

位与操作符将两个操作数逐位进行位与运算
位或运算:如果都为1,则结果为1,否则为0。

  1. 将数值清零
    用0与任何数进行位与运算,可以将该数清零。
int a = 39;
a = a & 0;
  1. 取指定位数的二进制数
    使用位与操作可以获得数值上指定位的信息。 例如,要获得一个整数的最低4位:
int a = 39;
int b;
···
b = a & 0x0f;

要获取数的哪几位,就将这个数与上一个这几位都为1的数。

范例3
使用位与操作符输出整数的二进制形式

#include <stdio.h>

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 <stdio.h>

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了为止。

4. 异或操作符(^)

如果两个数一样,则值为0;如果不一样,值为1。

  1. 与0异或,不改变数值
  2. 与1异或,取其相反值
    如果要对a后的7位取反:
    int a = 39;
    a = a ^ 0x7f;
    
  3. 一个数与本身异或得0

范例5
使用异或操作符实现两个数的交换

#include <stdio.h>

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;
}

在这里插入图片描述

5. 右移操作符(>>)

右移操作符是将一个数的各个二进制值全部右移若干位。

int a = 39;
a = a >> 2;

该表达式将a的二进制值右移2位,移到右端的低位被舍弃,高位补入的数值由符号位决定。如果无符号数,高位补0。
如果高位补入0,为“逻辑右移”;如果补入原符号位,为“算数右移”。

例如对于00011101:
逻辑右移:00000111
算数右移:01000111

范例6
检测右移方式

#include <stdio.h>

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;
}

在这里插入图片描述

6. 左移操作符(<<)

左移操作符是将一个数的各个二进制值全部左移若干位。移到左端的高位被舍弃,而低位补入的数值为0。

  • 若要将一个数除以2、4、8等2的次方时,可以用右移操作符将其右移1、2、3即可。
  • 若要将一个数乘以2、4、8等2的次方时,可以用左移操作符将其左移1、2、3即可。
  • 左乘右除

范例7
将4个单字节无符号整数转换为一个unsigned

#include <stdio.h>

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;
}

在这里插入图片描述

二、位运算操作符使用示例

1. 循环移位

循环移位与一般移位不同的是,移位时没有数位的丢失。

例如,要对a(无符号数)循环右移三位,可以采用以下方案:
(1)把将要移出右端的三个位左移(8-3位)(假定a占8个位)后保存在b中。
(2)将a右移3位,右端3位被舍弃,又由于a为无符号数,左端添加3个0。
(3)将a与b位或,由于0和任意值位或不改变该值,因此,得到yyyxxxxx。

范例8
循环向右移位

#include <stdio.h>
//输出二进制形式
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;
}

在这里插入图片描述

2. 使用子网掩码

子网掩码是一个32位地址,用于屏蔽IP地址的一部分以区别网络标识和主机标识。
如果一个IP地址与子网掩码相与的结果与一个子网地址相同,那么,这个IP地址便属于该子网。

范例9
该例子是子网掩码的使用

#include <stdio.h>

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;
}

在这里插入图片描述
练习1
在不开辟用于交换数据的临时空间,完成字符串的逆序

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 <stdio.h>

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;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值