c语言-文件

文件的基本知识

什么是文件

  • 程序文件: 源程序文件(.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. 该文件的目录是绝对路径,因此这样写,如果不写盘符比如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_SET 0
      当前位置 SEEK_CUR 1
      文件末尾 SEEK_END 2

      例如,把位置指针移动到离文件开头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;
}

1
2
3
4
5
6
//输出
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 函数. 或任何其他一个输入输出函数。

c语言-文件
https://clint456.github.io/2023/09/25/C语言-2023-09-26-文件/
作者
clint
发布于
2023年9月26日
许可协议