IV/ KIỂU TẬP TIN (FILE)
2. Tập tin định kiểu
Tập tin định kiểu là tập tin mà các phần tử (mẫu tin) của nó có cùng độ dài.
Kiểu tập tin có thể là kiểu cơ sở như ký tự, nguyên, thực,... hoặc kiểu có cấu trúc như mảng, bản ghi,...
a) Cách khai báo
+ Khai báo gián tiếp
TYPE
<Kiểu File> = FILE OF <Kiểu phần tử> ; VAR
<Biến File> : <Kiểu File> ;
Ví dụ 4.1:
TYPE {Ðịnh nghĩa các kiểu tập tin} FileNguyen = FILE OF integer ;
FileReal = FILE OF real ;
NhanSu = RECORD HoTen : string[25] ; Tuoi : Byte ; Dchi : string[35] ; Luong : real ; END ;
FileNhanSu = FILE OF NhanSu ; VAR
FN : FileNguyen ; FR : FileReal ; FC : FileKyTu ; FP : FileNhanSu ;
+ Khai báo trực tiếp
VAR
<Biến File> : FILE OF <Kiểu phần tử> ;
Ví dụ 4.2: TYPE NhanSu = RECORD HoTen : string[25] ; Tuoi : Byte ; Dchi : string[35] ; Luong : real ; END ; VAR FN : FILE OF integer ; FR : FILE OF real ; FC : FILE OF char; FP : FILE OF NhanSu ; Trong các ví dụ trên, ta có:
- Biến tập tin FN là một tập tin kiểu nguyên với mỗi phần tử của chúng là một số nguyên và có độ dài là 2 bytes.
- Biến tập tin FR là một tập tin kiểu thực với mỗi phần tử của chúng là một số thực và có độ dài là 6 bytes.
- Biến tập tin FC là một tập tin kiểu ký tự với mỗi phần tử của chúng là một ký tự và có độ dài là 1 byte.
- Biến tập tin FP là một tập tin kiểu bản ghi với mỗi phần tử của chúng là một bản ghi và có độ dài là 67 bytes.
b) Truy xuất các phần tử của tập tin
Tổng quát, có 2 loại tập tin có thể tạo ra và truy xuất bởi một chương trình máy tính: tập tin thường xuyên (permanent file) và tập tin tạm thời (temporary file). Các tập tin thường xuyên được trữ trong các thiết bị bộ nhớ phụ trợ và do vậy được duy trì sau khi chương trình hoàn tất việc thực hiện. Các phần tử của tập tin thường xuyên( là các thành phần tập tin riêng rẽ) có thể được truy xuất/ cải biên từ chương trình tạo ra nó hoặc từ một số chương trình khác bất kỳ lúc nào. Trong Pascal, tập tin thường xuyên được xem như các tập tin ngoại trú (external files).
Mặt khác, các tập tin tạm thời được trữ trong bộ nhớ chính của máy tính. Tập tin tạm thời sẽ bị mất đi khi chương trình đã được thi hành xong. Như vậy, tập tin tạm thời ít hữu dụng hơn tập tin thường xuyên. Trong Pascal, tập tin tạm thời được xem như các tập tin nội trú (internal file).
Tất cả các tập tin, dù là thường xuyên hay tạm thời, đều có thể tổ chức thành một trong 2 cấu trúc khác nhau: tuần tự (sequential) hoặc ngẫu nhiên (random). Muốn truy xuất các phần tử của tập tin ta phải có một biến đệm (tampon variable) gọi là con trỏ của tập tin. Con trỏ có thể di chuyển trong toàn bộ tập tin. Trong một tập tin có cấu trúc tuần tự, tất cả các phần tử trong tập tin được lưu trữ một cách tuần tự, phần tử này theo sau phần tử kia. Ðể có thể truy xuất một phần tử đặc thù nào đó thì cần thiết phải di chuyển con trỏ đi từ điểm khởi đầu và tìm qua tập tin toàn bộ cho đến khi phần tử mong muốn được tìm ra. Kiểu truy xuất này rất chậm nếu ta gặp một tập tin khá lớn. Tập tin tuần tự tương đối dễ tạo ra, tuy nhiên, chúng chỉ là một loại tập tin thích hợp có thể sử dụng với một kiểu phương tiện lưu trữ nào đó (ví dụ như băng từ).
Trong một tập tin truy xuất ngẫu nhiên, bất kỳ phần tử nào cũng có thể truy xuất trực tiếp, mà không phải qua tiến trình qua tập tin toàn bộ từ điểm khởi đầu. (Tập tin ngẫu nhiên cũng được biết như các tập tin truy xuất trực tiếp - direct access file, thực tế nó chỉ là cái tên mô tả). Các tập tin kiểu như vậy thì truy xuất nhanh hơn các tập tin tuần tự mặc dù nó khó tạo ra và duy trì.
Pascal chuẩn (ANSI Pascal) chỉ xác lập loại tập tin tuần tự, trong khi một số ngôn ngữ thực hành khác, bao gồm cả Turbo Pascal có hỗ trợ việc dùng tập tin truy cập trực tiếp.
c) Các thủ tục và hàm trên tập tin
Gọi:
- FileVar là 1 biến tập tin và ,
- FileName là một biểu thức kiểu string.
Truy xuất một biến tập tin, cần thông qua các thủ tục và hàm sau: * Thủ tục ASSIGN(FileVar, FileName)
Lệnh này dùng để gán tên tập tin FileName cho 1 biến file là FileVar. Filename là tên của một tập tin, do vậy FileName cần tuân theo cách đặt tên của MS-DOS.
Ví dụ 4.3:
VAR Fnguyen : FILE OF integer ; BEGIN
ASSIGN(Fnguyen, ‘SONGUYEN.DAT ’) ;
{ Gán tập tin có tên là SONGUYEN cho biến Fnguyen } REWRITE(FileVar){Cậu lệnh sẽ giải thích ở phần kế}
...
* Thủ tục REWRITE (FileVar)
Lệnh này mở một tập tin mới trên đĩa từ có tên là FileName (đã khai báo trong thủ tục ASSIGN). Nếu FileName đã có sẵn trong đĩa thì nội dung của nó bị xóa và con trỏ đặt ở đầu tập tin. Trong cả 2 trường hợp, sau lệnh này trong tập tin đều rỗng và thông tin mới sẽ được đưa vào nhờ thủ tục WRITE sẽ trình bày ở phần sau.
* Thủ tục RESET(FileVar)
Mở tập tin có sẵn trong đĩa từ có tên là FileName đã khai báo trong lệnh ASSIGN, con trỏ tập tin đặt ở đầu tập tin, dùng lệnh READ để đọc thông tin (sẽ đề cập ở phần sau).
* Thủ tục WRITE(FileVar, x1, x2, ..., xn)
Lệnh này ghi lần lượt các biến x1, x2, ..., xn, là các biến thuộc kiểu phần tử của biến File, vào tập tin có tên là FileName (trong lệnh ASSIGN) vào đĩa từ theo ví trí tuần tự của con trỏ đang đứng.
Lệnh này đọc tuần tự các giá trị x1, x2, ..., xn tại vị trí con trỏ đang đứng rồi gán vào các biến tương ứng cùng kiểu.
Chú ý: Sau khi đọc hoặc ghi một phần tử, thì con trỏ tập tin sẽ di chuyển xuống vị trí phần tử tiếp theo.
Ví dụ 4.4:
Program Write_Read;
Var Tepnguyen: File Of integer; i: integer; Begin Assign(tepnguyen,’U:\SONGUYEN.DAT ’); Rewrite(Tepnguyen); For i :=1 to 4 do write(tepnguyen,i); begin Read(tepnguyen,i); write(‘i= ‘,i:4); end; Close(Tepnguyen); Readln(tepnguyen); End. * Thủ tục SEEK(FileVar, n)
Lệnh này chỉ thị con trỏ tập tin chuyển đến vị trí phần tử thứ n của tập tin. Trong đó, n là số nguyên chỉ vị trí các phần tử trong tập tin, phần tử thứ nhất có số thứ tự là 0.
* Hàm FILEPOS(FileVar):integer
Cho kết quả là một số nguyên chỉ vị trí hiện tại của con trỏ tập tin.
* Hàm FILESIZE (FileVar):integer
Cho kết quả là một số nguyên chỉ số lượng phần tử có trong tập tin. FileSize nhận giá trị 0 khi tập tin rỗng, không có phần tử nào. Ðể đưa con trỏ về cuối tập tin, ta dùng thủ thuật:
SEEK(FileVar, FileSize(FileVar)) ;
* Thủ tục FLUSH(FileVar)
Cho phép đẩy thông tin nằm trong vùng nhớ đệm vào ra của tập tin ra đĩa từ.
* Thủ tục CLOSE(FileVar)
Ðóng tập tin lại và cập nhật thư mục để phản ánh tình trạng mới của tập tin.
* Thủ tục ERASE(FileVar)
Xóa tập tin trên đĩa. Chú ý tập tin phải có thủ tục CLOSE trước khi xóa.
* Thủ tục RENAME(FileVar, NewName)
Ðổi tên tập tin FileName thành NewName. NewName phải có qui cách của một tên tập tin.
* Hàm EOF(FileVar):Boolean
Hàm EOF (End-Of-File) này dùng để kiểm tra tình trạng hết tập tin. Nếu con trỏ ở cuối file thì nó sẽ cho kết quả là TRUE, ngược lại con trỏ ở bất kỳ nơi khác không phải ở cuối tập tin là FALSE.
Mỗi ô là một phần tử của tập tin. Nếu con trỏ nằm ở vị trí cuối, EOF sẽ là TRUE.
d) Kiểm tra các tập tin khi mở
Khi làm việc trên tập tin, ta có thể gặp một số rắc rối như:
- Khi dùng thủ tục RESET để mở một file nào đó thì ta có thể không rõ liệu tập tin đã có chưa? Nếu chưa thì chương trình sẽ báo File not found và ngưng thực hiện tiếp các lệnh về sau.
- Khi dùng thủ tục REWRITE để mở một file mới thì có thể tập tin cũ bị xóa toàn bộ nếu nó đã có trên đĩa.
- Ngoài ra ta cũng cần biết đĩa còn đủ chỗ để chứa thêm thông tin mới của tập tin không?
Turbo Pascal có cung cấp các chỉ dẫn giúp tránh một số vấn đề trên qua chương trình dịch đóng/mở việc kiểm tra sai lỗi trong quá trình vào/ra tập tin:
{$I+} : Mở kiểm tra, khi có lỗi vào/ra thì chương trình sẽ báo lỗi và ngưng thực hiện. Ðây là chế độ ngầm định (by default), nghĩa là chương trình dịch luôn luôn thực hiện chế độ nếu không được báo rõ.
{$I-} : Tắt chế độ kiểm tra lỗi vào/ra, khi có lỗi chương trình không ngừng lại nhưng sẽ treo các thủ tục vào/ra khác cho đến khi có lời gọi hàm kết quả IOResult.
Hàm IOResult: Word là hàm kiểm tra lỗi vào/ra và cho kết quả là một số nguyên kiểu word. Hàm này thường dùng để kiểm tra xem tập tin có tồn tại trên đĩa hay không? Khi có lời gọi hàm IOResult thì điều kiện sai bị xoá bỏ và các thủ tục vào ra vẫn tiếp tục. Nhiệm vụ sửa lỗi sẽ thuộc về người lập trình. Nếu IOResul = 0 thì file đã có trên đĩa, ngược lại thì file không có.
Ví dụ 4.5: Một chương trình mẫu để kiểm tra file khi đọc. PROGRAM KiemtraIOFile ;
VAR Loi : Boolean ; BEGIN
Repeat
Write(Tên tập tin : ) ; Readln(Filename) ; Assign(F, FileName) ;
{$I-} {giao việc kiểm lỗi cho người dùng} RESET(F) ;
Loi := IOResult = 0 ; {$I+}
IF NOT Loi THEN Write( Không mở tập tin này được ! ) ; Until Loi ;
END.
Ví dụ 4.6: { Về tập tin có kiểu bản ghi }
Trở lại với ví dụ 8.45 về Hệ thống Hóa đơn Khách hàng. Các bản ghi cho mỗi khách hàng cần phải được cập nhật (update) từ file cũ và ghi lại thành một file mới. Tùy theo điều kiện trả nợ của mỗi khách hàng mà ta sẽ thay đổi tình trạng nợ nần của họ. Các bước tiến hành cho việc lập chương trình như sau:
(1) Ðọc các bản ghi từ tập tin cũ (Old file)
(2) Thể hiện tên khách hàng, số tài khoản, chi trả kỳ trước... ra màn hình. Cho dấu nhắc con trỏ ở vị trí Chi trả hiện hành (Current Payment).
(4) Xác định cân đối nợ mới (New balance) và tình trạng tài khoản hiện hành (Current account status) và thể hiện ra màn hình.
(5) Ghi bản ghi thành tập tin dữ liệu mới (New data file) bất chấp có hay không việc cập nhật bản ghi.
(6) Tiếp tục tiến trình này cho đến khi tất cả các bản ghi trong tập tin cũ đã được xử lý xong.
Dưới đây là chương trình Pascal theo Byron S. Gottfried:
PROGRAM Billing; {Cập nhật bản ghi khách hàng và tạo ra một tập tin mới }
TYPE date = RECORD day : 1 .. 31; month : 1 .. 12 ; year : 1900 .. 2100; END; account = RECORD name : string ; cusno : 1 .. 9999 ; oldbalance : real ; newbalance : real ; payment : real ; paydate : date ; END ;
datafile = FILE OF account ; VAR newfile, oldfile : datafile ;
newaccount, oldaccount : account ; count : 1 .. 9999 ;
space : char ;
PROCEDURE update (VAR custaccount : account) ; {Cập nhật bản ghi}
BEGIN
WITH custaccount DO BEGIN
Writeln (' Khách hàng số :' , count) ; Write(' Tên khách hàng :' , name); Writeln (' Số tài khoản :' , custno:4) ;
Write (' Cân nợ trước :' , oldbalance :7:2) ; payment := 0
Write (' Chi trả hiện hành : ') ; Readln (payment) ; newbalance := oldbalance - payment;
IF payment > 0 THEN BEGIN
Write (' Ngày trả (dd mm yyyy) : ') ; WITH paydate DO
Readln (day, space, month, space, year) ; END;
Writeln(' Cân nợ mới :' , newbalance :1:2) ; If payment > 0 then oldbalance := newbalance ; Write(' Tình trạng tài khoản : ') ;
IF payment >= 0.1* oldbalance THEN Writeln( ‘CURRENT’)
ELSE IF payment > 0 THEN Writeln ( ‘OVERDUE’) ELSE Writeln ( ‘DELINQUENT”) ;
END; {WITH custaccount} END;
BEGIN {Thân chương trình chính}
ASSIGN(oldfile, ‘old.dat’) ; ASSIGN(newfile, ‘new.dat’) ; RESET(oldfile) ; REWRITE(newfile) ; count := 1 ; space := ‘ ‘ ;
Writeln(' HỆ THỐNG HÓA ÐƠN KHÁCH HÀNG - CẬP NHẬT FILE '); Writeln;
BEGIN {cập nhật tập tin cũ}
WHILE NOT eof(oldfile) DO BEGIN
Read(oldfile, oldaccount) ; update(oldaccount) ;
write(newfile, newaccount) ; count := count + 1
END; {WHILE NOT eof(oldfile) }
END; {file update}
Close(oldfile) ; close(newfile) ;
END.
Chú ý chương trình đọc tập tin cũ với tên ngoại trú là old.dat và tạo ra một tập tin mới với tên ngoại trú là new.dat.
Giả sử chúng ta có số liệu 4 khách hàng như ví dụ 8.43ï. Kết quả hội thoại giao tiếp bởi tiến trình cập nhật được thể hiện như sau. 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ẬP NHẬT FILE Khách hàng số 1
Tên khách hàng : Ngô Hoàng Trọng 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 Cân nợ mới : 222.88
Tình trạng tài khoản : CURRENT Khách hàng số 2
Tên khách hàng : Huỳnh Văn Tuấn 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 Cân nợ mới : 0.00
Tình trạng tài khoản : CURRENT Khách hàng số 3
Tên khách hàng : Lê Tấn Sĩ 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 Cân nợ mới : 352.42
Khách hàng số 4
Tên khách hàng : Tạ Quang Trinh Tài khoản số : 711
Cân nợ trước : 260.00 Chi trả hiện hành : 0 Cân nợ mới : 260.00
Tình trạng tài khoản : DELINQUENT
Sau khi chương trình chạy xong, tập tin old.dat sẽ bị xoá hoặc lưu trữ và tập tin new.dat sẽ được đổi tên thành old.dat. Việc này nhằm để tạo ra một tập tin mới sẽ được cập nhật trong tương lai. Trong ví dụ này chúng ta giả thiết rằng tập tin old.dat đã có sẵn, do vậy chúng ta không cần thảo luận thêm tập tin này phải tạo ra như thế nào lúc ban đầu. Sinh viên có thể xem đây là một bài tập.