C/C++位运算技巧

预备知识

对于位运算,大家都很熟悉,基本的位操作有与、或、非、异或等等。在面试中经常会出现位运算相关的题,所以我就做了简单的整理,参考了很多写的很好的博客及书籍。

现在简单说一下,移位运算。

左移运算:x << y。将x左移y位,将x最左边的y位丢弃,在右边补y个0。

右移运算:x >> y。将x右移y位,这需要区分x是有符号数还是无符号数。在x是无符号数时,只需将x的最右边的y位丢弃,在左边补上y个0。在x是有符号数时,又分为x是正数还是负数。正数时,同无符号数的处理相同;负数时,将将x的最右边的y位丢弃,在左边补上y个1。


一、概念辨析:区分C语言中的逻辑运算和位运算

1、逻辑运算:

C语言中提供了三种逻辑运算符:

&& 与运算 双目运算符 左结合性
|| 或运算 双目运算符 左结合性
! 非运算 单目运算符 右结合性
应用场合:用于条件语句

2、位运算:

C语言提供了六种位运算符:

(1) & 按位与
(2) | 按位或
(3) ^ 按位异或
(4) ~ 取反
(5) << 左移
运算法则:x << y——将x左移y位,将x最左边的y位丢弃,在右边补y个0。对于signed和unsigned是相同的

(6) >> 右移
运算法则:x >> y——将x右移y位,这需要区分x是有符号数还是无符号数,具体情况如下:

a、在x是无符号数时,只需将x的最右边的y位丢弃,在左边补上y个0;
b、在x是有符号正数时,同无符号数的处理相同;
c、在x是有符号负数时,将将x的最右边的y位丢弃,在左边补上y个1。
应用场合:用于二进制运算

3、逻辑运算和位运算的区别(以逻辑与和按位与为例)

对于两个逻辑量,都是一样的;

对于两个整型量,就不一样了,例如,对2和1进行逻辑与和按位与运算:

2&&1=1
2&1=0

二、位运算的使用

1、优秀程序员不得不知的20个位运算技巧
点击打开链接

在本篇博客所举得例子中,完成相应功能的C语言代码都仅仅是一行位运算搞定。此处仅列出标题,以对该片博文的作者致敬。
(1)获得int型最大值
(2)获得int型最小值
(3)获得long型最大值
(4)乘以2运算
(5)除以2运算
(6)乘以2的m次方
(7)除以2的m次方
(8)判断一个数的奇偶性
(9)不用临时变量交换两个数
(10)取绝对值
(11)取两个数的最大值
(12)取两个数的最小值
(13)判断符号是否相同
(14)计算n的二次方
(15)判断一个数是不是2的幂
(16)对2的n次方取余
(17)求两个整数的平均值
(18)从低位到高位,取n的m位
(19)从低位到高位,将n的第m位置1
(20)从低位到高位,将n的第m位置0
(21)另:计算n+1,计算n-1,取相反数,
(22)sign函数,参数为n,当n>0时候返回1,n<0时返回-1,n=0时返回0。

位运算技巧

计算一个数的二进制中1的个数

通过与初始值为1的标志位进行与运算,判断最低位是否为1;然后将标志位左移,判断次低位是否为1;一直这样计算,直到将每一位都判断完毕。

	计算一个数的二进制中1的个数
*/
int countOf1(int num)
{
	int count = 0;
	unsigned int flag = 1;
	while(flag)
	{
		if(num &amp; flag)
		{
			count++;
		}
		flag = flag &lt;&lt; 1;
	}
	return count;
}

还有一种方法,一个整数减一,可以得到该整数的最右边的1变为0,这个1右边的0变为1。对这个整数和整数减一进行与运算,将该整数的最右边的1变为0,其余位保持不变。直到该整数变为0,进行的与运算的次数即为整数中1的个数。

/*
	计算一个数的二进制中1的个数
*/
int countOf1_2(int num)
{
	int count = 0;

	while(num)
	{
		num = num &amp; (num - 1);
		count++;
	}
		return count;
}

判断一个数是否是2的n次方

一个数是2的n次方,则这个数的最高位是1,其余位为0。根据上一题的第二种解法可以很容易得到解决方案。将这个整数与整数减一进行与运算,如果得到的结果为零,可证明该数为2的n次方。

/*
	判断一个数是否为2的n次方(一个数为2的n次方,则最高位为1,其余位为0)
*/
bool is2Power(int num)
{
	bool flag = true;

	num = num &amp; (num - 1); //计算num和num - 1的与的结果
	if(num) //如果结果为0,则不是2的n次方
	{
		flag = false;
	}
		return flag;
}

整数n经过多少步可以变为整数m

n和m的异或结果可以得知两数不同位的个数,再调用计算一个数中1的个数的方法,即可得到结果。

/*
	求解n变化为m,需要进行的操作步数
*/
int countChange(int n,int m)
{
	n = n ^ m; //求n和m的异或,再计算结果中1的个数
	return countOf1_2(n);
}

获得最大的int值

/*
	获取最大的int
	得到结果:2147483647
*/
int getMaxInt()
{
	return (1 &lt;&lt; 31) - 1;
}
/*
	使用g++编译,出现warning: left shift count is negative
*/
int getMaxInt_2()
{
	return (1 &lt;&lt; -1) - 1;
}
int getMaxInt_3()
{
	return ~(1 &lt;&lt; 31);
}
/*
	在不了解int的长度情况下使用
*/
int getMaxInt_4()
{
	return ((unsigned int) -1) &gt;&gt; 1; 
}

获得最小的int值

与获得最大的int方法类似。

/*
	求最小int
	得到结果:-2147483648
*/
int getMinInt()
{
	return 1 << 31;
}
/*
	同样在g++下编译,出现warning: left shift count is negative
*/
int getMinInt_2()
{
	return 1 << -1;
}


获得最大的long

/*
	求最大long
	得到结果:9223372036854775807
*/
long getMaxLong()
{
	return ((unsigned long) -1) >> 1;
}

判断一个数的奇偶性

判断奇偶性,实质是判断最后一位是否是1.

/*
	判断一个数的奇偶性.返回1,为奇数;返回0,为偶数
*/
bool isOdd(int num)
{
	return num & 1 == 1;
}


交换两个数(不借助第三变量)

不用第三个变量交换两个数的方法也有几种,例如a = a + b; b = a - b; a = a - b。下面这种方法可以实现的基础是一个数m与另一个数n异或,再与n异或,得到的结果是m.

/*
	不适用临时变量,交换两个数
	a = a ^ b
	b = b ^ a
	a = a ^ b
*/
void mySwap(int* a,int* b)
{
	(*a) ^= (*b) ^= (*a) ^= (*b);
}


求一个数的绝对值

下面的方法实现的基础是将n右移31位,可以获得n的符号。

/*
	取绝对值
	n右移31位,可以获得n的符号。若n为正数,得到0;若n为负数,得到 -1
*/
int myAbs(int n){
	return (n ^ n >> 31) - (n >> 31);
}


求两个数的平均值

第一种方法较为普遍且简单,不多说了。第二种方法,需要知道的是,( m ^ n ) >> 1得到的结果是m和n其中一个数的有些位为1的值的一半,m & n得到的结果是m 和n都为1的那些位,两个结果相加得到m和n的平均数。

/*
	求m和n的平均数
*/
int getAverage(int m,int n){
	return (m + n) >> 1;
}

/*
求m和n的平均数
(m ^ n) >> 1 -> 获得m和n两个数中一个数的某些位为1的一半
m & n -> 获得m和n两个数中都为1的某些位
*/
int getAverage_2(int m,int n){
return ((m ^ n) >> 1) + (m & n);
}

求解倒数第m位相关问题

/*
	获取n的倒数第m位的值(从1开始计数)
*/
int getMthByTail(int n,int m){
	return (n >> (m - 1)) & 1;
}
/*
	将n的倒数第m位设为1
*/
int setMthByTail21(int n,int m)
{
	return n | (1 << (m - 1));
}
/*
	将n的倒数第m位设为0
*/
int setMthByTail20(int n,int m)
{
	return n & ~(1 << (m - 1));
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值