каковы практические различия между upvar и глобальными командами в tcl

Я новичок в TCL и предоставляю QA для некоторого кода, разработанного другими (на самом деле нет!). В этой конкретной программе очень много глобальных переменных, и я иногда вижу, как используется upvar, часто в сочетании с global. Я понимаю, что upvar эмулирует передачу по ссылке, но в чем будет практическая разница между двумя следующими процедурами?

set myBigFatGloblVariable "hello"

proc myFirstProc { var1 var2 } {
    upvar 1 $var1 local
    set local [expr $var2 * 3]
}

proc mySecondProc { var2 } {
    global myBigFatGlobalVariable
    set $myBigFatGlobalVariable [expr $var2 * 3]
} 

myFirstProc $myBigFatGlobalVariable 3
mySecondProc 3

Мне кажется, myFirstProc был бы чище и. Я что-то упустил?

Ответов (4)

Решение
set myBigFatGlobalVariable "hello"

proc myFirstProc { var1 var2 } {
    upvar 1 $var1 local
    set local [expr $var2 * 3] }

proc mySecondProc { var2 } {
    global myBigFatGlobalVariable
    set $myBigFatGlobalVariable [expr $var2 * 3] } 

myFirstProc $myBigFatGlobalVariable 3
mySecondProc 3

Большая разница между вашими двумя процессами заключается в следующем: myFirstProc устанавливает глобальный «привет», mySecondProc устанавливает локальный «привет».

mySecondProc ссылается на глобальный myBigFat ..., чтобы получить значение "hello", но не изменяет область действия переменной "hello".

myFirstProc получает значение «hello» в качестве параметра, а затем создает связь между переменной с именем «hello» на один кадр вверх по стеку и локальной переменной «local». Установка "local" приводит к установке "hello" на один кадр вверх по стеку.

Чтобы увидеть:


myFirstProc $myBigFatGlobalVariable 3
puts $hello ;# ==> 9
unset hello
mySecondProc 3
puts $hello ;# ==> can't read "hello": no such variable

Если вы действительно хотите установить глобальное приветствие от mySecondProc, вам нужно добавить global $myBigFatGlobalVariable

Они похожи, но немного отличаются.

upvar позволяет вам получить доступ к переменным до x уровней в стеке вызовов. Они не обязательно должны быть глобальными переменными.

Вы можете использовать upvar для эмуляции global, передав upvar # 0 varName localVarName В этом случае вы получите глобальную переменную с локальным именем.

Чтобы имитировать передачу по ссылке, вы передаете имя переменной, а затем вызываете upvar для этого имени.

Если вы знаете имя переменной, вы можете использовать его как есть.

Обратите внимание на следующий код:

    # here there is only 1 global variable, but we also need to access to variables defined in the calling functions
    proc p3 {} {
        # upvar defaults to 1, so not needed to put in here
        # also notice you can call upvar on more than one variable
        upvar dog myDog horse myHorse cat myCat
        upvar 2 cow myCow alpha myAlpha
        upvar #0 samurai mySamurai
        puts "Level 1: $myDog $myHorse $myCat"
        puts "Level 2: $myCow $myAlpha"
        puts "Global : $mySamurai"
    }
    proc p2 {} {
        set dog "bowow"
        set horse "niegh"
        set cat "meow"
        p3

    }
    proc p1 {} {
        set cow "moo"
        set alpha "beta"
        p2
    }

    set samurai "japan"
    p1

Это возвращает

    Level 1: bowow niegh meow
    Level 2: moo beta
    Global : japan

upvar - это просто способ получить переменные из стека вызовов. (вызывающие функции), включая «глобальный» стек.

Разница в том, что upvar 1 $ var local заставляет local брать свое значение из переменной, названной в $ var с уровня выше. Таким образом, в myBigFatGlobalVariable $ var необязательно определять в глобальной области видимости.

proc p1 { var1 } {
upvar 1 $var1 local1
puts $local1
}

proc p2 { } {
set local2 "local2"
p1 local2
}

set global1 "global1"
p1 global1
p2

p1 распечатает значение var1 с уровня 1 над ним в стеке вызовов. Глобальная переменная всегда определяется на верхнем уровне, поэтому upvar # 0 делает то же самое, что и global.

Ты говоришь:

В этой конкретной программе очень много глобальных переменных.

Мой опыт работы со средними и очень большими приложениями Tcl (более 20 тысяч строк!) Показывает, что использование пространств имен значительно поможет получить структуру в большом количестве глобальных переменных.

Приятная часть заключается в том, что вы можете добавлять их итеративно каждый раз, когда вы создаете новый модуль в свой код, или путем рефакторинга части вашего кода.

namespace eval module1 {
  variable counter
  variable name
}

namespace eval module2 {
  variable n
  variable names
}

Вы можете ссылаться на них через module1 :: counter (точно так же, как вы можете ссылаться на глобальную переменную как :: counter

См. Страницу пространства имен вики и страницу руководства Tcl о пространствах имен для получения дополнительной информации о пространствах имен.