C语言--文件操作


一、什么是文件

磁盘上的文件是文件
在程序设计中,一般谈的文件由两种:程序文件 和 数据文件

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

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

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

二、文件名

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

三、文件类型

根据数据的组织形式,数据文件被称为 文本文件(人看的) 或者 二进制文件(电脑看的)
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是 二进制文件
如果要在外存上以ASCII码的型存储,则需要在存储前转换
以ASCII字符的形式存储的文件就是文本文件

一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII码值形式存储,也可以使用二进制形式存储
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节)
而二进制形式输出,在磁盘上只占4个字节
在这里插入图片描述

测试代码
#include<stdio.h>
int main()
{
	int a = 10000;
	FILE* pf = fopen("test.txt","wb");
	fopen打开的文件叫test.txt,wb代表write bin以二进制的形式写进去
	fwrite(&a,4,1,pf);
	a的地址,4个字节,1个这样的数据,放到pf这个文件里面去
	fclose(pf);
	pf = NULL;
	return 0;
}

四、文件缓冲区

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

五、文件指针

缓冲文件系统中,关键的概念是 “文件类型指针”,简称“文件指针”
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,状态及位置)
这些信息是保存在一个结构体变量中,该结构体类型是有系统声明,取名FILE
例如,VS2008编译环境提供的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指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
在这里插入图片描述

六、文件的打开和关闭

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

FILE* fopen(const char* filename,const char* mode);    mode为打开方式
int fclose(FILE* stream);        不会置成空指针

“r”(只读) 为了输入数据,打开一个已经存在的文本文件 出错
“w”(只写) 为了输出数据,打开一个文本文件 建立一个新的文件
“a”(追加) 向文本文件尾添加数据 出错
“rb”(只读) 为了输入数据,打开一个二进制文件 出错
“wb”(只写) 为了输出数据,打开一个二进制文件 建立一个新的文件
“ab”(追加) 向一个二进制文件尾添加数据 出错
“r+”(读写) 为了读和写,打开一个文本文件 出错
“w+”(读写) 为了读和写,建议一个新的文件 建立一个新的文件
“a+”(读写) 打开一个文件,在文件尾进行读写 建立一个新的文件
“rb+”(读写) 为了读和写打开一个二进制文件 出错
“wb+”(读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件
“ab+”(读写) 打开一个二进制文件,在文件尾进行读和写 建立一个新的文件

#include<stdio.h>
int main()
{
.. 表示上一级路径
.  表示当前路径
	fopen("../../test.txt","r");
	fopen("test.txt","r");        相对路径
fopen("C:\\2020_code\\84\\test_5_6\\test.txt","r");        绝对路径
	FILE* pf = fopen("test.txt","r");    FILE* pf = fopen("test.txt","w");
若打开文件不存在,会传回来空指针NULL    w写入 会建立一个新的文件覆盖掉原来的文件,导致内容消失
加一个判断语句                            除非是追加
	if(pf == NULL)
	{
		printf("%s\n",strerror(errno));
		return 0;
	}
	关闭文件
	fclose(pf);    fclose接收的是FILE*类型的指针
	pf = NULL;
	return 0;
}

七、文件的顺序读写

字符输入函数 fgetc 所有输入流
字符输出函数 fputc 所有输出流
文本行输入函数 fgets 所有输入流
文本行输出函数 fputs 所有输出流
格式化输入函数 fscanf 所有输入流
格式化输出函数 fprintf 所有输出流
二进制输入 fread 文件
二进制输出 fwrite 文件

1.字符fputc,fgetc

int fputc(int c,FILE* stream) 写入一个字符到流中或标准输出,人话:从程序中写数据输入文件

int fgetc(FILE* steam) 从流中一个一个读取字符或者标准输入,人话:从文件中读取数据到程序
无符号char强制转换为int的形式返回读取的字符,如果到达文件末尾或者发生读错误,则返回EOF
#include<stdio.h>
int main()
{
	w是写入
	FILE* pfWrite = fopen("text.txt","w");
	if(pfWrite == NULL)
	{
		printf("%s\n",strerror(errno));
		return 0;
	}
	从程序中写数据输入文件
	fputc('F',pfWrite);
	fputc('6',pfWrite);
	fputc('G',pfWrite);
	关闭文件
	fclose(pfWrite);
	pfWrite = NULL;
	return 0;
}
此时text.txt里放的F6G

int main()
{
	r是读
	FILE* pfRead = fopen("text.txt","r");
	if(pfRead == NULL)
	{
		printf("%s\n",strerror(errno));
		return 0;
	}
	从文件中读取数据到程序
	printf("%c:,fgetc(pfRead);
	printf("%c:,fgetc(pfRead);
	printf("%c:,fgetc(pfRead);
	打印结果是F6G
	关闭文件
	fclose(pfRead);
	pfRead = NULL;
	return 0;
}

2.文本行fgets,fputs,puts

int puts(const char* string) 写一个字符串到标准输出上(屏幕)人话:从文件中读取数据到程序
	打印完一行数据后会自动换行

int fgets(char* string,int n,FILE* stream) 从流读取的信息放到string里面,人话:从文件中读取数据到程序
	n代表最后读取多少个字符,读取一行内所有内容

int fputs(const char* string,FILE* stream) 写一个字符串到流中,人话:从程序中写入数据到文件
	不会自己换行
假设test.txt放的是    abc
       				 hello
int main()
{
	char buf[1024]  = {0};
	FILE* pf = fopen("test.txt","r");
	if(pf == NULL)
	{
		return 0;
	}
	从文件中读取数据到程序
	fgets(buf,1024,pf);
	printf("%s\n",buf);
	puts(buf);    puts打印完一行数据后会自动换行
	打印为abc,abc后面本身有一个换行,fgets会把换行\n也读进去
	fgets(buf,1024,pf);
	printf("%s",buf);
	打印为hello,因为hello后面没有换行,所以不会换行
	puts(buf);    puts打印完一行数据后会自动换行
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

int main()
{
	char buf[1024] = {0};
	FILE* pf = fopen("text.txt","w");
	if(pf == NULL)
	{
		return 0;
	}
	从程序中写数据到文件中
	fputs("hello",pf);                        fputs("hello\n",pf);
	fputs("world",pf);                        fputs("world\n",pf);
	在文件text.txt写上了helloworld            要换行需要自己添加\n
	不会换行                                
	fclose(pf);
	pf = NULL;
	return 0;
}

3.格式化(复杂的数据类型)fscanf,fprintf

int fprintf(FILE* stream,const char* format[,argument]...)
	打印格式化的数据到流中,人话:从程序中输入数据到文件

int fscanf(FILE* stream,const wchar_t* format[,argument]...);
	写入格式化的数据到流中,人话:从文件中读取数据到程序中
	后面一串是地址
放进去
struct S
{
	int n;
	float score;
	char arr[10];
};
int main()
{
	struct S s = {100,3.14f,"bit"};
	FILE* pf = fopen("text.txt","w");
	if(pf == NULL)
	{
		return 0;
	}
	程序中的数据格式化的形式写入文件
	fprintf(pf,"%d %f %s",s.n,s.score,s.arr);
	显示到text.txt这个文件中
	fclose(pf);
	pf = NULL;
	return 0;
}

读出来的
struct S
{
	int n;
	float score;
	char arr[10];
};
int main()
{
	struct S s = {0};
	FILE* pf = fopen("text.txt","r");
	if(pf == NULL)
	{
		return 0;
	}
	将文本中的数据格式化的形式输入程序
	fscanf(pf,"%d %f %s",&(s.n),&(s.score),s.arr);
	printf("%d %f %s\n",s.n,s.score,s.arr);
	fclose(pf);
	pf = NULL;
	return 0;
}

5.二进制fread,fwrite

size_t fwrite(const void* buffer,size_t size,size_t count,FILE* stream);
	以二进制的形式写入文件,buffer指针,size元素大小,count几个元素

size_t fread(const void* buffer,size_t size,size_t count ,FILE* stream);
	以二进制的形式读取文件,buffer指针,size元素大小,count几个元素
	如果读取的个数大于原本的个数,返回的值是原本的元素个数
	如果读取的个数小于原本的个数,返回的值是读取的元素个数
	如果不成功或读到文件末尾返回0
struct S
{
	char name[20];
	int age;
	double score;
};

int main()
{
	struct S s = {"张三",20,55.6};
	写入
	FILE* pf = fopen("test.txt","wb");
	if(pf == NULL)
	{
		return 0;
	}
	二进制的形式写入文件
	fwrite(&s,sizeof(struct S),1,pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

int main()
{
	struct S tmp = {0};
	FILE* pf = fopen("test.txt","rb");
	if(pf == NULL)
	{
		return 0;
	}
	以二进制的形式读取文件
	fread(&tmp,sizeof(struct S),1,pf);
	printf("%s %d %lf\n",tmp.name,tmp.age,tmp.score);
	fclose(pf);
	pf = NULL;
	return 0;
}

6.sscanf,sprintf

int sscanf(const char* buffer,const char* format[,argument]...);
	从一个字符串中读取格式化的数据

int sprintf(char* buff,const char* format[,argument]...)
	写一个格式化的数据到字符串去
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};
	把格式化的数据转成字符串存储到buf
	sprintf(buf,"%d %f %s",s.n,s.score,s.arr);
	printf("%s\n",buf);字符串
	从buf中读取格式化的数据到tmp中
	sscanf(buf,"%d %f %s",&(tmp.n),&(tmp.score),tmp.arr);
	printf("%d %f %s\n",tmp.n,tmp.score,tmp.arr);    格式化
	return 0;
}

7.对比一组函数

scnaf,fscanf,sscanf
printf,fprintf,sprintf

scanf,printf 是针对标准输入流/标准输出流的格式化输入/输出语句
fscanf,fprintf 是针对所有输入流/输出流的格式化输入/输出语句
sscanf 是从字符串中读取格式化的数据
sprintf 是把格式化的数据输出成(存储到字符串)

八、文件的随机读写

1.fseek

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

itn fseek(FILE* stream,long int offset,int origin);
	offset是偏移量
	origin是文件指针的当前位置:三个选项
		文件指针的当前位置:SEEK_CUR
		文件指针的末尾位置:SEEK_END
		文件指针的起始位置:SEEK_SET
假设test.txt文件中存储的是abcdef
int main()
{
	FILE* pf = fopen("test.txt","r");
	if(pf == NULL)
	{
		return 0;
	}
	定位文件指针
	fseek(pf,4,SEEK_CUR);        fseek(pf,-2,SEEK_END);
	读取文件
	int ch = fgetc(pf);
	printf("%c\n",ch);
	打印结果为e
	fclose(pf);
	pf = NULL;
	return 0;
}

2.ftell

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

long int ftell(FILE* stream);
假设test.txt里面有"abcdef"
int main()
{
	FILE* pf = fopen("test.txt","r");
	if(pf == NULL)
	{
		return 0;
	}
	fseek(pf,-2,SEEK_END);
	int pos = ftell(pf);
	printf("%d\n",pos);
	打印结果为4
	fclose(pf);
	pf = NULL;
	return 0;
}

3.rewind

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

void rewind(FILE* stream);
假设test.txt里面有"abcdef"
int main()
{
	FILE* pf = fopen("test.txt","r");
	if(pf == NULL)
	{
		return 0;
	}
	int ch = fgetc(pf);
	printf("%c\n",ch);
	rewind(pf);
	ch = fgetc(pf);
	printf("%c\n",ch);
	两次打印结果都为a
	fclose(pf);
	pf = NULL;
	return 0;
}

九、文件的结束判定

1.feof(末尾)

int feof(FILE* stream);   
若当前指针位置是文件末尾,返回一个非0值,
若当前指针位置不是文件末尾,返回0且无错误返回

牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件是否结束
而是应用于当文件读取结束的时候,判断是读取失败结束 还是遇到文件尾结束

  1. 文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgetc)
    fgetc判断是否为EOF
    fgetc判断返回值是否为NULL

  2. 二进制文件的读取结束判断,判断返回值是或否小于实际要读的个数
    fread判断返回值是否小于实际要读的个数

2.ferror(无错反0,错反非0,流上的错误)

int ferror(FILE* stream);        测试流上的错误
如果流中未发生错误,则返回 0。否则,它将返回一个非零值。

3.文本文件的例子

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int c;    注意:int,非char,要求处理EOF
	FILE* fp = fopen("test.txt","r");
	if(!fp)
	{
		perror("FILE opening failed");        报错误信息,比strerror方便
		return Exit_FAILURE;
	}
	fgetc当读取失败的时候或者遇到文件结束的时候,都会返回EOF
	while((c = fgetc(pf)) ! = EOF)
	无符号char强制转换为 int   的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 	EOF{
		putchar(c);
	}
	判断是什么原因结束的
	if(ferror(fp))
		put("I/O error when reading");
	else if(feof(fp))
		puts("End of file reached successfully");
	fclose(fp);
}

4.二进制文件的例子

#include<stdio.h>
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_cod = fread(&b,sizeof(double),1,fp))>=1)
	fread判断返回值是否小于实际要读的个数
	如果读取的个数大于原本的个数,返回的值是读取的元素个数
	如果读取的个数小于原本的个数,返回的值是读取的元素个数
	如果不成功或读到文件末尾返回 0{
		printf("%lf\n".b);
	}
	if(feof(fp))
		printf("Error reading test,bin: unexpected end of ile\n");
	else if(ferror(fp))
		perror("Error reading test.bin");
	fclose(fp);
	fp = NULL;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值