挑战TJS Section1.6:操作符

正篇

Yuu:上一回,我们使用了System类的inputStirng()方法,不过我们写的脚本得到的内容嘛…

Yuni:即使输入的是一个数字,得到的也是一个字符串。一次我们必须将得到的字符串转成数字。

Yuu:没错。不过将字符串转成数字是很容易的。

Yuni:嗯?真的吗?

Yuu:嗯,因为有将字符串转成数字的操作符。顺便提一句,操作符是一种符号,我们用它来进行计算。

Yuni:那,加法“+”是操作符吗?

Yuu:没错。TJS中有很多操作符,所以这次我们介绍一些基本的操作符,其中就包括了类型转换操作符。

Yuni:好。

Yuu:首先是“=”。这被称为简单赋值操作符(英:Simple Assignment Operator,日:単純代入演算子)译者注:等号在这里的含义是赋值,将等号右面的当前值赋予等号左边内容(通常是变量)。注意这个符号不是等于比较的含义。

/简单赋值操作符/

var a, b;   // 定义变量a、b
a = 1;      // 将等号右侧的值(1)代入等号左边(变量a)
b = 1 + 2;  // 将等号右侧的计算结果(3)代入等号左边(变量b)

Yuni:嗯。我们用了好几次了,所以大家也都很熟悉他们了。

Yuu:下一个运算符是“+”。这是一个加法运算符(简称加号,英:Addition Operator,日:加算演算子),用于数值相加和字符串连接的操作符。如果加号左右都是数值,得到的结果是两者的和;如果加号左右至少有一个是字符串,得到的结果就是连接后的字符串。

/加法运算符/

var a, b, c;
a = 4 + 8;            // 变量a的结果是4+8的和为12
b = "T" + "J" + "S";  // 变量b的值是T、J、S连接在一起的字符串“TJS”
c = "1+1等于" + 2;      // 数字2会转成字符串“2”,然后与前面的“1+1等于”连接为“1+1等于2”,并赋值给变量c

Yuni:嗯,没问题

Yuu:下面是“-”。这是个减法运算符(简称减号,英:Subtraction Operator,日:減算演算子),用于计算减法。这里的减号和四则运算的减号含义相同,减法得到的结果是减号左边的值减去右边的值。如果减号左右存在字符串,那么这个字符串会先转成数字再完成减法。译者注:这种转换是比较“危险”的,因为它可能产生意想不到的结果。如果字符串本身就是合法的数值,转换不会有问题。如果字符串包含非数字内容,转换并不会产生错误,但得到的结果不一定是我们想要的。

/减法操作符/

var a, b;
a = 100 - 99;  // 变量a的值是100-99的差为1
b = "10" - 2;  // 字符串的值是“10”,会先转换为数值10,再做减法,得到的结果8代入变量b中

Yuni:变量a的值很容易理解因为它就是普通的减法运算。但是…变量b的那句就让人迷糊了。如果操作数存在字符串,会先转换成数值…译者:这和加法是完全相反的好不好…

Yuu:嘛…加法其实是最特殊的,乘除法在这上面的规定都和减法相同:出现字符串的话会先转成数值。

Yuni:这样的吗?

Yuu:嗯。字符串的减法、乘法和除法没有意义。

Yuni:你这么说也没问题。

Yuu:所以只有加法才有将数值转成字符串的情况。

Yuni:Ok了解。

Yuu:好,下面是“*”。这是乘法操作符(简称乘号,英:Multiplication Operator,日:乗算演算子),用来计算乘号左右的乘积。同样地,如果其中一个操作数是字符串,它会先被转换成数值。

/乘法操作符/

var a, b;
a = 3 * 6;     // 变量a是3×6的计算结果等于18
b = "20" * 5;  // 字符串“20”先转换为数值20,然后乘5,得到100,赋值给变量b

Yuni:这就是普通的乘法啊。

Yuu:我想也是。下面是除法运算符(简称除号,英:Division Operator;日:除算演算子),用于计算两个数的商。有两种除号“/”和“\”,它们都是计算除号左边的数除以右边的数的商,不过“/”得到是实数类型;而“\”得到的是整数类型。译者注:在其他语言中,“/”称为浮点除,“\”称为整除。和四则运算的规则相同,除数不能是0.

/除法操作符/

var a, b, c, d;
a = 10 / 4;    // 变量a的值是10÷4的结果为2.5
b = 10 \ 4;    // 变量b的值是10÷4的结果的整数部分,为2
c = "15" / 5;  // 字符串的值“15”先转换为数值15,然后再做除法,得到结果3赋值给c
d = "15" \ 5;  // 字符串的值“15”先转换为数值15,然后再做除法,得到结果3(整数部分就是3)赋值给c

Yuu:对于“\”,得到的商是整数类型,因此小数部分被截尾。

Yuni:是截尾而不是四舍五入?

Yuu:对。因为小数部分总是被截掉,因此即便原本的结果是2.99999999999,得到的也是2. 并且,负数也遵循这样的原则,-10 \ 4的结果是-2.5,截尾后是-2.

Yuni:嗯嗯。

译者注:到此,四则运算都介绍完了。下面介绍的运算符是程序语言中普遍定义的运算。

Yuu:下一个是“%”。你可以称它是取模操作符(英:Modulo Operation,日:剰余算演算子),用于计算取模操作符左边与右边作商后的余数。译者注:由于取模运算本身基于除法,因此除数不能是0。

/取模操作符/

var a, b;
a = 10 % 4;    // 变量a是10÷4的余数,为2
b = "10" % 5;  // 字符串的值”10“,先转换成数字10,然后计算10÷5的余数是0,并代入变量b中

Yuni:取模操作的对象都是整数。译者注:如果操作数有小数,它会被先截尾转成整数,再参与运算。

Yuu:没错。另外,加减乘除和取模操作符(+、-、*、/、\、%)都可以和赋值号“=”组合成组合赋值运算符(英:Compound Assignment Operator)

/组合赋值运算符/

var a = 3, b = 3, c = 3, d = 3, e = 3, f = 3;
a += 2;  // 等价于a = a + 2,a的值是5
b -= 2;  // 等价于b = b + 2,b的值是1
c *= 2;  // 等价于c = c * 2,c的值是6
d /= 2;  // 等价于d = d / 2,d的值是1.5
e \= 2;  // 等价于e = e \ 2,e的值是1
f %= 2;  // 等价于f = f % 2,f的值是1

Yuu:举个例子,你可以使用“+=”运算符快速地将a=a+b写成a+=b.

Yuni:这是个还挺方便的运算符。译者注:组合赋值运算符的计算方式是:先计算赋值号右边的值,然后按照a = a op b的形式计算新的等号左值。

Yuu:+=-=从某种程度上很像接下来要介绍的“++”、“–”。它们分别被称为自增运算符自减运算符(英:Increment/Decrement Operator,日:インクリメント演算子/デクリメント演算子),用于对某个变量进行加1和减1的操作。等同于a += 1a -= 1

/自增/自减操作符/

var a = 3, b = 3, c = 3, d = 3;
++a;  // 前置自增操作符,等同于a = a + 1,a的值是4
b++;  // 后置自增操作符,等同于b = b + 1,b的值是4
--c;  // 前置自减操作符,等同于c = c - 1,c的值是2
d--;  // 后置自减操作符,等同于d = d - 1,d的值是2

Yuu:对于“++”和“–”,数值可以在操作符的左边也可以在右边。在数值左边的“++”、“–”被称为前置自增/自减操作符;相反的,在数值右边的“++”、“–”被称为后置自增/自减操作符。

Yuni:在上面的例子中,它们都产生了相同的结果,那前置和后置的差别是什么?

Yuu:在下面的例子中,前置和后置将产生不一样的结果。

/前置后置自增自减运算符的差异/

var a = 3, b = 3, c = 3, d = 3;
System.inform(++a);  // 前置自增操作符:a先加1得4,再显示a(4)
System.inform(b++);  // 后置自增操作符:先显示b(3)之后b再加1
System.inform(--c);  // 前置自减操作符:c先减1得2,再显示c(2)
System.inform(d--);  // 后置自减操作符:先显示d(3)之后d再减1

Yuu:对于前置的(在数值左边)自增自减运算符,数值总会在它被使用前自增自减;对于后置的(在数值右边)自增自减运算符,数值总会在它被使用后自增自减。

Yuni:这听起来很难被使用啊…

Yuu:但是在这之后它还是很常用的,所以我觉得还是要适应它的使用。

Yuni:是吗?

Yuu:嗯。下一个是将字符串转成数值的“+”运算符!

Yuni:诶?“+”运算符不是用来进行加法的吗。

Yuu:实际上,有两种长得都是“+”的运算符。一个是加法,另一个是将字符串转成数字的。

Yuni:额,这一点也不好。“+”出现的时候你怎么知道是哪种运算符?

Yuu:嘛…加号是一个二元运算符(英:Binary Operator,日:2項演算子),而字符串转数字是一个单目运算符(英:Unary Operator,日:単項演算子)。

Yuni:什么是单目运算符和双目运算符?

Yuu:双目运算符是拥有两个操作数的运算符,通常两个操作数一个位于符号的左边一个位于右边。单目运算符只拥有一个操作数,操作数在符号的一边,具体是哪一边取决于符号的定义。像这样。

/“+”的单目运算与双目运算/

var a, b;
a = "1" + "2";  // 这里“+”的左右都有操作数,是双目运算符,a的结果是12
b = +"1";       // 这里“+”的左边没有操作数,是单目运算符(字符串转数值),b的结果是数值1

Yuni:没错。如果想将字符串转成数值,“+”的左边就没有操作数。

Yuu:没错,这是我们能分辨的原因。

Yuni:那,我们可以像自增自减运算符那样,把这里的“+”放到数值的后面,像这样a = "1" +;吗?

Yuu:不可以的。自增自减运算符在哪个边都可以,但是单目的“+”必需出现在字符串的左边。

Yuni:那我们怎么知道运算符应该是在左边还是在右边?

Yuu:单目运算符既可以在左边可以在右边,还可以两边都可以,这取决于运算符。因此我们无法仅凭运算符的类型知道。译者注:通常意义上,我们认为前置和后置自增自减是不同的运算符。因此,一个运算符只能出现在操作数的一侧。

Yuni:哦好吧。

Yuu:你可以记住一些常见的运算符的结合性。如果你不了解一个运算符的话,你可以去阅读TJS2文档中的“式と演算子”部分。

Yuni:好我知道了。

Yuu:此外,“-”也是一个值在右侧的单目运算符,你能猜出什么吗?

Yuni:额…

Yuu:像a = -1;这样。

Yuni:这是个可以取负数的运算符。

Yuu:没错。准确的来说,它是一个取相反数的运算符。

Yuni:嗯嗯,如果a是-1,-a就是1。

Yuu:没错。另外还有一些可以改变数据类型的单目运算符,这里也来介绍一下。

Yuni:好。

Yuni:它们是int操作符,用来转换成整数。real操作符,用来转换成实数。string操作符用来转换成字符串。它们的使用方式如下:

/类型转换操作符/

var a, b, c;
a = int "123";   // 字符串“123”转成整数123
b = real "2.6";  // 字符串2.6转成实数2.6
c = string -20;  // 数值-20转成字符串“-20”

Yuni:这和单目运算符“+”是一样的,数值在运算符的右边。哦对了,int和real操作符也可以将字符串转成数值,它们和单目的“+”有什么区别呢?

Yuu:intreal分别转成整数和实数,不论操作数是什么样的;而“+”可以根据操作数的内容动态选择转换的类型。如果字符串内容是个整数,转换的结果就是整数;如果内容是小数,转换的结果是实数。

/字符串转成数字的运算符差别/

var a, b, c, d;
a = int "1.2";  // 1.2的整数部分1是a的值
b = real "3";   // b的值是实数类型的3
c = +"1.2";     // “1.2”是小数形式,转成的数值是实数1.2
d = +"3";       // “3”是整数形式,转成的数值的整数3

Yuni:明白了。这看上去有点难以区分呢…

Yuu:嗯,当需要转换成数值的时候,你可以通常使用“+”操作符,除非你需要指定结果的类型。

Yuni:哦,这样好记多了。

Yuu:嘛,最后一个是typeof操作符。这也是个单目运算符,它告诉我们它右值的类型。

/typeof操作符/

var a = 1, b = 0.1, c = "abc", d = new Date(), e;
var A, B, C, D, E;
A = typeof a;  // 由于a是整数类型,A的值是“Integer”(表示整数)
B = typeof b;  // 由于b是实数类型,B的值是“Real”(表示实数)
C = typeof c;  // 由于c是字符串类型,C的值是“String”(表示字符串)
D = typeof d;  // 由于d是对象类型,D的值是“Object”(表示对象)
E = typeof e;  // 由于e没有被任何实体赋值,E的值是“void”

Yuu:由于TJS中有多种类型的变量,因此如果你需要知道当前变量的类型,它会很有用。

Yuni:哈,这也算是操作符啊。

Yuu:还有不少操作符没有介绍呢,不过这次就先介绍这些了。不过这回解决了字符串转数值的问题,下回我们继续1.5节的脚本。

Yuni:要继续啦…

Yuu:嗯,这次介绍了不少运算符的内容,下次就会关注脚本的编写了。

Yuni:okay

Yuu:嗯,下回见啦。

要点

  1. 运算符可以根据操作数的个数分为单目运算符和双目运算符。单目运算符只有一个操作数,可能出现在符号的左边或右边。双目运算符拥有两个操作数,它们通常一个在符号左边一个在符号右边。符号左边的称为左值,右边的称为右值。

  2. 赋值符号用“=”表示,赋值方向是从右向左。也即先计算赋值号右边的内容,然后把结果代入左边的内容(通常是变量)。这里,左值必须是一个可修改的内容,字面常量不可以作为左值。2 = 4是不合规的。赋值号并不是用来判断相等的。赋值符号是一个双目运算符。

  3. 四则运算:加号。加号完成数值加法之外,还可以连接字符串。加号是一个双目运算符。如果其中一个操作数是字符串,得到的结果就是字符串。非字符串的操作数会自动转换成字符串。

  4. 四则运算:减号、乘号。这两者都是完成普通四则运算中的减法和乘法。如果操作数的其中是字符串,它会先被转换成数值。字符串的转换方式是,从第一个字符开始转换,直到无法转换为止,中间已经转换的值就是它的值。这一点和PHP很相似。例:“152”->152、“15qwer”->15、“qwer15”->0.

  5. 四则运算:除法。除法的定义和四则运算的除法相同,如果操作数存在字符串也是先转换成数值。除号有两种,一种是浮点除“/”,得到商的真实值;另一种是整除“\”,得到商的整数部分。和四则运算相似,除数不能是0.

  6. 取模运算。这个就是求两个数的余数用的,也是双目运算符。和上面不同的是,它的两个操作数都必须是整数。如果不是,将会被转换成整数。取模运算在实际编程中出现频率还是比较高的。

  7. 单目运算符“+”和“-”。这两个符号都出现在操作数的左侧。“+”本身的含义是保持一个数字的本身数值,它经常用于将字符串转成数值,并且这种转换是考虑内容的。小数会转换成实数类型,整数会转换成整数类型。“-”本身的含义是取相反数,如果它后面接的是字符串,字符串会和“+”一样转换成对应的数值,再取相反数。

var a = 3.5, b = "3.5";
System.inform((+a) + " " + (+b) + " " + (-a) + " " + (-b));
  1. 组合赋值运算符:这种是一个运算符和赋值号结合的形式,对于加法来说,a += ba = a + (b)的简写形式。这里把b用括号括起来的原因是执行顺序上,右侧的部分会先执行,得到一个值再展开成原始赋值的形式。如果考虑到运算的优先级,a *= 5 + 2等价于a = a * (5 + 2)而不是a = a * 5 + 2.

  2. 自增自减运算符。这个和C里面自增自减是一样的。自增自减本身可以表达为a += 1a -= 1。因此,自增自减的运算对象是出现在赋值号左边的,它只能是变量,常量不可以(像++3)。自增自减符号可以放在操作数的左边或右边。放在左边的称为前置自增自减,放在右边的称为后置自增自减。前置自增自减在使用这个变量的值之前完成自增自减,后置自增自减在使用这个变量的值之后完成自增自减。自增自减在使用上建议单独出现,不要放在复杂的表达式中,否则容易出错,虽然这种是C语言中无聊的常见考题。例如a+=++i+j++这样。

  3. 类型转换运算符。其实就是在要转换的内容前转换后的类型名称。虽然类型有6个但是转换符只有3个:intrealstring。例如int "123"string 3.5。这种转换时类型指定的,这意味着如果指定了整型,即使数值时小数也会转成整数,例int "3.5"等于3而不是3.5。这一点是它和单目运算符“+”的区别。

  4. typeof运算符。这个运算符可以得到操作数的类型,是一个字符串。类型与typeof的对应关系:

类型 对应字符串
void void
整数 Integer
实数 Real
对象 Object
字符串 String
八位二进制 Octet
指定的对象成员不存在(第二章会讲解) undefined

拓展

优先级

虽然正篇中没有直接提及,但是我们都知道运算符本身是具有优先级的。比如乘除优先于加减执行。同一级不同运算符还存在着执行的顺序,有的是从左向右,有的是从右向左,这一点我们成为结合性。

简单总结下本次出现运算符的优先级。这个规律只对本次出现的运算符有效。

单目运算符 都大于 双目运算符
乘除+取模 都大于 加减

单目运算符之间都是右结合的,也就是从右向左执行;双目运算符都是左结合的,也就是从左向右执行。

举例:

1 * 2 + 3 % 4 => (1 * 2) + (3 % 4)
1 * 2 % 3 + 4 \ 5 => ((1 * 2) % 3) + (4 \ 5)
a++ * 2 + -b => ((a++) * 2) + (-b) => a * 2 + (-b), a+=1

可以看到,最后一行这样的写法是正确的,但是并不那么易懂。为了避免潜在的错误,建议使用括号指定运算顺序。

发表评论