Giáo trình tin học đại cương - Chương 3: Lập trình căn bản bằng ngôn ngữ Pascal

pdf 231 trang huongle 9510
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình tin học đại cương - Chương 3: Lập trình căn bản bằng ngôn ngữ Pascal", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdfgiao_trinh_tin_hoc_dai_cuong_chuong_3_lap_trinh_can_ban_bang.pdf

Nội dung text: Giáo trình tin học đại cương - Chương 3: Lập trình căn bản bằng ngôn ngữ Pascal

  1. CHƯƠNG 3 LẬP TRÌNH CĂNBẢN BẰNG NGÔN NGỮ PASCAL 1. GIỚI THIỆU NGÔN NGỮ PASCAL Pascal là ngôn ngữ lập trình cấp cao được giáo sư Niklaus Wirth ở Trường đại học Kỹ thuật Zurich (Thụy sĩ) thiết kế và công bố vào năm 1971. Ông đặt tên cho ngôn ngữ của mình là Pascal để tưởng nhớ nhà toán học nổi tiếng người Pháp ở thế kỷ 17: Blaise Pascal, người đã sáng chế ra chiếc máy tính cơ khí đầu tiên của nhân loại. Qua thời gian sử dụng, Pascal ngày càng được đông đảo người dùng đánh giá cao, và trở thành một trong các ngôn ngữ lập trình phổ biến nhất hiện nay. Thành công của ngôn ngữ Pascal là ở chỗ: nó là ngôn ngữ đầu tiên đưa ra và thể hiện được khái niệm lập trình có cấu trúc. Ý tưởng về một chương trình có cấu trúc xuất phát từ suy nghĩ cho rằng có thể chia một bài toán lớn, phức tạp thành nhiều bài toán nhỏ, đơn giản hơn. Nếu mỗi bài toán nhỏ được giải quyết bằng một chương trình con, thì khi liên kết các chương trình con này lại sẽ tạo nên một chương trình lớn giải quyết được bài toán ban đầu. Bằng cách chia một chương trình thành các chương trình con như vậy, người thảo chương có thể lập trình để giải quyết riêng lẻ từng phần một, từng khối một, hoặc có thể tổ chức để nhiều người cùng tham gia, mỗi người phụ trách một vài khối. Ðặc biệt khi phải thay đổi hay sửa chữa trong một khối thì điều đó sẽ ít ảnh hưởng đến các khối khác. Tính cấu trúc của ngôn ngữ Pascal còn thể hiện trong việc tổ chức các câu lệnh và tổ chức dữ liệu. Từ các lệnh đã có, người thảo chương có thể nhóm chúng lại với nhau và đặt giữa 57
  2. hai từ khóa Begin và End tạo thành một câu lệnh mới phức tạp hơn gọi là câu lệnh ghép. Ðến lượt mình, hai hay nhiều lệnh ghép lại có thể được nhóm lại để tạo thành một câu lệnh ghép phức tạp hơn nữa Tương tự như thế, ngôn ngữ Pascal cũng cho phép xây dựng các kiểu dữ liệu phức tạp hơn từ các kiểu dữ liệu đã có. Pascal là một ngôn ngữ không chỉ chặt chẽ về mặt cú pháp mà còn chặt chẽ về mặt dữ liệu. Mỗi biến, mỗi hằng tham gia trong chương trình luôn có một kiểu dữ liệu xác định và chỉ nhận những giá trị có cùng kiểu dữ liệu với nó. Ðiều này buộc người lập trình phải nắm chắc cú pháp và luôn chú ý đến tính tương thích của các biểu thức về mặt kiểu dữ liệu. Chính vì thế, lập trình bằng ngôn ngữ Pascal là một cơ hội tốt không chỉ rèn luyện tư duy mà còn rèn luyện tính cẩn thận và chính xác. Ngày nay, ngôn ngữ Pascal được dùng để viết các chương trình ứng dụng trong nhiều lĩnh vực. Với văn phạm sáng sủa, dễ hiểu, với khả năng đủ mạnh, Pascal được xem là ngôn ngữ thích hợp nhất để giảng dạy ở các trường phổ thông và đại học. 2. CẤU TRÚC CỦA CHƯƠNG TRÌNH PASCAL 2.1. Ví dụ mở đầu Để có một cái nhìn tổng quan trước khi đi vào các vấn đề chi tiết của ngôn ngữ Pascal, xin hãy cùng xem chương trình sau: Bài toán: Viết chương trình để nhập vào độ dài hai cạnh của một hình chữ nhật, tính và in lên màn hình diện tích và chu vi của hình chữ nhật đó. Nếu gọi hai cạnh của hình chữ nhật là a và b, gọi diện tích và chu vi lần lượt là S và P thì công thức tính S và P là: S = a*b P = 2*(a+b) 58
  3. Chương trình cụ thể như sau: PROGRAM VIDU; {Tinh dien tich va chu vi hinh chu nhat} Uses CRT; Var a, b, S, P: Real; Begin Clrscr; Write( ‘Nhap chieu dai: ‘); Readln(a); Write( ‘Nhap chieu rong: ‘); Readln(b); S:=a*b; P:=2* (a+b); Writeln (‘Dien tich = ‘, S:8:2); Writeln (‘Chu vi = ‘, P:8:2); Readln; End. Giải thích các dòng trong chương trình: + {Tinh dien tich va chu vi hinh chu nhat} Đây là lời chú giải, nêu lên mục đích của chương trình. + Uses CRT; 59
  4. Khai báo sử dụng thư viện CRT của Turbo Pascal. + Var a, b, S, P: Real; Khai báo bốn biến a, b, S, P có kiểu dữ liệu là số thực (Real). + Begin Lệnh bắt đầu chương trình + Clrscr; Lệnh xóa màn hình. + Write(‘Nhap chieu dai: ‘); Lệnh in lên màn hình câu ‘Nhap chieu dai: ‘nhằm nhắc người dùng nhập vào số đo chiều dài. + Readln(a); Lệnh nhập dữ liệu cho biến a. + Write( ‘Nhap chieu rong: ‘); Lệnh in lên màn hình câu ‘Nhap chieu rong: ‘nhằm nhắc người dùng nhập vào số đo chiều rộng. + Readln(b); Lệnh nhập dữ liệu cho biến b. + S:= a* b; Lệnh tính diện tích S của hình chữ nhật. + P:= 2*(a+b); Tương tự, lệnh tính chu vi P của hình chữ nhật. + Writeln(‘Dien tich = ‘, S:8:2); 60
  5. Lệnh này in lên màn hình câu ‘Dien tich= ‘, kế đó in giá trị của biến S. Chỉ thị S:8:2 ấn định dành 8 cột trên màn hình để in giá trị của S, trong đó có hai cột để in phần thập phân. + Writeln(‘Chu vi = ‘, P:8:2); Lệnh này in lên màn hình câu ‘Chu vi = ‘, kế đó in giá trị của chu vi P có cả thảy 8 chữ số, trong đó có 2 số phần lẻ. + Readln; Lệnh dừng màn hình để xem kết quả chạy chương trình. + End. Dấu hiệu kết thúc chương trình. 2.2. Cấu trúc chung của chương trình Pascal Chương trình là một dãy các câu lệnh chỉ thị cho máy các công việc phải thực hiện. Một chương trình Pasccal đầy đủ gồm ba phần chính: + Phần tiêu đề + Phần khai báo + Phần thân chương trình Program Têntựđặt; {Phần tiêu đề} {Phần khai báo } Uses {khai báo sử dụng thư viện chuẩn} Label {khai báo nhãn} Const {khai báo hằng} Type {khai báo kiểu dữ liệu} Var {khai báo biến} Function {khai báo các chương trình con} 61
  6. Procedure {hàm và thủ tục} {Phần thân chương trình } Begin {Các lệnh} End. 2.2.1. Phần tiêu đề chương trình Phần này bắt đầu bằng từ khóa Program, sau đó ít nhất là một khoảng trắng và một tên do người dùng tự đặt, cuối cùng kết thúc bằng dấu chấm phẩy ‘;’. Ví dụ: Program Btap1; hoặc: Program Giai_pt_bac2; Phần tiêu đề chiếm một dòng, còn gọi là phần đầu của chương trình, nó có thể không có cũng được. 2.2.2. Phần khai báo Phần khai báo có nhiệm vụ giới thiệu và mô tả các đối tượng, các đại lượng sẽ tham gia trong chương trình, giống như ta giới thiệu các thành viên trong một cuộc họp. Nó gồm khai báo sử dụng thư viện chuẩn, khai báo nhãn, khai báo hằng, khai báo kiểu dữ liệu mới, khai báo biến, và khai báo các chương trình con. Tùy theo yêu cầu cụ thể mà mỗi khai báo này có thể có hoặc không. Khai báo nhãn (Label) chỉ dùng khi trong chương trình có sử dụng lệnh nhảy vô điều kiện GOTO. Nhược điểm của lệnh GOTO là làm mất tính cấu trúc của chương trình, trong khi có thể thay thế nó bằng các câu lệnh có cấu trúc của Pascal. Vì thế, để rèn luyện kỹ năng lập trình có cấu trúc, chúng ta sẽ không dùng lệnh GOTO trong giáo trình này. 62
  7. Các thủ tục và hàm được dùng khi có nhu cầu thiết kế các chương trình lớn, phức tạp. Đối với các bài toán nhỏ, đơn giản, việc sử dụng chương trình con là chưa cần thiết. Chi tiết về phần này sẽ được trình bày kỹ trong các bài sau. Sau đây ta điểm qua vài nét về các khai báo thông dụng nhất. a) Khai báo hằng và khai báo biến Biến là đại lượng có giá trị thay đổi được, còn Hằng là đại lượng có giá trị không đổi, chúng được dùng trong chương trình để lưu trữ các dữ liệu, tham gia vào các biểu thức tính toán và các quá trình xử lý trong máy. Việc khai báo có tác dụng xác định tên và kiểu dữ liệu của biến hay hằng. Biến và hằng là những thành phần khó có thể thiếu được trong một chương trình. Để khai báo biến ta dùng từ khóa Var, để khai báo hằng ta dùng từ khóa Const, ví dụ: Const N=10; Var x, y: Real; i, k: Integer; b) Khai báo (định nghĩa) một kiểu dữ liệu mới Ngoài các kiểu dữ liệu mà bản thân ngôn ngữ đã có sẵn như kiểu thực, kiểu nguyên, kiểu ký tự, kiểu lôgic, người dùng có thể tự xây dựng các kiểu dữ liệu mới phục vụ cho chương trình của mình, nhưng phải mô tả sau từ khóa TYPE. Khi đã định nghĩa một kiểu dữ liệu mới, ta có thể khai báo các biến thuộc kiểu dữ liệu này. Ví dụ, ta định nghĩa một kiểu dữ liệu mới có tên là Mang: Type Mang = Array[1 10] of Real; Bây giờ có thể khai báo hai biến A và B có kiểu dữ liệu là kiểu Mang: Var A, B: Mang; 63
  8. c) Khai báo sử dụng thư viện chuẩn Turbo Pascal có sẵn một số hàm và thủ tục chuẩn, chúng được phân thành từng nhóm theo chức năng mang các tên đặc trưng, gọi là các thư viện hay đơn vị chương trình (Unit), như: Crt, Graph, Dos, Printer Muốn sử dụng các hàm hay thủ tục của thư viện nào, ta phải khai báo có sử dụng thư viện đó, lời khai báo phải để ở ngay sau phần tiêu đề của chương trình theo cú pháp: Uses danhsáchthưviện; Ví dụ: do thủ tục Clrscr nằm trong thư viện CRT, nên nếu trong chương trình mà có dùng lệnh Clrscr, thì phải khai báo: Uses CRT; Muốn sử dụng cả hai thư viện CRT và GRAPH, ta khai báo: Uses CRT, GRAPH; 2.2.3. Phần thân chương trình Đây là phần chủ yếu nhất của một chương trình, bắt buộc phải có. Thân chương trình bắt đầu bằng từ khóa BEGIN và kết thúc bằng END. (có dấu chấm ở cuối). Giữa khối BEGIN và END là các lệnh. Mỗi lệnh phải kết thúc bằng dấu chấm phẩy ‘;’. Một lệnh, nếu dài, thì có thể viết trên hai hay nhiều dòng, ví dụ: Writeln(‘Phuong trinh co hai nghiem la X1= ‘, X1:8:2,’va X2= ‘, X2:8:2); Ngược lại, một dòng có thể viết nhiều lệnh miễn là có dấu ‘;’ để phân cách các lệnh đó, chẳng hạn: Write(‘Nhap A, B, C: ‘); Readln(A,B,C); Thông thường mỗi dòng chỉ nên viết một lệnh để dễ đọc, dễ kiểm tra lỗi. 64
  9. 3. CÁC PHẦN TỬ CƠ BẢN CỦA PASCAL 3.1. Tập ký tự cơ bản Mỗi ngôn ngữ đều được xây dựng từ một tập ký tự nào đó. Nhiều ký tự nhóm lại với nhau tạo nên các từ. Nhiều từ liên kết với nhau theo một qui tắc ngữ pháp nhất định (gọi là văn phạm) thì tạo nên các mệnh đề. Trong các ngôn ngữ thảo chương, mệnh đề còn được gọi là câu lệnh. Một tập hợp các câu lệnh được sắp xếp theo một trật tự nhất định nhằm chỉ thị cho máy các thao tác phải thực hiện tạo thành một chương trình. Các chương trình được soạn thảo bởi người thảo chương và được lưu trữ trên đĩa dưới dạng các tập tin. Ngôn ngữ Pascal được xây dựng trên bộ ký tự cơ bản, gồm: + Các chữ cái latinh: A, B, C, ,Z, a, b, c, , z + Các chữ số: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Các ký hiệu đặc biệt: +, -, *, /, =, <, {,}, [, ], %, $, &, #, + Ký tự gạch nối ‘_’ và ký tự trắng ‘‘ (space) Các chữ Ả rập: , , , không thuộc bộ ký tự của Pascal. 3.2. Từ khóa (key word ) Có một số từ được Pascal dành riêng cho việc xây dựng các câu lệnh, các khai báo, các phép tính, gọi là từ khóa. Việc sử dụng các từ khóa đòi hỏi phải tuân thủ đúng quy tắc đề ra, và đặc biệt là người lập trình không được đặt một tên mới (tên biến, tên hằng, tên hàm, tên thủ tục, ) trùng với một trong các từ khóa. Dưới đây là danh sách các từ khóa của Pascal: absolute, and, array, Begin, case, const, div, do, downto, else, End, file, for, forward, function, goto, if, implementation, in, inline, interface, interrupt, label, mod, nil, not, of, or, packed, procedure, program, record, repeat, 65
  10. set, shl, shr, string, then, to, type, unit, until, uses, var, while, with, xor Các từ khóa có thể viết dưới dạng chữ hoa hay chữ thường hay xen kẽ chữ hoa với chữ thường đều được. Ví dụ viết Begin hay Begin hay BEGIN là như nhau. 3.3. Tên (identifier) Các biến, các hằng, các hàm, các thủ tục, được sử dụng trong chương trình đều cần phải đặt tên, còn gọi là định danh hay danh hiệu. Các tên này do người thảo chương tự đặt và phải đảm bảo đúng quy tắc: tên phải bắt đầu bằng chữ cái, kế đó có thể là chữ cái, chữ số, hay dấu gạch nối ‘_’. Tên không được đặt trùng với từ khóa. Chiều dài của tên tối đa là 127 ký tự. Thông thường tên nên đặt ngắn gọn và có tính gợi nhớ. Dưới đây là ví dụ về các tên được đặt đúng: + Delta, X1, X2, i, j , Chuc_vu, Luong, So_luong, Don_gia. + Còn các tên: 3ABC, In, Chu vi, Ma-so là sai vì: + 3ABC: bắt đầu bằng số + Chu vi: có chứa ký tự trắng + Ma-so: ký tự ‘-’là dấu trừ chứ không phải gạch nối. + In: trùng với từ khóa In Cũng giống như từ khóa, Tên không phân biệt viết hoa hay viết thường. Ví dụ viết X1 hay x1 cũng chỉ là một tên thôi. Trong Pascal có một số tên đã được đặt sẵn rồi, gọi là tên chuẩn, chẳng hạn: Abs, Arctan, Boolean, Byte, Char, Cos, Copy, Delete, Eof, False, Longint, Ord, Integer, Real, Readln, Writeln, True, Text, 66
  11. Mặc dù người lập trìmh có thể đặt một tên mới trùng với một trong các tên chuẩn, song, để đỡ nhầm lẫn, chúng ta nên tránh điều này. 4. CÁC KIỂU DỮ LIỆU ĐƠN GIẢN 4.1. Khái niệm Chức năng của máy điện toán là xử lý các thông tin. Các thông tin được nhập và lưu trữ trong bộ nhớ của máy dưới các dạng khác nhau: có thể là số, là chữ, có thể là hình ảnh, âm thanh, mà thuật ngữ tin học gọi chung là dữ liệu. Tính đa dạng của dữ liệu đòi hỏi phải tổ chức và phân phối bộ nhớ thích hợp để lưu trữ và xử lý tốt các dữ liệu. Ngôn ngữ thảo chương chia các dữ liệu thành từng nhóm riêng trên đó xây dựng một số phép toán tạo nên các kiểu dữ liệu khác nhau, mỗi kiểu dữ liệu là một tập hợp các giá trị mà một biến thuộc kiểu đó có thể nhận. Khi một biến được khai báo thuộc kiểu dữ liệu nào thì máy sẽ dành cho biến đó một dung lượng thích hợp trong bộ nhớ để có thể lưu trữ các giá trị thuộc kiểu dữ liệu đó. Các kiểu dữ liệu trong ngôn ngữ Pascal được chia ra thành hai loại chính: loại đơn giản và loại có cấu trúc. Mỗi kiểu dữ liệu đơn giản là một tập các giá trị cơ sở có thứ tự. Ví dụ kiểu Integer gồm các số nguyên nằm trong phạm vi từ -32768 đến 32767 và có thứ tự tự nhiên: -32768< < -1 < 0 < 1 < < 32767 , kiểu lôgic chỉ có hai giá trị False, True với quy ước False < True. Các kiểu dữ liệu có cấu trúc được xây dựng từ các kiểu dữ liệu đơn giản. Mỗi kiểu dữ liệu có cấu trúc là một tập các phần tử thuộc kiểu dữ liệu đơn giản được tổ chức lại theo một quy tắc nhất định . Các kiểu dữ liệu đơn giản gồm có: kiểu nguyên, kiểu thực, kiểu lôgic, kiểu ký tự, kiểu liệt kê và kiểu đoạn con. 67
  12. Các kiểu dữ liệu có cấu trúc gồm có: kiểu mảng, kiểu bản ghi, kiểu tập hợp và kiểu tập tin. Riêng chuỗi ký tự (STRING) là một kiểu dữ liệu đặc biệt, vừa có tính đơn giản lại vừa có tính cấu trúc. Mỗi chuỗi có thể xem là một giá trị, nhưng cũng có thể xem là một mảng các giá trị kiểu ký tự. Vì vậy, việc sử dụng chuỗi cũng có hai mức khác nhau: mức đơn giản và mức có cấu trúc. Các kiểu dữ liệu đơn giản còn được phân thành hai loại: đếm được (Ordinal type) và không đếm được. Kiểu thực thuộc loại không đếm được, các giá trị của nó dày đặc. Tất cả các kiểu dữ liệu đơn giản còn lại: nguyên, ký tự, lôgic, liệt kê và đoạn con đều thuộc loại đếm được (còn gọi là rời rạc). Dưới đây sẽ lần lượt trình bày kỹ về bốn kiểu dữ liệu đơn giản chuẩn và thông dụng: kiểu nguyên, kiểu thực, kiểu logic, kiểu ký tự. Kiểu chuỗi được giới thiệu để có thể sử dụng ngay ở mức đơn giản. 4.2. Kiểu số nguyên 4.2.1. Các kiểu số nguyên Tên kiểu Phạm vi giá trị Số byte ShortInt -128 127 1 Byte 0 255 1 Integer -32768 32767 2 Word 0 65535 2 LongInt -2147483648 2147483647 4 68
  13. Ngoài kiểu Integer là thông dụng nhất, các số nguyên còn được chia ra thành bốn kiểu nữa đó là: Byte, Word, ShortInt và LongInt. Bảng trên liệt kê chi tiết về tên gọi, phạm vi giá trị và độ dài tính theo đơn vị byte của từng kiểu nguyên. Các biến nguyên chỉ có thể nhận các giá trị là các số nguyên nằm trong phạm vi giá trị của biến đó. Khi gán cho một biến một số nguyên nằm ngoài phạm vi của biến thì máy sẽ báo lỗi: "Const out of range". Ví dụ, cho khai báo: Var i: Byte; N: Integer; thì các lệnh đưới đây là đúng: i:= 200; N:= -1500; còn các lệnh dưới đây là bị lỗi: i:= -5; N:= 50000; Ðặc biệt không thể gán một số thực cho một biến nguyên. Câu lệnh sau là sai: N:= 12.5; Khi gặp tình huống này, máy sẽ báo lỗi "Type mismatch". Chú ý Các số nguyên hệ thập lục phân (hệ 16) được biểu diễn bằng cách viết thêm dấu $ ở trước số, ví dụ ba số sau đây: $A, $FF và $10 là các số nguyên viết trong hệ 16. Chúng có giá trị tương ứng trong hệ 10 là: 10, 255 và 16 69
  14. 4.2.2. Các phép toán số học trên số nguyên + Phép cộng và trừ: ký hiệu + và - như thường lệ. + Phép nhân: ký hiệu bằng dấu *, ví dụ 4*2 cho kết quả là 8. + Phép chia: ký hiệu bằng dấu /, ví dụ 6/4 cho kết quả là 1.5. + Phép chia lấy phần nguyên: ký hiệu bằng từ khóa DIV. + Phép lấy phần dư nguyên của phép chia: ký hiệu bằng từ khóa MOD. Ví dụ: 15 DIV 6 cho kết quả là 2. 15 MOD 6 cho kết quả là 3. Các phép toán trên đều cho kết quả là các số nguyên, trừ ra phép chia (/) luôn cho kết quả là một số thực. Vì thế nếu N là một biến nguyên, mà gán: N:= 20/5; thì máy sẽ báo lỗi, bởi vế phải có giá trị kiểu thực (=4.0) mặc dù phần lẻ bằng không. Nhận xét: số nguyên N là chẵn nếu N mod 2 = 0 (tức N chia hết cho 2), ngược lại, là lẻ nếu N mod 2 trong Pascal có nghĩa là khác nhau ). * Thứ tự thực hiện các phép toán cũng giống như thường lệ: + Các biểu thức trong ( ) được tính trước tiên + Kế đến là *, /, div, mod + Sau cùng là +, - Ðối với các phép toán cùng thứ tự mà đứng liền nhau thì phép toán nào đứng trước được làm trước. 70
  15. Ví dụ: tính biểu thức sau: 15 mod (2 +4) * 20 div (10 div 4) + 40 mod ( 5* 3) = 15 mod 6 * 20 div 2 + 40 mod 15 = 3 * 20 div 2 + 10 = 60 div 2 + 10 = 30 + 10 = 40 * Ví dụ sau đây là một ứng dụng của các phép toán div, mod: + Ví dụ: Nhập một số tiền N đồng, đổi ra xem được bao nhiêu tờ 5 đồng, bao nhiêu tờ 2 đồng, bao nhiêu tờ 1 đồng sao cho tổng số tờ là ít nhất. Ví dụ N = 43đ = 8 tờ 5đ + 1 tờ 2đ + 1 tờ 1đ. Cách tính như sau: Số tờ 5đ = 43 div 5 = 8 Số tiền dư = 43 mod 5 = 3 Số tờ 2đ = Số tiền dư div 2 = 3 div 2 =1 Số tờ 1đ = Số tiền dư mod 2 = 3 mod 2 = 1 Dưới đây là chương trình cụ thể: PROGRAM VIDU4_1; {Ðổi tiền} Var N, st5, st2, st1, sodu: LongInt; Begin Write(‘Nhap so tien: ’); Readln(N); st5:= N div 5; Sodu:= N mod 5; {tính phần dư} 71
  16. st2:= Sodu div 2; st1:= Sodu mod 2; Writeln(‘KET QUA DOI TIEN LA: ‘); Writeln(‘So to 5đ= ‘, st5); Writeln(‘So to 2đ= ‘, st2); Writeln(‘So to 1đ=‘, st1); Readln; End. 4.2.3. Các phép toán so sánh Ngôn ngữ Pascal có sáu phép toán so sánh được liệt kê trong bảng Ký hiệu Ý nghĩa Ví dụ = bằng nhau x=y y lớn hơn x>y >= lớn hơn hoặc bằng x>=y Kết quả của các biểu thức so sánh là một giá trị lôgic Ðúng (TRUE) hoặc Sai (FALSE). Ví dụ: Biểu thức 5*2=10 cho kết quả là TRUE. 72
  17. Biểu thức 5+2 10 div 3 cho kết quả là FALSE. 4.2.4. Các phép toán lôgic trên số nguyên Các phép tính NOT, AND, OR, XOR xử lý các bít nhị phân được xác định như sau: NOT 1 = 0 1 AND 1=1 1 OR 1=1 1 XOR 1=0 NOT 0 = 1 1 AND 0=0 1 OR 0=1 1 XOR 0=1 0 AND 1=0 0 OR 1=1 0 XOR 1=1 0 AND 0=0 0 OR 0=0 0 XOR 0=0 Mỗi số nguyên được biểu diễn trong máy dưới dạng một dãy các bít nhị phân. Số kiểu Integer được biểu diễn bằng 16 bit. Ví dụ: số 1 và số 2 có biểu diễn trong máy lần lượt là: 0000 0000 0000 0001 0000 0000 0000 0011 Phép lấy NOT một số nguyên sẽ đảo tất cả các bít biểu diễn số nguyên đó, tức là 0 thành 1, còn 1 thành 0. Ví dụ: NOT 1 = 1111 1111 1111 1110 NOT 2 = 1111 1111 1111 1100 Phép lấy AND, OR, XOR hai số nguyên được tiến hành bằng cách AND, OR, XOR từng cặp bít tương ứng của hai số đó, ví dụ: 1 OR 2 = 0000 0000 0000 0011= 2 1 AND 2 = 0000 0000 0000 0001= 1 73
  18. 4.2.5. Các phép dịch chuyển số học SHR và SHL N SHR k: dịch các bít của số nguyên N sang phải đi k bit. N SHL k: dịch các bít của số nguyên N sang trái đi k bit. Có thể chứng minh được: N SHR k = N div 2k N SHL k = N * 2k Ví dụ: 120 shr 4 = 7, vì: 120 shr 4 = 120 div 24 = 120 div 16 = 7. 120 shl 3 = 960, vì: 120 shl 3 = 120 * 23 = 120 * 8 = 960. Hai phép toán SHR và SHL được dùng khi muốn tăng tốc độ tính toán trên các số nguyên. 4.2.6. Các hàm có đối số nguyên * Hàm PRED(k): đối số k nguyên, trả về số nguyên đứng ngay trước k, tức là k-1 . Ví dụ: Pred (5) = 4, Pred (-6) = -7. * Hàm SUCC(k): đối số k nguyên, trả về số nguyên đứng ngay sau k, tức là k+1 . Ví dụ: Succ (5) = 6, Succ (-6) = -5. Nhận xét Lệnh k:=k+1; tương đương với lệnh k:=Succ(k); Lệnh k:=k-1; tương đương với lệnh k:=Pred(k); * Hàm ODD(k): đối số k nguyên, trả về giá trị logic là TRUE nếu k lẻ, là FALSE nếu k chẵn. 74
  19. Ví dụ: Odd(15) = True; Odd(4) = False. + Ví dụ: Nhập số nguyên N, nếu N chẵn thì in ra chữ chẵn, nếu N lẻ thì in ra chữ lẻ. Chương trình như sau: PROGRAM VIDU4_2; Var N: Integer; Begin Write(‘Nhap so N:’); Readln(N); If Odd(N) = TRUE then Write(N, ‘la so le’) Else Write(N, ‘la so chan’); Readln; End. 4.2.7. Các thủ tục có đối số nguyên Có hai thủ tục khá thông dụng là: * Thủ tục INC(k): tăng k lên một đơn vị. Ví dụ, sau khi thực hiện các lệnh: k:=5; Inc(k); thì giá trị sau cùng của k là 6. Vậy, lệnh Inc(k); tương đương với lệnh k:=k+1; hay k:=Succ(k); * Thủ tục DEC(k): giảm k đi một đơn vị. Ví dụ, sau khi thực hiện các lệnh: 75
  20. k:=5; Dec(k); thì giá trị của k sẽ là 4. Vậy, lệnh Dec(k); tương đương với lệnh k:=k-1; hay k:=Pred(k); 4.3. Kiểu số thực 4.3.1. Kiểu Real và các kiểu mở rộng Kiểu Real là kiểu số thực thông dụng nhất dùng để biểu diễn các số thực x có trị tuyệt đối |x| nằm trong khoảng từ 2.9*10-39 đến 1.7*10+38.Nếu x > 1.7*10+38 thì không biểu diễn x trong máy được, còn nếu |x| < 2.9*10-39 thì x được coi là bằng 0. Có hai cách biểu diễn các số thực: * Cách 1: Viết bình thường, trong đó dấu phẩy thập phân được thay bằng dấu chấm thập phân. Ví dụ: 45.0 -256.45 +122.08 * Cách 2: Viết số dưới dạng khoa học: 1.257E+01 (có giá trị = 1.257*101 = 12.57 ) 1257.0E-02 (có giá trị = 1257*10-2 = 12.57 ) Trong dạng này số gồm có hai phần, phần đứng trước E gọi là phần định trị, được viết theo cách 1, phần đứng sau E gọi là phần bậc, gồm dấu cộng hoặc trừ, tiếp đến là một số nguyên. Số viết theo cách 1 còn gọi là số có dấu chấm thập phân cố định, số viết theo cách 2 còn gọi là số có dấu chấm thập phân di động hay số dạng khoa học (Scientific). Ví dụ: Muốn khai báo hai biến x, y kiểu real, ta viết: 76
  21. Var x, y: Real; Ngoài kiểu Real ra, các số thực còn có bốn kiểu mở rộng nữa là Single, Double, Extended và Comp.Bảng sau nêu chi tiết về phạm vi giá trị và số byte dùng để lưu trữ trong bộ nhớ của từng kiểu số thực. Tên kiểu Phạm vi giá trị Số byte Real 2.9*10-39 1.7*1038 6 Single 1.5*10-45 3.4*1038 4 Double 5.0*10-324 1.7*10308 8 Extended 3.4*10-4932 1.1*104932 10 Comp -9.2*1018 9.2*1018 8 + Chú ý: Turbo Pascal thường chỉ làm việc với một kiểu Real. Muốn dùng bốn kiểu thực còn lại, phải chuyển sang mode 8087 bằng cách viết chỉ thị {$N+} ở ngay đầu chương trình. 4.3.2. Các phép toán trên số thực Có bốn phép toán số học là nhân (*), chia (/), cộng (+) và trừ (-). Khi một trong các số hạng tham gia tính toán là kiểu thực thì kết quả của phép toán cũng là một số thực. Phép toán DIV, MOD không dùng cho các số thực. Ví dụ: với hai biến x, y kiểu thực thì lệnh sau là bị lỗi vì biểu thức vế phải không hợp lệ: y:= x mod 10; 77
  22. Các phép toán so sánh (= , , >= ) cũng dùng được cho các số hạng là thực hay nguyên. 4.3.3. Các hàm có đối số nguyên hoặc thực * Hàm ABS(x): tính trị tuyệt đối của x: |x|. Kiểu dữ liệu của kết quả cùng kiểu với đối số. Nếu x nguyên thì ABS(x) cũng nguyên, nếu x là số thực thì ABS(x) cũng là số thực. Ví dụ: Abs(5 - 8) = 3 * Hàm SQR(x): tính bình phương của x: x2. Kiểu dữ liệu của kết quả cùng kiểu với đối số. Ví dụ: Sqr(4.0) = 16.0 Sqr(7 div 3) = 4 Trong các hàm dưới đây, đối số x có thể là nguyên hay thực, nhưng giá trị trả về luôn luôn là kiểu thực: * Hàm SQRT(x): tính , (x ≥ 0) * Hàm EXP(x): tính ex * Hàm LN(x): tính lnx, (x > 0) * Các hàm SIN(x), COS(x), và ARCTAN(x): tính sinx, cosx và arctgx. * Hàm INT(x): cho số thực bằng phần nguyên của x. Ví dụ: Int(12.55) = 12.0 Int(1+10/3)=4.0 * Hàm FRAC(x): cho số thực bằng phần lẻ của x. Ví dụ: Frac(12.55) = 0.55 Hai hàm đặc biệt dưới đây cho kết quả là số nguyên: * Hàm TRUNC(x): cho số nguyên là phần nguyên của x. 78
  23. Ví dụ: Trunc(12.55) = 12 Trunc(-2.98) = -2 * Hàm ROUND(x): cho số nguyên bằng cách làm tròn x. Ví dụ: Round(12.45) = 12 Round(-2.98) = -3 Chú ý rằng hàm Int(x) và hàm Trunc(x) cùng cho phần nguyên của x, chúng chỉ khác nhau về kiểu dữ liệu của giá trị trả về. Int(4.5)= 4.0 còn Trunc(4.5) = 4 (viết 4 thì hiểu đó là số nguyên, còn viết 4.0 thì hiểu đó là số thực). + Ví dụ: Viết chương trình nhập số thực x bất kỳ, tính và in các giá trị y và z lên màn hình theo công thức: x Trong Pascal không có hàm tính trực tiếp 2 và Log4(x), nên ta phải chuyển qua hàm ex và Ln(x) như sau: , và Chương trình cụ thể như sau: PROGRAM VIDU4_3; Var x, y, z: Real; Begin Write(‘Nhap x: ‘); Readln(x); y:= (sqrt (x*x+1) + sin(x)*sin(x)) / (3*exp(2*x) + 1); 79
  24. z:= exp( x*Ln(2) ) + Ln(abs(x)+1) / Ln(4); Writeln(‘y= ‘, y:10:3 ); Writeln(‘z= ‘, z:10:3 ); Readln; End. Khi chạy chương trình, nếu nhập x = 0 thì kết quả y = 0.250 và z = 1.000. 4.4. Kiểu ký tự (char) 4.4.1. Ký tự và biến kiểu ký tự Ký Mã Ký Mã Ký Mã tự ASCII tự ASCII tự ASCII 32 A 65 a 97 0 48 B 66 b 98 1 49 C 67 c 99 2 50 D 68 d 100 3 51 E 69 f 101 4 52 F 70 e 102 5 53 G 71 g 103 80
  25. 6 54 H 72 h 104 7 55 I 73 i 105 8 56 J 74 j 106 9 57 K 75 k 107 L 76 l 108 M 77 m 109 N 78 n 110 O 79 o 111 P 80 p 112 Q 81 q 113 R 82 r 114 S 83 s 115 T 84 t 116 U 85 u 117 V 86 v 118 W 87 w 119 81
  26. X 88 x 120 Y 89 y 121 Z 90 z 122 Các ký tự dùng trong máy tính điện tử được liệt kê đầy đủ trong bảng mã ASCII gồm 256 ký tự khác nhau và được đánh số thứ tự từ 0 đến 255. Số thứ tự của mỗi ký tự còn gọi là mã ASCII của ký tự đó. Bảng trên liệt kê một phần của bảng mã ASCII gồm các chữ số và chữ cái kèm theo mã của chúng. Trong bảng, ký tự có mã bằng 32 là ký tự trắng (space). Tuy có 256 ký tự khác nhau song chỉ có 128 ký tự đầu tiên là hay dùng, còn lại là các ký tự mở rộng. Các ký tự có mã từ 0 đến 31 gọi là các ký tự điều khiển, không in ra được, được dùng để điều khiển các thiết bị ngoại vi, chẳng hạn ký tự có mã là 7 dùng để tạo một tiếng kêu bip, ký tự có mã là 13 dùng để chuyển con trỏ màn hình xuống đầu dòng dưới Mỗi ký tự trong bảng mã ASCII gọi là một hằng ký tự, chiếm độ dài 1 byte, và khi viết trong Pascal phải được đặt trong cặp nháy đơn: ‘0’, ‘1’, ‘A’, ‘B’, ‘$’, Giữa các ký tự, có một thứ tự mặc nhiên theo nguyên tắc: ký tự có mã nhỏ hơn thì nhỏ hơn. Tức là: Ký tự trắng < ‘0’< ‘1’< < ‘9’< ‘A’< ‘B’< ’Z’< ‘a’< ‘b’< < ‘z’ Biến nhận giá trị là các hằng ký tự gọi là biến kiểu ký tự, chúng được khai báo nhờ từ khóa CHAR, chẳng hạn như khai báo hai biến ch và ch1 dưới đây: Var ch, ch1: Char; 82
  27. Khi đó có thể gán: ch:=‘A’; ch1:=‘$’; Ký tự ‘A’gọi là giá trị của biến ch, còn ‘$’là giá trị của biến ch1. + Nhận xét: Từ bảng mã của các chữ cái ta suy ra: Mã chữ thường = Mã chữ hoa tương ứng + 32. (1) 4.4.2. Các hàm liên quan đến ký tự * Hàm PRED(ch): cho ký tự đứng ngay trước ký tự ch trong bảng mã. Ví dụ: Pred(‘B’)=‘A’ * Hàm SUCC(ch): cho ký tự đứng ngay sau ký tự ch trong bảng mã. Ví dụ: Succ(‘A’)=‘B’. * Hàm UpCase(ch): đổi ký tự ch thành chữ hoa. Ví dụ: Upcase( ‘a’) = ‘A’, Upcase( ‘b’) = ‘B’, Upcase( ‘A’) = ‘A’. * Hàm ORD(ch): cho mã của ký tự ch. Ví dụ: Ord (‘A’) = 65, Ord (‘a’) = 97 . * Hàm CHR(k): đối số k nguyên, 0 k< 255, cho ký tự có mã bằng k. Ví dụ: Chr (65)= ‘A’, Chr (97)= ‘a’, Chr(32) là ký tự trắng. 83
  28. Có một số ký tự không có trên bàn phím, để viết chúng lên màn hình ta phải dùng lệnh Write và hàm CHR. Ví dụ: Lệnh Writeln(Chr(201)); in ra ký tự: + Lệnh Writeln(Chr(187)); in ra ký tự: + Ký tự có mã là 7 gọi là ký tự BEL (chuông), và lệnh: Write(Chr(7)); hay Write(#7); có tác dụng phát ra một tiếng kêu bip. Chú ý * Turbo Pascal (TP) cho phép viết gọn Chr(k) thành #k nếu k là hằng số. Ví dụ, hai lệnh sau cùng in lên màn hình chữ A: Write(#65); Write(Chr(65)); * Trong TP không có hàm đổi chữ hoa ra chữ thường, nhưng có thể làm việc này nhờ công thức (1) và hai hàm Ord và Chr: Chữ thường:= Chr ( Ord(chữ hoa) + 32 ) (1) + Ví dụ: Nhập vào một số nguyên k, 0 < k < 255, in ra ký tự có mã là k. Chương trình kết thúc khi nhập vào số 0: PROGRAM VIDU4_4; Uses CRT; Var k: Byte; Begin CLRSCR; Writeln(‘Nhập số 0 để kết thúc:’); Repeat 84
  29. Write(‘Nhập mã của ký tự: ‘); Readln(k); Writeln(‘Ký tự có mã ‘, k, ‘là ‘, Chr(k) ); Until k=0; Readln; End. + Ví dụ: Nhập một ký tự, nếu là chữ hoa thì đổi ra chữ thường, nếu là chữ thường thì đổi ra chữ hoa. PROGRAM VIDU4_5; {Ðổi chữ hoa ra thường và ngược lại} Var ch, ch1: Char; Begin Write(‘Nhập một ký tự:’); Readln(ch); If (ch>=‘A’) and ( ch<=‘Z’) then ch1:=Chr( Ord (ch)+32) else ch1:= Upcase(ch); Writeln(ch, ‘đã đổi ra: ‘, ch1); Readln; End. 4.5. Kiểu lôgic (boolean) Kiểu boolean chỉ có hai giá trị là TRUE (đúng) và FALSE (sai), không phân biệt chữ hoa hay chữ thường. Về quan hệ thứ tự thì FALSE < TRUE. Mỗi giá trị boolean chiếm một byte bộ nhớ. 85
  30. Các phép toán lôgic gồm có: NOT, AND, OR và XOR. Nếu A và B là hai đại lượng lôgic thì NOT A, A and B, A or B và A xor B cũng là những đại lượng lôgic có kết quả được cho ở bảng sau: A not A True False False True A B A and B A or B A xor B True True True True False True False False True True False True False True True False False False False False Cũng từ bảng này ta rút ra các nhận xét: * A and B là đúng khi và chỉ khi A và B đồng thời đúng. (Do đó chỉ cần một trong hai biến A hoặc B sai thì A and B sẽ sai). * A or B là sai khi và chỉ khi A và B đồng thời sai. (Do đó chỉ cần một trong hai biến A hoặc B đúng thì A or B sẽ đúng). * A xor B là đúng khi và chỉ khi A khác B. Thứ tự thực hiện các phép toán lôgic là như sau: NOT tính trước, kế đến AND, sau cùng là OR, XOR. Ví dụ: sau khi thực hiện lệnh: 86
  31. A:=Not (2*3=5) or (‘A’ 1); thì giá trị của A= FALSE, thật vậy: Not (2*3=5) or (‘A’ 1) = TRUE or TRUE and FALSE xor TRUE = TRUE or FALSE xor TRUE = TRUE xor TRUE = FALSE Biến chỉ nhận giá trị là TRUE hoặc FALSE gọi là biến kiểu lôgic. Khi khai báo biến kiểu lôgic ta dùng từ khóa Boolean, ví dụ: Var A, B: Boolean; Trong chương trình ta có thể gán: A:= True; B:=2*2 < 3; Giá trị của biến B sẽ là False vì biểu thức 2*2 < 3 là sai. *Về thứ tự tính toán, các phép so sánh thì ngang cấp nhau và được tính sau tất cả các phép toán khác. Ví dụ tính biểu thức: 5+7 div 2 < -7 mod 3 + 5*2 = 5 + 3 < -1 + 10 = 8< 9 = TRUE Do đó, khi trong một biểu thức mà có các phép toán lôgic xen kẽ với các biểu thức so sánh thì các biểu thức so sánh phải để trong ngoặc đơn. 87
  32. Chẳng hạn, biểu thức sau là sai quy cách: N > 0 and N 0) and (N 0) and (b>0) and (c>0) and (a+b>c) and (a+c>b) and (b+c>a); If Tgiac= FALSE then Writeln(‘Khong phai ba canh cua tam giac !’) 88
  33. Else Begin P:=(a+b+c)/2; S:= Sqrt( P*(P-a)*(P-b)*(P-c)); Writeln(‘Chu vi = ‘, 2*P:10:2); Writeln(‘Dien tich S= ‘, S:10:2); End; Readln; End. Khi chạy chương trình này, để nhập ba cạnh của tam giác, bạn gõ ba số cách nhau khoảng trắng rồi enter, chẳng hạn: Nhap 3 canh cua tam giac: 3 4 5 Kết quả chu vi = 12.00, dien tich = 6.00. Nếu nhập ba cạnh là: 2 3 6 thì máy hiện câu: "Không phải ba cạnh của tam giác ! " (vì 2+3 < 6). 4.6. Chuỗi ký tự (string) *Một dãy ký tự đặt trong cặp nháy đơn gọi là một hằng chuỗi.Dưới đây là ba hằng chuỗi: ‘NGON NGU PASCAL’ ‘Tin hoc nam 2000’ ‘123456’ Các chuỗi có thể được ghép nối với nhau nhờ phép cộng chuỗi. Khi cộng (+) hai chuỗi ta được một chuỗi duy nhất bằng cách ghép chuỗi sau vào cuối của chuỗi đầu. Ví dụ phép cộng: ‘Ngon ngu’+ ‘Pascal’cho kết quả là ‘Ngon ngu Pascal’. 89
  34. Các chuỗi cũng so sánh được với nhau. Việc so sánh hai chuỗi được thực hiện bằng cách so sánh từng cặp ký tự tương ứng từ trái qua phải. Khi phát hiện có một cặp ký tự khác nhau thì chuỗi nào chứa ký tự nhỏ hơn sẽ nhỏ hơn. Ví dụ: Biểu thức ‘Anh’ ‘Tha’ là đúng vì ‘o’> ‘a’ Nếu nội dung của hai chuỗi giống nhau từ đầu đến hết chiều dài của chuỗi ngắn hơn thì chuỗi ngắn hơn là nhỏ hơn. Ví dụ: Biểu thức ‘Tha’< ‘Thang’ là đúng vì ‘Tha’ ngắn hơn ‘Thang’. Hai chuỗi bằng nhau nếu chúng dài bằng nhau và mọi cặp ký tự ở các vị trí tương ứng đều giống nhau. Ví dụ: Biểu thức ‘Pascal’= ‘Pascal’ cho kết quả là đúng Biểu thức ‘Pascal’= ‘PAscal’ cho kết quả là sai. * Biến nhận giá trị là các hằng chuỗi gọi là biến kiểu chuỗi. Có thể khai báo hai biến chuỗi như sau: Var Ho_ten: String[20]; St: String; khi đó Ho_ten là biến chuỗi có thể chứa tối đa 20 ký tự, còn biến chuỗi St có thể chứa tối đa 255 ký tự, và ta có thể gán: Ho_ten:= ‘Nguyen Van An’; St:= ‘lap trinh bang ngon ngu Pascal’; Chuỗi ‘Nguyen Van An’gọi là giá trị của biến Ho_ten. Tương tự, chuỗi ‘lap trinh bang ngon ngu Pascal’là giá trị của biến St. 90
  35. 5. HẰNG, BIẾN và BIỂU THỨC 5.1. Khái niệm về biến và hằng Trong phần trước ta đã biết mỗi kiểu dữ liệu có một tập các giá trị tương ứng. Các giá trị của kiểu nguyên hay kiểu thực là các số, như 40 hay 5.72, các giá trị của kiểu ký tự là các ký tự như ‘A’hay ‘a’, còn kiểu lôgic thì chỉ có hai giá trị là True và False, Quá trình xử lý trong máy tính đòi hỏi mỗi giá trị phải được lưu trữ ở một ô nhớ nào đó trong bộ nhớ của máy, và ô nhớ này được đặt một cái tên để gọi. Khi đó mọi việc tính toán hay xử lý liên quan đến mỗi giá trị được thực hiện gián tiếp thông qua tên của ô nhớ chứa giá trị đó. Ví dụ, nếu số 5.72 được lưu trong ô nhớ có tên là x, thì biểu thức 5.72*2 có thể được viết là x*2. Việc dùng tên x dễ nhớ và tiện hơn nhiều so với việc dùng và nhớ số 5.72. Như vậy, khi một ô nhớ được đặt tên thì tên này đồng nhất với giá trị của nó. Trong một chương trình, mỗi ô nhớ có một tên duy nhất nhưng giá trị của nó thì có thể thay đổi hoặc không. Nếu giá trị của ô nhớ có thể thay đổi được thì ô nhớ này là một biến, tên của ô nhớ là tên biến, ngược lại, nếu giá trị của ô nhớ không thể thay đổi, thì ô nhớ là một hằng, tên của ô nhớ là tên hằng. Các biến và hằng tham gia trong chương trình đều phải được khai báo. Việc khai báo có tác dụng báo trước cho máy dành sẵn các ô nhớ thích hợp trong bộ nhớ để sẵn sàng chứa dữ liệu. 5.2. Khai báo biến và khai báo hằng Biến là đại lượng có giá trị thay đổi được trong chương trình. Cách khai báo biến như sau: Var Danhsáchtênbiến: TênKiểuDữliệu; 91
  36. Tên biến là tự đặt, theo đúng quy tắc của một tên. Ví dụ: Var i, j: Integer; x, y: Real; Theo khai báo trên, ta có hai biến i và j cùng kiểu số nguyên (Integer), và hai biến x, y cùng kiểu số thực (Real). Hằng là một đại lượng có giá trị không đổi trong chương trình. Cách khai báo: Const Tên_hằng = giátrị; Tên hằng là tự đặt, theo đúng quy tắc của một tên. Ví dụ: Const N = 10; SoPi = 3.1416; SoE = 2.718; Turbo Pascal có sẵn một số hằng chuẩn cho phép sử dụng mà không phải khai báo, như: Pi, MaxInt. Hằng Pi có giá trị bằng số , còn MaxInt = 32767, là số Integer lớn nhất. Chẳng hạn, có thể dùng các lệnh sau: Writeln(‘Dien tich hinh tron bkinh r = 5 la: ‘, Pi*5*5:8:3); Writeln(‘So Integer lon nhat = ‘, MaxInt); 5.3. Biểu thức Biểu thức là một công thức gồm có một hay nhiều thành phần được kết nối với nhau bởi các phép toán. Mỗi thành phần (hay toán hạng) có thể là hằng, là biến hay là hàm. Khi các phép toán trong biểu thức được thực hiện thì ta nhận được một giá trị gọi là kết quả của biểu thức. Kiểu dữ liệu của kết quả gọi là kiểu dữ liệu của biểu thức. 92
  37. Ví dụ: 3*5 div 2+7 mod 4 là biểu thức nguyên, có kết quả là 10 2+sin(pi/2) là biểu thức thực, có kết quả là 3.0 Chr(ord(‘a’)-32) là biểu thức ký tự, có kết quả là ‘A’ (4+2=6) and (‘B’ , , >=, IN 93
  38. Việc tính toán một biểu thức dựa theo hai quy tắc: Quy tắc 1: Phép toán có cấp ưu tiên nhỏ thì được tính trước, phép toán có cấp ưu tiên lớn thì được tính sau. Quy tắc 2: Ðối với các phép toán đứng liền nhau và có cùng cấp ưu tiên, thì cái nào đứng trước được tính trước. Ví dụ: Tính biểu thức số học: (4+5)*2 div 7 + sin(pi/6) = 9 * 2 div 7 + 0.5 = 18 div 7 + 0.5 = 2 + 0.5 = 2.5 Ví dụ: Tính biểu thức lôgic: (2 > 4 div 2) or Not ( 49.25 + 2 2) or Not ( 51.25 < 50) = FALSE or Not FALSE = FALSE or TRUE = TRUE 6. CÁC CÂU LỆNH ĐƠN GIẢN 6.1. Phân loại câu lệnh Câu lệnh là một dãy các ký tự cơ bản được xây dựng theo một quy tắc nhất định (gọi là cú pháp) nhằm chỉ thị cho máy thực hiện một công việc xác định. Các câu lệnh được chia ra hai loại: câu lệnh đơn giản và câu lệnh có cấu trúc. Lệnh gán và lời gọi thủ tục được xếp vào loại đơn giản. Ví dụ: 94
  39. k:= 20; Clrscr; Writeln(k); Các lệnh rẽ nhánh và lệnh lặp được xếp vào loại có cấu trúc, chúng được xây dựng từ các lệnh đơn giản. Ví dụ: If k>=0 then Writeln(k) else Writeln( -k); Hai hay nhiều lệnh đơn giản được gom lại và đặt giữa hai từ khóa BEGIN và END tạo thành một câu lệnh ghép, câu lệnh ghép cũng là lệnh có cấu trúc, ví dụ: Begin Write(‘nhập k:’); Readln(k); End; Từ các lệnh đơn giản và các lệnh có cấu trúc đã có lại có thể xây dựng được các lệnh có cấu trúc phức tạp hơn. Ví dụ: If k>= 0 then Writeln(k) else Begin Writeln(‘k âm, xin nhập lại: ‘); Readln(k); 95
  40. End; 6.2. Lệnh gán và lệnh chú giải 6.2.1. Câu lệnh gán Lệnh gán có cú pháp như sau: TênBiến:= Biểuthức; Ý nghĩa: tính toán biểu thức bên phải, rồi lưu kết quả tính được vào tên biến ở vế trái. Ví dụ: cho khai báo: Var A, B: Real; K: Integer; Khi dùng lệnh các lệnh: K:= 10; B:= K* 3+5.5; thì biến K có giá trị là 10, biến B có giá trị là 35.5. Nếu thực hiện tiếp lệnh gán: B:= 17/2; thì giá trị của B bây giờ sẽ là 8.5. Như vậy nếu một biến được gán nhiều lần thì nó sẽ lấy giá trị của lần gán sau cùng, tính đến thời điểm đang xét. Ðặc biệt, lệnh: B:=B +1; có tác dụng tăng giá trị của biến B lên 1 đơn vị, kết quả là B có giá trị bằng 9.5. Cách thực hiện lệnh B:=B+1 là như sau: lấy giá trị hiện thời của biến B (là 8.5) cộng thêm 1 (được 9.5), rồi đem kết quả gán cho chính biến B. Tương tự, lệnh B:=B-1; có tác dụng giảm B đi 1 đơn vị. 96
  41. Yêu cầu để cho lệnh gán thực hiện được là kiểu dữ liệu của biểu thức ở vế phải phải phù hợp với kiểu dữ liệu của biến ở vế trái, nếu không phù hợp thì khi dịch (Compile) chương trình, Turbo Pascal sẽ thông báo lỗi: "Error 26: Type mismatch". Ví dụ, lệnh gán dưới đây là sai vì vế trái là kiểu thực còn vế phải là kiểu chuỗi: A:=‘Pascal’; Chú ý rằng một số nguyên có thể gán cho một biến thực, (chẳng hạn lệnh A:=10; là đúng ), nhưng một số thực không thể gán cho một biến nguyên. Ví dụ lệnh K:=10/4; là sai vì biến K có kiểu nguyên, còn vế phải cho kết quả là một số thực (=2.5). Xét thêm ví dụ về các kiểu dữ liệu khác: Cho khai báo: Var Ch: Char; St: String[20]; Khi đó: Lệnh St:=‘A’; là đúng. Lệnh St:=‘1234’; là đúng. Lệnh Ch:=‘ABCD’; là sai vì vế phải là một chuỗi. Lệnh St:= 100; là sai vì vế phải là một số. Lệnh Ch:=‘1’; là đúng. Lệnh Ch:=St; là sai vì vế phải là một chuỗi. 6.2.2. Lời chú giải Lời chú giải có thể đặt tại bất kỳ chỗ nào trong chương trình và được viết theo một trong hai cách: {lời giải thích} 97
  42. (* lời giải thích *) Lời giải thích là một chuỗi ký tự giải thích mục đích của chương trình hay của một câu lệnh. Nó chỉ có tác dụng cho người dùng tham khảo nhằm hiểu nhanh mục đích của chương trình hay của một câu lệnh mà không cần phải đọc hết chương trình hay câu lệnh đó. 7. CÁC LỆNH NHẬP XUẤT DỮ LIỆU 7.1. Nhập dữ liệu, thủ tục Readln Nhập và xuất dữ liệu là hai khâu quan trọng trong quá trình xử lý thông tin. Hầu như chương trình nào cũng phải giải quyết vấn đề nhập, xuất dữ liệu. Có nhập được dữ liệu thì mới có dữ liệu để tính toán hay xử lý. Có dữ liệu xuất ra thì mới biết được kết quả của quá trình xử lý trong máy. 7.1.1. Nhập dữ liệu kiểu số: Ðể nhập dữ liệu cho biến nguyên hay thực, ta dùng lệnh: Readln(biến1, biến2, , biếnk); trong đó biến1, biến2, , biếnk đã được khai báo và có kiểu dữ liệu là nguyên hay thực. Khi gặp lệnh này, chương trình tạm dừng, chờ ta gõ đủ k số từ bàn phím và kết thúc bằng Enter, rồi gán lần lượt k số đó cho biến1, biến2, , biếnk. Ví dụ, để nhập dữ liệu cho hai biến thực x, y và biến nguyên j, ta dùng lệnh: Readln(x, y, j); Cách nhập như sau: hoặc gõ 10 6.5 4  (có khoảng trắng giữa hai số ), hoặc gõ từng số và Enter như dưới đây: 98
  43. 10 6.5 4 Trong cả hai trường hợp ta đều được: x=10, y=6.5, và j=4. Ngoài cách dùng một lệnh Readln(x, y, j); ta cũng có thể nhập riêng cho từng biến bằng ba lệnh sau: Readln(x); Readln(y); Readln(j); 7.1.2. Nhập dữ liệu kiểu ký tự hay kiểu chuỗi Ta dùng lệnh: Readln( biến ); Ví dụ, cho khai báo: Var Ho_ten: String[18]; Phai: String[3]; Khoi_thi: Char; Muốn nhập dữ liệu cho ba biến Ho_ten, Phai, Khoi_thi ta phải dùng ba lệnh: Readln(Ho_ten); Readln(Phai); Readln(Khoi_thi); Khi nhập, ta gõ: Tran Van Thanh  nam 99
  44. A Kết quả, ba biến sẽ có giá trị là: Ho_ten = ‘Tran Van Thanh’, Phai= ‘nam’và Khoi_thi=‘A’. Khác với dữ liệu số, ta không nên dùng một lệnh Readln để nhập dữ liệu cho hai hay nhiều biến kiểu ký tự hay kiểu chuỗi, vì sẽ có những nhầm lẫn không kiểm soát được. Ví dụ, không dùng lệnh sau: Readln( Ho_ten, Phai, Khoi_thi); mà phải dùng ba lệnh Readln, mỗi lệnh nhập cho một biến như đã nêu ở trên. Các chú ý a) Dữ liệu nhập phải phù hợp với kiểu của biến. Nếu không phù hợp thì chương trình sẽ dừng ngay và hiện thông báo lỗi. Ví dụ khi gặp lệnh Readln(j); mà ta gõ 4.5 thì bị lỗi vì j là biến nguyên, còn 4.5 là số thực. b) Lệnh Readln; là một dạng nhập dữ liệu đặc biệt vì nó không có biến nào để nhận dữ liệu nhập vào. Người ta dùng lệnh này khi muốn tạm dừng chương trình để xem kết quả trên màn hình, xem xong, gõ phím Enter thì chương trình chạy tiếp. c) Biến kiểu lôgic không nhập được từ bàn phím. d) Pascal còn có một lệnh nhập dữ liệu nữa là Read, có công dụng như lệnh Readln, nhưng ít dùng. Sự khác nhau giữa Read và Readln là ở chỗ: sau khi đã nhận đủ các giá trị cho các biến cần nhập, lệnh Readln sẽ xóa sạch các giá trị nhập thừa, còn lệnh Read thì không. Các giá trị nhập thừa của lệnh Read sẽ được tự động gán cho các biến trong lệnh nhập tiếp theo. Ví dụ, xét hai lệnh: 100
  45. Readln(x, y); Readln(j); Nếu khi nhập, ta gõ: 12.5 20.6 10  thì x = 12.5, y = 20.6, còn số 10 thừa bị xóa luôn. Biến j trong lệnh Readln(j) ở dưới không bị ảnh hưởng. Muốn nhập số 9 cho j, ta gõ 9 . Với đoạn chương trình: Read(x, y); Readln(j); Nếu khi nhập, ta cũng gõ: 12.5 20.6 10  thì x = 12.5, y = 20.6, còn số 10 thừa không bị xóa mà tự động gán cho biến j trong lệnh Readln(j) tiếp theo, kết quả j =10 cho dù ta chưa muốn nhập cho j. Vậy, lệnh Read có thể làm sai ý đồ nhập của lệnh nhập tiếp theo. Lời khuyên là không nên dùng lệnh Read, chỉ dùng Readln. 7.2. Câu lệnh xuất dữ liệu Các dữ liệu được in lên màn hình nhờ các lệnh sau: Writeln( bt1, bt2 , , btk ); Write( bt1, bt2 , , btk ); ở đây, bt1, bt2, , btk là các biểu thức cần phải in giá trị lên màn hình. Trong trường hợp đơn giản, mỗi biểu thức này có thể là một biến, một hằng, hay một hàm. Việc in được thực hiện như sau: tại vị trí hiện thời của con trỏ trên màn hình, in ra giá trị của biểu thức bt1, in tiếp giá trị của biểu thức bt2, , in tiếp giá trị của biểu thức btk. Các giá trị 101
  46. này được in trên một dòng, nếu dài quá khổ màn hình thì sẽ được in tiếp ở dòng dưới. Ví dụ, lệnh Writeln(3*2+9); sẽ in lên màn hình số 15. Nếu i, j là các biến nguyên thì khi thực hiện các lệnh sau: i:=10; j:=15*2; Writeln(i, j+1, 678); trên màn hình sẽ hiện: 1031678. Sự khác nhau giữa lệnh Writeln và Write là ở chỗ: sau khi in xong giá trị của các biểu thức, lệnh Writeln sẽ đưa con trỏ xuống đầu dòng dưới, còn lệnh Write thì không. Ðiều này chỉ ảnh hưởng đến lệnh in tiếp theo mà thôi. Ví dụ, khi thực hiện hai lệnh sau: Writeln(‘lap trinh bang ‘); Writeln(‘ngon ngu Pascal’); kết quả trên màn hình sẽ hiện hai dòng: lap trinh bang ngon ngu Pascal Nếu thay lệnh đầu bằng Write như dưới đây: Write(‘lap trinh bang ‘); Writeln(‘ngon ngu Pascal’); thì kết quả trên màn hình sẽ hiện ra chỉ một dòng: lap trinh bang ngon ngu Pascal 7.2.1. In không định dạng *Ðối với các biểu thức kiểu nguyên, kiểu ký tự, kiểu lôgíc hay kiểu chuỗi, thì lệnh: 102
  47. Writeln( biểuthức ); sẽ in nguyên văn giá trị của biểu thức. Ví dụ: Lệnh Writen(‘ket qua x=‘, 4+15); sẽ in ra: ket qua x=19 Lệnh Writeln(‘A = ‘, 2*3<5); sẽ in ra: A=FALSE vì biểu thức 2*3< 5 có kết quả là FALSE. Nếu gán: Ho_ten:=‘Tran Van Thanh’; thì lệnh Writeln(‘Ho va ten: ‘, Ho_ten); sẽ in lên màn hình dòng chữ: Ho va ten: Tran Van Thanh Cần phân biệt hai đại lượng ‘Ho va ten: ‘và Ho_ten. Chúng khác nhau hoàn toàn: ‘Ho va ten: ‘là một giá trị chuỗi, tức là một hằng chuỗi nên sẽ được in nguyên văn lên màn hình, còn Ho_ten là một biến kiểu chuỗi nên sẽ in giá trị mà biến này đang chứa. *Ðối với các biểu thức kiểu số thực thì lệnh: Writeln( biểuthức ); sẽ in giá trị của biểu thức ra dưới dạng dấu chấm thập phân di động có cả thảy 17 ký số, trong đó có 10 ký số trong phần định trị, như sau: Ở đây ta dùng ký hiệu  để chỉ một ký tự trắng, còn X đại diện một ký số . 103
  48. Ví dụ: cho x, y là hai biến thực và gán x:=100/4; y:=-9/300; thì hai lệnh sau: Writeln(‘x= ‘, x); Writeln(‘y= ‘, y); sẽ in lên màn hình: x = 2.5000000000E+01 y = -3.0000000000E-02 7.2.2. In có định dạng 7.2.2.1. In số thực có định dạng In các số thực theo cách trên rất khó đọc. Vì thế các số thực thường được in có định dạng, giống như cách viết số thông thường, bằng lệnh: Writeln(biểuthức: n: k); Ở đây n và k là các số tự nhiên, ấn định dùng n cột để in giá trị của biểu thức, trong đó có k cột dành cho phần thập phân. Nếu số cần in có ít hơn n chữ số thì nó sẽ được in dồn về bên phải và thêm các ký tự trắng ở bên trái cho đủ n cột. Ví dụ: cho x, y là các biến kiểu thực và: x:= 100/4; y:= - 123.4824; Hai lệnh sau: Writeln(‘x=‘, x:6:2); Writeln(‘y=‘, y:10:3); sẽ in lên màn hình: x= 25.00 (trước số 2 có 1 ký tự trắng) y= -123.482 (trước dấu - có 2 ký tự trắng) 104
  49. Nếu n nhỏ hơn chiều dài của số cần in thì số sẽ được in ra với đầy đủ các chữ số trong phần nguyên. Ví dụ: khi thực hiện các lệnh sau: x:=12345.675; Writeln(‘x=‘,x:0:2); trên màn hình sẽ hiện: x=12345.68 Ở đây máy đã làm tròn số khi bỏ số lẻ cuối cùng. 7.2.2.2. In kiểu nguyên, ký tự, chuỗi và lôgic có định dạng Dùng lệnh: Writeln(biểuthức: n); trong đó n là số nguyên ấn định số cột dùng để in giá trị của biểu thức. Nếu n lớn hơn độ dài của giá trị cần in thì giá trị sẽ được in dồn về bên phải, và thêm các khoảng trắng ở bên trái cho đủ n cột. Nếu n nhỏ hơn độ dài của giá trị cần in thì giá trị sẽ được in nguyên văn. Ví dụ: Lệnh Write(5+40:4); in ra: 45 (có 2 ký tự trắng trước số 45) Lệnh Write(5+40:1); in ra: 45 (in nguyên văn giá trị 45) Lệnh Write(‘Pascal’:9); in ra: Pascal (có 3 ký tự trắng trước chữ Pascal) Lệnh Write(‘Pascal’:2); in ra: Pascal (in nguyên văn) Lệnh Write(‘*’:3); in ra: * (có 2 ký tự trắng trước dấu *) 105
  50. Các chú ý - Nhóm ba lệnh: Write(x); Write(y); Write(j); chỉ có tác dụng như một lệnh: Write(x, y, j); - Nhóm ba lệnh: Write(x); Write(y); Writeln(j); chỉ có tác dụng như một lệnh: Writeln(x, y, j); - Lệnh: Writeln; không in gì cả, chỉ đơn giản là đưa con trỏ xuống dòng dưới. + Ví dụ: Dưới đây là chương trình cho phép nhập họ tên, mã số của một sinh viên, rồi in họ tên, mã số của sinh viên đó trong một cái khung được vẽ bằng các dấu sao *, như hình dưới chẳng hạn: * Nguyen Van Tuan * * Ma so: 1972508 * Chương trình cụ thể như sau: PROGRAM VIDU7_1; Uses CRT; Var Ten: String[18]; Maso: String[11]; Begin CLRSCR; Write(‘Nhap ho va ten: ‘); Readln(Ten); Write(‘Nhap ma so sv: ‘); Readln(Maso); 106
  51. Writeln; Writeln(‘ ’); {in 22 dấu *} Writeln(‘*’, Ten:19, ‘*’:2); {in 1 dấu *, in Ten chiếm 19 cột, in tiếp dấu * chiếm 2 cột} Writeln(‘* Ma so:’, Maso:12, ‘*’:2); {in * Ma so, in Maso chiếm 12 cột, in tiếp dấu * chiếm 2 cột} Writeln(‘ ’); {in 22 dấu *} Readln; End. 8. CÂU LỆNH RẼ NHÁNH 8.1. Câu lệnh IF 8.1.1. Câu lệnh IF dạng 1 * Cú pháp: IF Ðiềukiện THEN LệnhP; Ðiềukiện là một biểu thức lôgic cho kết quả TRUE (đúng) hay FALSE (sai). LệnhP có thể là một lệnh đơn giản hoặc một lệnh có cấu trúc. Nếu LệnhP là một lệnh ghép, tức là gồm nhiều lệnh, thì nhớ là các lệnh này phải được đặt trong khối: begin và end . 107
  52. * Ý nghĩa: Tùy theo Ðiềukiện là đúng hay sai mà quyết định có làm LệnhP hay không. Nếu Ðiềukiện là đúng thì làm LệnhP rồi chuyển sang lệnh kế tiếp ở phía dưới. Nếu Ðiềukiện là sai thì không làm LệnhP mà chuyển ngay sang lệnh kế tiếp. Sơ đồ?cú pháp của lệnh IF được vẽ trong hình 8.1. + Ví dụ 8_1: Nhập vào hai số a và b, tìm và in lên màn hình số lớn nhất của hai số đó. Ta dùng một biến phụ đặt tên là Max để chứa giá trị lớn nhất phải tìm. Thuật toán gồm ba bước: Bước 1: Gán số thứ nhất vào Max, tức là: Max:=a; Bước 2: Kiểm tra nếu Max nhỏ hơn số thứ hai thì gán số thứ hai vào Max: If Max < b then Max:=b; Bước 3: In giá trị Max lên màn hình. Giải thích: Sau bước 1, biến Max có giá trị bằng a. Sang bước 2, có thể xảy ra hai tình huống: 108
  53. - Hoặc là Max = b, tức giá trị của Max là lớn nhất rồi nên không phải làm gì nữa. Chương trình cụ thể như sau: PROGRAM VIDU8_1; {Tim Max của hai so} Var a, b, max: Real; Begin Write(‘Nhap a va b:’); Readln(a,b); Max:=a; If Max b then Min:=b; * Có thể mở rộng thuật toán trên để tìm số lớn nhất trong ba số hoặc nhiều hơn. Ðầu tiên ta tìm số lớn nhất của hai số a và b, ký hiệu là Max, sau đó tìm số lớn nhất của hai số Max và c, 109
  54. cũng vẫn ký hiệu là Max. Dưới đây là các lệnh chính để tìm số lớn nhất trong ba số a, b, c: Max:=a; If Max< then Max:=b; {Max là số lớn nhất của a và b} If Max<c then Max:=c; {Max là số lớn nhất của a, b và c} + Ví dụ 8_2: Nhập vào họ tên và điểm trung bình (DTB) của một sinh viên. Hãy phân loại sinh viên theo DTB như sau: Loại: Kém nếu DTB<5, Tbình nếu 5 DTB<7, Khá nếu 7 DTB<9, Giỏi nếu DTB 9. In họ tên, điểm trung bình và phân loại sinh viên. Trong chương trình, ta dùng một biến phụ đặt tên là Loai để lưu trữ phân loại của sinh viên. Vì có năm loại cần lưu trữ là các chuỗi ‘Kem’, ‘Tbinh’, ‘Kha’, ‘Gioi’, nên biến Loai phải có kiểu dữ liệu là kiểu chuỗi. PROGRAM VIDU8_2; {Phân loại sinh viên} Var Ho_ten: String[18]; DTB: Real; Loai: String[6]; Begin Write(‘Nhap ho va ten: ’); Readln(Ho_ten); 110
  55. Write(‘Nhap điem trung binh: ’); Readln(DTB); {phân loại theo DTB} If DTB = 5) and (DTB = 7) and (DTB = 9 then Loai:=‘Gioi’; Writeln(Ho_ten, #32 , DTB:4:1 , #32 , Loai); {#32 là ký tự trắng} Readln; End. 8.1.2. Câu lệnh IF dạng 2 * Cú pháp: IF Ðiềukiện THEN LệnhP ELSE LệnhQ; * Chú ý: - Trước từ khóa ELSE không có dấu chấm phẩy. - LệnhP và LệnhQ có thể là một lệnh ghép, tức là gồm nhiều lệnh được đặt trong khối begin và end. * Ý nghĩa của lệnh: - Tùy theo Ðiềukiện là đúng hay sai mà quyết định làm một trong hai lệnh: LệnhP hoặc LệnhQ . - Nếu Ðiềukiện là đúng thì làm LệnhP, không làm LệnhQ, mà chuyển ngay sang thực hiện lệnh kế tiếp ở sau LệnhQ. 111
  56. - Ngược lại, nếu Ðiềukiện là sai thì không làm LệnhP mà làm LệnhQ rồi chuyển sang lệnh kế tiếp ở sau LệnhQ. + Ví dụ 8_3: Ðể tìm số lớn nhất của hai số a và b, dùng lệnh: If a<b then Max:=b else Max:=a; Chương trình dưới đây sẽ nhập vào hai số a và b, tìm và in số nhỏ nhất và số lớn nhất của chúng: PROGRAM VIDU8_3; {Tim so lon nhat va so nho nhat của hai so} Var a, b, Max, Min: Real; Begin Write(‘Nhap a va b:’); Readln(a,b); 112
  57. If a < b then Begin Max:= b; Min:= a; End else {trước else không có dấu;} Begin Max:= a; Min:= b; End; Writeln(‘So lon nhat la: ‘, Max:6:2); Writeln(‘So nho nhat la: ‘, Min:6:2); Readln; End. 8.1.3. Câu lệnh IF lồng nhau Trong câu lệnh IF, nếu LệnhP hoặc LệnhQ, hoặc cả hai, lại là câu lệnh IF thì ta có cấu trúc IF lồng nhau. Chẳng hạn dưới đây là hai câu lệnh IF ELSE lồng nhau: IF Ðiềukiện1 THEN If Ðiềukiện2 then LệnhP else LệnhQ ELSE LệnhR; 113
  58. + Ví dụ 8_4: Nhập vào họ tên một chủ hộ, chỉ số điện kế tháng trước (chiso1) và chỉ số điện kế tháng này (chiso2), tính tiền điện tháng này cho hộ, biết rằng: Mỗi kW trong 60 kW đầu tiên có đơn giá là 5đ, Từ kW thứ 61 đến kW thứ 160 có đơn giá 8đ, Từ kW thứ 161 trở lên có đơn giá 10đ. Ví dụ, ông A có chỉ số điện tháng trước là chiso1=1020 và chỉ số điện tháng này là chiso2=1070, lượng điện tiêu thụ tính ra là Ldtt= 1070-1020=50, do lượng điện tiêu thụ < 60 nên số tiền sẽ là: Tien = 50*5= 250đ. Nếu chiso2=1150 thì Ldtt = 1150-1020=130, do lượng điện tiêu thụ vượt quá 60 kw nhưng chưa vượt quá 160 kw nên tiền điện được tính là: Tien=60*5 + (130-60) *8 = 860đ. Nếu chiso2=1234, thì Ldtt = 1234-1020= 214, do lượng điện tiêu thụ vượt quá 160 kw nên tiền điện sẽ là: Tien=60*5+100*8+(214-160)*10=300+800+54*10= 1640 đ. Chương trình được viết như sau: PROGRAM VIDU8_4; {Tính tiền điện} Var Ho_ten: String[18]; chiso1, chiso2, Ldtt, Tien: Real; Begin Write(‘Nhap ho va ten: ’); 114
  59. Readln(Ho_ten); Write(‘Nhap chỉ số tháng trước, chỉ số tháng này: ‘); Readln(chiso1, chiso2); Ldtt:=chiso2- chiso1; If Ldtt<= 60 then Tien:=Ldtt*5 else if Ldtt <=160 then Tien:=60*5+(Ldtt - 60)*8 else Tien:=60*5 + 100*8 + (Ldtt - 160)*10; Writeln(‘Họ và tên là ‘, Ho_ten); Writeln(‘Tiền phải trả là ‘, Tien:10:2); Readln; End. 8.2. Câu lệnh CASE Trong một số trường hợp, khi phải lựa chọn một việc trong nhiều việc thì các cấu trúc IF lồng nhau tỏ ra rắc rối, khó viết, khó kiểm tra tính đúng đắn của nó. Việc dùng cấu trúc CASE có thể khắc phục được nhược điểm này. Lệnh CASE có hai dạng, chúng chỉ khác nhau ở một điểm là trong dạng 2 có ELSE LệnhQ, còn trong dạng 1 thì không (hình 8.3). 115
  60. CASE biểuthức OF CASE biểuthức OF hằng1: LệnhP1; hằng1: LệnhP1; hằng2: LệnhP2; hằng2: LệnhP2; hằngk: LệnhPk; hằngk: LệnhPk; END; ELSE LệnhQ; END; Dạng 1 Dạng 2 Chú ý là lệnh CASE phải kết thúc bằng END; * Các yêu cầu - Kiểu dữ liệu của biểuthức chỉ có thể là nguyên, ký tự, lôgic, hoặc kiểu liệt kê hay kiểu đoạn con. Xin nhấn mạnh rằng: biểuthức không được là kiểu thực hay kiểu chuỗi, và đây chính là hạn chế của lệnh CASE so với lệnh IF. - Các hằng1, hằng2, , hằngk phải có kiểu dữ liệu phù hợp với kiểu dữ liệu của biểuthức. * Ý nghĩa: Tùy theo giá trị của biểuthức bằng hằng nào trong các hằng1, hằng2, , hằngk mà quyết định thực hiện lệnh nào trong các lệnhP1, lệnhP2, , LệnhPk. * Cách thức thực hiện của lệnh CASE như sau - Bước 1: Tính toán giá trị của biểuthức - Bước 2: So sánh và lựa chọn: + Nếu giá trị của biểuthức = hằng1 thì thực hiện LệnhP1, rồi chuyển sang lệnh kế tiếp sau End, ngược lại: + Nếu giá trị của biểuthức = hằng2 thì thực hiện LệnhP2, rồi chuyển sang lệnh kế tiếp sau End, ngược lại: 116
  61. + .v.v. + Nếu giá trị của biểuthức = hằngk thì thực hiện LệnhPk, rồi chuyển sang lệnh kế tiếp sau End, ngược lại: a) chuyển ngay sang lệnh kế tiếp sau End (nếu là dạng 1) b) thực hiện LệnhQ, rồi chuyển sang lệnh kế tiếp sau End (nếu là dạng 2) . Hình 8.4 và hình 8.5 là các sơ đồ của lệnh CASE vẽ cho trường hợp k=3. Trong hình vẽ , ta ký hiệu: G là giá trị của biểuthức H1, H2, H3 là hằng1, hằng2, hằng3 P1, P2, P3, Q là LệnhP1, LệnhP2 , LệnhP3 và LệnhQ. 117
  62. + Ví dụ 8_5: Nhập vào họ tên và năm sinh của một người, cho biết người này thuộc lứa tuổi nào: sơ sinh, nhi đồng, thiếu niên, thanh niên, trung niên hay người lớn tuổi, biết rằng: Sơ sinh có tuổi từ 0 đến 1 Nhi đồng: có tuổi từ 2 đến 9 Thiếu niên có tuổi từ 10 đến 15 Thanh niên có tuổi từ 16 đến 32 Trung niên có tuổi từ 33 đến 50 Người lớn tuổi có tuổi trên 50. Chương trình được viết như sau: PROGRAM VIDU8_5; Var Ho_ten: String[20]; 118
  63. Namsinh, Namnay, Tuoi: Integer; Phanloai: String[14]; Begin Write(‘Nhập họ và tên: ‘); Readln(Ho_ten); Write(‘Nhập năm sinh và năm nay: ‘); Readln(Namsinh, Namnay); Tuoi:=Namnay - Namsinh; If Tuoi< 0 then writeln( ‘Nhập sai’) else Begin Case Tuoi OF 0 ,1: Phanloai:= ‘sơ sinh’; 2 9: Phanloai:= ‘nhi đong’; 10 15: Phanloai:= ‘thieu niên’; 16 32: Phanloai:= ‘thanh nien’; 33 50: Phanloai:= ‘trung nien’; else Phanloai:= ‘nguoi lon tuoi’; End; {hết Case} Writeln(Ho_ten, #32 , Tuoi, #32 , Phanloai); End; Readln; End. 119
  64. Trong ví dụ này, lệnh CASE dựa vào Tuổi để xác định lứa tuổi, kết quả lưu vào biến Phanloai. Ðóng vai trò hằng1 là hai số 0 và 1 viết cách nhau bởi dấu phẩy, và dòng: 0,1: Phanloai:= ‘So sinh’; có nghĩa là khi Tuổi bằng 0 hoặc bằng 1 thì thực hiện lệnh gán: Phanloai:= ‘So sinh’; Ðóng vai trò hằng2 là tất cả các số nguyên trong phạm vi từ 2 đến 9, và dòng: 2 9: Phanloai:= ‘nhi đong’; có nghĩa là khi Tuổi bằng một trong các số nguyên từ 2 đến 9 thì thực hiện lệnh gán: Phanloai:= ‘nhi đong’; + Ví dụ 8_6: Xây dựng thực đơn cho phép lựa chọn một trong bốn việc: tính tổng, tính hiệu, tính tích hoặc tính thương của hai số x, y nhập từ bàn phím. Màn hình cần hiện ra bốn mục sau cho mọi người lựa chọn: A. TÍNH TỔNG HAI SỐ B. TÍNH HIỆU HAI SỐ C. TÍNH TÍCH HAI SỐ D. TÍNH THƯƠNG HAI SỐ Muốn chọn mục nào ta gõ chữ cái đầu của mục đó. Ví dụ gõ A thì màn hình hiện kết quả của x+y, gõ B thì hiện kết quả của x-y, Ðối với mục D, nếu y khác không thì in kết quả của x/y, còn y=0 thì in câu "Không xác định". Nếu người dùng nhập một ký tự khác A, B, C, D, a, b, c, d thì máy hiện lời nhắc: "Không có mục này". Biến Ch kiểu ký tự được dùng để lưu chữ cái (mục) mà người dùng đã chọn. Tùy theo giá trị của Ch mà lệnh CASE sẽ quyết định phải làm gì. 120
  65. Chương trình được viết như sau: PROGRAM VIDU8_6; {Thực đơn} Uses Crt; Var x, y: Real; Ch: Char; Begin Clrscr; Write('Nhap x va y:'); Readln(x, y); Gotoxy(10, 3); Write('A. TINH TONG HAI SO'); Gotoxy(10, 5); Write('B. TINH HIEU HAI SO'); Gotoxy(10, 7); Write('C. TINH TICH HAI SO'); Gotoxy(10, 9); Write('D. TINH THUONG HAI SO'); Gotoxy(2,11); Write('Chon muc nao (A, B, C, D)?'); Readln(Ch); CASE Ch of 'A', 'a': Writeln('Tong =', x+y:6:2); 'B', 'b': Writeln('Hieu =', x-y:6:2); 'C', 'c': Writeln('Tich =', x*y:6:2); 'D', 'd': If y<>0 then Writeln('Thuong =', x/y:6:2) else 121
  66. Writeln(‘Khong xac dinh !'); ELSE Writeln('Khong co muc', Ch); END; Readln; End. Trong chương trình có sử dụng thủ tục: GOTOXY (m, n) thuộc thư viện CRT, có chức năng đặt con trỏ màn hình vào tọa độ cột thứ m, dòng thứ n trên màn hình. Ví dụ lệnh Gotoxy (10, 3); đặt con trỏ màn hình vào tọa độ cột 10, dòng 3. + Ví dụ 8_7: Nhập vào tháng và năm, cho biết tháng đó trong năm đó có bao nhiêu ngày. Theo dương lịch: Các tháng 4, 6, 9, và 11: có 30 ngày, Các tháng 1, 3, 5, 7, 8, 10 và 12: có 31 ngày, Riêng tháng 2 thì bình thường có 28 ngày, nhưng nếu là năm nhuận thì có 29 ngày. Cách xác định một năm là nhuận như sau: hoặc là năm chia hết cho 400 (ví dụ năm 1600, năm 2000). hoặc là năm không chia hết cho 100 và chia hết cho 4 (ví dụ các năm 1988, 1992, 1996 đều là năm nhuận). Trong chương trình ta dùng một biến lôgic có tên là Nhuan để xác định có phải là năm nhuận hay không. PROGRAM VIDU8_7; {Xác định số ngày của tháng} 122
  67. Var Thang, Nam, Songay: Integer; Nhuan: Boolean; Begin Write(‘Nhập Thang, Nam: ‘); Readln(Thang, Nam); If (Thang 12) then writeln(‘Nhập sai ’) else Begin Case Thang OF 4, 6, 9, 11: Songay:=30; 1, 3, 5, 7, 8, 10, 12: Songay:=31; 2: Begin Nhuan:= (Nam mod 400=0) or ((Nam mod 100<>0) and (Nam mod 4=0)); If Nhuan= TRUE then Songay:=29 else Songay:=28; End; End; {Hết Case} Writeln(‘So ngay la: ‘, Songay); End; 123
  68. Readln; End. Trong ví dụ này, khi Tháng=2 thì phải làm hai lệnh được đặt trong khối begin và end, đó chính là một lệnh ghép: Begin Nhuan:= (Nam mod 400=0) or ((Nam mod 100<>0) and (Nam mod 4=0)); If Nhuan=TRUE then Songay:=29 else Songay:=28; End; * Câu lệnh CASE lồng nhau: Trong cấu trúc CASE, khi một trong các LệnhP1, LệnhP2, , LệnhPk hay LệnhQ lại là một lệnh CASE thì ta có cấu trúc CASE lồng nhau. + Ví dụ 8_8:Một xí nghiệp tính tiền thưởng hàng tháng cho công nhân theo công thức: Tiền thưởng = Hệ số * 200. Trong đó Hệ số được tính dựa vào kết quả bình chọn phân loại lao động (loại A, B hay C) và nơi làm việc (cơ sở 1 hay cơ sở 2) của mỗi người trong tháng, cụ thể như sau: Loại Cơ sở 1 Cơ sở 2 A 2.0 2.5 B 1.5 1.8 C 1.0 1.0 124
  69. Ví dụ nếu ông X được xếp loại A và làm việc ở cơ sở 1 thì có hệ số thưởng là 2.0, nên tiền thưởng là =2.0*200=400. Viết chương trình nhập họ tên, phân loại lao động và nơi làm việc của một công nhân, tính tiền thưởng cho người đó. PROGRAM VIDU8_8; {Tính tiền thưởng cho công nhân} Var Ho_ten: String[20]; Loai: Char; Coso: Byte; Heso, Thuong: Real; Begin Write(‘Nhập họ và tên: ‘); Readln(Ho_ten); Write(‘Nhập cơ sở làm việc (1,2): ‘); Readln(Coso); Write(‘Nhập phân loại lao động (A,B,C): ‘); Readln(Loai); CASE Loai OF ‘A’, ‘a’: Case Coso of 1: Heso:=2.0; 2: Heso:=2.5; End; ‘B’, ‘b’: Case Coso of 1: Heso:=1.5; 125
  70. 2: Heso:=1.8; End; ‘C’, ‘c’: Heso:=1.0; END; {Hết CASE} Thuong:=Heso*200; Writeln(‘Họ và tên Tiền thưởng ‘); Writeln(Ho_ten , Thuong:8:2 ); Readln; End. So sánh lệnh Case với lệnh If + Lệnh If và lệnh Case đều là các câu lệnh rẽ nhánh, cho phép lựa chọn một công việc trong nhiều công việc được lựa chọn. Nhưng cấu trúc If tổng quát và mạnh hơn cấu trúc Case vì lệnh If không hạn chế gì cả, còn lệnh Case thì yêu cầu biểu thức và các hằng phải thuộc kiểu dữ liệu đếm được: nguyên, ký tự, lôgic, liệt kê hay đoạn con, không được là kiểu thực hay chuỗi. + Lệnh Case nào cũng có thể thay thế tương đương bằng các lệnh If. Ví dụ lệnh Case trong chương trình nói trên (Ví dụ 8.9) có thể thay bằng ba lệnh If sau: If (Loai=‘A’) or (Loai=‘a’) then if Coso=1 then Heso:=2.0 else Heso:=2.5; If (Loai=‘B’) or (Loai=‘b’) then if Coso=1 then Heso:=1.5 else Heso:=1.8; If (Loai=‘C’) or (Loai=‘c’) then Heso:=1.0; Tuy nhiên không phải lệnh If nào cũng thay bằng lệnh Case được. 126
  71. + Việc sử dụng lệnh Case trong nhiều trường hợp có tác dụng làm rõ ràng và nổi bật bố cục của một đoạn chương trình, từ đó dễ đọc, dễ hiểu hơn. Thông thường, người ta dùng lệnh Case để thay thế các cấu trúc If lồng nhau khi có nhiều (ba, bốn, ) tình huống rẽ nhánh và khi điều kiện cho phép. Các bạn hãy viết lại các chương trình trong các ví dụ 8_5, 8_6, 8_7, 8_8 nhưng thay lệnh Case bằng các lệnh If. 9. CÂU LỆNH LẶP FOR 9.1. Dạng 1 Cú pháp: FOR biến:= m1 TO m2 DO LệnhP; + Yêu cầu biến phải thuộc kiểu dữ liệu đơn giản đếm được, thường là kiểu nguyên, ký tự hay lôgic, không thể là kiểu thực hay chuỗi. m1, m2 là các biểu thức có cùng kiểu dữ liệu với biến. LệnhP có thể là một lệnh đơn giản, lệnh có cấu trúc, hoặc là một lệnh ghép gồm nhiều lệnh đặt trong khối begin và end. Hình 9.1 là sơ đồ khối của lệnh For với b là viết tắt của biến. + Cách thức hoạt động của FOR Bước 1: Gán giá trị biến:= m1; Bước 2: Nếu biến m2 thì làm LệnhP, rồi sang bước 3; Nếu biến > m2 thì không làm LệnhP mà chuyển sang lệnh kế tiếp ở phía dưới. Bước 3: Tăng giá trị của biến: biến:=Succ(biến); 127
  72. Quay lại bước 2. Tóm lại, LệnhP sẽ được làm đi làm lại, bắt đầu khi biến=m1, và kết thúc khi biến=m2+1, cả thảy là m2-m1+1 lần. Vì thế, người ta gọi FOR là vòng lặp có số lần lặp đã biết trước. * Các ví dụ cơ bản: + Ví dụ 9_1: Bài toán tính tổng: Hãy tính tổng: S= 12 + 22+ 32+ + 102 Thuật toán: Bước 0: gán S:=0; {gán giá trị ban đầu cho S} Bước 1: gán S:=S+1*1; {được S=12} Bước 2: gán S:=S+2*2; {được S=12+22} 128
  73. Bước 3: gán S:=S+3*3; {S=12+22+32} .v.v. Bước 10: gán S:=S+10*10; {được S=12+22+32+ +102} Quá trình từ bước 1 đến bước 10 được gọi là phép cộng dồn vào biến S. Tại bước thứ i, lấy giá trị của biến S cộng với i2, kết quả lại được gán cho biến S, do đó giá trị của biến S được tăng thêm một lượng bằng i2. Khi i thay đổi từ 1 đến 10 thì các số 12, 22, 32, , 102 đều được cộng vào S, kết quả là sau bước thứ 10 giá trị của S đúng bằng tổng 12 + 22 + 32 + + 102. Tóm lại, lệnh: S:=S + i*i; được làm cả thảy 10 lần, ứng với i=1, 2, , 10. Quá trình này được diễn đạt bằng lệnh FOR, như sau: For i:=1 To 10 DO S:=S+ i*i; Một cách tổng quát, để tính tổng: S=12 + 22+ 32+ + N2, trong đó N là một số nguyên dương bất kỳ, ta dùng hai lệnh: S:=0; For i:=1 To N DO S:=S+ i*i; Dưới đây là chương trình cụ thể: PROGRAM VIDU9_1; {Tính tổng bình phương các số tự nhiên <=N} Var N, i: Integer; S: LongInt; Begin Write(‘Nhập N:’); Readln(N); S:=0; For i:=1 to N do S:=S+i*i; 129
  74. Writeln(‘S= ‘, S); Readln; End. Mở rộng bài toán tính tổng: Tính tổng đan dấu: S = 12 - 22 + 32 - 42 + +(-1)N-1 N2 Ta viết: S = 12 + (- 22 ) + 32 + (- 42 ) + +(-1)N-1 N2 Nhận thấy, số hạng thứ i của vế phải có giá trị tuyệt đối bằng i2, mang dấu cộng nếu i lẻ, mang dấu trừ nếu i chẵn. Nói cách khác, ta sẽ cộng dồn i2 vào S nếu i lẻ, và cộng dồn (- i2 ) vào S nếu i chẵn. Việc xác định i lẻ hay chẵn dựa vào hàm Odd(i) hay kết quả của phép toán i Mod 2. Vậy, các lệnh sẽ dùng là: S:=0; For i:=1 To N DO if i mod 2 <> 0 then S:=S+ i*i else S:= S- i*i; Các bạn hãy viết chương trình để tính tổng đan dấu này. + Ví dụ 9_2: Bài toán tính tích: Tính S= 10! . Ta viết S=1*2*3* *10. Thuật toán: Bước 0: gán S:=1; {gán giá trị ban đầu cho S} Bước 1: gán S:=S*1; {được S=1} Bước 2: gán S:=S*2; {được S=1*2} Bước 3: gán S:=S*3; {được S=1*2*3} .v.v. 130
  75. Bước 10: gán S:=S*10; {được S=1*2*3* *10 =10!} Nếu trong ví dụ 1, ta phải cộng dồn vào biến S thì trong ví dụ này ta phải nhân dồn vào biến S. Tại bước thứ i, lấy giá trị của biến S nhân với i, rồi lại gán kết quả cho biến S. Khi i thay đổi từ 1 đến 10 thì S sẽ tích lũy đủ các thừa số 1, 2, 3, ,10, và giá trị của S sau bước thứ 10 đúng bằng 1*2*3* *10 =10!. Quá trình thực hiện từ bước 1 đến bước thứ 10 được mô tả bằng câu lệnh For: For i:=1 to 10 DO S:=S*i; Một cách tổng quát, để tính tích: S= 1*2* *N , trong đó N là một số nguyên dương bất kỳ, ta dùng hai lệnh: S:=1; For i:=1 To N DO S:=S* i; Dưới đây là chương trình cụ thể: PROGRAM VIDU9_2; {Tính S=N!} Var N, i: Integer; S: LongInt; Begin Write(‘Nhập số dương N: ‘); Readln(N); S:=1; For i:=1 to N do S:=S * i; Writeln(‘Giai thua = ‘, S); Readln; End. 131
  76. + Ví dụ 9_3: Bài toán tính lũy thừa: Nhập số tự nhiên N và một số thực x bất kỳ, tính S= xN . Tương tự như tính N!: đầu tiên ta gán S:=1, sau đó tại mỗi bước lặp, ta nhân dồn x vào S bằng lệnh S:=S*x. Sau N bước như vậy, S sẽ được nhân với x đúng N lần. Vậy hai lệnh cần dùng là: S:=1; For i:=1 to N do S:=S*x; Dưới đây là chương trình cụ thể: PROGRAM VIDU9_3; {Tính S=lũy thừa N của x} Var N, i: Byte; S, x: Real; Begin Write(‘Nhập hai số x và N: ‘); Readln( x, N); S:=1; For i:= 1 to N do S:= S * x; Writeln(‘Luy thua= ‘, S: 6:2); Readln; End. 132
  77. + Ví dụ 9_4: In bảng các chữ cái từ A đến Z thành bốn cột như sau: KÝ TỰ MÃ KÝ TỰ MÃ A 65 a 97 B 66 b 98 Yêu cầu in thành từng trang màn hình, mỗi trang 15 dòng. Trong chương trình ta dùng biến Dem để đếm số dòng đã in, mỗi khi in xong một dòng thì biến Dem được cộng thêm 1. Khi Dem = 15, 30, 45, (tức Dem mod 15=0) thì phải làm lệnh Readln; lệnh này sẽ dừng màn hình cho đến khi ta gõ Enter mới in tiếp. PROGRAM VIDU9_4; {In bảng các chữ cái} Uses Crt; Var ch, ch1: Char; Dem: Integer; Begin Clrscr; Writeln(‘Ky tu # Ma # Ky tu # Ma’); Dem:=0; For ch:=‘a’to ‘z’do Begin ch1:=Upcase(ch); Writeln(ch1:3, Ord(ch1):6, ch:6, Ord(ch):6); Inc(Dem); 133
  78. If Dem mod 15 = 0 then Begin Write(‘Enter để xem tiếp‘); Readln; End; End; Writeln(‘HET‘); Readln; End. Chương trình trên là một ví dụ về cách dùng biến chạy kiểu ký tự (ch) trong lệnh FOR, ngoài ra, đóng vai trò LệnhP là một lệnh ghép, gồm nhiều lệnh đặt trong khối begin và end. 9.2. Dạng 2 + Cú pháp FOR biến:= m2 DOWNTO m1 DO LệnhP; + Cách thức hoạt động của FOR dạng 2 Bước 1: Gán giá trị biến:= m2; Bước 2: Nếu biến  m1 thì làm LệnhP, rồi sang bước 3. Nếu biến < m1 thì không làm LệnhP mà chuyển sang lệnh kế tiếp ở phía dưới. Bước 3: Giảm giá trị của biến: biến:=Pred(biến); Quay lại bước 2. Tóm lại, LệnhP sẽ được làm đi làm lại, bắt đầu khi biến=m2, và kết thúc khi biến= m1-1, cả thảy là m2-m1+1 lần. 134
  79. + Ví dụ 9_5:Ðể tính S= N!, ta có thể viết: S=N*(N-1)*(N-2)* *2*1 Cách viết cho thấy ngay cách tính: đầu tiên gán S:=1, sau đó thực hiện việc nhân dồn S:=S* i với i= N, N-1, , 2, 1, tức là: S:=1; For i:=N downto 1 do S:=S* i; Tương tự, để tính S=xN, ta cũng có thể dùng FOR dạng 2: S:=1; For i:=N downto 1 do S:=S*x; Như vậy, lệnh FOR dạng 2 về bản chất chỉ là một cách viết khác của dạng 1. Thông thường người ta hay dùng lệnh FOR dạng 1, tuy nhiên có khá nhiều tình huống mà việc dùng lệnh FOR dạng 2 tỏ ra rất hiệu quả, như ví dụ sau đây: 135
  80. + Ví dụ 9_6: In các chữ cái theo thứ tự ngược từ Z đến A thành hai dòng: Z, Y, X, , C, B, A z, y, x, , c, b, a Chương trình được viết như sau: PROGRAM VIDU9_6; {In các chữ cái theo thứ tự đảo ngược từ Z đến A} Var Ch: Char; Begin For ch:=‘Z’downto ‘A’do write(ch:3); Writeln; For ch:=‘z’downto ‘a’do write(ch:3 ); Writeln; Readln; End. 9.3. Câu lệnh FOR lồng nhau Trong cấu trúc FOR, khi LệnhP cũng là một lệnh FOR thì ta có cấu trúc FOR lồng nhau: FOR biến1:= m1 TO m2 DO {1} FOR biến2:=n1 TO n2 DO LệnhP; {2} Cách thức hoạt động của lệnh này như sau: Ðầu tiên cho biến1:=m1 và làm lệnh ở dòng {2}. Vì dòng {2} là lệnh FOR nên với mỗi giá trị của biến2=n1, , n2, đều phải làm LệnhP, kết quả là LệnhP được làm n2-n1+1 lần. 136
  81. Bây giờ tăng: biến1:=Succ(biến1), rồi lại làm lệnh FOR ở dòng {2}, kết quả lệnhP được làm thêm n2-n1+1 lần nữa. Quá trình trên cứ tiếp tục cho đến khi biến1=m2+1 thì dừng. Lệnh FOR {1} làm m2-m1+1 lần lệnh FOR {2}, còn chính lệnh FOR {2} lại làm n2-n1+1 lần LệnhP. Vì thế lệnhP được làm cả thảy là (m2-m1+1)*(n2-n1+1) lần. + Ví dụ 9_7: In hình chữ nhật đặc như dưới đây: Ta thấy mỗi dòng gồm m chữ A, tức là chữ A được in liên tiếp cả thảy m lần, việc này được làm bằng lệnh: For j:=1 to m do write(‘A’); Lệnh Write in m chữ A trên một dòng. In xong, con trỏ vẫn nằm ở cuối dòng đó, vì thế trước khi in dòng tiếp theo, cần phải đưa con trỏ xuống dòng dưới bằng lệnh: Writeln; Tóm lại, muốn in dòng thứ i, cần phải làm hai lệnh: For j:=1 to m do write(‘A’); Writeln; Cả thảy ta phải in n dòng như thế, tức là: For i:=1 to n do In dòng i; 137
  82. Thay In dòng i bằng hai lệnh nói trên (đặt trong khối begin end), ta có thuật toán để in hình chữ nhật đặc là: For i:=1 to n do Begin for j:=1 to m do write(‘A’); Writeln; End; Các bạn hãy viết chương trình cụ thể cho ví dụ này, ở đây m và n là hai số nguyên dương nhập từ bàn phím. 9.4. Các ứng dụng khác của lệnh FOR Lệnh For rất thông dụng, dễ dùng và giải quyết được nhiều bài toán trong khoa học kỹ thuật và trong thực tiễn. Dưới đây chỉ xin nêu hai ứng dụng . + Ví dụ 9_8: Tìm các số Fibonaci. Dãy số Fibonaci {1, 1, 2, 3, 5, 8, 13, 21, } được xây dựng như sau: U0=1, U1=1 , Uk=Uk-1 + Uk-2 với mọi k= 2, 3, 4, Gọi U là số hạng thứ k, U0 và U1 lần lượt là hai số hạng đứng ngay trước U. Ðầu tiên ta gán: U0:=1; U1:=1; Bước 1: tính U:=U0+U1 và in U. Lúc này U=2 chính là U2. Ðể chuẩn bị tính U3, ta cho U0 đóng vai trò của U1 và U1 đóng vai trò của U, tức là gán: U0:=U1; U1:=U; 138
  83. Kết quả là Uo=1 và U1=2. Bước 2: tính U:=U0+U1 và in U. Lúc này U=3 chính là U3. Ðể chuẩn bị tính U4, ta lại cho U0 đóng vai trò của U1 và U1 đóng vai trò của U, tức là gán: U0:=U1; U1:=U; Kết quả là U0=2 và U1=3. .v.v. Tóm lại các lệnh phải lặp đi lặp lại là: U:=U0+U1; U0:=U1; U1:=U; Vì sang bước sau thì giá trị của U sẽ bị thay đổi nên tại mỗi bước ta đều phải in U. Chương trình được viết như sau: PROGRAM VIDU9_8; {In N+1 số Fibonaci đầu tiên} Var N, i, U, Uo, U1: Integer; Begin Write(‘Nhập N:’); Readln(N); Uo:=1; U1:=1; Writeln(N+1 , ‘số Fibonaci đầu tiên là:’); Write(Uo:3, U1:3); 139
  84. For i:=2 to N do Begin U:=Uo+U1; Write(U:3); Uo:=U1; U1:=U; End; Readln; End. + Ví dụ 9_9: Bài toán tính tiền lãi gửi ngân hàng: Nhập tiền vốn ban đầu, số tháng gửi N và lãi suất hàng tháng. Tính số tiền nhận được sau mỗi tháng gửi biết rằng tiền lãi hàng tháng được gộp vào tiền vốn. Ví dụ, tiền vốn là100, lãi suất tháng là 2%. Sau 1 tháng gửi sẽ có số tiền là: Số tiền=100 + 100*0.02 = 102 Sau 2 tháng gửi sẽ có số tiền là: Số tiền=102 + 102*0.02 = 104.04 Công thức tính tiền thu được sau mỗi tháng gửi là: Số tiền:= Tiền vốn + Tiền vốn * Lãi suất Số tiền này lại trở thành tiền vốn của tháng sau, tức là: Tiền vốn:= Số tiền; Quá trình cứ lặp đi lặp lại từ tháng 1 đến tháng N. Chương trình cụ thể như sau: PROGRAM VIDU9_9; {Tính tiền gửi ngân hàng sau N tháng} 140
  85. Var Tienvon, Laisuat, Sotien: Real; N, i: Byte; Begin Write(‘Nhập tiền vốn, lãi suất và số tháng gửi: ‘); Readln(Tienvon, Laisuat, N); For i:=1 to N do Begin Sotien:= Tienvon + Tienvon*Laisuat; Writeln(‘Số tiền sau‘, i ,‘tháng=‘, Sotien:8:2); Tienvon:=Sotien; End; Readln; End. 10. CÂU LỆNH LẶP WHILE VÀ REPEAT 10.1. Câu lệnh While + Cú pháp: WHILE Ðiềukiện DO LệnhP; + Ý nghĩa: Chừng nào Ðiềukiện còn đúng thì cứ làm LệnhP, cho đến khi Ðiềukiện sai thì không làm LệnhP nữa mà chuyển sang lệnh kế tiếp ở phía dưới. + Cách thức hoạt động của WHILE: Bước 1: Nếu Ðiềukiện sai thì chuyển ngay sang lệnh kế tiếp sau LệnhP, ngược lại, nếu Ðiềukiện đúng thì làm LệnhP, rồi quay lại bước 1. 141
  86. Lệnh P được gọi là thân của vòng lặp WHILE. Nếu Ðiềukiện không bao giờ sai thì LệnhP sẽ phải làm hoài, lúc đó ta có vòng lặp vô hạn. Trong trường hợp này, để dừng chương trình, hãy gõ đồng thời hai phím Ctrl và Pause (viết tắt là ^Pause). Ðể tránh các vòng lặp vô hạn, trong thân của vòng WHILE cần có ít nhất một lệnh có tác dụng làm biến đổi các đại lượng tham gia trong Ðiềukiện để đến một lúc nào đó thì Ðiềukiện sẽ sai và do đó vòng lặp sẽ kết thúc. + Ví dụ 10_1: - Nhập số tự nhiên N, - Dùng lệnh WHILE tính S=N!: PROGRAM VIDU10_1; {Tinh S=N! bằng lệnh WHILE } Var N, i: Integer; S: LongInt; Begin 142
  87. Write(‘Nhập N > 0: ‘); Readln(N); S:=1; i:=1; {9} While i<= N do Begin S:=S*i; i:=i+1; {13} End; Writeln(‘Giai thua = ‘, S); Readln; End. Khởi đầu biến i được gán giá trị 1 (dòng {9}). Trong vòng lặp WHILE, sau mỗi lệnh S:=S*i; biến i được tăng lên 1 đơn vị bằng lệnh i:=i+1; (dòng {13}). Khi i=N+1 thì điều kiện i<=N bị sai và lúc đó vòng lặp kết thúc, kết quả là lệnh S:=S*i; được thực hiện đúng N lần ứng với i=1, 2, 3, , N. Trong chương trình trên, nếu không có dòng lệnh {13}: i:=i+1; thì i luôn luôn bằng 1 nên điều kiện i<=N luôn luôn đúng (vì N 1), và do đó vòng lặp sẽ vô hạn . Sự khác nhau của lệnh WHILE so với FOR là ở chỗ: trong lệnh FOR, biến i được tự động gán giá trị ban đầu và sau mỗi bước lặp được tự động tăng lên, còn trong WHILE thì không, ta phải viết các lệnh đó. Tất cả các bài toán giải quyết được bằng lệnh FOR thì đều giải quyết được bằng lệnh WHILE. Ðặc điểm chung của các bài 143
  88. toán dạng này là số lần lặp của các vòng lặp đã được biết trước. Lệnh WHILE đặc biệt thích hợp với các vòng lặp có số lần lặp chưa biết trước, trong khi lệnh FOR không giải quyết được. Ðây chính là điểm mạnh của lệnh WHILE. Hãy xem ví dụ sau. + Ví dụ 10_2: Trở lại bài toán tính tiền gửi ngân hàng có tiền lãi hàng tháng gộp vào vốn (ví dụ 9.9). Câu hỏi bây giờ là: cần gửi tối thiểu là bao nhiêu tháng để có được số tiền S cho trước. Giả sử tiền vốn là 100, lãi suất hàng tháng là 2%, số tiền cần có là S=108. Ta tính số tiền có được sau mỗi tháng gửi: Sau 1 tháng gửi: Số tiền=100+100*0.02 = 102 Sau 2 tháng gửi: Số tiền=102+102*0.02 = 104.04 Sau 3 tháng gửi: Số tiền=104.04+104.04*0.02= 106.1208 Sau 4 tháng gửi: Số tiền=106.1208+106.1208*0.02= 108.2432 Vậy chỉ cần gửi N=4 tháng, số tiền sẽ có là 108.2431. Quá trình lặp kết thúc khi tới tháng đầu tiên có Số tiền S. Chương trình như sau: PROGRAM VIDU10_2; {Tính số tháng gửi ngân hàng để có số tiền S} Var Tienvon, Laisuat, Sotien, S: Real; N: Byte; Begin Write(‘Nhập tiền vốn, lãi suất và số tiền S cần có: ‘); 144
  89. Readln(Tienvon, Laisuat, S); Sotien:=Tienvon; N:=0; {N là số tháng gửi} While Sotien< S do Begin N:=N+1; Sotien:= Tienvon + Tienvon*Laisuat; Tienvon:=Sotien; End; Writeln(‘Cần gửi‘, N , ‘tháng‘); Writeln(‘Số tiền sẽ có = ‘, Sotien:6:2); Readln; End. Số lần lặp của lệnh: While Sotien < S do . . . không phải do ta ấn định từ trước mà tùy thuộc vào biểu thức Sotien < S là mau bị sai hay chậm bị sai. Số lần lặp ít hay nhiều phụ thuộc vào giá trị S nhỏ hay lớn và vào tốc độ tăng nhanh hay chậm của số tiền. 10.2. Câu lệnh Repeat + Cú pháp: REPEAT LệnhP; UNTIL Ðiềukiện; + Ý nghĩa: Chừng nào Ðiềukiện còn sai thì cứ làm LệnhP, cho đến khi Ðiềukiện đúng thì không làm LệnhP nữa mà chuyển sang lệnh kế tiếp ở phía dưới. 145
  90. + Cách thức hoạt động của REPEAT: Bước 1: Làm LệnhP, rồi kiểm tra Ðiềukiện, nếu Ðiềukiện đúng thì chuyển sang lệnh tiếp theo ở phía dưới, ngược lại, nếu Ðiềukiện sai thì quay lại bước 1. LệnhP cũng được gọi là thân của vòng lặp REPEAT, nếu nó gồm nhiều lệnh thì các lệnh đó không cần phải đặt trong khối Begin và End. Nếu Ðiềukiện không bao giờ đúng thì LệnhP sẽ phải làm hoài, lúc đó ta có vòng lặp vô hạn. Trong trường hợp này, muốn dừng chương trình, hãy gõ đồng thời hai phím Ctrl và Pause (^Pause). Ðể tránh các vòng lặp vô hạn, trong thân của lệnh REPEAT cần có ít nhất một lệnh có tác dụng làm biến đổi các đại lượng tham gia trong Ðiềukiện để đến một lúc nào đó thì Ðiềukiện sẽ đúng và do đó vòng lặp sẽ kết thúc. Các vòng lặp có số lần lặp biết trước đều có thể giải được bằng lệnh REPEAT. Ðặc biệt, cũng như lệnh WHILE, lệnh REPEAT rất thích hợp với các vòng lặp có số lần lặp không biết trước 146
  91. + Ví dụ 10_3: Ðảm bảo tính hợp lý của dữ liệu nhập từ bàn phím. Khi giải phương trình bậc hai Ax2+Bx+C=0, ta thường giả thiết A≠0, khi tính S=N!, ta thường yêu cầu N≥0. Sự hạn chế phạm vi đối với các dữ liệu nhập sẽ đảm bảo tính hợp lý của chúng và làm giảm bớt các phức tạp khi biện luận. Ðể buộc người sử dụng phải nhập A 0, nếu nhập A=0 thì bắt nhập lại cho tới khi nhập A 0 mới thôi, ta dùng cấu trúc: Repeat Write(‘Nhập A khác không: ‘); Readln(A); Until A =20) then write(#7); Until (0<N) and (N<20); Lệnh write(chr(7) ) hay write(#7) có công dụng phát ra tiếng kêu bip để cảnh báo người dùng đã nhập dữ liệu sai yêu cầu. + Ví dụ 10_4: Tìm bội số chung nhỏ nhất của hai số nguyên dương M và N. Bài toán này có những cách giải khác nhau, dưới đây là một cách đơn giản. Trước hết, hãy xem cách tìm BSCNN của hai số M=5 và N=9. 147
  92. Vì N>M nên ta sẽ tìm trong tập các bội số của N: {9, 18, 27, 36, 45, } số nhỏ nhất chia hết cho M, đó là số 45. Một cách tổng quát, gọi Max là số lớn nhất của M và N. Ðầu tiên ta gán: BSCNN:=0; Sau đó cứ làm lệnh BSCNN:=BSCNN+Max; hoài cho đến khi BSCNN chia hết cho cả M và N thì dừng. Trong chương trình ta dùng lệnh repeat để nhập hai số M, N đảm bảo dương. PROGRAM VIDU10_4; {Tìm BSCNN của M và N} Var M, N, Max, BSCNN: Integer; Begin Repeat Write(‘Nhập M và N dương:’); Readln(M, N); Until (M>0) and (N>0); If N>M then Max:=N else Max:=M; BSCNN:=0; Repeat BSCNN:=BSCNN + Max; Until (BSCNN mod N=0) and (BSCNN mod M=0); 148
  93. Writeln(‘Bội số chung nhỏ nhất= ‘, BSCNN); Readln; End. + Ví dụ 10_5: Thiết kế để chạy nhiều lần một chương trình. Trong Turbo Pascal, mỗi lần muốn chạy chương trình ta phải gõ cặp phím Ctrl và F9 (viết tắt là ^F9), điều này sẽ bất tiện nếu cần chạy chương trình nhiều lần ứng với các bộ dữ liệu thử khác nhau. Cấu trúc sau đây cho phép ta chạy chương trình một số lần theo ý muốn: REPEAT {Các lệnh của chương trình} Write(‘Tiếp tục nữa không (Y/N)?’); Readln(Traloi); {5} UNTIL (Traloi =‘N’) or ( Traloi=‘n’); Ở đây, Traloi là một biến kiểu ký tự (Char); Sau khi thực hiện xong {các lệnh của chương trình}, nếu muốn chạy tiếp thì ta gõ phím Y, nếu muốn dừng thì gõ N. Chú ý: lệnh Readln(Traloi); ở dòng thứ {5} có thể thay bằng: Traloi:=Readkey; Hàm Readkey thuộc thư viện CRT cho kết quả là một ký tự gõ từ bàn phím, nó khác lệnh Readln(Traloi) ở chỗ là khi nhập ký tự ta không cần phải Enter. Chương trình dưới đây cho phép thực hiện một số lần việc: in tam giác cân đặc có chiều cao m (0<m<20): 149
  94. PROGRAM VIDU10_5; {In tam giác cân đặc} Uses CRT; Const sao =‘*’; Var k, j, m: integer; Traloi: Char; Begin REPEAT {9} Clrscr; Repeat {11} Write(‘Nhập m (0 =20) then write(#7); Until (m>0) and (m<20); {15} Writeln(sao:m); {in đỉnh} {In hai cạnh bên của tam giác} For k:=1 to m-2 do 150
  95. Begin Write(chr(32): m-k-1); {in m-k-1 ký tự trắng} for j:=1 to 2*k+1 do Write(sao); {in 2k+1 dấu *} Writeln; End; For k:=1 to 2*m-1 do Write(sao); {in cạnh đáy} Writeln; Write(‘Tiếp tục nữa không (Y/N)?‘); Readln(Traloi); UNTIL (Traloi=‘N’) or ( Traloi=‘n’); {28} End. Chương trình 10.5 là một ví dụ về hai câu lệnh Repeat lồng nhau, điều này xảy ra khi thân của một lệnh Repeat lại chứa một lệnh Repeat khác: lệnh Repeat thứ nhất, từ dòng {9} đến dòng {28}, chứa lệnh Repeat thứ hai từ dòng {11} đến dòng {15}. 10.3. So sánh các lệnh For, While và Repeat: + Lệnh For dùng cho các vòng lặp có số lần lặp đã biết trước + Lệnh While hay Repeat tổng quát hơn lệnh For, dùng được cho tất cả các loại vòng lặp, nhưng thường dùng cho các vòng lặp có số lần lặp chưa biết trước. + Lệnh While và Repeat khác nhau ở điểm sau: lệnh While kiểm tra điều kiện trước, nếu đúng mới thực hiện các lệnh ghi trong thân của nó (lệnhP), còn lệnh Repeat thực hiện lệnhP rồi mới kiểm tra điều kiện. Vì thế, lệnh Repeat sẽ thực hiện các lệnh ghi trong thân của nó ít nhất được một lần. 151
  96. + Ngoài ra, lệnh While kết thúc khi điều kiện sai, lệnh Repeat kết thúc khi điều kiện đúng. 11. KIỂU LIỆT KÊ và KIỂU ÐOẠN CON 11.1. Kiểu liệt kê (enumerated type) 11.1.1. Cách khai báo Ngoài các kiểu dữ liệu đã có sẵn như kiểu nguyên, thực, ký tự, lôgic và chuỗi, Turbo Pascal còn cho phép người thảo chương có thể tự xây dựng các kiểu dữ liệu mới. Kiểu liệt kê được định nghĩa bằng cách sử dụng từ khóa TYPE và liệt kê ra tất cả các giá trị của kiểu, theo mẫu sau: Type Tênkiểu = (tên1, tên2, , tênN); trong đó tên1, tên2, , tênN là các tên tự đặt theo đúng quy ước về đặt tên. Ví dụ: Type Phai=(nam, nu); Ten_mau = (den, trang, xanh, vang, tim, nau); Theo khai báo này thì Phai là một kiểu dữ liệu liệt kê chỉ có hai giá trị là nam và nu, Ten_mau cũng là kiểu dữ liệu liệt kê và có sáu giá trị là: den, trang, xanh, vang, tim, nau. Khi một kiểu liệt kê đã được định nghĩa thì có thể khai báo các biến thuộc kiểu liệt kê này bằng từ khóa Var. Ví dụ: Var Ph1, Ph2: Phai; M1, M2: Ten_mau; 152
  97. Trong chương trình, ta có thể gán: Ph1:=nam; Ph2:=nu; M1:=den; M2:=trang; Pascal còn cho phép khai báo trực tiếp biến kiểu liệt kê không cần qua giai đoạn định nghĩa Type bằng cách liệt kê các giá trị mà biến có thể nhận. Ví dụ: các biến Ph1, Ph2, M1, M2 nói trên có thể khai báo trực tiếp như sau: Var Ph1, Ph2: (nam, nu); M1, M2: ( den, trang, xanh, vang, tim, nau); 11.1.2. Các hàm liên quan đến kiểu liệt kê + Hàm ORD(tên): Trả về số thứ tự của tên trong kiểu liệt kê. Các giá trị liệt kê được đánh số thứ tự bắt đầu từ 0. Ví dụ: Ord(nam)=0, Ord(xanh)=2 Thông qua hàm Ord, các giá trị liệt kê có thể so sánh với nhau theo quy tắc: giá trị nào có số thứ tự nhỏ hơn thì nhỏ hơn: den < trang < xanh< vang< tim< nau + Hàm PRED(tên) và hàm SUCC(tên): trả về giá trị đứng ngay trước và ngay sau tên trong kiểu liệt kê tương ứng. Ví dụ: Pred(nu)=nam Pred(nau)=tim Succ(den)=trang 153
  98. + Hàm Tênkiểu(k): trả về giá trị liệt kê có số thứ tự là k trong Tênkiểu, ví dụ: Phai(0)=nam Ten_mau(2)= xanh Hàm này là hàm ngược của hàm Ord. 11.1.3. Nhập, xuất kiểu liệt kê Các giá trị liệt kê không thể nhập, xuất trực tiếp bằng lệnh Readln và Write được. Ðây là hạn chế của kiểu liệt kê, khiến nó không thông dụng. Khi muốn nhập hay xuất kiểu liệt kê, ta có thể dùng một biến trung gian St kiểu chuỗi. Chẳng hạn, muốn nhập màu xanh cho biến M1, ta dùng hai lệnh: Readln(St); If St=‘xanh’then M1:=xanh; Tương tự, muốn in màu xanh lên màn hình, ta dùng lệnh: If M1=xanh then Writeln(‘xanh’); 11.2. Kiểu đoạn con (Subrange type) 11.2.1. Khai báo Kiểu đoạn con được mô tả bằng cách chỉ ra phạm vi giá trị mà các biến thuộc kiểu đó có thể nhận: TYPE Tênkiểu = hằng1 hằng2; VAR Tênbiến: Tênkiểu; hoặc khai báo trực tiếp: 154
  99. VAR Tênbiến: hằng1 hằng2; trong đó, hằng1< hằng2 là hai hằng thuộc cùng một kiểu dữ liệu. Kiểu dữ liệu của hằng1 và hằng2 chỉ có thể là kiểu nguyên, ký tự, lôgic, hay liệt kê Ví dụ: Type Chu_Hoa =‘A’ ’Z’; Tuoi= 0 200; Var Ch: Chu_hoa; T: Tuoi; Theo khai báo này thì ch là một biến kiểu đoạn con, có thể nhận các giá trị là các ký tự từ ‘A’đến ‘Z’, tương tự, biến T có thể nhận các giá trị là các số nguyên từ 0 đến 200. Cũng có thể khai báo hai biến Ch và T trực tiếp theo cách sau: Var Ch: ‘A’ ’Z’; T: 0 200; 11.2.2. Tác dụng của kiểu đoạn con Trong nhiều trường hợp, việc khai báo đoạn con có tác dụng tiết kiệm bộ nhớ. Tùy theo phạm vi hằng1 hằng2 mà Turbo Pascal sẽ cấp phát cho biến một số byte tối thiểu. Trong ví dụ trên, mỗi biến Ch hay T sẽ được chứa trong 1 byte. Kiểu đoạn con còn cho phép kiểm soát được giá trị của biến có vượt ra ngoài phạm vi của nó hay không. Ví dụ, nếu đối với 155
  100. biến T mà gán T:=201; thì máy sẽ báo lỗi "const out of range". Ngoài ra khi chạy chương trình trong mode {$R+}, chương trình sẽ dừng ngay nếu biến nhận giá trị vượt khỏi phạm vi. Kiểu liệt kê và kiểu đoạn con thuộc loại đơn giản và đếm được. 12. KIỂU TẬP HỢP 12.1. Khai báo Một tập hợp thì gồm nhiều giá trị có chung một kiểu dữ liệu, gọi là kiểu cơ bản. Kiểu cơ bản phải là kiểu vô hướng đếm được, tức chỉ có thể là kiểu byte, ký tự, lôgic hay liệt kê. Số phần tử của tập hợp tối đa là 256 phần tử. Kiểu tập hợp được mô tả bằng từ khóa SET OF, kế đến là kiểu cơ bản của các phần tử. Ví dụ: TYPE Kky_tu = SET OF Char; Kchu_hoa = SET OF ‘A’ ‘Z’; Kso = SET OF Byte; Var TapA, TapB: Kky_tu; TapH: Kchu_hoa; TapS: Kso; Các tập hợp TapA, TapB, TapH, TapS nói trên cũng có thể khai báo trực tiếp như sau: Var TapA, TapB: SET OF Char; 156
  101. TapH: SET OF ‘A’ ‘Z’; TapS: SET OF Byte; Chú ý rằng các khai báo dưới đây là sai, vì kiểu cơ bản không thể là Integer, và nếu là đoạn con thì phạm vi của đoạn con không được vượt quá phạm vi của kiểu Byte: Var T1: SET OF Integer; T2: SET OF 1 256; 12.2. Xác định một tập hợp Một tập hợp được xác định bằng cách liệt kê các phần tử của nó, các phần tử được phân cách nhau bởi dấu phẩy, và đặt giữa hai dấu ngoặc vuông. [ ]: tập rỗng [3 6]: tập các số nguyên 4, 3, 5, 6 [3 6, 9, 12]: tập các số nguyên 3, 4, 5, 6, 9, 12 [‘A’ ’C’, ‘X’, ‘Z’]: tập các chữ ‘A’, ‘B’, ‘C’, ‘X’, ‘Z’ Các phần tử của tập hợp có thể là biến hay biểu thức, ví dụ: [3, 5, i+j, 2*j]: tập hợp này có 4 phần tử là 3, 5, hai phần tử kia có giá trị bằng i+j và 2*j, trong đó i, j là các số nguyên sao cho i+j và 2*j nằm trong phạm vi từ 0 đến 255. 12.3. Các phép toán + Phép gán Có thể gán một tập hợp cho một biến tập hợp cùng kiểu. Ví dụ, với các biến khai báo ở trên, có thể gán: TapA:=[‘1’ ’5’, ‘9’, ‘A’]; TapH:=[‘C’ ’F’]; 157
  102. TapS:=[15 20, 30, 40]; Tập rỗng [ ] gán cho biến tập hợp kiểu nào cũng được: TapA:=[ ]; TapH:=[ ]; Lệnh gán dưới đây là sai vì hai vế không cùng kiểu dữ liệu: TapA:=[1 8]; TapS:=[‘1’ ’9’]; + Phép hợp Hợp của hai tập hợp A và B, ký hiệu là A+B, là một tập hợp gồm các phần tử hoặc thuộc tập A hoặc thuộc tập B. Ví dụ: [3 5]+[4 6,10, 15]=[ 3 6, 10, 15] + Phép giao Giao của hai tập hợp A và B, ký hiệu là A*B, là một tập hợp gồm các phần tử đồng thời thuộc A và B. Ví dụ: [1 10]*[5 15] =[5 10] + Phép hiệu Hiệu của hai tập hợp A và B, ký hiệu là A-B, là một tập hợp gồm các phần tử thuộc tập A nhưng không thuộc tập B. Ví dụ: [1 10] - [5 15] =[1 4] Nhận xét: Các phép toán trên chỉ thực hiện được khi A và B cùng kiểu, và kết quả là một tập hợp C cùng kiểu với A và B. + Phép thử IN (thuộc về) Dùng để kiểm tra xem một biến hay một giá trị có phải là phần tử của một tập hợp nào đó không. Biểu thức x IN TapA cho kết quả là True nếu giá trị x thuộc TapA, cho giá trị False trong trường hợp ngược lại. 158
  103. Ví dụ: ‘N’IN [‘N’, ‘n’] cho kết quả là TRUE ‘y’IN [‘N’, ‘n’] cho kết quả là FALSE + Các phép so sánh (=, =) Cho A và B là hai tập hợp cùng kiểu, kết quả của các phép so sánh A với B sẽ là TRUE hoặc FALSE. + Phép bằng A=B khi và chỉ khi mỗi phần tử của A đều thuộc B và mỗi phần tử của B đều thuộc A, trong trường hợp ngược lại, ta nói A khác B và viết A [‘a’,’B’] + Phép nhỏ hơn hoặc bằng A =B khi và chỉ khi mọi phần tử của B đều thuộc A, nói cách khác A>=B khi và chỉ khi B =[3 5] [‘A’ ’Z’]>=[‘A’ ’D’] Chú ý rằng trong các tập hợp không có phép so sánh nhỏ hơn ( ). Khi cần so sánh lớn hơn hay nhỏ hơn ta có thể viết: 159
  104. If (A B) then writeln (‘A =B) and ( A B’); 12.4. Các ví dụ + Ví dụ 12_1 Nhập vào một chuỗi St, cho biết trong St có những chữ hoa nào. Ví dụ St= ‘ABc3BAFdzA’thì có các chữ hoa là A, B, F. PROGRAM VIDU12_1; Var Taphoa: Set of ‘A’ ’Z’; N, i: byte; ch: char; St: String; Begin Write(‘Nhập chuỗi St: ‘); Readln(St); Taphoa:=[]; N:=length(St); For i:=1 to N do if St[i] IN [‘A’ ’Z’] then Taphoa:= Taphoa+[ St[i] ]; Writeln(‘Cac chu hoa co trong St la:’); For ch:=‘A’to ‘Z’do if ch IN Taphoa then write(ch:3); 160
  105. Readln; End. + Ví dụ 12_2 Nhập N số nguyên trong phạm vi từ 0 đến 255. In ra các tập số chẵn, lẻ và cho biết có bao nhiêu số chẵn, bao nhiêu số lẻ (các số trùng nhau chỉ kể 1 lần). Ví dụ, nhập 9 số: 1, 2, 3, 4, 1, 2, 3, 4, 5 thì có hai số chẵn là 2 và 4 và có 3 số lẻ là 1, 3, 5. PROGRAM VIDU12_2; Const N=10; Var Tapchan, Taple: Set of byte; N1, N2, i, k: byte; Begin Tapchan:=[]; Taple:=[]; For i:=1 to N do Begin Write(‘Nhap so thu ‘, i, ‘: ‘); Readln(k); If odd(k)=False then Tapchan:=Tapchan+[k] else Taple:=Taple+[k]; End; 161
  106. writeln(‘Các số chẵn là: ‘); N1:=0; For i:=0 to (255 div 2) do if 2*i IN Tapchan then Begin Write(2*i: 3); N1:=N1+1; End; Writeln( ‘Có ‘, N1, ‘số chẵn’); Writeln(‘Các số lẻ là: ‘); N2:=0; For i:=0 to (255 div 2) do if (2*i +1) IN Taple then Begin Write(2*i+1: 3); N2:=N2+1; End; writeln(‘Có ‘, N2, ‘số lẻ’); Readln; End. + Ví dụ 12_3 Tìm các số nguyên tố của số nguyên dương N cho trước. Có nhiều cách giải khác nhau, dưới đây giới thiệu phương pháp của Eratosthene, sử dụng dữ liệu kiểu tập hợp và không cần đến các phép toán nhân. 162
  107. Xuất phát từ tập số nguyên S=[2 N] ta loại ra số nguyên tố đầu tiên và tất cả các bội số của nó ra khỏi S, lặp lại quá trình trên cho đến khi S thành tập rỗng. Ví dụ: S=[2 15]; Bước 1: - đưa 2 vào tập số nguyên tố, Tapsnt:=[2] - loại 2 và các bội của 2 ra khỏi S, ta được S=[3,5,7,9,11,13,15 ] Bước 2: - đưa 3 vào tập số nguyên tố, Tapsnt:=[2,3] - loại 3 và các bội của 3 ra khỏi S, ta được S=[5,7,11,13 ] Bước 3: - đưa 5 vào tập số nguyên tố, Tapsnt:=[2,3,5] - loại 5 và các bội của 5 ra khỏi S, ta được S=[7,11,13] Bước 4: - đưa 7 vào tập số nguyên tố, Tapsnt:=[2,3,5,7] - loại 7 và các bội của 7 ra khỏi S, được S=[11,13 ] Bước 5: - đưa 11 vào tập số nguyên tố, Tapsnt:=[2,3,5,7,11] - loại 11 và các bội của 11 ra khỏi S, được S=[13 ] Bước 6: - đưa 13 vào tập số nguyên tố, Tapsnt:=[2,3,5,7,11,13] - loại 13 và các bội của 13 ra khỏi S, được S=[ ] Kết quả được Tapsnt = [2,3,5,7,11,13]. Tập S giống như một cái sàng để lọc và loại ra các số không phải số nguyên tố. Chương trình cụ thể như sau: PROGRAM VIDU12_3; Const 163
  108. N=20; Var Tapsnt, S: Set of 1 N; snt, i: Integer; Begin S:=[2 N]; Tapsnt:=[]; snt:=2; {so nguyen to nho nhat} Writeln(‘Cac so nguyen to la:’); Repeat {tim snt la so nho nhat trong S} While Not (snt IN S) do snt:=snt+1; Tapsnt:= Tapsnt+[snt]; {dua vao Tapsnt} Write(snt:4); {Loại các bội của snt ra khỏi S} i:=snt; While i<=N do Begin S:=S - [i]; i:=i+snt; End; Until S=[]; Readln; End. 164
  109. 13. KIỂU MẢNG 13.1. Mảng một chiều + Khái niệm Mảng là một tập gồm nhiều phần tử có cùng chung một kiểu dữ liệu. Mỗi phần tử của mảng có một đại lượng xác định vị trí tương đối của phần tử đó so với các phần tử khác trong mảng, gọi là chỉ số. Các yếu tố để xác định một mảng gồm có: Tên mảng Kiểu dữ liệu chung của các phần tử trong mảng Kiểu dữ liệu của chỉ số và phạm vi của chỉ số. Kiểu dữ liệu của các phần tử mảng là mọi kiểu dữ liệu mà một biến có thể có. Tuy nhiên, kiểu dữ liệu của chỉ số thì không được là kiểu thực hay kiểu chuỗi, nó chỉ có thể là kiểu đếm được: nguyên, ký tự, lôgic, liệt kê hay đoạn con. 13.1.1. Khai báo mảng một chiều Mảng một chiều, còn gọi là dãy, hay đơn giản là mảng, có thể khai báo theo một trong hai cách: * Cách 1: Khai báo trực tiếp theo cách sau Var Tênmảng: Array[m1 . . m2] of Tênkiểudữliệu; Ở đây m1, m2 là hai hằng xác định phạm vi của chỉ số, chúng có chung một kiểu dữ liệu, và m1 ≤ m2. Ví dụ: Cho khai báo dưới đây: Var A: Array[0 10] of Real; Hten: Array[1 5] of String[18]; B: Array[‘a’ ’d’] of Integer; 165
  110. Theo khai báo trên, ta có ba mảng: + Mảng thứ nhất tên là A, gồm 11 phần tử cùng kiểu Real, ứng với các chỉ số 0, 1, 2, , 10, đó là: A[0], A[1], A[2], , A[10] + Mảng thứ hai tên là HTen gồm 5 phần tử cùng kiểu dữ liệu là String[18] ứng với các chỉ số từ 1 đến 5: Hten[1], Hten[2], Hten[3], Hten[4], Hten[5] + Mảng thứ ba tên là B, gồm 4 phần tử cùng kiểu Integer ứng với các chỉ số ‘a’, ‘b’, ‘c’, ‘d’: B[‘a’], B[‘b’], B[‘c’], B[‘d’] Ðể có một hình ảnh về mảng, đối với mảng A, ta hình dung có một dãy nhà một tầng, tên gọi là dãy A, gồm 11 phòng liên tiếp giống hệt nhau được đánh số thứ tự từ 0,1, 2, , đến 10: A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 Tương tự, mảng B cũng giống như dãy nhà B một tầng có bốn phòng được đánh số thứ tự là các chữ a, b, c, d: Ba Bb Bc Bd * Cách 2: Khai báo qua một kiểu dữ liệu mới, gồm hai bước Bước 1: Ðịnh nghĩa kiểu dữ liệu mảng TYPE Tênkiểumảng = Array[m1 . . m2] of Tênkiểudữliệu; Bước 2: Khai báo biến có kiểu dữ liệu là kiểu mảng VAR Tênmảng: Tênkiểumảng; 166
  111. Ví dụ, đối với các mảng A, B và Hten ở trên ta có thể khai báo theo cách 2, như sau: Type Mang1 = array[0 10] of Real; Mang2 = array[1 5] of String[18]; Mang3 = array[‘a’ ’d’] of Integer; Var A: Mang1; Hten: Mang2; B: Mang3; 13.1.2. Khai báo mảng có gán trị ban đầu Pascal cho phép vừa khai báo mảng vừa gán giá trị ban đầu cho các phần tử mảng, chẳng hạn như dưới đây: Const X: array[1 5] of Integer = (12, 14, 16, 18, 20); Khi đó X là một mảng gồm năm phần tử cùng kiểu nguyên và có giá trị X[1]=12, X[2]=14, X[3]=16, X[4]=18, X[5]=20. Mặc dù từ khóa ở đây là Const song X lại được dùng như là một biến mảng, tức là các phần tử của X có thể thay đổi giá trị được. Ví dụ, trong chương trình ta có thể gán: X[1]:= 2; X[2]:=5+20; 13.1.3. Truy xuất các phần tử mảng Các xử lý trên mảng được quy về xử lý từng phần tử mảng. Ðể xác định một phần tử của mảng, ta dùng cách viết: 167
  112. Tênmảng [chỉ số của phần tử] Ví dụ: có thể gán: A[0]:= 15.8; A[1]:= 2*A[0]; Hten[3]:= ‘Nguyen Thi Loan’; B[‘a’]:=100; Chỉ số của một phần tử có thể là một biến, một hằng, hay một biểu thức. Ví dụ, cho i là biến kiểu nguyên, khi đó ta có thể dùng các lệnh: i:=6; A[i]:=100.25; Hai lệnh trên tương đương với một lệnh: A[6]:=100.25; Nếu biến i có giá trị là 6 thì lệnh: A[ i div 2 +1]:= 4.5; tương đương với lệnh: A[4]:=4.5; vì biểu thức i div 2 +1 có giá trị là 4. Khi nhập dữ liệu cho các phần tử của một mảng, ta có thể dùng câu lệnh For, While hay Repeat. Ví dụ: nhập dữ liệu cho các phần tử của mảng A: For i:=0 to 10 do begin Write(‘Nhập phần tử thứ ‘, i , ‘: ‘); Readln(A[i]); end; hoặc (dùng While): 168
  113. i:=0; While i<= 10 do begin Write(‘Nhập phần tử thứ ‘, i, ‘: ‘); Readln(A[i]); i:=i+1; end; Tương tự để nhập dữ liệu cho các phần tử của mảng B, ta viết: For ch:=‘a’to ‘d’do begin Write(‘Nhap phần tử thứ ‘, ch, ‘: ‘); Readln(B[ch]); end; Ðể in các giá trị của mảng A lên màn hình, ta viết: For i:=0 to 10 do Write(A[i]:6:2); Các giá trị của mảng A sẽ được in liên tiếp nhau trên cùng một dòng. Còn nếu muốn in mỗi phần tử trên một dòng, ta thay lệnh Write bằng Writeln. Tương tự, mảng B được in lên màn hình bằng lệnh: For ch:=‘a’to ‘d’do Write(B[ch]); Chú ý: Turbo Pascal cho phép gán một mảng này cho một mảng khác. Nếu X, Y là hai biến mảng cùng một kiểu mảng thì lệnh: X:= Y; 169
  114. có nghĩa là lấy giá trị của từng phần tử của mảng Y gán cho phần tử tương ứng trong mảng X. Ví dụ, cho khai báo: Var X, Y: Array[1 10] of Real; Khi đó, lệnh: X:= Y; tương đương với lệnh: For i:=1 to 10 do X[i]:=Y[i]; 13.1.4. Các bài toán cơ bản về mảng + Ví dụ 13_1: Ðếm số lần xuất hiện của giá trị x trong dãy A1, A2, , An . Ví dụ giá trị x=6 xuất hiện 3 lần trong dãy 6 7 1 2 6 0 6 1. Ta dùng biến Dem kiểu nguyên để đếm số lần xuất hiện của x. Ðầu tiên ta gán Dem:=0, sau đó duyệt từng phần tử A1, A2, An, mỗi khi có một phần tử bằng x thì tăng biến Dem lên một đơn vị. Kết quả là biến Dem có giá trị đúng bằng số phần tử bằng x. Hai lệnh chính của thuật toán là: Dem:=0; For i:=1 to N do If A[i]=x then Dem:=Dem+1; Ví dụ, đếm trong dãy số A có bao nhiêu số 0, ta viết: Dem:=0; For i:=1 to N do If A[i]=0 then Dem:=Dem+1; Writeln(‘Có ‘, Dem , ‘số không ‘); Nhận xét: Ðẳng thức A[i]=x (hay A[i]=0) là điều kiện để biến Dem được tăng thêm 1, vậy bài toán trên có thể mở rộng 170
  115. là: hãy đếm số phần tử của mảng A thỏa mãn một điều kiện cho trước. Trong lệnh For ở trên, khi thay đẳng thức A[i]=x bằng A[i] thỏa điều kiện , ta được thuật toán tổng quát hơn: Dem:=0; For i:=1 to N do If A[i] thỏa điều kiện then Dem:=Dem+1; Chương trình sau nhập một mảng A có N phần tử, in mảng A lên màn hình, và đếm xem mảng A có bao nhiêu số dương: PROGRAM VIDU13_1; {Ðếm số dương trong mảng} Type Kmang = Array[1 20] of Real; Var A: Kmang; i, N, Dem: Integer; Begin Repeat Write(‘Nhập số phần tử N: ‘); Readln(N); Until (N>0) and ( N<21); {nhập mảng} For i:=1 to N do Begin Write(‘Nhập A[‘, i , ‘]: ‘); Readln( A[i] ); End; 171