【C语言】一文搞懂C语言文件操作2

简介: 【C语言】一文搞懂C语言文件操作

4. 文件的顺序读写


顺序读写,即按照文件中信息的顺序,逐次读写。


但是说到读写,它到底是什么意思?


之前我们只了解printfscanf函数,scanf是从外部设备(键盘),输入(读取)数据到内存中,为读。printf是从内存中,输出数据到外部设备(屏幕)上,为写。


b17098c2d1e11ed39895ac941e41594f.png


而文件的读,是从文件中读取(输入)信息,到内存中;文件的写,是由内存向文件写入(输出)数据。

2dd4f4729e39076f0cc77c25ed08ea50.png


4.1 函数总览

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


4.2 fputc


向文件中写入一个字符:

int fputc ( int character, FILE * stream );


  • character:写入的字符
  • stream:相关文件的文件流(即字符指针)。

返回值为字符的ascii码值,所以类型为int。


接下来我们使用循环来向文件中写入26个英文字母:

int main()
{
  // 打开文件
  FILE* pf = fopen("test.txt", "w");// 写
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  // 向文件中写入26个英文字母
  for (int i = 0; i < 26; i++)
  {
    fputc('a' + i, pf);// 通过循环得到所有的小写字母字符
  }
  // 关闭文件
  fclose(pf);
  pf = NULL;
  return 0;
}


运行结果:

b45ca0b98fa5a55d9527e2a15c4b036a.png


4.3 fgetc


向文件中读取一个字符:

int fgetc ( FILE * stream );


  • stream:相关文件的文件流(即字符指针)。

返回值为字符的ascii码值,所以类型为int。


我们上次已经将26个英文字母写入了文件中,那么我们再将它读出来:

int main()
{
  // 打开文件
  FILE* pf = fopen("test.txt", "r");// 读
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  // 向文件中读取26个英文字母
  for (int i = 0; i < 26; i++)
  {
    int ch = fgetc(pf);// 将读到的字符的ascii码值放到ch中
    printf("%c ", ch);
  }
  // 关闭文件
  fclose(pf);
  pf = NULL;
  return 0;
}


运行结果:

e6d6b14ed1e8cd70de26a1eb95f0b9bb.png


但是如果我们不知道文件中到底有多少个字母,我们该如何读取?


其实在函数介绍的返回值部分,还有一点需要补充fgetc读取失败返回EOF


所以,纠正一下:fgetc读取成功返回字符的ascii码值,读取失败返回EOF。


所以我们可以改写成这样的形式:

int main()
{
  // 打开文件
  FILE* pf = fopen("test.txt", "r");// 读
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  int ch = 0;
  while ((ch = fgetc(pf)) != EOF)// fgetc返回值不等于EOF则继续读取
  {
    printf("%c ", ch);
  }
  // 关闭文件
  fclose(pf);
  pf = NULL;
  return 0;
}



4.4 fputs


从内存中写入数据到文件中:

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


  • str:写入文件的字符串。
  • stream:相关文件的文件流(即字符指针)。

fputs是按照顺序写入的,只关注写入的内容,不考虑换行。


将字符串写入文件中:

// 按照顺序写入文本行
int main()
{
  // 打开文件
  FILE* pf = fopen("test.txt", "w");// 写
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  // 写文件,按行写入
  fputs("hello\n", pf);// 手动写入换行
  fputs("world\n", pf);// 手动写入换行
  // 关闭文件
  fclose(pf);
  pf = NULL;
  return 0;
}


运行结果:

64864a4634559b9ee231441ed19aa918.png



4.5 fgets(精讲)


向文件中按行读取数据到内存中:

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


   str:存放读取数据的位置(读取的数据会被存入该空间中)。

   num:读取字符的最大数。

   stream:相关文件的文件流(即字符指针)。


那么fgets函数有没有什么细节?使用的时候我们需要注意什么?我们接下来一一观察:

(注:当前test.txt文件中依然存放着fputs写入的数据)


int main()
{
  // 打开文件
  FILE* pf = fopen("test.txt", "r");// 读
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  // 读文件,按行读取
  char arr[20] = "##########";
  fgets(arr, 3, pf);
  // 关闭文件
  fclose(pf);
  pf = NULL;
  return 0;
}

6ed3ad2496aa97778f37d27fc805e58d.png


当我num = 3时,只读取了2个字符。这说明当读取的最大字符数小于文本行数据的总长度时,fgets实际上只会读取num - 1个数据,还有一个为填充的’\0’!!!


当我们本行数据没有读取完成,下一次读取,仍然会读取这一行,直到本行读取完毕才会读取下一行:


c2c7b2d4ee418d43fe60a674bd0c04b0.png


运行结果:

fda4b51b40ed34f9fecca7df7bcf6b03.png


而当我们读取的最大字符数大于文本行数据的总长度时(例如我要读取10个字符,但是我的文本行只有hello),会发生什么?


668e558733edb4138068db83ae1bec49.png

我们发现当最大字符数大于文本行数据的总长度时,本行数据被读取完毕,停止读取,在字符串末尾加上’\0’,且不会读取下一行


那么读取两行文本行怎么做?


int main()
{
  // 打开文件
  FILE* pf = fopen("test.txt", "r");// 读
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  // 读文件,按行读取
  char arr[20] = "##########";
  fgets(arr, 7, pf);
  printf("%s", arr);
  fgets(arr, 7, pf);
  printf("%s", arr);
  // 关闭文件
  fclose(pf);
  pf = NULL;
  return 0;
}

运行结果:

1d116c3a508f3271a45f6767c617cbc6.png


注意点:若fgets读取数据到字符指针中一定要初始化!!!

例如:

char* p;// 一定要初始化,否则没有空间来存放数据


我们可以使用动态内存为其分配空间:

char* p = (char*)malloc(sizeof(int) * 20);


使用:

#include <stdlib.h>
int main()
{
  // 打开文件
  FILE* pf = fopen("test.txt", "r");// 读
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  // 读文件,按行读取
  char* p = (char*)malloc(sizeof(int) * 20);// 动态分配内存
  fgets(p, 7, pf);
  printf("%s", p);
  fgets(p, 7, pf);
  printf("%s", p);
  // 关闭文件
  fclose(pf);
  pf = NULL;
  return 0;
}


运行结果:00fdd125082ad6edc0c48031908c27c4.png


总结一下fgets在使用时的注意点:


   当最大字符数count小于文本行数据的总长度时,fgets实际上只会读取num - 1个数据,还有一个为填充的’\0’。

   当最大字符数count大于文本行数据的总长度时,本行数据被读取完毕,停止读取,在字符串末尾加上’\0’,且不会读取下一行。

   fgets在未完全读取一行元素后,下一次读取会先读取未读取完的这一行。本行读取完毕开始读取下一行。

   当str参数为指针时,需要初始化。

   当fgets读取失败时,会返回NULL空指针。



4.6 fprintf


前面的fgec、fgets分别对应着字符和字符串的操作,但是格式化数据如何写入文件?这时就要说起fprintf函数。



把格式化数据从内存写入文件中:

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


  • stream:相关文件的文件流(即字符指针)。
  • format:格式化字符串

其实对比printf我们就多了一个stream参数,只需要加上一个文件指针即可。


比如我们向文件中写入一个结构体:

struct S
{
  char name[20];
  int age;
  float score;
};
int main()
{
  struct S s = { "灰太狼", 10086, 114.514f };
  // 把s中的数据写到文件中
  FILE* pf = fopen("test.txt", "w");// 写
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  // 写文件
  fprintf(pf, "%s %d %f", s.name, s.age, s.score);
  fclose(pf);
    pf = NULL;
  return 0;
}


运行结果:95e1ac4f4af592a61505b9a8a6a75416.png


4.7 fscanf


把格式化数据从文件读取到内存中:

int fscanf ( FILE * stream, const char * format, ... );
  • stream:相关文件的文件流(即字符指针)。
  • format:格式化字符串。

fscanf对比scanf也是多了一个文件指针。


例如将文件中的内容读取到内存中:


(注:当前test.txt文件中依然存放着fprintf写入的数据)

struct S
{
  char name[20];
  int age;
  float score;
};
int main()
{
  struct S s = { "灰太狼", 10086, 114.514f };
  // 把s中的数据写到文件中
  FILE* pf = fopen("test.txt", "r");// 读
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  // 读文件
  fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));
  printf("%s %d %f\n", s.name, s.age, s.score);
  fclose(pf);
  pf = NULL;
  return 0;
}s


运行结果:

2672ac3a4589f7b4f256928ecb393e56.png


4.8 fwrite


以二进制形式向文件中写入数据:

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


  • ptr:被写入元素的起始地址。
  • size:写入元素的大小。
  • count:写入元素的个数。
  • stream:相关文件的文件流(即字符指针)。


通俗讲就是,从ptr开始的数据,一次写入countsize大小的元素到文件中。


例如我们以二进制形式向文件中写入一个结构体的数据:

struct S
{
  char name[20];
  int age;
  float score;
};
int main()
{
  struct S s = { "灰太狼", 10086, 114.514f };
  FILE* pf = fopen("test.txt", "wb");// 二进制只写
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  // 从s的地址处,以二进制形式向文件中写入一个大小为s的元素
  fwrite(&s, sizeof(s), 1, pf);
  fclose(pf);
  pf = NULL;
  return 0;
}


运行结果:

5aeb7f4f8d599a8dafd7ca64fb2f72a6.png

里面有些数据是看不懂的,因为我们是以二进制写入的。



4.9 fread


以二进制形式从文件中读取数据到内存中:

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


   ptr:存放读取数据的空间的起始地址。

   size:读取数据的大小。

   count:读取数据的个数。

   stream:相关文件的文件流(即字符指针)


fread的返回值:读取正常返回size的大小;读取失败返回实际读到的完整元素的个数(小与size)。

通俗讲就是,从文件流中读取count个大小为size的数据,到ptr指向的空间中。


(注:当前test.txt文件中依然存放着fwirte写入的数据)

struct S
{
  char name[20];
  int age;
  float score;
};
int main()
{
  struct S s = { "灰太狼", 10086, 114.514f };
  FILE* pf = fopen("test.txt", "rb");// 二进制只读
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  fread(&s, sizeof(s), 1, pf);
  printf("%s %d %f\n", s.name, s.age, s.score);
  fclose(pf);
  pf = NULL;
  return 0;
}


运行结果:

5f806611a1a0d861329491650072ac38.png

虽然我们肉眼看不懂二进制形式的数据,但是使用程序,以二进制形式读取这些数据到屏幕上,还是可以看懂的。



4.10 补充知识(重要)


在函数总览的表中,我们看到了上述函数适用于什么流。我们当前只看到上述函数适用于文件,但是它们是否能适用于其他流?


我们在操作文件时,需要打开或关闭文件。但是我们平常使用printf、scanf函数时,并没有打开键盘和屏幕。这是因为我们的屏幕和键盘是默认打开的,而文件则不是默认打开的。


键盘、屏幕、文件均为外部设备。


对任何一个C程序,只要运行起来,就默认打开3个流:


   stdin:标准输入流 - 键盘

   stdout:标准输出流 - 屏幕

   stderr:标准错误流 - 屏幕


而上述流的类型都是**FILE***的。


那么,既然我们的标准输入流stdin是FILE*,那么这是否说明,也能被上述函数使用?


举个例子,我们的fgetc和fputc函数是这样的形式:


int fgetc ( FILE * stream );

int fputc ( int character, FILE * stream );

它们的参数有FILE*类型,那么我们将stdin传入fgetc,将stdout传入fputc


int main()
{
    int ch = fgetc(stdin);
    fputc(ch, stdout);
    return 0;
}


运行结果:

6344becf680e060e82b11d137f590511.png



那么通过这个样例,我们也可以推导出:

scanf(...) <==> fscanf(stdin, ...);
-----------------------------------
printf(...) <==> fprintf(stdout, ...);


同样的我们也可以举出样例:

int main()
{
  int arr[] = { 1,2,3,4,5 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  // 输入
    for (int i = 0; i < sz; i++)
  {
    fscanf(stdin, "%d", &arr[i]);
  }
    // 输出
  for (int i = 0; i < sz; i++)
  {
    fprintf(stdout, "%d ", arr[i]);
  }
  return 0;
}



运行结果:

c640fe7a44c4fdc6795b89a9e705323d.png


4.11 杂例(选读)


这是博主探究fgetsfputs时,偶然发现的一个问题。就博主看来知识点与本篇博客有些不符,但是秉持让博客内容更加丰富的原则,加上这个问题也是博主踩过的一个坑,加上探究过程也是一波三折,于是将其作为选读,记录在本博客中,和大家一起分享!


当我在工程的目录下,手动创建一个test.txt,然后往里面写入中文:圣光背叛了我!

39d4b18e42be75ed8d6787d239cd9d1d.png



当我在程序中,使用只读的方式打印的是乱码:

int main()
{
  FILE* pf = fopen("test.txt", "r");
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  char arr[20] = "###########";
  fgets(arr, 20, pf);
  printf("%s", arr);
  fclose(pf);
  pf = NULL;
  return 0;
}


运行结果:

56090ce45be5378510b7855239b818b2.png


但是如果我先用fputs将这句话重新写入文件,再以fgets的方式取出时,就是正常的:

int main()
{
  FILE* pf = fopen("test.txt", "r");
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  char arr[20] = "###########";
  //fputs("圣光背叛了我!\n", pf);// 以fputs写入
  fgets(arr, 20, pf);
  printf("%s", arr);
  fclose(pf);
  pf = NULL;
  return 0;
}


运行结果:

cb73626bee66629744836c5744f8b6b1.png


这是为什么?


这里其实是一个字符编码集的问题。我们平常的设备默认的字符编码集是UTF - 8的。但是我们从程序里面读取数据的时候,放到运行的黑框中,它的默认显示的字符集是GB2312的。然后就导致它们两个的字符集不一样了。写入的字符集和打印输出的字符集不同,就显示就乱码了。


而我们用fputs写入用fgets读取是正常的是因为它们的字符集是一样的,不存在写入的软件和读取的软件的程序字符集不一样的情况。


然后博主就开始了尝试…


我是直接更改本文件的字符集的,在vs2022中大体步骤为:


   编辑区找到工具 -> 自定义 -> 命令 -> 菜单栏下拉找到文件 -> 添加命令 -> 类别中找到文件 -> 命令中下拉找到高级保存选项 -> 点击确定 -> 关闭自定义任务栏

   编辑区找到文件 -> 经过以上操作后会出现高级保存选项 -> 再打开将编码从简体中文(GB2312)改为UTF - 8


c50548434795a57b0acc630008975ec4.png



注:编辑区就是vs2022中最上面一条栏,有文件,编辑等…


由于这是一篇文件相关的操作,所以只列出最后一步,过程就不一一截图展示操作了,有兴趣的可以上网查找操作。


然后查看本电脑字符集是否为UTF - 8,不是的需要重启更改一下。


更改完毕就可以重新开始测试了,这时将文件内容手动输入为圣光背叛了我,无需使用fputs进行写入,直接使用fgets就可以读取到文件的内容。因为这时,电脑字符集和程序显示的字符集相同了。


int main()
{
  FILE* pf = fopen("test.txt", "r");
  if (NULL == pf)
  {
    perror("fopen");
    return 1;
  }
  char arr[20] = "###########";
  fgets(arr, 20, pf);
  printf("%s", arr);
  fclose(pf);
  pf = NULL;
  return 0;
}



运行结果:

27ccb1359f8770f1d27d217df1bd3000.png

注:这里博主电脑的默认字符集其实不是UTF - 8。在测试时,由于字符集不同,所以重启后测试时,可能是核心文件找不到的原因,导致一直跳弹窗,很令人苦恼,而且博主还测试错了好几遍,当时简直快奔溃了…如果你电脑的默认字符集不是UTF - 8的话,谨慎尝试,很烦,真的很烦!!!这也是为什么我将这个杂例设定为选读的原因…至于这个知识点,还是了解就好~




相关文章
|
19天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
42 9
|
21天前
|
存储 数据管理 C语言
C 语言中的文件操作:数据持久化的关键桥梁
C语言中的文件操作是实现数据持久化的重要手段,通过 fopen、fclose、fread、fwrite 等函数,可以实现对文件的创建、读写和关闭,构建程序与外部数据存储之间的桥梁。
|
23天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
73 3
|
1月前
|
存储 C语言
【c语言】玩转文件操作
本文介绍了C语言中文件操作的基础知识,包括文件的打开和关闭、文件的顺序读写、文件的随机读写以及文件读取结束的判定。详细讲解了`fopen`、`fclose`、`fseek`、`ftell`、`rewind`等函数的使用方法,并通过示例代码展示了如何进行文件的读写操作。最后,还介绍了如何判断文件读取结束的原因,帮助读者更好地理解和应用文件操作技术。
44 2
|
2月前
|
存储 C语言
C语言文件操作(2)
【10月更文挑战第2天】
|
3月前
|
C语言
C语言——文件操作
本文介绍了文件的基本操作,包括文件的打开、关闭、读取和写入。使用`fopen`函数以不同模式(如“r”、“w”等)打开文件,并通过`fclose`关闭。文章详细解释了如何利用`fputc`、`fputs`及`fprintf`进行格式化写入,同时介绍了`fgetc`、`fgets`和`fscanf`用于文件内容的读取。此外,还涵盖了二进制文件的读写方法以及如何通过`fseek`、`ftell`和`rewind`实现文件的随机访问。
55 1
C语言——文件操作
|
2月前
|
程序员 编译器 C语言
C语言底层知识------文件操作
本文详细介绍了文件操作的基本概念,包括文件的分类(程序文件和数据文件,其中着重于数据文件的文本文件和二进制文件),流的概念及其在C程序中的应用,以及标准输入输出流stdin、stdout和stderr的作用。作者通过示例展示了如何使用fopen、fclose和常见的读写函数如fgetc、fputc和fgets进行文件操作。
29 2
|
2月前
|
存储 缓存 编译器
文件操作——C语言
文件操作——C语言
|
2月前
|
存储 C语言
简述C语言文件操作
简述C语言文件操作
12 0
|
2月前
|
存储 文件存储 C语言
深入C语言:文件操作实现局外影响程序
深入C语言:文件操作实现局外影响程序