Giáo trình Lý thuyết ngôn ngữ lập trình (Nghề Lập trình máy tính): Phần 2 giúp bạn nắm được các khái niệm cơ bản của ngôn ngữ lập trình chung, hiểu được các thành phần của một ngôn ngữ lập trình, biết phân biệt các đặc trưng khác nhau của các ngôn ngữ lập trình. Mời các bạn tham khảo!
Trang 1Bài 3 TÊN BÀI:HÀM THỦ TỤC
MÃ BÀI:: ITPRG3-06.3Giớ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àonhững năm 1950 Mà sau đó chương trình con dạng hàm hay thủ tục đã được sử dụng rộngrã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àynay, 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ướinhiề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ìnhPascal
- 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áchxâ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àonhữ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ếtchươ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âydự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 taxâ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 concho 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 đượclặ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 trongcá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ươngtrì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ọicủ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
Trang 2ngô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ệmnà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, trongkhi 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ínhtoá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ênthâ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 khaibá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ộ (localvariable) 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ônphải có phép gán giá trị trả về cho tê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ứca+b và a-b
Program vi_du_max;
Var
Trang 3a, 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 := xelse r := y;
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 := xelse r := y;
Writeln(‘Max = ’, r:5:1);
End;
Trang 4(*Thân chương trình chính*)
Begin
a := 11.45
b := -42.7max2so(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ươngtrình con khác Chẳng hạn, chúng ta xem xét ví dụ thủ tục M sau:
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àmM1 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ìnhcon 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ìnhcon, 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ụngbất cứ đâu trong chương trình, là các biến a và b trong ví dụ trên
Trang 5- 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ủachương trình con (chúng ta sẽ được giới thiệu chi tiết hơn về tham số hình thứctrong 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ácchươ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âulệ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ếuchươ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ì khitrở 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 trongbiế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ầnnà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ụngbấ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ânchươ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ếncụ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ươngtrì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ươngtrì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:
Trang 6Hoạt động của chương trình này là như sau:
- bắt đầu chương trình chính, biến x được gán giá trị 10,
Trang 7- 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 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à:
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ìnhcon 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
D Truyền tham số dạng biến toàn cục
Do phạm vi sử dụng của biến toàn cục là bất kỳ mọi nơi trong chương trình nen ta cóthể sử dụng chúng đẻ truyền dữ liệu cho các chương trình con cũng như nhận kết quả tính
được từ các chương trình con
Trang 8Ví dụ chương trình giải phương trình bậc hai (ax2 + bx + c = 0) dưới đây truyền tham
số bằng biến toàn cục Trong ví dụ này, để giảm bớt phức tạp, chúng ta không xét đến các
trường hợp suy biến của phương trình bậc 2.Program Phuong_trinh_bac_2;
sử dụng biến toàn cục như thế này rất khó để kiểm soát giá trị của chúng nên điều nàythường dẫn đến sai sót
Do những nhược điểm như vây, nên người ta khuyến khích người lập trình không sửdụng phương pháp truyền tham số bằng biến toàn cục Phương pháp dưới đây sẽ khắcphục nhược điểm trên
Trang 9E Truyền tham số dạng tham trị
Chúng ta cần nhắc lại rằng, đối với chương trình con có hai loại tham số Tham sốhình thức là các biến khai báo sau tên chương trình con, tham số thực là các giá trị hay biếntruyền cho các tham số hình thức tương ứng khi gọi chương trình con
Tham số hình thức được chia làm hai dạng: tham trị và tham biến Trước hết chúng ta
Function ham (x, y : real; a, b : real) : real;
Procedure thutuc (x : real; a, b, c : 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ìnhthức Các tham số thực phải là một biểu thứ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ểu thứckiểu nguyên
Bây giờ chúng ta viết lại chương trình giải phương trình bậc 2 sử dụng tham số dạng thamtrị như sau:
r := sqrt(delta);
x1 := (-b-r)/(2*a);
x2 := (-b+r)/(2*a);
if (delta = 0) then Writeln(‘PT có nghiệm kép: ’, x1:5:2);
if (delta > 0) then Writeln(‘PT có hai nghiệm: x1 = ’, x1:5:2, ‘x2 = ’, x2:5:2);
Trang 10- truyền giá trị của các tham số thực cho các tham số dạng tham trị tương ứng, cácgiá trị x, y, z, được truyền vào tương ứng cho a, b, c,
- thực hiện các câu lệnh trong chương trình con,
- kết thúc chương trình con, máy sẽ giải phóng các biến cục bộ và các tham số hìnhthức, như thế các giá trị đặt trong các biến cục bộ và các tham số hình thức khôngthể đưa về để sử dụng trong một chương trình khác
Nhận xét: các tham số hình thức dạng tham trị chỉ được sử dụng trong chương trình conkhai báo chúng
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 Tuynhiê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ảiquyế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ựcphả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 3tham số dạng tham trị là a, b, c, trả về ba giá trị bởi tham biến là delta, x1, x2
Trang 11- 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ảitruyề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 trongchươ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ìnhcon đệ 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ĩanhư sau:
n! = 1.2.3 (n-1).n
hoặc
1 nếu n = 0n! =
n.(n-1)!nếu n1
Trang 12Trong cách định nghĩa sau, cách tính n! phụ thuộc vào (n-1)! Với định nghĩa nàychú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;
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:
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:
Trang 13Function 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ếtchươ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:
Trang 14BÀ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ẳnghạ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ộtchươ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úccâ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ậnxé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ảnchương trình từ chữ “Program” cho đến dấu chấm “.” kết thúc chương trình, thì cũng đềuchẳ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ânXâu (string) là một dãy hữu hạn các kí hiệu từ bảng chữ cái
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:
Trang 15Ðể xác định cú pháp của một ngôn ngữ, người ta dùng văn phạm phi ngữ cảnh CFG
(Context Free Grammar)
Văn phạm phi ngữ cảnh bao gồm bốn thành phần:
1 Một tập hợp các token , gọi là các ký hiệu kết thúc (terminal symbols)
Ví dụ: Các từ khóa, các chuỗi, dấu ngoặc đơn,
2 Một tập hợp các ký hiệu chưa kết thúc (nonterminal symbols), còn gọi là các biến (variables)
Ví dụ: Câu lệnh, biểu thức,
3 Một tập hợp các luật sinh (productions) trong đó mỗi luật sinh bao gồm một ký hiệu chưa kết thúc - gọi là vế trái, một mũi tên và một chuỗi các token và / hoặc các ký hiệu chưa kết thúc gọi là vế phải
4 Một trong các ký hiệu chưa kết thúc được dùng làm ký hiệu bắt đầu của văn phạm Chúng ta qui ước:
- Mô tả văn phạm bằng cách liệt kê các luật sinh
- Luật sinh chứa ký hiệu bắt đầu sẽ được liệt kê đầu tiên
- Nếu có nhiều luật sinh có cùng vế trái thì nhóm lại thành một luật sinh duy nhất, trong đó các vế phải cách nhau bởi ký hiệu “ | “ đọc là “hoặc”
Ví dụ 4.1: Xem biểu thức là một danh sách của các số phân biệt nhau bởi dấu + và dấu - Ta
có, văn phạm với các luật sinh sau sẽ xác định cú pháp của biểu thức
list list + digit
list list – digit
list digit
digit 0 | 1 | 2 | | 9
Như vậy văn phạm phi ngữ cảnh ở đây là:
Tập hợp các ký hiệu kết thúc: 0, 1, 2, , 9, +,
Tập hợp các ký hiệu chưa kết thúc: list, digit
- Các luật sinh đã nêu trên
- Ký hiệu chưa kết thúc bắt đầu: list
Trang 169 - 5 + 2 là một list vì 9 - 5 là một list và 2 là một digit.
Ví dụ 4.3:
Một list là một chuỗi các lệnh, phân cách bởi dấu ; của khối begin - end trong Pascal Một
danh sách rỗng các lệnh có thể có giữa begin và end
Chúng ta xây dựng văn phạm bởi các luật sinh sau:
block begin opt_stmts end
opt_stmts stmt_list |
stmt_list stmt_list ; stmt | stmt
Trong đó opt_stmts (optional statements) là một danh sách các lệnh hoặc không có lệnh nào( ).
Luật sinh cho stmt_list giống như luật sinh cho list trong ví dụ 2.1, bằng cách thay thế +, - bởi
; và stmt thay cho digit
H Cây phân tích cú pháp (parsing tree)
Cây phân tích cú pháp minh họa ký hiệu ban đầu của một văn phạm dẫn đến một chuỗi trong ngôn ngữ
Nếu ký hiệu chưa kết thúc A có luật sinh A XYZ thì cây phân tích cú pháp có thể có một nút trong có nhãn A và có 3 nút con có nhãn tương ứng từ trái qua phải là X, Y, Z
Hình 4.1 Cây phân tích cú pháp nhãn A
Một cách hình thức, cho một văn phạm phi ngữ cảnh thì cây phân tích cú pháp là mộtcây có các tính chất sau đây:
1 Nút gốc có nhãn là ký hiệu bắt đầu
2 Mỗi một lá có nhãn là một ký hiệu kết thúc hoặc một
3 Mỗi nút trong có nhãn là một ký hiệu chưa kết thúc
4 Nếu A là một ký hiệu chưa kết thúc được dùng làm nhãn cho một nút trong nào đó và X1 Xn là nhãn của các con của nó theo thứ tự từ trái sang phải thì A X1X2 Xn là một luật sinh Ở đây X1, , Xn có thể là ký hiệu kết thúc hoặc chưa kết thúc Ðặc biệt, nếu A thì nút có nhãn A có thể có một con có nhãn
string string + string | string - string | 0 | 1 | | 9
Với văn phạm này thì chuỗi biểu thức 9 - 5 + 2 có đến hai cây phân tích cú pháp như
Trang 17sau :
Hình 4.2 Hai cây phân tích cú pháp
Tương tự với cách đặt dấu ngoặc vào biểu thức như sau :
(9 - 5) + 2 9 - ( 5 + 2)
Bởi vì một chuỗi với nhiều cây phân tích cú pháp thường sẽ có nhiều nghĩa, do đó khi biên dịch các chương trình ứng dụng, chúng ta cần thiết kế các văn phạm không có sự nhập nhằng hoặc cần bổ sung thêm các qui tắc cần thiết để giải quyết sự nhập nhằng cho văn phạm
Ví dụ 4.5 : Trong ngôn ngữ C, biểu thức a = b = c tương đương a = ( b = c) vì chuỗi a =
b = c với toán tử kết hợp phải được sinh ra bởi văn phạm:
right letter = right | letter
letter a | b | | z
Ta có cây phân tích cú pháp có dạng như sau (chú ý hướng của cây
nghiêng về bên phải trong khi cây cho các phép toán có kết hợp trái
thường nghiêng về trái):
Trang 18Thứ tự ưu tiên của các toán tử
Xét biểu thức 9 + 5 * 2 Có 2 cách để diễn giải biểu thức này, đó là 9 + (5 * 2) hoặc ( 9 + 5) *
2 Tính kết hợp của phép + và * không giải quyết được sự mơ hồ này, vì vậy cần phải quy định một thứ tự ưu tiên giữa các loại toán tử khác nhau
Thông thường trong toán học, các toán tử * và / có độ ưu tiên cao hơn + và -
factor digit | (expr)
Phép nhân và chia có thứ tự ưu tiên cao hơn đồng thời chúng kết hợp trái nên luật sinh cho term tương tự như cho list :
term term * factor | term / factor | factor
Tương tự, ta có luật sinh cho expr :
expr expr + term | expr - term | term
Vậy, cuối cùng ta thu được văn phạm cho biểu thức như sau :
expr expr + term | expr - term | term
term term * factor | term / factor | factor
factor digit | (expr)
Như vậy: Văn phạm này xem biểu thức như là một danh sách các term được phân cách
nhau bởi dấu + hoặc - Term là một list các factor phân cách nhau bởi * hoặc / Chú ý rằng
bất kỳ một biểu thức nào trong ngoặc đều là factor, vì thế với các dấu ngoặc chúng ta có thể xây dựng các biểu thức lồng sâu nhiều cấp tuỳ ý
Cú pháp các câu lệnh
Từ khóa (keyword) cho phép chúng ta nhận ra câu lệnh trong hầu hết các ngôn ngữ Ví
dụ trong Pascal, hầu hết các lệnh đều bắt đầu bởi một từ khóa ngoại trừ lệnh gán Một số lệnh Pascal được định nghĩa bởi văn phạm (nhập nhằng) sau, trong đó id chỉ một danh biểu (tên biến)
stmt id := expr
Trang 19| if expr then stmt
| if expr then stmt else stmt
| while expr do stmt
| begin opt_stmts end
Ký hiệu chưa kết thúc opt_stmts sinh ra một danh sách có thể rỗng các lệnh, phân cách nhau bởi dấu chấm phẩy (;)
Kí hiệu Ý nghĩa
::=, hoặc , hoặc = được định nghĩa là
{ } chuỗi của 0 hoặc nhiều mục liệt kê tùy chọn
[ ] hoặc 0 hoặc 1 mục liệt kê tùy chọn
< > mục liệt kê phải được thay thế
| hoặc (theo nghĩa loịa trừ)
Các quy tắc BNF định nghĩa tên trong Pascal:
<tên> ::= <chữ> { <chữ> | <số> }
<chữ> ::= ‘A’ | … | ‘Z’ | ‘a’ | … | ‘z’
<số> ::= ‘)’ | … | ‘9’
Ví dụ văn phạm của một ngôn ngữ lập trình đơn giản dang BNF như sau:
<program> ::= program <statement> end
<statement> ::= <identifier> := <expression>;
<loop> ::= while <expression> do <statement> done
<expression> ::=
<value> | <value> + <value> | <value> <= <value>
<value> ::= <identifier> | <number>
<identifier> ::=
<letter> | <identifier><letter> | <identifier><digit>
<number>::= <digit> | <number><digit>
<letter> ::= ‘A’ | … | ‘Z’ | ‘a’ | … | ‘z’
Trang 201 Phân tích cú pháp
Một ngôn ngữ lập trình, như trình bày ở phần trên, được thường định nghĩa bởi cú pháp hayvăn phạm của nó bởi dạng chuẩn BNF Sau đó, khi người lập trình sử dụng ngôn ngữ đểviết chương trình, người lập trình phải tuân theo văn phạm đã được định nghĩa Để kiểm traxem một chương trình có đúng cú pháp hay không thì cần phải thực hiện phân tích cú pháp.Phân tích cú pháp là quá trình xác định xem một xâu/câu có thể được sinh ra từ một vănphạm cho trước không Cụ thể, phân tích cú pháp của một chương trình là xác định xemtừng câu lệnh của chương trình có được sinh ra bởi cú pháp của ngôn ngữ lập trình đókhông
Trong phần này, chúng ta chỉ giới thiệu sơ bộ về quá trình phân tích cú pháp Vấn đề này sẽđược trình bày đầy đủ hơn trong bài cuối cùng của môn học
Có nhiều phương pháp phân tích cú pháp khác nhau Tuy nhiên, các phương pháp này đềunằm trong hai lớp: từ trên xuống (top down) và từ dưới lên (bttom-up)
Để xác định xem một chương trình nguồn có được sinh ra từ một văn phạm hay không, cácphương pháp phân tích cú pháp thường xây dựng cây phân tích của chương trình nguồndựa trên văn phạm Nếu tồn tại cây phân tích thì ta nói chương trình được sinh ra bởi vănphạm hay chương trình đúng cú pháp, ngược lại thì chương trình nguồn là không đúng cúpháp
Để xây dựng cây phân tích cho một chương trình nguồn, chúng ta có thể tiến hành hai cách
cơ bản tương ứng với hai lớp các phương pháp phân tích cú pháp Phương pháp phân tích
từ trên xuống sẽ bắt đầu xây dựng cây phân tích từ các lá đi đến đỉnh của một câu haychương trình cho trước Ngược lại, phương pháp phân tích từ dưới lên sẽ bắt đầu xây dựngcây phân tích từ đỉnh đến các lá của một câu hay chương trình cho trước
Đối với mỗi lớp phương pháp phân tích cú pháp, có nhiều phương pháp khác nhau:
- Phân tích cú pháp từ trên xuống gồm:
o Phân tích đệ quy đi xuống: phương pháp này thực hiện việc xây dựng câyphân tích từ gốc đến lá và có khả năng quay lui (backtracking)
o Phân tích cú pháp đoán nhận trước: phương này phân tích từ trên xuốngnhưng không bị quay lui
o Phân tích cú pháp đoán nhận trước không đệ quy: phương này sử dụng ngănxếp (stack) thay vì quay lui
- Phân tích cú pháp từ dưới lên gồm:
o Phân tích cú pháp thứ tự yếu
o Phân tích cú pháp LR
Dưới đây là một ví dụ đơn giản minh họa phân tích cú pháp Chúng có văn phạm G địnhnghĩa ngôn ngữ sau:
<exp> <exp> + <term>
<exp> <exp> - <term>
<exp> <term>
<term> 0
Trang 21<term> 9
Chương trình nguồn là biểu thức: 9 – 5 + 3
Thực hiện việc phân tích cú pháp sẽ tạo được cây phân tích như sau:
Như thế, chương trình nguồn 9 – 5 + 3 là đúng đắn về mặt cú pháp
4 Ngữ nghĩa hình thức
Căn cứ vào cú pháp của ngôn ngữ lập trình, người lập trình viết chương trình gồmcác câu lệnh theo trình tự cho phép để giải quyết bài toán đặt ra Để đạt được mục đích đó,mỗi câu lệnh viết ra không những chỉ đúng đắn về mặt cú pháp mà còn phải đúng đắn vềmặt ngữ nhĩa (semantic) hay ý nghĩa logic câu lệnh Tính đúng đắn về mặt ngữ nghĩa chophép giải quyết được bài toán, chương trình chạy luôn dừng, ổn định và cho kết quả phùhợp với yêu cầu đặt ra
Ngữ nghĩa không chỉ là cơ sở cho việc chứng minh tính đúng đắn của chương trình mà còn
có ích cho quá trình thiết kế và cài đặt ngôn ngữ lập trình
Trong bài học này sẽ giới thiệu hai loại ngữ nghĩa hình thức: ngữ nghĩa tiên đề (axiomaticsemantics) và ngữ nghĩa biểu thị (denotationnal semantics)
5 Ngữ nghĩa tiên đề Ngữ nghĩa của phát biểu
Ngữ nghĩa của phát biểu đượcđặc tả bởi biểu thức sau:
{ P } S { Q }trong đó P là điều kiện về trị các biến trước khi thực thi S, gọi là điều kiện trước(precondition) của S; Q là điều kiện về trị các biến sau khi thực thi S, gọi là hậu điều kiện(postcondition) của S
Diễn dịch đặc tả trên: nếu P đúng thì sau khi S được thực hiện xong ta có Q đúng
Nếu với điều kiện sau bất kỳ của S, ta biết được những điều kiện trước sao cho khi S đượcthực hiện xong điều kiện sau trên được thỏa mãn, thì ta nói biết được ngữ nghĩa của S.Điều kiện P2 gọi là yếu hơn P1 nếu P1 P2 Điều kiện trước ở đặc tả của phát biểu càngyếu, ngữ nghĩa của phát biểu càng rõ Với điều kiện sau Q của S, chúng ta kí hiệu p (S, Q)
là điều kiện trước yếu nhất bảo đảm Q đúng sau khi được thực hiện xong Hàm p (S, Q)với Q là biến số có thể xem là ngữ nghĩa chính xác của S
Ví dụ: p (n := n +1, n > 0) = n 0 (n nguyên)
Trang 22Với mọi điều kiện trước P về n thỏa mãn đặc tả:
Luật L3 cho chúng ta điều phải chứng minh
Luật L4: (luật về câu lệnh ghép)
Nếu ({P} S1 {Q}) ({Q} S2 {R})thì {P} S1 ; S2 {R}
Ví dụ 4.8, chứng minh tính đúng đắn của đặc tả:
{f = i!} i := i + 1; f = f * i {f = i!}
Theo ví dụ 4.6 và 4.7chúng ta có:
{ f = i!} i := i +1 {f * i = i!} { f * i = i!} f := f * i {f = i!}
Áp dụng L4 cho chúng ta điều phải chứng minh
Trang 23Luật L5: (luật về phát biểu IF)
Vì (x.y < 0) (x > y) x > 0 nên theo L2 ta có:
{(x.y < 0) (x > y)} max := x {max > 0} (*)Tương tự theo L3 và L2 ta có:
{(x.y < 0) (x y)} max := y {max > 0} (**)
Luật L7: (luật về vòng lặp WHILE)
{f = i!} i = i +1; f := f*i {f = i!}
Vì (f = i!) (i n) f = i! Nên theo L2 ta có:
{(f = i!) (i n)} i = i +1; f := f*i {f = i!}
Trang 24nên theo L1 chúng ta suy ra được điều phải chứng minh.
5 Ngữ nghĩa biểu thị
Ở ngữ nghĩa biểu thị, ngữ nghĩa của mỗi cấu trúc cú pháp được đặc tả bằng một ánh
xạ, gọi là hàm ngữ nghĩa (semantic function), từ miền cú pháp (semantic domain) vào miềnngữ nghĩa (semantic domain)
Như thế, chúng ta nhận thấy, ngữ nghĩa biểu thị chỉ ra ngữ nghĩa của mỗi cấu trúc cú pháp,tưca là mỗi cấu trúc cú pháp có một ngữ nghĩa nhất định
Chúng ta sẽ lấy một ví dụ đơn giản: mô tả hình thức ngữ nghĩa của ngôn ngữ gồmcác số nhị phân bằng ngữ nghĩa biểu thị
Ngôn ngữ số nhị phân là ngôn ngữ chỉ gồm các số nhị phân ‘0’ và ‘1’ Ngữ nghĩa củacủa số nhị phân chính là giá trị thập phân của số nhị phân đó Như thế, miền cú pháp là tậphợp các số nhị phân, còn miền ngữ nghĩa là tập hợp các số tự nhiên (giá trị của các số nhịphân)
Cú pháp và ngữ nghĩa của ngôn ngữ số nhị phân được định nghĩa như sau:
Cú pháp:
N Nml số nhị phân
N ::= 0 | 1 | N0 | N1Miền ngữ nghĩa:
N = {0, 1, 2, } số tự nhiênHàm ngữ nghĩa:
Bài tập:
Câu 1: Trình bày định nghĩa cú pháp
Câu 2:Hãy nêu các vấn đề cú pháp
Câu 3: Hãy nêu các phương pháp phân tích cú pháp
Câu 4: Hãy nêu các loại ngữ nghĩa hình thức
Trang 25Bài 5 ĐẶC TRƯNG LẬP TRÌNH CÂU LỆNH (LẬP TRÌNH THỦ TỤC)
MÃ BÀI ITPRG3_06.5Học xong bài này học viên sẽ có khả năng:
5.1.1 Biến
Biến là một ÐTDL được người lập trình định nghĩa và đặt tên một cách tường minhtrong chương trình Giá trị của biến có thể bị thay đổi trong thời gian tồn tại của nó Tên biếnđược dùng để xác định và tham khảo tới biến Trong các NNLT, tên biến thường được quyđịnh dưới dạng một dãy các chữ cái, dấu gạch dưới và các chữ số, bắt đầu bằng một chữcái và có chiều dài hữu hạn
5.1.2 Hằng
Hằng là một ÐTDL có tên và giá trị của hằng không thay đổi trong thời gian tồn tạicủa nó Hằng trực kiện (literal constant) là một hằng mà tên của nó là sự mô tả giá trị của nó(chẳng hạn "27" là sự mô tả số thập phân của ÐTDL giá trị 27) Chú ý sự khác biệt giữa 2giá trị 27 Một cái là một số nguyên được biểu diễn thành một dãy các bit trong bộ nhớ trongquá trình thực hiện chương trình và cái tên "27" là một chuỗi 2 ký tự "2" và "7" mô tả một sốnguyên như nó được viết trong chương trình
5.2 Lập trình cấu trúc
Lệnh đơn là một sự tính toán được kết thúc bằng dấu chấm phẩy Các định nghĩa
biến và các biểu thức được kết thúc bằng dấu chấm phẩy như trong ví dụ sau:
int i; // lệnh khai báo
++i; // lệnh này có một tác động chính yếu
double d = 10.5; // lệnh khai báo
Trang 26Nhiều lệnh đơn có thể kết nối lại thành một lệnh phức bằng cách rào chúng bên
trong các dấu ngoặc xoắn Ví dụ:
{ int min, i = 10, j = 20;
min = (i < j ? i : j);
cout << min << '\n';
}
Bởi vì một lệnh phức có thể chứa các định nghĩa biến và định nghĩa một phạm vi cho
chúng, nó cũng được gọi một khối Phạm vi của một biến C++ được giới hạn bên trong khối
trực tiếp chứa nó Các khối và các luật phạm vi sẽ được mô tả chi tiết hơn khi chúng ta thảoluận về hàm trong chương kế
3.2 Lệnh if
Đôi khi chúng ta muốn làm cho sự thực thi một lệnh phụ thuộc vào một điều kiện nào
đó cần được thỏa Lệnh if cung cấp cách để thực hiện công việc này, hình thức chung củalệnh này là:
average = sum / count;
Để làm cho nhiều lệnh phụ thuộc trên cùng điều kiện chúng ta có thể sử dụng lệnhphức:
balance += interest;
} else {
Trang 27interest = balance * debitRate;
else interest = balance * debitRate;
balance += interest;
Hoặc đơn giản hơn bằng việc sử dụng biểu thức điều kiện:
interest = balance * (balance > 0 ? creditRate : debitRate);
balance += interest;
Hoặc chỉ là:
balance += balance * (balance > 0 ? creditRate : debitRate);
Các lệnh if có thể được lồng nhau bằng cách để cho một lệnh if xuất hiện bên trongmột lệnh if khác Ví dụ:
if (callHour > 6) {
if (callDuration <= 5) charge = callDuration * tarrif1;
else charge = 5 * tarrif1 + (callDuration - 5) * tarrif2;
} else charge = flatFee;
Một hình thức được sử dụng thường xuyên của những lệnh if lồng nhau liên quanđến phần else gồm có một lệnh if-else khác Ví dụ:
if (ch >= '0' && ch <= '9') kind = digit;
else {
if (ch >= 'A' && ch <= 'Z') kind = upperLetter;
else {
if (ch >= 'a' && ch <= 'z') kind = lowerLetter;
else kind = special;
} }
Để cho dễ đọc có thể sử dụng hình thức sau:
if (ch >= '0' && ch <= '9') kind = digit;
Trang 28else if (ch >= 'A' && ch <= 'Z') kind = capitalLetter;
else if (ch >= 'a' && ch <= 'z') kind = smallLetter;
else kind = special;
Ví dụ, chúng ta phải phân tích cú pháp một phép toán toán học nhị hạng thành bathành phần của nó và phải lưu trữ chúng vào các biến operator , operand1 , và operand2 Lệnh switch sau thực hiện phép toán và lưu trữ kết quả vào result
Trang 29Như đã được minh họa trong ví dụ, chúng ta cần thiết chèn một lệnh break ở cuốimỗi case Lệnh break ngắt câu lệnh switch bằng cách nhảy đến điểm kết thúc của lệnh này.
Ví dụ, nếu chúng ta mở rộng lệnh trên để cho phép x cũng có thể được sử dụng như là toán
tử nhân, chúng ta sẽ có:
switch (operator) { case '+': result = operand1 + operand2;
Chúng ta có thể quan sát rằng bất kỳ lệnh switch nào cũng có thể được viết nhưnhiều câu lệnh if-else Ví dụ, lệnh trên có thể được viết như sau:
if (operator == '+') result = operand1 + operand2;
else if (operator == '-') result = operand1 - operand2;
else if (operator == 'x' || operator == '*') result = operand1 * operand2;
else if (operator == '/') result = operand1 / operand2;
else cout << "unknown operator: " << ch << '\n';
Người ta có thể cho rằng phiên bản switch là rõ ràng hơn trong trường hợp này Tiếpcận if-else nên được dành riêng cho tình huống mà trong đó switch không thể làm đượccông việc (ví dụ, khi các điều kiện là phức tạp không thể đơn giản thành các đẳng thức toánhọc hay khi các nhãn cho các case không là các hằng số)
Trang 30khác 0 thì sau đó lệnh (cũng được gọi là thân vòng lặp) được thực hiện và toàn bộ quá trìnhđược lặp lại Ngược lại, vòng lặp được kết thúc
Ví dụ, chúng ta muốn tính tổng của tất cả các số nguyên từ 1 tới n Điều này có thểđược diễn giải như sau:
Bảng 3.1 Vết của vòng lặp while
Vòng lặp i n i <= n sum += i++
Một 1 5 1 1 Hai 2 5 1 3
Ba 3 5 1 6 Bốn 4 5 1 10 Năm 5 5 1 15
Đôi khi chúng ta có thể gặp vòng lặp while có thân rỗng (nghĩa là một câu lệnh null)
Ví dụ vòng lặp sau đặt n tới thừa số lẻ lớn nhất của nó
while (n % 2 == 0 && n /= 2) ;
Ở đây điều kiện lặp cung cấp tất cả các tính toán cần thiết vì thế không thật sự cần mộtthân cho vòng lặp Điều kiện vòng lặp không những kiểm tra n là chẵn hay không mà nó cònchia n cho 2 và chắc chắn rằng vòng lặp sẽ dừng
3.5 Lệnh do - while
Lệnh do (cũng được gọi là vòng lặp do) thì tương tự như lệnh while ngoại trừ thân của
nó được thực thi trước tiên và sau đó điều kiện vòng lặp mới được kiểm tra Hình thứcchung của lệnh do là:
do {
Trang 313.6 Lệnh for
Lệnh for (cũng được gọi là vòng lặp for) thì tương tự như vòng lặp while nhưng có haithành phần thêm vào: một biểu thức được ước lượng chỉ một lần trước hết và một biểu thứcđược ước lượng mỗi lần ở cuối mỗi lần lặp Hình thức tổng quát của lệnh for là:
for ( biểu thức1; biểu thức2; biểu thức3)
sum = 0;
for (i = 1; i <= n; ++i) sum += i;
Điều này được ưa chuộng hơn phiên bản của vòng lặp while mà chúng ta thấy trước
đó Trong ví dụ này i thường được gọi là biến lặp
C++ cho phép biểu thức đầu tiên trong vòng lặp for là một định nghĩa biến Ví dụtrong vòng lặp trên thì i có thể được định nghĩa bên trong vòng lặp:
for (int i = 1; i <= n; ++i) sum += i;
Trái với sự xuất hiện, phạm vi của i không ở trong thân của vòng lặp mà là chínhvòng lặp Xét trên phạm vi thì ở trên tương đương với:
int i;
for (i = 1; i <= n; ++i) sum += i;
Bất kỳ biểu thức nào trong 3 biểu thức của vòng lặp for có thể rỗng Ví dụ, xóa biểuthức đầu và biểu thức cuối cho chúng ta dạng giống như vòng lặp while:
for (; i != 0;) // tương đương với: while (i != 0)
Trang 32something; // something;
Xóa tất cả các biểu thức cho chúng ta một vòng lặp vô hạn Điều kiện của vòng lặpnày được giả sử luôn luôn là đúng
for (;;) // vòng lặp vô hạn something;
Trường hợp vòng lặp với nhiều biến lặp thì hiếm dùng Trong những trường hợp nhưthế, toán tử phẩy (,) được sử dụng để phân cách các biểu thức của chúng:
for (i = 0, j = 0; i + j < n; ++i, ++j) something;
Bởi vì các vòng lặp là các lệnh nên chúng có thể xuất hiện bên trong các vònglặp khác Nói các khác, các vòng lặp có thể lồng nhau Ví dụ,
for (int i = 1; i <= 3; ++i) for (int j = 1; j <= 3; ++j) cout << '(' << i << ',' << j << ")\n";
cho tích số của tập hợp {1,2,3} với chính nó, kết quả như sau:
(1,1) (1,2) (1,3) (2,1) (2,2) (2,3) (3,1) (3,2) (3,3)
do { cin >> num;
if (num < 0) continue;
// xử lý số ở đây … } while (num != 0);
Điều này tương đương với:
do { cin >> num;
if (num >= 0) {
Trang 33// xử lý số ở đây … }
} while (num != 0);
Một biến thể của vòng lặp này để đọc chính xác một số n lần (hơn là cho tới khi số đó
là 0) có thể được diễn giải như sau:
for (i = 0; i < n; ++i) { cin >> num;
if (num < 0) continue; // làm cho nhảy tới: ++i // xử lý số ở đây …
} Khi lệnh continue xuất hiện bên trong vòng lặp được lồng vào thì nó áp dụng trực tiếplên vòng lặp gần nó chứ không áp dụng cho vòng lặp bên ngoài Ví dụ, trong một tập cácvòng lặp được lồng nhau sau đây, lệnh continue áp dụng cho vòng lặp for và không áp dụngcho vòng lặp while:
while (more) { for (i = 0; i < n; ++i) { cin >> num;
if (num < 0) continue; // làm cho nhảy tới: ++i // process num here
} //etc
}
3.8 Lệnh break
Lệnh break có thể xuất hiện bên trong vòng lặp (while, do, hay for) hoặc một lệnhswitch Nó gây ra bước nhảy ra bên ngoài những lệnh này và vì thế kết thúc chúng Giốngnhư lệnh continue, lệnh break chỉ áp dụng cho vòng lặp hoặc lệnh switch gần nó Sử dụnglệnh break bên ngoài vòng lặp hay lệnh switch là lỗi
Ví dụ, chúng ta đọc vào một mật khẩu người dùng nhưng không cho phép một số hữu hạnlần thử:
for (i = 0; i < attempts; ++i) { cout << "Please enter your password: ";
Trang 34for (i = 0; i < attempts && !verified; ++i) { cout << "Please enter your password: ";
cin >> password;
verified = Verify(password));
if (!verified) cout << "Incorrect!\n";
} Người ta cho rằng phiên bản của break thì đơn giản hơn nên thường được ưa chuộnghơn
3.9 Lệnh goto
Lệnh goto cung cấp mức thấp nhất cho việc nhảy Nó có hình thức chung là: gotonhãn; trong đó nhãn là một định danh được dùng để đánh dấu đích cần nhảy tới Nhãn cầnđược theo sau bởi một dấu hai chấm (:) và xuất hiện trước một lệnh bên trong hàm nhưchính lệnh goto
Ví dụ, vai trò của lệnh break trong vòng lặp for trong phần trước có thể viết lại bởi một lệnhgoto
for (i = 0; i < attempts; ++i) { cout << "Please enter your password: ";
//etc
Bởi vì lệnh goto cung cấp một hình thức nhảy tự do không có cấu trúc (không giốngnhư lệnh break và continue) nên dễ làm gãy đổ chương trình Phần lớn các lập trình viênngày nay tránh sử dụng nó để làm cho chương trình rõ ràng Tuy nhiên, goto có một vài (dùcho hiếm) sử dụng chính đáng Vì sự phức tạp của những trường hợp như thế mà việc cungcấp những ví dụ được trình bày ở những phần sau
Trang 35int main (void) {
cout << "Hello World\n";
return 0;
} Khi một hàm có giá trị trả về không là void (như trong ví dụ trên), nếu không trả vềmột giá trị sẽ mang lại một cảnh báo trình biên dịch Giá trị trả về thực sự sẽ không đượcđịnh nghĩa trong trường hợp này (nghĩa là, nó sẽ là bất cứ giá trị nào được giữ trong vị trí bộnhớ tương ứng của nó tại thời điểm đó)
Bài tập
3.1 Viết chương trình nhập vào chiều cao (theo centimet) và trọng lượng (theo kilogram) của
một người và xuất một trong những thông điệp: underweight , normal , hoặcoverweight , sử dụng điều kiện:
Underweight: weight < height/2.5
Normal: height/2.5 <= weight <= height/2.3
Overweight: height/2.3 < weight
3.2 Giả sử rằng n là 20, đoạn mã sau sẽ xuất ra cái gì khi nó được thực thi?
if (n >= 0)
if (n < 10) cout << "n is small\n";
else cout << "n is negative\n";
3.3 Viết chương trình nhập một ngày theo định dạng dd/mm/yy và xuất nó theo định dạng
3.5 Viết chương trình nhập vào một số cơ số 8 và xuất ra số thập phân tương đương Ví dụ
sau minh họa các công việc thực hiện của chương trình theo mong đợi:
Nhap vao so bat phan: 214 BatPhan(214) = ThapPhan(140) 3.6 Viết chương trình cung cấp một bảng cửu chương đơn giản của định dạng sau cho các
số nguyên từ 1 tới 9:
1 x 1 = 1
1 x 2 = 2
9 x 9 =
Trang 36BÀI 6 LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
MÃ BÀI ITPRG3_06.5
6.1 Giới thiệu
Hướng đối tượng (object orientation) cung cấp một kiểu mới để xây dựng phần mềm.Trong kiểu mới này, các đối tượng (object) và các lớp (class) là những khối xây dựng trongkhi các phương thức (method), thông điệp (message), và sự thừa kế (inheritance) cung cấpcác cơ chế chủ yếu
Lập trình hướng đối tượng (OOP- Object-Oriented Programming) là một cách tư
duy mới, tiếp cận hướng đối tượng để giải quyết vấn đề bằng máy tính Thuật ngữ OOPngày càng trở nên thông dụng trong lĩnh vực công nghệ thông tin
Nếu bạn chưa bao giờ sử dụng một ngôn ngữ OOP thì trước tiên bạn nên nắm vữngcác khái niệm của OOP hơn là viết các chương trình Bạn cần hiểu được đối tượng (object)
là gì, lớp (class) là gì, chúng có quan hệ với nhau như thế nào, và làm thế nào để các đốitượng trao đổi thông điệp (message) với nhau, vâng vâng
OOP là tập hợp các kỹ thuật quan trọng mà có thể dùng để làm cho việc triển khaichương trình hiệu quả hơn Quá trình tiến hóa của OOP như sau:
Lập trình tuyến tính
Lập trình có cấu trúc
Sự trừu tượng hóa dữ liệu
Lập trình hướng đối tượng
Lập trình hướng đối tượng (OOP) là một phương pháp thiết kế và phát triển phần
mềm dựa trên kiến trúc lớp và đối tượng
Chương 6: Lập trình hướng đối tượng 76
6.2 Trừu tượng hóa (Abstraction)
Trừu tượng hóa là một kỹ thuật chỉ trình bày những các đặc điểm cần thiết của vấn
đề mà không trình bày những chi tiết cụ thể hay những lời giải thích phức tạp của vấn đề đó.Hay nói khác hơn nó là một kỹ thuật tập trung vào thứ cần thiết và phớt lờ đi những thứkhông cần thiết
Ví dụ những thông tin sau đây là các đặc tính gắn kết với con người:
Sự trừu tượng hóa đã không ngừng phát triển trong các ngôn ngữ lập trình, nhưngchỉ ở mức dữ liệu và thủ tục Trong OOP, việc này được nâng lên ở mức cao hơn – mức đối
Trang 37tượng Sự trừu tượng hóa được phân thành sự trừu tượng hóa dữ liệu và trừu tượng hóachương trình
Khái niệm 6.2
6.3 Đối tượng (object)
Các đối tượng là chìa khóa để hiểu được kỹ thuật hướng đối tượng Bạn có thể nhìnxung quanh và thấy được nhiều đối tượng trong thế giới thực như: con chó, cái bàn, quyển
vở, cây viết, tivi, xe hơi Trong một hệ thống hướng đối tượng, mọi thứ đều là đối tượng.Một bảng tính, một ô trong bảng tính, một biểu đồ, một bảng báo cáo, một con số hay một sốđiện thoại, một tập tin, một thư mục, một máy in, một câu hoặc một từ, thậm chí một ký tự,tất cả chúng là những ví dụ của một đối tượng Rõ ràng chúng ta viết một chương trìnhhướng đối tượng cũng có nghĩa là chúng ta đang xây dựng một mô hình
Trừu tượng hóa dữ liệu (data abstraction) là tiến trình xác định và nhóm các thuộctính và các hành động liên quan đến một thực thể đặc thù trong ứng dụng đang phát triển
Trừu tượng hóa chương trình (program abstraction) là một sự trừu tượng hóa dữ liệu
mà làm cho các dịch vụ thay đổi theo dữ liệu
của một vài bộ phận trong thế giới thực Tuy nhiên các đối tượng này có thể đượcbiểu diễn hay mô hình trên máy tính
Một đối tượng thế giới thực là một thực thể cụ thể mà thông thường bạn có thể sờ,nhìn thấy hay cảm nhận được Tất cả các đối tượng trong thế giới thực đều có trạng thái(state) và hành động (behaviour) Ví dụ:
Trạng thái Hành động
Con chó
Tên
Màu
Giống
Vui sướng
Bàn đạp
Dây xích
Bánh xe
Tăng tốc
Giảm tốc
Chuyển bánh răng
Hình 6.1 Ví dụ về trạng thái và hành động
Trang 38Các đối tượng phần mềm (software object) có thể được dùng để biểu diễn cácđối tượng thế giới thực Chúng được mô hình sau khi các đối tượng thế giới thực có cảtrạng thái và hành động Giống như các đối tượng thế giới thực, các đối tượng phần mềmcũng có thể có trạng thái và hành động Một đối tượng phần mềm có biến (variable) haytrạng thái (state) mà thường được gọi là thuộc tính (attribute; property) để duy trì trạng tháicủa nó và phương thức (method) để thực hiện các hành động của nó Thuộc tính là mộthạng mục dữ liệu được đặt tên bởi một định danh (identifier) trong khi phương thức là mộtchức năng được kết hợp với đối tượng chứa nó
OOP thường sử dụng hai thuật ngữ mà sau này Java cũng sử dụng là thuộc tính (attribute)
và phương thức (method) để đặc tả tương ứng cho trạng thái (state) hay biến (variable) vàhành động (behavior) Tuy nhiên C++ lại sử dụng hai thuật ngữ dữ liệu thành viên (memberdata) và hàm thành viên (member function) thay cho các thuật ngữ này
Xét một cách đặc biệt, chỉ một đối tượng riêng rẽ thì chính nó không hữu dụng Một chươngtrình hướng đối tượng thường gồm có hai hay nhiều hơn các đối tượng phần mềm tươngtác lẫn nhau như là sự tương tác của các đối tượng trong trong thế giới thực
xe đạp sẽ có các thuộc tính để xác định các trạng thái của chiếc xe đạp như: tốc độ của nó
là 10 km trên giờ, nhịp bàn đạp là 90 vòng trên phút, và bánh răng hiện tại là bánh răng thứ
5 Các thuộc tính này thông thường được xem như thuộc tính thể hiện (instance attribute)bởi vì chúng chứa đựng các trạng thái cho một đối tượng xe đạp cụ thể Trong kỹ thuậthướng đối tượng thì một đối tượng cụ thể được gọi là một thể hiện (instance)
Khái niệm 6.4
Hình 6.2 minh họa một xe đạp được mô hình như một đối tượng phần mềm:
Đối tượng xe đạp phần mềm cũng có các phương thức để thắng lại, tăng nhịp đạphay là chuyển đổi bánh răng Nó không có phương thức để thay đổi tốc độ vì tốc độ của xeđạp có thể tình ra từ hai yếu tố số vòng quay và bánh răng hiện tại Những phương thức nàythông thường được biết như là các phương thước thể hiện (instance method) bởi vì chúngtác động hay thay đổi trạng thái của một đối tượng cụ thể
Một đối tượng cụ thể được gọi là một thể hiện (instance)
6.4 Lớp (Class)
Trong thế giới thực thông thường có nhiều loại đối tượng cùng loại Chẳng hạn chiếc
xe đạp của bạn chỉ là một trong hàng tỉ chiếc xe đạp trên thế giới Tương tự, trong mộtchương trình hướng đối tượng có thể có nhiều đối tượng cùng loại và chia sẻ những đặcđiểm chung Sử dụng thuật ngữ hướng đối tượng, chúng ta có thể nói rằng chiếc xe đạp củabạn là một thể hiện của lớp xe đạp Các xe đạp có một vài trạng thái chung (bánh răng hiện
Trang 39tại, số vòng quay hiện tại, hai bánh xe) và các hành động (chuyển bánh răng, giảm tốc) Tuynhiên, trạng thái của mỗi xe đạp là độc lập và có thể khác với các trạng thái của các xe đạpkhác Trước khi tạo ra các xe đạp, các nhà sản xuất thường thiết lập một bảng thiết kế(blueprint) mô tả các đặc điểm và các yếu tố cơ bản của xe đạp Sau đó hàng loạt xe đạp sẽđược tạo ra từ bản thiết kế này Không hiệu quả nếu như tạo ra một bản thiết kế mới chomỗi xe đạp được sản xuất
Trong phần mềm hướng đối tượng cũng có thể có nhiều đối tượng cùng loại chia sẻnhững đặc điểm chung như là: các hình chữ nhật, các mẫu tin nhân viên, các đoạn phim, …Giống như là các nhà sản xuất xe đạp, bạn có thể tạo ra một bảng thiết kế cho các đốitượng này Một bảng thiết kế phần mềm cho các đối tượng được gọi là lớp (class)
Khái niệm 6.5
Trở lại ví dụ về xe đạp chúng ta thấy rằng một lớp Xedap là một bảng thiết kế chohàng loạt các đối tượng xe đạp được tạo ra Mỗi đối tượng xe đạp là một thể hiện của lớpXedap và trạng thái của nó có thể khác với các đối tượng xe đạp khác Ví dụ một xe đạphiện tại có thể là ở bánh răng thứ 5 trong khi một chiếc khác có thể là ở bánh răng thứ 3
Lớp Xedap sẽ khai báo các thuộc tính thể hiện cần thiết để chứa đựng bánh rănghiện tại, số vòng quay hiện tại, cho mỗi đối tượng xe đạp Lớp Xedap cũng khai báo vàcung cấp những thi công cho các phương thức thể hiện để cho phép người đi xe đạpchuyển đổi bánh răng, phanh lại, chuyển đổi số vòng quay, như Hình 6.3
Lớp (class) là một thiết kế (blueprint) hay một mẫu ban đầu (prototype) định nghĩa cácthuộc tính và các phương thức chung cho tất cả các đối tượng của cùng một loại nào đó Một đối tượng là một thể hiện cụ thể của một lớp
Sau khi bạn đã tạo ra lớp xe đạp, bạn có thể tạo ra bất kỳ đối tượng xe đạp nào từlớp này Khi bạn tạo ra một thể hiện của lớp, hệ thống cấp phát đủ bộ nhớ cho đối tượng vàtất cả các thuộc tính thể hiện của nó Mỗi thể hiện sẽ có vùng nhớ riêng cho các thuộc tínhthể hiện của nó Hình 6.4 minh họa hai đối tượng xe đạp khác nhau được tạo ra từ cùng lớpXedap:
Ngoài các thuộc tính thể hiện, các lớp có thể định nghĩa các thuộc tính lớp (classattribute) Một thuộc tính lớp chứa đựng các thông tin mà được chia sẻ bởi tất cả các thểhiện của lớp Ví dụ, tất cả xe đạp có cùng số lượng bánh răng Trong trường hợp này, địnhnghĩa một thuộc tính thể hiện để giữ số lượng bánh răng là không hiệu quả bởi vì tất cả cácvùng nhớ của các thuộc tính thể hiện này đều giữ cùng một giá trị Trong những trường hợpnhư thế bạn có thể định nghĩa một thuộc tính lớp để chứa đựng số lượng bánh răng của xeđạp.Tất cả các thể hiện của lớp Xedap sẽ chia thuộc tính này Một lớp cũng có thể khai báocác phương thức lớp (class methods) Bạn có thể triệu gọi một phương thức lớp trực tiếp từlớp nhưng ngược lại bạn phải triệu gọi các phương thức thể hiện từ một thể hiện cụ thể nàođó
6.5 Thuộc tính (Attribute)
Các thuộc tính trình bày trạng thái của đối tượng Các thuộc tính nắm giữ các giá trị
dữ liệu trong một đối tượng, chúng định nghĩa một đối tượng đặc thù
Trang 40Khái niệm 6.7
Một thuộc tính có thể được gán một giá trị chỉ sau khi một đối tượng dựa trên lớp ấyđược tạo ra Một khi các thuộc tính được gán giá trị chúng mô tả một đối tượng Mọi đốitượng của một lớp phải có cùng các thuộc tính nhưng giá trị của các thuộc tính thì có thểkhác nhau Một thuộc tính của đối tượng có thể nhận các giá trị khác nhau tại những thờiđiểm khác nhau
Thuộc tính lớp(class attribute) là một hạng mục dữ liệu liên kết với một lớp cụ thể mà không liên kết với các thể hiện của lớp Nó được định nghĩa bên trong định nghĩa lớp và được chia sẻ bởi tất cả các thể hiện của lớp
Phương thức lớp (class method) là một phương thức được triệu gọi mà không tham khảo tới bất kỳ một đối tượng nào Tất cả các phương thức lớp ảnh hưởng đến toàn bộ lớp chứ không ảnh hưởng đến một lớp riêng rẽ nào
Thuộc tính (attribute) là dữ liệu trình bày các đặc điểm về một đối tượng
Chương 6: Lập trình hướng đối tượng 82 6.6 Phương thức (Method)
Các phương thức thực thi các hoạt động của đối tượng Các phương thức là nhân tốlàm thay đổi các thuộc tính của đối tượng
Khái niệm 6.8
Các phương thức xác định cách thức hoạt động của một đối tượng và được thực thikhi đối tượng cụ thể được tạo ra.Ví dụ, các hoạt động chung của một đối tượng thuộc lớpChó là sủa, vẫy tai, chạy, và ăn Tuy nhiên, chỉ khi một đối tượng cụ thể thuộc lớp Chó đượctạo ra thì các phương thức sủa, vẫy tai, chạy, và ăn mới được thực thi
Các phương thức mang lại một cách nhìn khác về đối tượng Khi bạn nhìn vào đốitượng Cửa ra vào bên trong môi trường của bạn (môi trường thế giới thực), một cách đơngiản bạn có thể thấy nó là một đối tượng bất động không có khả năng suy nghỉ Trong tiếpcận hướng đối tượng cho phát triển hệ thống, Cửa ra vào có thể được liên kết tới phươngthức được giả sử là có thể được thực hiện Ví dụ, Cửa ra vào có thể mở, nó có thể đóng, nó
có thể khóa, hoặc nó có thể mở khóa Tất cả các phương thức này gắn kết với đối tượngCửa ra vào và được thực hiện bởi Cửa ra vào chứ không phải một đối tượng nào khác
6.7 Thông điệp (Message)
Một chương trình hay ứng dụng lớn thường chứa nhiều đối tượng khác nhau Cácđối tượng phần mềm tương tác và giao tiếp với nhau bằng cách gởi các thông điệp(message) Khi đối tượng A muốn đối tượng B thực hiện các phương thức của đối tượng Bthì đối tượng A gởi một thông điệp tới đối tượng B
Ví dụ đối tượng người đi xe đạp muốn đối tượng xe đạp thực hiện phương thứcchuyển đổi bánh răng của nó thì đối tượng người đi xe đạp cần phải gởi một thông điệp tớiđối tượng xe đạp
Đôi khi đối tượng nhận cần thông tin nhiều hơn để biết chính xác thực hiện công việc
gì Ví dụ khi bạn chuyển bánh răng trên chiếc xe đạp của bạn thì bạn phải chỉ rõ bánh răng