2012-06-15 7 views
8

Tôi có câu hỏi về lệnh upvar trong TCL. Sử dụng lệnh upvar, chúng ta có một tham chiếu đến một biến toàn cục hoặc một biến cục bộ trong thủ tục khác. Tôi thấy đoạn mã sau:cách lệnh upvar hoạt động trong TCL?

proc tamp {name1 name2} { 
    upvar $name1 Ronalod 
    upvar $name2 Dom 
    set $Dom "Dom" 
} 

thủ tục này được gọi là tamp name1 name2, và không có các biến toàn cục name1, name2 định nghĩa bên ngoài của nó, làm thế nào upvar này hoạt động trong trường hợp này?

Trả lời

11

Khi bạn gọi upvar 1 $foo bar, nó tìm thấy các biến trong phạm vi của người gọi tên là trong biến foo, và làm cho bar biến cục bộ vào một bí danh cho nó. Nếu biến không tồn tại, đó là đã tạo ra ở trạng thái chưa được đặt (tức là bản ghi biến tồn tại nhưng không có giá trị. Thực tế, việc triển khai sử dụng NULL để trình bày thông tin đó, đó là lý do tại sao Tcl không có NULL tương đương; NULL cho biết không tồn tại) nhưng liên kết vẫn được tạo. (Nó chỉ bị rách xuống khi phạm vi địa phương bị phá hủy hoặc nếu upvar được sử dụng để chỉ các biến cục bộ tại một cái gì đó khác.)

Vì vậy, chúng ta hãy nhìn vào những gì mã của bạn là thực sự thực hiện:

proc tamp {name1 name2} { 
    upvar $name1 Ronalod 
    upvar $name2 Dom 
    set $Dom "Dom" 
} 

Dòng đầu tiên nói rằng chúng ta đang tạo một lệnh gọi là tamp làm thủ tục, rằng thủ tục đó sẽ có hai đối số chính thức bắt buộc và các đối số đó được gọi là name1name2. Dòng thứ hai nói rằng chúng tôi đang ràng buộc tên biến trong người gọi (chỉ báo mức 1 từ lời giải thích trước đây của tôi là tùy chọn, nhưng được khuyến cáo mạnh mẽ trong mã thành ngữ) được đưa ra bởi biến số name1 (ví dụ: đối số cho quy trình) với biến cục bộ Ronalod. Từ nay, tất cả các truy cập tới biến cục bộ đó (cho đến khi kết thúc vòng đời của khung stack) sẽ thực sự được thực hiện trên biến bị ràng buộc trong người gọi.

Dòng thứ ba khá giống nhau, ngoại trừ name2 (đối số thứ hai) và Dom (biến cục bộ).

Dòng thứ tư thực sự khá thú vị. Nó đọc biến số Dom để nhận tên biến (tức là, biến được đặt tên trong đối số thứ hai cho cuộc gọi thủ tục) và đặt biến được đặt tên thành giá trị Dom. Hãy nhớ rằng, trong Tcl, bạn sử dụng $ để đọc từ một biến, không phải để nói về biến số.

Kết quả của cuộc gọi thủ tục sẽ là kết quả của lệnh cuối cùng trong cơ thể của nó (tức là, Dom theo nghĩa đen kể từ set mang lại nội dung của biến như kết quả của nó, giá trị mà nó vừa gán). (Dòng cuối cùng hoàn toàn không thú vị vì nó chỉ là phần cuối của nội dung thủ tục.)

Kết quả thực sự gọi lệnh này thực sự sẽ không có gì trừ khi đối số thứ hai đặt tên biến có chứa Ronalod hoặc Dom. Đó là khá khó hiểu. Tất nhiên, bit khó hiểu thực sự là funky set với một đối số đầu tiên biến. (Đó là gần như luôn luôn là một ý tưởng tồi, nó là một chỉ thị của Bộ luật mùi hôi.) Đã bạn sử dụng này để thay thế, mọi thứ sẽ được đơn giản hơn:

set Dom "Dom" 

Trong trường hợp này, biến mà Dom là cùng với (ví dụ, cái được đặt tên bởi đối số thứ hai cho quy trình) sẽ được đặt thành Dom; các biến có hiệu lực sẽ được truyền qua tham chiếu. Thêm $ tạo nên sự khác biệt lớn!

1

name1 và name2 không tồn tại trong phạm vi cuộc gọi - chúng chỉ là tham số cho proc của bạn. Ví dụ: bạn có thể gọi cho proc như sau:

% set first "James" 
James 
% set last "Bond" 
Bond 
% tamp first last 
Dom 

Vì nó là viết tắt của bạn thực sự không làm bất cứ điều gì. Nếu bạn thay đổi dòng cuối cùng như sau, sau đó nó có ý nghĩa hơn:

proc tamp {name1 name2} { 
    upvar $name1 Ronalod 
    upvar $name2 Dom 
    set Dom "Dom" 
} 
% tamp first last 
Dom 
% puts $first 
James 
% puts $last 
Dom 

Một trong những hướng dẫn tốt nhất mà tôi đã nhìn thấy sử dụng của upvar và uplevel là hướng dẫn Rob Mayoff của http://www.dqd.com/~mayoff/notes/tcl/upvar.html


tôi thêm một ví dụ nữa để giúp bạn thấy rằng name1 và name2 chỉ là các tham số đầu vào và không cần tồn tại trên toàn cầu. Chạy ví dụ này trong tclsh và xem nó có ý nghĩa hơn không.

% 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 
+0

nếu name1 và name2 là các tham số đơn giản, sau đó trong chế độ thuần hóa proc, upvar có nghĩa là gì? ý nghĩa tương tự như "set name1 Ronaldo"? –

+0

Xin chào! "upvar $ name1 Ronalod" theo nghĩa đen là tạo ra một biến có phạm vi cục bộ được gọi là Ronalod, được liên kết với biến được tham chiếu bởi $ name1. Sau đó, bất kỳ thay đổi nào đối với Ronalod cũng sẽ thay đổi biến được tham chiếu bởi $ name1. Bạn có thể thấy rằng trong ví dụ của tôi, giá trị của "cuối cùng" đã được thay đổi bởi tập Dom "Dom". – TrojanName

+0

cảm ơn bạn, nhưng trong mã của tôi, không có biến toàn cầu name1, name2, vì vậy nếu bạn đặt Dom "Dom", có nghĩa là biến Dom được thay đổi và biến được tham chiếu bởi $ name1 cũng được thay đổi, nhưng trong trường hợp của tôi, biến được tham chiếu bởi $ name1 không tồn tại? –

1

Khi intrreter Tcl nhập một thủ tục được viết bằng Tcl, nó tạo ra một bảng biến đặc biệt cục bộ cho thủ tục đó trong khi mã của nó đang được thực thi. Bảng này có thể chứa cả các biến cục bộ "thực" và "liên kết" đặc biệt cho các biến khác. Các liên kết này không thể phân biệt được với các biến "thực" miễn là các lệnh Tcl (chẳng hạn như set, unset v.v.) có liên quan.

Các liên kết này được tạo bởi lệnh upvar và có thể tạo liên kết tới bất kỳ biến nào trên khung ngăn xếp (bao gồm phạm vi toàn cầu — khung 0).

Kể từ Tcl là rất năng động, các biến của nó có thể đến và đi bất cứ lúc nào và do đó, một biến liên quan đến bằng cách upvar có thể không tồn tại đồng thời liên kết với nó được tạo ra, quan sát:

% 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 

Lưu ý rằng Lần đầu tiên tôi chứng minh rằng biến có tên "foo" không tồn tại, sau đó đặt biến trong thủ tục sử dụng upvar (và biến được tự động xử lý) và sau đó chứng minh rằng biến tồn tại sau khi thủ tục đã thoát.

Cũng lưu ý rằng upvar không phải là về truy cập biến toàn cầu — điều này thường đạt được bằng cách sử dụng các lệnh globalvariable; thay vào đó, upvar được sử dụng để làm việc với các biến số thay vì giá trị . Điều này thường là cần thiết khi chúng ta cần phải thay đổi một cái gì đó "tại chỗ"; một trong những ví dụ tốt hơn về điều này là lệnh lappend chấp nhận tên của một biến có chứa danh sách và nối thêm một hoặc nhiều phần tử vào danh sách đó, thay đổi nó tại chỗ. Để đạt được điều này, chúng tôi vượt qua lappend tên của một biến, không chỉ là giá trị danh sách. Bây giờ, hãy so sánh điều này với lệnh linsert chấp nhận giá trị, không phải biến, do đó, cần có danh sách và tạo một danh sách khác.

Một điều cần lưu ý là theo mặc định (trong biểu mẫu hai đối số), upvar liên kết đến biến có tên được chỉ định một cấp lên ngăn xếp, không đến biến toàn cầu. Ý tôi là, bạn có thể làm điều này:

proc foo {name value} { 
    upvar $name v 
    set v $value 
} 

proc bar {} { 
    set x "" 
    foo x test 
    puts $x ;# will print "test" 

}

Trong ví dụ này, các thủ tục "foo" thay đổi biến cục bộ thủ tục "thanh".

Do đó, để làm cho mục đích rõ ràng hơn, nhiều người thích luôn chỉ định số khung ngăn xếp upvar nên "trèo lên", như trong upvar 1 $varName v tương tự như upvar $varName v nhưng rõ ràng hơn.

Một ứng dụng hữu ích của việc này là đề cập đến biến địa phương, bằng cách xác định zero mức ngăn xếp để leo lên — thủ thuật này đôi khi hữu ích để thuận tiện hơn truy cập các biến trong mảng:

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 
    } 
} 

Như một phần thưởng, lưu ý rằng upvar cũng hiểu số lượng tuyệt đối của khung ngăn xếp được chỉ định bằng tiền tố "#" và "# 0" có nghĩa là phạm vi toàn cầu. Bằng cách đó, bạn có thể liên kết với một biến toàn cục trong khi quy trình trong ví dụ ban đầu của bạn sẽ chỉ liên kết với các biến chung nếu được thực hiện trong phạm vi toàn cục.

1

Vâng upvar trong tcl, được sử dụng để xác định các vars sẽ được sử dụng sau khi proc được thực hiện. Ví dụ:

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}" 

Bây giờ số 1 trong lệnh sau

upvar 1 $name2 Dom 

Nêu rõ mức độ trong đó bạn có thể sử dụng vars, ví dụ nếu chúng ta sử dụng 2

upvar 2 $ name2 Dom

vì vậy tôi có thể làm điều này:

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}" 

điều này có thể được thực hiện với upvar 2, nếu chúng tôi giữ upvar 1, nó không hoạt động.

Tôi hy vọng điều này sẽ hữu ích đối với bạn.