Truyền tham số dạng tham biến

Một phần của tài liệu Giáo trình lý thuyết ngôn ngữ lập trình (nghề lập trình máy tính) (Trang 75 - 80)

D. Truyền tham số dạng biến toàn cục

F. Truyền tham số dạng tham biến

Trong ví dụ trên, các nghiệm của phương trình được in ra ngay trong thủ tục. Tuy nhiên, nếu chúng ta muốn chương trình phải trả về nghiệm của phương trình và việc in sẽ được thực hiện trong chương trình chính thì cách sử dụng tham số dạng tham trị không giải quyết được. Trong trường hợp này, chúng ta sử dụng tham số dạng tham biến, tức là giá trị của tham số vẫn được sử dụng sau khi ra khỏi chương trình con.

Các tham số dạng tham biến được khai báo sau tên chương trình con giữa hai dấu ngoặc theo mẫu sau (với từ khóa Var):

Var danh_sách_tham_số : kiểu; Var danh_sách_tham_số : kiểu; ...

Ví dụ:

Function (x,y : real; Var a : real; Var p,q : real) : real;

Khi có lời gọi chương trình con, các tham số thực sẽ được truyền cho các tham số hình thức. Các tham số thực phải là một biến hay phần tử mảng có cùng kiểu với tham số hình thức tương ứng. Chẳng hạn, nếu tham số hình thức có kiểu nguyên thì tham số thực phải là một biến kiểu nguyên.

Chúng ta viết lại chương trình giải phương trình bậc 2, trong đó thủ tục gptb2 nhận 3 tham số dạng tham trị là a, b, c, trả về ba giá trị bởi tham biến là delta, x1, x2.

Program Phuong_trinh_bac_2; Var x, y, z, d, n1, n2 : real;

Procedure gptb2(a, b, c : real; Var delta, x1, x2 : real); Var r : real;

Begin

delta := b*b – 4*a*c; if delta >= 0 then

Begin r := sqrt(delta); x1 := (-b-r)/(2*a); x2 := (-b+r)/(2*a); End; End; (* Thân chương trình chính *) Begin Write(‘x = ’); readln(x); Write(‘y = ’); readln(y); Write(‘z = ’); readln(z); gptb2(x, y, z, d, n1, n2); (* gọi thủ tục gptb2 *) if (d < 0) then

Writeln(‘Phương trình vô nghiệm’); if (d = 0) then

Writeln(‘Phương trình có nghiệm kép: ’, n1:5:2); if (d > 0) then

Writeln(‘Phương trình có hai nghiệm: x1 = ’, n1:5:2, ‘x2 = ’, n2:5:2); End.

Đối với chương trình con sử dụng tham số dạng tham biến, thì khi gặp lời gọi chương trình con, máy sẽ:

- cấp phát bộ nhớ cho các biến cục bộ và các tham số dạng tham trị và tham biến, - truyền giá trị của tham số thực cho các tham số dạng tham trị tương ứng,

- truyền địa chỉ của các biến tham số thực cho các tham số dạng tham biến, - thực hiện các câu lệnh trong thâm chương trình con

Như thế, đối với các tham số dạng tham biến, thay vì truyền giá trị của tham số thực thì phải truyền địa chỉ của biến tham số thực. Vì vậy, mọi sự thay đổi giá trị của tham biến trong chương trình con sẽ kéo theo sự thay đổi của biến tham số thực. Trong ví dụ trên, mọi thay đổi giá trị trên các biến d, n1, n2 trong chuơng trình con cũng sẽ có hiệu lực sau khi đã thoát ra khỏi chương trình con.

Đệ quy

Một chương trình con được gọi là đệ quy (recursivity) nếu trong thân chương trình con đó có lời gọi đến chính nó. Nhiều ngôn ngữ lập trình cho phép xây dựng các chương trình con đệ quy.

Chúng ta lấy ví dụ tính giai thừa của một số nguyên n. Giai thừa n được định nghĩa như sau: n! = 1.2.3...(n-1).n hoặc 1 nếu n = 0 n! = n.(n-1)!nếu n1

Trong cách định nghĩa sau, cách tính n! phụ thuộc vào (n-1)!. Với định nghĩa này chúng ta xây dựng hàm đệ quy tính n! bằng ngôn ngữ Pascal như sau:

Function giai_thua1(n : longint) : longint; Begin

if n = 0 then giai_thua1 := 1;

else giai_thua1 := n * giai_thua1(n-1); End;

Như thế, chúng ta nhận thấy hàm giai_thua1 được gọi trong khi định nghĩa chính nó. Mỗi lời gọi đệ quy cũng như lời gọi chương trình con, máy phải cấp phát bộ nhớ cho các biến cục bộ, và sau khi kết thúc thì phải giải phóng chúng. Với một chương trình con đệ quy thì có thể có nhiều lần gọi, vì vậy có bao nhiêu lần gọi thì cũng có bấy nhiêu lần cấp phát và giải phóng các biến cục bộ. Quá trình giải phóng các biến cục bộ được thực hiện theo thứ tự ngược lại quá trình cấp phát chúng: các biến cục bộ được tạo ra trước sẽ được giải phóng sau.

Như thế, đối với một chương trình con đệ quy thì sẽ cần rất nhiều bộ nhớ cho các biến cục bộ. Thậm chí nếu chương trình con đệ quy thực hiện lời gọi đệ quy không dừng thì sẽ dẫn đến tình trạng tràn bộ nhớ. Chẳng hạn, nếu người sử dụng gọi hàm giai_thua1 trên như sau:

n = giai_thua1(-1); thì sẽ bị lỗi tràn bộ nhớ.

Tuy nhiên, chúng ta có thể dễ dàng nhận thấy rằng để tính n! chúng ta có thể sử dụng vòng lặp thay vì đệ quy như sau:

Function giai_thua2(n : longint) : longint; Var i, gt : longint; Begin gt := 1; if n > 0 then for i : = 1 to n do gt := gt * i; giai_thua2 := gt; End;

Bây giờ nếu, chúng ta phân tích hai lời gọi chương trình con sau: n = giai_thua1(100); n = giai_thua2(100);

Với lời gọi thứ nhất, hàm giai_thua1 được gọi đệ quy đến 100 lần, và mỗi lần gọi cần cấp phát 4 byte cho tham số n kiểu longint, như thế cần 400 byte bộ nhớ. Trong khi với lời gọi thứ hai, hàm giai_thua2 chỉ được gọi 1 lần, chỉ cần cấp phát 12 byte bộ nhớ cho tham số n và hai biến cục bộ i, gt kiểu longint.

Một ví dụ thứ hai minh họa chương trình con đệ quy. Ước số chung lớn nhất của hai số nguyên a và b được xác định theo công thức:

- nếu x = y thì usc(x, y) = x

- nếu x > y thì usc(x, y) = usc(x-y, y) - nếu x < y thì usc(x, y) = usc(x, y-x) Hàm đệ quy usc được viết như sau:

Function usc(a, b : int) : int; Begin

if x = y then usc := x;

else if x > y then usc := usc(x – y, y); else usc := usc(x, y - x);

End;

Nhận xét: Phương pháp đệ quy cho phép viết chương trình ngắn gọn đơn giản, nhưng lại không hiệu quả về mặt sử dụng tài nguyên bộ nhớ.

Tính ưu việt của chương trình con

Hầu hết tất cả các ngôn ngữ lập trình đều sử dụng khái niệm chương trình con. Chương trình con chỉ định nghĩa một lần nhưng sao đó được sử dụng nhiều lần. Việc viết chương trình sử dụng chương trình con chúng ta nhận thấy có các ưu điểm sau:

- giảm bớt số dòng lệnh của một chương trình, - giảm thời gian lập trình,

- giảm độ phức tạp của chương trình,

- chương trình được tổ chức theo dạng tập hợp các chương trình con, nên dễ quản lý hơn,

- dễ sữa đổi chương trình khi cần thiết, - dễ kiểm tra lỗi.

Bài tập

1. Hai khái niệm hàm và thủ tục khác nhau chổ nào? 2. Tại sao không nên sử dụng biến toàn cục?

3. Viết thủ tục giải phương trình trùng phương ax4 + bx2 + c = 0. 4. Viết hàm tính giá trị lớn nhất (nhỏ nhất) của một dãy số. 5. Viết hàm hay thủ tục giải hệ phương trình bậc nhất:

ax + by = p cx + dy = q 6. Viết hàm đệ quy tính:

P(n) = 1 + 22 + 32 + ... + n2

7. Viết chương trình sử dụng hàm đệ quy đẻ tạo ra dãy số Fibonacci F1, F2, ... Fn được định nghĩa như sau:

F1 = 1, F2 = 1 Fn = Fn-1 + Fn-2 Ví dụ: 1, 1, 2, 3, 5, 8, 13, 21, ...

BÀI 4

ĐẶC TRƯNG CÚ PHÁP VÀ NGỮ NGHĨA CHƯƠNG TRÌNH

Mã bài:ITPRG3-06.4

Giới thiệu

Bài học sẽ trình bày tổng quan các vấn đề liên quan đến ngôn ngữ lập trình. Chẳng hạn, một ngôn ngữ lập trình được xây dựng như thế nào, làm sao để máy tính có thể hiểu được một chương trình nào đó, … Như thế, việc hiểu được bản chất của ngôn ngữ lập trình sẽ giúp cho người lập trình viết các chương trình hữu hiệu hơn.

Mục tiêu thực hiện

- Hiểu được cú pháp của các ngôn ngữ

- Nắm các đặc trưng mang tính ngữ nghĩa của chương trình

- Nắm các tiền đề cho sự phát triển của chương trình qua ngữ pháp và ngữ nghĩa - Viết chương trình có khả năng thân thiện hơn

Nội dung chính

Trình bày ngắn gọn cách định nghĩa, xây dựng một ngôn ngữ lập trình, cách phân tích một chương trình, các thành phần cần thiết để phân tích một chương trình.

Khái niệm ngôn ngữ

Một ngôn ngữ dù là ngôn ngữ tự nhiên như tiếng Việt hay là ngôn ngữ lập trình như Pascal, cũng đều có thể xem là một tập hợp các câu có cấu trúc quy định nào đó. Cấu trúc câu được quy định ra sao thì đó là vấn đề cách biểu diễn ngôn ngữ. Chúng ta có thể nhận xét thấy rằng, một câu của ngôn ngữ, dù là câu tiếng Việt “bạn đi học” hay cả một văb bản chương trình từ chữ “Program” cho đến dấu chấm “.” kết thúc chương trình, thì cũng đều chẳng qua là một dãy các xâu/từ có sẵn như “bạn”, “đi”, “học”, hay “Program”, … được liệt kê trong một bảng chữ nào đó, mà ta có thể xem là các kí hiệu cơ bản của ngôn ngữ.

Từ nhận xét trên đây, chúng ta đi đến một số khái niệm hình thức về ngôn ngữ như dưới đây.

Bảng chữ (alphabet) là một tập hợp các kí hiệu. Ví dụ: {a, b, c, .., z} : bảng chữ cái Latin {0,1, 2, .., 9} : bảng chữ số thập phân

{0,1} : bảng chữ số nhị phân

Xâu (string) là một dãy hữu hạn các kí hiệu từ bảng chữ cái.

Ví dụ, với bảng chữ {0, 1} thì các xâu là 0, 1, 00, 01, 11, 001, 000, …

Một cách không hình thức, ngôn ngữ (language) là một tập hợp các xâu trên một bảng chữ cái.

Ví dụ, với bảng chữ {0, 1} thì ngôn ngữ trên bảng chữ này là tập hợp {0, 1, 00, 01, 11, 001, 000, …}.

Cụ thể, ngôn ngữ lập trình là một hệ thống gồm các kí hiệu và các quy tắc kết hợp các kí hiệu thành một cấu trúc có ý nghĩa. Phần cú pháp (syntax) qui định sự kết hợp các kí hiệu, còn phần ngữ nghĩa qui định ý nghĩa của mỗi sự kết hợp đó.

Sau đây là một ví dụ về các khía cạnh cú pháp và ngữ nghĩa trong ngôn ngữ lập trình. Chúng ta có các biểu thức sau:

bt1 = 2 bt2 = 1 + 1 bt3 = 1 * 2

Cả ba biểu thức trên có cùng giá trị, tức giống nhau về mặt ngữ nghĩa, tuy nhiên chúng khác nhau về mặt cú pháp.

1. Định nghĩa cú pháp

Trước hết, phần này sẽ trình bày chi tiết hơn về khai niệm cú pháp, văn phạm là cơ chế để mô tả ngôn ngữ.

Một phần của tài liệu Giáo trình lý thuyết ngôn ngữ lập trình (nghề lập trình máy tính) (Trang 75 - 80)