Post

文件

文件的基本知识

什么是文件

  • 程序文件: 源程序文件(.c)、目标文件(.obj)、可执行文件(.exe)—即程序代码
  • 数据文件:不是程序,而是供程序运行读写的数据—即数据
  • 操作系统把各种设备都统一作为文件来处理
  • 文件一般是指存储再外部介质上数据的集合
  • 数据流即数据的输入输出

文件名

  • 文件路径
  • 文件名主干
  • 文件后缀

文件分类

  • ASCII文件(文本文件) text file:每一个字节存放一个字符ASCII字
  • 二进制文件(映像文件)image file:存储在内存的数据映像

  • 如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换
  • 字符一律ascii形式存储
  • 数值型可以ascii或者二进制形式存储

比较:

  • 二进制节省外存的空间和转换时间
  • ASCII形式输出字节与字符一一对应,一个字节代表一个字符,便于输出字符;
  • 但是一般占存储空间比较多,而且需要花费转换时间

文件缓冲区

ANSI C 采用”缓冲文件系统”处理文件

  • 每个文件在内存中只有一个缓冲区
  • 在向文件输出数据时,作为输出缓冲区
  • 在向文件输入数据时,它作为输入缓冲区

文件类型指针

1
2
3
4
5
6
7
8
9
10
11
typedef struct { 
  int level;     /* fill/empty level of buffer */ 
  unsigned flags;     /* File status flags */ 
  char fd;     /* File descriptor */ 
  unsigned char hold;     /* Ungetc char if no buffer */ 
  int bsize;     /* Buffer size */ 
  unsigned char _FAR *buffer;     /* Data transfer buffer */ 
  unsigned char _FAR *curp;     /* Current active pointer */ 
  unsigned istemp;     /* Temporary file indicator */ 
  short token;     /* Used for validity checking */ 
} FILE;     /* This is the FILE object */

打开与关闭文件

fopen

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

Parameters

  • filename − This is the C string containing the name of the file to be opened.

  • mode − This is the C string containing a file access mode. It includes −

参数作用
r以只读方式打开文件,该文件必须存在。
r+以读/写方式打开文件,该文件必须存在。
rb+以读/写方式打开一个二进制文件,只允许读/写数据。
rt+以读/写方式打开一个文本文件,允许读和写。
w打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
w+打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
a以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。
a+以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。
wb以只写方式打开或新建一个二进制文件,只允许写数据。
wb+以读/写方式打开或新建一个二进制文件,允许读和写。
wt+以读/写方式打开或新建一个文本文件,允许读和写。
at+以读/写方式打开一个文本文件,允许读或在文本末追加数据。
ab+以读/写方式打开一个二进制文件,允许读或在文件末追加数据。
  • 函数的返回值: 则表示打开成功后的文件指针,格式为FILE类型,是一个结构体类型,供后面使用,如果打开失败,则返回NULL。

  • 如果我们现在想打开一个D盘根目录下的abc.dat,并且想读出该文件里的数据,那么我们可以这样写:
1
2
3
FILE *fp;
fp=fopen("d:\\abc.dat","r")
//后面通过fp指针开始读文件
1
2
3
4
5
- 1. 该文件的目录是绝对路径,因此这样写,如果不写盘符比如abc.dat则表示相对路径,表示与本程序同目录下。

- 2. 路径中的反斜杠虽然只有一个,但这里打了两个,原因在于C语言字符串中对反斜杠要当作转义字符处理,因此要用两个反斜杠才能表示一个。

- 3. 一旦以r也就是只读的方式打开文件,后面则不允许写数据,否则会出错,一定要保持一致!

fclose

1
2
int fclose( FILE *fp );
//返回值为整型,如成功关闭则返回0,失败则返回-1。

顺序读写文件

向文件读写字符

这里的数据以acsii码形式存储,即整型

fgetc
1
int fgetc (FILE *fp);
  • 返回值: 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF。

  • EOF 不绝对是 -1,也可以是其他负数,这要看编译器的实现。

1
2
3
char ch;
FILE *fp = fopen("D:\\demo.txt", "r+");
ch = fgetc(fp);
  • 在文件打开时,该指针总是指向文件的第一个字节。
  • 使用 fgetc() 函数后,该指针会向后移动一个字节,所以可以连续多次使用 fgetc() 读取多个字符。
  • 注意:这个文件内部的位置指针与C语言中的指针不是一回事。位置指针仅仅是一个标志,表示文件读写到的位置,也就是读写到第几个字节,它不表示地址。文件每读写一次,位置指针就会移动一次,它不需要你在程序中定义和赋值,而是由系统自动设置,对用户是隐藏的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
int main(){
    FILE *fp;
    char ch;
   
    //如果文件不存在,给出提示并退出
    if( (fp=fopen("D:\\demo.txt","rt")) == NULL ){
        puts("Fail to open file!");
        exit(0);
    }

    //每次读取一个字节,直到读取完毕
    while( (ch=fgetc(fp)) != EOF ){
        putchar(ch);
    }
    putchar('\n');  //输出换行符

    fclose(fp);
    return 0;
}
fputc
1
int fputc ( int ch, FILE *fp );
  • 参数 char – 这是要被写入的字符。该字符以其对应的 int 值进行传递。 fp – 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。

  • 返回值 如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main ()
{
   FILE *fp;
   int ch;
 
   fp = fopen("file.txt", "w+");
   for( ch = 33 ; ch <= 100; ch++ )
   {
      fputc(ch, fp);
   }
   fclose(fp);
 
   return(0);
}
  • c语言中,已经在头文件中把fputc和fgetc函数定义为宏putc和getc
1
2
 #define putc(ch,fp) fputc(ch,fp)
 #define getc(fp) fget(fp)

向文件读写字符串

fgets

文件 -- > 字符数组str

  • 函数原型
1
2
3
4
char *fgets(char *str,int n,FILE*fp)
//n是要求得到的字符个数
//实际上只有n-1个字符
//加上`\0`
fputs

字符数组\字符串常量\字符型指针 -- > 文件

1
int fputs(char *str,FILE *fp)

格式化的方式读写文本文件

fscanf(文件指针,格式化字符串,输入表列)
1
fscanf(fp,"%d,%f",&i,&f);
fprintf(文件指针,格式化字符串,输出表列)
1
fprintf(fp,"%d,%f",i,f);
  • fprintf和fscanf函数对磁盘读写
  • 需要将ASCII码转化为二进制形式
  • 再保存在内存变量中
  • 输出时又要再把二进制形式转化为字符
  • 花费时间较多

用二进制方式向文件读写一组数据

fread(buffer,size,count,fp)
fwrite(buffer,size,count,fp)
getw
putw
1
2
3
// #include <stdio.h>
// size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
// size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • buffer : 读入数据的存储区地址 / 以此地址开始的存储区
  • size: 要读写的字节数
  • count: 读入数据项个数(每个数据项长度为size)
  • fp:FILE文件类型指针

打开文件时,指定使用二进制文件,则fread和fwrite就可以读写任何文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   
struct  Student_type
{
    char name[10];
    int num;
    int age;
    char addr[10];
}stud[40];

for (i=0;i<40;i++)
//从文件中读取数据到结构体中
{    fread(&stud[i],sizeof(struct Student_type),1,fp);
}
//将内存中的数据写入到文件中
fwrite(&stud,sizeof(struct(Student_type),1,fp));

//补充,以上函数如果执行成功,则返回输入、输出数据项的个数

对 EOF 的说明

EOF 本来表示文件末尾,意味着读取结束,但是很多函数在读取出错时也返回 EOF,那么当返回 EOF 时,到底是文件读取完毕了还是读取出错了?

  • 我们可以借助 stdio.h 中的两个函数来判断,分别是 feof() 和 ferror()。

feof()
  • 用于检测文件流是否到达了文件末尾(EOF),函数的原型如下:
1
int feof(FILE *stream);
  • 参数 stream 是一个指向 FILE 类型的指针,表示要检查的文件流。
  • 返回值 如果文件流位于文件末尾,则该函数返回非零值。否则,返回零。

  • 注意 feof() 仅在尝试读取已到达文件尾之后的数据时才返回非零值,仅到达文件尾但未尝试读取的情况下,并不会导致 feof() 返回非零值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

int main() {
    FILE *file;
    char ch;

    // 以读取模式打开文件
    file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("文件打开失败");
        return 1;
    }

    // 循环读取文件,直到到达文件尾
    while (1) {
        ch = fgetc(file);
        if (feof(file)) {
            break; // 如果到达文件尾,跳出循环
        }
        printf("%c", ch); // 打印字符
    }

    // 关闭文件
    fclose(file);

    return 0;
}

ferror()

-ferror() 函数用来判断文件操作是否出错,它的原型是:

1
int ferror ( FILE *fp );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>
int main(){
    FILE *fp;
    char ch;
  
    //如果文件不存在,给出提示并退出
    if( (fp=fopen("D:\\demo.txt","rt")) == NULL ){
        puts("Fail to open file!");
        exit(0);
    }

    //每次读取一个字节,直到读取完毕
    while( (ch=fgetc(fp)) != EOF ){
        putchar(ch);
    }
    putchar('\n');  //输出换行符

    if(ferror(fp)){
        puts("读取出错");
    }else{
        puts("读取成功");
    }

    fclose(fp);
    return 0;
}

随机读写数据文件

  • 随机访问:可以对任何位置上的数据进行访问

文件位置标记及其定位

文件位置标记 – 文件指针

  • 一般情况(顺序读写):
    • 对字符进行顺序读写,文件位置指向文件开头,文件位置标记会自动向后移动一个位置。
    • 直到所有的数据写完,此时文件位置标记在最后一个数据之后。
  • 随机读写
    • 人为地移动文件标记的位置。
  • 流式文件即可以随机读写,又可以顺序读写
  • 关键在于控制文件的位置标记

文件位置标记的定位

  • rewind函数–使文件位置标记指向文件开头
1
void rewind ( FILE *fp );
  • fseek函数 – 改变文件位置标记 调用形式: fseek(文件指针,位移量,起始点)

    1
    
      int fseek ( FILE *fp, long offset, int origin );
    
    • 参数说明:

      1) fp 为文件指针,也就是被移动的文件。

      2) offset 为偏移量,也就是要移动的字节数。之所以为 long 类型,是希望移动的范围更大,能处理的文件更大。offset 为正时,向后移动;offset 为负时,向前移动。

      3) origin 为起始位置,也就是从何处开始计算偏移量。C语言规定的起始位置有三种,分别为文件开头、当前位置和文件末尾,每个位置都用对应的常量来表示:

      起始点常量名常量值
      文件开头SEEK_SET0
      当前位置SEEK_CUR1
      文件末尾SEEK_END2

      例如,把位置指针移动到离文件开头100个字节处: fseek(fp, 100L, 0); 值得说明的是,fseek() 一般用于二进制文件,在文本文件中由于要进行转换,计算的位置有时会出错。

  • ftell函数 – 测定文件位置标记的当前位置

    • 如果调用函数出错,返回-1L(fp文件指针不存在)

随机读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
例:10.6 在磁盘文件中存有10个学生数据。要求将1,2,5,7,9个学生
数据输入计算机,并在屏幕上显示出来
*/
#include <stdio.h>
#include <stdlib.h>

struct Student_type  //学生数据类型
{
    char name[10];
    int num;
    int age;
    char addr[15];

}stud[10];


int main(int argc, char const *argv[])
{
    int i;
    FILE *fp;
    struct Student_type * p;
    if((fp = fopen("stu.dat","rb"))==NULL)
    {
        printf("cannot open file\n");
        exit(0);
    }    
    for(p=stud; p<stud+10; p+=2)
    {
        fseek(fp,(p-stud)*sizeof(struct Student_type),0);
        //0 -- origin
        //1 -- current
        //2 -- end
        fread(p,sizeof(struct Student_type),1,fp); 
        //将一个数据块读入结构体变量
        
        printf("%s\t%d\t%d\t%s\n",p->name,p->num,p->age,p->addr);
         
    }
    
    fclose(fp);
    return 0;
}

//输出
Zhagmg  1001    19      room01
Tan     1001    19      room01
Li      1001    19      room01
Zhen    1001    19      room01
Qin     1001    19      room01

文件读写出错检测

  • ferror函数 – 错误流检查函数
    • 如果ferror 返回值为0 (假),表示未出错;如果返回一个非零值, 表示出错。
  • clearerr函数 – 使文件错误标志和文件结束标志置为0
    • 假设在调用一个输入输出,函数时出现错误, ferror 函数值为一个非零值。应该立即涸用clearerr(fp) ,使ferror( fp ) 的值变成0 ,以便再进行下一次的检测。
    • 只要出现文件读写出错标志,它就一直保留,直到对同一文件调用clearerr 函数或 rewind 函数. 或任何其他一个输入输出函数。
This post is licensed under CC BY 4.0 by the author.