【文件】C语言文件操作及其使用总结篇【初学者保姆级别福利】

一篇博客学好动态内存的管理和使用
这篇博客干货满满,建议收藏再看哦!!
求个赞求个赞求个赞求个赞 谢谢

先赞后看好习惯 打字不容易,这都是很用心做的,希望得到支持你 大家的点赞和支持对于我来说是一种非常重要的动力 看完之后别忘记关注我哦!️️️
本篇基本上涵盖了初学阶段动态内存的所有知识点,喜欢的伙伴一定要看完哦!

强烈建议本篇收藏后食用~ 干货满满!

看完这篇博客,相信伙伴们可以基本上掌握以下内容:
1.为什么使用文件
2.什么是文件
3.文件的打开和关闭
4.文件的顺序读写
5.文件的随机读写
6.文件读取结束的判定
7.文件缓冲区

前言

1.为什么要使用文件
在以前的C语言学习当中,我们了解到,我们在运行程序的时候所定义的数据,是保存在内存中的,有可能是栈区上,有可能是堆区上,也有可能是在其它地方上,但是,这些地方,都是属于内存上的。在内存上的数据,是计算机在程序运行的时候所开辟出来的,因此,在程序结束的时候,内存上的空间都会被返还给内存,因此我们是做不到数据的持久化保存的。
这时,我们就需要用到文件。使用文件,我们就可以将数据直接存放在电脑的硬盘上,做到了数据持久化。

什么是文件?
磁盘上的文件是文件。但是在程序设计中,我们一般所指的文件有两种:程序文件、数据文件
关于这部分知识相对于没有那么的重要,博主在这里就不展开细讲,想了解的小伙伴可以给我私信留言哦。

文件的打开和关闭

文件指针

首先,我们要知道,每一个被使用的文件都在内存中开辟了一个相应的文件信息去,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是要保存在一个结构体当中的。但是这个结构体是不用我们自己声明的,系统已经帮我们声明好了,已经被取好名了,为FILE

另外,每次我们打开文件的时候,系统都会自己生成一个FILE结构的变量,里面有关信息系统会自己录入,我们作为使用者暂时不必关心细节。

OK。接下来,我们想要找到这个系统给我们准备好的结构体并使用它,我们就要通过文件指针找到这个文件。

FILE*pf;//文件指针变量

文件的打开和关闭

有了pf这个指针,我们在操作文件之前,就要通过pf来打开文件,在使用结束后一定要记得关闭文件

想要打开和关闭文件,我们要用到两个函数:
fopen()和fclose()
我们先来看这两个函数的原型:

int main()
{
//打开文件
FILE* fopen(const char* filename, const char* mode);
//关闭文件
int fclose(FILE * stream);
return 0;
}

在fopen()函数中,filename指的是文件名,这个很好理解,fclose()函数中stream指的是,流。关于流的概念,我会在后面继续讲解,这里指的就是我们用FILE*类型定义的函数指针。
mode,指的是文件的打开方式,为char*类型。
文件的打开方式有如下几种

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

例子:

int main()
{
FILE*pf1=fopen("test.dat", "w");//这种是相对路径,是要放到程序那个指定文件夹里面才能被找到
FILE* pf2 = fopen("D:\\test.dat", "r");//有位置的叫绝对路径,是可以找到的
//注意\如果只有一个,是会被理解成转义字符的
//所以\都要变成两个
if (pf1 == NULL)
{
perror("fopen");
return 1;
}
//写文件 //关闭文件
fclose(pf1);
pf1 = NULL;
return 0;
}

在这里,我想给各位小伙伴讲一下使用使用文件的一些好习惯和细节:问下大家有注意到fopen()后面这一段代码吗
if (pf1 == NULL) { perror("fopen"); return 1; }
这一段代码的作用,就是防止文件打开失败,而fopen()在文件打开失败的时候,是会返回空指针的,如果我们不加以检验就对pf进行使用,注意:此时pf是个空指针,是会出问题的,所以,我们需要给pf验空。
另外,在我们fclose()关闭文件之后,必须要将pf置为空指针,防止后面对pf进行非法的解引用操作。

文件的顺序读写

基本函数的介绍

文件的顺序读写,我们必须知道一些必要的文件操作函数

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

例子

int main()
{
FILE* pf = fopen("test.dat", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fputc('b', pf);//往pf所指向的文件里面写入一个b
fputc('i', pf);
fputc('t', pf);
//按顺序写的
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

我们打开后台文件夹,就可以找到test.txt这个文件了,我们可以看到bit是按照顺序录入进了这个文件当中。

流的概念

流是一个高度抽象的概念
程序-屏幕/硬盘/优盘/光盘…
各种硬件读写方式也不同
为了让写程序的人不用那么麻烦
所以在程序和硬件之间加一个流的概念

C程序只要运行起来,就默认打开了三个流
stdin-标准输入流-键盘
stdout-标准输出流-屏幕
stderr-标准错误流-屏幕
以上三个流都是FILE*类型的

因此我们可以得出,文件是一种流,屏幕键盘也都是流,而流一般用stream表示。
例子:

//想用fputc给屏幕打印数据
int main()
{
fputc('b', stdout);
fputc('i', stdout);
fputc('t', stdout);
return 0;
//屏幕也是个流,文件也是个流
//我们写数据可以往文件里面写,当然也可以向屏幕上写
}

一些文件操作函数使用的例子

例子1:

int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
}
//读文件
int ret = fgetc(pf);
printf("%c", ret);
ret = fgetc(pf);
printf("%c", ret);
ret = fgetc(pf);
printf("%c", ret);
ret = fgetc(pf);
printf("%c", ret);
ret = fgetc(pf);
printf("%c", ret);
ret = fgetc(pf);
printf("%c", ret);
ret = fgetc(pf);
printf("%c", ret);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

分析:

这里的文件是用"r",也就是读取的方式打开的,我们通过fgetc()函数一次读取一个文件里面的字符,并存进ret后打印。因此,如果我们一开始要现在指定的文件夹里面先写入一些字符,我们F5运行起来之后,我们就可以看到这些打印的字符了。

> 运行后:

例子2:

//读键盘
//从标准输入流里面读取信息
int main()
{
int ret = fgetc(stdin);//从标准输入流读取信息
printf("%c", ret);
ret = fgetc(stdin);
printf("%c", ret);
ret = fgetc(stdin);
printf("%c", ret);
return 0;
}

分析:
标准输入流(键盘)里面输入信息之后,用printf()打印出来。

例子3:

int main()
{
FILE* pf = fopen("test.dat", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件-按照行来写
fputs("abc\n",pf);
fputs("qwertyuiop", pf);
//这种都叫以文本的形式写进去
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

经过上面的学习,这些代码都比较好理解,这里就不赘述了。
运行之后:


例子4:

int main()
{
FILE* pf = fopen("test.dat", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[10] = { 0 };
fgets(arr, 4, pf);
printf("%s", arr);
fgets(arr, 4, pf);
printf("%s", arr);
//这里的4,事实上其实只会读三个,因为要留位置给\0
//后面接着读是不会从第二行开始读的,要把内容读完才行
return 0;
}

注意: arr表示从文件录入的数据存入arr中,4表示录入4个字符,pf指向那个文件
但是,此时的录入的4个字符,只有3个是有效的,因为要留位置给'\0'
后面接着读是不会从第二行开始读的,要把内容读完才行

运行:

fprintf、fscanf、sprintf和sscanf函数的使用

fprintf()函数和fscanf()函数

fprintf()原型:int fprintf ( FILE * stream, const char * format, ... );
fscanf()原型:int fscanf ( FILE * stream, const char * format, ... );
通过对比我们发现,这两个函数分别比printf,scanf只多了一个参数FILE*stream,这个参数,也就是一个流。
那么我们通过两个例子,就可以很好的理解这两个函数。

例子1:

struct S
{
char arr[10];
int num;
float sc;
};
int main()
{
struct S s = { "abcdef",10,5.5f };
//现在想把它放到文件里面
//对格式化的数据进行写文件
FILE* pf = fopen("test.dat", "w");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件
fprintf(pf, "%s %d %f", s.arr, s.num, s.sc);
fclose(pf);
pf=NULL;

很容易理解,我们想要进行格式化输出的时候,只需要在fprintf()的第一个参数上传我们要操作的流,后面的参数和printf()的使用完全一致。同理,我们也可以很好的理解fscanf()这个函数。

例子2:
现在我们想要把刚才放进test.dat里面的数据还原到一个结构体中来。

struct S
{
char arr[10];
int num;
float sc;
};
//能否读出来,还原成一个结构体?
int main()
{
struct S s = { 0 };
//现在想把它放到文件里面
//对格式化的数据进行写文件
FILE* pf = fopen("test.dat", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//读文件
fscanf(pf,"%s %d %f", s.arr, &(s.num), &(s.sc));
//打印以下看看结果
printf("%s %d %f\n", s.arr, s.num, s.sc);
fclose(pf);
pf = NULL;
return 0;
}

思考:
通过以上两个例子,我们就可以很好的明白fprintf(),fscanf()这两个函数的使用。此时,我们要思考:我们前面说了,键盘,屏幕也都是流,那么,我们将fprint()fscanf()的第一个参数改成标准的输出输入流,那么它们的作用,就是相当于printf()scanf()啊。
事实确实是如此:
fprintf(stdout,...,...)printf(...,...)等价。
fscanf(stdin,...,...)scanf(...,...)等价。

sprintf()函数和sscanf()函数

其实这两个函数的运用并不属于文件这一部分的内容,但是我们讲到fprintf(),fscanf(),printf,scanf()这些函数,那我们也把这最后两个名字相似的函数讲了,给伙伴们区分一下。

scanf()针对标准输入的格式化的输入语句-stdin
fscanf()针对所有输入流的格式化的输入语句
sscanf()从一个字符串中读取一个格式化的数据

printf()针对标准输出的格式化输出语句-stdout
fprintf()针对所有输出流的格式化输出语句
sprintf()把格式化的数据,转成字符串

例子:

//sprintf/sscanf
//把格式化的数据写到一个字符串里面
struct S
{
char arr[10];
int age;
float f;
};
int main()
{
struct S s = { "hello",20,5.5f };
//能否转化为一个字符串呢
char buf[100] = { 0 };
sprintf(buf, "%s %d %f", s.arr, s.age, s.f);
printf("%s\n", buf);
//能否从buf字符串中还原出一个结构体
struct S tmp = { 0 };
sscanf(buf, "%s %d %f", tmp.arr, &(tmp.age), &(tmp.f));
printf("%s %d %f\n", tmp.arr, tmp.age, tmp.f);
//两次printf的结果应该是一样的
return 0;
}

文件的二进制读写

两个函数:fwrite()函数和fread()函数

fwrite():
原型: size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
第一个参数表示被写数据(可以是个数组)的地址,第二个参数表示每一个数据的大小(如果是数组,就是每个元素的大小),第三个参数表示数据个数(如果不是数组,就是1,如果是数组,就是数组元素个数,第四个参数表示流。
fread():
原型:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
参数含义和fwrite()相同。

顾名思义就是用二进制的形式读写,是我们看不懂的,我们通过两个例子就可以很好的理解这两个函数。

例子:

//二进制的读写
struct S
{
char arr[10];
int num;
float sc;
};
//fwrite
//size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
int main()
{
struct S s = { "abcde",10,5.5f };
//二进制的形式写
FILE* pf = fopen("test.dat", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fwrite(&s,sizeof(s),1,pf);
//后台打开文件,我们是看不懂的,因为是二进制形式搞进去的
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}


我们是看不懂后台输入文件的含义的,因为我们是以二进制的形式输入的,要想读懂,我们必须通过fread()函数,以二进制的形式读。

//我们要用fread才能看懂
//fread
struct S
{
char arr[10];
int num;
float sc;
};
//fread
//size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
int main()
{
struct S s = {0};
//二进制的形式读
FILE* pf = fopen("test.dat", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读
//参数和fwrite一样
fread(&s, sizeof(struct S), 1, pf);
//都出来之后我们打印出来看看
printf("%s %d %f\n", s.arr, s.num, s.sc);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}


通过fread(),我们就成功读出fwrite()刚刚写进去的内容了。

文件的随机读写

想要掌握文件的随机读写,我们必须掌握的其中一个函数就是fseek()函数

fseek():
这个函数的作用就是用来调整文件指针位置的。
比如说,后台的test.dat里面有一串字符串abcdefg,但是我读的时候不想从头开始读,那么我们就可以通过fseek()函数来调整文件指针的位置,让它从a的位置挪开。
原型:int fseek ( FILE * stream, long int offset, int origin );
第一个参数表示针对的流,第二个参数表示调整后的文件指针相对于原始位置的偏移量,第三个参数表示文件指针的原始位置。
在此其中,第三个参数中我们常用的三个量为:
SEEK_CUR;//current position of file pointer指针当前位置
SEEK_END;//end of file文件末尾
SEEK_SET;//beginning of file文件开头

ftell():
这个函数返回一个整型,告诉我们当前文件指针相对于起始位置的偏移量。
rewind():
这个函数可以让文件指针回到起始位置
那么上面这些函数怎么用呢,我们看一个例子就明白了

例子:

int main() {
FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
}
char ch = 0;
//先打开后台在里面写入abcdefg
//现在里面已经有abcdefg了
ch=fgetc(pf);
printf("%c\n", ch);//此处打印的是a
//现在a已经读完了,文件指针指向的是b,那么怎么跳过b,c直接读d呢?
fseek(pf, 2, SEEK_CUR);//用fseek()调整一下指针位置,跳过两个字符 ch = fgetc(pf);
printf("%c\n", ch);//此处打印的是d int ret = ftell(pf);
printf("%d\n", ret);//此处打印的是4 rewind(pf);
ret = ftell(pf);
printf("%d\n", ret);//此处打印的是0,因为pf已经被rewind了 fclose(pf);
return 0;
}

文件读取结束的判定

文件读取结束的判定
被错误使用的feof:在很多文章中和教学中经常说feof可以判断结束,其实是不准确的
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件是否结束
feof的作用是用于文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束,如果feof返回值为真,表示正常读取结束;如果为假表示文件读取失败而结束读取。

文本文件读取是否结束,是通过fgetc函数的返回值是否是EOF来判定的
fgets函数正常读取结束的时候会返回空指针,正常读取的时候,返回放字符串的空间起始地址
fread函数在读取的时候,返回的是实际读取到的完整元素的个数
如果发现读取到的完整的元素的个数小于指定的元素个数,这就是最后一次读取了。

例子:
写一个函数拷贝一个文件:

//写代码把test.txt文件拷贝一份,生成test2.txt文件
int main()
{
FILE* pfread = fopen("test.txt", "r");
if (pfread == NULL)
{
return 1;
}
FILE* pfwrite = fopen("test2.txt", "w");
if (pfwrite == NULL)
{
fclose(pfread);
pfread = NULL;
return 1;
}
//读写文件
int ch = 0;
while ((ch = fgetc(pfread) != EOF))
{
//写文件
fputc(ch, pfwrite);
}
//判断读取是因为什么结束的
if (feof(pfread))
{
printf("遇到文件结束标志,文件正常结束\n");
}
else if (ferror(pfread))
{
printf("文件读取失败结束\n");
}
//关闭文件
fclose(pfread);
pfread = NULL;
fclose(pfwrite);
pfwrite = NULL;
return 0;
}

文件缓冲区

文件缓冲区
程序数据区(内存)<>硬盘
在这其间存在输出缓冲区和输入缓冲区

例子:
这个例子不是本章重点,只是让伙伴们理解一下文件缓冲区的概念,小伙伴可以复制代码按照指示运行一下就可以理解了,这里不再赘述。

#include<windows.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
fputs("abcdef", pf);//先将代码放在输出缓冲区
printf("睡眠10s-已经在写数据了,打开test.txt文件,发现文件没有内容\n");
Sleep(10000);
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(硬盘)
//注:fflush在高版本vs上不能使用了
printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
Sleep(10000);
fclose(pf);
//注:fclose在关闭文件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}

尾声

能看到这里的小伙伴,如果你已经完全理解了这篇博客的内容,相信你对文件的理解已经提高了一个层次了。如果感觉这篇博客对你有帮助的话,别忘了你的赞,收藏和关注哦

【文件】C语言文件操作及其使用总结篇【初学者保姆级别福利】的更多相关文章

  1. c语言文件读写操作总结

    C语言文件读写操作总结 C语言文件操作 一.标准文件的读写 1.文件的打开 fopen() 文件的打开操作表示将给用户指定的文件在内存分配一个FILE结构区,并将该结构的指针返回给用户程序,以后用户程 ...

  2. go语言文件操作,这期资料比较详细( 欢迎加入go语言群: 218160862 )

    go语言文件操作,这期资料比较详细 欢迎加入go语言群: go语言深圳群 golang深圳 218160862 点击加入 文件操作 func Open(name string) (file *File ...

  3. C语言文件操作

    C语言文件操作,以下以基本的例子和说明来展开怎么通过C语言来进行文件操作. 操作文件,我们得需要知道什么?当然是路径和文件名. 首先我需要知道我操作的文件在哪里,叫什么名字.在C语言中还存在一个打开方 ...

  4. C语言文件操作相关函数

    在实际应用中,我们往往需要对文件进行操作,下面我将介绍C语言的一些关于操作文件的函数. 一.计算机文件 计算机文件是以计算机硬盘为载体存储在计算机上的信息集合,是存储在某种长期储存设备上的一段数据流. ...

  5. C 语言文件操作

    C 语言文件操作 1. 数据流:     程序与数据的交互以流的形式进行.fopen 即打开数据流,fclose 即刷新数据流.     所谓数据流,是一种抽象,表示这段数据像流一样,需要逐步接收,不 ...

  6. C语言文件操作函数

    C语言文件操作函数大全 clearerr(清除文件流的错误旗标) 相关函数 feof表头文件 #include<stdio.h> 定义函数 void clearerr(FILE * str ...

  7. C语言库函数--操作文件

    //C库函数读取文件的代码 I/O缓冲机制 C语言库函数写文件都是写在内存中,然后一次写入磁盘.提高了效率. 读写文件,不对系统进行操作,一般采用C语言库函数.移植可以在任何可以对C支持的操作系统,而 ...

  8. C语言文件操作解析(五)之EOF解析(转载)

      C语言文件操作解析(五)之EOF解析 在C语言中,有个符号大家都应该很熟悉,那就是EOF(End of File),即文件结束符.但是很多时候对这个理解并不是很清楚,导致在写代码的时候经常出错,特 ...

  9. 【转】C语言文件操作解析(三)

    原文网址:http://www.cnblogs.com/dolphin0520/archive/2011/10/07/2200454.html C语言文件操作解析(三) 在前面已经讨论了文件打开操作, ...

  10. 关于C语言文件操作

    关于C语言的文件操作之前我也写过一篇博客来介绍,但是当时写的很不全面,只是简单的使用了一下 ,今天再从新学习一下. 1.文件的写 首先还是先看一个简单的例子: include<stdio.h&g ...

随机推荐

  1. Educational Codeforces Round 93 (Rated for Div. 2)

    Educational Codeforces Round 93 (Rated for Div. 2) A. Bad Triangle input 3 7 4 6 11 11 15 18 20 4 10 ...

  2. 如何设置IDEA代码风格为Google风格,使用Google风格format

    1.在Github仓库寻找:google style 为了节省大家时间直接放链接了:Here 2.进到项目 找到名为intellij-java-google-style.xml 文件 Ctrl + F ...

  3. Python pydot与graphviz库在Anaconda环境的配置

      本文介绍在Anaconda环境中,安装Python语言pydot与graphviz两个模块的方法.   最近进行随机森林(RF)的树的可视化操作,需要用到pydot与graphviz模块:因此记录 ...

  4. Spring Boot 2.x :日志框架@Slf4j的使用和logback文件配置

    为什么是SLF4J? 默认情况下,Spring Boot会用SLF4J + Logback来记录日志,并用INFO级别输出到控制台. 怎么使用SLF4J? 如果我们在一个Spring Boot 的程序 ...

  5. 聚焦业务价值:分众传媒在 Serverless 上的探索和实践

    作者 | 吴松(分众传媒研发总监) **关注 Serverless 公众号后台回复 分众 即可获得云原生峰会 PPT! ** 本文总结于分众传媒研发总监吴松在阿里云云原生实战峰会上的分享,从三个方面详 ...

  6. 简单讲透Mac环境下多版本python的环境变量设置,仅对小白生效

    windows下设置多版本的python管理相对容器,一切都是可视化的,但linux和mac下的python多版本对于小白来说,可能就没那么容易理解了. python多版本安装的问题 假如,首次安装了 ...

  7. 使用QQ屏幕识图实现识别表格功能

    1.问题 目前市场上的OCR工具对于识别表格功能均是采取了收费制度,但我们时常要进行一些表格的复制(原表格为图片) 便可以使用QQ或钉钉自带的功能来实现 2.解决 1.QQ屏幕识图 先使用屏幕识图功能 ...

  8. 【FreeRTOS】任务调度

    启动调度器接口,主要是创建空闲任务和定时器任务以及执行特定架构的启动调度器接口 // FreeRTOS\Source\tasks.c void vTaskStartScheduler( void ) ...

  9. phpcms - 在删除文章后实现自动删除tag标签

    在使用phpcms程序制作网站的时候,我们会发现文章模型新建一篇文章后会自动向数据库中插入关键词,但如果删除文章后,数据库中的关键词表中字段中还存在之前文章的关键词,那么怎样才能在phpcms后台中删 ...

  10. RabbitMQ .net core 客户端 EasyNetQ 的使用

    依赖注入 var connectionConfiguration = new ConnectionConfiguration { Hosts = new List<HostConfigurati ...