2016-05-03 17 views
13

Trong Perl 5.20, vòng lặp for dường như có thể sửa đổi biến có phạm vi mô-đun nhưng không phải là biến lexical trong phạm vi gốc.cho vòng lặp không sửa đổi biến `my` nhưng sửa đổi biến` của chúng tôi

#!/usr/bin/env perl 
use strict; 
use warnings; 

our $x; 

sub print_func { 
    print "$x\n"; 
} 

for $x (1 .. 10) { 
    print_func; 
} 

in từ 1 đến 10 như bạn mong đợi, nhưng sau không:

#!/usr/bin/env perl 
use strict; 
use warnings; 

my $x; 

sub print_func { 
    print "$x\n"; 
} 

for $x (1 .. 10) { 
    print_func; 
} 

phát ra cảnh báo sau 10 lần:

Use of uninitialized value $x in concatenation (.) or string at perl-scoping.pl line 8. 

gì đang xảy ra ở đây? Tôi biết rằng các chương trình con perl không thể lồng nhau (và luôn có phạm vi mô-đun) và do đó có vẻ logic rằng chúng sẽ không thể đóng các biến số my. Dường như trong trường hợp đó, perl ở chế độ strict sẽ từ chối chương trình thứ hai bằng một thông báo như sau:

Global symbol "$x" requires explicit package name at perl-scoping.pl line 6. 
Global symbol "$x" requires explicit package name at perl-scoping.pl line 9. 

I.e. nó sẽ từ chối chương trình con vì biến miễn phí không được khai báo ở bất kỳ đâu và vòng lặp for bởi vì biến chưa được khai báo.

Tại sao Perl hoạt động theo cách này?

Trả lời

17

Thật khó hiểu, nhưng được ghi lại, hành vi có thể xuất phát từ quyết định không hợp lệ để biến biến vòng lặp lặp lại thành một ngôn ngữ toàn cục tiềm ẩn chứ không phải là từ vựng. Từ Foreach Loops in perlsyn.

Nếu biến được bắt đầu bằng từ khóa my, sau đó nó được lexically scoped, và do đó chỉ hiển thị trong vòng lặp. Nếu không, biến là ngầm cục bộ cho vòng lặp và lấy lại giá trị cũ của nó khi thoát khỏi vòng lặp. Nếu biến trước đây được khai báo với my, biến này sử dụng biến đó thay vì biến số toàn cầu, nhưng biến đó vẫn được bản địa hóa thành vòng lặp.

Nói một cách khác, vòng lặp luôn cục bộ để vòng lặp. Nếu nó là toàn cục thì nó hoạt động như được tuyên bố là local bên trong khối vòng lặp. Nếu nó là một từ vựng, thì nó hoạt động giống như nó được khai báo với my bên trong khối vòng lặp.

Áp dụng điều này cho hai ví dụ của bạn sẽ giúp hiểu những gì đang xảy ra.

our $x; 

sub print_func { 
    print "$x\n"; 
} 

for $x (1 .. 10) { 
    print_func; 
} 

Có ẩn ngụ local $x trên vòng lặp đó. local thực sự phải được đặt tên là temp. Nó tạm thời ghi đè giá trị của một biến toàn cầu trong khoảng thời gian phạm vi của nó, nhưng nó vẫn là số toàn cầu. Đó là lý do tại sao print_func có thể xem.

Giá trị cũ được khôi phục khi phạm vi của nó kết thúc. Bạn có thể thấy điều này nếu bạn thêm print $x sau vòng lặp for.

use v5.10; 

our $x = 42; 

for $x (1 .. 10) { 
    say $x; 
} 

say $x; # 42 

Hãy xem xét mã của bạn liên quan đến lexicals (my biến).

my $x; 

sub print_func { 
    print "$x\n"; 
} 

for $x (1 .. 10) { 
    print_func; 
} 

Điều thực sự xảy ra ở đây là bạn có hai biến từ vựng được gọi là $x. Một là tập tin phạm vi, một là phạm vi vòng lặp. Các bên trong $x trên vòng lặp for có tiền lệ trên bên ngoài $x. Điều này được gọi là "bóng tối".

Không thể nhìn thấy các từ vựng bên ngoài phạm vi vật lý của chúng. print_func() chỉ nhìn thấy bên ngoài uninitialized $x.


Có một số cách lấy cảm hứng từ phong cách này.

Luôn chuyển các thông số vào các chức năng của bạn.

Trong thực tế, print_func nên tham số. Sau đó, bạn không phải lo lắng về các quy tắc phạm vi phức tạp.

sub print_func { 
    my $arg = shift; 
    print "$arg\n"; 
} 

for $x (1..10) { 
    print_func($x); 
} 

Luôn luôn sử dụng for my $x.

Đừng dựa vào quy tắc phạm vi vòng lặp phức tạp for. Luôn khai báo trình lặp vòng lặp với my.

for my $x (1..10) { 
    print_func($x); 
} 

Tránh hình cầu.

Vì khó có thể biết được những gì đang truy cập toàn cầu, không sử dụng chúng. Nếu bạn nghĩ rằng bạn cần một toàn cầu, hãy viết một hàm thay vì kiểm soát quyền truy cập vào một tập tin có từ vựng.

my $Thing = 42; 
sub get_thing { return $Thing } 
sub set_thing { $Thing = shift; return } 

Khai báo các biến của bạn gần đến nơi mà họ đang sử dụng.

Kiểu mã hóa cũ sẽ làm những việc như khai báo tất cả các biến ở đầu tệp hoặc chức năng. Đây là một tổ chức từ các ngôn ngữ rất, rất, rất cũ mà yêu cầu các biến được khai báo chỉ ở những nơi nhất định. Perl, và hầu hết các ngôn ngữ hiện đại, không có hạn chế như vậy.

Nếu bạn khai báo tất cả các biến cùng một lúc, thật khó để biết chúng là gì, và thật khó để biết những gì đang sử dụng hoặc ảnh hưởng đến nó. Nếu bạn tuyên bố nó gần với việc sử dụng đầu tiên của nó giới hạn những gì có thể ảnh hưởng đến nó, và làm cho nó rõ ràng hơn những gì nó cho.

+0

Có vẻ như 'với $ x (...) {...}' tương đương với 'do {local $ x; với $ x (...) {...}}; ', nhưng khi tôi thay thế, tôi nhận được biến' không thể bản địa hóa từ $ x tại $ perl-scoping.pl' ... Tại sao không phải vậy lỗi cũng được kích hoạt trong phiên bản gốc? –

+2

@GregoryNisbet Điều đó sẽ không hoạt động vì 'local' không hoạt động trên từ vựng và tôi đoán bạn có' my $ x' đã có trong phạm vi. Ngược lại, sự tương tự của bạn về cơ bản là chính xác * nếu không có '$ x' trong phạm vi, hoặc nếu nó là toàn cầu *. Các hành vi của 'cho $ x (...) {...}' thay đổi nếu đã có một từ vựng '$ x' trong phạm vi. Trong trường hợp này 'với $ x' khai báo một từ vựng mới bên trong vòng lặp, do đó, nó giống như' do {my $ x; cho $ x (...) {...}}; '. Đây là lý do # 1398 lý do tại sao bạn nên luôn rõ ràng và viết 'cho $ x' của tôi. – Schwern

+0

Tôi đoán ngay bây giờ tôi đang cố gắng giải thích tại sao sức mạnh Perl không quyết định từ chối 'cho $ x (...) {...}' ở chế độ nghiêm ngặt hoặc làm cho nó phát ra một cảnh báo. Bạn có biết nếu nó đến trước? –

Các vấn đề liên quan