文件操作详解

今天给大🔥带来文件操作的讲解,虽然这些内容好像实际上用的不多,但是技多不压身嘛,

多学点知识总是好的!

目录

1. 为什么使用文件?

2. 什么是文件?

2.1程序文件

2.2数据文件

2.3文件名

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

4. 文件的打开和关闭

4.1流和标准流

4.1.1流

4.1.2标准流

4.2文件指针

4.3文件的打开和关闭

5. 文件的顺序读写

5.1顺序读写函数介绍

5.2对比两组函数

5.2.1第一组

5.2.2第二组

6. 文件的随机读写

6.1fseek

6.2ftell

6.3rewind

7. 文件读取结束的判定

7.1文本文件读取结束的判定

7.2二进制文件读取结束的判定

7.3被错误使用的feof

8. 文件缓冲区


1. 为什么使用文件?

我们知道,电脑的存储分为内存和磁盘空间,内存是在通电(程序运行)的时候才能够存储东西,一旦断电(程序关闭)就会导致存储的数据丢失,我们的机器一般不会永久不停地运行,这时就需要用到文件了,文件可以永久地保存数据,并且将数据存储到磁盘空间内,这些数据即使断电了也不会消失,下次启动程序时数据会得到保存。

2. 什么是文件?

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

2.1程序文件

程序⽂件包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执⾏程序(windows环境后缀为.exe)。

2.2数据文件

⽂件的内容不⼀定是程序,⽽是程序运⾏时读写的数据,⽐如程序运⾏需要从中读取数据的⽂件,或者输出内容的⽂件。

本章讨论的是数据⽂件。

2.3文件名

⼀个⽂件要有⼀个唯⼀的⽂件标识,以便⽤⼾识别和引⽤。
⽂件名包含3部分:⽂件路径+⽂件名主⼲+⽂件后缀
例如: c:\code\test.txt
为了⽅便起⻅,⽂件标识常被称为⽂件名。

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

根据数据的组织形式,数据⽂件被称为⽂本⽂件或者⼆进制⽂件。

对于同样一份数据:

①以二进制的形式写入数据的文件被称为二进制文件。

②将二进制(根据ASCII码)转化成字符的形式再写入数据的文件被称为文本文件

举个例子:

同样一份数据:阿拉伯数字10000,分别在文本文件和二进制文件中是如何存储的?

如上图

我们打开txt文件时默认的打开方式是打开文本文件的打开方式。

思考:如果用打开文本文件的打开方式来打开二进制文件会发生什么?

测试代码:

#include<stdio.h>
int main()
{
	int a = 10000;
	//打开文件
	FILE* pf = fopen("test.txt", "wb");//为了写数据,打开一个二进制文件
	//若打开文件失败,则退出程序
	if (pf == NULL)
	{
		perror(fopen);
		return 1;
	}
	//写入
	fwrite(&a, 4, 1, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

会出现乱码。

那么要如何正确的打开二进制文件呢?

以下是VS打开二进制文件的方法:

打开文件结果为:

4. 文件的打开和关闭

4.1流和标准流

4.1.1流

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

4.1.2标准流

既然说输入输出数据都要用到“流”,那么问题来了:咱写C程序时用的scanf和printf函数可以从键盘输入数据,可以像屏幕输出数据,这段过程中有打开过“流”吗?

有!

C程序在启动的时候,就会默认打开三个流:

stdin - 标准输⼊流,在大多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。
stdout - 标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出
流中。
stderr - 标准错误流,大多数环境中输出到显示器界⾯。
这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。
stdin、stdout、stderr 三个流的类型是: FILE * ,通常称为⽂件指针。
C语⾔中,就是通过 FILE* 的⽂件指针来维护流的各种操作的。

4.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;
不同的C编译器的FILE类型包含的内容不完全相同,但是⼤同⼩异。
每当打开⼀个文件的时候,系统会根据文件的情况自动创建⼀个FILE结构的变量,并填充其中的信 息,使用者不必关心细节。
⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使⽤起来更加⽅便。
创建一个FILE指针变量
FILE*pf;//文件指针变量
定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个⽂件的⽂件信息区(是⼀个结构体变量)。通过该⽂件信息区中的信息就能够访问该⽂件。也就是说,通过⽂件指针变量能够间接找到与它关联的⽂件 (通过pf找到FILE并进行操作)。

4.3文件的打开和关闭

⽂件在读写之前应该先打开⽂件,在使⽤结束之后应该关闭⽂件。
在编写程序的时候,在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建⽴了指针和⽂件的关系。
ANSI C 规定使用  fopen 函数来打开文件, fclose 来关闭文件。
//打开⽂件
FILE * fopen ( const char * filename, const char * mode );
//关闭⽂件
int fclose ( FILE * stream );

打开文件的时候需要确认两个参数:

①filename为文件名,例如:"test.txt"

②mode为文件的打开模式

以下都为文件的打开模式

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

实例代码:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");//用只写的模式打开文件test.txt
	if (pf == NULL)//如果打开失败就报错
	{
		perror("fopen");
		return 1;
	}
	//读写文件
	fputs("Hello World", pf);//往文件输出字符串数据


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

实现效果:

5. 文件的顺序读写

5.1顺序读写函数介绍

函数名功能适用于
fgetc字符输入函数所有输入流
fputc字符输出函数所有输出流
fgets文本输入函数所有输入流
fputs文本输出函数所有输出流
fscanf格式化输入函数所有输入流
fprintf格式化输出函数所有输出流
fread二进制输入文件输入流
fwrite二进制输出文件输出流
上⾯说的适⽤于所有输⼊流⼀般指适⽤于标准输入流和其他输入流(如文件输⼊流);
                         所有输出流⼀般指适⽤于标准输出流和其他输出流(如文件输出流)。

5.2对比两组函数

①printf   /   fprintf   /   sprintf

②scanf   /   fscanf   /   sscanf

5.2.1第一组

printf:通过标准输出流(stdout),向屏幕输出格式化的数据。

摘自网站cplusplus:printf - C++ Reference (cplusplus.com)

int printf ( const char * format);

ps:格式化就是数据要按照某种格式输出或输入

例如:使用printf时用的%d,这就是整形输出的格式;scanf用的%d就是整形输入格式。

fprintf:通过选择输出流(可选标准输出流或者文件输出流或者其他),向屏幕或者文件输出格式化数据。

函数定义部分:int fprintf ( FILE * stream, const char * format);

摘自cplusplus:fprintf - C++ Reference (cplusplus.com)

参数分析:

第一个参数的类型是FILE*,即选择流:若要打印到屏幕,则使用标准输出流(stdout),若要输出到文件,则使用文件输出流,传入文件指针pf。

第二个参数为格式化的数据,例如: "%d %s",10,"I am a student"

实例代码:

以下为:使用文件输出流的fprintf

#include<stdio.h>
typedef struct student
{
	char name[20];
	int age;
}S;

int main()
{
	S s = { "张三",20 };

	//打开文件
	FILE* pf = fopen("test.txt", "w");//用只写的模式打开文件test.txt
	if (pf == NULL)//如果打开失败就报错
	{
		perror("fopen");
		return 1;
	}
	//读写文件
	fprintf(pf, "%s %d", s.name, s.age);//使用文件输出流

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

运行结果:

以下为使用标准输出流(stdout)的fprintf:

#include<stdio.h>
typedef struct student
{
	char name[20];
	int age;
}S;

int main()
{
	S s = { "张三",20 };

	fprintf(stdout, "%s %d", s.name, s.age);//使用标准输出流

	return 0;
}

sprintf:将格式化的数据转化成字符串。

函数定义: int sprintf ( char * str, const char * format);

摘自cplusplus:sprintf - C++ Reference (cplusplus.com)

参数分析:

第一个参数是char型指针,即将格式化的数据转化成字符串,并将字符串的首地址赋给str。

第二个参数是格式化的数据。

代码实例:

#include<stdio.h>
typedef struct student
{
	char name[20];
	int age;
}S;
int main()
{
	S s = { "张三",20 };
	char str[200] = { 0 };
	sprintf(str,"%s %d", s.name, s.age);//将结构体成员转化成字符串
	printf("%s\n", str);
	return 0;
}

5.2.2第二组

scanf:使用标准输入流(stdin),将格式化的数据输入(通过地址)并赋给相应变量。

int scanf ( const char * format);

摘自:scanf - C++ Reference (cplusplus.com)

fscanf:选择输入流(可选标准输入流或者文件输入流或者其他),将格式化数据输入(通过地址)并赋给相应变量。

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

摘自:fscanf - C++ Reference (cplusplus.com)

参数分析:

第一个参数:选择流,若选标准输入流则使用stdin,若选文件输入流则使用pf(文件指针)。

第二个参数:格式化数据(注意:记得加取地址符&)

代码实例:

-使用文件输入流:

#include<stdio.h>
typedef struct student
{
	char name[20];
	int age;
}S;

#include<stdio.h>
int main()
{
	S s = { 0 };
	//打开文件
	FILE* pf = fopen("test.txt", "r");//用只读的模式打开文件test.txt
	if (pf == NULL)//如果打开失败就报错
	{
		perror("fopen");
		return 1;
	}
	//读写文件
	fscanf(pf, "%s %d", &s.name, &(s.age));//使用文件输入流读取数据并赋值给结构体成员
	printf("%s %d\n", s.name, s.age);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

文件test的内容如下:

程序运行结果如下:

-若使用标准输入流(效果与scanf相同):

#include<stdio.h>
typedef struct student
{
	char name[20];
	int age;
}S;

#include<stdio.h>
int main()
{
	S s = { 0 };

	fscanf(stdin, "%s %d", &s.name, &(s.age));//使用标准输入流读取数据并赋值给结构体成员
	printf("%s %d\n", s.name, s.age);

	return 0;
}

sscanf:将字符串数据转化成格式化数据,并赋值给相应变量。

int sscanf ( const char * s, const char * format);

摘自cplusplus:sscanf - C++ Reference (cplusplus.com)

参数分析:

第一个参数为字符串的首地址,用于转化成格式化数据。

第二个参数为格式化数据的输入,并赋值给相应的变量。

实例代码如下:

#include<stdio.h>
typedef struct student
{
	char name[20];
	int age;
}S;
int main()
{
	S s = { "张三",20 };
	char str[200] = { 0 };
	sprintf(str, "%s %d", s.name, s.age);//将结构体成员转化成字符串
	
	S t = { 0 };
	sscanf(str, "%s %d", t.name, &(t.age));//将字符串str按照格式化输入并赋值给结构体成员

	printf("%s %d\n",t.name,t.age );
	return 0;
}

6. 文件的随机读写

6.1fseek

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

功能:该函数能够将文件的光标指向设置好的位置。

以下为每个参数的含义:

①FILE*stream为文件指针变量名

②long int offset为偏移量(光标移动几下)

③int origin光标的起始位置(从哪里开始偏移)

对于参数origin有三种形式:

以下摘自cplusplus:fseek - C++ Reference (cplusplus.com)

实例代码:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt","r");
	if (pf == NULL)//如果打开失败就报错
		{
			perror("fopen");
			return 1;
		}

	//读写文件
	fseek(pf, 5, SEEK_SET);//将光标从文件开头处向右移动5位
	char ch1=fgetc(pf);//获取光标处的字符
	printf("ch1:%c\n", ch1);//打印该字符
	
	fseek(pf, 5, SEEK_CUR);//将光标从指针(pf)当前位置向右移动5位
	char ch2=fgetc(pf);
	printf("ch2:%c\n", ch2);//打印该字符
	
	fseek(pf, -2, SEEK_END);//将光标从文件末尾处向左移动2位
	char ch3 = fgetc(pf);//获取光标处的字符
	printf("ch3:%c\n", ch3);//打印该字符
	
	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

txt文本中的数据:

运行结果:

分析过程如下图:

要注意,SEEK_END指向的是文件末尾处,此时光标处的数据为空,如果直接打印此处数据会得出乱码。

6.2ftell

功能:返回⽂件指针相对于起始位置的偏移量
函数: long int ftell ( FILE * stream );

代码实例:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");//用只读的模式打开文件test.txt
	if (pf == NULL)//如果打开失败就报错
	{
		perror("fopen");
		return 1;
	}
	//读写文件
	fseek(pf, 0, SEEK_END);//将光标置到末尾处
	long size = ftell(pf);//返回光标相对于文件开头的偏移量
	printf("%d\n", size);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

txt文件内容:

运行结果:

6.3rewind

功能:让⽂件指针的位置回到⽂件的起始位置
函数:void rewind ( FILE * stream );

代码实例:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r+");//用读写的模式打开文件test.txt
	if (pf == NULL)//如果打开失败就报错
	{
		perror("fopen");
		return 1;
	}
	//读写文件
	char ch = 0;
	for (ch = 'A'; ch <= 'Z'; ch++)
	{
		fputc(ch, pf);//将26个字符:A-Z 依次输入到文件test内
	}
	rewind(pf);//让光标回到文件最开头处
	ch = 0;
	while ((ch = fgetc(pf)) != EOF)//将文件数据从头到尾打印出来
	{
		putc(ch, stdout);
	} 
	printf("\n");
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

7. 文件读取结束的判定

7.1文本文件读取结束的判定

⽂本⽂件读取是否结束,判断返回值是否为 EOF fgetc ),或者 NULL fgets
例如:
fgetc 判断是否为 EOF .
fgets 判断返回值是否为 NULL .

7.2二进制文件读取结束的判定

2. ⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。
例如:
fread判断返回值是否⼩于实际要读的个数。(fread会返回其读到数据的个数)
以下摘自cplusplus: fread - C++ Reference (cplusplus.com)
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

7.3被错误使用的feof

牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。
feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。
int feof ( FILE * stream );

结论:如果是读到文件末尾结束,就返回非0值,若是途中发生错误而结束,则返回0。

代码实例:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r+");//用只读的模式打开文件test.txt
	if (pf == NULL)//如果打开失败就报错
	{
		perror("fopen");
		return 1;
	}
	//读写文件
	char ch = 0;
	for (ch = 'A'; ch <= 'Z'; ch++)
	{
		fputc(ch, pf);//将26个字符:A-Z 依次输入到文件test内
	}
	rewind(pf);//让光标回到文件最开头处
	ch = 0;
	while ((ch = fgetc(pf)) != EOF)//将文件数据从头到尾打印出来
	{
		putc(ch, stdout);
	}
	printf("\n");
	//判断是什么原因结束的
	if (ferror(pf))
	{
		puts("I/O error when reading\n");
	 }
	else if (feof(pf))
	{
		puts("End of file reached successfully\n");
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:

8. 文件缓冲区

ANSIC 标准采⽤“缓冲⽂件系统” 处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的⼤⼩根据C编译系统决定的。


看完觉得有用的请点个免费的赞和关注,这对我真的很重要!非常感谢大家!

  • 46
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值