C语言中的位运算

C语言中的位运算:

位运算,即对数据的二进制形式按位进行运算操作,c++中有多种位运算操作:

由于位运算是直接对内存中二进制数据进行操作,不需要进行转化,因此效率很高,速度比+-*/等算数运算更快

C语言中 位运算速度 > +-速度 > */速度 > %速度

合理利用位运算操作可以一定程度上提高程序运行速度,从而避免TLE

壹.左移/右移:

<<  二进制左移(SHL)运算符:

将一个运算对象的各二进制位全部左移若干位,右边补0,超出对应类型范围时左边的位自动丢弃。

例:

	printf("%d", 3 << 2);//结果为12

(3)10 = (11)2

*(3)10表示十进制数3,(11)2表示二进制数11

 向左移动两位后,右边补0,得到 (1100)2 =  (1*23+1*22+0*21+0*20)10 = 12

不难看出: (1100)2 = 十进制下  22*(1*21+1*20) = 22*(3)

以此类推: 3<<k  = 2k * 3 ;

同理:n<<k = n* 2^k

因此:我们可以借助 1<<k便捷的计算2的k次方

#由于位运算操做是直接针对内存中的二进制编码进行简单操做,所以位运算的运算效率要高于加减(+ -)操作,更快于乘除(*  /)操作,

  1<<k,远远快于需要进行k此操作的函数: pow(2,k); 可以减少计算耗时。

  在程序时间限制极为紧张时,也可以考虑把一些乘法操作改写成加法操作与左移操作的组合,从而提高计算效率

  如: x*10 = x<<3+x<<1;

 

>>  二进制右移(SHR)运算符:

二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。

例:

	printf("%d", 5 >> 1);//结果为2
	printf("%d", -5 >> 1);//结果为-3

(5)10 = (00000101)2

向右移动并舍弃一位,左边补0得 (00000010)2 = (2)10

(-5)10 在计算机中以二进制补码的形式表示为:(11111011)

整体向右移一位,左边补1得:(11111101) = (-3)10

类比左移运算不难看出: n>>k 相当于 n/2^k 向下取整    (注意:对负数向下取整相当于对其绝对值向上取整)

(不建议对负数进行位运算操作)

贰.按位 与 或 取反 异或 运算:

&  按位与(AND)运算符:

按位与操作,按二进制位进行"与"运算,每一位的运算规则于逻辑 “与” && 类似,只有在参与运算两数同时为1时结果为1

即:1与1=1,1与0=0,0与1=0,0与0=0

( 二者位数不一致时向右对齐,较小数左端补0,如: 010 & 111 = 010 )

例:

	printf("%d", 7 & 5);//结果为5

(7)10=(111)2      (5)10=(101)2

第一位1&1=1,第二位1&0=0,第三位1&1=1

(111)   &    (101)2    =     (101) =  (5)10

 

#与运算遵循 交换律,结合律,对或运算的分配律

a&b = b&a   a&(b&c) = (a&b)&c    a&(b|c) = (a&b)|(a&c)

#不难看出,一切正数奇数的二进制最低位一定是1,偶数最低位一定是0

  所以可以通过n&1判断n的奇偶性,结果为1则为奇,为0则为偶

  因为位运算比取余运算更高效,所以该表达式比n%2耗时更少

#利用&将数的二进制最低非0位变为0

	int a = 6;
	printf("%d\n", a & a - 1);

6=(110)6-1=(101)6&5 =(100)2=4

#利用&求数a的二进制中有多少个一 时间复杂度O(k) k为a中1的数量

	int a = 6, k = 0;
	while (a)
	{
		a = a & a - 1;
		k++;
	}
	printf("%d\n", k);

每次循环都会将a的最低为1位改为0,并且k++,当所有位全为0时循环结束,可算出a的二进制表示中有多少个1

|  按位或(OR)运算符:

按位或运算符,按二进制位进行"或"运算,每一位的运算规则于逻辑 “或” || 类似,只有在参与运算两数同时为0时结果为0

即:0或0=0,1或0=1,0或1=1,1或1=1

( 二者位数不一致时向右对齐,较小数左端补0,如: 010 | 111 = 111 )

例:

	printf("%d", 6 | 9);//结果为15

(6)10=(110)2=(0110)2    (9)10=(1001)2

第一位1|0=1,第二位0|1=1,第三位1|0=1,第四位0|1=1

(110)2 | (1001)2 = (1111)2 = (15)10

#或运算遵循 交换律,结合律,对与运算的分配律

a|b = b|a   a|(b|c) = (a|b)|c    a|(b&c) = (a|b)&(a|c)

~  按位取反(NOT)运算符:

将二进制位取反,0变为1,1变为0 (对于原码补码的符号位同样生效)

	printf("%d", ~3);//结果为-4

(3)10 = (00000011)2 按位取反得到 11111100 是二进制补码形式 转化为十进制得 -4

# ~x+1 = -1*x (利用补码的性质)

^  按位异或(XOR)运算符:

异或运算符,按二进制位进行"异或"运算。运算规则:当参与运算两数相同时结果为0,不相同时结果为1

即:0^0 = 0 , 1^1 = 0, 0^1 = 1, 1^0 = 1.

( 二者位数不一致时向右对齐,较小数左端补0,如: 010 ^ 111 = 101 )

例:

	printf("%d", 2 ^ 3);//结果为1

(2)10=(10)2 , (3)10=(11)2

第一位1^1=0,第二位1^0=1

(10)2 ^ (11)2 = (01)2 = (1)10

#异或遵循:

1、交换律: a^b=b^a

2、结合律: (a^b)^c = a^(b^c)

3、对于任何数x,都有x^x=0,x^0=x

4、自反性 a^b^b = a^0=a

#利用异或交换两数(高效率)

  a ^= b;
  b ^= a;
  a ^= b;

第一步:a ^= b ---> a = (a^b)

第二步:b ^= a ---> b = b^(a^b) ---> b = (b^b)^a = a

第三步:a ^= b ---> a = (a^b)^a = (a^a)^b = b

叁.位运算赋值运算符:

类似于 +=,-=操做,可以将位运算符于等号组合,形成计算并赋值的位运算赋值运算符

<<= 左移且赋值运算符 C <<= n 等同于 C = C << n
>>= 右移且赋值运算符 C >>= n 等同于 C = C >> n
&= 按位与且赋值运算符 C &= n 等同于 C = C & n
^= 按位异或且赋值运算符 C ^= n 等同于 C = C ^ n
|= 按位或且赋值运算符 C |= n 等同于 C = C | n

肆.运算符优先级:

1.括号 ()
2.按位取反 ~
3.乘除取余 *  /  %
4.加减 + - 
5.左右移 << >> 
6.关系运算 < <= > >= 
7.相等 ==  != 
8.按位与
9.按位异或
10.按位或
11.逻辑与 && 
12.逻辑或 || 
13.三目运算符 ?: 
14.赋值  = += -= *= /= %=>>= <<= &= ^= |= 

#可以发现除了~以外,其他位运算符的运算优先级均较低,大致上优先级 算数运算 > 位运算 > 逻辑运算

  因此当出现位运算和算数运算(+-*/)相混合的表达式时,应当多加括号以避免运算顺序错误

#不建议背诵此表,只需有大致印象即可,当不确定优先级时,可以通过多加括号解决

#   & ^ | 优先级低于<<,因此在c++中可能出现如下错误

	cout << 1 ^ 2 << endl;//编译错误

因为 ^ 优先级低于<< ,所以编译器会尝试先执行输出操作,后进行运算,显然计算机是无法执行的,所以编译时编译器会报错,加上括号即可

	cout << (1 ^ 2) << endl;
posted @ 2021-10-17 23:31  STEllIAF0X  阅读(477)  评论(0编辑  收藏  举报