Lessons VIII : Xác định một hàm tiện ích

Perl

Chúng ta đều đã thấy và đã dùng các hàm được thiế kế sẵn (có sẵn) như chop, print…Bây giờ ta hãy nhìn vào các hàm mà bạn định nghĩa ra, tạo nên các lệnh chương trình Perl.

8.1 Xác định một hàm tiện ích

Một hàm tự tạo, thường hay được gọi là chương trình con hay trình con, được xác định trong chương trình Perl của bạn bằng việc dùng một kết cấu như:

sub  subname {

cau lenh 1;

cau lenh 2;

cau lenh 3;

….

}

subname là tên của chương trình con, là bất kì tên nào giống như tên ta đã đặt cho biến vô hướng, mảng và mảng băm (cho nên bạn có thể có một biến vô hướng $jerry, một mảng @jerry, một mảng kết hợp %jerry, và bây giờ một trình con jerry.

Khối các câu lệnh đi sau tên trình con trở thành định nghĩa của trình con. Khi trình con được gọi tới (được mô tả ngắn gọn), thì khối các câu lệnh tạo nên trình con này sẽ được thực hiện, và bất kì giá trị trả về nào (được mô tả sau đây) đều được trả về cho nơi gọi.

Chẳng hạn sau đây là một trình con cho hiển thị câu nói nổi tiếng:

sub say_hello {

print “Xin chao moi nguoi!\n”;

}

Định nghĩa trình con có thể ở bất kì đâu trong văn bản chương trình của bạn, nhưng bạn nên đặt tất cả các trình con vào cuối tệp, để cho phần còn lại của chương trình có vẻ như là ở đầu tệp. (Nếu bạn thích nghĩ theo kiểu Pascal thì bạn có thể đặt các trình con của mình vào đầu và các câu lệnh thực hiện vào cuối).

Các định nghĩa trình con là toàn cục; không có trình con cục bộ. Nếu bạn có hai định nghĩa trình con với cùng tên thì trình sau sẽ đè lấp trình trước mà không có cảnh báo gì cả.

Bên trong thân trình con, bạn có thể thâm nhập hay đặt các giá trị cho các biến được dùng chung với phần còn lại của chương trình (biến toàn cục). Trong thực tế, theo mặc định, mọi tham khảo biến bên trong thân trình con đều tham khảo tới biến toàn cục. Nhưng bạn có thể dùng các biệt lệ trong mục “Biến cục bộ trong hàm” ở dưới đây. Trong thí dụ sau:

sub  say_what  {

print  “Xin chao, $what\n”;

}

$what tham khảo tới giá trị toàn cục cho $what và được dùng chung với phần còn lại của chương trình.

8.2 Gọi một hàm tiện ích

Bạn gọi một trình con từ bên trong bất kì biểu thức nào bằng việc ghi ra tên của chương trình con và theo sau là danh sách các tham số được đặt trong hai dấu ngoặc (), như kiểu thế này:

say_hello();           #mot bieu thuc don gian

$a = 3 + say_hello();  #mot bieu thuc kep

for ($x = start_value(); $x < end_value(); $x += increment())  {

} #goi 3 trinh con

Một trình con có thể gọi một trình con khác, và trình con khác này đến lượt nó lại có thể gọi trình con khác nữa, và cứ như thế, cho tới khi tất cả bộ nhớ có sẵn đã bị chất đầy bằng địa chỉ quay về và các biểu thức được tính toán hết.

8.3 Giá trị trả về

Giống như trong C, một trình con bao giờ cũng là một phần của một biểu thức nào đó (không có cái tương đương trong lời gọi thủ tục tựa Pascal. Chắc chắn là như thế!!!). Giá trị của việc gọi trình con được gọi là giá trị trả lại. Giá trị trả lại của một trình con là giá trị của biểu thức cuối cùng được tính bên trong thân của trình con cho mỗi lần gọi.

Chẳng hạn, ta hãy định nghĩa trình con này:

sub  sum_of_a_and_b  {

$a + $b;

}

Bạn cũng có thể dùng câu lệnh return để trả về giá trị từ trình con:

sub  sum_of_a_and_b  {

return $a + $b;

}

Biểu thức cuối cùng được tính trong thân của trình con này (trong thực tế, đó là biểu thức duy nhất được tính) là tổng của $a và $b, cho nên tổng của $a và $b sẽ là giá trị cho lại. Sau đây là thí dụ:

$a = 3; $b = 4;

$c = sum_of_a_and_b();    #$c = 7

$d = 3*sum_of_a_and_b();  #$d = 21

Một trình con cũng có thể cho lại một danh sách các giá trị khi được tính trong hoàn cảnh mảng. ta hãy xét trình con này và lời gọi:

sub  list_of_a_and_b  {

($a, $b);

}

$a = 3; $b = 4;

@c = list_of_a_and_b();  #@c = (5, 6)

Biểu thức cuối được tính thực sự nghĩa là biểu thức cuối cùng được tính, thay vì là biểu thức cuối cùng được xác định trong thân của trình con. Chẳng hạn, trình con này cho lại $a nếu $a > 0, ngoài ra nó cho $b:

sub  gime_a_or_b  {

if ($a > 0)  {

print “chon a ($a)\n”;

$a;

} else {

print “chọn b ($b)\n”;

$b;

}

}

Lưu ý rằng trình con này cũng cho hiển thị một thông báo. Biểu thức cuối cùng được tính là $a hay $b, mà trở thành giá trị cho lại. Nếu bạn đảo ngược các dòng có chứa $a và print ngay trước nó, thì bạn sẽ nhận được một giá trị cho lại là 1 (giá trị được cho lại bởi hàm print thành công) thay vì giá trị của $a.

Tất cả chúng đều là các thí dụ khá đơn giản. Tốt hơn cả là ta nên truyền các giá trị khác nhau cho mỗi lần gọi tới một trình con thay vì phải dựa vào các biến toàn cục.

8.4 Đối số của hàm

Mặc dù các trình con có một chức năng đặc biệt là có ích, toàn bộ mức độ có ích mới trở thành sẵn có cho bạn khi bạn có thể truyền các đối số cho trình con. Trong Perl nếu lời gọi trình con có theo sau nó một danh sách nằm trong ngoặc tròn, thì danh sách này dẽ được tự động gán cho một biến đặc biệt có tên @_ trong suốt thời gian hoạt động của trình con. Trình con có thể thâm nhập vào biến này để xác định số các đối và giá trị của các đối đó. Chẳng hạn:

sub say_hello_to  {

print “Hello, $_[0]!\n”;  #tham bien dau la muc tieu

}

Tại đây ta thấy một tham khảo tới $_[0], chính là phần tử đầu tiên của mảng @_. Lưu ý đặc biệt: tương tự như dáng vẻ của chúng, giá trị $_[0] (phần tử đầu tiên của mảng @_) chẳng có bất kì liên quan gì với biến $_ (một biến vô hướng của riêng nó). Bạn đừng lầm lẫn chúng! Từ chương trình này, rõ ràng nó nói hello với bất kì cái gì chúng ta truyền cho nó như tham biến đầu tiên. Điều đó có nghĩa là chúng ta có thể gọi nó giống thế này:

say_hello_to(“world”);  #  se in ra Hello, world!

$x = “somebody”;

say_hello_to($x);  #  Hello, somebody!

say_hello_to(“me”) + say_hello_to(“you”);

Lưu ý rằng trong dòng cuối, giá trị cho lại không thực sự được dùng. Nhưng trong khi tính tổng Perl phải tính tất cả các bộ phận của nó, cho nên trình con này được gọi hai lần.

Sau đây là một thí dụ về việc dùng nhiều hơn một tham biến:

sub  say {

print  “$_[0], $_[1]!\n”;

}

say(“hello”, “world”);  #hello, world!

say (“goodbye”, “cruel world”);

Các tham biến vượt quá đều bị bỏ qua – nếu bạn chưa bao giờ xem tới $_[3], thì đối với Perl cũng không sao. Các tham số không đủ cũng bị bỏ qua – bạn đơn thuần nhận được undef nếu bạn nhìn vượt ra phần tử cuối của mảng @_, như với mọi mảng khác.

Biến @_ là cục bộ cho trình con này; nếu có một biến toàn cục cho @_, nó sẽ được cất giữ trước khi trình con được gọi và được khôi phục lại giá trị trước của nó khi trở về từ chương trình con. Điều này cũng có nghĩa là một trình con có thể truyền các đối cho một trình con khác mà không sợ mất biến @_ riêng của nó – việc gọi trình con lồng nhau đều nhận được @_ riêng của nó theo cùng cách.

Ta hãy xem xét lại trình con “cộng a và b” của mục trước. Tại đây một trình con thực hiện việc cộng hai giá trị bất kì, đặc biệt, hai giá trị được truyền cho trình con này như tham biến.

sub add_two  {

$_[0] + $_[1];

}

print add_two(3, 4);  #in 7

$c = add_two(5, 6);   #$c = 11

Bây giờ ta hãy tổng quát hoá chương trình này. Nếu chúng ta có ba, bốn hay hàng trăm giá trị cần phải cộng lại thì sao? Chúng ta có thể làm việc đó bằng một chu trình, tựa như:

sub add  {

$sum = 0 ;           #khoi dong gia tri cho $sum

foreach $_ (@_ )  {

$sum += $_ ;     #cong tung phan tu

}

$sum ;               #bieu thuc cuoi duoc tinh: tong cua tat ca cac phan tu,

#hoac ban co the dung return $sum;

}

$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

Điều gì xảy ra nếu ta có sẵn một biến mang tên $sum trước khi khi ta gọi hàm add_list? Trong mục tiếp theo chúng ta sẽ xem cách thức tránh điều này.

8.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:

sub bigger_than_100  {

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

}

return @result;                  #tra ve ket qua

}

#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.

8.6 Bài tập

Hãy viết 1 chương trình con nhận 1 số nằm trong khoảng từ 1 đến 9 làm tham số và trả  về một chuỗi tiếng Anh biểu thị của tham số. VD nếu tham số là 1 thì chương trình con trả về one, nếu là 2 thì trả về two…

Sử dụng lại chương trình con ở trên, hãy viết 1 chương trình nhập vào 2 số nguyên (trong khoảng từ 1 đến 9), cộng hai số đó lại và in ra kết quả dạng chữ. Vd: One plus two equals three. (Đừng quên ghi hoa chữ cái đầu tiên của chuỗi được in ra).

Hãy cải tiến hàm ở bài 1 để nhận thêm các đối số từ -9 đến -1 và 0. Nếu đối số là 0, hàm sẽ trả về zero; nếu đối số là -1, hàm trả về negative one; nếu đối số là 2 hàm trả về negative two…

(Sưu tầm từ diễn đàn tin học)


Thiết kế bởi Phạm Nguyễn Bảo Nguyên
GC Com 2005

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: