1 / 63

第 13 章 文 件

第 13 章 文 件. 13.1  C文件概述 13.2  文件类型指针 13.3  文件的打开与关闭 13.4  文件的读写 13.5  文件的定位 13.6  出错的检测 13.7  文件输入输出小结 习题. 13.1 C 文件概述. 为什么要使用文件 ? 断电后数据不丢失 操作系统是以文件为单位对数据进行管理的 在使用文件之前需要了解 目录结构 文件读写方式. 目录结构 ( Directory Tree ). 操作系统的文件系统都采用树型的目录结构 绝对路径 (Absolute Path) 完整的描述文件位置的路径就是绝对路径

huey
Télécharger la présentation

第 13 章 文 件

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第13章 文 件 13.1 C文件概述 13.2 文件类型指针 13.3 文件的打开与关闭 13.4 文件的读写 13.5 文件的定位 13.6 出错的检测 13.7 文件输入输出小结 习题

  2. 13.1C文件概述 • 为什么要使用文件? • 断电后数据不丢失 • 操作系统是以文件为单位对数据进行管理的 • 在使用文件之前需要了解 • 目录结构 • 文件读写方式

  3. 目录结构(Directory Tree) • 操作系统的文件系统都采用树型的目录结构 • 绝对路径(Absolute Path) • 完整的描述文件位置的路径就是绝对路径 • D:\VCProjects\sample\main.cpp • 相对路径(Relative Path) • 为什么要使用相对路径?“可移植性” • 相对于什么? 相对于当前路径 • 如果当前路径是: D:\VCProjects • 则: sample\main.cpp • “../”表示上一级目录

  4. 根据数据的组织形式, 文件可分为: • 二进制文件(Binary File) • 把数据按其在内存中的存储形式存放在磁盘上, .exe .doc .mp3… • 整数10000, 在内存中占2个字节, 按二进制形式, 也占2个字节 • 一个字节并不对应一个字符, 不能直接输出字符形式。一般中间结果数据需要暂时保存在外存上以后又需要输入到内存的, 常用二进制文件保存 • ASCII文件:又称文本(text)文件 • 为了便于使用, 一个字符用一个字节的ASCII代码存放 • 如果有一个整数10000, 占5个字节 • 所有内容都是可打印字符, .c .txt .h .htm… • 一般占存储空间较多, 而且要花费转换时间(二进制形式与ASCII码间的转换)

  5. 13.2 文件类型指针 • 每个被使用的文件都在内存中开辟一个区, 用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个由系统定义的FILE结构体变量中。 • Turbo C在stdio.h文件中有以下的文件类型声明:

  6. typedefstruct { short level; /*缓冲区“满”或“空”的程度*/ unsigned flags; /*文件状态标志*/ char fd; /*文件描述符*/ unsigned char hold; /*如无缓冲区不读取字符*/ short bsize; /*缓冲区的大小*/ unsigned char *baffer;/*数据缓冲区的位置*/ unsigned char *curp; /*指针, 当前的指向*/ unsigned istemp; /*临时文件, 指示器*/ short token; /*用于有效性检查*/ }FILE;

  7. FILEf[5];定义了一个结构体数组f, 它有5个元素, 可以用来存放5个文件的信息。 • FILEfp; • 可以使文件指针fp指向某一个文件的结构体变量, 从而通过该结构体变量中的文件信息能够访问该文件。也就是说, 通过文件指针变量能够找到与它相关的文件。

  8. 13.3 文件的打开与关闭 13.3.1 文件的打开(fopen函数) • fopen函数的调用方式通常为: FILE *fp; fp=fopen(文件名, 使用文件方式); • fp=fopen("a1", "r");

  9. 文件使用方式        意 义“rt”  只读打开一个文本文件, 只允许读数据 “wt”  只写打开或建立一个文本文件, 只允许写数据“at”追加打开一个文本文件, 并在文件末尾写数据“rb”  只读打开一个二进制文件, 只允许读数据“wb”  只写打开或建立一个二进制文件, 只允许写数据“ab”  追加打开一个二进制文件, 并在文件末尾写数据“rt+” 读写打开一个文本文件, 允许读和写“wt+” 读写打开或建立一个文本文件, 允许读写“at+” 读写打开一个文本文件, 允许读, 或在文件末追加数据“rb+” 读写打开一个二进制文件, 允许读和写 “wb+”读写打开或建立一个二进制文件, 允许读和写“ab+”读写打开一个二进制文件, 允许读, 或在文件末追加数据 • r(read): 读w(write): 写a(append): 追加t(text): 文本文件, 可省略不写b(binary): 二进制文件+: 读和写

  10. 说明: • (1) 如果打开一个不存在的文件用于写或追加, 该文件将被创建(如果可能的话)。 • (2) 当以写方式打开一个已存在的文件时, 该文件原来的内容将被覆盖。但是, 如果以追加方式打开一个文件, 则该文件原来的内容保留不变。 • (3) 读一个不存在的文件会导致错误, 其他一些操作也可能导致错误, 比如试图读取一个无读取权限的文件。如果发生错误, fopen将返回NULL。 if(NULL==(fp=fopen("file1", "r"))) { printf("cannot open this file\n"); exit(0); }

  11. (4) 在向计算机输入文本文件时, 将回车换行符转换为一个换行符, 在输出时把换行符转换成为回车和换行两个字符。在用二进制文件时, 不进行这种转换, 在内存中的数据形式与输出到外部文件中的数据形式完全一致, 一一对应。 • (5) 在程序开始运行时, 系统自动打开3个标准文件:标准输入、标准输出、标准出错输出。通常这3个文件都与终端相联系。因此以前我们所用到的从终端输入或输出都不需要打开终端文件。系统自动定义了3个文件指针stdin、stdout和stderr, 分别指向终端输入、终端输出和标准出错输出也从终端输出)。如果程序中指定要从stdin所指的文件输入数据, 就是指从终端键盘输入数据。

  12. 13.3.2 文件的关闭(fclose函数) • 应该养成在程序终止之前关闭所有文件的习惯, 如果不关闭文件将会丢失数据。 • 因为, 在向文件写数据时, 是先将数据输到缓冲区, 待缓冲区充满后才正式输出给文件。如果当数据未充满缓冲区而程序结束运行, 就会将缓冲区中的数据丢失。用fclose函数关闭文件, 可以避免这个问题, 它先把缓冲区中的数据输出到磁盘文件, 然后才释放文件指针变量。 • fclose函数调用的一般形式为:fclose(文件指针); • 例如:fclose(fp); • 关闭操作执行正确, 返回0;否则返回EOF(-1)。

  13. 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)

  14. 2. fgetc函数 • int fgetc(FILE *fp) • 从指定的文件返回下一个字符, 该文件必须是以读或读写方式打开的。 • 如果遇到文件结束符, 函数返回一个文件结束标志EOF(-1)。 • 如果想从一个磁盘文件顺序读入字符并在屏幕上显示出来, 可以: ch=fgetc(fp); while(!feof(fp)) { putchar(ch); ch=fgetc(fp); }

  15. 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); }

  16. ch=getchar();/*此语句用来接收在执行scanf语句时最后输入的回车符*/ch=getchar();/*此语句用来接收在执行scanf语句时最后输入的回车符*/ ch=getchar();/*接收输入的第一个字符*/ while(ch!='#') { fputc(ch, fp); putchar(ch); ch=getchar(); } fclose(fp); }

  17. 运行情况如下: file1.c (输入磁盘文件名) computer and c#(输入一个字符串) computer and c (输出一个字符串)

  18. 例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);

  19. 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); }

  20. 运行情况如下: Enter the infile name: file1.c(输入原有磁盘文件名) Enter the outfile name: file2.c(输入新复制的磁盘文件名) • 也可以用此程序来复制一个二进制文件, 只需将两个fopen函数中的“r”和“w”分别改为“rb”和“wb”即可。 • 也可以在输入命令行时把两个文件名一起输入。这时要用到main函数的参数。程序可改为

  21. 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); }

  22. while(!feof(in)) fputc(fgetc(in), out); fclose(in); fclose(out); }

  23. 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的值。

  24. 如果文件以二进制形式打开, 用fread和fwrite函数就可以读写任何类型的信息, 如: • fread(f, 4, 2, fp); • 其中f是一个实型数组名。一个实型变量占4个字节。 • 这个函数从fp所指向的文件读入2次(每次4个字节)数据, 存储到数组f中。

  25. 如果有一个如下的结构体类型: #define SIZE 4 struct student_type { char name[10]; int num; int age; char addr[15]; }stud[SIZE];

  26. 例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); }

  27. 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(); }

  28. void load() { FILE *fp; int i; if ((fp=fopen("c:\\a.txt", "rb"))==NULL) { printf("cannot open infile\n"); return; }

  29. 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); }

  30. 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(); }

  31. 请注意输入输出数据的状况。从键盘输入4个学生的数据是ASCII码, 也就是文本文件。在送到计算机内存时, 回车和换行符转换成一个换行符。再从内存以“wb”方式(二进制写)输出到“stu_list”文件, 此时不发生字符转换, 按内存中存储形式原样输出到磁盘文件上。在上面验证程序中, 又用fread函数从“stu_list”文件向内存读入数据, 注意此时用的是“rb”方式, 即二进制方式, 数据按原样输入, 也不发生字符转换。也就是这时候内存中的数据恢复到第一个程序向 “stu-list”输出以前的情况。最后在验证程序中, 用printf函数输出到屏幕, printf是格式输出函数, 输出ASCII码, 在屏幕上显示字符。换行符又转换为回车加换行符。

  32. 如果企图从“stu_list”文件中以“r”方式读入数据就会出错。如果企图从“stu_list”文件中以“r”方式读入数据就会出错。 • fread和fwrite函数一般用于二进制文件的输入输出。因为它们是按数据块的长度来处理输入输出的, 在字符发生转换的情况下很可能出现与原设想的情况不同。 • 例如, 如果写fread(&stud[i], sizeof(struct student-type), 1, stdin);

  33.  企图从终端键盘输入数据, 这在语法上并不存在错误, 编译能通过。如果用以下形式输入数据: • Zhang 100110room-101 • … • 由于fread函数要求一次输入29个字节(而不问这些字节的内容), 因此输入数据中的空格也作为输入数据而不作为数据间的分隔符了。连空格也存储到stud[i]中了, 显然是不对的。 • 这个题目要求的是从键盘输入数据, 如果已有的数据已以二进制形式存储在一个磁盘文件“stu-dat”中, 要求从其中读入数据并输出到“stu-list”文件中, 可以编写一个load函数, 从磁盘文件中读二进制数据。

  34. void load() {FILEfp; 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)

  35. {if(feof(fp)){fclose (fp); return;} printf("file read error\n"); } fclose (fp); }   将load函数加到本题原来的程序文件中, 并将main函数改为main() { load(); save(); }

  36. 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, 则输出到磁盘文件上的是以下的字符串:

  37. 3, 4.50 • 同样, 用以下fscanf函数可以从磁盘文件上读入ASCII字符:fscanf(fp, "%d, %f", &i, &t); • 磁盘文件上如果有以下字符: • 3, 4.5 • 则将磁盘文件中的数据3送给变量i, 4.5送给变量t。 • 用fprintf和fscanf函数对磁盘文件读写, 使用方便, 容易理解, 但由于在输入时要将ASCII码转换为二进制形式, 在输出时又要将二进制形式转换成字符, 花费时间比较多。因此, 在内存与磁盘频繁交换数据的情况下, 最好不用fprintf和

  38. 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函数如下:

  39. putw(int i, FILE *fp) {chars; 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个字节。由于

  40.  *(s+0)就是s[0], *(s+1)就是s[1], 因此, s[0]、s[1]分别对应i的第1字节和第2字节。顺序输出s[0]、s[1]就相当于输出了i的两个字节中的内容。见图13.3。 图13.3

  41. getw函数如下: getw(FILE *fp) {chars; int i; s=(char *)&i;/*使s指向i的起始地址*/ s[0]=getc(fp); s[1]=getc(fp); return(i); } • putw和getw并不是ANSI C标准定义的函数。但许多C编译都提供这两个函数, 但有的C编译可能不

  42.  以putw和getw命名此两函数, 而用其他函数名, 请用时注意。 2.  读写其他类型数据 • 如果用ANSI C提供的fread和fwrite函数, 读写任何类型数据都是十分方便的。如果所用的系统不提供这两个函数, 用户只好自己定义所需函数。例如, 可以定义一个向磁盘文件写一个实数(用二进制方式)的函数putfloat: putfloat(float num, FILE *fp) {chars;

  43. int count; s=(char *)&num; for(count=0;count<4;count++) putc(s[count], fp); } • 同样可以编写出读写任何类型数据的函数。 3. fgets函数和fputs函数 • fgets的作用是从指定文件读入一个字符串。如: • fgets(str, n, fp); • n为要求得到的字符, 但只从fp指向的文件输入n-1个字符, 然后在最后加一个‘\0’字符, 因此得到

  44. 的字符串共有n个字符。把它们放到字符数组str中。如果在读完n-1个字符之前遇到换行符或EOF, 读入即结束。fgets函数返回值为str的首地址。 • fputs函数的作用是向指定的文件输出一个字符串。如:fputs("China", fp);把字符串“China”输出到fp指向的文件。fputs函数中第一个参数可以是字符串常量、字符数组名或字符型指针。字符串末尾的‘\0’不输出。若输出成功, 函数值为0;失败时, 为EOF。 • 这两个函数类似以前介绍过的gets和puts函数, 只是fgets和fputs函数以指定的文件作为读写对象。

  45. 13.5 文 件 的 定 位 • 文件中有一个位置指针, 指向当前读写的位置。如果顺序读写一个文件, 每次读写一个字符, 则读写完一个字符后, 该位置指针自动移动指向下一个字符位置。如果想改变这样的规律, 强制使位置指针指向其他指定的位置, 可以用有关函数。 13.5.1rewind函数 • rewind函数的作用是使位置指针重新返回文件的开头。此函数没有返回值。 • 例13.4有一个磁盘文件, 第一次将它的内容显示在屏幕上, 第二次把它复制到另一文件上。

  46. #include<stdio.h> main() {FILEfp1, *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函数, 使文件的位置指针重

  47.  新定位于文件开头, 并使feof函数的值恢复为0(假)。 13.5.2fseek函数和随机读写 • 对流式文件可以进行顺序读写, 也可以进行随机读写。关键在于控制文件的位置指针, 如果位置指针是按字节位置顺序移动的, 就是顺序读写。如果能将位置指针按需要移动到任意位置, 就可以实现随机读写。所谓随机读写, 是指读写完上一个字符(字节)后, 并不一定要读写其后续的址?字节), 而可以读写文件中任意所需的字符(字节)。 • 用fseek函数可以实现改变文件的位置指针。

  48. 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个字节处

  49. fseek(fp, 50L, 1); 将位置指针移到离当前位置50个字节处 • fseek(fp, -10L, 2); 将位置指针从文件末尾处向后退10个字节 • 利用fseek函数就可以实现随机读写了。 • 例13.5在磁盘文件上存有10个学生的数据。要求将第1、3、5、7、9个学生数据输入计算机, 并在屏幕上显示出来。 • 程序如下: #include<stdio.h>

More Related