【C语言】第十三章-文件操作

第十三章-文件操作

  之前我们所讨论的动态内存管理所操作的是计算机中的内存,而本章节的内容所讨论的核心是计算机的外村,也就是磁盘。我们往往想要将一个程序开辟的内存中数据存储到外存中,以供下次程序启动时使用,而本章节就会讨论相关语法。

打开文件

  我们所有的文件相关操作都是使用一个文件的指针进行操作的,而这个文件的指针我们还有一个名字就是句柄,句柄就像是遥控器,我们往往无法直接对文件进行操作,但是使用遥控器就可以进行远程遥控,控制文件完成相关操作。而在文件最开始打开时我们就会获取到这么一个句柄

  在此我们会借助一个fopen()函数,函数原型为FILE * fopen ( const char * filename, const char * mode );接下来我们具体使用一下。

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
FILE* fp = fopen("./test.txt", "r");
if(fp == NULL)//如果文件 打开失败
{
perror("fopen");//输出错误提示
}
}

  以上这行代码我们我们调用了fopen函数,函数第一个参数时文件路径,第二个参数为打开模式。我们用r模式想要打开test.txt文件,但是如果我们当前目录下没有这个文件就会打开失败,于是会返回错误信息,产生以下结果。

1
2
[misaki@localhost test]$ ./Main 
fopen: No such file or directory

  而如果存在这个文件也有可能会打开失败,呢就是当我们没有文件的读权限的时候我们也会打开失败。

  关于打开模式有很多种,有的打开模式只支持读取不支持写入,有的打开模式再没有目标文件的时候会自行创建或是删除原有文件中的内容。具体可以 参考以下这张表。

打开模式

关闭文件

  在我们打开文件使用完毕后不可缺少的就是要关闭文件,那么我们如果不关闭文件会产生怎样的后果呢?

  在操作系统中我们是通过句柄来操作文件,但其实我们打开文件所产生的句柄所指向的是一个文件数据结构,这个数据结构包含一块缓冲区和一个文件描述符,这些都存储在内存上,而我们打开文件就会消耗内存进行管理,因此如果我们打开文件却忘了关闭文件就会造成和我们给指针划分内存却忘了释放一样的结果——内存泄漏文件描述符泄漏

1
fclose(fp);//文件不使用的时候,就需要及时关闭掉

读取文件

  我们在用打开文件后可以进行的首要操作就是文件的读取。这里要用到最多的一个函数就是fread(),函数原型为size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );,参数中ptr是读取到的缓冲区指针,stream是文件指针。这里要注意的是里面的sizecount参数。size参数是以多少个字节为一块,count参数表示一共读取多少块,也就是说size * count就是目标读取文件的字节数。然而文件所返回的数值表示实际读取了的块的数目。一般情况下我们会将size参数给定为1,以一个字节为一块,这样方便我们计算预计读取和实际读取的字节数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>                                                              
#include <errno.h>
int main()
{
//1、打开文件
//fp => 句柄 句柄就是遥控器
FILE* fp = fopen("./test.txt", "r");//第一个参数文件路径,第二个参数打开法方式
//w打开方式打开文件会将原来文件中的内容全部清空
//每个程序一打开就会默认打开三个文件:标准输入,标准输出,标准错误
if(fp == NULL)
{
perror("fopen");
//printf("%s\n", strerror(errno));//打印错误码
//进程的退出码,echo $?的 方式能获取上个进程的退出码
return 1;
//结果出错,返回非0
//进程退出码
}
//2、读文件
//buffer => 缓冲区:提高读取效率,一般情况下是一块内存
char buf[1024] = { 0 };
size_t n = fread(buf, 1, 100, fp);//1 * 100 = 预计读取的字节数
//1为一个元素几个字节,读取100个元素,返回成功读取的元素个数
printf("%s\n", buf);
printf("%ld\n", n);
fclose(fp);//文件不使用的时候,就需要及时关闭掉
//文件不及时关闭的话,句柄泄露/资源泄露/文件描述符泄漏
//可能导致后续的文件就无法打开了(一个进程可以打开的文件是有上限的)
return 0;
}


[misaki@localhost File]$ ./a.out
Misaki

7

  以上这段代码通过fread()函数将文件中的数据全部读入到了事先准备好的缓冲区中,然后我们又将缓冲区中的数据全部打印了出来。注意我们这里打印fread()函数读取的块数时返回了7,这是因为在文件中编辑默认末尾会加上换行符。

写入文件

  写文件是我们另外一个经常使用的功能,有读必有写。而写文件最为常用的函数是fwrite(),原型为size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );,这里的参数与fopen十分类似就不一一介绍了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>                                                              
#include <errno.h>
int main()
{
//1、打开文件
//fp => 句柄 句柄就是遥控器
FILE* fp = fopen("./test.txt", "w");//第一个参数文件路径,第二个参数打开法方式
//w打开方式打开文件会将原来文件中的内容全部清空
//每个程序一打开就会默认打开三个文件:标准输入,标准输出,标准错误
if(fp == NULL)
{
perror("fopen");
//printf("%s\n", strerror(errno));//打印错误码
//进程的退出码,echo $?的 方式能获取上个进程的退出码
return 1;
//结果出错,返回非0
//进程退出码
}
//3、写文件
char buf[1024] = "Misaki1";
size_t n2 = fwrite(buf, 1, 7, fp);
//参数与fread相同,同样返回成功写入的元素个数
printf("n2 = %lu\n", n2);
if(n2 == 0) //如果写入失败打印错误信息
{
perror("fwrite");
}
fclose(fp);//文件不使用的时候,就需要及时关闭掉
//文件不及时关闭的话,句柄泄露/资源泄露/文件描述符泄漏
//可能导致后续的文件就无法打开了(一个进程可以打开的文件是有上限的)
return 0;
}
[misaki@localhost File]$ ./a.out
n2 = 7
[misaki@localhost File]$ cat test.txt
Misaki1[misaki@localhost File]$

  可以看到缓冲区中的内容已经成功写入文件了。不过要注意的是如果我们想要向文件中写入,就必须要使用具有可写功能的打开模式,而在这里所选择的w模式会自动将文件清空。

文件随机读写

  当我们打开文件时文件指针便会指向文件,并且在文件指针指向的地方进行读写,然而我们可以通过以下几个函数控制文件指针。

1
2
3
int fseek ( FILE * stream, long int offset, int origin );//使用不同的模式使文件指针相对于原来位置进行移动,成功返回0,否则返回其他
void rewind ( FILE * stream );//让文件指针位置回到文件起始位置
long int ftell ( FILE * stream );//返回 文件相对于起始位置的偏移量

  例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>                                                              
#include <errno.h>
int main()
{
//1、打开文件
//fp => 句柄 句柄就是遥控器
FILE* fp = fopen("./test.txt", "w");//第一个参数文件路径,第二个参数打开法方式
//w打开方式打开文件会将原来文件中的内容全部清空
//每个程序一打开就会默认打开三个文件:标准输入,标准输出,标准错误
if(fp == NULL)
{
perror("fopen");
//printf("%s\n", strerror(errno));//打印错误码
//进程的退出码,echo $?的 方式能获取上个进程的退出码
return 1;
//结果出错,返回非0
//进程退出码
}
//3、写文件
char buf[1024] = "Misaki1";
size_t n2 = fwrite(buf, 1, 7, fp);
//参数与fread相同,同样返回成功写入的元素个数
printf("n2 = %lu\n", n2);
if(n2 == 0) //如果写入失败打印错误信息
{
perror("fwrite");
}
//7、文件的随机读写
size_t p = ftell(fp);
printf("%lu\n", p);
fseek(fp, 3, SEEK_SET);
p = ftell(fp);
printf("%lu\n", p);
//4、关闭文件
fclose(fp);//文件不使用的时候,就需要及时关闭掉
//文件不及时关闭的话,句柄泄露/资源泄露/文件描述符泄漏
//可能导致后续的文件就无法打开了(一个进程可以打开的文件是有上限的)
return 0;
}


[misaki@localhost File]$ ./a.out
n2 = 7
7
3

  以上这个例子就是用了文件指针的相关控制函数改变了文件指针的位置达到可以随机读写的目的。

其他函数

  在文件的读写上除了fread()fwrite()函数外还有一些函数有着强大的读写功能。

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

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

  第一组函数与putsgets的使用方法类似,不过这里是向文件中读取或写入字符串。

  2、int fgetc ( FILE * stream );

  int fputc ( int character, FILE * stream );

  第二组函数与getcputc类似,不过这里是向文件中读取或写入字符。

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

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

  这一组函数更为强大,是格式化文件输入输出函数,使用与printfscanf类似。

-------------本文结束感谢您的阅读!-------------
记录学习每一分,感谢您的赞助