630 likes | 815 Views
第 13 章 文 件. 13.1 C文件概述 13.2 文件类型指针 13.3 文件的打开与关闭 13.4 文件的读写 13.5 文件的定位 13.6 出错的检测 13.7 文件输入输出小结 习题. 13.1 C 文件概述. 为什么要使用文件 ? 断电后数据不丢失 操作系统是以文件为单位对数据进行管理的 在使用文件之前需要了解 目录结构 文件读写方式. 目录结构 ( Directory Tree ). 操作系统的文件系统都采用树型的目录结构 绝对路径 (Absolute Path) 完整的描述文件位置的路径就是绝对路径
E N D
第13章 文 件 13.1 C文件概述 13.2 文件类型指针 13.3 文件的打开与关闭 13.4 文件的读写 13.5 文件的定位 13.6 出错的检测 13.7 文件输入输出小结 习题
13.1C文件概述 • 为什么要使用文件? • 断电后数据不丢失 • 操作系统是以文件为单位对数据进行管理的 • 在使用文件之前需要了解 • 目录结构 • 文件读写方式
目录结构(Directory Tree) • 操作系统的文件系统都采用树型的目录结构 • 绝对路径(Absolute Path) • 完整的描述文件位置的路径就是绝对路径 • D:\VCProjects\sample\main.cpp • 相对路径(Relative Path) • 为什么要使用相对路径?“可移植性” • 相对于什么? 相对于当前路径 • 如果当前路径是: D:\VCProjects • 则: sample\main.cpp • “../”表示上一级目录
根据数据的组织形式, 文件可分为: • 二进制文件(Binary File) • 把数据按其在内存中的存储形式存放在磁盘上, .exe .doc .mp3… • 整数10000, 在内存中占2个字节, 按二进制形式, 也占2个字节 • 一个字节并不对应一个字符, 不能直接输出字符形式。一般中间结果数据需要暂时保存在外存上以后又需要输入到内存的, 常用二进制文件保存 • ASCII文件:又称文本(text)文件 • 为了便于使用, 一个字符用一个字节的ASCII代码存放 • 如果有一个整数10000, 占5个字节 • 所有内容都是可打印字符, .c .txt .h .htm… • 一般占存储空间较多, 而且要花费转换时间(二进制形式与ASCII码间的转换)
13.2 文件类型指针 • 每个被使用的文件都在内存中开辟一个区, 用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个由系统定义的FILE结构体变量中。 • Turbo C在stdio.h文件中有以下的文件类型声明:
typedefstruct { short level; /*缓冲区“满”或“空”的程度*/ unsigned flags; /*文件状态标志*/ char fd; /*文件描述符*/ unsigned char hold; /*如无缓冲区不读取字符*/ short bsize; /*缓冲区的大小*/ unsigned char *baffer;/*数据缓冲区的位置*/ unsigned char *curp; /*指针, 当前的指向*/ unsigned istemp; /*临时文件, 指示器*/ short token; /*用于有效性检查*/ }FILE;
FILEf[5];定义了一个结构体数组f, 它有5个元素, 可以用来存放5个文件的信息。 • FILEfp; • 可以使文件指针fp指向某一个文件的结构体变量, 从而通过该结构体变量中的文件信息能够访问该文件。也就是说, 通过文件指针变量能够找到与它相关的文件。
13.3 文件的打开与关闭 13.3.1 文件的打开(fopen函数) • fopen函数的调用方式通常为: FILE *fp; fp=fopen(文件名, 使用文件方式); • fp=fopen("a1", "r");
文件使用方式 意 义“rt” 只读打开一个文本文件, 只允许读数据 “wt” 只写打开或建立一个文本文件, 只允许写数据“at”追加打开一个文本文件, 并在文件末尾写数据“rb” 只读打开一个二进制文件, 只允许读数据“wb” 只写打开或建立一个二进制文件, 只允许写数据“ab” 追加打开一个二进制文件, 并在文件末尾写数据“rt+” 读写打开一个文本文件, 允许读和写“wt+” 读写打开或建立一个文本文件, 允许读写“at+” 读写打开一个文本文件, 允许读, 或在文件末追加数据“rb+” 读写打开一个二进制文件, 允许读和写 “wb+”读写打开或建立一个二进制文件, 允许读和写“ab+”读写打开一个二进制文件, 允许读, 或在文件末追加数据 • r(read): 读w(write): 写a(append): 追加t(text): 文本文件, 可省略不写b(binary): 二进制文件+: 读和写
说明: • (1) 如果打开一个不存在的文件用于写或追加, 该文件将被创建(如果可能的话)。 • (2) 当以写方式打开一个已存在的文件时, 该文件原来的内容将被覆盖。但是, 如果以追加方式打开一个文件, 则该文件原来的内容保留不变。 • (3) 读一个不存在的文件会导致错误, 其他一些操作也可能导致错误, 比如试图读取一个无读取权限的文件。如果发生错误, fopen将返回NULL。 if(NULL==(fp=fopen("file1", "r"))) { printf("cannot open this file\n"); exit(0); }
(4) 在向计算机输入文本文件时, 将回车换行符转换为一个换行符, 在输出时把换行符转换成为回车和换行两个字符。在用二进制文件时, 不进行这种转换, 在内存中的数据形式与输出到外部文件中的数据形式完全一致, 一一对应。 • (5) 在程序开始运行时, 系统自动打开3个标准文件:标准输入、标准输出、标准出错输出。通常这3个文件都与终端相联系。因此以前我们所用到的从终端输入或输出都不需要打开终端文件。系统自动定义了3个文件指针stdin、stdout和stderr, 分别指向终端输入、终端输出和标准出错输出也从终端输出)。如果程序中指定要从stdin所指的文件输入数据, 就是指从终端键盘输入数据。
13.3.2 文件的关闭(fclose函数) • 应该养成在程序终止之前关闭所有文件的习惯, 如果不关闭文件将会丢失数据。 • 因为, 在向文件写数据时, 是先将数据输到缓冲区, 待缓冲区充满后才正式输出给文件。如果当数据未充满缓冲区而程序结束运行, 就会将缓冲区中的数据丢失。用fclose函数关闭文件, 可以避免这个问题, 它先把缓冲区中的数据输出到磁盘文件, 然后才释放文件指针变量。 • fclose函数调用的一般形式为:fclose(文件指针); • 例如:fclose(fp); • 关闭操作执行正确, 返回0;否则返回EOF(-1)。
13.4 文件的读写 • 13.4.1fputc函数和fgetc函数(putc、fgetc) 1.fputc函数 • int fputc(int ch, FILE *fp); • 把字符ch写到fp所指向的文件中。 • 如果输出成功则返回输出的字符;如果输出失败, 则返回一个EOF(-1)。EOF是在stdio.h文件中定义的符号常量, 值为-1。 • putchar(c)是在stdio.h文件中定义的宏:#defineputchar(c)fputc(c, stdout) • #define putc(ch, fp) fputc(ch, fp) • #define getc(fp) fgetc(fp)
2. fgetc函数 • int fgetc(FILE *fp) • 从指定的文件返回下一个字符, 该文件必须是以读或读写方式打开的。 • 如果遇到文件结束符, 函数返回一个文件结束标志EOF(-1)。 • 如果想从一个磁盘文件顺序读入字符并在屏幕上显示出来, 可以: ch=fgetc(fp); while(!feof(fp)) { putchar(ch); ch=fgetc(fp); }
3. fputc和fgetc函数使用举例 • 例13.1 从键盘输入一些字符, 逐个把它们送到磁盘上去, 直到输入一个“#”为止。 void main() { FILE *fp; char ch,filename[10]; scanf("%s",filename); if((fp=fopen(filename,"w"))==NULL) { printf("cannot open file\n"); exit(0); }
ch=getchar();/*此语句用来接收在执行scanf语句时最后输入的回车符*/ch=getchar();/*此语句用来接收在执行scanf语句时最后输入的回车符*/ ch=getchar();/*接收输入的第一个字符*/ while(ch!='#') { fputc(ch, fp); putchar(ch); ch=getchar(); } fclose(fp); }
运行情况如下: file1.c (输入磁盘文件名) computer and c#(输入一个字符串) computer and c (输出一个字符串)
例13.2将一个磁盘文件中的信息复制到另一个磁盘文件中。例13.2将一个磁盘文件中的信息复制到另一个磁盘文件中。 #include <stdio.h> void main() { FILE *in, *out; char infile[10], outfile[10]; printf("Enter the infile name:\n"); scanf("%s", infile); printf("Enter the outfile name:\n"); scanf("%s", outfile);
if((in=fopen(infile, "r"))==NULL) { printf("cannot open infile\n"); exit(0); } if((out=fopen(outfile, "w"))==NULL) { printf("cannot open outfile\n"); exit(0); } while(!feof(in)) fputc(fgetc(in), out); fclose(in); fclose(out); }
运行情况如下: Enter the infile name: file1.c(输入原有磁盘文件名) Enter the outfile name: file2.c(输入新复制的磁盘文件名) • 也可以用此程序来复制一个二进制文件, 只需将两个fopen函数中的“r”和“w”分别改为“rb”和“wb”即可。 • 也可以在输入命令行时把两个文件名一起输入。这时要用到main函数的参数。程序可改为
void main(int argc, char*argv[]) { FILE *in, *out; if (argc!=3) { printf("You forgot to enter a filename\n"); exit(0); } if ((in=fopen(argv[1], "r"))==NULL) { printf("cannot open infile\n"); exit(0); } if ((out=fopen(argv[2], "w"))==NULL) { printf("cannot open outfile\n"); exit(0); }
while(!feof(in)) fputc(fgetc(in), out); fclose(in); fclose(out); }
13.4.2fread函数和fwrite函数 • 常常要求一次读入一组数据(例如, 一个实数或一个结构体变量的值), ANSI C标准提出设置两个函数(fread和fwrite), 用来读写一个数据块。 • 它们的一般调用形式为 fread(buffer, size, count, fp); fwrite(buffer, size, count, fp); • buffer:是一个指针。对fread来说, 它是读入数据的存放地址。对fwrite来说, 是要输出数据的地址(以上指的是起始地址)。 • size:要读写的字节数。 • count:要进行读写多少个size字节的数据项。 • fp:文件型指针。 • 如果fread或fwrite调用成功, 则函数返回值为count的值。
如果文件以二进制形式打开, 用fread和fwrite函数就可以读写任何类型的信息, 如: • fread(f, 4, 2, fp); • 其中f是一个实型数组名。一个实型变量占4个字节。 • 这个函数从fp所指向的文件读入2次(每次4个字节)数据, 存储到数组f中。
如果有一个如下的结构体类型: #define SIZE 4 struct student_type { char name[10]; int num; int age; char addr[15]; }stud[SIZE];
例13.3从键盘输入4个学生的有关数据, 然后把它们转存到磁盘文件上去。 void save() { FILE *fp; int i; if ((fp=fopen("c:\\a.txt", "wb"))==NULL) { printf("cannot open file\n"); return; } for (i=0; i<SIZE; i++) { if (fwrite(&stud[i], sizeof(struct student_type), 1, fp)!=1) printf("file write error\n"); } fclose (fp); }
void main() { int i; for(i=0;i<SIZE;i++) scanf("%s%d%d%s", stud[i].name, &stud[i].num, &stud[i].age, stud[i].addr); save(); }
void load() { FILE *fp; int i; if ((fp=fopen("c:\\a.txt", "rb"))==NULL) { printf("cannot open infile\n"); return; }
for (i=0; i<SIZE; i++) { if (fread(&stud[i], sizeof(struct student_type), 1, fp)!=1) { if(feof(fp)) { fclose (fp); return; } printf("file read error\n"); } } fclose (fp); }
void main() { load(); for (int i=0; i<SIZE; i++) { printf("%s %d %d %s\n",stud[i].name, stud[i].num, stud[i].age, stud[i].addr); } save(); }
请注意输入输出数据的状况。从键盘输入4个学生的数据是ASCII码, 也就是文本文件。在送到计算机内存时, 回车和换行符转换成一个换行符。再从内存以“wb”方式(二进制写)输出到“stu_list”文件, 此时不发生字符转换, 按内存中存储形式原样输出到磁盘文件上。在上面验证程序中, 又用fread函数从“stu_list”文件向内存读入数据, 注意此时用的是“rb”方式, 即二进制方式, 数据按原样输入, 也不发生字符转换。也就是这时候内存中的数据恢复到第一个程序向 “stu-list”输出以前的情况。最后在验证程序中, 用printf函数输出到屏幕, printf是格式输出函数, 输出ASCII码, 在屏幕上显示字符。换行符又转换为回车加换行符。
如果企图从“stu_list”文件中以“r”方式读入数据就会出错。如果企图从“stu_list”文件中以“r”方式读入数据就会出错。 • fread和fwrite函数一般用于二进制文件的输入输出。因为它们是按数据块的长度来处理输入输出的, 在字符发生转换的情况下很可能出现与原设想的情况不同。 • 例如, 如果写fread(&stud[i], sizeof(struct student-type), 1, stdin);
企图从终端键盘输入数据, 这在语法上并不存在错误, 编译能通过。如果用以下形式输入数据: • Zhang 100110room-101 • … • 由于fread函数要求一次输入29个字节(而不问这些字节的内容), 因此输入数据中的空格也作为输入数据而不作为数据间的分隔符了。连空格也存储到stud[i]中了, 显然是不对的。 • 这个题目要求的是从键盘输入数据, 如果已有的数据已以二进制形式存储在一个磁盘文件“stu-dat”中, 要求从其中读入数据并输出到“stu-list”文件中, 可以编写一个load函数, 从磁盘文件中读二进制数据。
void load() {FILEfp; int i; if((fp=fopen("stu-dat", "rb"))==NULL) {printf("cannot open infile\n"); return;} for(i=0;i<SIZE;i++) if(fread(&stud[i], sizeof(struct student-type), 1, fp)!=1)
{if(feof(fp)){fclose (fp); return;} printf("file read error\n"); } fclose (fp); } 将load函数加到本题原来的程序文件中, 并将main函数改为main() { load(); save(); }
13.4.3fprintf函数和fscanf函数 • fprintf函数、fscanf函数与printf函数、scanf函数作用相仿, 都是格式化读写函数。只有一点不同:fprintf和fscanf函数的读写对象不是终端而是磁盘文件。它们的一般调用方式为fprintf(文件指针, 格式字符串, 输出表列);fscanf (文件指针, 格式字符串, 输入表列);例如: • fprintf(fp, "%d, %6.2f", i, t); • 它的作用是将整型变量i和实型变量t的值按%d和%6.2f的格式输出到fp指向的文件上。如果i=3, t=4.5, 则输出到磁盘文件上的是以下的字符串:
3, 4.50 • 同样, 用以下fscanf函数可以从磁盘文件上读入ASCII字符:fscanf(fp, "%d, %f", &i, &t); • 磁盘文件上如果有以下字符: • 3, 4.5 • 则将磁盘文件中的数据3送给变量i, 4.5送给变量t。 • 用fprintf和fscanf函数对磁盘文件读写, 使用方便, 容易理解, 但由于在输入时要将ASCII码转换为二进制形式, 在输出时又要将二进制形式转换成字符, 花费时间比较多。因此, 在内存与磁盘频繁交换数据的情况下, 最好不用fprintf和
fscanf函数, 而用fread和fwrite函数。 13.4.4 其他读写函数 • 1. putw和getw函数 • 大多数C编译系统都提供另外两个函数:putw和getw, 用来对磁盘文件读写一个字(整数)。例如: • putw(10, fp); • 它的作用是将整数10输出到fp指向的文件。而i=getw(fp);的作用是从磁盘文件读一个整数到内存, 赋给整型变量i。 • 如果所用的C编译的库函数中不包括putw和getw函数, 可以自己定义该两函数。putw函数如下:
putw(int i, FILE *fp) {chars; s=(char*)&i; putc(s[0], fp);putc(s[1], fp); return(i); } 当调用putw函数时, 如果用“putw(10, fp);”语句, 形参i得到实参传来的值10, 在putw函数中将i的地址赋予指针变量s, 而s是指向字符变量的指针变量, 因此s指向i的第1个字节, s+1指向i的第2个字节。由于*(s+0)就是s[0], *(s+1)就是s[1], 因此, s[0]、s[1]分别对应i的第1字节和第2个字节。由于
*(s+0)就是s[0], *(s+1)就是s[1], 因此, s[0]、s[1]分别对应i的第1字节和第2字节。顺序输出s[0]、s[1]就相当于输出了i的两个字节中的内容。见图13.3。 图13.3
getw函数如下: getw(FILE *fp) {chars; int i; s=(char *)&i;/*使s指向i的起始地址*/ s[0]=getc(fp); s[1]=getc(fp); return(i); } • putw和getw并不是ANSI C标准定义的函数。但许多C编译都提供这两个函数, 但有的C编译可能不
以putw和getw命名此两函数, 而用其他函数名, 请用时注意。 2. 读写其他类型数据 • 如果用ANSI C提供的fread和fwrite函数, 读写任何类型数据都是十分方便的。如果所用的系统不提供这两个函数, 用户只好自己定义所需函数。例如, 可以定义一个向磁盘文件写一个实数(用二进制方式)的函数putfloat: putfloat(float num, FILE *fp) {chars;
int count; s=(char *)# for(count=0;count<4;count++) putc(s[count], fp); } • 同样可以编写出读写任何类型数据的函数。 3. fgets函数和fputs函数 • fgets的作用是从指定文件读入一个字符串。如: • fgets(str, n, fp); • n为要求得到的字符, 但只从fp指向的文件输入n-1个字符, 然后在最后加一个‘\0’字符, 因此得到
的字符串共有n个字符。把它们放到字符数组str中。如果在读完n-1个字符之前遇到换行符或EOF, 读入即结束。fgets函数返回值为str的首地址。 • fputs函数的作用是向指定的文件输出一个字符串。如:fputs("China", fp);把字符串“China”输出到fp指向的文件。fputs函数中第一个参数可以是字符串常量、字符数组名或字符型指针。字符串末尾的‘\0’不输出。若输出成功, 函数值为0;失败时, 为EOF。 • 这两个函数类似以前介绍过的gets和puts函数, 只是fgets和fputs函数以指定的文件作为读写对象。
13.5 文 件 的 定 位 • 文件中有一个位置指针, 指向当前读写的位置。如果顺序读写一个文件, 每次读写一个字符, 则读写完一个字符后, 该位置指针自动移动指向下一个字符位置。如果想改变这样的规律, 强制使位置指针指向其他指定的位置, 可以用有关函数。 13.5.1rewind函数 • rewind函数的作用是使位置指针重新返回文件的开头。此函数没有返回值。 • 例13.4有一个磁盘文件, 第一次将它的内容显示在屏幕上, 第二次把它复制到另一文件上。
#include<stdio.h> main() {FILEfp1, *fp2; fp1=fopen("file1.c", "r"); fp2=fopen("file2.c", "w"); while(!feof(fp1))putchar(getc(fp1)); rewind(fp1); while(!feof(fp1))putc(getc(fp1), fp2); fclose(fp1);fclose(fp2); } • 在第一次将文件的内容显示在屏幕以后, 文件file1.c的位置指针已指到文件末尾, feof的值为非零(真)。执行rewind函数, 使文件的位置指针重
新定位于文件开头, 并使feof函数的值恢复为0(假)。 13.5.2fseek函数和随机读写 • 对流式文件可以进行顺序读写, 也可以进行随机读写。关键在于控制文件的位置指针, 如果位置指针是按字节位置顺序移动的, 就是顺序读写。如果能将位置指针按需要移动到任意位置, 就可以实现随机读写。所谓随机读写, 是指读写完上一个字符(字节)后, 并不一定要读写其后续的址?字节), 而可以读写文件中任意所需的字符(字节)。 • 用fseek函数可以实现改变文件的位置指针。
fseek函数的调用形式为fseek(文件类型指针, 位移量, 起始点) • “起始点”用0、1或2代替, 0代表“文件开始”, 1为“当前位置”, 2为“文件末尾”。ANSI C标准指定的名字如表13.2所示。 • “位移量”指以“起始点”为基点, 向前移动的字节数。ANSI C和大多数C版本要求位移量是long型数据。这样当文件的长度大于64K时不致出问题。ANSI C标准规定在数字的末尾加一个字母L, 就表示是long型。 • 下面是fseek函数调用的几个例子: • fseek(fp, 100L, 0);将位置指针移到离文件头100个字节处
fseek(fp, 50L, 1); 将位置指针移到离当前位置50个字节处 • fseek(fp, -10L, 2); 将位置指针从文件末尾处向后退10个字节 • 利用fseek函数就可以实现随机读写了。 • 例13.5在磁盘文件上存有10个学生的数据。要求将第1、3、5、7、9个学生数据输入计算机, 并在屏幕上显示出来。 • 程序如下: #include<stdio.h>