VIII. Các vấn đề nảy sinh từ sử dụng ngôn ngữ lập trình
TÊN BÀI:HÀM THỦ TỤC
MÃ BÀI:: ITPRG3-06.3
Giới thiệu
Khái niệm chương trình con (sub-program hay sub-routine) ra đời từ rất sớm vào những năm 1950. Mà sau đó chương trình con dạng hàm hay thủ tục đã được sử dụng rộng rãi trong các ngôn ngữ lập trình, đặc biệt là các ngôn ngữ lập trình mệnh lệnh. Cho đến ngày nay, khi mà các ngôn ngữ lập trình rất pgong phú đa dạng thì khái niệm này vẫn tồn dưới nhiều hình thức khác nhau.
Mục tiêu thực hiện
- Hiểu rõ cơ chế thực hiện của chương trình con dạng hàm và thủ tục - Phân biệt và sử dụng đúng các dạng tham số
- Nắm cấu trúc chuẩn của một chương trình con - Hiểu được tính ưu việt của các chương trình con
- Nắm được cách xây dựng và sử dụng chương trình con trong ngôn ngữ lập trình Pascal
- Nắm được khái niệm đệ quy
Nội dung chính
Trình bày hai khái niệm hàm và thủ tục. Nêu bật ưu điểm của hàm và thủ tục. Trình bày cách xây dựng hàm và thủ tục trong ngôn ngữ lập trình Pascal.
Khái niệm chương trình con
Khái niệm chương trình con (sub-program hay sub-routine) được ra đời từ rất sớm vào những năm 1950, khi mà ngôn ngữ để lập trình mới chỉ là ngôn ngữ máy. Do việc, viết chương trình bằng các bit nhị phân là rất phức tạp, khó khăn, người ta đã nghĩ đến việc xây dựng sẵn các đoạn chương trình thường hay sử dụng. Các đoạn chương trình này chính là tiền thân cho khái niệm chương trình con.
Chương trình con thực ra là những đoạn chương trình (dãy các câu lệnh) thường được hay sử dụng lặp đi lặp lại trong khi lập trình. Để giảm bớt thời gian lập trình, người ta xây dựng sẵn các thư viện chứa các chương trình con mà sau đó các chương trình con này có thể được sử dụng nhiều lần.
Ví dụ, tính cos hay sin là các công việc thường hay gặp trong toán học. Thế thì thay vì mỗi lần cần đến ta phải thực hiện tính toán, ta có thể xây dựng sẵn các chương trình con cho phép thực hiện công việc tính toán này và sau đó chỉ việc sử dụng.
Trong thực tế, trong hầu hết tất cả các ngôn ngữ lập trình các công việc thường được lặp đi lặp lại như thế này đều được xây dựng sẵn thành các chương trình con chứa trong các thư viện dành cho người sử dụng. Ngoài ra trong quá trình lập trình, người lập trình có thể tự xây dựng cho mình các chương trình con được sử dụng nhiều lần trong một chương trình.
Khái niệm chương trình con có hầu hết trong các ngôn ngữ lập trình, mà có thể tên gọi của nó bị thay đổi đi chút ít, như: hàm, thủ tục, thao tác, phương thức, ... Đặc biệt trong các
ngôn ngữ lập trình mệnh lệnh (như Pascal) thì chương trình con được chia làm hai loại: hàm (function) và thủ tục (procedure).
Trong bài học này chúng ta sẽ tìm hiểu về hai loại chương trình con này thông qua ngôn ngữ lập trình Pascal, là một ngôn ngữ mang tính sư phạm cao và thể hiện rất rõ hai khái niệm này.
Xây dựng hàm và thủ tục
Trước hết hàm hay thủ tục đều là những đoạn chương trình thường được sử dụng lặp đi lặp lại. Thế sự khác nhau giữa hai khái niệm này là gì?
Hàm sau khi thực hiện xong công việc thì tra về một giá trị thông qua tên hàm, trong khi thủ tục không trả về giá trị nào cả.
Ví dụ, hàm binhphương tính giá trị bình phương của một số nguyên sẽ trả về giá trị đó qua tên hàm. Trong khi thủ tục xuatmanhinh thực hiện việc in ra màn hình một kết quả tính toán nào đó thì nó không trả về một giá trị nào cả.
Trong ngôn ngữ Pascal, các chương trình con phải được khai báo và viết bên trên thân chương trình, sau đó được sử dụng trong thân chương trình.
Cú pháp tổng quát để viết một hàm trong Pascal như sau:
Function tên_hàm (khai báo các tham số hình thức) : kiểu_trả_về_của_hàm; (* Các khai báo hằng, biến cục bộ *)
Begin
(*thân hàm*)
tên_hàm := biểu_thức; (* gán giá trị trả về *) End;
Khi khai báo một hàm, nếu hàm đó có sử dụng các hằng hay biến cục bộ thì phải khai báo sau khi khai báo hàm. Ở đây chúng ta thấy xuất hiện khái niệm biến cục bộ (local variable) là các biến được khai báo bên trong một hàm (hay thủ tục). Trong thân hàm luôn phải có phép gán giá trị trả về cho tên hàm.
Ví dụ, viết hàm tính tổng của 3 số thực: Function tong3so (x, y, z : real) : real;
Begin
tong3so := x + y + z; End;
Đây là hàm rất đơn giản, nhận 3 giá trị số thực và trả về tổng của chúng. Đối với hàm này không có các khai báo thêm hằng, biến cục bộ. Hàm được bắt đầu bởi từ khóa Function, sau đó là tên hàm tong3so. Hàm nhận 3 tham số hình thức là x, y, z có kiểu real và trả về giá trị kiểu real. Thân hàm gồm các câu lệnh được đặt giữa hai từ khóa Begin và End. Giá trị tổng 3 số thực được gán trực tiếp cho tên hàm trong thân hàm.
Dưới đây là một ví dụ khác, chương trình có chứa hàm tính giá trị lớn nhất của hai số thực. Hàm được sử dụng trong thân chương trình để tính giá trị lớn nhất của các biểu thức a+b và a-b.
Program vi_du_max; Var
a, b, s : real;
(*Khai báo hàm max2so*)
Function max2so(x, y : real) : real; Var
r : real; (* khai báo biến cục bộ *) Begin if x > y then r := x else r := y; max2so = r; End; (*Thân chương trình chính*) Begin a := 11.45 b := -42.7
s := max2so(a+b, a-b); (* gọi chương trình hàm *) Writeln(‘Max = ’, s:5:1);
End.
Như thế, chúng ta nhận thấy hàm luôn trả về một giá trị trong tên hàm. Trong khi định nghĩa hàm thì phải gán tên hàm cho giá trị trả về. Ngược lại, thủ tục không trả về giá trị. Cú pháp tổng quát để viết một thủ tục trong Pascal là như sau:
Procedure tên_thủ_tục (khai báo các tham số hình thức); (* Các khai báo hằng, biến cục bộ*)
1. Begin
(*thân thủ tục*) End;
Bây giờ chúng ta viết lại chương trình tính giá trị lớn nhất hai số thực sử dụng chương trình con là thủ tục như sau:
Program vi_du_max; Var
a, b : real;
(*Khai báo thủ tục max2so*) Procedure max2so(x, y : real);
Var
r : real; (* khai báo biến cục bộ *) Begin
if x > y then r := x else r := y;
Writeln(‘Max = ’, r:5:1); End;
(*Thân chương trình chính*) Begin
a := 11.45 b := -42.7
max2so(a+b, a-b); (* gọi chương trình thủ tục *) End.
Trong ngôn ngữ Pascal, còn cho phép viết các chương trình con bên trong thân một chương trình con khác. Chẳng hạn, chúng ta xem xét ví dụ thủ tục M sau:
(* khai báo thủ tục M*) Procedure M (x, y : real);
Var
s : real; (* biến cục bộ của thủ tục M*) (* khai báo hàm M1 bên trong thân thủ thủ tục M*) Function M1 (m, n : real) : real;
Var
r : real; (* biến cục bộ của thủ tục M1*) Begin
if m > n then r := m else r := n;
M1 := r; End;
(* khai báo thủ tục M2 bên trong thân thủ tục M*) Procedure M2 (a : real);
Begin
Writeln(‘In ket qua : ’, a:5:1); End; (* thân của thủ tục M *) Begin s := M1(x, y); (* gọi hàm M1*) M2(s); (* gọi thủ tục M2*) End;
Trong ví dụ này, bên trong thân của thủ tục M chứa hai chương trình con khác là hàm M1 và thủ tục M2. Sau đó, trong thân của thủ tục M sử dụng hai chương trình con này.
Lưu ý là không phải ngôn ngữ lập trình nào cũng cho phép khai báo các chương trình con bên trong chương trình con khác, chẳng hạn như ngôn ngữ C không cho phép điều này.
Cơ chế hoạt động của chương trình con
Liên quan đến chương trình con (hàm và thủ tục ở trên), chúng ta có một số khái niệm sau: - Biến cục bộ: là biến được khai báo và chỉ sử dụng bên trong thân một chương trình
con, là biến r trong ví dụ trên.
- Biến toàn cục: là biến được khai báo ở đầu chương trình và có thể được sử dụng bất cứ đâu trong chương trình, là các biến a và b trong ví dụ trên.
- Tham số hình thức (hay còn được gọi là đối): là các biến được khai báo sau tên của chương trình con (chúng ta sẽ được giới thiệu chi tiết hơn về tham số hình thức trong các phần tiếp theo), là các tham số x và y trong ví dụ trên.
- Tham số thực: là các giá trị truyền cho các tham số hình thức tương ứng khi gọi các chương trình con. Chẳng hạn, trong ví dụ trên là các giá trị của biểu thức a+b và a- b.
Cơ chế hoạt động của một chương trình con là như sau: chương trình được batứ đầu từ câu lệnh đầu tiên và kết thúc khi thực hiện xong câu lệnh cuối cùng trong thân chương trình, nếu chương trình gặp một lời gọi chương trình con (thủ tục hay hàm) thì máy sẽ thực hiện:
- cấp phát bộ nhớ cho các biến cục bộ của chương trình con,
- truyền giá trị của các tham số thực cho các tham số hình thức tương ứng, - thực hiện lần lượt các câu lệnh trong thân chương trình con,
- giải phóng các biến cục bộ và trở về nơi gọi nó, nếu chương trình con là hàm thì khi trở về mang theo một giá trị.
Quay trở lại chương trình chứa thủ tục max2so trên, hoạt động của nó là như sau: - gán giá trị 11.45 cho biến a và –42.7 cho biến b,
- gặp lời gọi thủ tục max2so, thực hiện thủ tục max2so:
o cấp phát bộ nhớ cho biến cục bộ r và các tham số hình thức x và y,
o giá trị của biểu thức a+b và a-b được truyền cho các tham số hình thức x và y,
o thực hiện các câu lệnh trong thân thủ tục để tính giá trị lớn nhất chứa trong biến r,
o gọi thủ tục Writeln để in ra kết quả,
o giải phóng các biến cục bộ r và tham số hình thức x, y,
o máy thoát ra khỏi thủ tục để trở về chương trình chính, - kết thúc chương trình chính.
Biến toàn cục và biến cục bộ
Ở trên chúng ta đã nhắc đến hai khái niệm biến cục bộ và biến toàn cục, trong phần này chúng ta sẽ xem xét một cách chi tiết hơn.
Biến toàn cục (global variable) là những biến được khai báo ở đầu chương trình, chúng tồn tại trong suốt thời gian làm việc của chương trình. Biến toàn cục được sử dụng bất kì đâu ở trong chuơnưg trình, nghĩa là trong thân chương trình chính hoặc trong các thân chương trình con.
Biến cục bộ (local variable) là biến được khai báo ở đầu một chương trình con. Biến cục bộ được cấp phát bộ nhớ khi chương trình con được gọi tới và bị giải phóng khỏi bộ nhớ khi máy ra khỏi chương trình con. Biến cục bộ chỉ được sử dụng bên trong thân của chương trình con khai báo nó cũng như các chương trình con khác nằm bên trong thân của chương trình con khai báo nó.
Để phân biệt rỏ sự khác nhau của biến cục bộ và biến toàn cục, chúng ta quan sát ví dụ sau:
Program cac_loai_bien; Var
x : integer; (* x là biến toàn cục *) (* Khai báo thủ tục M *)
Procedure M; Var
a, b : integer; (* a và b là biến cục bộ trong M *) (* Khai báo thủ tục M1 *)
Procedure M1;
Var n : inetger; (* n là biến cục bộ trong M1 *) Begin x := x + 1; (* sử dụng biến toàn cục x *) n := a + b; Writeln(‘n = ’, n); a := a + 1; b := b + 1; End; (* Thân thủ tục M *) Begin a := 1; b := 5; Writeln(‘a = ’, a); Writeln(‘b = ’, b); M1; (* gọi thủ tục M1 *) Writeln(‘a = ’, a); Writeln(‘b = ’, b); End; (* Thân chương trình chính *) Begin x := 10; Writeln(‘x = ’, x); M; (* gọi thủ tục M *) Writeln(‘x = ’, x); End.
Trong ví dụ này, chương trình chính chứa thủ tục M, thủ tục M lại chứa thủ tục M1. x là biến toàn cục được khai báo ở đầu chương trình chính, x có thể được sử dụng bất kỳ đâu trong chương trình. a và b là các biến cục bộ khai báo đầu thủ tục M, nên a và b chỉ có thể được sử dụng trong thân thủ tục M và thủ tục M1. n là biến cục bộ khái báo trong thủ tục M1, nên m chỉ có thể được sử dụng trong thân thủ tục M1.
Hoạt động của chương trình này là như sau:
- in x ra màn hình,
- gọi thủ tục M, thực hiện các câu lệnh trong thân thủ tục M,
o gán biến a bằng 1 và biến b bằng 5,
o in ra màn hình giá trị các biến a và b,
o gọi thủ tục M1, thực hiện các câu lệnh trong thân thủ tục M1,
biến toàn cục x được tăng lên một đơn vị, gán biến cục bộ n bằng giá trị biểu thức a+b, in n ra màn hình,
tăng mỗi biến cục bộ a và b của thủ tục M lên một đơn vị, kết thúc thủ tục M1, quay trở về thủ tục M,
o in ra giá trị của các biến cục bộ a và b,
o kết thúc thủ tục M, quay trở về chương trình chính, - in ra giá trị của biến cục bộ x và kết thúc chương trình. Kết quả của chương trình trên là:
x = 10 a = 1 b = 5 n = 6 a = 2 b = 6 x = 11 Nhận xét:
- Biến cục bộ và tham số hình thức có cơ chế hoạt động giống nhau, chúng chỉ tồn tại trong thời gian chương trình con hoạt động.
- Biến toàn cục có thể bị thay đổi giá trị bất cứ đâu trong chương trình, điều này dẫn đến việc sử dung tùy tiện các biến toàn cục sẽ làm cho chương trình rất phức tạp, khó gỡ rối khi có lỗi xảy ra. Vì vậy, nên hạn chế sử dụng biến toàn cục.
Cơ chế truyền tham số
Ở trong các phần trên, chúng ta đã được giới thiệu cách xây dựng các chương trình con. Sau đó, chúng ta có thể sử dụng (lời gọi chương trình con) chương trình con. Mỗi khi sử dụng chương trình con, thông thường đều phải truyền dữ liệu cho nó. Có các cách truyền dữ liệu cho chương trình con khác nhau sau:
- truyền tham số dạng biến toàn cục, - truyền tham số dạng tham trị, - truyền tham số dạng tham biến.