Как команда 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" означает глобальную область видимости. Таким образом, вы можете связать глобальную переменную, тогда как процедура в вашем исходном примере будет связываться только с глобальными переменными, если выполняется в глобальной области.

Другие вопросы по тегам