C语言文件操作

目录

一 为什么使用文件

二 什么是文件

2.1 程序文件

2.2 数据文件

2.3 文件名

三 文件的打开和关闭

3.1 文件指针

3.2 文件的打开和关闭

四 文件的顺序读写

fputc

fgetc

fputs 

fgets 

 实现一个代码,将date.txt拷贝一份生成date2.txt

fprintf

 fscanf

 fwrite

fread

4.1 对比一组函数

五 文件的随机读写

5.1 fseek

5.2 ftell

5.3 rewind

六 文本文件和二进制文件

七 文件读取结束的判断

7.1 被错误使用的feof

八 文件缓冲区


励志模块

好事总是会发生在下一个转弯


文章重点

(1)为什么使用文件(2)是么是文件(3)文件的打开和关闭(4)文件的顺序读写(5)文件的随机读写(6)文本文件和二进制文件(7)文件读取结束的判定(8)文件缓冲区


一 为什么使用文件

      我们想要把内容记录下来,选择删除数据的时候,数据才会不存在,这是关于数据持久化的问题,一般数据持久化的方法有:把数据存放在磁盘文件、存放在数据库等。

使用文件我们可以把文件直接存放到电脑的硬盘上,做到数据的持久化。

二 什么是文件

磁盘上的文件是文件;程序设计中,从文件的功能来分,文件分为数据文件和程序文件。

2.1 程序文件

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

2.2 数据文件

  文件的内容不一定是程序,而是程序运行时读写的数据。比如:程序运行时从中读取的文件或者输出内容的文件。(这篇文章讨论的是数据文件)

(在以前写的文章中,数据的输入输出都是以终端为对象的,就是从终端的键盘输入数据,运行结果显示到显示器上。 但是我们有时候,我们会把信息输出到磁盘上,有需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件。)

2.3 文件名

文件要有一个唯一的文件标识。文件名包括三部分:文件路径+文件名主干+文件后缀。(例如:C:\code\test.txt。C:\code\就是文件路径,test就是文件名主干,.txt就是文件后缀)(为了方便起见,文件标识称为文件名)

三 文件的打开和关闭

使用文件需要打开文件,不使用要关闭文件。

3.1 文件指针

缓冲文件系统中,关键的概念是 文件类型指针 ,简称 文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统 声明的,取名 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 FIRE;
不同的 C 编译器的 FILE 类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个 FILE 结构的变量,并填充其中的信息,使用者不必关心细节。 一般都是通过一个 FILE 的指针来维护这个 FILE 结构的变量,这样使用起来更加方便。

下面我们可以创建一个 FILE* 的指针变量 :
FILE * pf ; // 文件指针变量
定义 pf 是一个指向 FILE 类型数据的指针变量。可以使 pf 指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说, 通过文件指针变量能够找到与它关联 的文件。(返回的是FIRE*的指针变量,指向文件信息区的起始地址

3.2 文件的打开和关闭

文件在读写之前应该先 打开文件 ,在使用结束之后应该 关闭文件
在编写程序的时候,在打开文件的同时,都会返回一个 FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。(返回的是FIRE*的指针变量,指向文件信息区的起始地址
// 打开文件
FILE * fopen ( const char * filename , const char * mode );
// 关闭文件
int fclose ( FILE * stream);
#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");//默认的路径是test.c的路径,
	//""注意是双引号
	return 0;
}

下图,test.txt和test.c在同一个路径下 

 

 

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
	//打开文件,读文件
	FILE* pf = fopen("test.txt", "r");//默认的路径是test.c的路径,
	//""注意是双引号
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));//如果没有这个文件的话,就会报错,读文件
		return 0;
	}
	//关闭文件
	fclose(pf);
	pf = NULL;//这个指针变量不再用的时候,置为空指针
	return 0;
}

 

"r"(只读),为了输入数据,打开一个已经存在的文本文件,文件不存在的话,就会报错(返回空指针)。

"w"(只写),为了输出文件,打开一个文本文件,文件不存在的话,建立一个新的文件。(如果没有写文件路径,那么默认在操作文件的路径下,如果文件存在的话,打开的时候,会把里面的内容全部删除)

内存中的数据输出到屏幕或者文件中是输出,反之就是输入

四 文件的顺序读写

fgetc,fputc,注意对象是字符。

fputc

一个一个的写:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
	FILE* pf = fopen("date.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	fputc('a', pf);
	fputc('b', pf);
	fputc('c', pf);

	fclose(pf);
	pf = NULL;
	return 0;
}

 

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
	FILE* pf = fopen("date.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	//fputc('a', pf);
	//fputc('b', pf);
	//fputc('c', pf);

	char ch = 0;
	for (ch = 'a'; ch <= 'z'; ch++)
	{
		fputc(ch, pf);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

 内容在文件中显示出来就得打开文件关闭文件,为什么屏幕就不用呢,因为在C语言中,程序运行起来,就会打开三个流:stdio—标准输入流,stdout—标准输出流(在屏幕中显示),stderr—标准错误流(这三个都是FILE*类型的)

所以,上个代码,把pf改成stdout,就可以把内容输出到屏幕上

fgetc

一个一个的读:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
	FILE* pf = fopen("date.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
	}

	//读文件(一个一个的)
	int ch = 0;//fgetc返回的是ASCII码值,用char也可以,但是,当文件结束的时候,返回的是EOF(-1),所以用int
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c ", ch);
	}// 在这里,pf会自动向后移一位
	fclose(pf);
	pf = NULL;
	return 0;
}

fputs 

 一行一行的写:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
	FILE* pf = fopen("date.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
	}
//一行一行的写
	fputs("nihao\n", pf);
	fputs("youyoumen\n", pf);//自己加换行符号,再加上,文本输出函数
	fclose(pf);
	pf = NULL;
	return 0;
}

fgets 

 读一行:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
	FILE* pf = fopen("date.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
	}
//一行一行的读
	char buf[1000] = { 0 };
	fgets(buf, 1000, pf);//读1000-1个,仅仅显示一行。如果这一行的字符不止999个,最次用fgets,就会接着获取后面的内容;返回空指针的时候,文件结束
	printf("%s", buf);
	fclose(pf);
	pf = NULL;
	return 0;
}

 实现一个代码,将date.txt拷贝一份生成date2.txt

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
	//打开
	FILE* pr = fopen("date.txt", "r");
	if (pr == NULL)
	{
		printf("open for reading:%s\n", strerror(errno));
		return 0;
	}
	FILE* pw = fopen("date2.txt", "w");
	if (pw == NULL)
	{
		printf("open for writting:%s\n", strerror(errno));
		fclose(pr);
		pr = NULL;
		return 0;
	}
	//拷贝文件
	//读一个拷贝一个
	int ch = 0;
	while ((ch = fgetc(pr)) != EOF)
	{
		fputc(ch, pw);
	}




	//关闭
	fclose(pw);
	pw = NULL;
	fclose(pr);
	pr = NULL;
	return 0;
}

fprintf

(格式化输出函数)写格式化的数据:写数据到文件里

#include <stdio.h>
#include <string.h>
#include <errno.h>

struct Stu
{
	char name[20];
	int age;
	double d;
};

int main()
{
	struct Stu s = { "张三", 20, 95.5 };
	FILE* pf = fopen("date.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//写数据
	fprintf(pf, "%s %d %lf", s.name, s.age, s.d);
	fprintf(stdout, "%s %d %lf", s.name, s.age, s.d);
	printf("%s %d %lf", s.name, s.age, s.d);

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

 fscanf

(格式化输入函数)从文件里读数据

 

#include <stdio.h>
#include <string.h>
#include <errno.h>

struct Stu
{
	char name[20];
	int age;
	double d;
};

int main()
{
	struct Stu s = { 0 };
	FILE* pf = fopen("date.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//读数据
	fscanf(pf, "%s %d %lf", s.name, &(s.age), &(s.d));
	printf("%s %d %lf", s.name, s.age, s.d);

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

 

 fwrite

#include <stdio.h>
#include <string.h>
#include <errno.h>

struct Stu
{
	char name[20];
	int age;
	double d;
};

int main()
{
	struct Stu s[2] = { {"张三", 20, 95.5},{"李四", 19, 95 } };
	FILE* pf = fopen("date.txt", "wb");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//按照二进制的方式写文件,放进二进制信息
	fwrite(s, sizeof(struct Stu), 2, pf);//

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

fread

#include <stdio.h>
#include <string.h>
#include <errno.h>

struct Stu
{
	char name[20];
	int age;
	double d;
};

int main()
{
	struct Stu s[2] = { 0 };
	FILE* pf = fopen("date.txt", "rb");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//按照二进制的方式读文件
	fread(s, sizeof(struct Stu), 2, pf);//


	printf("%s %d %lf", s[0].name, s[0].age, s[0].d);
	printf("%s %d %lf", s[1].name, s[1].age, s[1].d);

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

 知识点:fread的返回值是读取文件内容的块数目,不是文件总大小

4.1 对比一组函数

#include <stdio.h>
#include <string.h>
#include <errno.h>

struct Stu
{
	char name[20];
	int age;
	double d;
};

int main()
{
	struct Stu s = { "张三", 20, 95.5 };
	struct Stu tmp = { 0 };
	char buf[100] = { 0 };
	sprintf(buf, "%s %d %lf\n", s.name, s.age, s.d);//把后面的内容,按照格式放到字符串buf中

	printf("%s\n", buf);
	sscanf(buf, "%s %d %lf", tmp.name, &(tmp.age), &(tmp.d));//把字符串buf里的内容放到后面
	printf("%s %d %lf", tmp.name, tmp.age, tmp.d);
	//关闭
	return 0;
}

scanf、fscanf、sscanf

printf、fprintf、sprintf

scanf 从标准输入流(stdin)上进行格式化输入的函数

printf向标准输出流(stdout)上进行格式化的输出函数

fscanf 可以从标准输入流(stdin)/指定的文件上读取格式化的数据

fprintf把数据按照格式化的方式输出到标准输出流(stdout)/指定的文件

sscanf 可以从一个字符串提取(转化)出格式化数据

sprintf 把一个格式化的数据转化成字符串

五 文件的随机读写

5.1 fseek

根据文件指针的位置和偏移量来定位文件指针
int fseek ( FILE * stream , long int offset , int origin );

EILE* stream表示pf

long int offset表示偏移量

int origin有三种选择:SEEK_SET(这个表示起始位置)、SEEK_CUR(这个表示除了起始和末尾的位置)、SEEK_END(这个表示末位的位置)

读:

#include <stdio.h>
#include <string.h>
#include <errno.h>


int main()
{
	FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}

	//读文件
	int ch = fgetc(pf);
	printf("%c\n", ch);//a
	ch = fgetc(pf);
	printf("%c\n", ch);//b 如果不加干预的话,下次获取出来的就是c,
	//定位指针文件
	//用fseek可以直接让pf定位到f
	//fseek(pf, 3, SEEK_CUR);//方法一:直接让pf定位到f
	//fseek(pf, 5, SEEK_SET);//方法二:也可以直接让pf定位到f,这里的SEEK_SET表示起始位置
	fseek(pf, -1, SEEK_END);//方法三:也可以直接让pf定位到f,这里的SEEK_END表示末尾位置
	ch = fgetc(pf);
	printf("%c\n", ch);//f

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

打印结果;abf

写:

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
	FILE* pf = fopen("text.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//写文件
	int ch = 0;
	for (ch = 'a'; ch < 'z'; ch++)
	{
		fputc(ch, pf);
	}
	fseek(pf, -1, SEEK_END);//直接让pf定位到z,这里的SEEK_END表示末尾位置
	fputc('#', pf);
	

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

打印:

5.2 ftell

返回文件指针相对于起始位置的偏移量
long int ftell ( FILE * stream ); 返回的是偏移量的值,即偏移量是多少(偏移量从0开始)

5.3 rewind

让文件指针的位置回到文件的起始位置。
void rewind ( FILE * stream ); (文件指针回到起始位置)

六 文本文件和二进制文件

  数据文件被称为 文本文件 或者二进制文件。 数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是 二进制文件(我们看不懂的文件) 如果要求在外存上以 ASCII 的形式存储,则需要在存储前转换。以 ASCII 字符的形式存储的文件就是 文本文件
字符一律以 ASCII 形式存储,数值型数据既可以用 ASCII 形式存储,也可以使用二进制形式存储。

七 文件读取结束的判断

7.1 被错误使用的feof

在文件读取过程中,不能用 feof 函数的返回值直接用来判断文件的是否结束。
而是 应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束
1. 文本文件读取是否结束,判断返回值是否为 EOF fgetc ),或者 NULL fgets
例如:fgetc 判断是否为 EOF .   fgets 判断返回值是否为 NULL .
2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:fread 判断返回值是否小于实际要读的个数。

(fgetc(字符)fgets(文本)fread(二进制))

八 文件缓冲区

      ANSIC 标准采用 缓冲文件系统 处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序 中每一个正在使用的文件开辟一块 文件缓冲区 。从内存向磁盘输出数据会先送到 内存中 缓冲区 满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓 冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根 C 编译系统决定的。
  因为有缓冲区的存在, C 语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文 件。 如果不做,可能导致读写文件的问题。

关闭文件,会刷新缓冲区

  • 44
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 38
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 38
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是小刘同学啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值