当前位置: 首页 > news >正文

【通关文件操作(上)】--文件的意义和概念,二进制文件和文本文件,文件的打开和关闭,文件的顺序读写

目录

一.文件的意义和概念

1.1--为什么要使用文件?&& 什么是文件?

1.2--程序文件 && 数据文件

1.3--文件名

二.二进制文件和文本文件

三.文件的打开和关闭

3.1--流和标准流

3.1.1--流

3.1.2--标准流

3.2--文件指针

3.3--文件的打开和关闭

3.3.1--fopen函数

3.3.2--fclose函数

四.文件的顺序读写

4.1--fputc函数

4.2--fgetc函数

4.3--feof和ferror函数

4.4--fputs函数

4.5--fgets函数

4.6--fprintf函数

4.7--fscanf函数


🔥个人主页:@草莓熊Lotso的个人主页

🎬作者简介:C++研发方向学习者

📖个人专栏:《C语言》

⭐️人生格言:生活是默默的坚持,毅力是永久的享受。


一.文件的意义和概念

1.1--为什么要使用文件?&& 什么是文件?

--如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用文件。

--磁盘(硬盘)上的文件是文件。但是在程序设计中,我们⼀般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。

1.2--程序文件 && 数据文件

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

数据文件:

  •  文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
--本篇文章主要介绍的是数据文件,在之前的学习中我们所处理数据的输入输出都是以终端为对象的 即从终端的键盘输入数据,运行结果显示到屏幕上。
但其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件

1.3--文件名

--一个文件要有唯一个的文件标识,以便我们识别和使用。

文件名包含3个部分:文件路径+文件名主干+文件后缀

例如:D:\code\test.txt


二.二进制文件和文本文件

根据数据的组织形式,数据文件被分为文本文件和二进制文件。
  1. 数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件。
  2. 如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在文件中如何存储呢?
  • 字符⼀律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
  • 如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符⼀个字节),而二进制形式输出,则在磁盘上只占4个字节。

代码演示:

#include <stdio.h>int main()
{int a = 10000;FILE* pf = fopen("data.txt", "wb");if (pf == NULL){perror("fopen");return 1;}fwrite(&a, 4, 1, pf);//二进制的形式写到文件中//关闭文件fclose(pf);pf = NULL;return 0;
}

我们默认打开的会是文本文件,观察不了二进制形式,所以我们需要在VS上打开二进制文件 ,具体操作如下:

我们把data.txt的打开方式设置为二进制编辑器,就可以观察二进制文件了。

 


三.文件的打开和关闭

3.1--流和标准流

3.1.1--流

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。
C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的。⼀般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。

3.1.2--标准流

那为什么我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?
这是因为C语言程序在启动的时候,默认打开了3个流:
  • stdin - 标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
  • stdout - 标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出
  • 流中。
  • stderr - 标准错误流,大多数环境中输出到显示器界面。

这就是是默认打开的三个流,所以我们使用scanf、printf等函数就可以直接进行输入输出操作。stdin、stdout、stderr 三个流的类型是: FILE * ,通常称为文件指针。 C语言中,就是通过 FILE* 的文件指针来维护流的各种操作的。

3.2--文件指针

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

每个被使用的文件都在内存中开辟了⼀个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名为: 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 ;
不同的编译器FILE类型包含的内容不完全相同,每次打开文件,系统都会自动创建一个FILE结构的变量,并填充其中信息,我们不需要关心这个
我们一般都是通过一个FILE指针来维护这个结构变量,这样使用起来更加方便。
创建形式如下:
FILE* pf; //文 件指针变量
定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是以个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与它关联的文件

 

3.3--文件的打开和关闭

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

在编写程序的时候,在打开文件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建立了指针和文件的关系。

我们使用  fopen 函数来打开文件, fclose 来关闭文件。

3.3.1--fopen函数

//打开文件
1. FILE * fopen ( const char * filename ,   const char * mode );

功能:

fopen函数是用来打开参数filename所指定的文件的,同时将其和一个流进行关联,后续对流的操作是通关fopen函数返回的指针来维护的。具体对流的操作是通过参数mode指定的

参数:

  • filename:表示被打开的文件的名字,这个名字可以是绝对路径,也可以是相对路径。
  • mode:表示对打开文件的操作方式,后面会有具体解析

返回值:

  • 如果文件成功打开,该函数返回一个指向FILE对象的指针,该指针可以用于后续操作中标识对应的流
  • 如果打开失败,则返回NULL指针,所以我们要对fopen的返回值进行判断,来验证文件是否成功打开

代码演示:这里演示5种不同情况

//1.data.txt在当前工程的目录下
int main()
{FILE* pf = fopen("data.txt", "r");//这里的路径是相对路径,表示在当前的工程目录下的data.txtif (pf == NULL){perror("fopen");return 1;}//读取文件//关闭文件return 0;
}//2. . --表示当前路径, .. --表示上一级路径
//所以如果data.txt在上一级路径下的时候应该这样写
int main()
{FILE* pf = fopen("./../data.txt", "r");//这里的路径是相对路径,表示在当前的工程目录下的data.txtif (pf == NULL){perror("fopen");return 1;}//读取文件//关闭文件return 0;
}//3.如果data.txt在上二级路径下的时候应该这样写
int main()
{FILE* pf = fopen("./../../data.txt", "r");//这里的路径是相对路径,表示在当前的工程目录下的data.txtif (pf == NULL){perror("fopen");return 1;}//读取文件//关闭文件return 0;
}//4.如果data.txt在上二级路径下的文件夹text中的text2文件夹里的时候应该这样写
int main()
{FILE* pf = fopen("./../../text/text2/data.txt", "r");//这里的路径是相对路径,表示在当前的工程目录下的data.txtif (pf == NULL){perror("fopen");return 1;}//读取文件//关闭文件return 0;
}//5.如果data.txt在桌面上,就得用绝对路径了
int main()
{FILE* pf = fopen("C:/Users/LOTSO/Desktop/data.txt", "r");//这里的路径是绝对路径if (pf == NULL){perror("fopen");return 1;}//读取文件//关闭文件return 0;
}

mode--文件操作方式:

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

3.3.2--fclose函数

1. int fclose ( FILE * stream );

功能:
关闭参数 stream 关联的文件,并取消其关联关系。与该流关联的所有内部缓冲区均会解除关联并刷新:任何未写入的输出缓冲区内容将被写入,任何未读取的输入缓冲区内容将被丢弃。
参数:
  • stream :指向要关闭的流的 FILE 对象的指针
返回值:成功关闭 stream 指向的流会返回0,否则会返回 EOF

代码演示:

#include<stdio.h>int main()
{FILE* pf = fopen("data.txt", "r");//这里的路径是相对路径,表示在当前的工程目录下的data.txtif (pf == NULL){perror("fopen");return 1;}//读取文件//关闭文件fclose(pf);pf = NULL;return 0;
}

四.文件的顺序读写

在进行文件写的时候,我们会涉及下面这些函数:

函数名功能适用于
fgetc从输入流中读取一个字符所有输入流
fputc  向输出流中写入一个字符所有输出流
fgets从输入流中读取一个字符串所有输入流
fputs向输出流中写入一个字符串所有输出流
fscanf从输入流中读取带有格式的数据所有输入流
fprintf向输出流中写入带有格式的数据所以输出流
fread从输入流中读取一块数据文件输入流
fwrite从输出流中写入一块数据文件输出流

4.1--fputc函数

1. int fputc ( int character, FILE * stream );

功能:
将参数 character 指定的字符写入到 stream 指向的输出流中,通常用于向文件或标准输出流写入字符。在写入字符之后,还会调整指示器。字符会被写入流内部位置指示器当前指向的位置,随后该指示器自动向前移动⼀个位置。
参数
  • character :被写入的字符
  • stream :是⼀个FILE*类型的指针,指向了输出流(通常是文件流或stdout)。
返回值:
  • 成功时返回写入的字符(以 int 形式)。
  • 失败时返回 EOF (通常是 -1),错误指示器会被设置,可通过 ferror() 检查具体错误。

代码演示:

//fputc函数演示
#include<stdio.h>
int main()
{FILE* ps = fopen("data.txt", "w");//w--写,用这个才可以写文件if (ps == NULL){perror("fopen");return 1;}//写文件fputc('a', ps);fputc('b', ps);fputc('c', ps);//关闭文件fclose(ps);ps = NULL;return 0;
}//循环写入多个字符
int main()
{FILE* ps = fopen("data.txt", "w");//w--写,用这个才可以写文件if (ps == NULL){perror("fopen");return 1;}//写文件for (int i = 'a';i <= 'z';i++){fputc(i, ps);}//关闭文件fclose(ps);ps = NULL;return 0;
}

//直接打印在屏幕上,标准输出流
#include<stdio.h>
int main()
{fputc('a', stdout);fputc('b', stdout);fputc('c', stdout);return 0;
}

4.2--fgetc函数

1.   int fgetc ( FILE * stream );
功能:
从参数 stream 指向的流中读取一个字符。函数返回的是文件指示器当前指向的字符,读取这个字符之后,文件指示器自动前进道下⼀个字符。
参数:
  • stream : FILE*类型的文件指针,可以是 stdin ,也可以是其他输入流的指针。如果是 stdin 就从标准输入流读取数据。如果是文件流指针,就从文件读取数据。
返回值:
  • 成功时返回读取的字符(以 int 形式)。
  • 若调用时流已处于文件末尾,函数返回 EOF 并设置流的文件结束指示器( feof )。
  • 若发生读取错误,函数返回 EOF 并设置流的错误指示器( ferror )。 

 代码演示: 

//fgetc函数演示,假设文件里是26个字母
#include<stdio.h>
int main()
{FILE* ps = fopen("data.txt", "r");if (ps == NULL){perror("fopen");return 1;}//读文件for (int i = 0;i < 10;i++){int c=fgetc(ps);fputc(c, stdout);}//关闭文件fclose(ps);ps = NULL;return 0;
}

从键盘读取数据
#include<stdio.h>
int main()
{//读数据int c=fgetc(stdin);fputc(c, stdout);return 0;
}

4.3--feof和ferror函数

int feof ( FILE * stream );
// 检测 stream 指针指向的流是否遇到文件末尾
int ferror ( FILE * stream );
// 检测 stream 指针指向的流是否发生读 / 写错误
  • 如果在读取文件的过程中,遇到了文件末尾,文件读取就会结束。这时读取函数会在对应的流上设置⼀个文件结束的指示符,这个为文件结束指示符可以通过 feof 函数检测到。如果 feof 函数检测到文件结束指示符已经被设置,则返回非0的值,如果没有设置则返回0。
  • 如果在读/写文件的过程中,发生了读/写错误,文件读取就会结束。这时读/写函数会在对应的流上设置⼀个错误指示符,这个错误指示符可以通过 ferror 函数检测到。如果 ferror 函数检测到错误指示符已经被设置,则返回非0的值,如果没有设置则返回0。

测试feof: 

//测试feof,假设文件里是abcdef
#include<stdio.h>
int main()
{FILE* ps = fopen("data.txt", "r");if (ps == NULL){perror("fopen");return 1;}//读文件int i = 0;for (i = 0;i < 10;i++){int c = fgetc(ps);if (c == EOF){if (feof(ps)){printf("遇到文件结尾了\n");//读到f就结尾了}else if (ferror(ps)){printf("读取发生错误\n");}}else{fputc(c, stdout);}}fclose(ps);ps = NULL;return 0;
}

 测试ferror:

//测试ferror,
// 以写的形式打开文件后,后面再读文件,就会发生错误
#include<stdio.h>
int main()
{FILE* ps = fopen("data.txt", "w");if (ps == NULL){perror("fopen");return 1;}//读文件int i = 0;for (i = 0;i < 6;i++){int c = fgetc(ps);if (c == EOF){if (feof(ps)){printf("遇到文件结尾了\n");}else if (ferror(ps)){printf("读取文件错误\n");}}else{fputc(c, stdout);}}fclose(ps);ps = NULL;return 0;
}

4.4--fputs函数

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

功能:
将参数 str 指向的字符串写入到参数 stream 指定的流中(不包含结尾的空字符 \0 ),适用于文件流或标准输出(stdout)。
参数:
  • str : str是指针,指向了要写入的字符串(必须以 \0 结尾)
  • stream :是⼀个 FILE* 的指针,指向了要写入字符串的流。
返回值:
  • 成功时返回非负整数。
  • 失败时返回 EOF (-1),同时会设置流的错误指示器,可以使用 ferror() 检查错误原因。

代码演示:

//fputs函数演示
#include<stdio.h>
int main()
{FILE* ps = fopen("data.txt", "w");if (ps == NULL){perror("fopen");return 1;}//写文件fputs("abc\n", ps);fputs("def", ps);//关闭文件fclose(ps);ps = NULL;return 0;
}

和fputc函数一样,我们也可以直接打印在屏幕上,把ps改成stdout就可以,这里就不再演示了。 

4.5--fgets函数

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

功能:
stream 指定输入流中读取字符串,至读取到换行符、文件末尾(EOF)或达到指定字符数
(包含结尾的空字符 \0 ),然后将读取到的字符串存储到str指向的空间中。
参数:
  • str :是指向字符数组的指针,str指向的空间用于存储读取到的字符串。
  • num :最大读取字符数(包含结尾的 \0 ,实际最多读取 num-1 个字符)。
  • stream :输入流的文件指针(如文件流或 stdin )。
返回值:
  • 成功时返回 str 指针。
  • 若在尝试读取字符时遇到⽂件末尾,则设置文件结束指示器,并返回 NULL ,需通过 feof()检测。
  • 若发生读取错误,则设置流错误指示器,并返回 NULL,通过 ferror() 检测。

 代码演示:

// fgets函数演示,假设文件里还是上面fputs函数写的 abc\n def
#include<stdio.h>
int main()
{FILE* fp = fopen("data.txt", "r");if (fp == NULL){perror("fopen");return 1;}char arr1[] = "*************";/*char* f = fgets(arr1, sizeof(arr1), fp);fputs(f, stdout);*///全部打印出来abcchar*p=fgets(arr1,2, fp);//实际读取的是num-1个fputs(p,stdout);//所以是a//不再使用文件时,需要关闭文件fclose(fp);fp = NULL; //将指针置为NULL,避免成为野指针。return 0;
}

我们再来看一个例子并且通过调试以及打印在屏幕上来观察: 

//把abc\n def全读出来,调试着看并打印在屏幕上
#include <stdio.h>
int main()
{FILE* fp = fopen("data.txt", "r");if (fp == NULL){perror("fopen\n");return 1;}char arr1[] = "*************";char*p=fgets(arr1, sizeof(arr1), fp);fputs(p, stdout);char arr2[] = "*************";char* f = fgets(arr2, sizeof(arr1), fp);fputs(f, stdout);//不再使用文件时,需要关闭文件fclose(fp);fp = NULL; //将指针置为NULL,避免成为野指针。return 0;
}

 

 和fgetc函数一样,我们也可以直接从键盘上读取,把fp改成stdin就可以,这里也不再演示了。

4.6--fprintf函数

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

2.  //我们类比printf看看

3.  int  printf ( const char * format, ... );

功能:
fprintf 是将格式化数据写入指定文件流的函数。它与 printf 类似,但可以输出到任意文件(如磁盘文件、标准输出、标准错误等),而不仅限于控制台。
参数:
  • stream :指向 FILE 对象的指针,表⽰要写⼊的⽂件流(如 stdout 、文件指针等)。
  • format :格式化字符串,包含要写⼊的⽂本和格式说明符(如 %d %s 等)。
  • ... :可变参数列表,提供与格式字符串中说明符对应的数据。
返回值:
  • 成功时,返回写入的字符总数(非负值)。
  • 失败时,先设置对应流的错误指示器,再返回负值,可以通过 ferror() 来检测。

代码演示:

// fprintf函数演示
#include <stdio.h>
struct stu
{char name[30];int age;float score;
};
int main()
{struct stu s = { "zhangsan",18,95.5f};FILE* fp = fopen("data.txt", "w");if (fp == NULL){perror("fopen");return 1;}//写文件fprintf(fp, "%s %d %f", s.name, s.age, s.score);//关闭文件fclose(fp);fp = NULL;return 0;
}

当然这也可以直接打印在屏幕上,还是把fp换成stdout,这样效果等同于printf,这里就不演示了。

4.7--fscanf函数

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

2. //类比scanf看看

3.   int scanf const char * format, ... );

功能:
fscanf 是从指定文件流中读取格式化数据的函数。它类似于 scanf ,但可以指定输入源(如文件、标准输入等),而非仅限于控制台输⼊。适用于从文件解析结构化数据(如整数、浮点数、字符串等)。
参数:
  • stream :指向 FILE 对象的指针,表⽰要读取的⽂件流(如 stdin 、文件指针等)。
  • format :格式化字符串,定义如何解析输⼊数据(如 %d %f %s 等)。
  • ... :可变参数列表,提供存储数据的变量地址(需与格式字符串中的说明符匹配)。
返回值:
成功时,函数返回成功填充到参数列表中的项数。该值可能与预期项数⼀致,也可能因以下原因少
于预期(甚至为零):
  • 格式和数据匹配失败;
  • 读取发生错误;
  • 到达文件末尾(EOF)。
如果在成功读取任何数据之前发生:
  • 发生读取错误,会在对应流上设置错误指示符,则返回 EOF
  • 到达文件末尾,会在对应流上设置文件结束指示符,则返回 EOF

代码演示:

//fsanf函数演示
struct stu
{char name[30];int age;float score;
};
int main()
{struct stu s = { 0 };FILE* fp = fopen("data.txt", "r");if (fp == NULL){perror("fopen");return 1;}//写文件fscanf(fp, "%s %d %f", s.name, &(s.age), &(s.score));fprintf(stdout, "%s %d %f", s.name, s.age, s.score);//打印数据在stdout//关闭文件fclose(fp);fp = NULL;return 0;
}

当然这也可以直接从键盘上读取,还是把fp换成stdin,这样效果等同于scanf,这里就不演示了。 

--由于篇幅原因,后面还有几个函数,我们在下篇文章再一起来继续学习。


往期回顾:

【C语言动态内存管理】--动态内存分配的意义,malloc和free,calloc和realloc,常见的动态内存的错误,动态内存经典笔试题分析,柔性数组,总结C/C++中程序内存区域划分

【自定义类型-结构体】--结构体类型,结构体变量的创建和初始化,结构体内存对齐,结构体传参,结构体实现位段

【自定义类型-联合和枚举】--联合体类型,联合体大小的计算,枚举类型,枚举类型的使用 

结语:本篇文章就到此结束了,继前面一篇文章后,在此篇文章中给大家分享了文件操作中的文件的意义和概念,二进制文件和文本文件,文件的打开和关闭,文件的顺序读写(部分)等知识点,下篇文章会接着从文件的顺序读写后面的几个函数开始。分析文件操作中的剩余知识点,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。

相关文章:

  • 智能体赋能效率,企业知识库沉淀价值:UMI企业智脑的双轮驱动!
  • Pydantic 学习与使用
  • TDengine 中的存储配置
  • 电商 API 开发指南:基于唯品会 API 实现商品详情页动态数据采集
  • 推荐GitHub项目:Pangolin开源Amazon关键词解析器(Python)与电商数据采集API技术剖析
  • 《仿盒马》app开发技术分享-- 确认订单页(数据展示)(端云一体)
  • [网页五子棋][用户模块]数据库设计和配置(MyBatis)、约定前后端交互接口、服务器开发
  • java中的定时期
  • TWTSolutions水厂污水厂设计计算软件:化学强化絮凝单元
  • MobaXterm连接Docker Desktop中的容器(shell)
  • oracle在线迁移数据文件
  • 系统设计——项目设计经验总结3
  • JDK21深度解密 Day 6:ZGC与内存管理进化
  • Odoo 财务模块全面深度解读(VIP15万字版)
  • 数据库的事务(Transaction)
  • 5G 核心网 UE 状态深度剖析:机制、迁移与演进
  • SAR ADC 比较器的响应设计
  • C++STL之deque
  • Halcon 霍夫变换
  • 计算机科技笔记: 容错计算机设计05 n模冗余系统 特殊的双模系统 复杂结构 非并行串行结构的两种计算方法
  • 局机关门户网站建设自查报告范文/免费创建属于自己的网站
  • 广告公司网站首页设计页面/百度竞价排名正确解释
  • 政府门户网站建设情况工作汇报/2023疫情第三波爆发时间
  • 今日最新疫情/北京seo外包 靠谱
  • 建网站首页图片哪里找/网站的优化和推广方案
  • 专门查大学的网站/百度游戏排行榜风云榜