490 likes | 625 Views
C 语言大学实用教程. 第 10 章 文件操作. 哈尔滨工业大学计算机科学与技术学院 苏小红 sxh@hit.edu.cn. I/O 设备. 输入设备 键盘、鼠标 软盘、硬盘、光驱(以文件的形式) 串行口、并行口、 USB 接口、网络端口 扫描仪、视频采集卡、电视卡、游戏杆、话筒 …… 输出设备 显示器、打印机 软盘、硬盘、 CD-R/DVD-R (以文件的形式) 串行口、并行口、 USB 接口、网络端口 音箱 …… 单纯的输入设备或者单纯的输出设备越来越少. 标准输入输出. 字符界面的操作系统一般都提供标准输入与输出设备
E N D
C语言大学实用教程 第10章 文件操作 哈尔滨工业大学计算机科学与技术学院 苏小红sxh@hit.edu.cn
I/O设备 • 输入设备 • 键盘、鼠标 • 软盘、硬盘、光驱(以文件的形式) • 串行口、并行口、USB接口、网络端口 • 扫描仪、视频采集卡、电视卡、游戏杆、话筒 • …… • 输出设备 • 显示器、打印机 • 软盘、硬盘、CD-R/DVD-R(以文件的形式) • 串行口、并行口、USB接口、网络端口 • 音箱 • …… • 单纯的输入设备或者单纯的输出设备越来越少
标准输入输出 • 字符界面的操作系统一般都提供标准输入与输出设备 • DOS、Linux、Unix…… • 一般情况,标准输入就是键盘,标准输出就是终端显示器 • 操作系统有能力重定向标准输入与输出,比如让文件作为标准输入,打印机作为标准输出 • 这种重定向程序本身是感觉不到的 自来水厂
DOS下的标准输入输出重定向 • 程序prog如下 • main(){char c;while ((c=getchar()) != '\n') putchar(++c);} • 输入重定向 • prog < infile • 输出重定向 • prog > outfile
流(Stream) • 水流 • “子在川上曰:逝者如斯夫” • 计算机中的流的概念 • 一般称为数据流,也有叫做字节流、比特流的,还有很具体的文件流、视频流、音频流等 • 时光不能倒流,但计算机中的很多流都是会倒流的 • 如果你想重新读已经读过的数据,或者要修改已经写入的数据,可以发出流控(Flow Control)命令 • 不会倒流的数据流也很多,例如网络上的数据流。网络和数据线等介质只有很小的数据缓冲区,没有大量存储的能力
文件(File)的概念 • 计算机的内存容易健忘,所以数据必须保存在硬盘、软盘、光盘和磁带等“不健忘”的外存上 • 这些能大量、永久保存信息的媒介,一般都以文件的形式给用户及应用程序使用 • 文件 • 一般指存储在外部介质上具有名字(文件名)的一组相关数据的集合 • 用文件可长期保存数据,并实现数据共享 • 程序中的文件 • 在程序运行时由程序在磁盘上建立一个文件,并通过写操作将数据存入该文件;或由程序打开磁盘上的某个已有文件,并通过读操作将文件中的数据读入内存供程序使用
文件与流 • 在C语言中,文件可以是磁盘文件、终端显示器或打印机等等。 • 程序通过打开操作把流与设备联系起来,文件打开后,可以在程序和文件之间交换数据。 • 程序通过关闭操作断开流与文件的联系。 • 所有流的性质都一样。因为流与设备无关,所以能写入磁盘文件的同一函数也能写入另一设备,如控制台终端等。 • 但文件的能力则可能不同。例如,磁盘文件可以支持随机存取,而键盘则不行。
文件的存放 • 可以建立若干目录(文件夹),在目录里保存文件,同一级目录里保存的文件不能同名。 • 对使用者而言,只要知道文件的路径(全目录)和文件名,就能使用该文件 • C:\home\Sunner\main.c • 这都是托OS的福
文件的格式 • 二进制文件 • 是一种字节序列,没有字符变换 • 按照数据在内存中的存储形式存储到文件 • 如整数127,在内存占2个字节,为0000000001111111,则文件中也存储为0000000001111111,占2个字节 • 文本文件/ASCII码文件 • 是一种字符序列 • 文件中存储每个字符的ASCII码 • 如整数127在文件中占3个字节,分别存放这3个字符的ASCII码,即49,50,55
int 100 float 100.0 字符串"100" 字符串"END" 64 00 00 00 C8 42 31 30 30 00 45 4E 44 00 D È B 1 0 0 E N D TEST.BIN内容(十六进制) 对应的ASCII字符 文件的格式 • 数据必须按照存入的类型读出,才能恢复其本来面貌 • 公开的标准格式 • 如bmp、tif、gif、jpg和mp3等类型的文件 ,有大量软件能生成和使用这些类型的文件 • 也有不公开、甚至加密的文件格式 • 如Microsoft Word的doc格式就不公开,所以至今还没有Word以外的其它软件能完美地读出doc文件
文件访问 • 文件访问的基本模式 • open:打开文件,获得对此文件的指针、引用和句柄等,以证明可以使用此文件 • read:读文件。参数一般指明要读多少字节,读到哪块内存。每次调用此功能,都是接着上次调用的结束位置读。(所以是个输入流) • write:写文件。参数一般指明把哪块内存的内容写入文件,要写多少字节。每次调用此功能,都是接着上次调用的结束位置写。 (所以是个输出流) • close:关闭文件,表明操作结束,不再使用此文件。文件使用完毕必须关闭,否则影响系统性能 • seek:随机控制流的当前位置,文件定位
C语言中的文件访问 • 下面介绍的函数均定义在<io.h>和<fcntl.h>中 • int open(constchar *pathname, int access); • int fd = open("C:\\CONFIG.SYS", O_RDWR | O_CREAT); • pathname是文件名,包含路径。如果不含路径,表示打开当前目录下的文件 • access是打开方式,常用为O_RDONLY、O_WRONLY、O_RDWR与O_CREAT、O_TRUNC、O_APPEND的或运算 • 返回值为文件句柄(File Handle),留待以后使用。如果打开失败,返回值为-1
C语言中的文件访问 • int read(int handle,void *buf,unsigned len); • int n_read = read(fd, buf, BUFSIZ); • handle是open获得的文件句柄 • buf是保存读入内容的内存指针 • len是最大可以读入的字节数 • 返回值为实际读入的字节数,可能大于len。返回0表示读到了末尾,返回-1表示出错
C语言中的文件访问 • int write(int handle,constvoid *buf,unsigned len); • int n_write = write(fd, buf, BUFSIZ); • handle是open获得的文件句柄 • buf是要写入内容的内存指针 • len是写入多少字节 • 返回值为实际写入的字节数,可能小于len。返回-1表示出错
C语言中的文件访问 • int close(int handle); • int ret = close(fd); • handle是open获得的文件句柄 • 关闭成功返回0,否则返回-1
C语言中的文件访问 • long lseek(int handlelong offsetint fromwhere); • int pos = lseek(fd, 100L, SEEK_CUR); • handle是open获得的文件句柄 • offset是相对fromwhere的位置偏移多少,可以为负数 • fromwhere可以是SEEK_SET、SEEK_CUR或SEEK_END中的一个,分别表示文件头、当前位置和文件结尾 • 成功返回移位后的当前位置,从文件头算起;否则返回-1L • 几个巧妙运用 • curPos = lseek(fh, 0L, SEEK_CUR); • fileLength = lseek(fh, 0L, SEEK_END); • 得到文件长度
C语言独特的文件访问 • 下面介绍的函数均定义在<stdio.h>中 • FILE *fopen(constchar *filename,constchar *mode); • FILE *fp = fopen("C:\\CONFIG.SYS", "rw"); • filename是文件名,包含路径。如果不含路径,表示打开当前目录下的文件 • mode是打开方式,常用为"r"、"w"、"rw"和"a",分别表示只读、只写、读写和添加 • 返回值为指向此文件的指针,留待以后使用。如果打开失败,返回值为NULL
“r”只读 必须是已存在的文件。 “w” 只写 不论该文件是否存在,都新建一个文件。 “a”追加 向文本文件尾增加数据,该文件必须存在“r+” 读写 打开一个已存在的文件,用于读写。 “w+”读写 建立一个新文件,可读可写。 “a+”读写 向文件尾追加数据,也可读。 对应二进制文件 对应文本文件 “rb” “wb” “ab” “rb+” “wb+” “ab+” • mode:
文件指针 • FILE *fp ; • 是FILE型指针变量 • 标识一个特定的磁盘文件 • 与文件相关联的每个流都有一个FILE类型的控制结构,定义有关文件操作的信息,用户绝对不应修改
文件指针(File Pointer) • typedefstruct { short level; /*缓冲区‘满’或‘空’的程度*/ unsigned flags; /*文件状态标志*/ char fd; /*文件描述符*/ unsigned char hold; /*如无缓冲区不读字符*/ short bsize; /*缓冲区的大小*/ unsigned char *buffer;/*数据缓冲区的位置*/ unsigned char *curp; /*指针当前的指向*/ unsigned istemp; /*临时文件指示器*/ short token; /*用于有效性检查*/ }FILE; • 在stdio.h文件中定义
C语言独特的文件访问 • int fgetc(FILE *fp); • int fputc(int c, FILE *fp); • char *fgets(char *s,int n, FILE *fp); • int fputs(constchar *s, FILE *fp); • int fscanf(FILE *fp, constchar *format, ...); • int fprintf(FILE *fp,constchar *format, ...);
C语言独特的文件访问 • size_t fread(void *ptr, size_t size, size_t n, FILE *fp); • size_t fwrite(constvoid *ptr, size_t size, size_t n, FILE *fp); • int feof(FILE *fp); • int fseek(FILE *fp, long offset,int whence); • int fclose(FILE *fp);
错误处理 • 错误处理 • 文件错误一般都是外界造成的,出错率很高 • 被删除、修改、磁盘空间满、被其他文件打开 • 通过判断返回值发现错误 • 所有文件操作出错时都返回-1 • 出错处理 • 打印错误信息给用户,等待用户的处理 • void perror(const char *s); • 向标准错误输出字符串s,随后附上错误的文字说明
错误处理——例10.1 #include <stdio.h> #include <errno.h> #include <io.h> #include <fcntl.h> main() { /* c:\abc.abc文件并不存在 */ int fh = open("c:\\abc.abc", O_RDONLY | O_BINARY); if (fh == -1) /* fh必然为-1 */ { perror("Can't open c:\\abc.abc. Error"); } } Can't open c:\\abc.abc. Error: No such file or directory
两种方式的区别 • open族的功能一般由OS直接提供,其使用方式也比较具有通用性,在各种语言里基本一样 • fopen族的函数系包装了open族的函数,提供更强大的功能,但是效率略逊 • open族通常情况能直接反映文件的真实情况,因为它的操作都不假定文件的任何结构 • fopen族比较适合处理文本文件,或者结构单一的文件。会为了处理的方便而改变一些内容
从文件说开去 • open、read、write、close这种模式成为使用数据流的经典模式 • 比如网络数据流操作就是使用此模式,甚至在一些平台上可以和文件采用完全相同的函数操作 • seek并不是所有的流都支持 • 网络就不支持 • 对于不能进行流控的流,如果当前数据不立即处理,后来流过的数据就会冲走当前数据,永远不能追回。流控成为流处理中的一个专门技术
例如: fp=fopen(“a1.txt”,”r”); fp=fopen(“d:\\sqs\\a1.txt”,”r”); fopen函数返回指向文件a1.txt的指针并赋给fp,即fp指向文件a1.txt. • 若不能实现打开任务,fopen函数将带回一个空指针值NULL(值为0)。 • 常用打开文件的方法为: if((fp=fopen(“a1.txt”,”r”))==NULL) { printf(“cannot onen this file\n”); exit(0); }
文件的关闭:fclose函数 fclose(文件指针); 关闭fp所指向的文件。把遗留在缓冲区中的数据写入文件,实施操作系统级的关闭操作。同时,释放与流联系的文件控制快,以后可以再次使用这部分空间。 多数情况下,系统限制同时处于打开状态的文件总数,因此,打开文件前先关闭无用文件是必要的 例如:fclose(fp); fclose函数的返回值: 当顺利地执行了关闭操作,则返回值为0; 如果返回值为非零值,则表示关闭时有错误。一般只有驱动器中无盘或盘空间不够时,才失败。 关闭失败会引起数据丢失、破坏文件和程序中的随机错误
二、文件的字符读写 fgetc和fputc函数 fputc (ch,fp); ---把字符ch写到fp所指向的文件中去。 该文件应以写或读写方式打开。 返回值:若输出成功,则返回输出的字符,否则返回EOF。 ch=fgetc (fp); ---从指定文件读入一个字符。 该文件应以只读或读写方式打开。 返回值:若输入成功,则返回字符,否则返回一个EOF。 EOF:在stdio.h中定义的常量,表示文件结束 #define EOF (-1)
例:将键盘输入的字符顺序存入磁盘文件tt.txt中,输入ctrl+z结束例:将键盘输入的字符顺序存入磁盘文件tt.txt中,输入ctrl+z结束 #include “stdio.h” main() { char c; FILE *fp; if((fp=fopen(“tt.txt”, “w”))==NULL) { printf(“error!\n”): exit(0); } c=getchar(); while(c!=EOF) { fputc(c,fp); c=getchar(); } fclose(fp); } 程序文件名为f2.c,程序运行结果是生成文件tt.txt;
例:从文件tt.txt中顺序读取字符并显示出来。 #include “stdio.h” main() { char c; FILE *fp; if((fp=fopen(“tt.txt”, “r”))==NULL) { printf(“error!\n”): exit(0); } c=fgetc(fp); while(c!=EOF) { putchar(c); c=fgetc(fp); } fclose(fp); }
例:从文件tt.txt中顺序读取字符并显示出来。 #include “stdio.h” main() { char c; FILE *fp; if((fp=fopen(“tt.txt”, “r”))==NULL) { printf(“error!\n”): exit(0); } c=fgetc(fp); while(c!=EOF) { putchar(c); c=fgetc(fp); } fclose(fp); }
三、文件的数据块的读写—fread和 fwrite函数 fwrite()函数的功能: 将内存中的一组数据写到fp所指向的文件中。 按照内存中的存储形式输出,为二进制文件。 fwrite(buff, size, count, fp) 指向写入文件的指针 所要写入文件的数据在内存中的起始地址 写入文件的数据项数 写入文件的每个数据项的字节数 函数调用成功返回count的值,否则返回-1。
例1:文件的数据块写的操作 从键盘输入n个同学 的学号,姓名和成绩存入文件。 #include “stdio.h” typedef struct student STUD; struct student { int num; char name[12]; int score; };
main() { STUD stu; /*stu为结构体变量*/ FILE *fp; char filename[12]; int i,n; scanf(“%s”,filename); if((fp=fopen(filename, “wb” ))==NULL) { printf(“can’t open the file!\n”); exit(0); } scanf(“%d”, &n); fwrite(&n,2,1,fp); for(i=0; i<n; i++) { scanf(“%d %s %d”, &stu.num, stu.name, &stu.score); if( fwrite( &stu, sizeof(STUD), 1, fp) != 1 ) /*写入一个数据项*/ { printf(“file write error!”); exit(0); } } fclose(fp); } program1
program2 main() { STUD stu[30];/*stu[30]为结构体数组*/ FILE *fp; int i,n; char filename[12]; scanf(“%s”,filename); if((fp=fopen(filename, “wb” ))==NULL) { printf(“can’t open the file!\n”); exit(0); } scanf(“%d”,&n); fwrite(&n,2,1,fp); for(i=0; i<n; i++) scanf(“%d %s %d”, &stu[i]. num,stu[i].name, &stu[i].score); fwrite( stu, sizeof(STUD), n, fp);/*一次写入n个数据项*/ fclose(fp); }
fread()函数: 功能: 在fp所指向的文件中读取一组数据并将其放到内存中去。 fread(buff, size, count, fp) 指向待读文件的指针 待读文件的数据在内存中的起始地址 从文件读入的数据项数 从文件读入的每个数据项的字节数 函数调用成功返回count的值,否则返回-1。
例2:文件的数据块读的操作 #include “stdio.h” typedef struct student STUD; struct student { int num; char name[12]; int score; }; main() { STUD stu; FILE *fp; int i,n; char filename[12]; scanf(“%s”,filename); if((fp=fopen(filename, “rb”))==NULL) { printf(“can’t open the file!\n”); exit(0); }
fread(&n,2,1,fp); printf(“n=%d”,n); for(i=0; i<n; i++) { if(fread( &stu, sizeof(STUD), 1, fp) != 1 )/*读入一个*/ { printf(“file read error!”); exit(0); } printf(“%d\t%s\t%d\n”,stu.num, stu.name, stu.score); } fclose(fp); }
main() { STUD stu[20]; …… fread(&n,2,1,fp); printf(“n=%d”,n); if(fread( stu, sizeof(STUD) , n , fp ) ! = n )/*读入n个*/ { printf(“file read error!”); exit(0); } for(i=0; i<n; i++) printf(“%d\t%s\t%d\n”, stu[i].num, stu[i].name, stu[i].score); fclose(fp); }
四、文件的格式化读写(文本文件) • fprintf函数 格式:fprintf(fp,format,arg1,arg2,…,argn); 将输出项arg1,arg2…argn按指定format格式写入fp所指的文件中,即输出到磁盘文件; • fscanf函数 格式:fscanf(fp,format,&arg1,&arg2,…&argn); 按指定format格式从fp所指的文件中读取数据依次送入arg1,arg2…argn的内存单元; • fp: 为指向fopen函数打开的文件的指针 • format :为格式字符串 • arg1,arg2,…argn(&arg1,&arg2,…&argn)输出/输入项表列
最好给出长度,读写统一 fprintf ( fp,“%d,%6.2f”,i,t ); 将整型变量i和实型变量t的值按%d和%6.2的格式输出到fp指向的文件中。 fscanf ( fp,“%d,%6.2f”,&i,&t );
例1:文件的格式化写操作 #include “stdio.h” typedef struct student STUD; struct student { int num; char name[12]; int score; }; main() { STUD stu; FILE *fp; int i,n; char filename[12]; scanf(“%s”,filename);
if((fp=fopen(filename, “w”))==NULL) /*新建一个文件*/ { printf(“can’t open the file!\n”); exit(0); } scanf(“%d”,&n); fprintf(fp, “%4d”,n); for(i=0; i<n; i++) { scanf(“%d%s%d”,&stu.num, stu.name, &stu.score); fprintf(fp, “%6d%10s%3d”,stu.num, stu.name, stu.score); } fclose(fp); }
例2:文件的格式化读操作 #include “stdio.h” typedef struct student STUD; struct student { int num; char name[12]; int score; }; main() { STUD stu; FILE *fp; int i,n; char filename[12]; scanf(“%s”,filename);
if((fp=fopen(filename, “r”))==NULL) /*新建一个文件*/ { printf(“can’t open the file!\n”); exit(0); } fscanf(fp,“%6d”,&n); printf(“n=\n”,n); for(i=0; i<n; i++) { fscanf(fp, “%6d%10s%3d”,&stu.num,stu.name,&stu.score); printf(“%d\t%s\t%d\n”,stu.num,stu.name,stu.score); } fclose(fp); }
五、文件的定位 • 文件指针 : 打开的文件中有一个位置指针指示动迁的读写位置,对文件每进行一次读写,文件指针自动指向下一个读写位置,则可方便地进行顺序读写,利用定位函数还可实现随机读写. • rewind ()函数 功能:使文件指针重新指向文件的开头位置。 此函数没有返回值 形式: rewind(fp) fp为指向fopen函数打开的文件的指针。
fseek()函数: 利用该函数可以改变文件指针的位置,从而实现随机读写 形式: fseek(fp,offset,from); • fp:为指向fopen函数打开的文件的指针; • offset:文件指针的位移量,字节数,long型,加l或L; • from:起始位置 0----文件开始 1-----当前位置 2-----文件末尾 • ftell()函数: 取得文件指针的当前位置,用字节数,long类型。
六、出错的控制 • ferror()函数 形式:ferror(fp); 功能:检查输入输出函数的调用是否正确,返回非0值,表示出错。 • feof()函数 形式:feof(fp); 功能:检测文件是否结束,返回非0值,表示结束。