前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >C语言-文件操作这一篇足够

C语言-文件操作这一篇足够

作者头像
HABuo
发布2024-11-19 18:30:58
发布2024-11-19 18:30:58
9300
代码可运行
举报
运行总次数:0
代码可运行

1.文件是什么

上篇博客中我们写了通讯录的实现,但会有一个问题困扰着我们,就是说当这个程序结束之后,信息便也就丢失了,没有进行保存,这也不是我们想要的一个结果,我们希望在程序执行结束之后,下次再执行时,用户信息仍然在里面,这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据 库等方式。今天呢,我们就通过这篇博客来介绍文件操作的内容,把数据存储到磁盘文件当中去。

2.文件类型

在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。

2.1 程序文件

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

2.2 数据文件

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

本篇博客讨论的就是数据文件。 在以前我们所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的其实就是磁盘上文件。

2.3 文件名

文件名包含3部分:文件路径+文件名主干+文件后缀,平常我们为了方便起见,文件标识常被称为文件名。

3.文件的打开与关闭

3.1 文件指针

怎么把文件与我们所写的程序建立一定的联系呢?

我们首先要知道每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE。一般都是通过一个FILE 的指针来维护这个 FILE 结构的变量,从而对文件进行操作。如下图所示:

3.2 文件的打开与关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。文件的打开与关闭都是成对出现的。

注意:一个程序打开文件资源是有限的,如果打开的文件不关闭最终会导致文件打不开

3.2.1 fopen和fclose

FILE *fopen( const char *filename, const char *mode );

这个函数有两个参数:第一个是所打开的文件名称,可以是文件地址例如:D:\C(hab)\Project1\test1\test1\test2.dat,写成这种形式需要注意\别与转义字符造成混淆。第二个参数是打开的方式,例如下面两个图,可以以"r"(只读),"w"(只写)...等方式进行打开文件。

它的返回值有两种:

1)成功返回指向这个文件的指针

2)失败返回NULL

注意:fopen这个函数以'w'、'w+'形式打开的时候,会格式化就是把文件的数据清空

int fclose( FILE *stream );

这个函数结构比较简单,参数就是文件指针,返回值是int类型,返回结果如下:

1)成功关闭文件就是返回0

2)失败的话返回EOF

直接上代码:

代码语言:javascript
代码运行次数:0
复制
int main()
{
	//以写的方式打开文件
	FILE* pf = fopen("test2.dat", "w");
    //fopen("D:\\C(hab)\\Project1\\test1\\test1\\test2.dat", "w");该地址填写时需注意\,
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

4.文件的顺序读写

4.1 fgetc和fputc

int fgetc( FILE *stream );

这个函数结构也很简单,就是从文件指针所指向的文件里读取一个字符,注意:这是顺序读取。

它的返回值:

1)读取成功了返回的就是这个字符啊ascll码值

2)读取失败了就是返回EOF。

int fputc( int c, FILE *stream );

这个函数是往文件里输入一个字符,参数有两个,第一个是我们所输入的字符,第二个是我们所输入字符的文件。

返回值有两个:

1)成功就是字符被输入,这个字符的ascll码值被返回

2)失败了就是返回EOF。

这里需要知道一个知识点,的概念 流可以降低程序员学习成本,不需要对各种各样的硬件进行学习。 C语言程序,只要运行起来,就默认打开了3个流 stdin - 标准输入流 - 键盘 stdout - 标准输出流 - 屏幕 stderr - 标准错误流- 屏幕

代码语言:javascript
代码运行次数:0
复制
int main()
{
	//以写的方式打开文件
	FILE* pf = fopen("test2.dat", "w");
	//fopen("D:\\C(hab)\\Project1\\test1\\test1\\test2.dat", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputc("a", pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

int main()
{
	//读的方式打开文件
	FILE* pf = fopen("test2.dat", "r");
	//fopen("D:\\C(hab)\\Project1\\test1\\test1\\test2.dat", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
    int ret = fgetc(pf)
	printf("%c", ret);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

4.2 fgets和fputs

char *fgets( char *string, int n, FILE *stream );

这个函数就是用来读取一个字符串的,参数有三个,第一个就是我们用来存放从文件读取过来的字符串的地址,第二个参数是我们所读取字符的个数,第三个参数是文件指针用来找到我们想从哪个文件去读取数据。

返回值,成功的话返回所读取字符串的起始地址,错误会返回NULL指针。

注意:读取字符串的时候最后位置会放置'\0'

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

这个函数是往一个文件放置字符串的,参数有两个,第一个是我们所放置的字符串,第二个是我们需要放置字符串的文件指针。

返回值:如果成功的话会返回一个非负的值,失败的话会返回EOF。

代码语言:javascript
代码运行次数:0
复制
int main()
{
	//写的方式打开文件
	FILE* pf = fopen("test2.dat", "w");
	//fopen("D:\\C(hab)\\Project1\\test1\\test1\\test2.dat", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputs("abcdef", pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}


int main()
{
	char arr[20] = { 0 };
	//读的方式打开文件
	FILE* pf = fopen("test2.dat", "r");
	//fopen("D:\\C(hab)\\Project1\\test1\\test1\\test2.dat", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	fgets(arr, 5, pf);
	printf("%s\n", arr);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

4.3 fscanf和fprintf

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

int scanf( const char *format [,argument]... );

这个函数与scanf非常相像,其实就是在scanf的基础之上,加入一个文件指针的参数,具体用法看下例:

代码语言:javascript
代码运行次数:0
复制
struct S
{
	char arr[10];
	int a;
	float b;
};
int main()
{
	struct S s = {0};
	//打开文件
	FILE* pf = fopen("test2.dat", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fscanf(pf, "%s %d %f", s.arr, &s.a, &s.b);
	printf( "%s %d %f", s.arr, s.a, s.b);//	fprintf(stdout, "%s %d %f", s.arr, s.a, s.b)

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

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

int printf( const char *format [, argument]... );

这个函数与printf也非常相像,其实也是在printf的基础之上,加入一个文件指针的参数,具体用法看下例:

代码语言:javascript
代码运行次数:0
复制
struct S
{
	char arr[10];
	int a;
	float b;
};
int main()
{
	struct S s = {"abcde", 10, 5.5f};
	//打开文件
	FILE* pf = fopen("test2.dat", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fprintf(pf, "%s %d %f", s.arr, s.a, s.b);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

注意:我们可以用fprintf去实现printf的功能,如下所示: fprintf(stdout, "%s %d %f", s.arr, s.a, s.b)

4.4 fread和fwrite

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

这个函数就是往文件里以二进制的形式去写数据,最终所展现的就是我们看不懂的形式,它有四个参数:

1)第一个是我们所要写的数据的地址

2)第二个参数是数据元素的大小

3)第三个是数据的个数

4)第四个就是我们的文件指针

具体用法见下例:

代码语言:javascript
代码运行次数:0
复制
struct S
{
	char arr[10];
	int a;
	float b;
};
int main()
{
	struct S s = {"abcde", 10, 5.5f};
	//打开文件
	FILE* pf = fopen("test2.dat", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fwrite(&s, sizeof(struct S), 1, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

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

既然有以二进制的形式展现的文件,看不懂,必然程序里有可以读懂的函数,那么fread应运而生,它四个参数,只不过它是从文件指针所指向的文件里读count个size大小的数据。

1)第一个参数就是我们从文件读取数据后所要存放在哪里的地址

2)第二个参数就是数据元素的大小

3)第三个参数就是要读取几个数据

4)第四个就是我们的文件指针

用法见下例:

代码语言:javascript
代码运行次数:0
复制
struct S
{
	char arr[10];
	int a;
	float b;
};
int main()
{
	struct S s = {0};
	//打开文件
	FILE* pf = fopen("test2.dat", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fread(&s, sizeof(struct S), 1, pf);
	printf("%s %d %f", s.arr, s.a, s.b);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里额外介绍两个函数:sprintf与sscanf

int sprintf( char *buffer, const char *format [, argument] ... );

这个函数是把一个格式化的数据转化成一个字符串,它的参数与printf的唯一区别就在于buffer这个参数,它就是格式化数据转化之后所存储的空间的地址。如下例所示:

代码语言:javascript
代码运行次数:0
复制
struct S
{
	char arr[10];
	int a;
	float b;
};
int main()
{
	struct S s = { "abcde", 10, 5.5f };
	char b[20] = { 0 };
	sprintf(b, "%s %d %f", s.arr, s.a, s.b);
	printf("%s\n", b);
	return 0;
}

int sscanf( const char *buffer, const char *format [, argument ] ... );

这个函数是把一个字符串转化成一个格式化的输出,它的参数与scanf的唯一区别就在于buffer这个参数,它就是字符串转化格式化数据中的字符串空间的地址。如下例所示:

代码语言:javascript
代码运行次数:0
复制
struct S
{
	char arr[10];
	int a;
	float b;
};
int main()
{
	struct S s = { "abcde", 10, 5.5f };
	char b[20] = { 0 };
	struct S temp = { 0 };
	sprintf(b, "%s %d %f", s.arr, s.a, s.b);
	printf("%s\n", b);
	sscanf(b, "%s %d %f", temp.arr, &temp.a, &temp.b); 
	printf( "%s %d %f\n", temp.arr, temp.a, temp.b);
	return 0;
}

scanf、fscanf、sscanf和printf、fprintf、sprintf scanf:针对标准输入的格式化的输入语句 - stdin fscanf:针对所有输入流的格式化的输入语句 - stdin/文件 sscanf:从一个字符串中读取一个格式化的数据 printf:针对标准输出的格式化输出语句 - stdout fprintf:针对所有输出流的格式化输出语句 - stdout/文件 sprintf :把一个格式化的数据转换成字符串

5.文件的随机读写

5.1 fseek

int fseek( FILE *stream, long offset, int origin );

这个函数就是fgetc的升级版,它可以随机访问,但是它不返回字符,仅仅是用作调整文件指针,它有3个参数,其中第二个参数就是可以自己去定义偏移量从而访问要访问的字符,关于它的参数如下:

1)第一个参数stream就是文件指针用来从目标文件中读取字符

2)第二个参数offset就是偏移量,是偏移起始位置。

3)第三个参数就是起始位置,它有三种,一、SEEK_CUR,这是指当前位置。二、SEEK_END,这是指结束位置,三、SEEK_SET,这是指起始位置

它的返回值:(1)成功返回0(2)失败返回非0

具体使用如下所示:

代码语言:javascript
代码运行次数:0
复制
int main()
{
	FILE* pf = fopen("test2.dat", "r");//文件设置的是abcde
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf, 1, SEEK_CUR);
	int ret = fgetc(pf);
	printf("%c\n", ret);//b
	fclose(pf);
	pf = NULL;
	return 0;
}

5.2 ftell

long ftell( FILE *stream );

返回文件指针相对于起始位置的偏移量,参数就是文件指针

代码语言:javascript
代码运行次数:0
复制
int main()
{
	FILE* pf = fopen("test2.dat", "r");//文件设置的是abcde
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf, 1, SEEK_CUR);
	int ret = fgetc(pf);
	printf("%c\n", ret);//b
	printf("%d\n", ftell(pf));//2
	fclose(pf);
	pf = NULL;
	return 0;
}

5.3 rewind

void rewind( FILE *stream );

让文件指针的位置回到文件的起始位置。使用也比较简单。

代码语言:javascript
代码运行次数:0
复制
int main()
{
	FILE* pf = fopen("test2.dat", "r");//文件设置的是abcde
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf, 1, SEEK_CUR);
	int ret = fgetc(pf);
	printf("%c\n", ret);//b
	printf("%d\n", ftell(pf));//2
	rewind(pf);
	printf("%d\n", ftell(pf));//0
	fclose(pf);
	pf = NULL;
	return 0;
}

6. 文本文件与二进制文件

文本文件:就是内存中的数据经过转化转变成ascll码值的形式在文件当中进行呈现的文件

二进制文件:就是内存中的数据不经过任何的转化直接在文件当中进行呈现的文件

数据在内存中的存储:字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

7.文件读取结束的判定

7.1 如何判定文件结束

我们可以使用fgetc、fgets、fread等函数的返回值进行判定

fgertc:函数在读取失败结束的时候,会返回EOF,正常读取的时候,返回的是读取到的字符的ASCII码值 fgets:函数在读取失败结束的时候,会返回NULL,正常读取的时候,返回存放字符串的空间起始地址 fread:数在读取的时候,返回的是实际读取到的完整元素的个数,如果发现读取到的完整的元素的个数小于指定的元素个数,这就是最后一次读取了

7.2 feof

int feof( FILE *stream );

它主要应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。

它的返回值:

1)如果读取到最后正常结束就是返回一个非0值

2)没有读到最后就是返回的是0,此时文件读取失败了

注意:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。

代码语言:javascript
代码运行次数:0
复制
int main()
{
	FILE* pf = fopen("test2.dat", "r");
	if (pf == NULL)
	{
		return;
	}
	FILE* pd = fopen("test1.dat", "w");
	if (pd == NULL)
	{
		fclose(pf);
		pf = NULL;
	}
	char ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		fputc(ch, pd);
	}
	if (feof(pf))
	{
		printf("文件正常读取结束\n");
	}
	else if (ferror(pf))
	{
		printf("文件读取失败结束\n");
	}
	fclose(pf);
	pf = NULL;
	fclose(pd);
	pd = NULL;
	return 0;
}

8. 文件缓冲区

什么叫做文件缓冲区?

所谓缓冲文件系统是指系统自动地在内存中为程序 中每一个正在使用的文件开辟一块“文件缓冲区”

其实就是当我们的程序想要把数据存储到文件当中会先放到文件缓冲区当中,只有文件缓冲区存储满之后才会从文件缓冲区中向文件存储数据,读取时也是如此。如下图所示:

有什么用处呢?其实操作系统每时每刻都在执行语句,当我们要把数据存储到文件当中,就会打断操作系统,存一个打断一下存一个打断一下,效率极其低下,有了缓冲区当我们缓冲区存储满之后,操作系统一下全部存储到文件当中,那这样效率就会提高了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.文件是什么
  • 2.文件类型
    • 2.1 程序文件
    • 2.2 数据文件
    • 2.3 文件名
  • 3.文件的打开与关闭
    • 3.1 文件指针
    • 3.2 文件的打开与关闭
      • 3.2.1 fopen和fclose
  • 4.文件的顺序读写
    • 4.1 fgetc和fputc
    • 4.2 fgets和fputs
    • 4.3 fscanf和fprintf
    • 4.4 fread和fwrite
  • 5.文件的随机读写
    • 5.1 fseek
    • 5.2 ftell
    • 5.3 rewind
  • 6. 文本文件与二进制文件
  • 7.文件读取结束的判定
    • 7.1 如何判定文件结束
    • 7.2 feof
  • 8. 文件缓冲区
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档