不久前,我和一个新的 C + + 开发人员一起工作,他问了这样一个问题: “为什么变量名不能以数字开头?”
除了一些数字可以包含文本(123456L,123456U)之外,我想不出其他答案。如果编译器认为所有带有一定数量字母的东西都是变量名,那么这是不可能的。
这是正确答案吗? 还有其他原因吗?
string 2BeOrNot2Be = "that is the question"; // Why won't this compile?
好好想想:
int 2d = 42; double a = 2d;
什么是2.0还是42?
提示,如果没有得到,数字后面的 d 表示前面的数字是双字面值
因为那样的话,一串数字将是一个有效的标识符,也是一个有效的数字。
int 17 = 497; int 42 = 6 * 9; String 1111 = "Totally text";
对我来说,编译器/解析器/词法分析器已经是很久很久以前的事了,但是我记得很难明确地确定编译单元中的数字字符是表示文字还是标识符。
空间不重要的语言(如果我没记错的话,比如 ALGOL 和原始的 FORTRAN)不能接受数字作为开始标识符。
这可以追溯到很久以前——在表示存储或数字基础的特殊符号出现之前。
这可能是出于几个原因做出的决定,当您解析令牌时,您只需要查看第一个字符来确定它是标识符还是字面值,然后将其发送到正确的函数进行处理。这就是性能优化。
另一种选择是检查它是否不是一个文字,并将标识符的域保留为宇宙减去文字。但是要做到这一点,您必须检查每个标记的每个字符,以了解如何对其进行分类。
还有文体含义标识符被认为是助记符,所以单词比数字更容易记住。当很多原始语言被写下来为接下来的几十年设定风格时,他们并没有考虑用“2”代替“ to”。
我认为简单的答案是它可以,这种限制是基于语言的。在 C + + 和许多其他语言中它不能,因为语言不支持它。规则里没有允许这种情况发生。
这个问题类似于国王下象棋时为什么不能一次移动四格?因为在象棋里这是违法的。可以在另一场比赛中肯定。这只取决于游戏规则。
使用数字作为变量名的开头使得编译或交换过程中的错误检查变得更加复杂。
允许使用以数字开头的变量名可能会给语言设计人员带来巨大的问题。在源代码解析过程中,每当编译器/解释器遇到一个以数字开头的令牌时,它就必须搜索一组庞大而复杂的规则,以确定这个令牌到底是一个变量还是一个错误。添加到语言解析器中的复杂性可能无法证明这一特性的合理性。
回想起来(大约40年前) ,我认为我从未使用过允许使用数字作为变量名开头的语言。我肯定至少有过一次。也许这里真的有人在什么地方见过这个。
正如一些人已经注意到的,变量名的有效格式有很多历史包袱。语言设计者总是受到他们创建新语言时所掌握的知识的影响。
也就是说,几乎所有时候,语言不允许变量名以数字开头是因为这些是语言设计的规则。这通常是因为这样一个简单的规则使语言的解析和词法分析变得非常容易。不过,并非所有语言设计师都知道这是真正的原因。现代词法分析工具很有帮助,因为如果您试图将其定义为允许的,它们将给您带来解析冲突。
OTOH,如果您的语言具有唯一可标识的字符来预示变量名,则可以为它们设置以数字开头的字符。类似的规则变化也可以用于允许变量名中有空格。但是,最终产生的语言可能与任何流行的传统语言都不太相似,如果有的话。
举一个相当简单的 HTML 模板语言的例子,它允许变量以数字开头并且有嵌入的空格,看看 请坐。
C + + 不能拥有它,因为语言设计者把它作为一个规则。如果您要创建自己的语言,当然可以允许它,但是您可能会遇到与他们相同的问题,并决定不允许它。会引起问题的变量名示例:
0x,2d,5555
可能是因为这样人们就更容易分辨出它是一个数字还是一个标识符,也可能是因为传统。使用可以以数字开头的标识符不会使词法扫描复杂化太多。
并非所有语言都有以数字开头的禁用标识符。在 Forth 中,它们可以是数字,小整数通常被定义为 Forth 单词(本质上是标识符) ,因为将“2”作为例程读取以将一个2放到堆栈上比将“2”作为值为2的数字识别要快。(在处理来自程序员或磁盘块的输入时,Forth 系统会将输入按空格分割。它会尝试在字典中查找这个标记,看它是否是一个已定义的单词,如果不是,它会尝试把它翻译成一个数字,如果不是,它会标记一个错误。)
假设您确实允许符号名以数字开头。现在假设您希望将变量命名为12345foobar。你怎么区分这个和12345?实际上,使用正则表达式并不十分困难。问题实际上是性能问题。我不能很详细地解释为什么会这样,但是它基本上归结为这样一个事实: 区分12345foobar 和12345需要回溯。这使得正则表达式不确定。
这个 给你有更好的解释。
我同意允许标识符以数字开头会很方便。有一两个人提到,您可以通过在标识符前面加上下划线来绕过这个限制,但是这真的很难看。
我认为问题的部分原因来自数字字面值,比如0xdead Oxford,这使得很难为可以以数字开头的标识符提供容易记忆的规则。一种方法可能是允许任何与[ A-Za-z _ ] + 不是关键字或数字字面值相匹配的内容。问题是这会导致一些奇怪的事情,比如允许吃死猪肉,但不允许吃死牛肉。最后,我认为我们应该公平对待所有的肉类。
当我第一次学习 C 语言时,我记得感觉到变量名的规则是任意的和限制性的。最糟糕的是,它们很难记住,所以我放弃了学习它们的努力。我只是做了感觉对的事,而且效果很好。现在我学到了很多东西,看起来也没那么糟糕,我终于找到机会把它学好了。
放松语法约定的一个关键问题是,它在编码过程中引入了认知失调。您对代码的看法可能会受到它所引入的不清晰性的深刻影响。
戴克斯特拉不是说过“任何工具最重要的方面是它对用户的影响”吗?
因为如果允许关键字和标识符以数字字符开头,lexer (编译器的一部分)就不能很容易地区分数字字面值和关键字的开头,否则就会变得更加复杂(而且速度更慢)。
现在这是一个惯例,但它最初是作为一个技术要求。
在过去,FORTRAN 或 BASIC 等语言的解析器不需要使用空格。所以,基本上,以下几点是一样的:
10 V1=100 20 PRINT V1
还有
10V1=100 20PRINTV1
现在假设允许使用数字前缀。您将如何解释这一点?
101V=100
作为
10 1V = 100
或作为
101 V = 100
1 01V = 100
所以,这是非法的。
这种限制是任意的。许多 Lisps 允许符号名以数字开头。
最初,这只是因为它更容易记住(你可以给它更多的含义)变量名字作为字符串,而不是数字,虽然数字可以包含在字符串中,以增强字符串的含义,或允许使用相同的变量名称,但它被指定为有一个单独的,但接近的含义或上下文。例如 loop 1、 loop 2等总是让你知道你在一个循环中,或者 loop 2是 loop 1中的一个循环。 你更喜欢哪个变量(更有意义) : address 还是1121298? 哪个更容易记住? 然而,如果语言使用某些东西来表示它不仅仅是文本或数字(例如 $in $address) ,那么它真的不应该有什么不同,因为这会告诉编译器接下来的内容将被视为一个变量(在本例中)。 在任何情况下,都可以归结为语言设计者希望用什么作为他们语言的规则。
变量名不能以数字开头,因为它可能导致以下问题:
int a = 2; int 2 = 5; int c = 2 * a;
C 的值是多少? 是4还是10!
另一个例子:
float 5 = 25; float b = 5.5;
前5是一个数字,或者是一个对象(. 运算符) 第二个5也有类似的问题。
也许,还有其他的原因。所以,我们不应该在变量名的开头使用任何数字。
编译器很容易在内存位置而不是数字上使用 ASCII 标识变量。
因为在编译的词法分析中避免了回溯:
Apple;
当它遇到字母“ A”时,编译器就会立刻知道它是一个标识符。
然而,一个变量是:
123apple;
编译器将无法决定它是一个数字还是标识符,直到它命中“ a”,因此它需要回溯。
COBOL 允许变量以数字开头。
编译器也可以在编译期间将该变量视为值 因此该值可以一次又一次递归地调用该值
在词法分析阶段编译 代码时避免了回溯。像苹果这样的变量,当编译器在词法分析阶段遇到字母“ a”时,就会立即知道它的标识符。然而,像123apple 这样的变量,编译器不能决定它是一个数字还是一个标识符,直到它点击“ a”,它需要回溯到词法分析阶段来识别它是一个变量。但是在编译器中不支持。
参考文献
编译器有如下7个阶段:
在编译代码时,在词法分析阶段避免了回溯。这个变量就像苹果一样,当编译器在词法分析阶段遇到字母“ a”时,它会立刻知道它的标识符。然而,像123apple 这样的变量,编译器不能决定它是一个数字还是标识符,直到它点击“ a”,它需要回溯到词法分析阶段来识别它是一个变量。但是在编译器中不支持。
在解析令牌时,只需要查看第一个字符来确定它是标识符还是文字,然后将其发送到正确的函数进行处理。这就是性能优化。
在声明变量时,它可能没有什么问题。但是,当它试图在其他地方像这样使用该变量时,会有一些模棱两可的地方:
Let 1 = “ Hello world!” 列印(1) 列印(1)
Print 是一个接受所有类型变量的泛型方法。因此,在这种情况下,编译器不知道程序员指的是哪个(1) : 整数值的1或存储字符串值的1。 在这种情况下,编译器最好允许定义类似的东西但是当尝试使用这些模棱两可的东西时,带一个具有纠正能力的错误来修复这个错误并清除这个模棱两可的东西。