例如,能否做到定義一個變數,名稱為一個字元串變數的內容。而這個字元串變數的內容是要在計算過程中才知道的,在程序運行前並不知道。

實現起來有什麼困難?


很多腳本語言都允許這麼做,以便實現引用語義(比如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,運行時的變數名稱是什麼,有什麼意義嗎。


因為程序不僅是給機器看的,也是給人看的。


放棄類型檢查來追求一個並沒有什麼用的特性, 有什麼意義啊


推薦閱讀:
相關文章