算数操作符
说起算数操作符,大家应该都不陌生。它就是我们日常使用的加减乘除,再加上一个取模。它们分别为
+ - * / %
加减乘我不再讲述,它和日常使用相同。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,第二个才是小数。
除法在C语言中分为小数除法和整数除法两种,具体是哪种除法是有除号两边的操作数决定的,当两边的操作数出现浮点数时,就是小数除法;否则默认是整数除法。
取模也可以理解为取余。它与整数除法息息相关,模10的意思便是一个数除以10后的余数。所以,%两边的操作数的数据类型必须是整型,否则会出错。我们试着运行以下代码
虽然不美观,但也能看。图左边是示例代码,右边是计算结果。由这些可以看出,取余运算就是基本的整数除法,它的结果是除法后的余数,结果的符号则与被除数的正负一致。
移位操作符
移位操作符,移动的是二进制位。在讲解这个操作符之前,我们先简单了解一下进制之间的转换。
数据在计算机中是以二进制的形式存储的,这里我们以整数为例。将十进制整数转换为二进制的方法是:对十进制整数不断除2,取其余数,直到商为0时,接着将每一步的余数进行排列,便得到相应二进制数。以10为例,请看图:
首先,10除以二,商5余0;接着5除以二,商2余1;2除以二,商1余0;最后,1除以二,商0余1。然后对余数进行排列,10的二进制数是1010。同样的方法,5的二进制数是101。
弄清楚10进制转化成2进制后,我们来看一下二进制如何转换成10进制,看图:
方框中的数字是二进制数,方框是二进制位。而绿色数字是二进制位的权重。就像在十进制中,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位):
而计算机存储的就是二进制数的补码。不过补码转换成原码的方法也很简单:如果是正数的话,我们直接可以得知原码;如果是负数,有两个方法:一、将补码减一后得到反码,然后符号位不变,其他位按位取反得到原码。二、补码符号位不变,其他位按位取反,之后再加1,便得到原码。例:
数据存储就此结束,接下来是移位操作符登场时间。首先登场的是,左移操作符:<<。
左移操作符
左移操作符的基本规则为:左边抛弃,右边补零。
如图:
这就是左移操作符的效果,但请注意,对一个变量进行左移操作并不能直接改变这个变量的值,就像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;
}
运行结果:
上面这张图已经说明了一切,已经不需要赘述了。不过,可以发现,左移一个单位结果相当于给变量乘以2。实际上,左移几个单位,就是给变量乘以2的几次方,读者可以自行尝试,注意不要超出数据类型的取值范围。负数也是同样的效果。
左移操作符到此结束,接下来便是她的姐妹,右移操作符。
右移操作符
右移操作符有点复杂,她分为算数右移和逻辑右移,具体是哪种右移方式由编译器决定,但现在一般都是算数右移,但逻辑右移也该有所了解。两种右移方式规则如下:
逻辑右移:右边抛弃,左边补0。
算数右移:右边抛弃,左边补符号位。
具体的可以看上图,接下来我们试着使用代码验证一下:
#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;
}
由结果可以看出,作者使用的VS2022是算数右移。如果num为5,我预测结果会变为2,我们试运行一下,结果如下:
由此可以看出,右移和左移一样,并不能直接修改变量,需对变量进行赋值才能达到修改的目的。
在这里我们需要注意一点,对于移位操作符,移动的位数必须是整数。并且,移动负数位或者移动位数过大的话,这种行为是未定义的,编译器会警告,但不报错,谁知道会出现什么结果。而移动小数位,则会直接报错,看下面代码:
#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,且对两个移位进行了警告:
当其移动小数位时,直接进行报错:
所以使用时千万要小心。
位操作符
位操作符和移位操作符相同,都是对二进制位进行处理,使用时注意一点,计算机使用补码储存,所以位操作符进行计算时,也是通过补码进行计算。位操作符有三个:|(按位或)、&(按位与)和 ^(按位异或)。
首先,他们的操作数必须是整数。看下面一个代码:
#include <stdio.h>
int main()
{
int num = 10;
num | 1.0;
num & 1;
num ^ 1;
return 0;
}
编译器报错,因为|右操作数类型为double。
讲清注意事项后,来看一看各操作符的作用。首先是按位或:
|(这并不是字母):
按位或便是将两边操作数对应位进行或运算。 同为0时为0,其余情况为1。具体看图:
我们使用代码验证一下:
#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;
}
结果如下:
按位或就此结束,接下来是按位与:
&:
按位与是将两边操作数对应数位进行与运算,即都为1时结果为1,其余情况结果为0。具体看图,我们依旧使用上面两个数:
使用代码验证一下:
#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;
}
看来手动计算并没有错,读者可以自己试试。注意:运算的是补码,结果也是补码!
^:
好的,终于见到异或了,这个和数学次方一模一样的符号在C语言中又有什么作用呢?它的作用很好记忆,相同为0,不同为1。我们还是以那两个数为例:
来,代码走起:
#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;
}
结果如下:
根据异或的计算规则,我们可以思考一下,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;
}
分析一下代码,如图:
加法可以实现,但是在加的过程中,会有数值溢出的风险,所以需要改进。 我们可以使用异或,相关的代码如下:
#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;
}
试运行一下,得到结果:
我们试着分析一下,仍然是看图:
这么做主要是因为异或满足交换律和结合律,但是在平常使用时,还是使用创建临时变量的方法进行交换,一是代码可读性高,二是创建临时变量的话,程序运行效率会更快一点。
操作符就先讲到这里,当然,C语言的操作符并没有完,我们下次再见。如果有什么地方出错或者有什么想法,欢迎大佬留言(o゚▽゚)o 。