在大多数编码语言(如果不是全部)中,您需要声明变量。例如在C#中,如果它是一个数字字段,则
int PhoneNumber
如果我用正常的英语,我不需要申报PhoneNumber
为int
使用它。例如,如果我请朋友萨姆给我他的电话号码,我会说:
“山姆给我电话号码”
我不会说
“ Char(20)Sam给我int电话号码”
为什么我们必须完全指定数据类型?
在大多数编码语言(如果不是全部)中,您需要声明变量。例如在C#中,如果它是一个数字字段,则
int PhoneNumber
如果我用正常的英语,我不需要申报PhoneNumber
为int
使用它。例如,如果我请朋友萨姆给我他的电话号码,我会说:
“山姆给我电话号码”
我不会说
“ Char(20)Sam给我int电话号码”
为什么我们必须完全指定数据类型?
Answers:
在大多数编码语言(如果不是全部)中,您需要声明变量。
[…]
为什么我们必须完全指定数据类型?
这是两个独立的问题:
顺便提一句,两者的答案是:我们没有。
有许多静态类型的编程语言,您无需声明类型。编译器可以根据周围的上下文和用法来推断类型。
例如,在Scala中,您可以说
val age: Int = 23
或者你可以说
val age = 23
两者是完全等效的:编译器将从Int
初始化表达式推断类型23
。
同样,在C♯中,您可以说这两个,它们的意思完全相同:
int age = 23;
var age = 23;
此功能称为类型推断,除了Scala和C♯之外,许多语言都具有此功能:Haskell,Kotlin,Ceylon,ML,F♯,C ++,您都可以命名。甚至Java也具有有限类型的类型推断。
在动态类型的编程语言中,变量甚至没有类型。类型仅在运行时动态存在,而不是静态存在。只有值和表达式才具有类型,并且只有在运行时变量才没有类型。
例如在ECMAScript中:
const age = 23;
let age = 23;
最后,在许多语言中,您甚至都不需要声明变量。例如在Ruby中:
age = 23
实际上,最后一个示例在多种编程语言中均有效。例如,完全相同的代码行也可以在Python中工作。
所以,
auto i = 1 // i is inferred to type int
,vector<int> vec; auto itr = vec.iterator(); // itr is inferred to type vector<int>::iterator
等等。如果您想知道它的工作原理,可以在规范中进行查找。
当您使用自然语言来引用信息时,它不是很精确,尤其是不会与他人就您的意图进行过多交流。尝试使用自然语言进行数学运算时也会发生类似的问题:不够精确。
编程很复杂;错误太容易出现。类型是检查系统的一部分,该系统旨在通过检测错误情况来防止程序处于非法状态。不同的语言使用不同的类型:某些语言在编译时大量使用类型来检测错误。几乎所有语言都有一些不兼容类型的概念,即运行时错误。通常,类型错误表示程序中存在某种错误。当我们允许程序继续执行但有错误时,我们可能会得到非常差的答案。我们更喜欢停止程序,而不是得到错误或错误的答案。
换句话说,类型表示对程序行为的约束。当约束通过某种机制强制执行时,可以提供保证。这样的保证限制了考虑程序所需的推理量,从而简化了程序员阅读和维护程序的任务。如果没有类型及其检测类型错误的工具(即编译器)的含义,那么编程负担会大大增加,因此成本也会更高。
确实,(许多)人类可以轻松区分欧洲,美国和国际电话号码。但是,计算机并没有真正“思考”,如果告诉他们,会拨打欧洲的美国电话号码,反之亦然。例如,类型是区分这些情况的好方法,而不必教计算机如何“思考”。在某些语言中,由于尝试在美国电话系统上混用欧洲电话号码,我们可能会遇到编译时错误。该错误告诉我们,在尝试运行程序之前,我们需要修改程序(可能是通过将电话号码转换为国际拨号序列,或者使用欧洲的电话号码)。
此外,由于计算机不认为,字段或变量的名称(例如phonenumber
)对计算机没有任何意义。对于计算机,该字段/变量名称仅为“ blah123”。考虑一下如果所有变量均为“ blahxxx”,您的程序将如何。kes。好吧,这就是计算机所看到的。提供类型可以使计算机了解变量的含义,而变量的含义仅凭其名称是无法推断的。
此外,正如@Robert所说,在许多现代语言中,我们不必像过去那样指定类型,因为C#之类的语言执行“类型推断”,这是确定适当类型的一组规则用于上下文中的变量。C#仅提供对局部变量的类型推断,而不提供形式参数,类或实例字段的类型推断。
Types are part of a system of checks that ...
因为它直接回答了Why do we have to specify data type at all?
..
static member add x y = x + y
,member x.Append s = x.Text + s
。在第一种情况下,由于加法x
,y
将被推断为int
。在第二种情况下,根据类型的不同,它们将是有效的x.Text
-如果为a string
,则s
也将为a string
。我确实同意类型注释是文档。
除其他答案外,还应包含一件事。请记住,计算机只是位。说我给你的字节:
26 3A 00 FF
那是什么意思?它是通过计算机以这种方式存储的,但是没有任何解释,只是位。可以是4个ASCII字符。可以是整数。可能是数组中的一些字节。它可能是对象的一部分。它可能是指向该猫视频缓冲位置的指针。从汇编开始,几乎所有的编程语言都需要一些知识来知道如何解释这些位,以使它们进行有意义的计算。
而且由于计算机无法知道这些位的含义,因此需要您通过类型注释或其他答案中提到的类型推断机制来明确告知它们。
为什么计算机需要此信息的答案与数据表示有关。
“数据类型”的名称是对规则的引用,这些规则可帮助计算机从计算机内存中的原始0和1原始状态存储和检索信息。
例如,您的常规8位ASCII字符将存储为计算机内存(RAM或磁盘)为01000001
(大写字符“ A”,ASCII代码65)或00001000
(百分号),或0和在这8位中为1。
再举一个例子,一些8位无符号整数可以存储为00000101
(数字5)或00001000
(数字8)
请注意,8和%字符的二进制表示形式可能是相同的,但是它们的含义不同,因为它们的类型不同。
即使是推断数据类型的语言,它们也可能没有规则“所有变量的类型都必须由程序员声明”,它们具有诸如“如果您的字符系列用引号括起来,则是字符串”之类的规则,并且每个数据类型还有更多规则。
因此,即使这些都需要数据类型才能理解0和1的含义,因此,例如,如果您尝试“加”两个字符,它们可以执行字符串连接功能;如果您要添加两个整数,则可以进行整数加法。
在您的故事中,也可以说您没有询问Sam的电话号码,但是Sam给了您一张纸,上面写有“ 1123581321”。您不确定Sam是不是前八个斐波那契数字的粉丝,还是一个电话号码。要进行猜测,您必须考虑可用的上下文和提示,例如您可能是一天前向Sam询问了电话号码,或者便条上写着“给我打电话”,或者您是否在数数并找到它与大多数电话号码的格式匹配。只有到那时,您才知道这是您可以拨打的电话号码,而不是您要输入计算器的一些数字。
请注意,这些提示如何使您猜测该数字是电话号码,与这些提示如何引导一种不需要声明即可推断出值类型的计算机语言类似。
在某些语言中,您不必指定数据类型。
支持类型推断的语言通常可以从您的用法中找出数据类型。例如,
var name = "Ali"
在内部键入为字符串,因为该值用引号引起来。
某些语言也不要求您声明变量。该变量在首次使用时创建。但是,出于多种重要原因,最好是专门声明变量的最佳实践。主要是因为做得更好可以表达您的意图。
var name = "Ali"
实际上,这种样式对于现代静态类型的语言很常见。在静态类型语言中,类型在创建时是固定的,但仍可以由初始化程序确定。动态类型语言的定义是类型附加到值,而不是变量。因此,为变量分配值也可以设置变量的类型。
var x = 5; x = "";
因为第一个语句导致x
与“ Number”类型相关联,因此不允许使用以下代码(Javascript)x
?与动态类型冲突。如果不是,那么与变量关联的类型除了与值关联的类型以外,还有什么作用?
x = "";
将x 的类型更改为字符串,即使以前是数字也是如此。
因为那是语言设计所指定的。因此,要回答您的问题,我们需要查看使用C#和C ++等语言进行显式键入的意图。(好吧,C#之所以这样做,是因为C ++是因为C来做到这一点,所以我们需要回顾一下当时的意图)。
首先,显式和静态类型提供了严格的编码-将变量指定为整数意味着将变量或字符串分配给变量时,编译器和软件应感到惊讶并抛出错误。动态类型输入可能会给那些粗心的人带来麻烦(只需看一下PHP或javascripts处理数组和空字符串之类的事实的方法)。
您可以使用静态类型进行隐式键入-将变量初始化为字符串意味着变量应该只能是字符串,但是我的感觉是,这可能会给人类阅读代码带来麻烦(我倾向于假设在使用隐式键入时使用动态类型) )。
同样,在某些语言中,可以编写如下这样的伪代码来初始化来自字符串输入的类:
PhoneNumber phoneNumber = "(61) 8 8000 8123";
其次,显式类型也与内存分配紧密结合。一个int总是那么多字节。一个电话号码太多字节。编译器可以分配适当大小的内存块,然后在以后使用该内存块,而不必查看分配值时需要多少空间。
PhoneNumber phoneNumber;
...
phoneNumber = "some value from somewhere";
最后,它消除了混乱...是123是整数还是无符号整数?它们需要相同数量的字节,但是存储在这两种类型的变量中的最大值都大不相同...
并不是说显式要比隐式好,但是语言设计依赖于这类选择,而C#在隐式类型上的工作方式有所不同。PHP和javascript与显式键入的工作方式有所不同。
因为Sam比编译器聪明。例如,当您说给我电话号码时,您无需指定是否要输入国家/地区前缀或区号,而只需要最后4位数字的电话号码即可。另外,如果您要求提供当地披萨店的编号,您将可以回答“ pizza4u”。
山姆,从上下文中弄清楚了。尽管编译器也可以从上下文中找出问题,但Sam会做得更好(并且能够打断要求澄清的过程)。
有两种类型和变量的基本方法,一种是变量具有类型,在这种情况下,类型不允许的操作被禁止并阻止编译,或者值具有类型和变量不允许的操作。类型在运行时捕获。
每种方法都有其优点和缺点。通常,编译器作者会尝试最小化缺点并最大化优点。例如,这就是为什么C#允许var phoneNumber = GetPhoneNumber();
并将从GetPhoneNumber的签名推断出phoneNumber的类型的原因。这意味着您必须声明方法的类型,而不是声明接收结果的变量。另一方面,针对javascript有各种类型的提示/强制项目。一切都是权衡。
这取决于数据的存储方式。如果您要与Sam互动,可以做一个更好的比较,以便您可以写下来,但纸上只有八个字符。
“山姆,给我电话号码。”
“ 5555555555”
“哦,不,我没纸了。如果我能提前知道要多少数据,我本可以准备得更好!”
因此,大多数语言使您声明一个类型,以便它会提前知道并准备:
“山姆,电话号码多久?”
“十个字符。”
“好吧,那就让我拿张大纸吧。现在给我手机号码。”
“ 5555555555”
“知道了!谢谢山姆!”
当您查看数据的实际基本存储方式时,它变得更加毛茸茸。如果您像我一样,则有一个笔记本,上面有杂项笔记,数字被涂了字迹,没有任何上下文或标签,也不清楚三天后的含义。这也是很多时候计算机的问题。许多语言都有“ int”类型(int,long,short,byte)和“ float”(浮点,双精度)类型。为什么有必要?
首先,让我们看一下如何存储整数,并且该整数通常在计算机中表示。您可能已经知道,在基本级别上,它们都是二进制的(1和0)。二进制实际上是一个数字系统,其工作方式与我们的十进制数字系统完全相同。以十进制表示,从0到9计数(没有写的无限的隐含前导零),然后滑回到0并递增下一个数字,这样就得到10。重复直到从19滑到20,重复进行,直到从99滚动到100,依此类推。
二进制没有什么不同,除了不是0到9,而是0到1。0、1、10、11、100、101、110、111、1000。因此,当您输入9时,以二进制记录的内存为1001。这是实际数字。可以完全按照这种形式对其进行加,减,乘等操作。10 + 1 =11。10+ 10 = 100(将1滚动到0并携带1)。11 x 10 = 110(相当于11 + 11 = 110)。
现在,在实际的存储器(包括寄存器)中,有一个列表,数组(无论您要调用什么),位(可能为1或0')彼此相邻,这就是如何按逻辑方式将这些位组织成一个大于1的数字。问题是,如何处理小数?您不能只在寄存器的两个位之间插入硬件,而在每对位之间添加“十进制位”会花费太多的时间。那么该怎么办?
您对其进行编码。通常,CPU或软件的体系结构将确定如何完成此操作,但是一种常见的方法是在寄存器的第一位尾数中存储一个符号(+或-,通常为1为负)。(移位的数字)但是对于接下来的X个位数,很多时候需要去掉小数),而对于其余的位数,则需要一个指数(必须移位它的次数)。这类似于科学记数法。
通过键入,编译器可以知道它在看什么。想象一下,您将值1.3存储在寄存器1中。在这里,我们将提出自己的特殊编码方案,1位表示符号,4表示尾数,3表示指数(1表示符号,2表示幅度)。这是一个正数,因此符号为正(0)。我们的尾数为13(1101),指数为-1(101(负数为1,01 = 1))。因此,我们将01101101存储在寄存器1中。现在,我们没有键入此变量,因此当运行时使用它时,它会说“确定,这是一个整数,为什么不这样做”,因此当它打印值时,我们看到109(64 32 + 8 + 4 + 1),这显然是不对的。
但是,并非每种语言都要求您显式键入。C#具有关键字“ var”,该变量导致在编译时解释变量的类型,而诸如Javascript之类的其他语言则是完全动态键入的,以至于您可以将整数存储在变量中,然后将其分配给布尔值,然后再次将其分配给字符串,语言将对其进行跟踪。
但这在编译器,解释器或运行时上要容易得多,并且通常会导致程序更快,因为它不必花费宝贵的资源来对所有类型进行排序—询问您,程序员,哪种您提供的数据。
在某些编程语言中,您不必声明变量的数据类型。甚至在某些编程语言中,您也不必事先声明变量。您可以立即使用它们。
不声明变量名的麻烦在于,如果您不小心拼错了变量名,那么现在您将不小心创建了一个新的,完全不相关的变量。所以,当你运行你的程序,你不能明白,为什么地狱变量设置了突然有什么也没有......直到几个小时的调试后,你意识到你键入的名字该死的错!RR!
因此他们做到了,因此您必须事先声明要使用的变量名。现在,当您输入错误的名称时,您将得到一个编译时错误,该错误会在程序甚至运行之前立即告诉您错误的确切位置。那不是那么容易吗?
同样处理数据类型。在某些编程语言中,您不必声明事物应该是什么类型。如果您的customer
变量实际上只是一个客户的名字,而不是整个客户对象,则尝试从一个普通的普通字符串中获取客户地址……将不起作用。静态类型的全部要点是程序将无法编译。它会大声抱怨,指出问题所在的确切位置。这比运行代码并试图弄清楚为什么它无法正常工作要快得多。
所有这些功能都可以告诉编译器您打算做什么,因此它可以检查您实际做了什么并确保它有意义。这使编译器可以为您自动定位错误,这很重要。
(回溯到遥远的过去,您不必声明子例程。您只需声明GOSUB
特定的行号。如果要在子例程之间传递信息,则可以设置特定的全局变量,调用子例程,然后检查其他子例程。子例程返回时返回变量,但是这使得忘记初始化其中一个参数非常容易,因此现在几乎所有现代编程语言都要求您声明子例程采用的实际参数,因此我们可以检查是否已全部指定了它们。 )
var x=1
类似的结果。但这没什么。在Haskell中,您可以编写完全没有类型签名的整个程序,但是它们都是静态类型的,并且如果您犯了错误,仍然会出错...(尽管不完全是主流。)
for (auto i=0; i<SomeStdVector.size(); ++i)
您的lint 会抱怨,因为它推导出了有符号类型,然后您将其与无符号类型进行比较。您必须编写auto i=0ul
(再次明确输入类型信息,因此应该size_t i=0
首先编写)。
如果我使用的是普通英语,则无需将PhoneNumber声明为int即可使用它。例如,如果我请朋友萨姆给我他的电话号码,我会说:
“山姆给我电话号码”
我不会说>
“ Char(20)Sam给我int电话号码”
为什么我们必须完全指定数据类型?
流行到MathOverflow或理论计算机科学和阅读一段时间才能得到的人类如何沟通与另一个算法的时候,他们希望确保没有误解的可能性的想法。或阅读一些成熟的编程语言的标准。
你会发现,定义什么类型的值被允许的一个术语是真正的精确通信的一部分,甚至练习的人对人。
您已经注意到,日常交互相当正常,人类非常容错,因此,通过共享参与者的知识,通常可以避免对电话号码的误解。
但是,您是否曾经尝试删除另一个国家/地区某人的电话号码?他们是否明确告诉过您多少次将零推到国际地址?他们有没有告诉你他们的国家代码?你有这样认识吗?您期望多少位数?你得到了多少?您知道如何将数字分组吗?甚至如果分组有什么意义?
突然,问题变得更加棘手,您可能需要花费更多的精力来明确检查所接收的号码是否已理解发件人的意思。
许多早期的编程语言(尤其是Fortran)不需要您在使用前声明变量。
这导致了许多问题。一个非常明显的事实是,编译器不再能够可靠地捕获简单的印刷错误。如果您有应该修改现有变量的代码,但有错别字,那么您仍然拥有完全合法的代码,它们只是创建了一个新变量(并为其分配了值):
longVariableName = 1
// ...
longVaraibleName = longvariableName + anotherLongVariableName
现在,孤立地看待这个问题,我已经提到过错别字是问题的根源,在这里找到错别字和问题可能很容易。在一个很长的程序中,它被埋在许多其他代码的中间,因此容易遗漏。
即使当前使用许多动态类型的语言,您仍然可以很容易地获得相同的基本问题。有些人有一些设施可以警告您是否将变量赋给您,但从未读过(启发式地捕获了很多这样的问题),其他人都没有。
当声明任何变量时,会在内存中分配一些空间,但是机器(在本例中为计算机)尚不知道必须为该变量分配多少空间。
示例:-您创建了一个程序,要求用户输入任何数字,在这种情况下,您必须指定一种数据类型来存储该数字,否则机器无法自行判断应该分配2个字节 还是 ,如果尝试, 2 GB的数据另一方面,如果您在程序中指定数据类型,则在编译之后,计算机将根据需要分配适当的空间。