5. Khai báo và sử dụng chương trình con:
5.5 Biến cục bộ trong hàm
Chúng ta đã nói tới biến @_ và cách thức việc sao chép cục bộ được tạo ra cho từng trình con có gọi tới tham biến. Bạn có thể tạo ra các biến vô hướng, mảng hay mảng kết hợp của riêng mình làm việc theo cùng cách. Bạn làm điều này với toán tử local() hoặc
my(), local và my nhận một danh sách các tên biến và tạo ra các bản cục bộ của chúng (hay các thể nghiệm). Sau đây lại là hàm cộng ở trên, lần này dùng my():
Chú ý: Theo tài liệu của Per 5, bạn nên dùng my thay vì local. sub add {
my $sum = 0 ; #tao bien cuc bo $sum va khoi tao gia tri foreach $_ (@_ ) {
$sum += $_ ; #cong tung phan tu }
return $sum; #tra ve ket qua }
$a = add(4,5,6) ; #cong 4+5+6 = 15, va gan cho $a print add(1,2,3,4,5) ; #in ra 15
print add(1..5); #cung in ra 15, vi 1..5 duocc mo rong thanh 1,2,3,4,5
Khi câu lệnh thân đầu tiên được thực hiện (lệnh my), thì bất kì giá trị hiện tại nào của biến toàn cục $sum cũng đều được cất giữ và một biến mới mang tên $sum sẽ được tạo ra (với giá trị undef). Khi chương trình con kết thúc, Perl bỏ qua biến cục bộ và khôi phục giá trị trước (toàn cục) của $sum. Điều này vận hành cả khi biến $sum hiện là biến cục bộ của một trình con khác (một trình con mà gọi tới trình con này, hay một trình con gọi tới một trình mà gọi tới trình con này...). Các biến có thể có nhiều bản cục bộ lồng nhau, mặc dầu bạn có thể thâm nhập mỗi lúc chỉ vào một biến.
Sau đây là cách để tạo ra một danh sách tất cả các phần tử có giá trị lớn hơn 100 của một mảng lớn:
my (@result);
foreach $_ (@_ ) { #duyet qua danh sach
if ($_ > 100) { #kiem tra xem co lon hon 100? push (@result, $_); #day vao danh sach
}
return @result ; #tra ve ket qua }
Điều gì xảy ra nếu chúng ta muốn tất cả các phần tử này lớn hơn 50 thay vì 100? Chúng ta phải sửa chương trình này, đổi tất cả các số 100 thành 50. Nhưng điều gì xảy ra nếu chúng ta lại cần cả hai? Được, chúng ta có thể thay thế 50 hay 100 bằng một biến tham khảo. Điều này làm chương trình trông giống thế này:
sub bigger_than {
my ($n, @values); #tao ra cac biet cuc bo
($n, @values) = @_; #khoi tao gia tri cho bien cuc bo, thuc chat la lay gia tri tu cac tham so
my (@result); #them 1 bien cuc bo nua
foreach $_ (@values) { #duyet danh sach cac gia tri
if ($_ > $n) { #phan tu nay co gia tri lon hon $n (=50, 100 hay gia tri nao do)
push (@result, $_) ; #dua vao danh sach neu hop le }
} #vi du
@new = bigger_than(100, @list); #tim cac phan tu lon hon 100 trong mang @list @this = bigger_than(5,1,5,15,30); #tim cac phan tu lon hon 5 trong danh sach, ket qua la (15, 30)
Lưu ý rằng lần này tôi đã dùng hai biến cục bộ phụ để đặt tên cho các đối số. Điều này khá thông dụng trong thực hành - ghi và truy cập vào $n và @values sẽ dể hiểu và dễ ghi nhớ hơn là $_[0] và @_[1..$#_] rất nhiều.
Kết quả của my() và local() là một danh sách gán được, nghĩa là nó có thể được dùng ở vế bên trái của toán tử gán mảng. Danh sách này có thể được đặt giá trị khởi đầu cho từng biến mới được tạo ra. (Nếu bạn không đặt giá trị cho danh sách này, thì các biến mới bắt đầu với một giá trị của undef, giống như bất kì biến mới nào khác). Điều này có nghĩa là chúng ta có thể tổ hợp hai câu lệnh đầu của trình con này, bằng cách thay thế:
my ($n, @values); ($n, @values) = @_; bằng
my ($n, @values) = @_;
Và trên thực tế, tôi đã dùng lối thay thế này ở các ví dụ đầu tiên bạn còn nhớ không? Trong thực tế, đây là một thứ rất đặc thù thông dụng Perl cũng hệt như khai báo vậy, my() thực sự là một toán tử thực hiện được. Nếu bạn đặt nó vào bên trong chu trình, thì bạn sẽ thu được một biến mới cho mỗi lần lặp chu trình, mà gần như là vô dụng trừ phi bạn thích lãng phí bộ nhớ và quên mất mình đã tính gì trong lần lặp trước. Chiến lược lập trình Perl tốt gợi ý rằng bạn nên nhóm tất cả các toán tử my() của mình vào phần
đầu định nghĩa chương trình con, trước khi bạn vào phần thân của chương trình con này.