例如,能否做到定义一个变数,名称为一个字元串变数的内容。而这个字元串变数的内容是要在计算过程中才知道的,在程序运行前并不知道。

实现起来有什么困难?


很多脚本语言都允许这么做,以便实现引用语义(比如shell脚本、perl不用strict模式),甚至是传递引用的唯一方式(比如cmake)。

其实从底层来看,几乎所有的脚本语言的符号表在实质上都是这样的东西:你在访问foo函数的时候,是在某个代表了当前名字空间的哈希表里搜索"foo"键的值。

带有解析器的脚本语言不但允许你动态生成变数名,甚至可能允许你动态生成代码。通常它们都提供了名为eval的函数,接受一个字元串,里面是自己语言的源代码,并直接解析执行它。

但经典的perl小骆驼书里面说过这样一句话,大意是「你学编程语言的时候,通常都会问这样的问题,这代表了你在编程思想上进了一步;但当你理解为什么最好别这么干的时候,你又进了一步」。问题在于:

  • 这么干引入了巨量的动态性,不但会让程序调试变得很困难,而且其中的部分完全不能被现代编辑器、IDE进行代码分析辅助。这都会严重影响开发效率。
  • 破坏数据与代码的边界,是安全问题的基本根源之一。
  • 通常你不需要这么做,就能达到目的。你可能只是需要一个键值对类型的数据结构而已。


无论动态语言还是静态语言都有一百种方法元编程,你却要使用最屑的字元串拼变数名...

拼变数名能让type check直接报废,原地爆炸...


实现起来其实也没啥困难的 ,只不过就传统语言来说,代价太大。

最初,所有变数名仅仅只在编译期间存在,在程序运行期间,已经没有变数名的概念了,所以没有办法在程序运行期间动态解析变数名。

然后,动态链接库机制本身使得函数必须支持运行时间动态载入,这就必须把所有的函数名信息放进目标代码中,你可以试试,现在所有的语言都可以支持用字元串拼出来的函数名并且能够调用。

再然后,面向对象语言为了支持动态的类型转换,所有的类名以及继承关系都必须在运行时间存在,这就引入了RTTI的概念,后续的大多数面向对象语言开始支持这一点,也就使得程序内置了类型名称库,理论上就允许了字元串拼出来的类型名可以使用。

不过无论函数名还是类型名都还没有到普通变数的程度。

再后来我们知道,有了虚拟机这种东西。应用虚拟机的语言,可以支持包括变数名在内的全部符号信息都在运行时间存在。因此别说变数名,就算整个代码都可以用字元串拼接出来。毕竟,虚拟机本身就允许执行原生代码。python,php,lua 等大量的语言都可以支持这一点。

--

回到题主的问题:

  1. 支持你所说功能的语言很多,目前很流行的python就能支持,这也并不是什么大不了的功能,事实上这种功能会对IDE以及程序员间合作带来很多不方便。
  2. 由于工业上最流行的语言基本上都算是C的一代二代三代衍生品,(比方说C++算是一代衍生品,C#,objC,Java算是二代衍生品,Swift,Kotlin勉强算是三代衍生品),而最初的C语言是不支持这些的,所以从C语言派生出来的一系列语言往往都不能支持你所说的功能。而其他的很多与C不同源的语言却往往可以支持这样的功能。


CPU才不需要变数名,只需要内存地址,变数名完全就是为了照顾人类渺小的运存的,不把变数名写出来给人看还有什么意义?


主要取决于解析变数名得到具体存储地址的过程是在编译时进行还是运行时进行,动态语言一般允许运行时通过变数名获取具体存储位置,例如常用的脚本语言Python、PHP、Javascript都是支持的:

Python

a = 1
globals()[a] #1
b = a
globals()[b] #1
# 如果是函数内部要用locals()

PHP

$a = 1;
$b = a;
$c = $$b; # 1

Javascript

let a = 1;
let b = window[a]; # 1
let c = b;
let d = window[b]; # 1

编译型语言一般需要在编译过程中就确定每个变数的存储位置,这样可以提高程序的运行效率,但同时也就丧失了这种动态访问变数的能力。不过一般来说这也无关紧要,因为完全可以自己把需要的值都放到map里面就行了。

但是,现在许多编译型语言其实是支持反射的,所以也不是完全做不到,只是间接引用的变数也需要提前定义好。


如果没理解错的话,Python这类解释型语言是可以实现题主的需求的,例如:

name = input()
globals()[name] = 0

这样的话会定义一个名称为字元串name中内容(这里由用户输入)的全局变数,值为0。局部变数有类似的locals(),但有一些不同,参见这篇文章。

至于C/C++等编译型语言不能实现这种功能的原因,是因为这些语言的变数名在编译后就全部丢失了,剩下的只是内存地址而已。


std::map& variants;
variants.insert(std::make_pair("a", 1));
variants.insert(std::make_pair("b", 2));
std::cout &second &second &


变数名是给代码作者以及编译器用的(解析语言则是解析器),数据本身才是真正有意义的东西。

那么问题来了,你一个变数名为什么还要做运算才能得到,有什么实际意义嘛……大多数语言编译完直接就不存在变数名这种东西了。

如果你说的是要通过计算来确定使用哪个数据,那所有语言都有一大堆数据结构给你选啊。map/list/array都能做,你要字元串索引那map不正好可以用?

不是很明白你想要什么。


因为计算机科学中有两大难题,一个是缓存失效,另外一个就是起名字。


var abc = "1234";
var str;
// some code asking user to input something, and we input "abc"
eval("alert(" + str +")");

这样子吗……最后输出 1234?


因为你这个需求,用一个map&就能做到啊,没什么难度


为什么打了一个 python tag?那就用 python实现一个吧

import random
a = 0
b = 1
c = 2
d = 3
e = 4

print(eval(print( + random.choice([a, b, c, d, e]) +)))


我们管这种变数叫指针或者引用。

把字元串看成整数(地址)就完全符合了题目了

假设有这几个变数,abc = 1,bcd = 2, cde = 3,有一个指针p。我们完全可以让p = abc 或 bcd 或 cde。

因为变数的名字只是地址的另一个表达。不过在一般的实现里,地址不能是字元串。我们当然可以让地址按字元串分布,没有本质的区别。

"abc" : 1
"bcd" : 2
"cde" : 3
p = "abc"
p = "bcd"
p = "cde"


php就可以,而且还是关键语言特性,就这特性折磨的我要死要活的,让我直接理解了为什么php难以维护,我看完书里php这个特性就直接放弃了继续学php了

另外bash也可以做到


还就打上了Python的标签。你需要的需求在Python中完全是可以实现的:

hello = Hello, world!
variable = hello
print(eval(variable)) # =&> Hello, world!
print(globals()[variable]) # =&> Hello, world!

Ruby自从2.1.3开始,除了eval之外,Binding类支持local_variable_get和local_variable_set等骚操作。

如果你将binding泄漏出去,甚至可以让程序从外部修改你的局部变数:

fiber = Fiber.new do
hello = Hello, world!
puts hello
Fiber.yield binding
puts hello
end
b = fiber.resume # =&> Hello, world!
b.local_variable_set :hello, Hello, Ulysses!
fiber.resume # =&> Hello, Ulysses!


lz这是正常的需求,在动态语言中这种操作正常。

以我了解的python为例说明一个使用场景。比如程序的后端存储需要支持不同引擎,根据配置文件中的栏位切换。这就可以拼出字元串后动态载入相应模块。

当然用一堆switch判断,工厂函数实例化不同类同样能实现这种需求。用字元串动态载入可以少写几行代码,两者实现起来效果都一样


首先你要明确变数和变数名的意义。

变数代表计算机内存(也许是寄存器)中的数据,访问变数使用的是内存的地址;变数名只是给这个数据起了一个名字,它是一个代号,是给程序员看的,程度本身并不需要知道这个名字,在机器语言层面,这名字会被内存地址(指针)替换。

所以,变数只和它的内存地址(还有大小)有关,改变变数名字对程序没有任何意义。

你为什么要动态确定变数的名字?我实在想不出使用场景是什么。


并不是实现起来有什么困难,是实现起来没什么意义。

所谓变数名称,只有你在看代码的时候才有意义。程序运行起来,都是0和1,运行时的变数名称是什么,有什么意义吗。


因为程序不仅是给机器看的,也是给人看的。


放弃类型检查来追求一个并没有什么用的特性, 有什么意义啊


推荐阅读:
相关文章