用实例说明类型系统

2020-09-29 10:37:23

“我的语言更好,因为它有很强的类型系统!”你的同事开发人员戴夫(Dave)尖叫着,试图将编程语言Cobol推向你们公司的下一项微服务。

在开发人员中,关于编程语言及其类型系统的讨论可能很快就会变得情绪化。在这些讨论中,我们经常抛出“类型系统”、“数据类型”、“类型推断”、“静态类型”、“弱类型”、“强制”等词。但是,我们真的理解所有这些术语的含义吗?

然而,理解我们正在使用的语言的类型系统是最原始的。这就是本文的目标;我们将看到:

我将使用两种语言来说明不同的概念,Golang和PHP。如果你不认识他们,别担心!这些例子简单明了,易于理解。

我鼓励你在阅读的时候使用一些在线的PHP解释器和围棋操场,自己玩和试验。这将帮助你理解并帮助你把所有这些好的知识放入你的长期记忆中。

我不会在这里详述血淋淋的细节。正如您将看到的,很难概括特定于编程语言的东西。不过,本文将很好地概述大多数类型系统的常见属性。我们会从清澈的水逐步走到沼泽,所以带上你的橡皮靴,我们走吧!

类型系统由类型组成。我知道,这太让人兴奋了。这些类型也称为数据类型。他们是什么?。

首先,让我们澄清编程语言的语法和语义之间的区别。

例如。你母语的句法是一套规定句子结构的规则。对于许多口语来说,你需要一个主语和一个动词才能使你的句子正确。相反,编程语言具有不同的结构,如表达式、控制结构或语句。

例如,要使PHP中的if语句语法正确,您需要:

另一方面,语义是构式背后的意义。例如,IF语句的语义可以解释如下:

为什么我们需要语法和语义?它旨在与两种不同类型的实体进行通信:

回到我们的主题,类型可以将语义附加到数据。例如,在PHP中:

在数学中,65是整数集的一部分,因此PHP将值65视为整数类型。您的变量$INTEGER也具有值65,因此它也是一个整数。

当您将65赋给变量$INTEGER时,就为它赋予了我们在第二行声明它时没有的语义。此语义将让您知道可以对该变量执行哪些操作。

类型是一组值。对于整型,它将是一个小数范围。对于类型字符串,它将是一系列可能的字符串。

当您声明一个变量并为其赋值时,内存将以二进制形式保存它的值。我们的计数系统是十进制的,这意味着我们每天知道和使用的数字在二进制中有很大的不同:

<;?php$INTEGER=65;//十进制表示法printf(";%d的二进制表示法:%b\n";,$INTEGER,$INTEGER);//=65:1000001的二进制表示法。

您的同事开发人员Dave在阅读这些行时充满了疑问。“像‘A’这样的角色是如何保存在记忆中的呢?”他想知道。“这不是一个数字!它不能用二进制表示!“。

戴夫是对的。按照ASCII标准,必须首先将字符转换为十进制数。然后,该ASCII码被保存在存储器中。

<;?php$INTEGER=65;printf(";二进制表示形式:%b\n";,$INTEGER);//=>;二进制表示形式:1000001$CHARACTER=';A';;$ASCII=ORDER(';A';);//';A';的ASCII代码。Printf(";ASCII代码:%d\n";,$ASCII);//=>;ASCII代码:65打印f(";二进制表示:%b\n";,$ASCII);//=&>;二进制表示:1000001。

整数65和字符串‘A’在内存中具有相同的二进制表示!

戴夫很困惑。他绝望地问天空:“见鬼,我们的程序怎么知道$INTEGER等于65,$CHARACTER等于';A‘?怎样做?“。实际上,在内存中,$CHARACTER和$INTEGER这两个值完全相同:1000001。当您在代码中使用这两个变量时,语言的类型系统将解释内存中的这两个值,它将决定什么是字符,什么是整数。

理解这一点很重要,因为这种解释对于某些类型(如浮点数)并不总是准确的。

类型还将决定如何在内存中存储值。对于字符和整数,我们看到它们的存储方式相同。例如,存储浮点数的方式就非常不同。

对于开发人员来说,类型系统很好地抽象了内存存储,不需要考虑这些混淆的0和1,这样您就可以专注于更重要的问题。至少在抽象不泄露的情况下是这样。如果您不确定什么是抽象,我写了一篇关于它的文章。

类型系统也是一组规则,或多或少都很严格。你不能对某些类型做任何你想做的事。

<;?PHP printf((3+";Hello World!";)。";\n";);//=>;PHP警告:在/home/myusername/phpgooies/test.php第3行//=>;3printf(";执行继续!";);//=>;继续执行!

这段代码没有什么意义。我试图通过将整数3加到字符串上来重新发明数学。PHP将抛出警告,但仍会给出结果3.当您违反类型系统的规则时,结果可能介于这两个极端之间:

在我们的示例中,PHP将抛出警告,但执行仍将继续。您甚至可以消除臭名昭著的php.ini中的警告。

为了与另一种语言进行比较,让我们以Golang中完全相同的内容为例:

包主要导入(";fmt";)func main(){fmt.。Println(3+";Hello World";)//=>;无效操作:3+";Hello World";(不匹配的类型非类型化int和非类型化字符串)fmt。Println(";继续执行!";)}

编译器会给你一个错误,你的程序甚至不会被编译。

编程语言通常有一整套开箱即用的类型。这些类型称为基元类型。例如:整型、布尔型、浮点型等等。

通常,您还可以使用复合类型,即包含多个值的类型,可能还可以使用多个基元类型。这也是我们所说的数据结构。数组是复合类型:

附加到这些数据类型的规则由您选择的语言的编译器(或解释器)强制执行。

自从抽象数据类型(ADT)出现以来,您就有能力在高级编程语言中创建自己的类型。例如,在PHP中:

当您编写$Shipping=new Shipping()时,您将创建类Shipping的一个实例。对象$SHIPTING也可以被视为类型为SHIPTING。

我们还可以说,$Shipping是它的类的抽象,它的接口(您与抽象交互的方式)是方法send()。

在Golang中,您没有类,但也可以创建自定义类型:

程序包主导入(";fmt";)类型分钟int func(m分钟)秒()int{return int(M)*60}func main(){var min=2 fmt。Printf(";%d分钟是%d秒";,分钟,分钟。Second())//2分钟等于120秒}。

在这种情况下,新类型Minint将获得与类型int相同的规则集。然后,您可以将方法附加到这个新类型Minint,就像方法Second()一样。

如果类型迫使我们遵守某些规则,编程语言需要一个算法来检查我们是否遵守它们。这称为类型检查。

即使类型系统可能非常相似,也可能非常不同,这取决于编程语言,但我们是人,所以我们需要将这些完全不同的东西分类以理解它们。

类型检查有两个重要类别:静态类型检查和动态类型检查。它们主要是关于类型检查算法何时验证您的代码。仅此而已。

对于动态类型系统,类型检查将在运行时(当计算机执行您的代码时)进行。

如果您的编程语言没有任何静态类型检查,通常会说它是一种“动态类型语言”。

如果您不尊重数据类型的规则,则需要执行代码,以便类型检查器检测错误并采取相应行动(抛出警告、停止执行等)。

对于静态类型系统,类型检查在运行时之前、编译期间进行。在这一点上,如果编译器强加给您的类型的规则得不到遵守,您很可能永远也不会编译您的程序。

这意味着编译器需要知道程序中使用的每个数据的确切数据类型,然后才能运行它。当数据类型只能在运行时确定时,这可能是一个问题。这就是为什么静态类型语言通常也有一些动态类型检查的原因!

尽管如此,如果类型主要是在编译时检查的,我们可以说编程语言是“静态类型的”。

由于大多数类型不需要在运行时进行检查,因此程序的性能通常会更好。编译器可以优化您的代码,因为它知道类型系统的规则是受尊重的。

就目前而言,在打字之地,一切看起来都很好,而且井井有条。然而,世界并不是一成不变的,我们的代码库也不是一成不变的。情况会发生变化,我们的类型通常也需要在运行时进行更改。

让我们举另一个微不足道的例子。您想在您梦幻般的电子商务上展示一些价格,如下所示:

<;?PHP ECHO";<;p>;嘿!买我的鱼吧!现在!。这是价格:34;。65岁。";欧元<;/p>;";;

您会注意到这里混合了两种类型:整数(65)和字符串(所有内容都用双引号引起来)。PHP解释器的类型检查器理解您希望将整数65更改为字符串,因此它会自动执行此操作,而不会询问您。

这就是所谓的胁迫。解释器(或编译器)不会显式地告诉您发生了类型更改,但无论如何都会发生。

包主要导入(";fmt";)func main(){fmt.。Println(";<;p>;嘿!买我的鱼吧!现在!。这里是价格:";+65+&34;欧元/p&>;&34;)//=&>;无效操作:";<;p&>;嘿!买我的鱼吧!现在!。这里是价格:+65(不匹配的类型非类型化字符串和非类型化整型)}。

这一次,类型检查器将在编译时抛出一个错误。你的程序永远不会编译,你的电商不会卖鱼,告别财富和荣耀吧。

编程语言并不总是隐式地为您更改类型,当它们这样做时,您需要意识到后果。当然,我们的示例很琐碎(否则您不会再阅读本文),但是真正的代码库要复杂得多。

另一方面,您可以显式更改类型,这意味着类型更改将在代码中明确指示。这通常称为类型转换。

<;?PHP ECHO";<;p>;嘿!买我的鱼吧!现在!。这是价格:34;。(字符串)65。";欧元<;/p>;";;

包主要导入(";fmt";)func main(){fmt.。Println(";<;p>;嘿!买我的鱼吧!现在!。这里是价格:";+string(65)+";欧元/p>;&34;)}。

这一次,它成功了!Golang对类型转换很满意,但不支持类型强制。

在某些语言(如PHP)中,您可以通过赋值直接定义变量的类型:

<;?PHP$BeautufulInt=65;$myString=";Hello";;$isItReallyTrue=true;$aCompositeType=[];echo gettype($BeautifulInt)。";\n";;//=>;整数回显gettype($myString)。";\n";;//=>;String echo gettype($isItReallyTrue)。";\n";;//=>;布尔回显gettype($aCompositeType)。";\n";;//=>;数组。

在声明每个变量时,您不需要精确地指定它们的类型。PHP解释器将在运行时为您执行此操作,具体取决于变量的值。这称为类型推断。

软件包main import(";fmt";";)func main(){BeauufulInt:=65 myString:=";hello";isItReallyTrue:=true fmt。Println(反射。Typeof(Beauty Int))//=>;int fmt。Println(反射。Typeof(MyString))//=>;String fmt。Println(反射。Typeof(IsItReallyTrue)//=>;bool}。

在某些语言中,声明变量时需要显式键入变量。在其他语言(如PHP)中,您不能这样做。某些编程语言(如Golang)可以让您同时做到这两点:

程序包主导入(";fmt";";reflect";)func main(){BeautifulInt:=65 var explitBeautifulInt int=65 fmt。Println(反射。Typeof(Beauty Int))//=>;int fmt。Println(反射。Typeof(ExplitBeautifulInt)//=>;int}。

对于变量EXPLICIT BeautifulInt,显式声明了一个类型int。该类型成为编程语言本身的语法的一部分。

您可以在某些语言中声明函数参数的类型,也可以声明函数输出的类型。

<;?PHP函数CoolFunction($CoolThing){return";我认为是";。$CoolThing。";是一个很酷的东西!";;}ECHO COOL Function(";Daffodil";);

同样,函数的输入和输出没有显式类型声明。但是,这一次,如果需要,您可以添加它:

<;?PHP函数CoolFunction(String$CoolThing):String{return";我认为是";。$CoolThing。";是一个很酷的东西!";;}ECHO COOL Function(";Daffodil";);

在这里,我们显式声明$CoolThing并将返回值声明为字符串。当声明函数参数的类型是可选的时,我们通常称之为类型提示。

<;?PHP函数CoolFunction(String$CoolThing):String{return";我认为是";。$CoolThing。";是很酷的东西!";;}ECHO CoolFunction(65);//=>;我认为65是很酷的东西!

PHP解释器将强制将您的值从整数转换为字符串。如果您返回65而不是字符串,它也会被强制。

您必须声明输入和输出的类型,否则将抛出错误。

Package main import(";fmt";)func CoolFunction(CoolThing String)String{return";我认为a";+CoolThing+";是很酷的东西!";}func main(){fmt。Println(CoolFunction(";Daffodil&34;))//=>;我认为水仙花是很酷的东西!}

如果您不给CoolFunction一个字符串,您会得到一个错误。如果CoolFunction返回字符串以外的任何内容,您也会得到一个错误。

一些开发人员会根据类型系统的不同属性将语言分类为“强”或“弱”;例如,如果类型系统强制您的值。但似乎没有人对什么应该是“强”或“弱”有相同的定义,这使得辩论变得困难。

我对工具龙也是这么想的。但是正如我们所看到的,Golang是一种静态类型语言,但是您可以隐式类型声明。Python是动态类型的,但不做太多强制。简而言之,上面的映射是错误的。

我认为围绕编程语言的健康辩论是有益的。决定什么是解决手头问题的最好工具是至关重要的,我们经常从这些讨论中进行教学和学习。开发人员通常对他们的工艺充满热情,这绝对是一个好观点!

然而,谈论“弱”或“强”语言是令人困惑的,因为它们的定义是模糊的。取而代之的是,我们应该使用上面看到的词汇,具体说明我们喜欢打字系统中的什么,以及为什么它比另一个打字系统更好。这会增加谈话的精确性和清晰的含义。

当人们谈到“弱类型”和“强类型”语言时,通常指的是语言的类型安全。一般来说,它是您的编译器或解释器检查类型错误的“次数”,以及将它们扔到您的脸上的频率。

这个“多少”也没有很好的定义。另外,和往常一样,这取决于编程语言,甚至取决于您使用的编程语言的版本。例如,使用最新版本的PHP,您可以这样做:

<;?PHP DECLARE(STRICT_TYES=1);ECHO";我的名字是";。65岁。";\n";;//这不会引发错误//=>;我的名字是65 Function CoolFunction(String$CoolThing):string{return";我认为是";。$CoolThing。";是一个很酷的东西!";;}ECHO CoolFunction(65);//=>;PHP致命错误:未捕获TypeError:传递给CoolFunction()的参数1必须是字符串类型,int给定

另一方面,严格的类型系统可能会在您的设计中造成一些僵化,这将使代码在混乱的现实世界(上下文)演变时更难更改。尽管如此,它们仍将有助于避免整个类别的错误。

这就是为什么您应该学习您选择的语言如何处理类型。在官方文件中可以看到这一点。用它做实验。试着找出最终的不一致之处和陷阱。如果您感兴趣,我为PHP7写了一篇关于这方面的文章。

即使您使用相同的编程语言多年,这个建议仍然有效。语言就像软件一样,是要进化的。保持为。

.