操作符详解(1)

1.操作符的分类

算术操作符:+、-、*、/、%

移位操作符: << >>

位操作符:& | ^

赋值操作符:=、+=、-=、*=、/=、<<=、>>=、&=、|=、^=

单目操作符:!、++、–、&、*、+、-、~、sizeof、(类型)

关系操作符:>、>=、<、<=、==、!=

逻辑操作符:&&、||

条件操作符:?、:

逗号表达式:,

下标引用:[ ]

函数调用:( )

结构成员访问:. 、->

2.二进制和进制转换

2进制、8进制、10进制、16进制是数值的不同表示形式而已。

比如:数值15的各种进制的表示形式:

2进制:1111
8进制:17
10进制:15
16进制:F
16进制的数值之前写:0x
8进制的数值之前写:0

10进制是我们生活中经常使用的,我们已经形成了很多尝试:

10进制中满10进1
10进制的数字每一位都是0~9的数字组成的

其实2进制也是一样的:

2进制中满2进1
2进制的数字每一位都是0~1的数字组成的。

2.1. 二进制转10进制

10进制的每一位是有权重的,10进制的数字从右向左是个位、十位、百位……,分别每一位的权重是10的0次方 , 10的一次方 , 10的二次方 …

如下图:
在这里插入图片描述
2进制和10进制是类似的,只不过3进制的每一位权重,从右向左是:2的0次方、2的1次方、2的2次方……

如下图:
在这里插入图片描述

2.1.1 10进制转2进制数字

在这里插入图片描述

2.2 二进制转8进制和16进制

2.2.1 二进制转8进制

8进制的数字每一位是0 ~ 7的,0 ~ 7的数字,写成2进制,最多有3个2进制位就足够了,比如7的二进制是111,所以在2进制转8进制的时候,从2进制序列中右边低位开始向左每3个2进制位会换算一个8进制位,剩余不够3个2进制位的直接换算。

如:2进制的01101011,换成8进制:0152,0开头的数字,会被当做8进制。
在这里插入图片描述

2.2.1 二进制转换16进制

16进制的数字每一位是0 ~9,a ~ f的数字,各自写成2进制,最多有4个2进制位就足够了,比如 f 的二进制是1111,所以在2进制转16进制的时候,从2进制序列的右边低位开始向左每4个2进制位会换算一个16进制位,剩余不够4个二进制位的直接换算。

如:2进制01101011,换成16进制:0x6b,16进制表示的时候前面加0x
在这里插入图片描述

3.原码、反码、补码

整数的2进制表示方法有三种,即原码、反码和补码。

有符号整数的三种表示方法均有符号位数值位两部分,2进制序列中,最高位的1位是被当作符号位,剩余的都是数值位。

符号位都是用0表示,用1表示

正整数的原、反、补码都相同。
负整数的三种表示方法各不相同。

原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。

反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码:反码+1就得到补码。

补码得到原码也是可以使用:取反,+1的操作

对于整型来说:数据存放内存中其实存放的是补码

4.位移操作符

<<左移操作符 >>右移操作符
注:移位操作符的操作数只能是整数。

4.1左移操作符

移位规则:左边抛弃,右边补0

#include<stdio.h>
int main()
{
	int n = 12;
	//左移移动的是二进制序列
	//12
	//00000000 00000000 00000000 00001100
	int m = n << 1;
	printf("%d\n", m);
	return 0;
}

在这里插入图片描述
左移后的结果是:24

在这里插入图片描述
在这里插入图片描述
我们可以看到,n的值没有发生变化。就只是移位后的结果是这个结果,这个结果赋值到m上。这就等价于下面的代码:

int n = 10;
int m = n + 1;

如果要让n发生变化:

n <<= 1;

让移位的结果赋值位n,n就发生了变化。

我们可以举一个负数的例子:

int main()
{
	int a = -10;
	//10000000 00000000 00000000 00001010-原码
	//11111111 11111111 11111111 11110101-反码
	//11111111 11111111 11111111 11110110-补码
	int b = a << 1;
	printf("%d\n", b);
	printf("%d\n", a);
	return 0;
}

结果:
-20
-10

在这里插入图片描述
我们可以发现向左移动有乘2的效果

4.2 右移操作符

移位规则:右移运算分两种:

  • 逻辑右移:左边用0填充,右边丢弃
  • 算术右移:左边用原该值的符号位填充,右边丢弃

算术右移是原来的数字是正数用0填充,原来是负数用1填充。

右移到底是算术右移,还是逻辑右移,是取决于编译器的,但是大部分的编译器采用的都是算术右移。

#include<stdio.h>
int main()
{
	int a = -10;
	//10000000 00000000 00000000 00001010
	//11111111 11111111 11111111 11110101
	//11111111 11111111 11111111 11110110
	int b = a >> 1;
	return 0;
}

上面的代码在VS中采用的是算术右移,结果是:

-5

右移一位有除2的效果。

5.位操作符

位操作符有:

& 按位与
| 按位或
^ 按位异或
~ 按位取反

注意:他们的操作数必须是整数。

这里的位都是二进制,所以我们在参与运算的时候都需要转化为二进制。

5.1 &按位与

对应的二进制位进行按位与,有0则为0,同时为1才为1

int main()
{
	int a = -5;
	int b = 13;
	int c = a & b;//对应的二进制位进行按位与,有0则为0,同时为1才为1
	//-5 
	//10000000 00000000 00000000 00000101
	//11111111 11111111 11111111 11111010
	//11111111 11111111 11111111 11111011
	//13
	//00000000 00000000 00000000 00001101
	//a&b
	//00000000 00000000 00000000 00001001 
	printf("%d",c);
		return 0;
}

结果为9

5.2 | 按位或

对应的二进制位或运算,有1就为1,两个同时为0才为0

int main()
{
	int a = -5;
	int b = 13;
	int c = a | b;//对应的二进制位进行按位与,有0则为0,同时为1才为1
	//-5 
	//10000000 00000000 00000000 00000101
	//11111111 11111111 11111111 11111010
	//11111111 11111111 11111111 11111011
	//13
	//00000000 00000000 00000000 00001101
	//a|b
	//11111111 11111111 11111111 11111111 
	printf("%d", c);
	return 0;
}

结果为-1

当我们推导出a|b的二进制位时,这时只是补码,但是这是一个负数,需要变为原码,还需要进行转化,最终为-1.

5.3 ^ 按位异或

对应的二进制位进行异或运算,相同为0,相异为1.

int main()
{
	int a = -5;
	int b = 13;
	int c = a ^ b;//对应的二进制位进行按位与,有0则为0,同时为1才为1
	//-5 
	//10000000 00000000 00000000 00000101
	//11111111 11111111 11111111 11111010
	//11111111 11111111 11111111 11111011
	//13
	//00000000 00000000 00000000 00001101
	//a^b
	//11111111 11111111 11111111 11110110还是需要再转化
	printf("%d", c);
	return 0;
}

结果为:-10

5.4 ~按位取反

原来是0,改为1;原来是1,改为0.

int main()
{
	int a = 0;
	//00000000 00000000 00000000 00000000-补码
	//11111111 11111111 11111111 11111111
	//10000000 00000000 00000000 00000000
	//10000000 00000000 00000000 00000001
	printf("%d\n", ~a);//~a就是对a按位取反
	return 0;
}

结果为:-1

分析几道面试题:
①不能创建临时变量(第三个变量),实现两个整数的交换。

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:%d %d\n", a, b);
	a = a + b;
	b = a - b;
	a = a - b;
	printf("交换后:%d %d\n", a, b);
}

上面代码将a+b放入a中,再用a+b的a减去b就是a,将吖的值放入b中,现在b中的值是a的值,再用a+b的a减去b,得到的就是b的值,将b的值放入a中。

但是上面代码也有一些问题,可能会有溢出的风险,如果交换的两个数都是较大的数字,两个相加就可能会超出编译器所限定的。

那么还有一种方式:

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:%d %d\n", a, b);
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("交换后:%d %d\n", a, b);
}

我们需要知道的是^按位异或是相同为0,相异为1,我们可以推导出,0无论和哪个数字异或,都是这个数字本身。

上面的代码也可以写成如下形式,可以帮助理解:

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:%d %d\n", a, b);
	a = a ^ b;
	b = a ^ b^b;//b和b按位异或得到的是0,0和任何数字按位异或,都是数字本身
	a = a^a ^ b;
	printf("交换后:%d %d\n", a, b);
}

b和b按位异或得到的是0,0和任何数字按位异或,都是数字本身
②编写代码实现:求一个整数存储在内存中的二进制中1的个数。

接下来我们来分析一下:

在这里插入图片描述
在这里插入图片描述
我们可以通过分析得出,上面这种方式可以写出该数字的二进制,接下来只需要进行判断,如果是1,就进行记数。

int main()
{
	int n = 13;
	int count = 0;
	while(n)
	{
		if (n % 2 == 1)
			count++;
		n = n / 2;
	}
	printf("%d", count);
	return 0;
}

但是这个代码对负数是有问题的。

假设我们现在要求15的二进制位1的个数。
在这里插入图片描述

int main()
{
	int n = -1;
	int count = 0;
	int i = 0;
	for (i = 0; i < 32; i++)
	{
		if (((n >> i) & 1) == 1)
		{
			count++;
		}
	}
	printf("%d\n", count);
	return 0;
}

还可以再改进一下:
在这里插入图片描述
这个表达式可以执行几次,说明就有几个1.

int main()
{
	int n = 0;
	scanf("%d", &n);
	int count = 0;
	int i = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	printf("%d\n", count);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值