⭐ 1.2 浮点数(下)¶
休息好了吗?那我们继续吧。
二元运算符¶
数字的部分,我们已经讲得够多了。但如何用它们进行计算呢?还是0.1+0.2的那个例子:
结合我们现有的知识,从直观上理解,这句话应该是想运用控制台Console的成员WriteLine来把0.1 + 0.2的计算结果显示在你的屏幕上。
进一步分析一下加号+在这里的作用:首先它的左右两侧各有一个数字,然后计算两个数字的和,最后把和提供给WriteLine()。简单到有点啰嗦了是吗?但这就是“二元运算符”的工作原理,“二元”表示运算符可以操作两个数字(对象)。
上面这样的写法其实干了同样的事情,只不过把0.1和0.2分别储存进了变量a和b中,然后用二元运算符+计算它们的和,把和提供给了变量c,最终在屏幕上显示c。
弄明白了加法,其他二元运算符也是一样的:
double a = 0.1;
double b = 0.2;
//减法
double c = a - b;
Console.WriteLine(c);
//乘法
c = a * b;
Console.WriteLine(c);
//除法
c = a / b;
Console.WriteLine(c);
在这里还要隆重介绍一种生活中不太常见,但编程中常用的运算:求余数。比如10÷3=3……1,在这里的1就是余数。用C#表示:
%就是求余数运算的符号。试一下结果到底是不是1吧!
Info
求余数又叫做“取模”。
运算顺序¶
一个表达式也可以包含多个运算符,计算的顺序遵循你学过的数学知识。括号()的存在会改变运算的优先级。
猜猜答案是多少?运行一下,看看结果和你预测的是否一样!更详细的运算符优先级介绍请查看这里。
二元运算符两边是不同类型的数值怎么办,比如0.1f + 1?编译器会隐式转换为合适的类型!具体的规则请点击这里查看。
题外话
有些“手册性质”的知识讲起来比较啰嗦,本教程不会事无巨细地展示它们,而是像上面那样给出文档链接。如果你需要了解,可以打开链接查看。
赋值运算符¶
赋值的符号=是一个二元运算符吗?毕竟它的左右两边也是数字或变量!按照二元运算符的原理,它应该对符号左右两边的数进行操作,然后把结果提供给别人。赋值确实对左右两边的数进行了操作——把右边的数值赋予了左边的变量,但它有提供一个结果吗?测试一下:
多么奇怪的写法!但确实输出了一个数:2。这说明赋值运算符作为一个二元运算符,它把右边的值赋给左边之后,还会把这个值作为运算的结果提供。这样做的好处是,我们可以使用连续的赋值:
在上面的例子中,会先把10赋值给变量c,然后赋值运算的结果10再赋值给变量b,第二次赋值运算的结果10再赋值给变量a。哈哈,像俄罗斯套娃!
复合赋值¶
最后,像a = a + b;这样的算式,可以写为a += b;。这叫复合赋值,也就是说,这种符号把赋值运算和其他运算给结合了。
我们一步一步分析:首先+=是一个二元运算符,它的左右两边都有数字或变量(或者叫“操作数”)。然后+开始发力,把左右两边的值加起来,接着=把运算的结果赋值给左边的变量。
同理,还有-=、*=、/=和%=等等符号,只要稍微类推一下,你就能知道是什么意思了。
一句话总结
先运算,再赋值。
表达式?语句?¶
那又要问了,二元运算符只运算,不把计算结果提供给谁行不行呢?
合理使用空格
在操作数和运算符之间留一个空格可以增加可读性。
出现了一个错误 ❌CS0201:只有 assignment(赋值)、call(调用)、increment(自增)、decrement(自减) 和 new 对象表达式可用作语句。
哦!看来单纯的加法运算不是一个完整的语句。错误信息里面提到了5种可以被视为完整语句的情况。赋值表达式可以作为一个语句,难怪我们直接写a = 1;的时候没有报错。调用就是像Console.WriteLine(0.1 + 0.2);这样,使用一个方法。new我们暂且不提。自增和自减则属于我们接下来要说的——一元运算符。
Info
在1.3节有更详细的关于语句和表达式的介绍。
一元运算符¶
首先来看+和-。当这两个符号被用作二元运算符的时候,可以执行加法和减法运算。当作为一元运算符的时候呢:
使用起来非常符合我们的预期——正号+直接把右边的操作数作为运算的结果——相当于什么都没做!负号-呢,则是将右边的操作数的相反数作为计算结果。嗯!很容易理解。
那两个加号++和两个减号--呢?我们要分两种情况来看。当操作数在运算符的左边时,++运算符会把它左边的数值增加1、--运算符则是减少1。因此,我们把++叫做自增运算符,--叫自减运算符!然后,把增加1或者减少1之前的数值作为运算的结果提供。请看下面的例子:
首先我们让变量a等于6,然后用++运算符对a进行操作:先让a增加了1,也就是变成7了。然后把增加之前的a的值,即6,作为结果提供给变量b对不对?因此a是7,b是6。
需要注意的是++a,也就是操作数在运算符的右边这种情况。++运算符依然会把它右边的数值增加1、--运算符也是减少1。但是,会把增加1或者减少1之后的数值作为运算的结果提供。
首先我们让变量a等于6,然后用++运算符对a进行操作:先让a增加了1,也就是变成7了。然后把增加之后的a的值,即7,作为结果提供给变量b!因此最终a和b都是7。可见++a和a += 1以及a = a + 1的效果是一样的!这就是不同之处。
记忆助手
a++ → a在++的前面 → 结果是增加前的a
++a → a在++的后面 → 结果是增加后的a
只影响自增运算的结果。无论如何a都会增加1。
只需类推一下,你就明白--运算符是如何使用的了!只不过是把增加1变成减少1而已!最后提醒一下,和赋值=一样,++和--运算的结果也可以不提供给谁,单独作为一个语句,这一点在前面提到的错误CS0201中也说明了。
关于返回值¶
讲到这里,也许有必要澄清一下。我们说运算符的原理就是对操作数进行一些处理,得到一个结果,然后把结果提供给别人。这个“别人”有可能是一个变量,比如int b = ++a;;也有可能是一个方法,比如Console.WriteLine(++a);。有时候不提供给谁也没问题,比如a++;。我知道诸如“别人”这样的表述不太严谨,但我现在更想说明的是“结果”和“提供”。
假设仓库里有某样货物a,库存有100件。想象一下你作为老板,当你写下int b = a++;这行代码时,你其实派了你的员工——a++表达式去检查变量a,员工先把a的数量100记录下来,然后把a的数量加1件。完成工作后,兢兢业业的员工回来报告它记录的数量——100,接着就开心地下班了(我滴任务,完成辣!)。最后100被写到账本b上面;与此同时,仓库里有101件货物a。
如果你写的是a++;呢?那么你忠诚的员工a++表达式跑去仓库里记住了a的数量,然后把a增加一件。干完这些以后,它兴冲冲地跑回来报告任务完成,却发现无人在意a本来的数量是多少,于是失落地下班了。
好吧,总之,你的员工在完成工作之后回来向你复命的过程就叫做 “返回”(Return),员工向你报告的那个结果叫“返回值”。有时我们只需要员工报告任务完成而不需要报告结果,那么返回值就会被丢弃。也有的员工干完活后只会报告一声任务完成,而没有返回值。对于员工来说,在返回之后就代表着它们可以下班了。
Note
编程中的“返回”(Return)和返回选项的“返回”(Go Back)是两个不同的概念哦!
最后的最后,完成几个挑战吧!
挑战1
为什么
的结果是0.6000000000000001而不是0.6?
为什么
的结果是0.9999999999999999999999999999而不是1?把上例第一行的1m / 3m改为1 / 3会怎么样?
查看答案
第1问:0.1属于二进制浮点数double类型,进制转换会带来误差。
第2问:高精度浮点数decimal处理十进制无限循环小数时会截断,从而带来误差。
第3问:1m / 3m是浮点数之间的除法,结果是浮点数0.333……。而1 / 3是整数除法,结果是整数0。
挑战2
编写代码,计算 (0.25+2)×(6.5-3)÷(-7) 。
挑战3
今天是星期日,1387天后是星期几?请编程计算。