【C语言】文件操作

简介: 对文件操作的相关内容进行介绍,文件的概念,文件操作函数,文件结束原因判断函数,文件缓冲区的概念。

为什么使用文件?

我们程序的运行,数据使存储在内存中的,一旦程序运行完成,那么内存中的数据就会返回操作系统,当我们再次运行程序时,之前的数据就会消失。为了能够实现数据的持久化,我们将数据存到文件当中。

什么是文件?

程序文件

程序⽂件包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执⾏程序(windows环境后缀为.exe)。

数据文件

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

我们这一期讲的就是如何操作数据文件。

文件名

一个文件有它唯一的文件名,用来辨别和区分。文件名包括3个部分:

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

C:\code\practice\test_1_20\test_1_20\data.txt

其中蓝色的就是文件路径,红色的就是文件名主干,紫色的就是文件后缀。

文件名后缀表示的是文件的默认打开方式,可以有也可以去掉。

二进制文件和文本文件

二进制文件:文件的数据为二进制的形式。

文本文件:文件的数据为ASCII码的形式存储。

文本文件我们看得懂,二进制文件要用二进制的形式我们才能看懂。

       

流的概念

我们的数据输出到屏幕/文件/网络,我们也可以从屏幕/文件/网络中去读取数据,但是由于不同外部设备的输入输出的不同,所要进行的操作也就大不相同,所以有流的概念,

我们可以想象为中间媒介,我们通过来进行数据的输入和输出。

标准流

我们的scanf和printf,一个是从键盘中输入数据,一个是向屏幕输出数据。这里面就存在流来进行操作,但是平时我们并没有主动去打开流,因为在我们程序运行时,就会默认打开3个标准流:

stdin:标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。

stdout:标准输出流,大多数的环境中输出到屏幕上,printf函数就是将信息输出到标准输出流。

stderr:标准错误流,大多数环境中输出到屏幕。

stdin、stdout、stderr这3个标准流在程序进行时就会默认打开。

stdin、stdout、stderr这3个流的类型时:FILE*,通常称为文件指针

如果我们想要进行对文件的操作,那么我们也要打开对应的文件输入流或者文件输出流,这些在打开文件的函数中进行操作。

文件指针

在我们在内存,也就是在程序中打开文件,就会在内存中开辟文件信息区,来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息都存放在结构体中,结构体具体如下:这个结构体的类型就是FILE,这个文件信息区的地址就是文件指针,类型是FILE*

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

通过文件指针我们可以找到这个文件信息区,然后就可以对文件进行操作了。

       

文件的打开和关闭

那这个文件指针我们如何得到,下面介绍两个函数,

文件的打开

fopen:打开文件函数,const char * filename就是我们要打开的文件名,const char * mode就是打开文件的模式(是读还是写)。这个函数的返回值就是文件指针

打开文件的模式:

文件的打开模式 含义 如果指定文件不存在
"r"(只读) 为了输入数据,打开一个二进制文件 出错
"w"(只写) 为了输出数据,打开一个二进制文件 建立一个新的文件
"a"(追加) 向一个二进制文件尾添加数据 建立一个新的文件
"rb"(只读) 为了输入数据,打开一个二进制文件 出错
"wb"(只写) 为了输出数据,打开一个二进制文件 建立一个新的文件
"ab"(追加) 向一个二进制文件尾添加数据 建立一个新的文件
“r+"(读写) 为了读和写,打开一个文本文件 出错
"w+"(读写) 为了读和写,建立一个新的文件 建立一个新的文件
"a+"(读写) 打开一个文件,在文件尾进行读写 建立一个新的文件
"rb+"(读写) 为了读和写,新建一个新的二进制文件 出错
"wb+"(读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件
”ab+"(读写) 打开一个二进制文件,在文件尾进行读和写 建立一个新的文件

下面是以写的形式打开文件,然后对文件输出数据。


下面的"data.txt"并没有文件路径,那么文件会建立在这个源文件test.c的路径底下,称为相对路径。有具体的路径称之为绝对路径。


pf就是我们的文件指针,通过fopen函数得到文件指针,通过文件指针对文件进行操作。


fopen函数有可能打开文件失败,所以我们要对pf文件指针进行检验,如果为空指针,那么我们要完善报错,通过perror函数,将报错信息显示。

#include <stdio.h>
int main()
{
  FILE* pf = fopen("data.txt", "w");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  fputs("abcde", pf);//这是顺序读写函数,接下来会仔细讲。
  fclose(pf);
  pf = NULL;
  return 0;
}

文件的关闭

fclose:关闭文件函数,有打开文件操作,就要有关闭文件操作 ,如果只有fopen函数,文件处于打开,对我们的内存是一种泄漏,所以fopen要和fclose搭配使用。

fclose相对简单,FILE * stream就是文件指针pf,将文件信息区的内容进行删除,断掉访问文件的途径,文件信息区的空间已经返回操作系统,但是访问文件信息区的地址还在pf,为了防止野指针问题,所最后将pf = NULL。

对文件的关闭就大功告成了。

输入输出的概念

什么是输入?什么是输出?

是相对于我们程序而言的,相对内存而言的,

将内存中的数据输送到文件,我们称之为输出

将文件中的数据输送到内存,也就是我们的程序中,用局部变量来存放文件中的数据,我们称之为输入


文件的顺序读写

接下来介绍对文件进行输入输出操作的具体函数。

函数名 功能 适用于
fgetc 字符输入函数 所有输入流
fputc 字符输出函数 所有输出流
fgets 文本行输入函数 所有输入流
fputs 文本行输出函数 所有输出流
fscanf 格式化输入函数 所有输入流
fprintf 格式化输出函数 所有输出流
fread 二进制输入 文件
fwrite 二进制输出 文件

所有输入流,包括标准输入流和文件输入流等(从文件输入或者从键盘输入)

所有输出流,包括标准输出流和文件输出流等(向文件输出或者向屏幕输出)

fgets:

输入函数,对字符进行接收。

FILE * stream是文件指针,如果从文件中输入数据,就是将fopen函数的返回值FILE* pf这个pf写进去。

fgets的返回值就是从文件中读取的字符,我们可以用字符变量ch对字符进行接收。

char ch = fgetc(pf);

这是文件中数据,通过fgetc接收到的是'a'这个字符。


fputc:

输出函数,将字符输出到文件中,输出到屏幕上。

int character:这是需要输出的字符

FILE * stream:指定要输出的文件的指针。



注意:打开模式为:“w"的时候,是新建一个文本文件,相当于是把之前的数据进行了覆盖。

#include <stdio.h>
int main()
{
  FILE* pf = fopen("data.txt", "w");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  fputc('a', pf);//这里的'a'是字符,所有用单引号括起来。
  fclose(pf);
  pf = NULL;
  return 0;
}


fgets:

文本行输入函数,其实可以看作字符串的输入函数。


char * str:接收字符串的地址,用数组来进行接收,数组名就是首元素的地址,将数组名写进去就可以。

int num:最大输入的字符量,最多可以输入多少字符。

FILE * stream:从进行输入操作的指定文件的指针。


#include <stdio.h>
int main()
{
  FILE* pf = fopen("data.txt", "r");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  char arr[20] = { 0 };
  fgets(arr,10, pf);
  fclose(pf);
  pf = NULL;
  return 0;
}



fputs:

文本行输出函数,其实可以看作字符串的输出函数。

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

FILE * stream:需要输出的指定文件的指针。



#include <stdio.h>
int main()
{
  FILE* pf = fopen("data.txt", "w");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  fputs("abcdef", pf);//这里是字符串,用双引号括起来。
  fclose(pf);
  pf = NULL;
  return 0;
}


fscanf:

格式化输入函数,可以对任意类型的数据进行接收。

FILE * stream:从要输入的指定文件的文件指针。

const char * format:这里可以类比scanf的参数进行比较。

#include <stdio.h>
struct S
{
  char name[20];
  int age;
  float f;
};
int main()
{
  struct S s = { 0 };
  FILE* pf = fopen("data.txt", "r");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  fscanf(pf,"%s %d %f", s.name,&(s.age), &(s.f));
  fclose(pf);
  pf = NULL;
  return 0;
}

这是data文本文件的数据:

fprintf:

格式化输出函数,可以对任意类型的数据进行接收。

FILE * stream:需要进行输出文件的文件指针。

const char * format, ...:这个参数可以对比printf函数进行理解。

#include <stdio.h>
struct S
{
  char name[20];
  int age;
  float f;
};
int main()
{
  struct S s = { "zhangsan",20,90.5f };
  FILE* pf = fopen("data.txt", "w");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  fprintf(pf,"%s %d %.1f", s.name,s.age,s.f);
  fclose(pf);
  pf = NULL;
  return 0;
}

fread:

二进制输入函数,对二进制函数进行解读,并接收。


const void * ptr:要进行输入操作的数据指针。

size_t size:输入数据的元素大小。

size_t count:输入数据元素的个数。

size_t count:要输入的文件的指针。


#include <stdio.h>
struct S
{
  char name[20];
  int age;
  float f;
};
int main()
{
  struct S s = {0 };
  FILE* pf = fopen("data.txt", "r");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  fread(&s, sizeof(struct S), 1, pf);
  fclose(pf);
  pf = NULL;
  return 0;
}


fwrite:

二进制输出函数,将数据以二进制的形式进行输出。

const void * ptr:要进行输出操作的数据指针。

size_t size:输出数据的元素大小。

size_t count:输出数据元素的个数。

size_t count:要输出的文件的指针。

#include <stdio.h>
struct S
{
  char name[20];
  int age;
  float f;
};
int main()
{
  struct S s = { "zhangsan",20,90.5f };
  FILE* pf = fopen("data.txt", "w");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  fwrite(&s, sizeof(struct S), 1, pf);
  fclose(pf);
  pf = NULL;
  return 0;
}


文件的随机读写

上面的函数将的的是顺序读写,按照从前往后的顺序进行输入和输出,实际上是光标的移动,每输入和输出一个字符,光标就会向后面移动一位。

随机读写函数就是对光标位置的呈现或者是对光标位置的改变。

随机读写函数:fseek、ftell、rewind。

fseek:

将光标移动位置。

FILE * stream:指定文件的文件指针。

long int offset:光标的偏移量,正数代表着光标往右边移动,负数代表着光标向左边移动。

int origin :光标偏移前的起始位置。

origin分为3种:

参数 光标位置
SEEK_SET 文件的开头
SEEK_CUR 当前光标的位置
SEEK_END 文件的末尾
#include <stdio.h>
int main()
{
  FILE* pf = fopen("data.txt", "r");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  char ch = 0;
  ch = fgetc(pf);
  printf("%c\n", ch);//a
  fseek(pf, 1, SEEK_CUR);
  ch = fgetc(pf);
  printf("%c\n", ch);//c
  fclose(pf);
  pf = NULL;
  return 0;
}

这是文本文件中的数据,按照顺序,会先打印a,在打印b,通过fseek,改变光标的位置,

结果就变成a和c。



ftell:

呈现当前位置光标相对于文章开头的偏移量。通过返回值来呈现。


FILE * stream:指定文件的文件指针。

还是刚才的文件内容和代码,我们最后看一下光标所在的位置的偏移量是3。通过两次的fgetc,第一次接收’a',光标移动一位,fseek将光标再次向后移动一位,最后fgetc接收‘c',光标再向后移动一位。


所有当前的光标偏移量为3。

#include <stdio.h>
int main()
{
  FILE* pf = fopen("data.txt", "r");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  char ch = 0;
  ch = fgetc(pf);
  printf("%c\n", ch);//a
  fseek(pf, 1, SEEK_CUR);
  ch = fgetc(pf);
  printf("%c\n", ch);//c
  int n = ftell(pf);
  printf("%d", n);
  fclose(pf);
  pf = NULL;
  return 0;
}



rewind:

将光标移到开头的位置。

FILE * stream:指定文件的文件指针。

还是原来的文件数据,通过中间的rewind,将光标的位置移动到文章开头,所以两次fgetc接收的结果都是'a'。

#inlcude <stdio.h>
int main()
{
  FILE* pf = fopen("data.txt", "r");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  char ch = 0;
  ch = fgetc(pf);
  printf("%c\n", ch);//a
  rewind(pf);
  ch = fgetc(pf);
  printf("%c\n", ch);//a
  fclose(pf);
  pf = NULL;
  return 0;
}



文件读取结束原因判定函数

feof

用来判断文章读取结束是不是因为到了文章的末尾。如果是读取完成,就会返回非零。如果读取没有完成,就会返回0。

ferror:

用来判断文章读取结束是不是因为读取错误。如果是读取错误就会返回非零,如果不是读取错误就会返回0。

 

文件缓冲区

文件缓冲区就是在内存中开辟的一块空间,在文件传输的过程中,将运输的数据存到里面,等到达一定的量再一起传输。为的就是减少操作系统的操作次数,达到提高效率的效果。


我们的顺序读写函数,比如说fputc,函数的读取并不是在运行完这个函数直接读取的,而是将读取到的数据放在文件缓冲区中。

有3种情况使得系统真正将数据进行运输:

1.文件缓冲区的内存已满。

2.通过fflush函数刷新文件缓冲区。

3.fclose关闭文件,文件的传输到此结束。


目录
相关文章
|
11天前
|
存储 C语言
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
|
1月前
|
C语言
【C语言篇】文件操作(下篇)
因为有缓冲区的存在,C语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂ 件。如果不做,可能导致读写⽂件的问题。
|
28天前
|
存储 API 数据处理
C语言中的文件操作
C语言中的文件操作
66 0
|
1月前
|
C语言 索引
【C语言】文件操作全解速览
【C语言】文件操作全解速览
27 0
|
1月前
|
存储 程序员 C语言
【C语言篇】文件操作(上篇)
在程序设计中,我们⼀般谈的⽂件有两种:程序⽂件、数据⽂件(从⽂件功能的⻆度来分类的)。
|
1月前
|
存储 编译器 程序员
【C语言】文件操作讲解
【C语言】文件操作讲解
|
3月前
|
C语言
C语言——文件操作
C语言——文件操作
44 2
C语言——文件操作
|
3月前
|
存储 程序员 编译器
文件操作(C语言)
文件操作(C语言)
|
3月前
|
存储 C语言 C++
【C语言基础】:文件操作详解(前篇:准备知识)
【C语言基础】:文件操作详解(前篇:准备知识)
|
3月前
|
C语言
【C语言基础】:文件操作详解(后篇)-2
【C语言基础】:文件操作详解(后篇)