操作符详解(上)

1、操作符的分类

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

移位操作符:<<  >>

位操作符:& 、|、^

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

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

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

逻辑操作符:&&、||

条件操作符:?:

逗号操作符:,

下标引用:[  ]

函数调用:( )

结构成员访问:.  、->

关于第一个算术操作符我们已经理解了;下面的操作符我们要先了解一下关于2进制的概念

2、二进制和进制转换

我们经常听到的2进制、8进制、16进制,它们都是表示数值,只是它们表示数值的方式不同。

比如我们15的个进制表示形式

15的2进制:1111

15的8进制:017

15的10进制:15

15的16进制:0xF

0x表示16进制

0表示8进制

二进制是逢二进一,是由0和1数字组成的;

八进制是逢八进一,是由0~8的数字组成的;

十进制是逢十进一,是由0~9的数字组成的;

2.1 2进制转10进制

我们10进制的123是一百二十三;是因为每一位是有权重的,数字从右往左数,每一位的权重是10的0次方、10的1次方、10的2次方,以此类推,再把它们与各自的权重相乘,再相加;

2进制和10进制的类似,每个数字都有权重;2的0次方,2的1次方、2的2次方.......

如果是2进制1101;从右往左的数字,各自的权重相乘再相加。

2.1.1 10进制转2进制

我们把10进制数除于2取余数,再从下往上把余数写出来,就是10进制转2进制的数

10进制的125转2进制数是1111101;

2.2 2进制转8进制和16进制

2.2.1 2进制转8进制

8进制是由0~7的数字组成的,0~7的数字转2进制,最高位7:0111,看的出来只要三位数就可以可以表示0~7的数字了,那2进制数从右往左边数,每三位数换算成一个8进制数。

2进制的01101011,换成8进制:0153,0开头的数字表示8进制数,

01不够直接算;

反过来8进制数转2进制数,从右往左边数,每一个8进制数,转换为三位8进制数;

2.2.2  2进制转 16进制数

16进制是由0~9数字,a~f(10~15)组成的;16进制最高位 f 用二进制表示1111,看得出来只要4位数就可以表示0~ f 的每一位数字;二进制数从右边往左边数每4位二进制数转换1位16位进制数;

比如2进制的01101011,转换为16进制,0x6b;

反过来;16进制数转2进制数;每一位16进制数转化为4位2进制数;

3、原码、反码、补码

存在内存的值2进制存储的,那整数2进制的表示方法有3种:原码,反码,补码;整数由分为有符号整数和无符号整数,它们的表达方式是不一样的;

有符号整数:三种表示方法都有符号位和数值位两部分;在2进制中,最高位是符号位,剩下的是数值位;

最高位1表示负;最高位0表示正;

比如-7是有符号整型,是个负数;

-7是负数,4个字节,32个比特位;要存满,最高位是1表示负;剩下的是值;

直接按照正负的形式写成2进制的数是原码;

原码的符号位不变;其他的依次取反就是反码;

反码+1就是补码

正数的原码、反码、补码是一样的。

无符号整数是没有符号位的,都是数值位;

注意原码取反就是反码,反码+1就是补码;反过来补码-1就是反码;反码取反就是原码;

补码取反+1就是原码;

比如-7的补码

补码取反+1就是原码

 

 对于整型来说:数据存放在内存中的就是补码;

在计算机里,数值一律用补码表示和存储。使用补码,可以将符号位和数值位统一处理;加法和减法也能统一处理,CPU只能算加法,补码和原码之间相互转换,运算过程是相同的,不需要额外的硬件电路。

CPU只能算加法,如果算减法就是1+(-1);

举例,如果数值存的是原码不是补码,那计算的结果就是-2了,符号位都相加了;

数值存的是补码,那么相加起来逢二进一,符号位就溢出了;刚好是一个无符号整数;

4、移位操作符

<<  左移操作符

>>右移操作符

注意:移位操作符的操作数只能是整数;

4.1左移操作符

比如5;5的补码

00000000 00000000 00000000 00000101  5的补码

 5<<1; 我们左移操作符还是右移操作符操作的都是数值的补码;5的补码向左移了一位,然后右边补0;

 

#include<stdio.h>
int main()
{
	int a = 5;
	int b = a << 1;
	printf("%d\n",a);
	printf("%d\n",b);
	return 0;
}

运行之后的结果看的出,左移操作符是有x2的这种效果; 

4.2 右移操作符

右移的规则是分两种的;

1、逻辑右移;左边用0填充;右边丢弃;

2、算术右移;左边用该值的符号位来填充;右边丢弃;

至于是用逻辑右移还是算术右移,是看编译器的,大致是用算术右移的;

比如10;10的补码;

如果是逻辑右移的话,那-10就变成5了;

10000000 00000000 00000000 00001010  -10的补码

00000000 00000000 00000000 00000101    逻辑右移

10000000 00000000 00000000 00000101    算术右移

大致上右移都是算术右移 

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

从结果上可以看出右移操作符有 /2 的效果;

注意 下面的写法都是错的,一个整型就是32个比特位,怎么能移50位呢?对于移负数位,在标准中这是没有定义的,弄不了,是错的;

int a =-10;

a>>50;  a<<50

a<<-1 ;  a>>-1

5、位操作符:& 、|、^、~

位操作符的操作的是数值的二进制位;把数值转换位二进制位这种形式,再对对应的二进制位进行计算;

&  按位与

|   按位或

^  按位异或

~  按位取反

比如  -5&13

int main()
{
	int a = -5;
	int b = 13;

	int c = a & b;
	printf("%d\n",c);
	return 0;
}

我们算出-5的补码和13的补码得出结果就是9; 

 -5|13 

int main()
{
	int a = -5;
	int b = 13;

	int c = a | b;
	printf("%d\n",c);
	return 0;
}

-5的补码按位或13的补码;有1为1;得出的结果是个负数 的补码,还要取反+1得到原码放回C;结果是-1;

 

-5^13

int main()
{
	int a = -5;
	int b = 13;

	int c = a ^ b;
	printf("%d\n",c);
	return 0;
}

按位异或,相同为0;相异为1;结果为-10;

 

 ~按位取反;比如~0

int main()
{
	int a = 0;
	printf("%d\n",~a);
	return 0;
}

0的补码取反是负数的补码;要取反+1的原码;结果是-1; 

 

举例; 不 能创建临时变量(第三个变量),实现两个数的交换。

我们实现两个数的交换通常都是创建一个中间变量;

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

但是题目说不能使用第三个变量,我们可以这样做;

先把a+b的值赋予a;

再a-b的值赋予b;这个a是a+b的值,a-b就是a的值,赋予b;

然后a-b的值赋予a;这个a还是a+b的值,但这b是上条代码a的值,a+b-a就是b,赋值给a;

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

注意,这个代码是有问题的,如果a和b的值太大,那就会有溢出的问题。

还有一种方法就是按位异或;相同为0,相异为1;

假设a=3;a^a=0;每个二进制都相同,结果为0;

a^0=a;a转为二进制为011,011^0相同为0,相异为1,那就是 011,就是它本身a;

先把a^b的值赋予a;

这时a=a^b,那下面代码就是b=a^b^b,b^b等于0,a^0等于a,所以就把a赋予的b;

此时a还是等于a^b;根据上条代码此时b的值是a;那就是a=a^b^a;a^a等于0;0^b等于b,然后再赋予a,所以a=b;

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

 练习1

编写代码实现:求⼀个整数存储在内存中的⼆进制中1的个数

 比如我们取10进制里面的每一位就是%10,取余数,然后去掉,那换过来取2进制里的每一个位,那就是%2,取余数,再/2去掉;得到的余数再从下往上数就是13的2进制位1101;

那我们转化成代码,只要是余数是1,那就++,表示2进制位里有几个1;

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

但是这个代码是有问题的,如果是-1呢?

-1原码是10000000 00000000 00000000 00000001

-1补码是111111111 111111111 1111111111 111111111

-1是由32个1的,如果这个代码是-1,那-1%2!=1,条件不成立,count不++;-1/2为0,循环结束,结果是0;是不对的;

那我们换一种方法,我们每次都取二进制位的最低位,然后再&上1;这样如果最低位是1,那就++,表示有二进制位有几个1;每算一次,就向右移1位,再算最低位,一共算32次;

 

 

算负数,-1结果是32,这个代码是正确的,但不是很简洁,假设算1,那只有1个1,但还是要算32次,那效率不高; 

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

其实还有一种更简便的方法:n = n & (n - 1) ,假设n是13,1101,那我们按照这个公式算一次,结果是1100,它把最右边的1去掉了,那再算一遍,结果是1000,又把最右边的1去掉了,再算一次,结果还是把最右边的1去掉了,那我们可以看出来,这个代码每算一次就去掉一个1;n - 1那对应的位就是0,后面剩下的位也是0,按位与的结果就把1给去掉了;

那我们转化成代码,只要n不是0;这个代码执行多少次那就有多少个1;每次执行-1,直到n=0,就停止循环,假设是13,那13,1101有3个1,就执行了3次,效率高了;

int main()
{
	int n = 13;
	int count = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	printf("%d\n",count);
	return 0;
}

再举个例子:判断n是不是2的次方数

那2的次方数就是1,2,4,8,16,2的0次方,2的1次方.........

我们转为2进制数

2的0次方,1,0001

2的1次方,2,0010

2的2次方,4,0100

2的3次方,8,1000

2的4次方,16,10000

看的出来,2的次方数的2进制位都是只有一个1,那我们用上一条代码,n = n & (n - 1) ,-1后,就是为0,那我们判断一下只要-1后为0,那就是2的次方数;

int main()
{
	int n = 0;
	scanf("%d",&n);
	if ((n & (n - 1)) == 0)
	{
		printf("yes");
	}
	else
	{
		printf("no");
	}
	return 0;
}

练习2

⼆进制位置0或者置1

编写代码将13⼆进制序列的第5位修改为1,然后再改回0

我们先将13的二进制序列改为的第5位改为1,那我们就在第5位按位或上一个1,那这个怎么来,那就是1<<4,13&1<<4;

那我们再改回0,那把第5位按位与0,那剩下的按位与1,原来是0,还是0,原来是1还是1,

那就是1<<4后取反,~(1<<4); 

 

int main()
{
	int n = 13;
	n = n | (1 << 4);
	printf("%d\n",n);

	n = n & (~(1 << 4));
	printf("%d\n",n);
	return 0;
}

感谢观看!感谢指正! 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值