第十二章 文件  

§12.1 概述 §12.2 文件类型指针
§12.3 文件的打开与关闭 §12.4 文件的读写
§12.5 文件的定位 §12.6 出错的检测 
§12.7 非缓冲文件系统 本章要求

 

§12.1 概述  

一、文件的概念

1、“文件(file)”:是记录在介质上的数据的集合,以文件名作为访问文件的标识。

介质:一般是磁盘、磁带、光盘等。

2、内存文件:在内存中开辟一段空间,以文件的方式存放数据。如、DOS虚拟磁盘驱动程序RAMDRIVE.SYS建立的虚拟磁盘上存放的文件。Windows9.X恢复盘也建立虚拟磁盘,用于临时存放系统命令文件。

3、操作系统把与主机关联的终端也当作文件处理,如:
   键盘: 输入文件
   显示器:输出文件
   打印机:输出文件

4、C语言把文件看作一个字节序列,即由一连串的字节组成,称为“流(stream)” ,以字节为单位访问,没有记录的界限(实际存在界限,例如,在FAT16文件系统中,文件的最大长度是2GB)。

5、按文件所依附的介质来分:有磁盘文件、磁带文件、内存文件、设备文件等。

6、按文件的内容区分:有源程序文件、目标文件、数据文件等。

7、按文件中的数据组织形式来分:数据文件可分为ASCII码文件和二进制文件。

  ASCII码文件,又称为“文本文件”(text),其每一个字节存放一个ASCII码。
  二进制文件,把内存中的数据按其在内存中的存储形式存放在磁盘上。

例、十进制整数10000,
在内存中占两字节,其存放形式是:0010,0111,0001,0000。
在二进制文件中也按这中方式存放。
在ASCII文件中,存放为31H、30H、30H、30H、30H,占五个字节,它们分别是1、0、0、0、0、0字母的ASCII码。

8、按照操作系统对磁盘文件的读写方式,文件可以分为“缓冲文件系统”和“非缓冲文件系统”。

缓冲文件系统:操作系统在内存中为每一个正在使用的文件开辟一个读写缓冲区。

     

非缓冲文件系统:操作系统不开辟读写缓冲区。

 

§12.2 文件类型指针  

要调用一个文件,需要有以下的信息:
  文件当前的读写位置
  与该文件对应的内存缓冲区的地址
  缓冲区中未被处理的字符串
  文件操作方式等

缓冲文件系统会为每一个文件系统开辟一个“文件信息区”,在stdio.h中,它被定义为FILE类型数据。
  typedef struct
   {
    int _fd;    /* 文件号 */
    int _cleft;   /* 缓冲区中剩下的字节数 */
    int _mode;   /* 文件操作模式 */
    char * _nextc; /* 下一个字节的位置 */
    char * _buff; /*文件缓冲区位置 */
   }FILE;

其中:每个成员就是用来存放有关文件的各种信息的数据项。

定义文件指针变量的一般形式为:

   FILE *文件结构指针变量名

例如、FILE *fp;

注意:只有通过文件指针,才能调用相应的文件。

 

§12.3 文件的打开与关闭

文件操作的过程:对磁盘文件的操作必须“先打开,后读写,最后关闭”。

“打开”文件的含义:以某中方式从磁盘上查找指定的文件或创建一个新文件。

   FILE * fopen(const char *filename,const char *mode);

filename: 文件名(可以包含逻辑驱动器、路径、文件名、扩展名)
mode: 打开方式
FILE *:返回值

如:

  FILE *fp;
  fp = fopen("file1","r");

如果成功打开,返回一个指向被打开文件的文件信息区的起始地址;
如果打开失败,返回一个NULL指针。

文件打开方式 含义
"r"(只读) 为输入打开一个文本文件
"w"(只写) 为输出打开一个文本文件
"a"(追加) 为追加打开一个文本文件
"rb"(只读) 为输入打开一个二进制文件
"wb"(只写) 为输出打开一个二进制文件
"ab"(追加) 为追加打开一个二进制文件
"r+"(读写) 为读/写打开一个文本文件
"w+"(读写) 为读/写创建一个文本文件
"a+"(读写) 为读/写打开一个文本文件
"rb+"(读写) 为读/写打开一个二进制文件
"wb+"(读写) 为读/写创建一个二进制文件
"ab+"(读写) 为读/写打开一个二进制文件

说明:

1、打开文件的方式,必须与文件的属性兼容。例如,不能以"w"和"a"方式打开具有“只读”属性的文件。但可以以任何方式打开“隐藏”属性文件。

2、DOS/Windows中,无“只写”文件属性,一个文件可写,同时意味着可读。

3、用带"r"的方式("r"、"rb"、"r+"、"rb+")打开文件时,若文件不存在,则返回NULL指针。一般用以下方式检查是否成功打开:

   FILE *fp;
   if ((fp=fopen("file1", "r")) = =NULL )
   {
    printf("cannot open this file\n");
    exit(0);
   }

 exit(0)是程序返回操作系统。

4、在Turbo C2.0中,用带"a"或带"w"的方式打开文件时,若文件不存在,则创建该文件。但fopen()函数仍可能因为磁盘写保护或目录满而失败。

5、在Turbo C2.0中,用带"a"或带"w"的方式打开文件时,若文件已存在,"a"方式在原文件后追加数据,"w"方式把原文件删除、并重新创建一个相同名字的文件。

文件的关闭:“关闭”文件的作用:

(1)使文件指针fp与文件脱离。

(2)刷新文件输入/输出缓冲区。

关闭方法:

   fclose(fp);

 

§12.4 文件的读写

一、fputc()/fgetc()

int fputc(int c, FILE *fp)----把字符c写入文件fp,成功时返回字符c的ASCII码,失败时返回EOF(在stdio.h中,符号常量EOF的值等于-1)。

int fgetc(FILE *fp)----从文件fp中读一个字符,返回读得的字符。
对于文本文件,遇文件尾时返回EOF。
对于二进制文件,用feof(fp) 判别是否遇文件尾。feof(fp)=1说明遇文件尾。

例、从文本文件test中顺序读入文件内容,并在屏幕上显示出来。

#include "stdio.h"

void main()
{
 FILE *fp;
 char ch;
 fp = fopen("test", "r");
 if (fp == NULL)
  {
   printf("can not open test\n");
   exit(0);
  }
 ch = fgetc(fp);
 while(ch != EOF)
 {
  putchar(ch);
  ch = fgetc(fp);
 }

 fclose(fp);
}

例、从二进制文件test中读入文件内容.

#include "stdio.h"

void main()
{
 FILE *fp;
 char ch;
 fp = fopen("test", "rb");
 if (fp == NULL)
  {
   printf("can not open test\n");
   exit(0);
  }

 while(!feof(fp))
 {
  ch = fgetc(fp);
  ....
 }

 fclose(fp);
}

[例12.1] 从键盘输入一些字符,逐个把它们送入磁盘文件,知道从键盘输入#为止。

#include "stdio.h"

void main()
{
  FILE *fp;
  char ch;
  char filename[10]; /* 文件名 */

  printf("Input filename\n");
  scanf("%s\n", filename); /* 从键盘输入文件名 */

  if ((fp = fopen(filename, "w"))==NULL) /* 打开文件 */
   {
    printf("can not open file %s\n", filename);
    exit(0);
   }

  ch = getchar();
  while(ch != '#') /* 从键盘读入字符,直到#为止 */
   {
    fputc(ch,fp);
    putchar(ch);
    ch = getchar();
   }

  fclose(fp); /* 关闭文件 */
}

[例12.2]、将一个磁盘文件的内容复制到另一个磁盘文件。

#include "stdio.h"
void main()
{
  FILE *in, *out;
  char ch;
  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("can not open infile %s\n", infile);
    exit(0);
   }
  if ((out = fopen(outfile, "w"))==NULL)
   { printf("can not open outfile %s\n", outfile);
    exit(0);
   }

   while(!feof(in)) fputc(fgetc(in), out);

  fclose(in);
  fclose(out);
}

二、fread()/fwrite()

 size_t fread(void *buffer, size_t size, size_t count, FILE *fp);

从文件fp中读入count次,每次读size字节,读入的信息存在buffer指针指向的缓冲区。函数返回值等于实际读入的次数(可能少于count)。

 size_t fwrite(void *buffer, size_t size, size_t count, FILE *fp);

将buffer地址开始的信息,写入count次,每次写size字节至文件fp中。函数返回值等于实际写入的次数(可能少于count)。

[例]、结构体类型数据。

  struct student_type
  {
   char name[10];
   int num;
   int age;
   char addr[30];
  }stu[40];

写入文件:

  for(i=0; i<40; i++) /* 每次写一个学生 */
  fwrite(&stu[i], sizeof(struct student_type), 1, fp);

  或:写一次
  fwrite(stu, sizeof(struct student_type), 40, fp);

从磁盘文件读出:

  for(i=0; i<40; i++)
  fread(&stu[i], sizeof(struct student_type), 1, fp);

  或:
  fread(&stu[i], sizeof(struct student_type), 40, fp);

[例12.3] 从键盘上输入一批学生数据,然后存储到磁盘上。

#include "stdio.h"

#define SIZE 4

struct student_type
{
 char name[10];
 int num;
 int age;
 char addr[15];
}stud[SIZE];

void save(); /* 原型 */
void load(); /* 原型 */

void main()
{ int i;
 for(i=0; iSIZE; i++) /* 从键盘读入数据 */
  scanf("%s%d%d%s",
    stud[i].name, &stud[i].num, &stud[i].age, stud[i].addr);

 save(); /* 存盘 */
 load(); /* 从盘读出 */

 for(i=0; iSIZE; i++) /* 屏幕上显示 */
  printf("%-10s%4d%4d%-15s\n", stud[i].name, stud[i].num, stud[i].age, stud[i].addr);
}

void save()
{ FILE *fp;
 int i;

 if ((fp=fopen("stu_list", "wb"))==NULL)
 {
  printf("can not open file\n";
  exit(0);
 }

 for(i=0; iSIZE; i++)
 if (fwrite(&stud[i], sizeof(struct student_type), 1, fp) != 1)
   printf("file read error\n");
} 

void load()
{ FILE *fp;
 int i;

 if ((fp=fopen("stu_list", "rb"))==NULL)
 {
  printf("can not open file\n";
  return;
 }

 for(i=0; iSIZE; i++)
 if (fread(&stud[i], sizeof(struct student_type), 1, fp) != 1)
 {
   if (feof(fp)) return;
   printf("file read error\n");
 }
} 

三、fprintf()/fsacnf()函数

  fprintf(文件指针,格式控制,变量列表);
  fscanf (文件指针,格式控制,变量地址列表);

除增加“文件指针”外,与printf()/scanf()用法相同。

四、其它读写磁盘函数

 int putw(int w, FILE *fp) /* 整数w写入文件fp */
 int getw(FILE *fp) /* 从文件fp读一个整数 */

 char * fgets(char *str, int n, FILE *fp);
  从文件fp读n-1个字节到str,str最后一个字节加'\0'。
 int fputs(const char *str, FILE *fp)
  把str写入fp。

§12.5 文件的定位

文件指针FILE *fp中,包含一个读写位置指针char *_nextc,它指向下一次文件读写的位置。

  typedef struct
   {
    int _fd;    /* 文件号 */
    int _cleft;   /* 缓冲区中剩下的字节数 */
    int _mode;   /* 文件操作模式 */
    char * _nextc; /* 下一个字节的位置 */
    char * _buff; /*文件缓冲区位置 */
   }FILE;

每当进行一次读写后,该指针自动指向下一次读写的位置。

当文件刚打开或创建时,该指针指向文件的开始位置。

可以用函数ftell()获得当前的位置指针,也可以用rewind()/fseek()函数改变位置指针,使其指向需要读写的位置。

一、rewind()函数

原型:void rewind(FILE *fp)
作用:使文件fp的位置指针指向文件开始。

[例12.4] 把一个文件的内容显示在屏幕上,并同时复制到另一个文件。

#include "stdio.h"

void main()
{
FILE *fp1, *fp2;
fp1 = fopen("file1.c", "r"); /* 源文件 */
fp2 = fopen("file2.c", "w"); /* 复制到file2.c */

while(!feof(fp1)) putchar(fgetc(fp1)); /* 显示到屏幕上 */

rewind(fp1); /* fp回到开始位置 */

while(!feof(fp1)) fputc(fgetc(fp1), fp2);

fclose(fp1);
fclose(fp2);
}

二、fseek()函数

原型:int fssek (FILE *fp, long offset, int origin);
功能:把文件fp的位置指针从起始点origin开始,移动offset字节。
   成功返回0,失败返回非0。

起始点origin 符号常量
文件开始位置 SEEK_SET 0
当前位置 SEEK_CUR 1
文件尾 SEEK_END 2

[例12.5] 在磁盘文件stud_dat上,存有10个学生(0~9)的数据,读出1、3、5、7、9号学生数据,并在屏幕上显示出来。

#include "stdio.h"

struct student_type
{
 char name[10];
 int num;
 int age;
 char sex;
}stud[10];

void main()
{
 int i;
 FILE * fp;

 if ((fp=fopen("stud_dat", "rb")) == NULL)
 {
   printf("can not open file\n");
   exit(0);
 }

 for(i=0; i<10; i += 2)
 {
  fseek(fp, i*sizeof(struct student_type), SEEK_SET);
  fread(&stu[i], sizeof(struct student_type), 1, fp);

  printf("%s %d %d %c\n",
    stud[i].name, stud[i].num, stud[i].age, stud[i].sex);
 }
 fclose(fp);
}

三、ftell()函数

原型:long ftell (FILE *fp);
功能: 获得文件fp的当前位置指针。若返回-1则失败。

 

§12.6 出错的检测   

在文件操作时,如果出错,除了操作函数的返回值有所反应外(如fopen()函数返回NULL),还可以用ferror()函数获得是否出错。

原型:int ferror(FILE *fp)
功能:若上一次文件操作未出错,返回0;否则返回非0。

文件操作出现错误后,该错误信息将一直保留在系统中,ferror()函数可以取走该错误信息(同时清除),也可以用clearerr()函数清除。

原型:void clearerr(FILE *fp)
功能:清除一次文件操作的错误。

§12.7 非缓冲文件系统  

缓冲文件系统用文件指针FILE *fp代表一个文件。

非缓冲文件系统用一个整数来代表一个文件,该整数称为“文件代号”或“文件句柄”。非缓冲文件操作的函数原型在io.h中声明。

1、open()

原型:int open(const char * filename, int mode);
功能:以mode方式打开文件filename,返回文件代号。
   失败时返回-1。
打开方式: (符号常量在fcntl.h中定义)
   O_RDONLY----只读
   O_WRONLY----只写
   O_RDWR -----读写
   O_CREAT-----创建
   O_TEXT------文本文件
   O_BINARY----二进制文件

例、int fd
  fd = open("test.h", O_RDWR | O_CREAT | O_BINARY);
  if (fd == -1) { printf("can not open file\n"); exit(0); }

2、close()

原型: int close(int fd);
功能:关闭文件fd。成功时返回0,失败时返回-1。

3、creat()

原型:int creat(const char *filename, int mode);
功能:创建文件。成功返回文件代号。

4、read()

原型:int read(int fd, void *buf, unsigned count)
功能:从文件fd中取count字节到buf缓冲区,返回实际读得的字节数。

5、write()

原型:int write(int fd, void *buf, unsigned count)
功能:把buf缓冲区的count字节内容写入文件fd中,返回实际写入的字节数。

6、lseek()

原型:long lseek(int fd, long offset, int origin)
功能:把fd文件的读写位置指针从起始点origin开始移动offset字节。返回实际移动的字节数。失败时返回-1L。

本章要求

1、文件中数据的组织形式:文本文件、二进制文件。
2、 打开文件的含义,为什么要关闭文件?
3、缓冲文件操作函数的使用。