作者:小阿斌 Scratch 格物堂投稿,原文有删减。
大家首先看一下这个scratch代码,你们来说他成立吗?
其实这个小节的主题才是笔者动笔写这篇文章的主要动机。它来源于一位群友的问题。一个非常经典的问题。
初学者会这样写脚本的原因是将数学中的习惯带到编程里来了(别问笔者是怎么知道的,问就是笔者也犯过相同的错误。。)乍一看好像没有什么问题。但是要知道,程序是一步一步执行的,像这样嵌套关系的积木,也是先执行上层的,再执行下层的(相当于数学中的括号)。3>2的结果是true(不打引号以区分字符类型),再比较true和1的大小。到这一步,不论接下去的结果如何,都应该能发现,和数学中连续比较的的3>2>1不一样,这么写是错的,是肯定无法得到预期结果的。本着 Scratch 格物堂的“格物”精神,继续往下看。true和1怎么比大小?这还不简单吗?《Scratch 3.0的大小比较是如何进行的》一文中已经说得很清楚了,字符类型的数据在参与比较运算时,比较的是ASCII码或者说是Unicode码。而数字0的十进制编号是48,A是65,a是97。就算Scratch不区分大小写,二十六个字母怎么的也是排在数字后面,字母是肯定大于数字的,“true”>1肯定是true。
看似没毛病
但是一运行整个脚本。。。
于是开始倒推哪个环节出了问题。
还是没问题,true在参与比较运算时,真的是“true”吗?我们想当然地以为true被转换成了字符类型。但不是字符类型还能是什么?在我们其它编程语言中,往往用1表示true,0表示false。
测试1:
布尔类型(true)参数参与算术运算(+空字符)
测试2:
布尔类型(false)参数参与算术运算(+空字符)
这两个小测试足以证明,true被转换为数字类型时,会被转换为1;false在被转换为数字类型时,会被转换为0.在上文中的比较运算符的连续嵌套使用示例中,第二次其实是在比较1>1,结果自然是false了。至于为什么“气泡输出”会失效,这是因为“说xxx”这类积木会将参数转换为字符类型。
实例
也许使用布尔值参与比较运算的确实用价值不高,一般在案例实战中不会这么用,仅仅需要了解其原因即可。但是笔者的的确确见过一种使用布尔值参与算术运算,巧妙利用数据类型隐式转换这个特点的案例。下面和读者朋友们分享一下。
一起盗窃案,有4名嫌疑人。这4人只有一名小偷。已知,这4名嫌疑人中,有且仅有1人在说谎。
首先用脚本把4个嫌疑人的供词用逻辑表达式表示出来:
1号:“不是我。”
2号:“小偷是3号。”
3号:“小偷是4号。”
4号:“3号在说谎”
接着根据条件“4个嫌疑人中有且仅有1人在说谎”,可以推导出结论:以上4个逻辑表达式中,有3个是成立的,有1个是不成立的。再结合上文中get的新技能:布尔值在被转换为数字类型时,true会被转换为1,false会被转换为0,不难得出以下结论:
4名嫌疑人中,有且仅有1人在说谎,最后要做的就只有一件事了:遍历,从小偷=1开始,逐个验证即可。
总结
如果用哲学中的矛盾观来分析数据类型的隐式转换这个问题的话,积木本身的种类、本身的参数类型是主要矛盾,居于主导地位,它决定数据将会被转换成什么类型;而参数的数据类型则是次要矛盾,位居从属地位。如果积木本身的参数类型与实际参数类型不匹配,则会优先尝试将其转化为本身的参数类型,同时兼顾地“考虑”一下参数本身的“想法”,看看它是否“愿意”被转换为那种类型,若“不愿意”,则另做打算。
Scratch数据类型的隐式转换这个问题,笔者秉持辩证的观点来看待。
首先这样无疑对新手来说无疑是十分友好的,易上手。否则动不动地给你报个错,如果对Scratch的数据类型不够了解的话,几乎解决不了。那么现有的教学知识体系中又要多一块庞大却又对逻辑思维能力的提升不大且极为枯燥的内容了,这绝对是在“劝退”初学者。另一个优点就是,只要你的想象力足够丰富,就能利用这个特点做出精妙的案例(前提是你要清楚地知道自己在做什么)。
但是隐式转换带来的负面影响也同样地明显。相比强类型的语言,Scratch在数据类型这方面来说显得不够严谨。其次,和其他弱类型语言一样,隐式转换总是被打上“它们的存在将导致错误的发生”的标签。一如上文中的3>2>1。Scratch数据类型隐式转换所造成的BUG数不胜数,细分下去种类繁多。并且就像他的名字一样,十分隐蔽,极难排查,即使像上文中那样运用一般的debug方法,也很有可能会因为再次发生隐式转换而失效。