C语言:文件操作详解

简介: C语言:文件操作详解

                                                   创作不易,友友们给个三连吧!!

一、为什么我们需要使用文件

      我们在写程序的时候,输入的数据是存储在电脑内存中的,如果程序退出内存回收,相应数据也就丢失了,等再次运行程序,就看不到上次输入的数据了,所以为了能够让我们的数据永久化保存,我们可以使用文件!

二、什么是文件

2.1 文件的分类

    在磁盘(硬盘上)的文件就叫做文件,在程序设计中,文件一般分为两种:程序文件、数据文件。

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

     数据文件,因为文件的内容不一定是程序,也可能是程序运行时读写的数据,所以可以理解为程序运行需要从中读取数据的文件,或者输出内容的文件。

     本文内容以数据文件为主!!

2.2 终端

     终端:包括显示器、鼠标、键盘、耳机、麦克风、显示器和摄像头等等。 这些外围设备就被称为终端,负责向主机输入数据的就叫输入终端,比如鼠标、键盘、麦克风、摄像头,负责接收主机输出数据的设备就被称作输出终端,比如显示器、耳机。

      以往在C语言程序编写中,我们处理数据的输入和输出都是以终端为对象的,即通过终端的键盘输入数据,并将运行结果显示在显示器上。

      但是这样的方式显然不能达到永久保存数据的目的,所以我们需要把信息输出到磁盘的文件中,当我们需要的时候再通过磁盘中的文件将数据读取到内存中去使用!

2.3 文件名

     我们为了将程序中输入的数据永久化保存起来,就需要用到文件,而每个文件都要自己的一个唯一的标识,用来方便用户的识别和引用。我们把这种文件标识叫做文件名。

    文件名包括了3部分:文件路径+文件名主干+文件后缀

    比如c:\code\test.txt

    c:\code是文件路径,test是文件主干,.txt是文件后缀

一般想要知道某个文件的路径,即找到这个文件,右击属性,就可以找到他的位置

三、二进制文件和文本文件

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

     数据在内存中是以二进制形式存储的,所以如果不加以转换输出到磁盘文件中,那该文件就是二进制文件。如果要求以ASCII码的形式是存储在磁盘文件中,那么在存储前需要将二进制转换成ASCII形势存储,那么该文件就被成为文本文件。

    数据在内存中的存储形式:字符一律以ASCII码形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

     .txt后缀的文件一般为文本文件的格式,是我们肉眼可以看得懂的,而二进制文件是我们肉眼看不懂得,但是编译器可以读懂。

四、文件的打开和关闭

     如何操作文件呢?操作文件的步骤其实和我们喝饮料一样,喝饮料的时候,先打开瓶盖,然后喝,最后关紧瓶盖。而我们想要操作文件,首先是打开文件,然后对文件进行读写操作,最后是关闭文件。

4.1 流

      我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了方便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。

    也就是说,流其实就是程序底层设计出来的一种东西,他存在于内存中的某一块区域,专门负责对接不同的外部设备,所以我们程序员并不需要关心我们的数据如何和外部设备建立联系,我们只需要关注流!!所以C程序针对⽂件、画面、键盘等的数据输⼊输出操作都是通过流操作的。

    ⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。

4.2 标准流

    在C程序中,我们通过键盘输入数据,并向屏幕中输出数据,其实都是通过流来完成。

因为在C语言程序启动的时候,默认打开了3个流:

• stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。

• stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出流中。

• stderr - 标准错误流,大多数环境中输出到显示器界流。perror函数就是将错误的信息输出到标准输出流中

     有了这三个流,我们就可以通过scanf/printf/perror函数来进行输入和输出操作。

     而流本身作为程序底层开发出来的内容,存在于内存中的某一块区域,C语言中,通过FILE*的文件指针来维护流的各种操作。

     stdin、stdout、stderr 三个流的类型是: FILE* ,通常称为⽂件指针。

4.3 文件指针

    缓冲⽂件系统中,关键的概念是“⽂件类型指针”,简称“⽂件指针”。

    每一个被使用的文件都在内存中开辟了一个相应的文件区,专门用来存放文件的相关信息(如⽂件的名字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系 统声明的,取名FILE。

    例如,VS2013编译环境提供的 stdio.h 头⽂件中有以下的⽂件类型申明:

struct _iobuf {
 char *_ptr;
 int _cnt;
 char *_base;
 int _flag;
 int _file;
 int _charbuf;
 int _bufsiz;
 char *_tmpfname;
 };
typedef struct _iobuf FILE;

   不同的C编译器的FLIE类型包含的内容不完全相同,但是大同小异,理论上来说,越高级的编译器,封装得会越完善,所以越不容易观察到这些细节。

   也就是说,我们每当打开一个文件,系统就会根据文件的情况况⾃动创建⼀个FILE结构的变量,并填充其中的信息,使⽤者不必关⼼细节。

   ⼀般都是通过⼀个FILE*的指针来维护这个FILE结构的变量,这样使⽤起来更加⽅便。

  如图,首先我们打开文件,其实就是向内存中申请了一块空间,该空间的类型是FILE类型,我们把它称为文本信息区,该文本信息区首先会根据原有的文本信息进行填充,之后再由操作者利用FILE*指针对这个文本信息区进行读或者写的操作,我们程序员不需要知道文件(data.txt)是如何和这个用结构体变量存储的文件信息区建立联系的,我们只需要知道我们可以用FILE*来操作文件,该文本信息区就相当于"流",我们通过"流"和外部设备建立联系。

  4.4  文件的打开和关闭

      文件在读写之前要打开文件,使用结束后应关闭文件。

      而我们在打开文件的同时,都会返回一个FILE*类型的指针,来帮助我们建立和该文件的联系,我们可以通过这个指针来进行读写的操作。

     ANSIC 规定使⽤ fopen 函数来打开文件, fclose 来关闭文件。

4.4.1 fopen

const char * filename:传需要打开的文件的文件名

const char * mode :传该文件的打开方式

Open file:作用是打开文件

返回类型:FILE*指针,我们可以通过该指针完成对文件的读写操作(相当于通过该指针打开了一个文件流)

注:文件可能会打开失败,如果FILE*指针成功返回,说明打开成功,如果返回的是NULL,则打开失败!

4.4.2 fclose

FILE * stream:传维护该文件流的指针

Close file:作用是关闭文件

4.4.3 打开模式

  fopen函数中的参数mode表示文件的打开模式,下面都是文件的打开模式。

4.4.3.1 w和a的区别

   w和a都可以写,但是如果我们打开的文件原来保存着一些数据,用w的话,会先清空掉文件的数据再进行写的操作,而用a则是直接在这些数据的后面进行追加

4.4.3.2 r+、w+、a+的区别

  r+是覆盖写,即在原来文件数据的基础上从头开始覆盖写入

  w+是清空写,即原来文件数据会被清空再开始写

  a+是追加写,即在原本文件数据的基础上在后面追加写入

4.4.4 文件的存储路径

1.相对路径

int main()
{
  FILE* pf;
  //打开文件
  pf = fopen("./../data.txt", "w");//桌面生成
  if (pf == NULL)//如果打开失败
  {
    perror("fopen");
    return 1;
  }
  //文件操作
   
  //关闭文件
  fclose(pf);
  return 0;
}

./   当前目录

../  上一级目录

/    根目录

2.绝对路径

int main()
{
  FILE* pf;
  //打开文件
  pf = fopen("C:\\Users\\lenovo\\Desktop\\data.txt", "w");//桌面生成
  if (pf == NULL)//如果打开失败
  {
    perror("fopen");
    return 1;
  }
  //文件操作
   
  //关闭文件
  fclose(pf);
  return 0;
}

五、文件的顺序读写

顺序读写函数介绍:

    上⾯说的适⽤于所有输⼊流⼀般指适⽤于标准输⼊流和其他输⼊流(如⽂件输⼊流);所有输出流⼀ 般指适⽤于标准输出流和其他输出流(如⽂件输出流)。

5.1 fputc

int character :传需要输出的字符

FILE * stream:传需要输出的流

Write character to stream:作用是将字符写入流中

     放置的过程其实跟光标有关,最开始光标是在最前面,输出a的时候光标移到b的后面了,所以再输出的b就是在a的后面,以此类推。

    fpuc可适用于所有输出流,所以也可以使用标准输出流

5.2 fgetc

FILE * stream:传需要读取的流

Get character from stream:作用是从流中获取字符

注:如果读取失败,会返回EOF;

原本文件里有abcd,我们调用了四次fgetc将他读取出来并打印在屏幕上。

与fputc一样,fgetc也适用于标准输入流,所以也可以从键盘读取

但是因为读取也是按照光标去逐个读取的,所以如果加上空格,就会有的数据读取不到

写一个代码,完成将data.txt文件的内容,拷贝一份生成data1.txt文件

int main()
{
  //打开文件
  FILE* pfread = fopen("data.txt", "r");
  if (pfread == NULL)//如果打开失败
  {
    perror("fopen->data.txt");
    return 1;
  }
  FILE* pfwrite = fopen("data1.txt", "w");
  if (pfwrite == NULL)//如果打开失败
  {
    //第二次打开文件,如果打开失败,返回前必须把前一个文件关闭掉并置空
    fclose(pfread);
    pfread = NULL;
    perror("open->data1.txt");
    return 1;
  }
  //文件操作
  //数据的拷贝
  int ch = 0;
    while ((ch = fgetc(pfread)) != EOF)//循环读取直到读取不到
  {
    fputc(ch, pfwrite);
  }
//循环结束后可以做到完全拷贝
  //关闭文件
  fclose(pfread);
  fclose(pfwrite);
  return 0;
}

5.3 fputs

const char * str:传需要输出的字符串

FILE * stream:传需要输出的流

Write string to stream:作用是将字符串写入流中

5.4 fgets

char * str:传字符串存放的地址(一般是一个字符数组)

int num:传从流中读取的最大字符串长度(由于字符串末尾有一个\0,所以默认其实最多读取num-1个)

FILE * stream:传需要读取的流

Get string from stream:作用是从流中获取字符串

我们可以看到,由于字符串的末尾要默认跟一个\0,所以最多只能读取num-1个到arr数组中

5.5 fprintf

FILE * stream:传需要输出的流

const char * format:传格式化字符串,用于指定输出的格式。

...:可变参数列表

Write formatted data to stream:作用是将一块格式化的数据输出到流中

5.6 fscanf

FILE * stream:传需要读取的流

const char * format:传格式化字符串,用于指定输入的格式。

...:可变参数列表

Read formatted data from stream:作用是从流中读取一块格式化的数据

注:%f打印时可以控制格式

5.7 fwrite

const void * ptr:传要写入的数据的指针

size_t size:传每个数据的大小

size_t count:传要写入数据项的数量

FILE * stream:传需要输出的流

Write block of data to stream:作用是将整块的数据输出到流中

   因为是二进制形式,所以我们看不懂,但是编译器可以看懂,所以要使用fwrite就要配套使用fread!!

注:无论是什么文件,字符都是以ascii码值存储的,所以打印出来都一样!!

5.8 fread

const void * ptr:传要写入的数据的指针

size_t size:传每个数据的大小

size_t count:传要写入数据项的数量

FILE * stream:传需要输入的流

Read block of data to stream:作用是将整块的数据输入到流中

将fwrite输出到文件里的数据通过fread成功读取回来。

六、一些函数的对比

6.1 scanf/fscanf/sscanf

const char * s:用于读取的字符串

const char * format:格式化的字符串,用于输入指定的格式

...:可变参数列表

Read formatted data from string:在字符串中读取一个格式化的数据

      对比一下参数,共同点都是读取一个格式化的数据,不同的是scanf是默认的标准输入流,从键盘上读取,而fscanf是所有的标准输入流都可以,参数可以传文件流也可以跟scanf一样传stdin(标准输入流),而sscanf是从一个字符串中读取。

6.2 printf/fprintf/sprintf

const char * s:用于输出的字符串

const char * format:格式化的字符串,用于输出指定的格式

...:可变参数列表

Write formatted data from string:把一个格式化的数据转换成字符串

     对比一下参数,共同点都是输出一个格式化的数据,不同的是printf是默认的标准输出流,输出到屏幕上,而fprintf是所有的标准输出流都可以,参数可以传文件流也可以跟printf一样传stdout(标准输入流),而sprintf是输出到一个字符串中。

6.3 总结

scanf:针对标准输入(键盘)的格式化输入函数

printf:针对标准输出(屏幕)的格式化输出函数

fscanf:针对所有输入流的格式化输入函数

fprintf:针对所有输出流的格式化输入函数

sscanf:从一个字符串中读取格式化的数据

sprintf:把一个格式化的数据转换成字符串

七、文件的随机读写

     顺序读写,顾名思义就是按顺序读写,本质是根据光标的移动按顺序读写,而光标最开始就是在文件的最前面。

    这里的随机,并不是类型随机数的那种随机,而是我们想在文件哪个位置读取就在哪个位置读取,想在文件哪个位置输出就在哪个位置输出,那么我们想要实现文件的随机读写,就要想办法去操控光标

   而由于流是通过FILE*指针维护的,所以本质上,也是通过操控指针来达到操控光标的目的。

7.1 fseek

FILE * stream:需要操作的流

long int offset: 偏移量

int origin:起始位置

这个origin可以传3个参数:

SEEK_SET:起始位置

SEEK_CUR:当前光标的位置

SEEK_END:文件的末尾

Reposition stream position indicator:作用是重新定位光标位置指示器(根据⽂件指针的位置和偏移量来定位⽂件指针

7.2 ftell

FILE * stream:需要操作的流

Get current position in stream:作用是获取流中的当前位置(返回⽂件指针相对于起始位置的偏移量

利用fseek和ftell来判断文件有多少个字节(fseek操控光标到尾部,再由ftell返回长度)

7.3 rewind

FILE * stream:需要操作的流

Set position of stream to the beginning:作用是将流的位置设置为开头(即让文件指针返回到文件的起始位置)

八、文件读取结束的判定

8.1 feof

FILE * stream:需要操作的流

Check end-of-file indicator:作用是检查文件结束指示符(即在文件读取结束后,判断是否是因为遇到文件末尾而结束)

8.2 ferror

FILE * stream:需要操作的流

Check error indicator:作用是检查错误指示灯(即检查操作目标文件的过程中是否出现了错误)

8.3 判断文本文件是否读取结束的方法

⽂本⽂件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )

例如:

• fgetc 判断是否为 EOF

• fgets 判断返回值是否为 NULL .

8.4 判断二进制文件是否读取结束的方法

⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。

例如:

• fread判断返回值是否小于实际要读的个数。

8.5 判断文件是因为什么原因结束的

8.5.1 文本文件  

8.5.2 二进制文件

8.6 putchar

int character:要输出的字符

Write character to stdout:作用是将字符写到屏幕上

8.7 getchar

Get character from stdin:作用是从键盘获取字符

8.8 puts

const char * str:要输出的字符串

Write string to stdout:作用是将字符串输出到屏幕上

8.9 gets

char * str:存放读取字符串的数组

Get string from stdin:作用是从键盘上获取字符串

九、文件缓冲区

       ANSIC 标准采⽤“缓冲⽂件系统”处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为 程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的⼤⼩根据C编译系统决定的。

这⾥可以得出⼀个结论:

      因为有缓冲区的存在,C语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂件。 如果不做,可能导致读写⽂件的问题(数据丢失)。

相关文章
|
21天前
|
存储 编译器 C语言
关于文件操作---C语言
关于文件操作---C语言
|
22天前
|
存储 程序员 C语言
C语言-文件操作
C语言-文件操作
48 2
|
2月前
|
安全 算法 程序员
【C/C++ 文件操作】深入理解C语言中的文件锁定机制
【C/C++ 文件操作】深入理解C语言中的文件锁定机制
45 0
|
2月前
|
存储 编译器 数据库
【文件操作】C语言
【文件操作】C语言
|
2月前
|
存储 程序员 C语言
【进阶C语言】C语言文件操作
【进阶C语言】C语言文件操作
43 0
|
2月前
|
存储 编译器 C语言
C语言文件操作
C语言文件操作
46 1
|
2月前
|
存储 安全 C语言
在C语言中文件操作
在C语言中文件操作
|
2月前
|
存储 缓存 C语言
C语言从入门到实战——文件操作
C语言中的文件操作是通过使用文件指针来实现的。可以使用标准库中的函数来打开、读取、写入和关闭文件。
53 0
|
2月前
|
C语言
C语言文件操作
C语言文件操作
17 0
C语言文件操作
|
2月前
|
C语言
C语言文件操作
C语言文件操作