文件操作

文件指针

每个被使用的文件都在内存中开辟了一个相应的文件信息区(结构体对象),用于存放文件相关信息(文件名、状态、位置……)。该结构体类型是有系统声明的,取名FILE

打开关闭文件

文件读写前应 打开文件 ,使用结束后应 关闭文件

//打开关闭文件所用的函数
FILE* fopen ( const char* filename, const char* mode );
int fclose ( FILE* stream );
int main(){
	//打开文件 并检测
	FILE* pf = fopen( "test.txt", "r" );
	if(pf == NULL){
	perror("fopen"); return 1;
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

如上打开文件时,编译器会在这个路径下寻找,没有相应的文件则打开失败
在这里插入图片描述
在这里插入图片描述
当然也可以输入路径打开相应的文件:

FILE* pf1 = fopen( "D:\\code\\test.txt","r" );	//绝对路径——目标文件在硬盘上的真实路径(最精确路径)
FILE* pf2 = fopen( "../test","r" );	//相对路径
//1.引用上级文件: ../test.txt
//2.引用同级文件: test.txt
//3.引用下级文件: file/test.txt
//4.引用上上级文件: ../../test.txt

文件的打开方式

最简单诸如 “r” “w” “a”
“r” 只读,不创建文件
"w"只写,可创建文件,每次都会先将文件内容销毁
"a"追加,可创建文件,文件原先的内容会被保留
"rb"以二进制的方式读
"wb"以二进制的方式写
………………………………

文件顺序读写

利用输入输出函数:

功能函数名适用于
字符输入函数fgetc所有输入流
字符输出函数fputc所有输出流
文本行输入函数(输出同理)fgets所有输入流
格式化输入函数(输出同理)fscanf所有输入流
二进制输入fread文件
二进制输出fwrite文件

说来在我理解里这里的输入输出的主语一般是内存:
fprintf :内存输出数据到文件 fscanf:内存从文件中录入数据

fgetc fgets 使用示例

//…………………………
//写文件"w"时
fputc('c',pf);
fputs("abcdef",pf);
//读文件"r"时
int ch = 0;
while((ch = fgetc(pf)) != EOF){ printf("%c",ch); }//注意加上括号,赋值优先级低于双目运算符 != 
//或 char arr[255] = {0};  printf("%s\n", fgets(arr, 256, pf));
//…………………………

fprintf fscanf 使用示例

//对于结构体	——可用fprintf格式化输入输出
//何为“格式化输出”:输出带有格式(像是%d %f %s 结构体……)的数据
//类比:int printf( const char *format [, argument]... );	//输出到显示器
//     int fprintf( FILE *stream, const char *format [, argument ]...);		//输出到文件
typedef struct S{
	char name[20]; int age; double d;
}S;
int main() {
	S s = { "张三",20,10 };
	//打开文件
	FILE* fpin = fopen("test.txt", "w");
	if (fpin == NULL) { perror("fopen"); return 1; }
	//输出	——这是在 写 入 文 件 !
	fprintf(fpin, "%s %d %lf", s.name, s.age, s.d);
	//关闭文件
	fclose(fpin); fpin = NULL;

	S s2 = { 0 };
	//打开文件
	FILE* fpout = fopen("test.txt", "r");
	if (fpout == NULL) { perror("fopen"); return 1; }
	//读取文件 打印结果
	fscanf(fpout, "%s %d %lf", s2.name, &s2.age, &s2.d);//同scanf一样传入地址 %s %d之间加不加空格无所谓,因为会自动跳过空白字符寻找相应数据
	printf("%s %d %lf\n", s2.name, s2.age, s2.d);
	//关闭文件
	fclose(fpout); fpout = NULL;
	return 0;
};

关于 fscanf 的返回值:

Each of these functions returns the number of fields successfully converted and assigned; the return value does not include fields that were read but not assigned. A return value of 0 indicates that no fields were assigned. If an error occurs, or if the end of the file stream is reached before the first conversion, the return value is EOF for fscanf or WEOF for fwscanf.
大致意思是说返回转换、分配成功的字段数——还挺抽象
我们用以下代码说明:

typedef struct S {
	char name[20]; int age; double d;
}S;
int main() {
	S s = { "张三",20,10 };
	FILE* fpin = fopen("test.txt", "w");
	if (fpin == NULL) { perror("fopen"); return 1; }
	fprintf(fpin, "%d %lf", s.age, s.d);
	fclose(fpin); fpin = NULL;

	S s2 = { 0 };
	FILE* fpout = fopen("test.txt", "r");
	if (fpout == NULL) { perror("fopen"); return 1; }
	printf("%d\n", fscanf(fpout, "%d %lf", &s2.age, &s2.d));//fscanf 成功转换、分配两个字段,故打印2 —— 转换、分配失败的例子暂时没整出来 QAQ
	printf("%d %lf\n", s2.age, s2.d);
	fclose(fpout); fpout = NULL;
	return 0;
};

fwirte fread 使用示例

//size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
//size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
typedef struct S{
	char name[20]; int age; double d;
}S;
int main(){
	S s={"张三",20,10};
	FILE* pf = fopen("test.txt","wb");//"wb"以二进制的方式写
	if(pf == NULL){ perror("fopen"); return 1; }
	fwrite(&s,sizeof(S),1,pf);//从哪读,读多大,读几个,读到哪(流)
	fclose(pf); pf = NULL;
	return 0;
}
//fread 同理

●何为“”:
有很多外部输入输出设备(eg.麦克风、键盘、屏幕……),它们各有差异,操作的方式各不同,若要学这么多不同的东西应该怎么使用学习成本太高,于是它们的上层封装了一层 “流” 统筹它们的使用,使得我们只需通过 “流” 便可以使用它们

fgetc 所谓的“适用于所有输入流”,即表示既可以从文件中读取,也可以从键盘之类的其他途径读取

int ch = getc(stdin);//放入标准输入流 stdin,从键盘读取
printf("%c",ch); //也可以写成 fputc(ch, stdout);//放入标准输出流,打印到屏幕上

sscanf sprintf

既然都说了以上函数了,那么就不得不提到和上面那些函数如此相像的 sscanf 和 sprintf 了
就如该函数的名字一般,这是对 内存 和 字符串 之间进行操作的函数
—— 从字符串中读取格式化数据 – 将格式化数据写入字符串

示例 1:

//类比:int printf( const char *format [, argument]... );
//	   int sprintf( char *buffer, const char *format [, argument] ... );

//sscanf 从字符串中读取
char str[10] = "20220101";
int date1 = 0;
sscanf(str, "%d", &date1); //虽然不知道为什么还是要取地址 &
//sprintf 输出到字符串中
int date2 = 20451212;
sprintf(str,"%d",date2);

示例 2:

//………………………………
char buf[256]={0};
sprintf(buf, "%s %d %lf",s.name,s.age,s.d);//将上面的结构体以字符串的形式输出到buf中
printf("%s\n",buf);

● 这两个函数表示将数据从 a 输出到 b 时的格式(字符串 数据)与之前学的 strcpy (被拷贝 拷贝来源)不一样,需要注意写法

sscanf 和 sprintf 常用于 字符串转换为数字(至少我到目前都这么用)
可转换字符串的函数还有 atoi

char str[10] = "20220101";
int date1 = 0;
date1 = atoi(str);

文件随机读写

fseek

将文件指针移动到指定位置

int fseek( FILE *stream, long offset, int origin );//流 偏移量 起始位置 —— SEEK_CUR -文件指针当前的位置 SEEK_END - 文件末尾的位置 SEEK_SET - 文件开始位置
//…………………………
fputc('a',pf); fputc('b',pf); fputc('c',pf);//此时pf指向自开始位置三个字节后(字符c后的地址)
fseek(pf,2,SEEK_SET);//使pf移至开始位置两个字节后
fputc('d',pf);//将字符c用d覆盖
//…………………………

ftell

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

文件读取结束的判定

文件结束判断

文本文件读取是否(不正常)结束:返回值是否为 EOF(fgetc)或 NULL(fgets)
二进制文件读取是否(不正常)结束:返回值是否小于实际要读的个数 count(fread)

文本文件二进制文件:内存中数据以二进制的形式存在,转换为字符(“w”)后写入文件即文本文件,以二进制的形式直接写入文件(wb)即二进制文件

容易被用错的 feof

int feof( FILE *stream );

用于文件读取结束时判断是读取失败结束还是遇到文件尾结束
在文件读取过程中,不能用来判断文件读取是否结束

以下摘录自 __CFeng 的博客

#include<stdio.h>
int main()
{
    FILE* pf = fopen("2128.txt","r");
    if(pf == NULL) perror("pf");
    
    if(feof(pf)) printf("end of file");
    return 0;
}

我们觉得应该输出 end of file,但实际上文件无内容
因为 当要首次尝试读取文件末尾(EOF)后面的内容的时候 feof 才返回非零值 ;所以 要想利用feof函数判定文件读取结束的前提是已经读取到了EOF
可以理解为只有 feof 函数才能读取EOF后面的内容,所以上面那个错误的用法中,读的是EOF(而非EOF后面的内容),返回值为0

结合上面 feof 的特点,用 feof 判断文件是否结束时会发生什么:

#include<stdio.h>
int main() {
	FILE* fp = fopen("test.txt", "w");
	for (int i = 1; i <= 5; i++) {
		fprintf(fp, "%d ", i);
	}
	fclose(fp);
	
	fp = fopen("test.txt", "r");
	int c = 0;
	while (!feof(fp)) {//用 feof 判断文件是否结束
		c = fgetc(fp);
		printf("%d ", c);
		//你会发现最后打印了个 -1 ——这是因为feof首次尝试读取文件末尾(EOF)后面的内容的时候 feof 才返回非零值
		//如果内存从文件输入数据这么写:fscanf(fp,"%d",&c); 你会发现最后打印了两个 5 —— 我不知道原理,不过这肯定不是我们想要的结果
	}
	fclose(fp);
	return 0;

正确示例

//文本文件
//………………………………(打开文件)
int c;// int 非 char,要求处理 EOF !!!
while((c = fgetc(fp)) != EOF){//读文件中
	putchar(c);
}
//读取结束判断是否正常结束	——我觉得是不是用一个 eof 就够了
if(ferror(fp)) printf("I/O error when reading\n");//读取时出问题了
else if(feof(fp)) printf("End of file reached successfully\n");//成功读取结束
//……………………………(关闭文件)

fscanf 亦是和 EOF 比较

//二进制文件
#define SIZE 5
int main() {
	int src[SIZE] = { 1,2,3,4,5 };
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL) { perror("fopen"); return 1; }
	fwrite(src, 4, 5, pf);
	fclose(pf); pf = NULL;

	int dest[5] = { 0 };
	pf = fopen("test.txt", "rb");
	int ret = fread(dest, 4, 5, pf);
	if (ret == SIZE) {//读取正常结束,打印出来
		for (int i = 0; i < SIZE; i++) { printf("%d ", dest[i]); }
	}
	else {//读取非正常结束
		if (eof(pf)) printf("二进制文档读取错误:文件结尾与预期不同\n");
		else if (ferror(pf)) printf("二进制文档读取错误\n");
	}
	return 0;
}

说来代码真是神奇,不用担心文件中间有 -1 导致判断文件已经结束 —— 至少现在我不知道是为什么

#include<stdio.h>
int main() {
	FILE* fp = fopen("test.txt", "w");
	fprintf(fp, "%d ", -1);
	fprintf(fp, "%d ", -1);
	for (int i = 1; i <= 10; i++) {
		fprintf(fp, "%d ", i);
	}
	fclose(fp);
	
	fp = fopen("test.txt", "r");
	int c = 0;
	while ((c = fgetc(fp)) != EOF) {//读文件中
		putchar(c);
	}
	//读取结束判断是否正常结束	——我觉得是不是用一个 eof 就够了
	if (ferror(fp))  printf("I/O error when reading\n"); //读取时出问题了
	else if (feof(fp))  printf("End of file reached successfully\n"); //成功读取结束
	fclose(fp);

文件缓冲区

没有什么特别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值