C语言操作符

算数操作符

        说起算数操作符,大家应该都不陌生。它就是我们日常使用的加减乘除,再加上一个取模。它们分别为

+                    -                    *                    /                    %                 

        加减乘我不再讲述,它和日常使用相同。C语言中的除法需要注意一下,我们先看一下下面这个代码

#include <stdio.h>

int main()
{
	int a = 10, b = 3;
	printf("%d\n", a / b);

    printf("%f\n", 10 / 3.0);
	return 0;
}

        第一个结果是3.3吗?运行程序后我们知道,第一个结果是3,第二个才是小数。 

de18412df4324f91b215cc61984b8de6.png

        除法在C语言中分为小数除法和整数除法两种,具体是哪种除法是有除号两边的操作数决定的,当两边的操作数出现浮点数时,就是小数除法;否则默认是整数除法。

         取模也可以理解为取余。它与整数除法息息相关,模10的意思便是一个数除以10后的余数。所以,%两边的操作数的数据类型必须是整型,否则会出错。我们试着运行以下代码

dae07e2361574328bf661095c4a2d60b.png

        虽然不美观,但也能看。图左边是示例代码,右边是计算结果。由这些可以看出,取余运算就是基本的整数除法,它的结果是除法后的余数,结果的符号则与被除数的正负一致。 

移位操作符

        移位操作符,移动的是二进制位。在讲解这个操作符之前,我们先简单了解一下进制之间的转换。

        数据在计算机中是以二进制的形式存储的,这里我们以整数为例。将十进制整数转换为二进制的方法是:对十进制整数不断除2,取其余数,直到商为0时,接着将每一步的余数进行排列,便得到相应二进制数。以10为例,请看图:

7ea9790d1b694763b36c5c2dd23270c6.jpeg

        首先,10除以二,商5余0;接着5除以二,商2余1;2除以二,商1余0;最后,1除以二,商0余1。然后对余数进行排列,10的二进制数是1010。同样的方法,5的二进制数是101。

        弄清楚10进制转化成2进制后,我们来看一下二进制如何转换成10进制,看图:

c50b9f7d111741e5b7e0ba03289253b7.png

        方框中的数字是二进制数,方框是二进制位。而绿色数字是二进制位的权重。就像在十进制中,100实际上是1*10^2 + 0*10^1 + 0*10^0 = 100。二进制1010就是1*2^3 + 1*2^1 = 8 + 2 = 10。所以,二进制转换为十进制非常简单,只要用相应数位上的数字乘以权重,最后相加就是相应的十进制数。

        8进制和16进制与二进制类似,只是基数不同罢了,读者自行研究。

        进制转换到此为止,那么计算机是怎么区分正负数的呢?当然是因为计算机会将最高位作为符号位进行存储。例如,int型变量的大小是32位,那么计算机会将第32位作为符号位进行存储,0代表整数,1代表负数。当然,如果数据类型是无符号整数,例如unsigned,那就没有符号位的说法,它的32位就全部用来表示数字,这也是为什么unsigned能够表示的正数最大值要大于int。看一个代码:

#include <stdio.h>
#include <limits.h>

int main()
{
	printf("unsigned_max: %u\n", UINT_MAX);//4294967295 2^32 - 1
	printf("int_max: %d", INT_MAX);//2147483647 2^31 - 1

	return 0;
}

        注释便是它的结果。也许因为运行环境不同,结果会有所不同,但unsigned最大值确实大于int。 

        明白了正负数,我们得了解三种编码方式,他们分别为:原码,补码和反码。原码就是最高位是符号位,其他位是数值。整数的反码和补码与原码相同。负数的反码是由原码符号位不变其他位按位取反得到的。负数的补码就是符号位不变,然后在反码的基础上加1。以5为例,如图(32位):

3cbbf872ecfd413bb35fdbcc1e8659a6.png

        而计算机存储的就是二进制数的补码。不过补码转换成原码的方法也很简单:如果是正数的话,我们直接可以得知原码;如果是负数,有两个方法:一、将补码减一后得到反码,然后符号位不变,其他位按位取反得到原码。二、补码符号位不变,其他位按位取反,之后再加1,便得到原码。例:

e412eb24ea364a47ada5c07851694e37.png

        数据存储就此结束,接下来是移位操作符登场时间。首先登场的是,左移操作符:<<。

左移操作符

        左移操作符的基本规则为:左边抛弃,右边补零。 

         如图:

69109184a6a6492199d384b33f5a18ef.png

        这就是左移操作符的效果,但请注意,对一个变量进行左移操作并不能直接改变这个变量的值,就像a + 1并不改变a的值一个道理,只有对变量赋值才能改变变量的值。看下面一个代码:

#include <stdio.h>

int main()
{
	int num = 5;
	num << 1;
	printf("num: %d\n", num);

	num = num << 2;
	printf("num: %d\n", num);

	return 0;
}

        运行结果:

81722f399cc2400cbfbe406bd393c3c8.png

        上面这张图已经说明了一切,已经不需要赘述了。不过,可以发现,左移一个单位结果相当于给变量乘以2。实际上,左移几个单位,就是给变量乘以2的几次方,读者可以自行尝试,注意不要超出数据类型的取值范围。负数也是同样的效果。 

        左移操作符到此结束,接下来便是她的姐妹,右移操作符。

右移操作符

        右移操作符有点复杂,她分为算数右移和逻辑右移,具体是哪种右移方式由编译器决定,但现在一般都是算数右移,但逻辑右移也该有所了解。两种右移方式规则如下:

        逻辑右移:右边抛弃,左边补0。

        算数右移:右边抛弃,左边补符号位。

 88abf3bf7feb408ca1c7de5a24038089.png

        具体的可以看上图,接下来我们试着使用代码验证一下: 

#include <stdio.h>

int main()
{
	int num = -1;
	num >> 1;
	printf("num: %d\n", num);

	num = num >> 1;
	printf("num: %d\n", num);

	return 0;
}

   533abed2938d4d05a0c659b3faa7d3ce.png 

        由结果可以看出,作者使用的VS2022是算数右移。如果num为5,我预测结果会变为2,我们试运行一下,结果如下:

 d4e350d7405a420d8449d9c8557424db.png

        由此可以看出,右移和左移一样,并不能直接修改变量,需对变量进行赋值才能达到修改的目的。

        在这里我们需要注意一点,对于移位操作符,移动的位数必须是整数。并且,移动负数位或者移动位数过大的话,这种行为是未定义的,编译器会警告,但不报错,谁知道会出现什么结果。而移动小数位,则会直接报错,看下面代码:

#include <stdio.h>

int main()
{
	int num = 5;

	num = num >> -2;
	printf("num: %d\n", num);
	num = num >> 100;
	printf("num: %d\n", num);

	return 0;
}

        我运行后结果全为0,且对两个移位进行了警告:

d1e19fc4ccdc4062b1098d17cb49b14b.png

        当其移动小数位时,直接进行报错:

e6e7bf71939045e683542aca4c321939.png

        所以使用时千万要小心。

位操作符

        位操作符和移位操作符相同,都是对二进制位进行处理,使用时注意一点,计算机使用补码储存,所以位操作符进行计算时,也是通过补码进行计算。位操作符有三个:|(按位或)、&(按位与)和 ^(按位异或)。 

        首先,他们的操作数必须是整数。看下面一个代码:

#include <stdio.h>

int main()
{
	int num = 10;
	num | 1.0;
	num & 1;
	num ^ 1;
	return 0;
}

        编译器报错,因为|右操作数类型为double。 

        讲清注意事项后,来看一看各操作符的作用。首先是按位或:

|(这并不是字母):

        按位或便是将两边操作数对应位进行或运算。 同为0时为0,其余情况为1。具体看图:

dc0ff2aaac9d43a0abebdf5dd939fdd5.png

        我们使用代码验证一下:

#include <stdio.h>

int main()
{
	int num = 10;
	int num1 = 5;
	
	printf("10 | 5 = %d\n", num | num1);
	printf("10 | -5 = %d\n", num | (-num1));

	return 0;
}

        结果如下:

6c6a8dcb0c2a43bcbbb2fb00e931e3e1.png

        按位或就此结束,接下来是按位与:

&:

         按位与是将两边操作数对应数位进行与运算,即都为1时结果为1,其余情况结果为0。具体看图,我们依旧使用上面两个数:

07a459304e41423ba75ad6247b7a581b.png

        使用代码验证一下: 

#include <stdio.h>

int main()
{
	int num = 10;
	int num1 = 5;

	printf("10 & 5 = %d\n", num & num1);
	printf("10 & -5 = %d\n", num & (-num1));

	return 0;
}

        e87c15e4bef943f4878894e7638beb39.png 

        看来手动计算并没有错,读者可以自己试试。注意:运算的是补码,结果也是补码! 

^: 

         好的,终于见到异或了,这个和数学次方一模一样的符号在C语言中又有什么作用呢?它的作用很好记忆,相同为0,不同为1。我们还是以那两个数为例:

ee48e0c243de4a289dc724a646c5a8e8.png

        来,代码走起:

#include <stdio.h>

int main()
{
	int num = 10;
	int num1 = 5;

	printf("10 ^ 5 = %d\n", num ^ num1);
	printf("10 ^ -5 = %d\n", num ^ (-num1));

	return 0;
}

        结果如下:

        93b5fe6f0f8d4fcbbbd6e963c86902b7.png 

        根据异或的计算规则,我们可以思考一下,5^5,5^0他们的结果分别是什么?

        实际上,一个数异或它本身,结果为0,异或0,结果还是这个数。读者可以自己试试。这样的话,就有操作空间了,我们看下面这个简单的代码:

#include <stdio.h>

int main()
{
	printf("5 ^ 5 ^ 6 = %d\n", 5 ^ 5 ^ 6);
	printf("5 ^ 6 ^ 5 = %d\n", 5 ^ 6 ^ 5);
	printf("6 ^ 5 ^ 5 = %d\n", 6 ^ 5 ^ 5);
	printf("6 ^ (5 ^ 5) = %d\n", 6 ^ (5 ^ 5));

	return 0;
}

        他们的结果都为6。这证明,异或是满足交换律和结合律的。接下来看一道题:

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

        我们平常交换整数时,会创建一个临时变量,来储存一个数据,比如:

int main()
{
    int a = 10, b = 20;
    int t = a;
    a = b;
    b = a;

    return 0;
} 

        这样就实现了交换,那么不创建临时变量的话该怎么做呢?这时候可以使用加法。

int main()
{
    int a = 10, b = 20;
    a = a + b;
    b = a - b;
    a = a - b;

    return 0;
}

        分析一下代码,如图: acafc4dfef8d4c1aa6520052e09e2213.png

        加法可以实现,但是在加的过程中,会有数值溢出的风险,所以需要改进。 我们可以使用异或,相关的代码如下:

#include <stdio.h>

int main()
{
    int a = 10, 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;
}

        试运行一下,得到结果: 

5066953947c14da0b524390a743e54ff.png

        我们试着分析一下,仍然是看图: 

64000a50cfdc4939ae351ec780f7fedd.png

        这么做主要是因为异或满足交换律和结合律,但是在平常使用时,还是使用创建临时变量的方法进行交换,一是代码可读性高,二是创建临时变量的话,程序运行效率会更快一点。

        操作符就先讲到这里,当然,C语言的操作符并没有完,我们下次再见。如果有什么地方出错或者有什么想法,欢迎大佬留言(o゚▽゚)o 。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值