【c语言】文件知识点总结

简介: 一. 什么是文件1. 文件基本概念广义上磁盘上的文件都是文件。但在程序设计中我们把文件分为两类:程序文件、数据文件。

一. 什么是文件


1. 文件基本概念


广义上磁盘上的文件都是文件。但在程序设计中我们把文件分为两类:程序文件、数据文件。

程序文件:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

数据文件:文件的内容不一定是程序,还可能是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

下面我们讨论的是数据文件。


2. 文件标识


一个文件要有一个唯一的文件标识,以便用户识别和引用。其中文件标识包括三个部分:文件路径 + 文件名 + 文件后缀。

fd1e2a49ce774b8493dfa252955a9e18.png


3. 文件类型


根据数据的组织形式,数据文件又分为为文本文件和二进制文件。

文本文件:如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。

二进制文件:数据在内存中以二进制的形式存储,如果不加转换地输出到外存,默认就是二进制文件。

规定:字符一律按ASCII码形式存储,数值型数据既可以用ASCII码形式存储,也可以使用二进制形式存储。

如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(一个整型数据占4个字节)。


4. 文件控制块


为了描述一个文件相关的信息,每个被使用的文件都在内存中开辟了一个相应的文件控制块,用来存放文件的相关信息(如文件的名字,文件状态及

文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有由系统声明的,取名FILE。

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。每当打开一个文件的时候,操作系统会根据文件的情况自动为该文件创建一个FILE类型的结构体变量,并填充其中的信息,使用者不必关心具体的实现细节

a8874230282b4c4e965315ac3145d8e0.png

C将文件的内容存储到顺序字节流里,其实就是一块缓冲区,这个字节流以文件结束符EOF(end of file)作为结束标志。在文件控制块中有一个指针 _ptr 指向这个字节流的起始位置,相当于数组名;还有一个位置指示器 _charbuf 标识字节流当前的位置,相当于数组的下标。


二. 文件操作的函数


前面已经介绍了文件相关的基本知识和概念。那么在语言层面上(比如C语言),如何对一个文件进行打开、关闭、读写数据的操作呢?C语言提供了一系列文件操作相关的库函数,包含在头文件stdio.h里。


1. 文件的打开与关闭


1.1 文件打开函数 — fopen


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

参数介绍:

filename:如果我们要打开的文件与我们当前正在运行的源代码在同级目录下,那么可直接写文件名字。不在同级目录下的话需要写明文件所在的路径(绝对路径或相对路径都可以)。

mode:文件打开方式,常用的包括以下几种。

image.png

功能:以特定的方式打开文件。操作系统会根据第一个参数的路径找到或创建该文件,并新建一个存储该文件信息的文件控制块,最后返回文件控制块的地址。注意:文件刚打开时,位置指示器指向最开始位置。

返回值:

  • 打开成功:返回该文件的文件控制块的地址。
  • 打开失败:返回空指针,即NULL。

函数使用举例

在当前路径下创建一个新文件log.txt,因为事先不存在这个文件,所以我们采用"w"模式打开。

// 1、打开文件
FILE* pf = fopen("log.txt", "w");    
if(pf == NULL)    
{    
  printf("open error\n");    
  return 1;    
}    
// 2、对文件进行一系列操作...    
// 3、关闭文件
return 0;

运行结果:

bfe4fa0ad110470c859794c51b3c46ce.png

1.2 文件关闭函数 — fclose


就像malloc动态开辟空间,使用完毕之后需要手动free释放掉这块空间一样的道理。我们通过fopen函数打开一个文件,操作系统为我们动态创建了这个文件的文件控制块,不使用的话需要fclose函数来释放它。注意并不是删除该文件,这个文件依然没有改变,只是删除fopen函数创建的存储该文件信息的文件控制块。


函数原型

int fclose ( FILE* stream );

参数:文件控制块的地址。

功能:释放开辟的文件控制块的空间。形象说就是关闭一个文件。

返回值:关闭成功返回0,失败返回EOF。

86aadeded3e749b1bea625268fb2c296.png

1.3 文件操作的大致流程


#include <stdio.h>        
int main()      
{      
  // 1、打开文件      
  FILE* pf = fopen("log.txt", "w");      
  if(pf == NULL)      
  {      
    printf("open error\n");      
    return 1;      
  }      
  // 2、对文件进行一系列操作      
  // .......      
  // 3、关闭文件      
  fclose(pf);      
  pf = NULL;      
  return 0;
}  


2. 文件的顺序读写


在上面,我们介绍了最基本的打开、关闭文件,其本质是创建、释放文件控制块。当然这只是开始,最重要的是我们拿到文件控制块后,如何通过它来完成文件的读写操作。

对文件数据的读写可以分为顺序读写和随机读写。顺序读写,即挨着顺序逐个字符的对文件中的数据进行写入和读取。下面我们介绍C语言中对文件进行顺序读写的一系列函数。


2.1 字符的写入和读取函数 — fputc 和 fgetc


一、fputc

int fputc ( int character, FILE* stream );

参数

  • character:被写入的字符。
  • stream:文件控制块的地址。

功能:字符被写入字节流的位置指示器所指示的位置,然后该指示器将自动前进1。

56e11dc6d297409cb2dbfb4ec5174150.png


返回值:写入成功返回这个被写入的字符。如果写入错误,则返回EOF并设置错误指示符(ferror)。

fdb4d2c20a6d474f99517d491b866430.png

函数使用举例

在当前目录不下创建一个新的文件log.txt,写入小写字母序列a~z。

void Testfputc()    
{    
  // 1、打开文件    
  FILE* pf = fopen("log.txt", "w");    
  // 2、使用fputc对文件进行字符的写入   
  int i = 0;    
  for(i = 'a'; i <= 'z'; ++i)    
  {    
    fputc(i, pf);    
  }    
  // 3、关闭文件    
  fclose(pf);    
  pf = NULL;    
}  

最终当前目录下生产一个文件log.txt。

92c05c7aa4174afeb5ff2dcb031f7748.png


二、fgetc

int fgetc ( FILE* stream );

参数:文件控制块地址。


功能:返回指定文件控制块的内部位置指示器当前指向的字符。然后将文件位置指示器+1。

返回值

  • 成功的话返回提取到的字符。
  • 如果调用时字节流位于文件结束位置,则该函数返回EOF并设置文件结束指示符(feof)。如果发生其他读取错误,该函数也返回EOF,但设置其错误指示符(ferror)。

函数使用举例

从上面刚刚写入完成的log,txt里读取数据。

void Testfgetc()    
{    
  // 1、用"r"方式打开已经存在的文件    
  // 刚打开时位置指示器指向字符的开始位置                                                                                   
  FILE* pf = fopen("log.txt", "r");    
  // 2、使用fgetc依次读取文件的每一个字符    
  int ch = 0;    
  while((ch = fgetc(pf)) != EOF)    
  {    
    printf("%c", ch);    
  }    
  printf("\n");    
  // 3、关闭文件    
  fclose(pf);    
  pf = NULL;    
}  

编译运行

4926fd43085047719d2f6fc78f207c9a.png

注意:读取的过程只是在移动文件信息区里的_ptr指针,文件内容并没有任何更改。

2c66afe017164cdb99341babe5ae2c8e.png


2.2 字符串的写入和读取函数 — fputs 和 fgets


一、fputs

int fputs ( const char* str, FILE* stream );

参数

  • str:写入的字符串。
  • stream:被写入的文件控制块指针。

功能:写一个字符串到字节流中,写入的字符串遇到’\0’结束,并且最后的’\0’不会被写入字节流。

返回值:成功返回一个非负的数字。当写入错误时,函数返回EOF并设置错误指示符(ferror)。

函数使用举例

我们对log.txt写入一个字符串“hellow world”。

void Testfputs()    
{    
  // 1、以"w"方式打开文件
  FILE* pf = fopen("log.txt", "w");    
  // 2、使用fputs写入一行字符串    
  const char* s = "hello world";    
  fputs(s, pf);    
  // 3、关闭文件    
  fclose(pf);    
  pf = NULL;                                                                                                          
}  

查看当前目录下生成的log.txt

5badfdad267249dc84b46b0f155fed08.png

二、fgets

char * fgets ( char* str, int num, FILE* stream );

参数

  • str:把文件里读取到的字符串放到str里。
  • num:需要读取的字符的个数。
  • stream:文件信息区地址。

功能:从字节流中读取字符,并将它们作为C字符串存储到str中,直到(num-1)字符被读取,或者到达换行符或文件结束符,以最先发生的为准。


b6a8216f63ec43808d8ee05b26abd110.png

返回值

  • 调用成功返回str。
  • 如果读取到了字节流最后的文件结束符,则返回的指针为空指针,并设置文件结束符(feof)。
  • 如果发生读取错误,则设置错误指示符(ferror)并返回一个空指针。

关于fgets读取结束后的几种处理方式说明

  1. 如果确实可以读取到 num-1 个字符,在把num-1个字符复制到目的地str之后,最后会自动附加一个’\0\到str,这样合起来一共是n个字符。
  2. 如果中途读取到字节流中的文件结束符,那么读取结束,最后依然会附加一个’0’。
  3. 如果中途遇到’\n’,读取结束,’\n’也一起读取到str中。

函数使用举例

读取前面写入的字符串"hello world‘"到字符数组str中

void Testfgets()    
{    
  // 1、用"r"方式打开已经写入过的log.txt    
  FILE* pf = fopen("log.txt", "r");    
  // 2、使用fgets从文件里读取字符串放到str中    
  char str[12] = {0};    
  fgets(str, 20, pf);                                                                                                 
  printf("%s\n", str);    
  // 关闭文件    
  fclose(pf);    
  pf = NULL;    
} 

编译运行:

bd099c5d30804c6ca207fcb9bb484ba2.png


2.3 格式化写入和读取函数 — fprintf 和 fscanf


一、fprintf

int fprintf ( FILE* stream, const char* format, ... );

参数:第一个参数是被写入文件控制块地址,第二个参数格式控制序列,其用法就和printf一样。

c1ce6a6eb7ae43648a1b230f43b56acc.png

功能:格式化写入数据。

返回值:写入成功返回写入的数据个数.如果发生写错误,则设置错误指示符(ferror)并返回一个负数。

函数使用举例

struct Person    
{    
  char name[20];                                                                                                      
  int age;    
};    
void Testfprintf()    
{    
  // 1、打开文件    
  FILE* pf = fopen("log.txt", "w");    
  // 2、对文件进行格式化写入    
  Person p = {"zhangsan", 18};    
  fprintf(pf, "%s %d", p.name, p.age);    
  // 3、关闭文件    
  fclose(pf);    
  pf = NULL;    
}  

查看当前目录下的log.txt文件

3103291c5183451ca4c0b70642ac11a4.png

二、fscanf

int fscanf ( FILE* stream, const char* format, ... );

参数:第一个参数是被读取文件控制块地址,第二个参数格式控制序列,其用法就和scanf一样。

112d683bac7d46bda47edcc2e735b425.png

功能:从字节流里格式化读取数据到指定实参中。

返回值

  • 如果成功,函数将返回成功填充参数列表的项数。
  • 如果发生读取错误或在读取时到达文件结束,则设置对应的指示符(feof或ferror)。返回EOF。

函数使用举例

struct Person                                               
{                                         
  char name[20];                                     
  int age;                                                          
};                                                            
void Testfscanf()    
{    
  // 1、打开前面格式化写入之后的文件    
  FILE* pf = fopen("log.txt", "r");    
  // 2、用fscanf格式化读取文件内容    
  Person p;    
  fscanf(pf, "%s %d", p.name, &p.age);    
  // 3、打印文件中读取出来的数据    
  printf("%s %d\n", p.name, p.age);    
  // 4、关闭文件    
  fclose(pf);    
  pf = NULL;                                                                                                          
}    

编译运行:

375c4f78392c42a98cf99824d46803f7.png


2.4 二进制写入和读取函数 — fwrite 和 fread


一、fwrite

size_t fwrite ( const void* ptr, size_t size, size_t count, FILE* stream );

参数:

  • ptr:指向要写入的元素数组的指针。
  • size:每个元素所占字节大小。
  • count:元素个数。
  • stream:文件控制块地址。

功能:

1e1eb33681f84642a53a75aecfc4f9bb.png

返回值:

  • 写入成功返回写入的元素个数count。
  • 如果返回的数字与count不同则写入错误,设置错误指示符(ferror)。如果size或count为零,则函数返回零,错误指示符保持不变。

函数使用举例

void Testfwrite()    
{    
  // 1、以"wb"方式打开文件,写入内容将转化为二进制形式    
  FILE* pf = fopen("log.txt", "wb");    
  // 2、把数组arr的内容以二进制形式写入到文件中    
  int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};    
  fwrite(arr, sizeof(int), sizeof(arr)/sizeof(int), pf);    
  // 3、关闭文件    
  fclose(pf);    
  pf = NULL;    
} 

编译运行后,看到log.txt里都是一些看不懂的二进制乱码。

f5d5c55f86a145d79bb5f2867aff99d1.png


二、fread

size_t fread ( void* ptr, size_t size, size_t count, FILE* stream );

参数:和fwrite的一样,就不在重复介绍了,要说差别的话就是第一个参数作为输出型参数要被修改,所以是非const的。

功能:从字节流中读取count个元素,每个元素的大小为size字节,并将它们存储在ptr指定的内存块中。

6b3b99a280c14aa68d6e38d797b85dff.png

返回值:

  • 返回成功读取的元素总数count。
  • 如果这个数字与count参数不同,则可能是在读取时发生了读取错误或到达了文件结束。在这两种情况下,都设置了标识符,可以分别用ferror和feof来检查。

函数使用举例

读取刚刚写入到文件里的二进制存储的数据。

void Testfread()      
{                     
  // 1、以"rb"方式打开文件,进行二进制内容的读取    
  FILE* pf = fopen("log.txt", "rb");    
  // 2、读取文件内容到数组arr中    
  int arr[10] = {0};    
  fread(arr, sizeof(int), 10, pf);    
  int i = 0;    
  for(i = 0; i < 10; ++i)    
  {    
    printf("%d ", arr[i]);    
  }                   
  printf("\n");      
  // 3、关闭文件    
  fclose(pf);    
  pf = NULL;    
}

编译运行:

b5039ab2871c464d9d1d7712497dcb10.png


3. 文件的结束判定


我们在使用读取文件内容的函数时,读取结束有两种特殊的情况,即读取过程中出现错误或读取到文件结束标志,这时都返回相同的值,但它们设置了不同标识符,可以分别用ferror和feof来检查和加以区分读取文件结束的原因。

image.png


3.1 ferror


int ferror ( FILE* stream );

只需传入文件控制块地址,函数内部会去检查里面的错误标识符是否为ferror,即检查是否发生错误,若使用时没有发生错误,则ferror函数返回0;否则,ferror函数将返回一个非零的值。

46ae23409411461a9d2a7c2243e030ba.png


函数使用举例

if(ferror(pf))
{
  printf("文件读取发生错误而结束");
}


3.2 feof


feof函数的功能也是判断使用某一文件指针的过程中,是否读取到文件末尾,若使用时没有读取到文件末尾,则feof函数返回0;否则,feof函数将返回一个非零的值。调用feof函数时,也只需将待检查的文件指针传入即可。

函数使用举例

if (feof(pf))
{
  printf("文件读取到文件末尾而结束");
}


3.3 实际中ferror和feof的配合使用


在实际使用中,读取文件结束后,要配合ferror和feof一起来使用,判断文件是正常结束、发生错误而结束还是读取到文件尾而结束。

void Testfgetc()    
{    
  // 1、用"r"方式打开已经存在的文件,内容为:abcdef                                                                                    
  FILE* pf = fopen("log.txt", "r");    
  // 2、使用fgetc依次读取文件的第一个字符
  int ch = 0;    
  ch = getc(pf);  
  // 判断读取情况
  if(ferror(pf))
  {
   printf("文件读取发生错误而结束");
  }
  else if(feof(pf))
  {
   printf("文件读取到文件末尾而结束");
  }
  else 
  {
    printf("读取字符成功");
  }
  // 3、关闭文件    
  fclose(pf);    
  pf = NULL;    
}  


4. 文件的随机读写


前面我们介绍的顺序读写的函数,只能往后走不能往回走,这样使用起来不是很灵活,比如看下面这个例子:

文件log.txt已经存在,且内容为:abcdef

void Test()    
{    
  FILE* pf = fopen("log.txt", "r");    
  // 读取第一个字符,位置指示器向后移动一位    
  int ch = 0;    
  ch = fgetc(pf);    
  printf("%c\n", ch);// a    
  // 再次读取一个字符,位置指示器继续往后移动一位    
  ch = fgetc(pf);    
  printf("%c\n", ch);// b   
  fclose(pf);    
  pf = NULL;    
} 

读取了两个字符之后,位置指示器移到了字符c的位置,想要再次读取最开始的字符a,有什么办法能让位置指示器回到’a’位置呢?

4f2bbff1802d4541a420f66df776bac6.png

4.1 fseek

int fseek ( FILE* stream, long int offset, int origin );

功能:将与字节流关联的位置指示器设置为新位置。

参数:

  • stream:文件控制块地址。
  • offset:相较于origin位置,想要偏移的量。
  • origin:偏移前的初始位置(注意并非文件信息区的起始位置),C对origin定义了以下三个常量:

image.png

返回值:成功返回0,出错返回非0值,并设置错误标识符(ferror)。

函数使用举例

对于开头的例子我们使用fseek有三种方法可以让位置指示器回到最开始的’a’位置。

1.方法一:相对起始位置开始偏移

fseek(pf, 0, SEEK_SET);                                                                        
ch = fgetc(pf);                                                                                
printf("%c\n", ch); //a

2.方法二:相对当前位置开始偏移

fseek(pf, -2, SEEK_CUR);                                                                                            
ch = fgetc(pf);    
printf("%c\n", ch); //a

3.方法三:相对EOF后一个位置开始偏移

fseek(pf, -7, SEEK_END);                                                                                            
ch = fgetc(pf);    
printf("%c\n", ch); //a


4.2 ftell


ong int ftell ( FILE* stream );

获得字节流当前的位置相到起始位置的偏移量。只需传入文件控制块地址即可,调用成功返回偏离量,失败返回-1。

函数使用举例

依然是最开始的那个例子,我们读取了两个字符后来带’c’位置,距离最开始的位置偏移了两个字符。

long distance = ftell(pf);    
printf("%ld\n", distance);//2


4.3 rewind


void rewind ( FILE* stream );

让位置指示器回到最开始的位置,只需传入文件控制块地址即可,无返回值。

函数使用举例

使用rwind让该文件的我只指示器回到最开始’a’的位置。

// 让位置指示器回到最开始位置,即'a'位置
rewind(pf);    
// 直接读取就是'a'
ch = fgetc(pf);    
printf("%c\n", ch);//a                                                                                              
fclose(pf);

 


相关文章
|
27天前
|
存储 编译器 C语言
如何在 C 语言中判断文件缓冲区是否需要刷新?
在C语言中,可以通过检查文件流的内部状态或使用`fflush`函数尝试刷新缓冲区来判断文件缓冲区是否需要刷新。通常,当缓冲区满、遇到换行符或显式调用`fflush`时,缓冲区会自动刷新。
|
27天前
|
存储 编译器 C语言
C语言:文件缓冲区刷新方式有几种
C语言中文件缓冲区的刷新方式主要包括三种:自动刷新(如遇到换行符或缓冲区满)、显式调用 fflush() 函数强制刷新、以及关闭文件时自动刷新。这些方法确保数据及时写入文件。
|
1月前
|
C语言
C语言学习笔记-知识点总结上
C语言学习笔记-知识点总结上
76 1
|
1月前
|
C语言
【C语言】探索文件读写函数的全貌(三)
【C语言】探索文件读写函数的全貌
|
1月前
|
存储 C语言
【C语言】探索文件读写函数的全貌(二)
【C语言】探索文件读写函数的全貌
|
1月前
|
C语言
【C语言】探索文件读写函数的全貌(一)
【C语言】探索文件读写函数的全貌
|
1月前
|
存储 文件存储 C语言
【C语言】深入了解文件:简明指南
【C语言】深入了解文件:简明指南
|
2月前
|
Linux C语言
C语言 文件IO (系统调用)
本文介绍了Linux系统调用中的文件I/O操作,包括文件描述符、`open`、`read`、`write`、`lseek`、`close`、`dup`、`dup2`等函数,以及如何获取文件属性信息(`stat`)、用户信息(`getpwuid`)和组信息(`getgrgid`)。此外还介绍了目录操作函数如`opendir`、`readdir`、`rewinddir`和`closedir`,并提供了相关示例代码。系统调用直接与内核交互,没有缓冲机制,效率相对较低,但实时性更高。
|
3月前
|
存储 C语言
【C语言】C语言-学生成绩管理系统(源码+数据文件+课程论文)【独一无二】
【C语言】C语言-学生成绩管理系统(源码+数据文件+课程论文)【独一无二】
53 15
|
3月前
|
存储 C语言
【c语言】职工信息管理系统 包含读取写入txt文件,职工信息的增删改查
【c语言】职工信息管理系统 包含读取写入txt文件,职工信息的增删改查