WHCSRL 技术网

c语言文件操作

1.什么是文件
2.文件名
3.文件类型
4.文件缓冲区
5.文件指针
6.文件的打开和关闭
7.文件的顺序读写
8.文件的随机读写
9.文件结束的判定

什么是文件

磁盘上的文件是文件但是在程序设计中,我们一般谈的文件有两种:程序文件,数据文件

程序文件

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

数据文件

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

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

文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用
文件名包含3部分:文件路径+文件名主干+文件后缀
例如:c:code est.txt
为了方便起见,文件标识常被称为文件名

文件类型

根据数据的组织形式,数据文件被称为文本文件或者二进制文件
数据在内存中以二进制的形式存储,如果不加转移的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换,以ASCII字符的形式存储的文件就是文本文件
一个数据在内存中是咋样存储的
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储

如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节,(每个字符一个字节),而二进制形式
输出,则在磁盘上只占4个字节(vs2013编译器中的数据)
在这里插入图片描述
注意:ASCII形式的(1)(0)(0)(0)(0)
都是字符 比如(1)的ASCII码值就是97那么
97的二进制就是00110 0001

测试代码

int main()
{
	int a = 10000;
	FILE* pf = fopen("test.txt","wb"); //wb以二进制形式写文件
	fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
	 数据来自a地址处, 4个字节,写14个字节的数据,放到pf维护的文件里面去
	fclose(pf);
	pf = NULL;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

文件缓冲区

ANSIC标准采用缓冲文件系统处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在
使用的文件k开辟一块文件缓冲区,从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘
上,如果从磁盘向计算机读入数据则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐
个地将数据送到程序数据区(程序变量等),缓冲区的大小根据c编译系统决定的
在这里插入图片描述
printf 输出缓冲区 屏幕

文件指针

缓冲文件系统中,关键的概念是"文件类型指针",简称"文件指针"
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及
文件当前的位置等)。这些信息是保存在一个结构体变量中的,该结构体类型是有系统声明的,取名FILE

FILE是什么

struct _iobuf {
	char *_ptr;
	int   _cnt;
	char *_base;
	int   _flag;
	int   _file;
	int   _charbuf;
	int   _bufsiz;
	char *_tmpfname;
};
typedef struct _iobuf FILE;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

不同的c编译器的FILE类型包含的内容不完全相同,但是大同小异
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量并填充其中的信息,使用者不必关心细节
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便

下面我们可以创建一个FILE*的指针变量

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

定义pf是一个指向FILE类型数据的指针变量,可以使pf指向m某个文件的文件信息区(是一个结构体变量)通过该文
件信息区中的信息就能够访问该文件,也就是说,通过文件指针变量能够找到与它相关联的文件
在这里插入图片描述

文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的
关系 ANSIC规定使用fopen函数来打开文件,fclose来关闭文件

fopen和fclose的参数

FILE * fopen(const char* filename, const char* mode);
filename是文件名 mode是打开的方式
int fclose (FILE * stream);

打开的方式如下

在这里插入图片描述
在这里插入图片描述

#include<errno.h>
int main()
{
	 .. 两个点表示上一级路径
	.  一个点表示当前路径

	fopen("..\text.txt", "r");	表示打开上一个路径的文件

	//打开文件test.txt
	fopen("test.txt","r");
	//绝对路径的写法
	fopen("D:\123_test\10_28\test.txt","r");//就是把内容写的更加具体

	FILE* pf = fopen("test.txt", "r");//打开成功就会返回指针,指向结构体变量,打开失败会返回空指针
	//这里创建test.txt的文件信息区  pf指针指向这个文件信息区

	//图6

	if (pf == NULL)
	{
		printf("%%s
", strerror(errno));//打印错误码所对应的错误信息
	}

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
  • 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

在这里插入图片描述

文件的顺序读写

在这里插入图片描述
fgetc 从文件里面读信息出来
fputc 把字符输出到文件里面去

fputc

参数是: int fputc(int c, FILE*stream);
c是我们要写的字符 stream是流pf

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	FILE* pfWrite = fopen("TEST.txt", "w");
	if (pfWrite == NULL)
	{
		printf("%%s
", strerror(errno));
	}
	fputc('b', pfWrite);将字符b输出到文件去
	fputc('y', pfWrite);将字符y输出到文件去
	fputc('t', pfWrite);将字符t输出到文件去
	fputc('e', pfWrite);将字符e输出到文件去
	fclose(pfWrite);
	pfWrite = NULL;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

fgetc

参数是 int fgetc(FILE* stream);
从流里面读一个字符 返回来

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	FILE* pfRead = fopen("TEST.txt", "r");
	if (pfRead == NULL)
	{
		printf("%%s
", strerror(errno));
	}
	//读文件
	printf("%%c", fgetc(pfRead)); //b
	printf("%%c", fgetc(pfRead)); //y
	printf("%%c", fgetc(pfRead)); //t
	printf("%%c", fgetc(pfRead)); //e
	fclose(pfRead);
	pfRead = NULL;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

从键盘输入
输出到屏幕
键盘&屏幕都是外部设备

键盘-标准输入设备 - stdin
屏幕-标准输出设备 - stdout
是一个程序默认打开的两个流设备
stdin FILE* stdin类型默认是FILE
stdout FILE
stdout类型默认是FILE
stderr FILE
stderr类型也默认是FILE*的
这些设备都是默认打开的
因为fgetc和fputc是适用于所有流的

int main()
{
	int ch = fgetc(stdin);
	读取一个字符  从键盘里面读取也就是 stdin 
	fputc(ch, stdout);
	从键盘上输入 打印到屏幕上面
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

fgets

fgets的参数是
char fgets(char string, int n, FILE * stream);
string是 数据的存储位置
n 是最多读取多少个字符
stream是流
puts直接打印到屏幕上

int main()
{
	
	fgets读取一行数据
	fgets的参数是
	char *fgets(char* string, int n, FILE * stream);
	string是  数据的存储位置
	n 是最多读取多少个字符
		
	char buf[1024] = { 0 };
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		return 0 ;
	}
	读文件
  fgets(buf, 1024, pf);
  printf("%%s", buf);//就可以将文件第一行显示在显示屏幕中
  puts(buf);//直接打印到屏幕上面
  fgets(buf, 1024, pf);
  printf("%%s", buf);//就可以将文件第二行显示到显示屏幕上
  puts(buf);//直接打印到屏幕上面
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0; 
}
  • 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

fputs

参数是 int fputs(const char * string, FILE *stream);
写一个字符串到流里面去

int main()
{
	char buf[1024] = { 0 };
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		return 0;
	}
	//写文件
	fputs("hello", pf);
	fputs("world", pf);
	fclose(pf);
	pf = NULL;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
int main()
{
	char buf[1024] = { 0 };
	//从键盘读取一行文本信息
	fgets(buf , 1024, stdin);
	//从标准输入也就是键盘进行读取
	fputs(buf , stdout);
	// 输出到标准输出流 也就是键盘
	gets(buf);
	//从键盘上读取
	puts(buf);
	//输出到屏幕上
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

fprintf

参数是:
int fprintf(FILE* stream, const char* format[,argument]…);
stream流 后面跟printf一样
将有格式的文件,输出到流去

fprintf 的参数   int fprintf(FILE* stream, const char* format[,argument]...);
struct S
{
	int n;
	float score;
	char arr[10];
};
int main()
{ 
	struct S s = { 100, 3.14f, "bit" };
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		return 0;
	}
	格式化的形式写文件
	fprintf(pf, "%%d", "%%f", "%%s", s.n, s.score, s.arr);
	fclose(pf);
	pf = NULL;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

fscanf

参数是
int fscanf(FILE * stream, const char* format [,argument]…);

咋样读出来
fscanf - 参数是   int fscanf(FILE * stream, const char* format [,argument]...);
struct S
{
	int n;
	float score;
	char arr[10];
};
int main()
{
	struct S s = {0};
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		return 0;
	}
	//格式化的输入数据
	fscanf(pf ,"%%d %%f %%s", &(s.n), &(s.score),s.arr);
	printf("%%d %%f %%s
", s.n, s.score, s.arr);
	fclose(pf);
	pf = NULL;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

因为适用于所有流所以

struct S
{
	int n;
	float score;
	char arr[10];
};
int main()
{
	struct S s = { 0 };
	//格式化的输入数据
	fscanf(stdin, "%%d %%f %%s", &(s.n), &(s.score), s.arr);//从键盘上读取数据
	//格式化的输出数据
	fprintf(stdout, "%%d %%f %%s", s.n, s.score, s.arr);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

面试题

对比一组函数
scanf/fscanf/sscanf
print/fprint/sprintf

scanf/print
是针对标准输入流和标准输出流的格式化输入/输出语句
fscanf/fprintf
是针对所有输入流/所有输出流的格式化输入输出语句这组函数包含了上面那组函数功能文件流
sscanf/sprintf sscanf
是从字符串中读取格式化的数据 sprintf是把格式化的数据输出成(存储到)字符串

sscanf

参数是
int sscanf(const char* buffer, const char*format[,argument]…);
sscanf从字符串里面读一个格式化的数据

sprintf

参数是
int sprintf(char * buffer, const char* format[,argument]…);
sprintf 写格式化的数据,到一个字符串里面去

sprintf和sscanf的具体使用

struct S
{
	int n;
	float score;
	char arr[10];
};
int main()
{
	struct S s = { 100, 3.14f, "abcdef" };
	struct S tmp = { 0 };
	char buf[1024] = { 0 };

	sprintf(buf, "%%d %%f %%s", s.n, s.score, s.arr);//把格式化的数据转换成字符串存储到buf
	//printf("%%s
", buf);

	sscanf(buf, "%%d %%f %%s", &(tmp.n), &(tmp.score), &(tmp.arr));//从buf中读取格式化的数据到tmp中
	printf("%%d %%f %%s
", tmp.n, tmp.scor	e, tmp.arr);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

fwrite 二进制读

参数是
size_t fwrite(const void * buffer, size_t size, size_t count, FILE* stream);
buffer指针指向被写的文件 size元素的大小单位是字节 count写几个这样的东西大小 pf流

二进制的形式写代码
struct S
{
	char name[20];
	int age;
	double score;
};
int main()
{
	struct S s = { "张三", 20, 55.6 };
	FILE* pf = fopen("test.txt", "wb");
	//wb为了输出数据,打开一个二进制文件
	if (pf == NULL)
	{
		return 0;
	}
	//二进制的形式写文件
	fwrite(&s, sizeof(struct S), 1, pf);

	fclose(pf);
	pf = NULL;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

fread 二进制的形式读代码

代码参数
size_t fread(void* buffer, size_t size, size_t count, FILE* stream);
buffer存储数据的位置
元素大小
最多读取多个少

struct S
{
	char name[20];
	int age;
	double score;
};
int main()
{
	//struct S s = { "张三", 20, 55.6 };这是放进去的数据
	struct S tmp = { 0 };
	FILE* pf = fopen("test.txt", "rb");//rb是以二进制的形式读文件
	if (pf == NULL)
	{
		return 0;
	}
	//二进制的形式读文件
	fread(&tmp, sizeof(struct S), 1, pf);
	printf("%%s %%d %%lf
", tmp.name, tmp.age, tmp.score);
	fclose(pf);
	pf = NULL;
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

文件的随机读写

fseek 根据文件指针的位置和偏移量来定位文件指针

fseek作用移动文件指针到指定的位置
int fseek(FILE* stream, long int offset, int origin);
stream 就是pf
offset就是偏移量
origin就是文件指针的当前位置

关于起始位置有3个选项:
SEEK_CUR 文件指针当前在哪里,就以当前位置来偏移
SEEK_END 文件的末尾位置 作为起始位置进行偏移
SEEK_SET 文件的起始位置为起始点来进行偏移

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		return 0;
	}
	//1.定位文件指针
	fseek(pf, 2, SEEK_CUR);
	//2.读取文件
	int ch = fgetc(pf);
	printf("%%c
", ch);
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

ftell 返回文件指针相对于起始位置的偏移量

参数 long int ftell(FILE * stream);

ftell  返回文件指针相对于起始位置的偏移量
文件内容是abcdef
参数 long int ftell(FILE * stream);
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		return 0;
	}
	//1.定位文件指针
	fseek(pf, -2, SEEK_END);从文件结尾位置向前偏移-2
	int pos = ftell(pf); 

	printf("%%d
", pos);计算文件的偏移数4
	fclose(pf);
	pf = NULL;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

rewind 让文件指针的位置回到文件的起始位置

void rewind(FILE* stream);
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		return 0;
	}
	int ch = fgetc(pf);//这里的文件指针偏移了一个位置
	printf("%%c
", ch);

	rewind(pf);//这里让文件指针回到了起始位置

	int cs = fgetc(pf);
	printf("%%c
", cs);
	fclose(pf);
	pf = NULL ;
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

EOF -文件结束标志

int main()
{
	//   EOF - end of file 文件结束标志   #define EOF (-1)
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		return 0;
	}
	int ch = fgetc(pf);
	printf("%%d
", ch);// -1 说明文件结束位置放了一个EOF存在 
	fclose(pf);
	pf = NULL;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

文件结束判定

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

perror错误报告函数

int main()
{
	//strerror - 把错误码对应的错误信息的字符串地址返回
	//printf("%%s
", strerror(errno));
	//perror
	FILE* pf = fopen("test2.txt", "r");
	if (pf == NULL)
	{
		perror("hehe"); //打印出来的结果是  hehe: No such file or directory
      //perror直接会把你放过来的字符串先打印,然后打印:和空格
	 //然后在把错误码所对应的错误信息打印到后面去
		fclose(pf);
		pf = NULL;
		return 0;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

feof使用实例

int main()
{
	int c;//注意:int,非char,要求处理EOF
	FILE* fp = fopen("test.txt", "r");
	if (fp == NULL) //!fp
	{
		perror("file opening failed");
		return -1;
	}
	//fgetc当读取失败的时候或者遇到文件结束的时候,都会返回EOF
	while ((c = fgetc(fp)) !=  EOF)// 标准 C I/O读取文件循环
	{
		putchar(c);
	}
	//判断是什么原因结束的
	if (ferror(fp))   //读取失败报错停止
		puts("I/O error when reading");
	else if (feof(fp))//是遇到了EOF而结束的
		puts("End of file reached successfully");
	fclose(fp);
  fp = NULL;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

feof使用实例

int main()
{
	FILE* pf = fopen("test2.txt", "r");
	if (pf == NULL)
	{
		perror("open file test2.txt");
		return 0;
	}
	//读文件
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		putchar(ch);
	}
	if (ferror(pf))错误原因1出错导致结束
	{
		printf("error
");
	}
	else if (feof(pf))错误原因遇到EOF结束的
	{
		printf("end of file
");
	}
	fclose(pf);
	pf = NULL;
	return 0;
}
  • 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

二进制文件的例子

enum{SIZE = 5};
int main()
{
	double a[SIZE] = { 1.0, 2.0, 3.0, 4.0, 5.0 };
	double b = 0.0;
	size_t ret_code = 0;
	FILE *fp = fopen("test.bin", "wb");//必须用二进制模式
	fwrite(a, sizeof(*a), SIZE, fp);//写double的数组
	fclose(fp);
	fp = fopen("test.bin", "rb");
	//读double的数组
	while ((ret_code = fread(&b, sizeof(double), 1, fp)) >= 1)
	{
		printf("%%lf
", b);
	}
	if (feof(fp))//这里用feof判断是不是遇到了EOF结束
		printf("Error reading test.bin: unexpected end of file
");
	else if (ferror(fp))//读取失败结束
	{
		perror("Error reading test.bin");
	}
	fclose(fp);
	fp = NULL;
	return 0;
}
  • 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

ferror

Tests for an error on a stream
测试流上的错误
int ferror(FILE* stream);
返回值 (Return Value)
如果设置了与流关联的错误指示符,则该函数返回非零值,否则返回零值

推荐阅读