Как команда upvar работает в TCL?
У меня есть вопрос по поводу команды упвар в TCL. Используя команду upvar, у нас есть ссылка на глобальную переменную или локальную переменную в другой процедуре. Я видел следующий код:
proc tamp {name1 name2} {
upvar $name1 Ronalod
upvar $name2 Dom
set $Dom "Dom"
}
эта процедура называется tamp name1 name2
, и нет глобальных переменных name1, name2, определенных вне его, как этот упвар работает в этом случае?
4 ответа
Когда вы звоните upvar 1 $foo bar
, он находит переменную в области действия вызывающего, имя которой находится в foo
переменная, и делает местный bar
переменная в псевдоним для него. Если переменная не существует, она создается в незаданном состоянии (т. Е. Запись переменной существует, но не имеет значения. Фактически, реализация использует NULL
представлять эту информацию, поэтому Tcl не имеет NULL
эквивалент; NULL
указывает на отсутствие), но ссылка все еще создается. (Он разрушается только при разрушении локальной области видимости или если upvar
используется для указания локальной переменной на что-то еще.)
Итак, давайте посмотрим, что на самом деле делает ваш код:
proc tamp {name1 name2} {
upvar $name1 Ronalod
upvar $name2 Dom
set $Dom "Dom"
}
Первая строка говорит, что мы создаем команду с именем tamp
как процедура, эта процедура будет иметь два обязательных формальных аргумента и что эти аргументы называются name1
а также name2
,
Вторая строка говорит, что мы связываем имя переменной в вызывающей 1
индикатор уровня из моего более раннего объяснения является необязательным, но настоятельно рекомендуется в идиоматическом коде), который задается name1
переменная (т. е. первый аргумент процедуры) для локальной переменной Ronalod
, Отныне все обращения к этой локальной переменной (до конца срока службы стека) будут фактически выполняться для связанной переменной в вызывающей стороне.
Третья строка почти такая же, за исключением name2
(второй аргумент) и Dom
(локальная переменная).
Четвертая строка на самом деле довольно прикольная. Это читает Dom
переменная, чтобы получить имя переменной (т. е. переменную, названную во втором аргументе вызова процедуры) и установить эту переменную в значение Dom
, Помните, в Tcl вы используете $
читать из переменной, не говорить о переменной.
Результатом вызова процедуры будет результат последней команды в ее теле (т. Е. Литерала Dom
поскольку set
выдает содержимое переменной как ее результат, значение, которое она только что присвоила). (Последняя строка совершенно неинтересна, так как это только конец тела процедуры.)
Конечный результат вызова этой команды на самом деле будет практически ничем, если только второй аргумент не назовет переменную, содержащую Ronalod
или же Dom
, Это довольно запутанно. Конечно, сбивающий с толку бит действительно set
с переменным первым аргументом. (Это почти всегда плохая идея; это показатель плохого запаха кода.) Если бы вы использовали это вместо этого, все было бы проще:
set Dom "Dom"
В этом случае переменная, которая Dom
связан с (то есть, тот, который назван вторым аргументом процедуры) будет установлен в Dom
; переменные в действительности будут передаваться по ссылке. Это лишнее $
имеет огромное значение!
name1 и name2 не существуют в вызывающей области - они просто параметры вашего процесса. Например, вы можете вызвать proc следующим образом:
% set first "James"
James
% set last "Bond"
Bond
% tamp first last
Dom
В настоящее время ваш процесс ничего не делает. Если вы измените последнюю строку следующим образом, это имеет больше смысла:
proc tamp {name1 name2} {
upvar $name1 Ronalod
upvar $name2 Dom
set Dom "Dom"
}
% tamp first last
Dom
% puts $first
James
% puts $last
Dom
Одним из лучших руководств, которые я видел, чтобы использовать upvar и uplevel, является руководство Роба Майоффа http://www.dqd.com/~mayoff/notes/tcl/upvar.html
Я добавляю еще один пример, чтобы помочь вам увидеть, что name1 и name2 являются просто входными параметрами и не должны существовать глобально. Запустите этот пример в tclsh и посмотрите, имеет ли он смысл.
% proc tamp {name1 name2} {
upvar $name1 Ronalod
upvar $name2 Dom
puts "Ronalod=$Ronalod, Dom=$Dom"
set Ronalod "Brian"
set Dom "Fenton"
puts "NOW: Ronalod=$Ronalod, Dom=$Dom"
}
%
% tamp name1 name2
can't read "Ronalod": no such variable
% set first "James"
James
% set last "Bond"
Bond
% tamp first last
Ronalod=James, Dom=Bond
NOW: Ronalod=Brian, Dom=Fenton
% puts $first
Brian
% puts $last
Fenton
Итак, upvar в tcl, используется для определения переменных, которые будут использоваться после выполнения процедуры. Пример:
proc tamp {name1 name2} {
upvar 1 $name1 Ronalod
upvar 1 $name2 Dom
set $Dom "Dom"
}
#Call the proc tamp
tamp $name1 $name2
#Now we can use the vars Ronalod and Dom
set all_string "${Ronalod}${Dom}"
Теперь номер 1
в следующей команде
upvar 1 $name2 Dom
Укажите уровень, в котором вы можете использовать переменные, например, если мы используем 2
упвар 2 $ имя2 дом
так что я могу сделать это:
proc tamp_two {name1 name2} {
# do something ...
tamp $name1 $name2
}
proc tamp {name1 name2} {
upvar 2 $name1 Ronalod
upvar 2 $name2 Dom
set $Dom "Dom"
}
#Call the proc tamp_two
tamp_two $name1 $name2
#Now we can use the vars Ronalod and Dom
set all_string "${Ronalod}${Dom}"
это может быть возможно с upvar 2
, если мы будем продолжать upvar 1
не работает
Я надеюсь, что это будет полезно для вас.
Когда интерпретатор Tcl входит в процедуру, написанную на Tcl, он создает специальную таблицу переменных, локальных для этой процедуры, пока выполняется ее код. Эта таблица может содержать как "реальные" локальные переменные, так и специальные "ссылки" на другие переменные. Такие ссылки неотличимы от "реальных" переменных до тех пор, пока команды Tcl (такие как set
, unset
и т. д.) обеспокоены.
Эти ссылки созданы upvar
команда, и она может создать ссылку на любую переменную в любом кадре стека (включая глобальную область видимости - кадр 0).
Так как Tcl очень динамичен, его переменные могут приходить и уходить в любое время, поэтому переменная связана с upvar
может не существовать на момент создания ссылки на него, обратите внимание:
% unset foo
can't unset "foo": no such variable
% proc test name { upvar 1 $name v; set v bar }
% test foo
bar
% set foo
bar
Обратите внимание, что я сначала демонстрирую, что переменная с именем "foo" не существует, затем устанавливаю ее в процедуре, которая использует upvar
(и переменная создается автоматически), а затем продемонстрируйте, что переменная существует после завершения процедуры.
Также обратите внимание, что upvar
не о доступе к глобальным переменным - это обычно достигается с помощью global
а также variable
команды; вместо, upvar
используется для работы с переменными, а не со значениями. Это обычно необходимо, когда нам нужно что-то изменить "на месте"; Одним из лучших примеров этого является lappend
команда, которая принимает имя переменной, содержащей список, и добавляет один или несколько элементов в этот список, изменяя его на месте. Чтобы достичь этого, мы передаем lappend
имя переменной, а не просто значение списка. Теперь сравните это с linsert
команда, которая принимает значение, а не переменную, поэтому она берет список и создает другой список.
Следует также отметить, что по умолчанию (в форме с двумя аргументами) upvar
ссылки на переменную с указанным именем на один уровень выше стека, а не на глобальную переменную. Я имею в виду, вы можете сделать это:
proc foo {name value} {
upvar $name v
set v $value
}
proc bar {} {
set x ""
foo x test
puts $x ;# will print "test"
}
В этом примере процедура "foo" заменяет переменную local на процедуру "bar".
Следовательно, чтобы сделать намерение более ясным, многие люди предпочитают всегда указывать количество кадров стека upvar
должен "подняться", как в upvar 1 $varName v
который так же, как upvar $varName v
но понятнее.
Другое полезное применение этого относится к локальным переменным, определяя нулевые уровни стека для подъема - этот прием иногда полезен для более удобного доступа к переменным в массивах:
proc foo {} {
set a(some_long_name) test
upvar 0 a(some_long_name) v
puts $v ;# prints "test"
upvar a(some_even_more_long_name) x
if {![info exists x]} {
set x bar
}
}
В качестве бонуса отметим, что upvar
также понимает абсолютные числа стековых фреймов, которые указаны с использованием префикса "#", а "#0" означает глобальную область видимости. Таким образом, вы можете связать глобальную переменную, тогда как процедура в вашем исходном примере будет связываться только с глобальными переменными, если выполняется в глобальной области.