![]() ![]() |
|
C语言编程常见问题解答之数据文件 | |
作者:佚名 文章来源:不详 点击数 更新时间:2008/4/18 13:58:39 文章录入:杜斌 责任编辑:杜斌 | |
|
|
本章重点讨论C语言的强大功能之一 —— 磁盘输入和输出。多年来,最快、最简单的专业程序都是用C语言编写的,并且受益于C语言优化了的文件I/O程序。 处理数据文件有时是比较困难的,本章将综合分析这方面的一些常见问题,例如流(stream)、文件模式(文本(text)和二进制(binary))以及文件和目录的处理等。目前,大多数专业程序是面向网络的,因此本章末尾讨论了有关文件共享和一致性控制的一些问题,希望读者认真阅读。此外,本章也讨论了许多与文件有关的问题,例如DOS中的文件句柄和硬件错误处理程序的安装。 4. 1 当errno为一个非零值时,是否有错误发生? 许多标准的C库函数都通过全局变量errno向程序传递一个错误号,以表明发生哪种错误,但是,你的程序不应该通过检查errno的值来判断是否发生了错误。通常,被调用的标准的C库函数都有一个返回值,该值将表示是否发生了错误,并且表示是否已给errno赋予了相应的错误号。在没有发生错误或所调用的函数不使用errno时,在errno中很可能仍然保留着一个错误号。有时,为了改善运行速度,使用errno的函数并不将errno清零。 总之,绝对不能单凭errno的值来判断是否发生了错误,而应该根据函数的返回值来判断是否应该检查errno的值。请参考你所使用的编译程序的有关文档,看看哪些函数使用了errno全局变量,以及errno的有效值清单。 4. 2 什么是流(stream)? 流是程序输入或输出的一个连续的字节序列,设备(例如鼠标、键盘、磁盘、屏幕、调制解调器和打印机)的输入和输出都是用流来处理的。在C语言中,所有的流均以文件的形式出现----不一定是物理磁盘文件,还可以是对应于某个输入/输出源的逻辑文件。C语言提供了5种标准的流,你的程序在任何时候都可以使用它们,并且不必打开或关闭它们。以下列出了这5种标准的流。 ------------------------------------------------ 名称 描 述 例 子 ------------------------------------------------ stdin 标准输入 键盘 stdout 标准输出 屏幕 stderr 标准错误 屏幕 stdprn 标准打印机 LPT1端口 stdaux 标准串行设备 COM1端口 ------------------------------------------------ 需要注意的是,stdprn和stdaux并不总是预先定义好的,因为LPT1和COM1端口在某些操作系统中是没有意义的,而stdin,stdout和stderr总是预先定义好的。此外,stdin并不一定来自键盘,stdout也并不一定显示在屏幕上,它们都可以重定向到磁盘文件或其它设备上。 请参见: 4.3 怎样重定向一个标准流? 4.4 怎样恢复一个重定向了的标准流? 4.5 stdout能被强制打印到非屏幕设备上吗? 4.3 怎样重定向一个标准流? 包括DOS在内的大多数操作系统,都提供了将程序的输入和输出重定向到不同设备上的手段。这就是说,程序的输出并不一定是到屏幕上,还可以重定向到文件或打印机端口上;程序的输入并不一定来自键盘,还可以重定向到文件上。 在DOS中,重定向是通过重定向字符“<”和“>”来实现的。例如,如果你要求程序PRINTIT.EXE的输入来自文件STRINGS.TXT,你就可以在DOS提示符下键入如下命令: C:\>PRINTIT<STRINGS.TXT 请注意,可执行文件的名称总是第一个出现。“<”符号告诉DOS将STRINGS.TXT中的字符串作为程序PRINTIT.EXE的输入。关于重定向stdout标准流的例子请看4. 5。
标准流的重定向并不一定总在操作系统下进行,在程序内部,用标准C库函数freopen()同样可以重定向标准流。例如,如果你要求在程序内部将标准流stdout重定向到文件OUTPUT.TXT,你就可以象下面这样使用freopen()函数: freopen("output.txt","w",stdout); 现在,程序中每条输出语句(例如prinft();puts(),putch()等)输出的内容都将出现在文件OUTPUT.TXT中。 请参见: 4.2 什么是流(stream)? 4.4 怎样恢复一个重定向了的标准流? 4.5 stdout能被强制打印到非屏幕设备上吗? 4.4 怎样恢复一个重定向了的标准流? 4.3中的例子演示了如何在程序内部重定向标准流。如果要将重定向了的标准流恢复到初始状态,可以使用标准C库函数dup()和fdopen()。 dup()函数可以复制一个文件句柄,你可以用dup()函数保存对应于stdout标准流的文件句柄。fdopen()函数可以打开一个已用dup()函数复制了的流。这样,你就可以重定向并恢复标准流,请看下例: #include <stdio.h> void main(void); void main(void) { int orig-stdout; / * Duplicate the stdout file handle and store it in orig_stdout. */ orig_stdout = dup (fileno (stdout)); / * This text appears on-screen. * / printf("Writing to original stdout... \n") ; / * Reopen stdout and redirect it to the "redir. txt" file. * / freopen("redir.txt", "w", stdout); / * This text appears in the "redir. txt" file. * / printf("Writing to redirected stdout.., \n"); /* Close the redirected stdout. * / fclose (stdout); / * Restore the original stdout and print to the screen again. * / fdopen(orig_stdout, "w" ); printf("I’m back writing to the original stdout. \n"); } 4.2 什么是流(stream)? 4.3 怎样重定向一个标准流? 4. 5 stdout能被强制打印到非屏幕设备上吗? 4. 5 stdout能被强制打印到非屏幕设备上吗? 尽管标准流stdout的缺省方式是打印在屏幕上,但你可以将它重定向到其它设备上。请看下面的例子: /* redir.c */ #include<stdio.h> void main(void); void main(void) { printf(”Let’s get redirectedI\n”), } 在DOS提示符下,通过重定向字符“>”,可以将上例对应的可执行程序的输出重定向到非屏幕设备上。例如,下例将该程序的输出重定向到prn设备(通常就是连接到LPTl端口的打印机)上: C:\>REDIR>PRN 同样,你也可以将该程序的输出重定向到一个文件上,请看下例: C:\>REDIR>REDIR.OUT 在上例中,原来在屏幕上显示的输出内容将全部写入文件REDIR.OUT中。 请参见: 4.2什么是流(stream)? 4.3怎样重定向一个标准流? 4.4怎样恢复一个重定向了的标准流? 4. 6 文本模式(textmode)和二进制模式(binarymode)有什么区别? 流可以分为两种类型:文本流和二进制流。文本流是解释性的,最长可达255个字符,其中回车/换行将被转换为换行符“\n”,反之亦然。二进制流是非解释性的,一次处理一个字符,并且不转换字符。
通常,文本流用来读写标准的文本文件,或者将字符输出到屏幕或打印机,或者接受键盘的输入;而二进制流用来读写二进制文件(例如图形或字处理文档),或者读取鼠标输入,或者读写调制解调器。 请参见: 4.18怎样读写以逗号分界的文本? 4.7 怎样判断是使用流函数还是使用低级函数? 流函数(如fread()和fwrite())带缓冲区,在读写文本或二进制文件时效率更高。因此,一般来说,使用流函数比使用不带缓冲区的低级函数(如read()和write())会使程序性能更好。 然而,在多用户环境中,文件需要共享,文件中的一部分会不断地被加锁、读、写或解锁,这时流函数的性能就不如低级函数好,因为共享文件的内容变化频繁,很难对它进行缓冲。因此,通常用带缓冲区的流函数存取非共享文件,用低级函数存取共享文件。 4.8 怎样列出某个目录下的文件? C语言本身没有提供象dir_list()这样的函数来列出某个目录下所有的文件。不过,利用C语言的几个目录函数,你可以自己编写一个dir_list()函数。 首先,头文件dos.h定义了一个find_t结构,它可以描述DOS下的文件信息,包括文件名、时间、日期、大小和属性。其次,C编译程序库中有_dos_findfirst()和_dos_findnext()这样两个函数,利用它们可以找到某个目录下符合查找要求的第一个或下一个文件。 dos_findfirst()函数有三个参数,第一个参数指明要查找的文件名,例如你可以用“*.*”指明要查找某个目录下的所有文件。第二个参数指明要查找的文件属性,例如你可以指明只查找隐含文件或子目录。第三个参数是指向一个find_t变量的指针,查找到的文件的有关信息将存放到该变量中。 dos_findnext()函数在相应的目录中继续查找由_dos_findfirst()函数的第一个参数指明的文件。_dos_findnext()函数只有一个参数,它同样是指向一个find_t变量的指针,查找到刚文件的有关信息同样将存放到该变量中。 利用上述两个函数和find_t结构,你就可以遍历磁盘上的某个目录,并列出该目录下所有的文件,请看下例: #include <stdio.h> #include <direct.h> #include <dos.h> #include <malloc.h> #include <memory.h> #include <string.h> typedef struct find_t FILE_BLOCK void main(void); void main(void) { FILE_BLOCK f-block; /* Define the find_t structure variable * / int ret_code; / * Define a variable to store the return codes * / / * Use the "*.*" file mask and the 0xFF attribute mask to list all files in the directory, including system files, hidden files, and subdirectory names. * / ret_code = _dos_findfirst(" *. * ", 0xFF, &f_block); /* The _dos_findfirst() function returns a 0 when it is successful and has found a valid filename in the directory. * / while (ret_code == 0) { /* Print the file’s name * / printf(" %-12s\n, f_block, name); / * Use the -dos_findnext() function to look
for the next file in the directory. * / ret_code = _dos_findnext (&f_block); } printf("\nEnd of directory listing. \n" ); } 请参见: 4. 9 怎样列出一个文件的日期和时间? 4. 10 怎样对某个目录下的文件名进行排序? 4. 11 怎样判断一个文件的属性? 4. 9 怎样列出一个文件的日期和时间? 在_dos_findirst()和_dos_findfnext()函数所返回的find_t结构中(请参见4.8),存放着查找到的文件的日期和时间,因此,只要对4.8中的例子稍作改动,就可以列出每个文件的日 期、时间和文件名。 文件的日期和时间存放在结构成员find_t.wr_date和find_t.wr_time中。文件的时间存放在一个双字节的无符号整数中,见下表: ------------------------------------------------------------- 元 素 位域大小 取值范围 ------------------------------------------------------------- 秒 5位 0—29(乘以2后为秒值) 分 6位 0—59 时 5位 0—23 ------------------------------------------------------------- 文件的日期同样也存放在一个双字节的无符号整数中,见下表: ------------------------------------------------------------- 元 素 位域大小 取值范围 ------------------------------------------------------------- 日 5位 1—31 月 4位 1—12 年 7位 1--127(加上1980后为年值) ------------------------------------------------------------- 因为DOS存储文件的秒数的间隔为两秒,所以只需使用0--29这个范围内的值。此外,DOS产生于1980年,因此文件的日期不可能早于1980年,你必须加上“1980”这个值才能得到真正的年值。 以下是列出某个目录下所有的文件及其日期和时间的一个例子: #include <stdio.h> #include <direct.h> #include <dos. h> #include <malloc.h> #include <memory.h> #include <string.h> typedef struct find_t FILE_BLOCK void main(void); void main(void) { FILE_BLOCK f_block; /* Define the find-t structure variable * / int ret-code; / * Define a variable to store return codes * / int hour; / * We’re going to use a 12-hour clockl * /
char * am_pm; / * Used to print "am" or "pm" * / printf("\nDireetory listing of all files in this directory:\n\n"); / * Use the ’ *. * ’ file mask and the 0xFF attribute mask to list all files in the directory, including system files, hidden files, and subdirectory names. * / ret_code = _dos_findfirst(" *.* ", 0xFF, &f_block); /* The_dos_findfirst() function returns a 0 when it is successful and has found a valid filename in the directory. * / while (ret_code == 0) { / * Convert from a 24-hour format to a 12-hour format. * / hour = (f_block. wr_time>>11); if (hour > 12) { hour = hour - 12; am_pm = "pm"; } else am_pm="am"; / * Print the file’s name, date stamp, and time stamp. * / printf("%-12s %2d/%2d/%4d %2d:%2d:%02d %s\n", f_block.name, / * name * / (f-block.wr_date >> 5) & 0x0F, / * month * / (f_block.wr_date) & 0x1F, / * day * / (f_block.wr_date >> 9) + 1980 , / * year * / hour, / * hour * / (f-block. wr_time >> 5) & 0x3F, / * minute * /
(f_block. wr_time & 0x1F) * 2, / * seconds * / am_pm); /* Use the _ dos_findnext() function to look for the next file in the directory. * / ret_code = _dos_findnext (&f_block); } printf("\End of directory listing. \n" ); } 请注意,为了获得时间变量和日期变量的各个元素,要进行大量移位操作和位处理操作,如果你非常讨厌这些操作,你可以自己定义一个find_t这样的结构,并为C语言定义的find_t结构和你自己定义的结构创建一个共用体(请看下例),从而改进上例中的代码。 / * This is the find_t structure as defined by ANSI C. * / struct find_t { char reserved[21]; char attrib; unsigned wr_time; unsigned wr_date; long size; char name[13]; / * This is a custom find_t structure where we separate out the bits used for date and time. * / struet my_find_t { char reserved[21]; char attrib; unstgned seconds: 5; unsigned minutes: 6; unsigned hours: 5; unsigned day: 5; unstgned month: 4; unsigned year: 7; long size; char name[13]; } /* Now, create a union between these two strucures so that we can more easily access the elements of wr_date and wr_time. * / union file_info { struct find_t ft; struct my_find_t mft; } 用上例中的自定义结构和共用体,你就可以象下例这样来抽取日期变量和时间变量的各个元素,而不必再进行移位操作和位处理操作了: ... file_info my_file; ... printf(" %-12s %2d/%2d/%4d %2d: %2d: %2d %s\n", my_file, mfr.name, / * name * / my-file, mfr.month, / * moth * / my_file, mfr.day, / * day * / (my-file. mft.year + 1980), / * year * /
my-file, raft. hours, / * hour * / my- file. mfr. minutes, / * minute * / (my_file. mft. seconds * 2), / * deconds * / am_pm); 请参见: 4.8 怎样列出某个目录下的文件? 4.10 怎样对某个目录下的文件名进行排序? 4. 11 怎样判断一个文件的属性? 4.10 怎样对某个目录下的文件名进行排序? 在4.8的例子中,用_dos_findfirst()和_dos_findnext()函数遍历目录结构,每找到一个文件名,就把它打印在屏幕上,因此,文件名是逐个被找到并列出来的。 当你对某个目录下的文件名进行排序时,这种逐个处理的方式是行不通的。你必须先将文件名存储起来,当所有的文件名都找到后,再对它们进行排序。为了完成这项任务,你可以建立一个指向find_t结构的指针数组,这样,每找到一个文件名,就可以为相应的find_t结构分配一块内存,将其存储起来。当所有的文件名都找到后,就可以用qsort()函数按文件名对所得到的find_t结构数组进行排序了。 qsort()函数是一个标准C库函数,它有4个参数:指向待排数组的指针,待排元素的数目,每个元素的大小,指向用来比较待排数组中两个元素的函数的指针。比较函数是你要提供的一个用户自定义函数,根据所比较的第一个元素是大于、小于或等于第二个元素,它将返回一个大于、小于或等于0的值。 请看下例: #include <stdio.h> #include <direct.h> #include <dos.h> #include <malloc.h> #include <memory.h> #include <string.h> typedef struct find_t FILE_BLOCK ; int sort_files(FILE_BLOCK * * , FILE-BLOCK * * ); void main(void); void main(void) { FILE_BLOCK f_block; /* Define the find_t structure variable * / int ret_code; /* Define a variable to store the retur codes * / FILE_BLOCK * * file_block; /* Used to sort the files * / int file_count; / * Used to count the flies * / int x; / * Counter variable * / file_count = -1; / * Allocate room to hold up to 512 directory entries. * / file_list = (FILE_BLOCK * * ) malloc(sizeof(FILE_BLOCK * ) * 512);
printf("\nDirectory listing of all files in this directory ; \n\n"); / * Use the " *. * " file mask and the 0xFF attribute mask to list all files in the directory, including system files, hidden files, and subdirectory names. * / ret_code = _dos_findfirst(" *.* ", 0xFF, &f_block); / * The _dos_findfirst() function returns a 0 when it is successful and has found a valid filename in the directory. * / while (ret_code == 0 && file_count < 512) { / * Add this filename to the file list * / file_list[++ file_count] = (FILE_BLOCK * ) malloc (sizeof(FILE_BLOCK)); * flile_list[file_count] = f_block; /* Use the _dos_findnext() function to look for the next file in the directory. * / ret_code = _dos_findnext (&f_block); } /* Sort the files * / qsort(file_list, file_count, sizeof(FILE_BLOCK * ), sort_files); / * Now, iterate through the sorted array of filenames and print each entry. * / for (x=0; x<file_count; x++) { printf(" %-12s\n", file_list[x]->name); } printf("\nEnd of directory listing. \n" ); } int sort_files(FILE_BLOCK* * a, FILE_BLOCK* * b) { return (strcmp((*a)->name, (*b)->name)); } 在上例中,由用户自定义的函数sort_files()来比较两个文件名,它的返回值实际就是标准C库函数strcmp()的返回值。只要相应地改变sort_files()函数的操作对象,上例就可按日期、时间或扩展名进行排序。 请参见: 4.8 怎样列出某个目录下的文件? 4.9 怎样列出一个文件的日期和时间? 4. 11 怎样判断一个文件的属性? |
|
![]() ![]() |