1、C文件概述
C语言把文件看作一个字符的序列,即由一个一个字符(字节)的数据顺序组成。根据数据的组织形式,可分为ASCII文件和二进制文件。ASCII文件有称为文本(text)文件,它的每个字节存放一个ASCII代码,代表一个字符。二进制文件是把内存中的数据按其在内存中的存储形式原样输出。如果一个数10000,在内存中占2个字节。如果按照ASCII存储则占5个字节,如按二进制存储则占2字节。如下:
二进制形式 | 内存中样式 | ASCII形式 |
00100111 0001000 | 00100111 0001000 | 00110001 ----> 1 00110000 ----> 0 00110000 ----> 0 00110000 ----> 0 00110000 ----> 0 |
由前述,一个C文件是一个字符流或二进制流。在C语言中文件的存取以字符(字节)为单位。输入输出的数据流的开始和结束仅受程序控制而不受物理符号(如回车换行符)控制。也就是说,在输出时不会增加回车换行符作为记录结束的标志,输入时不以回车换行符作为间隔。这种文件称为流式文件。
在过去使用C版本有两种对文件的处理方式:a、缓冲文件系统 b、非缓冲文件系统。缓冲文件系统在每次向文件读写时都进过缓冲区。
2、文件类型指针
缓冲文件系统中,关键的概念是“文件指针”。每个被使用的文件在内存中开辟一个区,用来存放文件的有关信息。这些信息保存在一个结构体变量中。该结构体有系统定义的,取名为FILE。在Turbo C中stdio.h对文件定义如下:
typedef struct { short level; /* 缓冲区“满”或“空”的程度 */ unsigned flags; /* 文件状态标志 */ char fd; /* 文件描述符 */ unsigned char hold; /* 如无缓冲区不读去字符 */ short bsize; /* 缓冲区的大小 */ unsigned char *buffer; /* 数据缓冲区的位置 */ unsigned ar *curp; /* 指针,当前的指向 */ unsigned istemp; /* 临时文件,指示器 */ short token; /* 用于有效性检查 */ } FILE;
有了文件FILE结构体类型后,就可以定义文件类型的变量和指针变量。如:
FILE f[5] 定义一个文件数组
FILE *fp 定义一个文件指针
3、文件的打开和关闭
ANSI C规定了标准输入输出函数库,用fopen()函数来实现打开文件。fopen函数调用方式通常为:
FILE * fp; fp = fopen(文件名, 使用文件方式);
文件使用方式 | 含义 |
“r”(只读) | 为输入打开一个文本文件 |
“w”(只写) | 为输出打开一个文本文件 |
“a”(追加) | 向文本文件尾添加数据 |
“rb”(只读) | 为输入打开一个二进制文件 |
“wb”(只写) | 为输出打开一个二进制文件 |
“ab”(追加) | 向二进制文件末尾添加数据 |
“r+”(读写) | 为读写打开一个文本文件 |
“w+”(读写) | 为读写建立一个新的文本文件 |
“a+”(读写) | 为读写打开一个文本文件 |
“rb+”(读写) | 为读写打开一个二进制文件 |
“wb+”(读写) | 为读写建立一个新的二进制文件 |
“ab+”(读写) | 为读写打开一个二进制文件 |
说明:
(1)用"r"打开的文件只能用于向计算机输入文件,而且该文件已经存在。不能打开不存在的文件,否则会报错。
(2)用"w"打开的文件只能用于输出文件。如果不存在文件,则在打开是新建一个以指定名称的文件。如果存在,则删除存在的文件,然后重新建立一个新文件。
(3)如果使用"a"方式打开则可以向文件末尾追加数据,但是该文件必须已经存在。打开时,位置指针移动到文件末尾。
(4)使用"r+"、"w+"、"a+"方式打开的文件仅可以读也可以写。用“r+”打开的文件必须存在,用"w+"删除旧文件,重新建立新文件。用"a+"方式打开不会删除文件。
(5)如果打开文件失败,fopen()函数返回一个NULL(NULL在stdio.h文件中定义为0)。如:
if((fp = fopen("file1", "r")) == NULL) { printf("不能打开这个文件\n"); exit(0); }
(6)在向计算机输入文本文件时,回车换行符被替换成一个换行符,输出时转换成回车换行符。二进制文件,则原样保存。
(7)在程序开始运行时,系统自动打开3个标准输入输出。stdin标准输入、stdout标准输出、stderr标准出错输出。
常用函数简介:
1、fclose(文件指针)函数用于关闭文件,关闭成功返回0。否则返回EOF(-1)。如:fclose(fp);
2、fputc(输出字符,文件指针)函数把一个字符写到磁盘上去。如:fputc(ch,fp)表示将ch字符输出到fp指向的文件中去。输出成功返回输出的字符,否则返回EOF
3、putchar函数是从fputc函数派生而来。#define putchar(c) fputc(c, stdout)
4、fgetc(文件指针)函数从指定的文件中读取一个字符,该文件必须以读或读写方式打开的。如:ch = fgetc(fp);从fp指定的文件中取出一个字符,赋给ch变量。
ch = fgetc(fp); while( ch != EOF) { putchar( ch ); ch = fgetc( fp); }
以上是将一个文件以顺序显示在屏幕上。
注意:
EOF不是可输出字符,因为ASCII码不可能出现-1。当读取文件等于-1(EOF)时,表示读入的已不是正常字符而是文件结束符。但是值适合于文本文件。当读取二进制文件时,读取的数据可能为-1,因此ANSI C提供了feof(文件指针)函数来判断文件是否已经结束。如果结束,feof(文件指针)返回值为1(真),否则为0(假);
如:
while( !feof(fp) ) { c = fgetc( fp ); }
可以顺利的读取一个二进制文件
【实例】从键盘输入一个字符串,逐个把他们写到磁盘文件上,知道输入‘#’为止。
#include <stdio.h> #include <stdlib.h> void main() { FILE * fp; char ch, filename[10]; scanf("%s", filename); // 提示用户输入文件名 if((fp = fopen(filename, "w")) == NULL) { printf("不能打开文件\n"); exit(0); // 终止程序 } ch = getchar(); // 用来接收在执行scanf语句时最后输入的回车符 ch = getchar(); // 接受输入的第一个字符 while(ch != '#') { fputc(ch, fp); // 写入到文件中去 putchar(ch); ch = getchar(); } putchar(10); // 向屏幕输出一个换行符 fclose(fp); }
【实例】将文件中的数据输出到屏幕上
#include <stdio.h> #include <stdlib.h> void main() { FILE *in; char ch, infile[10]; printf("请输入文件名:\n"); scanf("%s", infile); if( (in = fopen(infile, "r")) == NULL) { printf("打开输入文件失败\n"); exit(0); } // 将文件中的数据输出到屏幕上 while( !feof(in)) { putchar(fgetc(in)); } // 关闭文件 close(in); }
【实例】将一个文件的数据写入到另一个文件中去。
#include <stdio.h> #include <stdlib.h> void main() { FILE *in, *out; char ch, infile[10], outfile[10]; printf("请输入被拷贝文件名:\n"); scanf("%s", infile); printf("请输入拷贝文件名:\n"); scanf("%s", outfile); // 打开输入文件 if( (in = fopen(infile, "r")) == NULL) { printf("打开输入文件失败\n"); exit(0); } // 打开输出文件 if( (out = fopen(outfile, "w")) == NULL) { printf("打开输出文件失败\n"); exit(0); } // 将输入文件的数据写入到输出文件中去 while( !feof(in)) { fputc(fgetc(in), out); } // 关闭文件 close(in); close(out); }
对文件之间拷贝的改进:
#include <stdio.h> #include <stdlib.h> void main(int argc, char * argv[]) // argc-表示参数argv数组的长度 argv-表示参数 { FILE *in, *out; char ch; if(argc != 3) // argv[0]-可执行文件名 argv[0]-输入文件名 argv[0]-输出文件名 { printf("你忘记输入文件名"); exit(0); } if( (in = fopen(argv[1], "r")) == NULL) { printf("打开输入文件失败\n"); exit(0); } if( (out = fopen(argv[2], "w")) == NULL) { printf("打开输出文件失败\n"); exit(0); } // 将文件中的数据输出到屏幕上 while( !feof(in)) { fputc(fgetc(in), out); } // 关闭文件 close(in); }
运行方式:
C:\> fileExe text.txt new.txt
5、fread(buffer, size, count, fp) 和 fwrite(buffer、size、count、fp)
参数介绍:
buffer 要读入/写出存放数据的地址(起始地址)
size 要去读的字节数
count 要读写多少个size字节的数据
fp 文件指针
如:fread(f,4,2,fp)表示从fp中读取2个4字节的数据到数组f中
如果存在一下学生信息结构体:
struct student_type ( char name[20]; -- 学生姓名 int num; -- 学号 int age; -- 年龄 char addr[30]; -- 居住地址 )stud[40];
我们可以使用一下语句读取和写出数据。
for(i = 0; i < 40; i ++) { fread(&stud[i], sizeof(struct student_type), 1, fp); } for(i = 0; i < 40; i ++) { fwrite(&stud[i], sizeof(struct student_type), 1, fp); }
如果调用成功放回count的值。
【实例】从键盘输入4个学生的有关数据,然后将他们存到磁盘文件上。
#include <stdio.h> #include <stdlib.h> #define SIZE 4 struct student_type { char name[20]; // 姓名 int num; // 学号 int age; // 年龄 char addr[15]; // 地址 } stud[SIZE]; void save() { FILE *fp; int i; if((fp = fopen("student_list","wb")) == NULL) { printf("打开student_list文件失败\n"); return; } for(i=0; i<SIZE; i++) { if(fwrite(&stud[i], sizeof(struct student_type), 1 , fp) != 1) { printf("写入错误\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(); }
【实例】将上面写入的数据读取到屏幕上面
#include <stdio.h> #include <stdlib.h> #define SIZE 4 struct student_type { char name[20]; int num; int age; char addr[15]; } stud[SIZE]; void main() { int i; FILE *fp; fp = fopen("student_list", "rb"); for(i = 0; i < SIZE; i ++) { fread(&stud[i], sizeof(struct student_type), 1, fp); printf("%-10s%4d%4d%-15s\n", stud[i].name, stud[i].num, stud[i].age, stud[i].addr); } fclose(fp); }
注意:fread()和fwrite()一般用于二进制文件的输入输出。因为他们是按照数据块的长度来处理输入输出的,在字符串发生转换的情况下很有可能出现与原设想不同的情况。
【实例】将一个已经存在学生基本信息的文件加载到内存,然后写入到新的文件中去。
#include <stdio.h> #include <stdlib.h> #define SIZE 4 struct student_type { char name[20]; int num; int age; char addr[15]; } stud[SIZE]; /** * 将student_list文件中保存的学生信息加载到内存中 */ void load() { FILE *fp; int i; if((fp = fopen("student_list","rb")) == NULL) { printf("打开student_list文件失败\n"); exit(0); } for(i=0; i<SIZE; i++) { if(fread(&stud[i], sizeof(struct student_type), 1, fp) != 1) { if(feof(fp)) { fclose(fp); return; } printf("\n"); } } } /** * 将加载到内存的数据保存到new_student_list文件中 */ void save() { FILE *fp; int i; if((fp=fopen("new_student_list", "wb")) == NULL) { printf("打开new_student_list文件失败\n"); exit(0); } for(i=0; i<SIZE; i++) { if(fwrite(&stud[i], sizeof(struct student_type), 1, fp) != 1) { printf("向new_student_list文件写入数据失败\n"); } } fclose(fp); } void main() { load(); save(); }
6、fprintf( )和fscanf( )函数
这两个函数与printf和scanf函数作用相仿,都是格式化读写函数。只有一点不同:fprintf和fscanf函数的读写对象不是终端而是磁盘文件。它们的一般格式为:
fprintf(文件指针、格式化字符串、输出列表)
fscanf(文件指针、格式化字符串、输入列表)
如:fprintf(fp, "%d,%6.2f", i, c) 写到磁盘文件
fscanf(fp, "%d,%f", &i, &t) 磁盘文件上存在:3,4.5
【实例】将0~9十个数字写入到number.txt文件中
#include <stdio.h> #include <stdlib.h> void main() { FILE *fp; int i; if((fp = fopen("number.txt", "w")) == NULL) { printf("不能打开文件\n"); exit(0); } // 向number.txt文件循环写入0~9数字 for(i=0; i<10; i++){ fprintf(fp, "%d", i); } fclose(fp); }
【实例】从磁盘文件读取数据,存到变量中,然后将变量输出到屏幕上。
#include <stdio.h> #include <stdlib.h> void main() { FILE *fp; int a; float b; if((fp = fopen("number.txt", "r")) == NULL) { printf("不能打开文件\n"); exit(0); } fscanf(fp, "%d,%f", &a, &b); // 20,25.4 printf("a=%d, b=%f\n", a, b); // a=20, b=25.4 fclose(fp); }
用fprintf或fscanf对磁盘文件进行读写,使用方便,容易理解。但由于在输入时要将ASCII码转换成二进制格式,在输出时要将二进制转换成字符,花费时间较多。因而,在内存与磁盘交换频繁时,最好不要用fprintf和fscanf函数,而用fread和fwrite函数。