C按位操作符(~、&、|、^)

目录

1、按位逻辑运算符

1.1 二进制反码或按位取反:~

1.2 按位与:&

1.3 按位或:|

1.4 按位异或:^

2、用法

2.1 掩码(&)

2.2 打开位(设置位)(|)

2.3 关闭位(清空位)(& ~)

2.4 切换位(^)

2.5 检查位的值

3、移位运算符

3.1 左移:<<

3.2 右移:>>

4、位字段

5、位字段和按位运算符

6、对齐特性(C11)


C提供按位逻辑运算符和移位运算符。

1、按位逻辑运算符

4个按位逻辑运算符都用于整形数据,包括char。之所以叫作按位运算,是因为这些操作都是针对每一个位进行,不影响它左右两边的位。

1.1 二进制反码或按位取反:~

一元运算符~把1变为0,把0变为1。

~(10011010)    //表达式
(01100101)    //结果值

在二进制中,00000010表示2,那么取反的值为11111101,即253,该运算符不会改变变量的值。

如果要把val的值改为~val,可以用下面的语句:

val = ~val;

1.2 按位与:&

二元运算符&通过逐位比较两个运算对象,生成一个新值。对于每个位,只有两个运算对象中相应的位都为1时,结果才为1(从真/假方面看,只有当两个位都为真时,结果才为真)。

(10010011) & (00111101)    //表达式
(00010001)        //结果值

C有一个按位与和赋值结合的运算符: &=。下面两条语句产生的最终结果相同”

val &= 0337;
val = val & 0337;

1.3 按位或:|

二元运算符|,通过逐位比较两个运算对象,生成一个新值。对于每个位,如果两个运算对象中相应的位为1,结果就为1(从真/假方面看,如果两个运算对象中相应的一个位为真或两个位都为真,那么结果为真)。

(10010011) | (00111101)    //表达式
(10111111)                 //结果值
//效果相同
val |= 0377;
val = val | 0377;

1.4 按位异或:^

二元运算符^逐位比较两个运算对象。如果两个运算对象中相应的位一个为1(只有一个1),结果为1(从真/假方面看,如果两个运算对象中相应的一个位为真且不是两个同为1,那么结果为真)。

(10010011) ^(00111101)    //表达式
(10101110)                //结果值
//效果相同
val ^= 0377;
val = val ^ 0377;

2、用法

2.1 掩码(&)

按位与运算符常用于掩码/所谓掩码指的是一些设置位开(1)或关(0)的位组合。例如,假设定义符号常量MASK为2(即,二进制形式为00000010),只有1号位为1,其他位都是0。下面的语句:

flags = flags & MASK;

把flags中除1号位以外的所有位都设置位0,因为使用按位与运算符(&)任何位与0组合都得0。1号位的值不变(如果1号位是1,那么1&1得1;如果1号位是0,那么0&1也得0)。这个过程叫作“使用掩码”,因为掩码中的0隐藏了flags中相应的位。

2.2 打开位(设置位)(|)

以上一节的flags和MASK为例。下面的语句:

flags = flags | MASK;

把flags的1号位设置为1,且其他位不变。因为使用 | 运算符,任何位与0组合,结果都为本身;任何位与1组合,结果都为1。

2.3 关闭位(清空位)(& ~)

和打开特定的位类似,有时也需要在不影响其他位的情况下关闭指定的位。

flags = flags & ~MASK;

由于MASK除1号位为1以外,其他位全为0,所以~MASK除1号位为0以外,其他位全为1。使用&,任何位与1组合都得本身,所以这条语句保持除1号位以外的其他各位不变。

例如,假设flags是00001111,MASK是10110110.下面的表达式:

flags & ~MASK
//即是:
(00001111) & ~(10110110)    //表达式
//其结果为:
(00001001)                  //结果值

MASK中为1的位在结果中都被设置(清空)为0.flags中与MASK为0 的位相应的位在结果中都未改变。

2.4 切换位(^)

切换位指的是打开已关闭的位,或关闭已打开的位。可以使用按位异或运算符(^)切换位。

假设b是一个位(1或0),如果b为1,则1^b为0;如果b为0,则1^b为1。另外,无论b为1还是0,0^b均为b。 因此,如果使用 ^ 组合一个值和一个掩码,将切换该值与MASK为1的位相对应的位,该值与MASK为0的位相对应的位不变。

flags = flags ^ MASK;

例如,假设flags是00001111,MASK是10110110。

flags ^ MASK
//即是:
(00001111) ^ (10110110)    //表达式
//其结果为:
(10111001)                 //结果值

2.5 检查位的值

例如,检查flags中1号位是否被设置为1?可以这样比较:

if ((flags & MASK) == MASK)
    puts("wow!");

必须先覆盖flags中的其他位,只用1号位和MASK比较。

为了避免信息漏过边界,掩码至少要与其覆盖的值宽度相同。

3、移位运算符

3.1 左移:<<

左移运算符(<<)将其左侧运算对象每一位的值向左移动其右侧运算对象指定的位数。左侧运算对象移出左末端位的值丢失,用0填充空出的位置。

(10001010) << 2        //表达式
(00101000)             //结果值

该操作产生了一个新的位值,但是不改变其运算对象。例如,假设stonk为1,那么stonk<<2为4,但是stonk本身不变,仍为1.可以使用左移赋值运算符(<<=)来更改变量的值。

3.2 右移:>>

右移运算符(>>)将其左侧运算对象每一位的值向右移动其右侧运算对象指定的位数。左侧运算对象移出右末端位的值丢失。对于无符号了类型,用0填充空出的位置;对于有符号类型,其结果取决于机器。

对于无符号的值:

(10001010) >> 2
(00100010)

同左移相同,也可以用(>>=)将其左侧的变量向右移动指定数量的位数。

移位运算符针对2的幂提供快速有效的乘法和除法。

number << n //number乘以2的n次幂
number >> n //如果number为非负,则用number除以2的n次幂

4、位字段

操控位的第2种方法是位字段位字段是一个signed int 或unsigned int 类型变量中的一组相邻的位。位字段通过一个结构声明来建立,该结构声明为每个字段提供标签,并确定该字段的宽度。

struct
{
	unsigned int autfd : 1;
	unsigned int bldfc : 1;
	unsigned int undln : 1;
	unsigned int itals : 1;
}prnt;

根据该声明,prnt包含4个1位的字段。现在,可以通过普通的结构成员运算符(.)单独给这些字段赋值:

prnt.itals = 0;
prnt undln = 1;

由于每个字段恰好为1位,所以只能为其赋值1或0。但是,要确保所赋的值不超过字段可容纳的范围。

如果声明的总位数超过了一个unsigned int 类型的大小会怎么样?会用到下一个unsigned int 类型的存储位置。一个字段不允许跨越两个unsigned int 之间的边界。编译器会自动移动跨界的字段,保持unsigned int 的边界对齐。一旦发生这样的情况,第1个unsigned int 中会留下一个未命名的“洞”。

可以用未命名的字段宽度“填充”未命名的“洞”。使用一个宽度为0的未命名字段迫使下一个字段与下一个整数对齐:

struct
{
	unsigned int field1 : 1;
	unsigned int		: 2;
	unsigned int field2 : 1;
	unsigned int		: 0;
	unsigned int field3 : 1;
}stuff;

这里,在stuff.field1和stuff.field2之间,有一个2位的空隙;stuff.field3将存储在下一个unsigned int中。

5、位字段和按位运算符

在同类型的编程中问题中,位字段和按位运算符是两种可替换的方法,用哪种方法都可以。可以通过一个联合吧结构方法和位方法放在一起。

假定声明了 struct box_props 类型,然后这样声明联合:

union Views    //把数据看作结构或unsigned int类型的变量
{
    struct box_props st_view;
    unsigned shord us_view;
};

在某些系统中,unsigned int 和box_props 类型的结构都占用16位内存。但是,在其他系统中是32位。无论哪种情况,通过联合,都可以使用 st_view 成员吧一块内存看作是一个结构,或者使用 us_view 成员吧相同的内存块看作是一个unsigned short。联合只允许初始化第1个成员,所以初始化值必须与结构相匹配。

#include <stdio.h>
#include <stdbool.h>
#include <limits.h>
//位字段符号常量
//边框线样式
#define SOLID	0
#define DOTTED	1
#define DASHED	2

//三原色
#define BLUE	4
#define GREEN	2
#define RED		1

//混合颜色
#define BLACK	0
#define YELLOW	(RED | GREEN)
#define MAGENTA	(RED | BLUE)
#define CYAN	(GREEN | BLUE)
#define WHITE	(RED | GREEN | BLUE)

//按位方法中用到的符号常量
#define OPAQUE			0x1
#define FILL_BLUE		0x8
#define FILL_GREEN		0x4
#define FILL_RED		0x2
#define FILL_MASK		0xE
#define BORDER			0x100
#define BORDER_BLUE		0x800
#define BORDER_GREEN	0x400
#define BORDER_RED		0x200
#define BORDER_MASK		0xE00
#define B_SOLID			0
#define B_DOTTED		0x1000
#define B_DASHED		0x2000
#define STYLE_MASK	0x3000

const char* colors[8] = { "black","red","green","yellow","blue","magenta","cyan","white" };

struct box_props
{
	bool opaque					: 1;
	unsigned int fill_color		: 3;
	unsigned int				: 4;
	bool show_border			: 1;
	unsigned int border_color	: 3;
	unsigned int border_style	: 2;
	unsigned int				: 2;
};

union Views//把数据看作结构或unsigned short 类型的变量
{
	struct box_props st_view;
	unsigned short us_view;
};

void show_settings(const struct box_props* pb);
void show_settings1(unsigned short);
char* itobs(int n, char* ps);

int main(void)
{
	//创建Views联合,并初始化initialize struct box view
	union Views box = { {true,YELLOW,true,GREEN,DASHED} };
	char bin_str[8 * sizeof(unsigned int) + 1];

	printf("Original box settings:\n");
	show_settings(&box.st_view);
	printf("\nBox settings using unsigned int view:\n");
	show_settings1(box.us_view);

	printf("bits are %s\n", itobs(box.us_view, bin_str));
	box.us_view &= ~FILL_MASK;				//把表示填充色的位清0
	box.us_view |= (FILL_BLUE | FILL_GREEN);//重置填充色
	box.us_view ^= OPAQUE;					//切换是否透明的位
	box.us_view |= BORDER_RED;				//错误的方法
	box.us_view &= ~STYLE_MASK;				//把样式的位清0
	box.us_view |= B_DOTTED;				//把样式设置为点
	printf("\nModified box settings:\n");
	show_settings(&box.st_view);
	printf("\nBox settings using unsigned int view:\n");
	show_settings1(box.us_view);
	printf("bits are %s\n", itobs(box.us_view, bin_str));

	return 0;
}

void show_settings(const struct box_props* pb)
{
	printf("Box is %s.\n", pb->opaque == true ? "opaque" : "transparent");
	printf("The fill color is %s.\n", colors[pb->fill_color]);
	printf("Border %s.\n", pb->show_border == true ? "showm" : "not shown");
	printf("The border color is %s.\n", colors[pb->border_color]);
	printf("The border style is ");
	switch (pb->border_style)
	{
	case SOLID:
		printf("solid.\n");
		break;
	case DOTTED:
		printf("dotted.\n");
		break;
	case DASHED:
		printf("dashed.\n");
		break;
	default:
		printf("unknown type.\n");
	}
}

void show_settings1(unsigned short us)
{
	printf("box is %s.\n", (us & OPAQUE) == OPAQUE ? "opaque" : "transparent");
	printf("The fill color is %s.\n", colors[(us >> 1) & 07]);
	printf("Border %s.\n", (us & BORDER) == BORDER ? "showm" : "not shown");
	printf("The border style is ");
	switch (us & STYLE_MASK)
	{
	case B_SOLID:
		printf("solid.\n");
		break;
	case B_DOTTED:
		printf("dotted.\n");
		break;
	case B_DASHED:
		printf("dashed.\n");
		break;
	default:
		printf("unknown type.\n");
	}
	printf("The border color is %s.\n", colors[(us >> 9) & 07]);
}

char* itobs(int n, char* ps)
{
	int i;
	const static int size = CHAR_BIT * sizeof(int);

	for (i = size - 1; i >= 0; i--, n >>= 1)
	{
		ps[i] = (01 & n) + '0';
		ps[size] = '\0';

		return ps;
	}
}

6、对齐特性(C11)

_Alignof运算符给出一个类型的对齐要求,在关键字_Alignof后面的圆括号中写上类型名即可:

size_t d_align = _Alignof(float);

假设d_align 的值是4,意思是 float 类型对象的对齐要求是4.也就是说,4是存储该类型值相邻地址的字节数。一般而言,对齐值都应该是2的非负整数次幂。较大的对齐值被称为stricter或stronger  较小的对齐值被称为weaker。

可以使用 _Alignas 说明符指定一个变量或类型的对齐值。但是,不应该要求该值小于基本对齐值。例如,如果 float 类型的对齐要求是4,不要请求其对齐值是1或2.搞说明符用作声明的一部分,说明符后面的圆括号内包含对齐值或类型:

_Alignas(double) char c1;
_Alignas(8) char c2;

C11在 stdlib.h 库还添加了一个新的内存分配函数,用于对齐动态分配的内存。该函数的原型如下:

void *aligned_alloc(size_t alignment, size_t size);

第一个参数代表指定的对齐,第2个参数是所需的字节数,其值应是第1个参数的倍数。与其他内存分配函数一样,要使用 free() 函数释放之前分配的内存。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值