Giáo trình Lập trình cơ bản C - Bài 21: Quản lý tập tin

Bài 21  
Quản tập tin  
Mục tiêu:  
Kết thúc bài học này, bạn thể:  
Giải thích khái niệm luồng (streams) và tập tin (files)  
Thảo luận các luồng văn bản và các luồng nhị phân  
Giải thích các hàm xử tập tin  
Giải thích con trỏ tập tin  
Thảo luận con trỏ kích hoạt hiện hành  
Giải thích các đối số tdòng nhắc lệnh (command-line).  
Giới thiệu  
Hầu hết các chương trình đều yêu cầu đọc và ghi dữ liệu vào các hệ thống lưu trữ trên đĩa. Các chương  
trình xử văn bản cần lưu các tập tin văn bản, chương trình xử bảng tính cần lưu nội dung của các  
ô, chương trình cơ sỡ dữ liệu cần lưu các mẫu tin. Bài này sẽ khám phá các tiện ích trong C dành cho  
các thao tác nhập/xuất (I/O) đĩa hệ thống.  
Ngôn ngữ C không chứa bất kỳ câu lệnh nhập/xuất nào một cách tường minh. Tất cả các thao tác  
nhập/xuất đều thực hiện thông qua các hàm thư viện chuẩn của C. Tiếp cận này làm cho hệ thống quản  
tập tin của C rất mạnh uyển chuyển. Nhập/xuất trong C là tuyệt vời dữ liệu thể truyền ở  
dạng nhphân hay ở dạng văn bản mà con người thể đọc được. Điều này làm cho việc tạo tập tin để  
đáp ứng mọi nhu cầu một cách dễ dàng.  
Việc hiểu sự khác biệt giữa stream và tập tin là rất quan trọng. Hệ thống nhập/xuất của C cung cấp  
cho người dùng một giao diện độc lập với thiết bị thật sự đang truy cập. Giao diện này không phải là  
một tập tin thật sự mà là một sự biễu diễn trừu tượng của thiết bị. Giao diện trừu tượng này được gọi là  
một stream và thiết bị thật sự được gọi tập tin.  
21.1  
File Streams  
Hệ thống tập tin của C làm việc được với rất nhiều thiết bị khác nhau bao gồm máy in, ổ đĩa, ổ băng từ  
và các thiết bị đầu cuối. Mặc tất cả các thiết bị đều khác nhau, nhưng hệ thống tập tin có vùng đệm  
sẽ chuyển mỗi thiết bị về một thiết bị logic gọi một stream. Vì mọi streams hoạt động tương tự, nên  
việc quản lý các thiết bị rất dễ dàng. Có hai loại streams – văn bản (text) và nhị phân (binary).  
21.1.1 Streams văn bản  
Một streams văn bản một chuỗi các ký tự. Các streams văn bản thể được tổ chức thành các  
dòng, mỗi dòng kết thúc bằng một tự sang dòng mới. Tuy nhiên, ký tự sang dòng mới là tùy chọn  
trong dòng cuối được quyết định khi cài đặt. Hầu hết các trình biên dịch C không kết thúc stream  
văn bản với tự sang dòng mới. Trong một stream văn bản, thể xảy ra một vài sự chuyển đổi ký  
tự khi môi trường yêu cầu. Chẳng hạn như, tự sang dòng mới thể được chuyển thành một cặp ký  
tự về đầu dòng/nhảy đến dòng kế. vậy, mối quan hệ giữa các ký tự được ghi (hay đọc) những ký  
tự ở thiết bị ngoại vi có thể không phải mối quan hệ một-một. cũng sự chuyển đổi thể xảy  
ra này, số lượng tự được ghi (hay đọc) thể không giống như số lượng tự nhìn thấy ở thiết bị  
ngoại vi.  
Quản tập tin  
285  
21.1.2  
Streams nhị phân  
Một streams nhị phân một chuỗi các byte với sự tương ứng một-một với thiết bị ngoại vi, nghĩa là,  
không có sự chuyển đổi tự. Cũng vậy, số lượng byte đọc (hay ghi) cũng sẽ giống như số lượng  
byte ở thiết bị ngoại vi. Các stream nhị phân là các chuỗi byte thuần túy, mà không có bất kỳ hiệu  
nào được dùng để chỉ ra điểm kết thúc của tập tin hay kết thúc của record. Kết thúc của tập tin được  
xác định bằng độ lớn của tập tin.  
21.2  
Các hàm về tập tin và structure FILE  
Một tập tin có thể tham chiếu đến bất cứ cái gì: từ một tập tin trên đĩa đến một thiết bị đầu cuối hay  
một máy in. Tuy nhiên, tất cả các tập tin đều không có cùng khả năng. dụ như, một tập tin trên đĩa  
thể hổ trợ truy cập ngẩu nhiên trong khi một bàn phím thì không. Một tập tin sẽ kết hợp với một  
stream bằng cách thực hiện thao tác mở. Tương tự, sẽ thôi kết hợp với một stream bằng thao tác  
đóng. Khi một chương trình kết thúc bình thường, tất cả các tập tin đều tự động đóng. Tuy nhiên, khi  
một chương trình bị treo hoặc kết thúc bất thường, các tập tin vẫn còn mở.  
21.2.1  
Các hàm cơ bản về tập tin  
Một hệ thống quản tập tin theo chuẩn ANSI bao gồm một số hàm liên quan với nhau. Các hàm  
thông dụng nhất được liệt kê trong bảng 21.1.  
Name  
fopen()  
fclose()  
fputc()  
fgetc()  
fread()  
fwrite()  
fseek()  
Function  
Mở một tập tin  
Đóng một tập tin  
Ghi một tự vào một tập tin  
Đọc một tự từ một tập tin  
Đọc từ một tập tin vào một vùng đệm  
Ghi từ một vùng đệm vào tập tin  
Tìm một vị trí nào đó trong tập tin  
fprintf() Hoạt động giống như printf(), nhưng trên một tập tin  
fscanf()  
feof()  
ferror()  
Hoạt động giống như scanf(), nhưng trên một tập tin  
Trả về true nếu đã đến cuối tập tin (end-of-file)  
Trả về true nếu xảy ra một lỗi  
rewind() Đặt lại con trỏ định vị trí (position locator) bên trong tập tin về đầu tập tin  
remove() Xóa một tập tin  
fflush()  
Ghi dữ liệu từ một vùng đệm bên trong vào một tập tin xác định  
Bảng 21.1: Các hàm cơ bản về tập tin  
Các hàm trên chứa trong tập tin header stdio.h. Tập tin header này phải được bao gồm vào chương  
trình có sử dụng các hàm này. Hầu hết các hàm này tương tự như các hàm nhập/xuất từ thiết bị nhập  
xuất chuẩn. Tập tin header stdio.h còn định nghĩa một số macro sử dụng trong quá trình xử tập tin.  
dụ như, macro EOF được định nghĩa là -1, chứa giá trị trả về khi một hàm cố đọc tiếp khi đã đến  
cuối tập tin.  
21.2.2  
Con trỏ tập tin  
Một con trỏ tập tin (file pointer) rất cần thiết cho việc đọc và ghi các tập tin. Nó là một con trỏ đến  
một structure chứa thông tin về tập tin. Thông tin bao gồm: tên tập tin, vị trí hiện tại của tập tin, tập tin  
đang được đọc hay ghi, có bất kỳ lỗi nào xuất hiện hay đã đến cuối tập tin. Người dùng không cần  
thiết phải biết chi tiết, vì các định nghĩa lấy từ studio.h có bao gồm một khai báo structure tên là  
FILE. Câu lệnh khai báo duy nhất cần thiết cho một con trỏ tập tin là:  
286  
Lập trình cơ bản C  
FILE *fp;  
Khai báo này cho biết fp một con trỏ trỏ đến một FILE.  
21.3  
Các tập tin văn bản  
nhiều hàm khác nhau để quản tập tin văn bản. Chúng ta sẽ thảo luận trong các đoạn bên dưới:  
21.3.1 Mở một tập tin văn bản  
Hàm fopen() mở một stream để sử dụng và liên kết một tập tin với stream đó. Con trỏ kết hợp với tập  
tin được trả về từ hàm fopen(). Trong hầu hết các trường hợp, tập tin đang mở một tập tin trên đĩa.  
Nguyên mẫu của hàm fopen() là:  
FILE *fopen(const char *filename, const char *mode);  
trong đó filename một con trỏ trỏ đến chuỗi tự chứa một tên tập tin hợp lệ cũng thể chứa  
cả phần tả đường dẫn. Chuỗi được trỏ đến bởi con trỏ mode xác định cách thức tập tin được mở.  
Bảng 21.2 liệt kê các chế độ hợp lệ một tập tin có thể mở.  
Chế độ  
Ý nghĩa  
r
w
a
Mở một tập tin văn bản để đọc  
Tạo một tập tin văn bản để ghi  
Nối vào một tập tin văn bản  
r+  
w+  
a+f  
Mở một tập tin văn bản để đọc/ghi  
Tạo một tập tin văn bản để đọc/ghi  
Nối hoặc tạo một tập tin văn bản để đọc/ghi  
Bảng 21.2: Các chế độ mở tập tin văn bản.  
Bảng 21.2 cho thấy các tập tin có thể được mở ở nhiều chế độ khác nhau. Một con trỏ null được trả về  
nếu xảy ra lỗi khi hàm fopen() mở tập tin. Lưu ý rằng các chuỗi như “a+f” có thể được biễu diễn như  
“af+”.  
Nếu phải mở một tập tin xyz để ghi, câu lệnh sẽ là:  
FILE *fp;  
fp = fopen ("xyz", "w");  
Tuy nhiên, một tập tin nói chung được mở bằng cách sử dụng một tập hợp các câu lệnh tương tự như  
sau:  
FILE *fp;  
if ((fp = fopen ("xyz", "w")) == NULL)  
{
printf("Cannot open file");  
exit (1);  
}
Macro NULL được định nghĩa trong stdio.h là ‘\0’. Nếu sử dụng phương pháp trên để mở một tập tin,  
thì hàm fopen() sẽ phát hiện ra lỗi nếu có, chẳng hạn như đĩa đang ở chế độ cấm ghi (write-protected)  
hay đĩa đầy, trước khi bắt đầu ghi đĩa.  
Quản tập tin  
287  
Nếu một tập tin được mở để ghi, bất kỳ một tập tin nào có cùng tên và đang mở sẽ bị viết chồng lên.  
Vì khi một tập tin được mở ở chế độ ghi, thì một tập tin mới được tạo ra. Nếu muốn nối thêm các mẫu  
tin vào tập tin đã có, thì nó phải được mở với chế độ “a”. Nếu một tập tin được mở ở chế độ đọc và nó  
không tồn tại, hàm sẽ trả về lỗi. Nếu một tập tin được mở để đọc/ghi, sẽ không bị xóa nếu đã tồn  
tại. Tuy nhiên, nếu nó không tồn tại, thì nó sẽ được tạo ra.  
Theo chuẩn ANSI, tám tập tin có thể được mở tại một thời điểm. Tuy vậy, hầu hết các trình biên dịch  
C và môi trường đều cho phép mở nhiều hơn tám tập tin.  
21.3.2  
Đóng một tập tin văn bản  
số lượng tập tin có thể mở tại một thời điểm bị giới hạn, việc đóng một tập tin khi không còn sử  
dụng một điều quan trọng. Thao tác này sẽ giải phóng tài nguyên và làm giảm nguy cơ vượt quá  
giới hạn đã định. Đóng một stream cũng sẽ làm sạch và chép vùng đệm kết hợp của nó ra ngoài (một  
thao tác quan trọng để tránh mất dữ liệu) khi ghi ra đĩa. Hàm fclose() đóng một stream đã được mở  
bằng hàm fopen(). Nó ghi bất kỳ dữ liệu nào còn lại trong vùng đệm của đĩa vào tập tin. Nguyên mẫu  
của hàm fclose() là:  
int fclose(FILE *fp);  
trong đó fp là một con trỏ tập tin. Hàm fclose() trả về 0 nếu đóng thành công. Bất kỳ giá trị trả về nào  
khác 0 đều cho thấy lỗi xảy ra. Hàm fclose() sẽ thất bại nếu đĩa đã sớm được gỡ ra khỏi ổ đĩa hoặc  
đĩa bị đầy.  
Một hàm khác dùng để đóng stream là hàm fcloseall(). Hàm này hữu dụng khi phải đóng cùng một lúc  
nhiều stream đang mở. sẽ đóng tất cả các stream và trả về sstream đã đóng hoặc EOF nếu có phát  
hiện lỗi. Nó có thể được sử dụng theo cách như sau:  
fcl = fcloseall();  
if (fcl == EOF)  
printf("Error closing files");  
else  
printf("%d file(s) closed", fcl);  
21.3.3 Ghi một tự  
Streams thể được ghi vào tập tin theo từng tự một hoặc theo từng chuỗi. Trước hết chúng ta hãy  
thảo luận về cách ghi các ký tự vào tập tin. Hàm fputc() được sử dụng để ghi các ký tự vào tập tin đã  
được mở trước đó bằng hàm fopen(). Nguyên mẫu của hàm này như sau:  
int fputc(int ch, FILE *fp);  
trong đó fp một con trỏ tập tin trả về bởi hàm fopen() ch là ký tự cần ghi. Mặc ch được khai  
báo là kiểu int, nhưng được hàm fputc() chuyển đổi thành kiểu unsigned char. Hàm fputc() ghi  
một tự vào stream đã định tại vị trí hiện hành của con trỏ định vtrí bên trong tập tin và sau đó tăng  
con trỏ này lên. Nếu fputc() thành công, nó trả về tự đã ghi, ngược lại trả về EOF.  
21.3.4 Đọc một tự  
Hàm fgetc() được dùng để đọc các ký tự từ một tập tin đã được mở ở chế độ đọc, sử dụng hàm  
fopen(). Nguyên mẫu của hàm là:  
int fgetc (FILE *fp);  
trong đó fp một con trỏ tập tin kiểu FILE trả về bởi hàm fopen(). Hàm fgetc() trả về tự kế tiếp  
của vị trí hiện hành trong stream input, và tăng con trỏ định vtrí bên trong tập tin lên. Ký tự đọc được  
288  
Lập trình cơ bản C  
một tự kiểu unsigned char được chuyển thành kiểu int. Nếu đã đến cuối tập tin, fgetc() trả về  
EOF.  
Để đọc một tập tin văn bản từ đầu cho đến cuối, câu lệnh sẽ là:  
do  
{
ch = fgetc(fp);  
}
while (ch != EOF);  
Chương trình sau đây nhận các ký tự từ bàn phím và ghi chúng vào một tập tin cho đến khi người  
dùng nhập tự @’. Sau khi người dùng nhập thông tin vào, chương trình sẽ hiển thị nội dung ra  
màn hình.  
dụ 1:  
#include <stdio.h>  
main()  
{
FILE *fp;  
char ch= ' ';  
/* Writing to file JAK */  
if ((fp=fopen("jak", "w"))==NULL)  
{
printf("Cannot open file \n\n");  
exit(1);  
}
clrscr();  
printf("Enter characters (type @ to terminate): \n");  
ch = getche();  
while (ch !='@')  
{
fputc(ch, fp) ;  
ch = getche();  
}
fclose(fp);  
/* Reading from file JAK */  
printf("\n\nDisplaying contents of file JAK\n\n");  
if((fp=fopen("jak", "r"))==NULL)  
{
printf("Cannot open file\n\n");  
exit(1);  
}
do  
{
ch = fgetc (fp);  
putchar(ch) ;  
}
while (ch!=EOF);  
getch();  
fclose(fp);  
}
Quản tập tin  
289  
Một mẫu chạy cho chương trình trên là:  
Enter Characters (type @ to terminate):  
This is the first input to the File JAK@  
Displaying Contents of File JAK  
This is the first input to the File JAK  
21.3.5 Nhập xuất chuỗi  
Ngoài fgetc() và fputc(), C còn hổ trợ các hàm fputs() và fgets() để ghi vào và đọc ra các chuỗi tự  
từ tập tin trên đĩa.  
Nguyên mẫu cho hai hàm này như sau:  
int fputs(const char *str, FILE *fp);  
char *fgets(char *str, int length, FILE *fp);  
Hàm fputs() làm việc giống như hàm fputc(), ngoại trừ là nó viết toàn bộ chuỗi vào stream. Nó trả về  
EOF nếu xảy ra lỗi.  
Hàm fgets() đọc một chuỗi từ stream đã cho cho đến khi đọc được một tự sang dòng mới hoặc sau  
khi đã đọc được length-1 tự. Nếu đọc được một tự sang dòng mới, tự này được xem như là  
một phần của chuỗi (không giống như hàm gets()). Chuỗi kết quả sẽ kết thúc bằng tự null. Hàm trả  
về một con trỏ trỏ đến chuỗi nếu thành công và null nếu xảy ra lỗi.  
21.4  
Các tập tin nhị phân  
Các hàm dùng để xử lý các tập tin nhị phân cũng giống như các hàm sử dụng để quản tập tin văn  
bản. Tuy nhiên, chế độ mở tập tin của hàm fopen() thì khác đi trong trường hợp các tập tin nhị phân.  
21.4.1 Mở một tập tin nhị phân  
Bảng sau đây liệt kê các chế độ khác nhau của hàm fopen() trong trường hợp mở tập tin nhị phân.  
Chế độ  
rb  
wb  
Ý nghĩa  
Mở một tập tin nhị phân để đọc  
Tạo một tập tin nhị phân để ghi  
Nối vào một tập tin nhị phân  
ab  
r+b  
w+b  
a+b  
Mở một tập tin nhị phân để đọc/ghi  
Tạo một tập tin nhị phân để đọc/ghi  
Nối vào một tập tin nhị phân để đọc/ghi  
Bảng 21.3: Các chế độ mở tập tin nhị phân.  
Nếu một tập tin xyz được mở để ghi, câu lệnh sẽ là:  
FILE *fp;  
fp = fopen ("xyz", "wb");  
21.4.2 Đóng một tập tin nhị phân  
290  
Lập trình cơ bản C  
Ngoài tập tin văn bản, hàm fclose() cũng thể được dùng để đóng một tập tin nhị phân. Nguyên mẫu  
của fclose như sau:  
int fclose(FILE *fp);  
trong đó fp là một con trỏ tập tin trỏ đến một tập tin đang mở.  
21.4.3 Ghi một tập tin nhị phân  
Một số ứng dụng liên quan đến việc sử dụng các tập tin dữ liệu để lưu trữ các khối dữ liệu, trong đó  
mỗi khối bao gồm các byte liên tục. Mỗi khối nói chung sẽ biểu diễn một cấu trúc dữ liệu phức tạp  
hoặc một mảng.  
Chẳng hạn như, một tập tin dữ liệu thể bao gồm nhiều cấu trúc có cùng thành phần cấu tạo, hoặc nó  
thể chứa nhiều mảng có cùng kiểu và kích thước. với những ứng dụng như vậy thường đòi hỏi  
đọc toàn bộ khối dữ liệu từ tập tin dữ liệu hoặc ghi toàn bộ khối vào tập tin dữ liệu hơn đọc hay ghi  
các thành phần độc lập (nghĩa là các thành viên của cấu trúc hay các phần tử của mảng) trong mỗi khối  
riêng biệt.  
Hàm fwrite() được dùng để ghi dữ liệu vào tập tin dữ liệu trong những tình huống như vậy. Hàm này  
thể dùng để ghi bất kỳ kiểu dữ liệu nào. Nguyên mẫu của fwrite() là:  
size_t fwrite(const void *buffer, size_t num_bytes, size_t  
count, FILE *fp);  
Kiểu dữ liệu size_t được thêm vào C chuẩn để tăng tính tương thích của chương trình với nhiều hệ  
thống. được định nghĩa trước như một kiểu số nguyên đủ lớn để lưu giữ kết quả của hàm  
sizeof(). Đối với hầu hết các hệ thống, nó có thể được dùng như một số nguyên dương..  
Buffer một con trỏ trỏ đến thông tin sẽ được ghi vào tập tin. Số byte phải đọc hoặc ghi được cho bởi  
num_bytes. Đối số count xác định có bao nhiêu mục (mỗi mục dài num_bytes) được đọc hoặc ghi.  
Cuối cùng, fp một con trỏ tập tin trỏ đến một stream đã được mở trước đó. Các tập tin mở cho  
những thao tác này phải mở ở chế độ nhị phân.  
Hàm này trả về số lượng các đối tượng đã ghi vào tập tin nếu thao tác ghi thành công. Nếu giá trị này  
nhỏ hơn count thì đã xảy ra lỗi. Hàm ferror() (sẽ được thảo luận trong phần tới) thể được dùng để  
xác định lỗi.  
21.4.4 Đọc một tập tin nhị phân  
Hàm fread() thể được dùng để đọc bất kỳ kiểu dữ liệu nào. Nguyên mẫu của hàm là:  
size_t fread(void *buffer, size_t num_bytes, size_t count  
FILE *fp);  
buffer là một con trỏ trỏ đến vùng nhớ sẽ nhận dữ liệu từ tập tin. Số byte phải đọc hoặc ghi được cho  
bởi num_bytes. Đối số count xác định có bao nhiêu mục (mỗi mục dài num_bytes) được đọc hoặc  
ghi. Cuối cùng, fp một con trỏ tập tin trỏ đến một stream đã được mở trước đó. Các tập tin đã mở  
cho những thao tác này phải mở ở chế độ nhị phân.  
Hàm này trả về số lượng các đối tượng đã đọc nếu thao tác đọc thành công. Nó trả về 0 nếu đọc đến  
cuối tập tin hoặc xảy ra lỗi. Hàm feof() và hàm ferror() (sẽ được thảo luận trong phần tới) thể được  
dùng để xác định nguyên nhân.  
Quản tập tin  
291  
Các hàm fread() và fwrite() thường được gọi là các hàm đọc hoặc ghi không định dạng.  
Miễn tập tin được mở cho các thao tác nhị phân, hàm fread() fwrite() thể đọc và ghi bất kỳ  
kiểu thông tin nào. Ví dụ, chương trình sau đây ghi vào và sau đó đọc ngược ra một số kiểu double,  
một số kiểu int và một số kiểu long từ tập tin trên đĩa. Lưu ý rằng sử dụng hàm sizeof() để xác định  
độ dài của mỗi kiểu dữ liệu.  
dụ 2:  
#include <stdio.h>  
main ()  
{
FILE *fp;  
double d = 23.31 ;  
int i = 13;  
long li = 1234567L;  
clrscr();  
if ((fp = fopen ("jak", "wb+")) == NULL )  
{
printf("Cannot open file ");  
exit(1);  
}
fwrite (&d, sizeof(double), 1, fp);  
fwrite (&i, sizeof(int), 1, fp);  
fwrite (&li, sizeof(long), 1,fp);  
fclose (fp);  
if ((fp = fopen ("jak", "rb+")) == NULL )  
{
printf("Cannot open file");  
exit(1);  
}
fread (&d, sizeof(double), 1, fp);  
fread(&i, sizeof(int), 1, fp);  
fread (&li, sizeof(long), 1, fp);  
printf ("%f %d %ld", d, i, li);  
fclose (fp);  
}
Như chương trình này minh họa, thể đọc buffer và thường chỉ một vùng nhớ để giữ một biến.  
Trong chương trình đơn giản trên, giá trị trả về của hàm fread() fwrite() được bỏ qua. Tuy nhiên,  
để lập trình hiệu quả, các giá trị đó nên được kiểm tra xem đã lỗi xảy ra không.  
Một trong những ứng dụng hữu dụng nhất của fread() fwrite() liên quan đến việc đọc và ghi các  
kiểu dữ liệu do người dùng định nghĩa, đặc biệt là các cấu trúc. Ví dụ ta có cấu trúc sau:  
struct struct_type  
{
float balance;  
char name[80];  
} cust;  
292  
Lập trình cơ bản C  
Câu lệnh sau đây ghi nội dung của cust vào tập tin đang được trỏ đến bởi fp.  
fwrite(&cust, sizeof(struct struct_type), 1, fp);  
21.5  
Các hàm xử tập tin  
Các hàm xử tập tin khác được thảo luận trong phần này.  
21.5.1 Hàm feof()  
Khi một tập tin được mở để đọc ở dạng nhị phân, một số nguyên có giá trị tương đương với EOF có  
thể được đọc. Trong trường hợp này, quá trình đọc sẽ cho rằng đã đến cuối tập tin, mặc chưa đến  
cuối tập tin thực sự. Một hàm feof() thể được dùng những trong trường hợp này. Nguyên mẫu của  
hàm là:  
int feof(FILE *fp );  
trả về true nếu đã đến cuối tập tin, nếu không nó trả về false (0). Hàm này được dùng trong khi đọc  
dữ liệu nhị phân.  
Đoạn lệnh sau đây đọc một tập tin nhị phân cho đến cuối tập tin.  
.
.
while (!feof(fp) )  
ch = fgetc(fp);  
.
.
21.5.2 Hàm rewind()  
Hàm rewind() đặt lại con trỏ định vị trí bên trong tập tin về đầu tập tin. Nó lấy con trỏ tập tin làm đối  
số. Cú pháp của rewind() là:  
rewind(fp);  
Chương trình sau mở một tập tin ở chế độ đọc/ghi, sử dụng hàm fputs() với đầu vào là các chuỗi, đưa  
con trỏ quay về đầu tập tin và sau đó hiển thị các chuỗi giống như vậy bằng hàm fgets().  
dụ 3:  
#include <stdio.h>  
main()  
{
FILE *fp;  
char str [80];  
/* Writing to File JAK */  
if ((fp = fopen("jak", "w+")) == NULL)  
{
printf ("Cannot open file \n\n");  
exit(1);  
}
clrscr ();  
do  
Quản tập tin  
293  
{
printf ("Enter a string (CR to quit): \n");  
gets (str);  
if(*str != '\n')  
{
strcat (str, "\n"); /* add a new line */  
fputs (str, fp);  
}
} while (*str != '\n');  
/*Reading from File JAK */  
printf ("\n\n Displaying Contents of File JAK\n\n");  
rewind (fp);  
while (!feof(fp))  
{
fgets (str, 81, fp);  
printf ("\n%s", str);  
}
fclose(fp);  
}
Một mẫu chạy chương trình trên như sau:  
Enter a string (CR to quit):  
This is input line 1  
Enter a string (CR to quit) :  
This is input line 2  
Enter a string (CR to quit):  
This is input line 3  
Enter a string (CR to quit):  
Displaying Contents of File JAK  
This is input line 1  
This is input line 2  
This is input line 3  
21.5.3 Hàm ferror()  
Hàm ferror() xác định liệu một thao tác trên tập tin có sinh ra lỗi hay không. Nguyên mẫu của hàm là:  
int ferror(FILE * fp) ;  
trong đó fp một con trỏ tập tin hợp lệ. trả về true nếu xảy ra một lỗi trong thao tác cuối cùng  
trên tập tin ; ngược lại, trả về false. Vì mỗi thao tác thiết lập lại tình trạng lỗi, nên hàm ferror()  
phải được gọi ngay sau mỗi thao tác; nếu không, lỗi sẽ bị mất.  
Chương trình trước thể được sửa đổi để kiểm tra và cảnh báo về bất kỳ lỗi nào trong khi ghi như  
sau:  
294  
Lập trình cơ bản C  
.
.
do  
{
printf(“ Enter a string (CR to quit): \n");  
gets(str);  
if(*str != '\n')  
{
strcat (str, "\n"); /* add a new line */  
fputs (str, fp);  
}
if(ferror(fp))  
printf("\nERROR in writing\n");  
} while(*str!='\n');  
.
.
21.5.4  
Xóa tập tin  
Hàm remove() xóa một tập tin đã định. Nguyên mẫu của hàm là:  
int remove (char *filename);  
trả về 0 nếu thành công ngược lại trả về một giá trị khác 0.  
dụ, xét đoạn lệnh sau đây:  
.
.
printf ("\nErase file %s (Y/N) ? ", file1);  
ans  
=
getchar ();  
.
.
if(remove(file1))  
{
printf ("\nFile cannot be erased");  
exit(1);  
}
21.5.5  
Làm sạch các stream  
Thông thường, các tập tin xuất chuẩn được trang bị vùng đệm. Điều này có nghĩa kết xuất cho tập  
tin được thu thập trong bộ nhớ và không thật sự hiển thị cho đến khi vùng đệm đầy. Nếu một chương  
trình bị treo hay kết thúc bất thường, một số tự vẫn còn nằm trong vùng đệm. Kết quả chương  
trình có vẻ như kết thúc sớm hơn là nó thật sự đã làm. Hàm fflush() sẽ giải quyết vấn đề này. Như tên  
gọi của nó, nó sẽ làm sạch vùng đệm và chép những gì có trong vùng đệm ra ngoài. Hành động làm  
sạch tùy theo kiểu tập tin. Một tập tin được mở để đọc sẽ có vùng đệm nhập trống, trong khi một tập  
tin được mở để ghi thì vùng đệm xuất của sẽ được ghi vào tập tin.  
Nguyên mẫu của hàm này là:  
int fflush(FILE * fp);  
Quản tập tin  
295  
Hàm fflush() sẽ ghi nội dung của bất kỳ vùng đệm dữ liệu nào vào tập tin kết hợp với fp. Hàm  
fflush(), không có đối số, sẽ làm sạch tất cả các tập tin đang mở để xuất. trả về 0 nếu thành công,  
ngược lại, trả về EOF.  
21.5.6 Các stream chuẩn  
Mỗi khi một chương trình C bắt đầu thực thi dưới DOS, hệ điều hành sẽ tự động mở 5 stream đặc biệt.  
5 stream này là:  
Nhập chuẩn (stdin)  
Xuất chuẩn (stdout)  
Lỗi chuẩn (stderr)  
Máy in chuẩn (stdprn)  
Thiết bị hỗ trợ chuẩn (stdaux)  
Trong đó, stdin, stdout stderr được gán mặc định cho các thiết bị nhập/xuất chuẩn của hệ thống  
trong khi stdprn được gán cho cổng in song song đầu tiên và stdaux được gán cho cổng nối tiếp đầu  
tiên. Chúng được định nghĩa như là các con trỏ cố định kiểu FILE, vì vậy chúng có thể được sử dụng ở  
bất kỳ nơi nào mà việc sử dụng con trỏ FILE là hợp lệ. Chúng cũng thể được chuyển một cách hiệu  
quả cho các stream hay thiết bị khác mỗi khi cần định hướng lại.  
Chương trình sau đây in nội dung của tập tin vào máy in.  
dụ 4:  
#include <stdio.h>  
main()  
{
FILE *in;  
char buff[81], fname[13];  
clrscr();  
printf("Enter the Source File Name:");  
gets(fname);  
if((in=fopen(fname, "r"))==NULL)  
{
fputs("\nFile not found", stderr);  
/* display error message on standard error rather  
than standard output */  
exit(1);  
}
while(!feof(in))  
{
if(fgets(buff, 81, in))  
{
fputs(buff, stdprn);  
/* Send line to printer */  
}
}
fclose(in);  
}
296  
Lập trình cơ bản C  
Lưu ý cách sử dụng của stream stderr với hàm fputs() trong chương trình trên. Nó được sử dụng thay  
cho hàm printf vì kết xuất của hàm printf là stdout, nơi mà có thể định hướng lại. Nếu kết xuất của  
một chương trình được định hướng lại một lỗi xảy ra trong quá trình thực thi, thì tất cả các thông  
báo lỗi đưa ra cho stream stdout cũng phải được định hướng lại. Để tránh điều này, stream stderr  
được dùng để hiển thị thông báo lỗi lên màn hình vì kết xuất của stderr cũng thiết bị xuất chuẩn,  
nhưng stream stderr không thể định hướng lại. Nó luôn luôn hiển thị thông báo lên màn hình.  
21.5.7 Con trỏ kích hoạt hiện hành  
Để lần theo vị trí nơi mà các thao tác nhập/xuất đang diễn ra, một con trỏ được duy trì trong cấu trúc  
FILE. Mỗi khi một tự được đọc ra hay ghi vào một stream, con trỏ kích hoạt hiện hành (current  
active pointer) (gọi curp) được tăng lên. Hầu hết các hàm nhập xuất đều tham chiếu đến curp, và  
cập nhật nó sau các thủ tục nhập hoặc xuất trên stream. Vị trí hiện hành của con trỏ này có thể được  
tìm thấy bằng sự trợ giúp của hàm ftell(). Hàm ftell() trả về một giá trị kiểu long int biểu diễn vị trí  
của curp tính từ đầu tập tin trong stream đã cho. Nguyên mẫu của hàm ftell() là:  
long int ftell(FILE *fp);  
Câu lệnh trích từ một chương trình sẽ hiển thị vị trí của con trỏ hiện hành trong stream fp.  
printf("The current location of the file pointer is : %1d ",  
ftell (fp));  
Đặt lại vtrí hiện hành  
Ngay sau khi mở stream, con trỏ kích hoạt hiện hành được đặt là 0 và trỏ đến byte đầu tiên của  
stream. Như đã thấy trước đây, mỗi khi có một tự được đọc hay ghi vào stream, con trỏ kích  
hoạt hiện hành sẽ tăng lên. Bên trong một chương trình, con trỏ thể được đặt đến một vị trí bất  
kỳ khác với vị trí hiện hành vào bất kỳ lúc nào. Hàm rewind() đặt vị trí con trỏ này về đầu. Một  
hàm khác được sử dụng để đặt lại vị trí con trỏ này là fseek().  
Hàm fseek() định lại vị trí của curp dời đi một số byte tính từ đầu, từ vị trí hiện hành hay từ cuối  
stream là tùy vào vị trí được qui định khi gọi hàm fseek(). Nguyên mẫu của hàm fseek() là:  
int fseek(FILE *fp, long int offset, int origin);  
trong đó offset số byte cần di chuyển vượt qua vị trí tập tin được cho bởi tham số origin. Tham  
số origin chỉ định vị trí bắt đầu tìm kiếm phải có giá trị là 0, 1 hoặc 2, biễu diễn cho 3 hằng ký  
hiệu (được định nghĩa trong stdio.h) như trong bảng 21.4:  
Origin  
Vị trí tập tin  
SEEK_SET or 0 Đầu tập tin  
SEEK_CUR or 1 Vị trí con trỏ của tập tin hiện hành  
SEEK_END or 2 Cuối tập tin  
Bảng 21.4: Các hằng hiệu  
Hàm fseek() trả về giá trị 0 nếu đã thành công và giá trị khác 0 nếu thất bại.  
Đoạn lệnh sau tìm mẫu tin thứ 6 trong tập tin:  
struct addr  
{
char name[40];  
Quản tập tin  
297  
char street[40];  
char city[40];  
char state[3];  
char pin[7];  
}
FILE *fp;  
.
.
.
fseek(fp, 5L*sizeof(struct addr), SEEK_SET);  
Hàm sizeof() được dùng để tìm độ dài của mỗi mẩu tin theo đơn vị byte. Giá trị trả về được dùng để  
xác định số byte cần thiết để nhảy qua 5 mẩu tin đầu tiên.  
21.5.8 Hàm fprintf() và fscanf()  
Ngoài các hàm nhập xuất đã được thảo luận, hệ thống nhập/xuất có vùng đệm còn bao gồm các hàm  
fprintf() fscanf(). Các hàm này tương tự như hàm printf() scanf() ngoại trừ rằng chúng thao tác  
trên tập tin. Nguyên mẫu của hàm fprintf() fscanf() là:  
int fprintf(FILE * fp, const char *control_string,..);  
int fscanf(FILE *fp, const char *control_string,...);  
trong đó fp là con trỏ tập tin trả về bởi lời gọi hàm fopen(). Hàm fprintf() fscanf() định hướng các  
thao tác nhập xuất của chúng đến tập tin được trỏ bởi fp. Đoạn chương trình sau đây đọc một chuỗi và  
một số nguyên từ bàn phím, ghi chúng vào một tập tin trên đĩa, và sau đó đọc thông tin hiển thtrên  
màn hình.  
.
.
printf("Enter a string and a number: ");  
fscanf(stdin, "%s %d", str, &no);  
/* read from the keyboard */  
fprintf(fp, "%s %d", str, no);  
/* write to the file*/  
fclose (fp);  
.
.
fscanf(fp, "%s %d", str, &no)  
/* read from file */  
fprintf(stdout, "%s %d", str, no)  
/* print on screen */  
.
.
Nên nhớ rằng, mặc fprintf() fscanf() thường là cách dễ nhất để ghi vào và đọc dữ liệu hỗn hợp  
ra các tập tin trên đĩa, nhưng chúng không phải luôn luôn hiệu quả nhất. Nguyên nhân là mỗi lời gọi  
phải mất thêm một khoảng thời gian, vì dữ liệu được ghi theo dạng ASCII có định dạng (như sẽ  
xuất hiện trên màn hình) chứ không phải theo định dạng nhị phân. Vì vậy, nếu tốc độ độ lớn của tập  
tin là đáng ngại, fread() fwrite() sẽ lựa chọn tốt hơn.  
298  
Lập trình cơ bản C  
Tóm tắt  
Ngôn ngữ C không chứa bất kỳ câu lệnh nhập/xuất nào tường minh. Tất cả các thao tác nhập/xuất  
được thực hiện bằng cách sử dụng các hàm trong thư viện chuẩn của C.  
Có hai kiểu stream – stream văn bản và stream nhị phân.  
Một stream văn bản một chuỗi các ký tự.  
Một stream nhị phân một chuỗi các byte.  
Một tập tin có thể bất cứ từ một tập tin trên đĩa đến một thiết bị đầu cuối hay một máy in.  
Một con trỏ tập tin là một con trỏ trỏ đến cấu trúc, trong đó chứa các thông tin về tập tin, bao gồm  
tên, vị trí hiện hành của tập tin, tập tin đang được đọc hoặc ghi, và có lỗi xuất hiện hay đã đến cuối  
tập tin.  
Hàm fopen() mở một stream để dùng và liên kết một tập tin với stream đó.  
Hàm fclose() đóng một stream đã được mở bằng hàm fopen().  
Hàm fcloseall() thể được sử dụng khi cần đóng nhiều stream đang mở cùng một lúc.  
Hàm fputc() được dùng để ghi ký tự, và hàm fgetc() được dùng để đọc tự từ một tập tin đang  
mở.  
Hàm fgets() fputs() thao tác giống như hàm fgetc() và fputc(), ngoại trừ rằng chúng làm việc  
trên chuỗi.  
Hàm feof() được dùng để chỉ ra cuối tập tin khi tập tin được mở cho các thao tác nhị phân.  
Hàm rewind() đặt lại vị trí của con trỏ định vị trí về đầu tập tin.  
Hàm ferror() xác định liệu một thao tác trên tập tin có sinh lỗi hay không.  
Hàm remove() xóa một tập tin đã cho.  
Hàm fflush() làm sạch và chép các buffer ra ngoài. Nếu một tập tin được mở để đọc, thì vùng đệm  
nhập của sẽ trống, trong khi một tập tin được mở để ghi thì vùng đệm xuất của được ghi vào  
tập tin.  
Hàm fseek() thể được sử dụng để đặt lại vị trí của con trỏ định vị bên trong tập tin.  
Các hàm thư viên fread() fwrite() được dùng để đọc và ghi toàn bộ khối dữ liệu vào tập tin.  
Hệ thống nhập xuất có vùng đệm cũng bao gồm hai hàm fprintf() fscanf(), hai hàm này tương  
tự như hàm printf() scanf(), ngoại trừ chúng thao tác trên tập tin.  
Quản tập tin  
299  
Kiểm tra tiến độ học tập  
1. Có hai kiểu stream là stream __________ và stream _________.  
2. Các tập tin đang mở được đóng lại khi chương trình bị treo hay kết thúc bất thường.  
3. Hàm _________ mở một stream để dùng và liên kết một tập tin với stream đó.  
4. Hàm được dùng để ghi ký tự vào tập tin là ________.  
(Đúng /Sai)  
(Đúng / Sai)  
5. Hàm fgets() xem ký tự sang dòng mới như một phần của chuỗi.  
6. Hàm ________ đặt lại vị trí của con trỏ định vị bên trong tập tin về đầu tập tin.  
7. Mỗi khi một tự được đọc hay ghi từ một stream, ___________ được tăng lên.  
8. Các tập tin mà trên đó hàm fread() và fwrite() thao tác thì phải được mở ở chế độ ________.  
9. Vị trí hiện hành của con trỏ kích hoạt hiện hành có thể được tìm thấy bằng sự trợ giúp của hàm  
________.  
300  
Lập trình cơ bản C  
Bài tập tự làm  
1. Viết một chương trình để nhập dữ liệu vào một tập tin và in nó theo thứ tự ngược lại.  
2. Viết một chương trình để truyền dữ liệu từ một tập tin này sang một tập tin khác, loại bỏ tất cả các  
nguyên âm (a, e, i, o, u). Loại bcác nguyên âm ở dạng chữ hoa lẫn chữ thường. Hiển thị nội dung  
của tập tin mới.  
Quản tập tin  
301  
302  
Lập trình cơ bản C  
doc 18 trang Thùy Anh 26/04/2022 13340
Bạn đang xem tài liệu "Giáo trình Lập trình cơ bản C - Bài 21: Quản lý tập tin", để tải tài liệu gốc về máy hãy click vào nút Download ở trên

File đính kèm:

  • docgiao_trinh_lap_trinh_co_ban_c_bai_21_quan_ly_tap_tin.doc