(超详细)一起来学C语言——文件

C语言之文件

文件的分类

在我们的程序设计中文件分为2种,

一种是程序文件,另一种是数据文件

程序文件

程序文件是指源文件.c,目标文件.obj,可执行文件.exe等文件

数据文件

数据文件是指在程序的运行中,从中读取的数据的文件,或者进行写入数据,输出数据的文件

今天,我们讨论的就是数据文件

在之前,我们都是从终端(键盘)读入数据,在将数据输出到终端(显示器)上面去。

但是,今天我们就是要从数据文件上进行读和写入数据。

文件的名字

文件名必须是唯一的,以便被用户所识别

文件名由3个部分组成:

以下面这个例子来说:

c:\code\test.txt
  1. 文件路径 c:\code\
  2. 文件名主干 test
  3. 文件后缀 .txt

数据文件的分类

对数据文件来说,它还可以再进行分类。按照它的内容来分类,它可以被分为二进制文件文本文件

二进制文件

数据在内存中本身就是按照2进制来存储文件的,如果不进行类型转换就直接输出到外存,那么就是二进制文件.

如果你在内存种就是以大端进行存储的,那就在二进制文件中也是大端。

如果是小端,那就是小端存储

文本文件

但是,如果把二进制数据转换为ASCII版本的,那就是文本文件了。

数据在内存中是怎么存储的?

如果是字符,一律按照ASCII进行存储。

如果是数字,既可以按照ASCII,也可以按照2进制进行存储。这两种方式在内存中存储时不一样的。

image-20210927184452468

文件指针

对于每一个被打开的文件来说,系统都会在内存中为它开辟一个名为FILE的结构体类型,用来存放文件的各种信息(文件的名字,文件状态及文件当前的位置 )。而这个文件是由系统生成的,包含在stdio.h内部。

不同的C编译器的FILE结构体的内容大致相同。

下面就是VS2008的FILE结构体的组成

image-20210927190512653

这个也叫做该文件的文件信息区。文件信息区就是FILE结构体。

这时候,我们还缺少一个文件指针。

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

每当我们打开一个文件的时候,我们就返回一个文件指针。该指针就指向该文件对应的FILE结构体,也就是它的文件信息区。

于是,我们就通过该文件指针就成功找到了我们需要打开的文件的信息。

image-20210927191000118

文件的打开和关闭

在我们对文件读写前我们要打开文件,结束后我们就要关闭文件

打开文件我们使用fopen函数来进行打开

fopen函数

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

打开方式:

对于文件来说,一共有下面这几种打开方式:

image-20210927192533596

[超详细的打开方式]((237条消息) C语言文件打开模式(r/w/a/r+/w+/a+/rb/wb/ab/rb+/wb+/ab+)浅析_StrayedKing-245176013-CSDN博客_c语言rb+)

‘w’

注意’w’是要打开一个空的文件,如果里面有内容的话,里面的内容会被销毁。

image-20211002130451720

fclose函数

打开文件之后,不能只顾着打开,还要程序员手动的将文件关闭。

函数原型

int fclose(FILE* stream);
//stream是文件指针

具体使用

int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen:");
		return -1;
	}
	//读文件
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
//会返回错误信息:fopen:: No such file or directory    

文件的顺序读写

文件,就如同我们的硬盘,软盘,光碟一样。都是存储数据的东西。

我们可以在里面输入数据进行存储,也可以将文件中的数据输出

fputs函数(单个数据写到文件中去)

fputs函数就是将数据输入到文件中去,这个文件就是相当于屏幕。输出到外部去。

函数原型:

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

实际上,就如同屏幕这样的展示输出的页面,它也是有着对应的FILE*类型的变量的,和文件并没有什么区别。

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	//写文件
	//这里我们使用fputc
	fputc('h', pf);
	fputc('e', pf);
	fputc('l', pf);
	fputc('l', pf);
	fputc('o', pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

将hello写入到文件中去。

image-20211004081029319

fgets函数(从文件中读单个数据)

函数原型:

int fgets(FILE* stream);

这个函数是从文件中读取数据,也相当于scanf从键盘中读入数据一样。

一次只能返回一个字符。

从文件中读取:

#include<stdio.h>int main(){	//打开文件(以读的形式)	FILE* pf = fopen("data.txt", "r");	if (pf == NULL)	{		perror("fopen");		return -1;	}    //读文件	int ch=fgetc(pf);	printf("%c\n", ch);	ch = fgetc(pf);	printf("%c\n", ch);	ch = fgetc(pf);	printf("%c\n", ch);	ch = fgetc(pf);	printf("%c\n", ch);	ch = fgetc(pf);	printf("%c\n", ch);	return 0;}

从键盘上读取:

#include<stdio.h>int main(){		int ch = fgetc(stdin);	printf("%c\n", ch);	ch = fgetc(stdin);	printf("%c\n", ch);	ch = fgetc(stdin);	printf("%c\n", ch);	ch = fgetc(stdin);	printf("%c\n", ch);	ch = fgetc(stdin);	printf("%c\n", ch);	return 0;}

fputs(将字符串写入到输出流)

函数原型:

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

不同于fputc,fputs是将字符串写入到stream中去。

写到文件中去:

#include<stdio.h>int main(){	//打开文件	FILE* pf = fopen("data.txt", "w");	if (pf == NULL)	{		perror("fopen");		return -1;	}	//写文件	//这里我们使用fputs	fputs("helll", pf);	//关闭文件	fclose(pf);	pf = NULL;	return 0;}

写到屏幕上去:

经过测试,我们发现就只可以使用puts,不可以使用fputs

#include<stdio.h>int main(){	//输出	//这里我们使用puts	puts("helll");	return 0;}

fgets(从输入流中读入数据)

一个读取一行的字符

函数原型:

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

从stream中读取number个字符,并返回到string中去。

具体使用:

函数使用:只会读取number-1个字符,放入到string中去,因为在字符串的最后要补上\0,所以加上\0后就成为了number个字符。

  1. 如果number小于文件中字符的个数:

只会将前number-1个字符拷贝,最后末尾加上\0

#include<stdio.h>int main(){	//打开文件(以读的形式)	FILE* pf = fopen("data.txt", "r");	if (pf == NULL)	{		perror("fopen");		return -1;	}	//读文件	char str[12];	fgets(str, 4, pf);	printf("%s", str);	return 0;}
image-20211004092717053
  1. 如果number大于文件中字符的个数

只是拷贝这一行的数据,最后面加上\0

#include<stdio.h>int main(){	//打开文件(以读的形式)	FILE* pf = fopen("data.txt", "r");	if (pf == NULL)	{		perror("fopen");		return -1;	}	//读文件	char str[20];	fgets(str, 20, pf);	printf("%s", str);		return 0;}
image-20211004093101021

字符指针要注意:

对于字符指针,我们一定要引起注意。我们是用来存储字符的,所以我们采用malloc一个字符串来进行

	char* str2 = (char*)malloc(sizeof(char) * 20);	fgets(str2, 20, pf);	printf("%s", str2);

fprintf函数(按照格式化输出)

fprintf的函数原型:

printf相比,多了一个pf文件指针。

fprintf是严格按照格式化来进行输出的。就和printf那样输出到屏幕上一样,输出到文件中来。

#include<stdio.h>struct s1{	int number;	double score;};int main(){	//打开文件	FILE* pf = fopen("data.txt", "w");	if (pf == NULL)	{		perror("fopen");		return -1;	}	struct s1 s = { 23,89.0 };	//写文件	//这里我们使用fprintf	fprintf(pf,"%d %lf",s.number,s.score);	//关闭文件	fclose(pf);	pf = NULL;	return 0;}

看看效果,就是将printf("%d %lf",s.number,s.score)打印到文件pf中去了。

image-20211004135336075

同样,它也可以输出到屏幕上。

pf是stdout即可

#include<stdio.h>struct s1{	int number;	double score;};int main(){	//打开文件	FILE* pf = fopen("data.txt", "w");	if (pf == NULL)	{		perror("fopen");		return -1;	}	struct s1 s = { 23,89.0 };	//写文件	//这里我们使用fprintf	fprintf(stdout,"%d %lf",s.number,s.score);	//关闭文件	fclose(pf);	pf = NULL;	return 0;}
image-20211004135611281

fscanf(按照格式化输入)

fprintf是输出数据到文件中,fscanf就是从文件中读取数据

#include<stdio.h>struct s1{	int number;	double score;};int main(){	//打开文件	FILE* pf = fopen("data.txt", "r");	if (pf == NULL)	{		perror("fopen");		return -1;	}	struct s1 s ={ 0 };	//读文件	//这里我们使用fscanf	fscanf(pf, "%d %lf", &(s.number),&( s.score));	printf("%d\n", s.number);	printf("%lf\n", s.score);	//关闭文件	fclose(pf);	pf = NULL;	return 0;}
image-20211004140454192

fwrite(以二进制的形式输出)

fwrite是以二进制的形式将数据写入到流中去

函数原型:

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

s是想要输出的元素的地址,size是该类型所占的字节数,count是想要输入的个数,stream是文件

​ 正常返回:实际输出数据块的个数,即count。
  异常返回:返回0值,表示输出结束或发生了错误。

具体使用1:

image-20211004142100290

这样写入文件中都是以二进制的形式输出的。

具体使用2:

image-20211005094640472

fread(以二进制的形式输入)

fread是读入二进制的文本信息

函数原型:

和fwrite没有什么区别

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

s是储存读入元素的地址,size是该类型所占的字节数,count是想要输入的个数,stream是文件

对于这种乱码的二进制文件,也就只有fread可以读出来了。

返回值
  正常返回:实际读取数据块的个数,即count。
  异常返回:如果文件中剩下的数据块个数少于参数中count指出的个数,或者发生了错误,返回0值。此时可以用feof()和ferror()来判定到底出现了什么情况。

#include<stdio.h>struct s1{	int number;	double score;	char arr[20];};int main(){	//打开文件	FILE* pf = fopen("data.txt", "rb");	if (pf == NULL)	{		perror("fopen");		return -1;	}	struct s1 s ={ 0 };	//读文件	//这里我们使用fread	fread(&s,sizeof(s),1,pf);//读出来的数据放入到s中	printf("%d\n", s.number);	printf("%lf\n", s.score);	printf("%s\n", s.arr);	//关闭文件	fclose(pf);	pf = NULL;	return 0;}
image-20211004142841299

fwrite,fread,fseek函数的联合使用

#include <stdio.h>#include <string.h>struct std_type{    int num;    char name[20];    int age;    char class;}stud;//定义了一个结构体变量stud//写入信息到文件中int cstufile(){    int i;    FILE* fp;    if ((fp = fopen("stufile", "wb")) == NULL)    {        printf("The file can't be opened for write.\n");        return 0;    }    for (i = 1; i <= 100; i++)    {        stud.num = i;        strcpy(stud.name, "aaaa");        stud.age = 17;        stud.class = '8';        fwrite(&stud, sizeof(struct std_type), 1, fp);//将数据写入到stud中去    }    fclose(fp);    return 1;}//读文件到信息中void main(){    int n;    FILE* fp;    if (cstufile() == 0) return;    if ((fp = fopen("stufile", "rb")) == NULL)    {        printf("The file can not be opened.\n");        return;    }    //想要打印奇数学号的同学信息    for (n = 0; n < 100; n += 2)    {        fseek(fp, n * sizeof(struct std_type), SEEK_SET);        //如果只打印奇数的话,那必须使用fseek调整文件指针的位置,因为正常条件下只是打印后面的那一个        fread(&stud, sizeof(struct std_type), 1, fp);        printf("%10d%20s%10d%4c\n", stud.num, stud.name, stud.age, stud.class);    }    fclose(fp);}

sprintf(将格式化数据打印成字符串)

和fprintf不同的是,fprintf是将数据打印到文件中去,但是sprintf是将数据打印到一个字符中去。

#include<stdio.h>struct s1{	int number;	double score;	char arr[20];};int main(){	struct s1 s = { 1,90.0,"luoxiangyu" };	char str[100] = {0};	sprintf(str, "%d %lf %s", s.number, s.score, s.arr);	printf("%s", str);	return 0;}
image-20211004144543977

sscanf(从字符串输入数据转换成格式化形式)

和fscanf不同的是,fscanf是从一个文件中读入信息,而sscanf是从一个字符串中读取信息,并且转换成格式化形式

#include<stdio.h>struct s1{	int number;	double score;	char arr[20];};int main(){	char arr[50] = "1 90.000000 luoxiangyu";	struct s1 s = { 0 };	sscanf(arr, "%d %lf %s", &s.number, &s.score, s.arr);	printf("%d\n", s.number);	printf("%lf\n", s.score);	printf("%s\n", s.arr);	return 0;}
image-20211004145413379

相似函数的比较

输入函数作用
scanf从标准输入流(键盘)中输入格式化的数据
fscanf从各种输入流(键盘/文件)中输入格式化的数据
sscanf从字符中输入格式化的数据
输出函数作用
printf把格式化的数据输出到标准输出流(屏幕)中
fprintf把格式化的数据输出到各种输出流(屏幕/文件)中
sprintf把格式化的数据转换成为一个字符串中

文件的随机读写

上面的函数都是随机进行读写的。下面我们就来介绍怎么对任意位置进行读写。

我们使用fseek函数找到要读写的位置。

fseek函数

函数介绍:

image-20211005082510141

功能说明
   使文件指针fp移到基于origin的相对位置offset处。
参数说明
   fp:文件指针。
   offset:相对base的字节位移量
   origin:文件位置指针移动的基准位置,是计算文件位置指针位移的基点。

​ origin 有三种情况:

​ SEEK_CUR 当前位置

​ SEEK_END 文件末尾位置

​ SEEK_SET 文件的起始位置

返回值

正常返回:**当前指针位置。**注意返回的新的位置。想要打印还需要fgetc函数
  异常返回:-1,表示定位操作出错。

具体使用:

这个fseek只是返回的新的指针的位置,还需要配合其他的打印函数才可以将字符打印在屏幕上。

int main(){	//打开文件	FILE* pf = fopen("data.txt", "r");	if (NULL == pf)	{		perror("fopen");		return -1;	}	//123456	//读数字3,当前位置为1	fseek(pf, 2, SEEK_SET);	char c = fgetc(pf);	printf("%c\n", c);	//读数字1,当前位置为4	fseek(pf, -3, SEEK_CUR);//1在4的左边差3个位置	c = fgetc(pf);//读取字符1	printf("%c\n", c);	//关闭文件	fclose(pf);	pf = NULL;	return 0;}

ftell(相对起始位置的偏移量)

在上面的fseek函数中,offset的位置就是字节偏移量。

long int ftell ( FILE * stream );

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

具体应用:

#include <stdio.h>int main(){	FILE* pFile;	long size;	pFile = fopen("data.txt", "rb");	if (pFile == NULL) perror("Error opening file");	else	{		fseek(pFile, 0, SEEK_END); // 此时函数指针在文件末尾位置		size = ftell(pFile);		fclose(pFile);		printf("Size of data.txt: %ld bytes.\n", size);//起始和末尾位置的偏移量就是文件所占字节数	}	return 0;}

rewind(让文件回到起始位置的函数)

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

函数原型:

void rewind ( FILE * stream );

具体使用:

#include <stdio.h>int main(){	int n;	FILE* pFile;	char buffer[27];	pFile = fopen("myfile.txt", "w+");	for (n = 'A'; n <= 'Z'; n++)		fputc(n, pFile);	rewind(pFile);//让原本在末尾的文件指针到最前方	fread(buffer, 1, 26, pFile);	fclose(pFile);	buffer[26] = '\0';	puts(buffer);	return 0;}

feof

看到feof函数,你认为它代表的是什么呢?

可能,很多人都认为这是一个判断文件是否结束的函数,但是其实不是的。

其实,它是来判断文件是以什么方式结束的:

是走到函数末尾结束的,还是遇到错误结束的

如果是走到函数末尾结束的,就返回1

如果是遇到错误结束的,就返回0

feof一般搭配使用的就是ferror

对于ferror来说,

如果是遇到错误结束的,就返回1

如果是走到函数末尾结束的,就返回0

具体使用:

文本文件的例子:

int main(void){	int c; // 注意:int,非char,要求处理EOF	FILE* fp = fopen("test.txt", "r");	if (!fp) {		perror("File opening failed");		return EXIT_FAILURE;	}	//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))		puts("End of file reached successfully");	fclose(fp);}

二进制文件的例子:

enum { SIZE = 5 };int main(void){	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\n", b);	}	if (feof(fp))		printf("End of file reached successfully\n");	else if (ferror(fp)) {		perror("Error reading test.bin");	}	fclose(fp);	fp = NULL;}

文件缓冲区

ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在
使用的文件开辟一块“文件缓冲区”

在将数据从内存存入到磁盘或者从磁盘区数据到内存中去的时候,都会经过这个文件缓冲区的区域。都是将数据填满之后,再进行操作。

image-20211005104506765

在C语言中fflush()函数是专门用来刷新缓存区的。

另外,fclose和exit()也都可以用来刷新缓存区。

所以,一定打开文件后一定要关闭文件,要不然的话写入的文件还在缓存区中就无法保存到文件中了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值