III/ KIỂU BẢN GHI (RECORD)
5. Bản ghi có cấu trúc thay đổi
Cấu trúc của bản ghi là các trường với tên, kiểu dữ liệu và độ rộng. Các kiểu record nêu ở trên là kiểu bản ghi cố định, không thay đổi. Trong thực tế, ta thường gặp một số bản ghi có đến hàng chục trường, mà trong đó chỉ có một số trường là mọi đối tượng đều có (phần cố định), còn lại một số trường khác thì mỗi đối tượng lại có phần khác nhau (phần thay đổi). Việc tổ chức một bản ghi cố định theo cách đã học ở trên sẽ gây lãng phí bộ nhớ. Ngôn ngữ Pascal cho phép thành lập các bản ghi có một phần cấu trúc thay đổi được hay còn gọi là record thay đổi.
Chẳng hạn, để ghi kết quả môn thi của sinh viên, trong đó 3 ngành:
- Nông nghiệp:Trồng trọt, Chăn nuôi, Thủy sản, Chế biến, Anh văn, Tin học - Sư phạm: Toán học, Vật lý, Hoá học, Sinh học, Anh văn, Tin học
- Công nghệ: Xây dựng, Cơ khí, Thủy lợi, Môi trường, Anh văn, Tin học Như vậy, trong 3 ngành trên đều có chung môn Anh văn và Tin học (phần cố định), còn lại là mỗi ngành lại có môn học phân biệt khác nhau (phần thay đổi). Thay vì khai báo 18 môn học cho 3 ngành khác nhau, ta có thể tổ chức cách khác bằng cách tạo ra một cấu trúc record thay đổi. Trước hết, ta xét các định nghĩa trong một bản ghi có cấu trúc thay đổi:
- Phần cố định (các trường cố định) là phần giống nhau trong mọi trường hợp của tất cả mọi đối tượng. Trong record có cấu trúc thay đổi, phần này có thể có hoặc không, nếu có thì phần cố định sẽ được khai báo trước. Phần cố định được viết bình thường như đã trình bày ở phần trên.
- Trong các trường cố định, ta chọn một trường làm chỉ tiêu để phân loại cho các trường hợp mà mỗi trường hợp có một số trường khác nhau (trường thay đổi) tương ứng với từng giá trị mà trường làm chỉ tiêu phân loại nhận. Trường làm chỉ tiêu phân loại này được gọi là trường phân loại, như vậy trường phân loại này cũng chính là trường cố định. Trường phân loại này luôn được đặt sau cùng trong các trường cố định.
- Phần thay đổi (các trường thay đổi ) luôn được đặt sau trường phân loại.
Trường phân loại này được đặt trong từ khóa CASE .. OF. Tùy theo trường phân loại này nhận giá trị nào thì sẽ có các trường thay đổi tương ứng.
Dạng tổng quát của record thay đổi:
TYPE
<tên kiểu> = <kiểu trường> ; ...
<tên kiểu record> = RECORD
<Tên trường 1a>[,<Tên trường1b>,...] : <Tên kiểu> ; <Tên trường 2a>[,<Tên trường2b>,...] : <Tên kiểu> ;
... {các trường cố định}... ;
CASE <tên trường phân loại> : <tên kiểu> OF
<giá trị 1> : (<danh sách các trường thay đổi 1>:< Tên kiểu >); <giá trị 2> : (<danh sách các trường thay đổi 2>:< Tên kiểu >);
...{Các trường thay đổi}... ;
END;
Ghi chú: Danh sách của trường thay đổi tùy thuộc vào từng giá trị cụ thể của trường phân loại, dấu ngoặc đơn ( ... ) bao danh sách của trường thay đổi là bắt buộc phải có kể cả khi nó rỗng.
Với ví dụ trên ta thấy thay vì ghi một bản ghi 18 môn học, ta chỉ cần dùng record biến đổi có số lượng môn học là 6 theo thực tế của mỗi sinh viên.
Ví dụ 3.7: Trong ví dụ trên, bài toán lập trình có thể viết như sau: TYPE
NGANH = (NN, SP, CN) ; DSMONHOC = RECORD
AVAN, TINHOC: Diemthi;
CASE MONHOC : NGANH OF
NN : (TTROT, CNUOI, TSAN, CBIEN: Diemthi) ; SP : (TOAN, LY, HOA, SINH: Diemthi) ;
CN : (XDUNG, CKHI, TLOI, MTRUONG: Diemthi) ; END ;
Ở bản ghi DSMONHOC, ta có 2 trường cố định là AVAN và TINHOC, một trường phân loại là MONHOC, và 4 trường thay đổi tương ứng tùy theo trường phân loại MONHOC nhận gía trị là NN, SP, hay CN.
Ví dụ 3.8: Xét một lớp các loại hình phẳng là gồm các cá thể : tam giác, chữ nhật, và hình tròn. Mỗi loại hình đều có :
* Các chỉ tiêu chung là: Tên hình, diện tích, chu vi. * Các chỉ tiêu riêng là:
- Ba cạnh đối với tam giác.
- Chiều dài, chiều rộng đối với hình chữ nhật. - Bán kính đối với hình tròn.
Ta dùng kiểu bản ghi có cấu trúc thay đổi, để viết chương trình tính tính diện tích và chu vi cho từng loại hình như sau:
PROGRAM BAN_GHI_THAY_DOI; USES CRT;
TYPE Cathe = RECORD Dt,cv:real; Case kieu:(TG,CN,HT) of TG: (c1,c2,c3:real); CN: (cr,cd:real); HT: (bk:real); End; VAR Doituong: Cathe; Tenhinh:string[2]; i:byte;ds:boolean;
BEGIN Clrscr; Repeat
Write('Nhập loại hình "TG/CN/HT" vào: ');readln(tenhinh); For i:=1 to length(tenhinh) do tenhinh[i]:=upcase(tenhinh[i]); Until (tenhinh='TG') OR (tenhinh='CN') OR (tenhinh='HT'); If tenhinh='TG' then doituong.kieu:=TG;
If tenhinh='CN' then doituong.kieu:=CN; If tenhinh='HT' then doituong.kieu:=HT; With doituong do
Case kieu of TG: begin
Writeln('ÐỐI TƯỢNG LÀ HÌNH TAM GIÁC'); Write(' Nhập cạnh thứ nhất = ');readln(c1); Write(' Nhập cạnh thứ hai = ');readln(c2); Write(' Nhập cạnh thứ ba = ');readln(c3);
If (c1+c2>c3) AND (c1+c3>c2) and (c2+c3>c1) then Begin
Cv:=(c1+c2+c3)/2; {nửa chu vi}
Dt:=SQRT(cv*(cv-c1)*(cv-c2)*(cv-c3)); Cv:=cv*2; {chu vi};
Writeln(' Chu vi = ',cv:6:2); Writeln(' Diện tích = ',dt:6:2); End
Else writeln(' Không thỏa tính chất tam giác'); End;
CN: begin
Writeln('ÐỐI TƯỢNG LÀ HÌNH CHỮ NHẬT'); Write('Nhập chiều rộng = ');readln(cr);
Write('Nhập chiều dài = ');readln(cd); Dt:=cr*cd;
Writeln(' Chu vi = ',cv:6:2); Writeln(' Diện tích = ',dt:6:2); End;
HT: begin
Writeln(' ÐỐI TƯỢNG LÀ HÌNH TRÒN'); Write('Nhập bán kính = ');readln(bk); Cv:=2*Pi*bk; dt:=pi*SQR(bk); Writeln(' Chu vi = ',cv:6:2); Writeln(' Diện tích = ',dt:6:2); End; End; Readln; END.
Ví dụ 3.9: (Xem xét kỹ hơn ở trường hợp ví dụ 8.37)
Một Công ty bán hàng trả góp cần lập trình một Hệ thống Hóa đơn Khách hàng (Customer Billing System). Chương trình này đòi hỏi tạo một record về khách hàng vào máy tính. Mỗi khách hàng sẽ được cân đối số tiền nợ tồn hàng ngày dựa vào các số tiền chi trả trước và mức phải chi trả hiện hành.
Mỗi tài khoản sẽ được xem xét là đúng kỳ hạn, trừ khi :
- Mức chi trả hiện hành là lớn hơn 0 nhưng nhỏ hơn 10% của cân đối nợ tồn lần trước. Trong trường hợp này, tài khoản sẽ được xem như quá hạn (overdue).
- Nếu cân đối tồn nợ vẫn còn và giá trị chi trả hiện hành là 0, trong trường hợp này tài khoản sẽ được xem xét như phạm lỗi chểnh mảng, dây dưa" việc trả nợ (delinquent).
Ðể dễ dàng thao dõi, tất cả các bản ghi (record) sẽ được trữ như các phần tử của một mảng một chiều. Do vậy, chiến lược lập trình tổng thể sẽ như sau:
(1). Khai báo số lượng tài khoản (chính là số record) phải xử lý. (2). Trong mỗi bản ghi, đọc các hạng mục sau:
a. Tên khách hàng (name) b. Số nhà và tên đường (street) c. Tên thành phố (city)
d. Số tài khoản (account number) e. Nợ trước (previous balance)
f. Chi trả hiện hành (current payment) g. Ngày chi trả (payment date)
(3). Sau khi tất cả các bản ghi được nhập vào máy tính, xử lý mỗi bản ghi theo cách sau:
a. So sánh chi trả hiện hành với số nợ tồn và định tình trạng nợ phù hợp.
b. Tính toán cân đối tài khoản mới bằng cách lấy nợ trước trừ đi số chi trả hiện hành (nếu ra số âm thì xem đó là một tín dụng - credit).
(4). Sau khi mỗi bản ghi đã được xử lý, hiện hình các thông tin sau cho mỗi bản: a. Tên khách hàng (name)
b. Số tài khoản (account number) c. Tên số nhà và tên đường (street) d. Tên thành phố (city)
e. Cân nợ cũ (old balance)
f. Chi trả hiện hành (current payment) g. Cân nợ mới (new balance)
h) Tình trạng tài khoản (account status)
Chúng ta sẽ lập trình theo cách thức module, với một thủ tục (procedure) riêng biệt để nhập dữ liệu, thủ tục xử lý dữ liệu và thủ tục in ra màn hình kết quả. Mỗi thủ tục sẽ viết theo lối thẳng tiến và không cần phải mô tả tỉ mỉ. Thân chương trình chính sẽ làm đơn giản nhập các dữ liệu record và sau đó truy xuất các thủ tục thích hợp. Các chữ viết tắt theo giải thích ở ví dụ 8.37. Sau đây là một trình Pascal về bài toán trên (viết theo Byron S. Gottried, xem sách tham khảo):
PROGRAM customers ;
{Chương trình HỆ THỐNG HÓA ÐƠN KHÁCH HÀNG } {dùng minh họa cách sử dụng Record}
TYPE status = (current, overdue, delinquent) ; Date = RECORD day : 1 .. 31; month : 1 .. 12; year : 1900 .. 2100; END; Account = RECORD name : string ; street : string ; city : string ;
custno : 1 .. 9999 ; custtype : status ; oldbalance : real ; newbalance : real ; payment : real ; paydate : date ; END ;
VAR Customer : ARRAY [1 .. 10] OF account ; i, n : 1 .. 10 ;
PROCEDURE Readinput ;
{Ðọc số liệu nhập cho mỗi bản ghi} VAR space : char ;
BEGIN
FOR i := 1 TO n DO
WITH customer[i] DO BEGIN
Writeln ('Khách hàng số :' , i:3) ;
Write(' Tên khách hàng : ');Readln(name) ;
Write (' Số nhà và tên đường : ') ;Readln (street) ; Write ( 'Tên thành phố :' ) ;Readln (city) ;
Write (' Số tài khoản : ') ;Readln (custno) ; Write ( 'Cân nợ trước : ') ;Readln (oldbalance) ; Write ( 'Chi trả hiện hành : ') ;Readln (payment) ; Write ( 'Ngày trả (dd mm yyyy) : ') ;
WITH paydate DO
Readln(day,month, year) ; END; {readinput}
PROCEDURE Processdata ;
{Xác định tình trạng trả nợ và tính cân nợ mới cho mỗi bản ghi} BEGIN
FOR i := 1 TO n DO WITH customer[i] DO
BEGIN
IF (payment > 0) AND (payment < 0.1* oldbalance) THEN custtype := overdue ;
IF (oldbalance > 0) AND (payment = 0) THEN custtype := delinquent ; newbalance := oldbalance - payment END ;
END ; {processdata} PROCEDURE Writeoutput;
{ In ra các thông tin hiện hành cho mỗi bản ghi } BEGIN
FOR i := 1 TO n DO
WITH customer[i] DO BEGIN
Writeln ;
Write(' Tên khách hàng :' , name) ; Writeln(' Số tài khoản : ' , custno) ;
Writeln( 'Số nhà và tên đường :' , street) ; Writeln(' Tên thành phố :' , city) ;
Writeln ;
Write(' Tồn nợ cũ :' , oldbalance :7:2) ; Write(' Chi trả hiện hành :' , payment :7:2) ; Writeln(' Cân đối mới :' , newbalance :7:2) ; Writeln ;
Write(' Tình trạng tài khoản : ' ) ; CASE custtype OF
current : writeln(‘ CURRENT ’) ;
overdue : writeln(‘ OVERDUE ‘) ; delinquent : writeln(‘ DELINQUENT ‘) ; END ;
Writeln; END ;
END ; {Writeoutput}
BEGIN {Thân chương trình chính}
Writeln ;
Writeln(' Có bao nhiêu khách hàng ? ') ; Readln (n) ;
Readinput ; Processdata ; Writeoutput ; END.
Giả sử chúng ta có số liệu 4 khách hàng nào đó. Nhập dữ liệu như bên dưới, lưu ý là các chữ số có gạch dưới là của người sử dụng nhập vào máy tính.
HỆ THỐNG HÓA ÐƠN KHÁCH HÀNG Có bao nhiêu khách hàng ? 4
Khách hàng số 1
Tên khách hàng : Ngô Hoàng Trọng Số nhà và tên đường : 123 Trần Hưng Ðạo Tên thành phố : Cần thơ Tài khoản số : 4208 Cân nợ trước : 247.88 Chi trả hiện hành : 25.00 Ngày trả (dd mm yyyy) : 14 6 1993 Khách hàng số 2
Tên khách hàng : Huỳnh Văn Tuấn Số nhà và tên đường : 151/53 Lộ Tầm vu Tên thành phố : Cần thơ Tài khoản số : 2219 Cân nợ trước : 135.00 Chi trả hiện hành : 135.00 Ngày trả (dd mm yyyy) : 10 8 1993 Khách hàng số 3 Tên khách hàng : Lê Tấn Sĩ
Số nhà và tên đường : 71 Phan Ðình Phùng Tên thành phố : Long xuyên
Tài khoản số : 8452 Cân nợ trước : 387.42
Chi trả hiện hành : 35.00
Ngày trả (dd mm yyyy) : 4 7 1993 Khách hàng số 4
Tên khách hàng : Tạ Quang Trinh Số nhà và tên đường : 82 Mậu thân Tên thành phố : Rạch giá
Tài khoản số : 711 Cân nợ trước : 260.00 Chi trả hiện hành : 0
Ngày trả (dd mm yyyy) : 27 11 1993
Chương trình khi chạy sẽ in ra kết quả tương ứng với số liệu nhập trên như sau: Tên khách hàng : Ngô Hoàng Trọng Số tài khoản : 4208
Số nhà và tên đường : 123 Trần Hưng Ðạo Tên thành phố : Cần thơ
Tồn nợ cũ : 247.88Chi trả hiện hành : 25.00 Cân đối mới : 222.88 Tình trạng tài khoản : CURRENT
Tên khách hàng : Huỳnh Văn Tuấn Số tài khoản : 2219
Số nhà và tên đường : 151/53 Lộ Tầm vu Tên thành phố : Cần thơ
Tồn nợ cũ : 135.00Chi trả hiện hành : 135.00 Cân đối mới : 0.00 Tình trạng tài khoản : CURRENT
Tên khách hàng : Lê Tấn Sĩ Số tài khoản : 8452
Số nhà và tên đường : 71 Phan Ðình Phùng Tên thành phố : Long xuyên
Tồn nợ cũ : 387.42chi trả hiện hành : 35.00 Cân đối mới : 352.42 Tình trạng tài khoản : OVERDUE
Tên khách hàng : Tạ Quang Trinh Số tài khoản : 711 Số nhà và tên đường : 82 Mậu thân
Tên thành phố :Rạch giá
Tồn nợ cũ : 260.00 Chi trả hiện hành : 0.00 Cân đối mới : 260.00 Tình trạng tài khoản : DELINQUENT
Sinh viên có thể thử lại và tự cải tiến chương trình này như một bài tập