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