c语言位运算符

目录

1.位移运算符介绍

2.原码、反码、补码

3.运算符讲解

<< 左移动运算符

>> 右移运算符

& 按位与

| 按位或

^ 按位异或

~ 取反

4.位运算宏举例

获取低16位

获取某一位

2的n次幂

对位截取

参数说明

宏的工作原理

示例


1.位移运算符介绍

位运算符用来对二进制位进行操作,C语言中提供了如上表所示的位运算符。

位运算符中,除 ~ (取反)以外,其余均为双目运算符。

  • & 按位与
  • | 按位或
  • ^ 按位异或
  • ~取反
  • >>右移
  • <<左移

整数在内存中存储的形式是补码的二进制

位运算符是对补码进行运算

2.原码、反码、补码

整数的二进制表示:有3种(原码、反码、补码)

原码:直接根据数值写出的二进制序列就是原码(32位)

反码:原码的符号位不变,其他位按位取反就是反码

补码:反码加1,就是补码

对于正整数的原码、反码、补码都相同;负数是存放在二进制的补码中,负整数的原码、反码、补码都不相同。

 例如:1(正整数的原码、反码、补码都相同)(最高位是符号位,0 表示正数)

原码:0000000 00000000 00000000 00000001
 
反码:0000000 00000000 00000000 00000001
 
补码:0000000 00000000 00000000 00000001

例如:-1(负整数的原码、反码、补码都不相同)(最高位为是符号位,1表示负数)

原码:10000000 00000000 00000000 00000001
 
反码:11111111 11111111 11111111 11111110(按位取反,符号位不变)
 
补码:11111111 11111111 11111111 11111111(反码加1)

3.运算符讲解

<< 左移动运算符

规则:左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补0。

简单说就是:左边丢弃,右边补0

举例说明

#include <stdio.h>

int main()
{
	int a = -5;
	int b = a << 1;
 
	printf("%d\n", a); // -5
	printf("%d\n", b); // -10
 
	return 0;
}

过程说明

先写出 -5 的补码

原码:10000000 00000000 00000000 00000101 (最高位为1)
 
反码:11111111 11111111 11111111 11111010 (按位取反,符号位不变)
 
补码:11111111 11111111 11111111 11111011 (反码加1)

补码向左移动一位,左边去掉,右边补0,得到

11111111 11111111 11111111 11110110 

反推原码进行打印(%d为打印形式,因此对补码按int解读,首位为符号位)

补码:11111111 11111111 11111111 11110110
 
反码:11111111 11111111 11111111 11110101(补码 -1 得到反码)
 
原码:10000000 00000000 00000000 00001010(按位取反得到原码)

此时得到的原码打印就是-10了

左移操作符通常用于对数值进行快速的乘以2的幂次方的操作,因为每向左移动一位就相当于乘以2。

也常用于在底层编程中操作位字段。

int a = 5; // 二进制表示为 0000 0101  
int n = 2;  
int result = a << n; // 结果是 20,二进制表示为 0001 0100

int a = 7; // 二进制表示为 0000 0111  
int result = a << 3; // 结果是 56,因为 7 * 2^3 = 56  
// 二进制表示:0000 0111 -> 0011 1000

>> 右移运算符

右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0。然后符号位与原来保持一致。

举例说明

int main()
{
	char a = -126;
	char b = a >> 1;
 
	printf("%d\n", a); // -126
	printf("%d\n", b); // -63
 
	return 0;
}

-126的补码

原码:1111 1110

反码:1000 0001

补码:1000 0010

右移1位

补码:1000 0010

右移一位后的补码:0100 0001

符号位与原来保持一致:1100 0001
 
反码:1100 0000

原码:1011 1111

1011 1111就是-63

这个操作通常用于对整数进行快速的除以2的幂次方的操作,或者用于在处理位字段时调整位的位置。

#include <stdio.h>

int main()
{
    // 7 >> 1 就是 7 / 2
    printf("%d\n", 7 >> 1); // 3
 
	return 0;
}

& 按位与

规则:两个二进制操作数对应位同为 1 ,结果位才为 1 ,其余情况为 0 。

示例代码

#include <stdio.h>

int main()
{
    unsigned char a = 252;
    unsigned char b = 143;
    char c = a & b;
    printf("%d\n", c); // -116
 
	return 0;
}

解释

a的补码: 1111 1100  
b的补码: 1000 1111  
按&运算------------  
c的补码: 1000 1100

注意c类型为char,是有符号的,首位看作符号,获取原码

c的补码: 1000 1100

c的反码:1000 1011

c的原码:1111 0100

111 0100对应116.所以打印-116

| 按位或

规则:两个二进制操作数对应位只要有一个为 1 ,结果 位 就为 1 ,其余情况为 0 

示例代码

#include <stdio.h>

int main()
{
	int a = 5;
	int b = -2;
	int c = a | b;
	printf("%d\n", c);  // -1
 
	return 0;
}
      5 的补码:00000000 00000000 00000000 00000101
     -2 的补码:11111111 11111111 11111111 11111110
 5 | -2 的补码:11111111 11111111 11111111 11111111

补码,需要原码才能打印

5 | -2 的补码:11111111 11111111 11111111 11111111
5 | -2 的反码:11111111 11111111 11111111 11111110
5 | -2 的原码:10000000 00000000 00000000 00000001

^ 按位异或

规则:两个二进制操作数对应 位 相同为 0 ,不同为 1

示例

#include <stdio.h>

int main()
{
	int a = 5;
	int b = -2;
	int c = a ^ b;
	printf("%d\n", c);  // -5
 
	return 0;
}

过程

​      5 的补码:00000000 00000000 00000000 00000101
     -2 的补码:11111111 11111111 11111111 11111110
 5 ^ -2 的补码:11111111 11111111 11111111 11111011

补码,需要原码才能打印

5 ^ -2 的补码:11111111 11111111 11111111 11111011
5 ^ -2 的反码:11111111 11111111 11111111 11111010
5 ^ -2 的原码:10000000 00000000 00000000 00000101

~ 取反

规则:一个二进制操作数,对应位为 0 ,结果位为 1 ;对应位为 1 ,结果位为 0
作用是将每位二进制取反

示例

#include <stdio.h>

int main()
{
	int a = 5;
	int c = ~a;
	printf("%d\n", c); // -6
 
	return 0;
}

过程

​ 5 的补码:00000000 00000000 00000000 00000101
     取反:11111111 11111111 11111111 11111010

     反码:11111111 11111111 11111111 11111001
     原码:10000000 00000000 00000000 00000110

4.位运算宏举例

获取低16位

// 定义一个宏,提取低16位赋值给uint16_t  
#define EXTRACT_LOW_16_BITS(value) ((uint16_t)((value) & 0xFFFF)) 
 

uint32_t idx = 0x12345678; // 假设这是你的原始32位值
uint16_t index; 

// 使用宏来提取低16位  
index = EXTRACT_LOW_16_BITS(idx); 
// 此时index将包含0x5678
// 你可以安全地将这个值作为uint16_t类型使用

// 定义一个宏,提取低8位赋值给uint8_t  

#define EXTRACT_LOW_8_BITS(value) ((uint8_t)((value) & 0xFF))

获取某一位

// 获取第bitPos位的bit值,0,1,2....等

#define GET_BIT(num, bitPos) ((num >> bitPos) & 0x1)  
// 通过右移操作将目标位移动到最低位
// 然后使用位与操作(&)与1进行运算,以获取最低位的值  

2的n次幂

#define Power_Two(n) ((uint32)(0x1UL << (n)))

作用是计算并返回2的n次幂,其中n是函数的参数,表示幂次

0x1UL << (n) 是一个C或C++语言中的表达式,它结合了字面量、类型转换和左移操作符

  • 0x1:这是一个十六进制字面量,它等于十进制的 1
  • UL:这是两个字符的组合,分别代表 unsigned long 类型的字面量后缀。在这个上下文中,UL 主要用于确保字面量具有足够的位宽来容纳左移操作的结果。
  • <<:这是左移操作符。
  • 0x1UL << (n) 的意思是:将无符号长整型字面量 1(在二进制中表示为 0000 0001,这里省略了前导零以简化说明)向左移动 n 位。
  • 在它原来所在的位置和更高位之间填充了 0。
  • 如果 n 是 2,则 0x1UL << (n) 的结果是 4(在二进制中表示为 0000 0100)
     

对位截取

#define GET_BITS_U32(x, hi, lo) (((x) >> (lo)) & ((1U << ((hi) - (lo) + 1)) - 1))

这是一个宏定义,用于从一个无符号32位整数(x)中提取从低位(lo)到高位(hi,包括)之间的位。这个宏利用了位操作和位掩码的概念来实现其功能。下面是对这个宏的详细解释:

参数说明

  • x:待操作的无符号32位整数。
  • hi:需要提取的最高位的索引(从0开始计数)。
  • lo:需要提取的最低位的索引(从0开始计数)。

宏的工作原理

  1. 右移操作((x) >> (lo)):这部分将x向右移动lo位。这样做的目的是将目标位段(从lohi)移动到最低位(即0到(hi-lo+1))的位置。
  2. 生成掩码((1U << ((hi) - (lo) + 1)) - 1)
    • 1U 是一个无符号整数常量,值为1。1U << ((hi) - (lo) + 1) 将1向左移动(hi - lo + 1)位。这样做是为了创建一个掩码,这个掩码在第(hi - lo + 1)位上为1,其余位都是0。
    • 然后通过-1操作,第(hi - lo + 1)位上为1变为0,第0位到第(hi - lo)位为1
  3. 按位与操作(((x) >> (lo)) & ((1U << ((hi) - (lo) + 1)) - 1)):最后,将上述两步的结果进行按位与操作。得到从lo位到hi位的截取。

示例

假设x = 0b 1101 0010 1011 0011 0011 0011 0011 0011(二进制表示),hi = 10lo = 4

  • 首先,(x >> lo) 得到 0b 0000 1101 0010 1011 0011 0011 0011 0011。
  • 然后,(1U << ((hi) - (lo) + 1)) - 1 生成掩码 0b 0111 1111。
  • 最后,两者按位与得到 0b 011 0011,这就是从x中提取的从第4位到第10位(包括)的值。

end

  • 34
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值