C语言笔记16:文件操作

文章目录

C语言笔记16:文件操作

前置知识

文件作用

文件能够将数据持续化保存,按照功能分类可以分为程序文件和数据文件,Linux下路径分隔符为/,windows下为\

二进制文件和文本文件

文本文件和二进制文件的区别在于存储内容和解释方法不同,这点在程序中就能体现,比方说一个"12345"字符串,占用空间6个字节(包括\0),而使用int a = 12345;却只占用4个字节,文本文件存储的内容一般是utf-8或者ASCII等等编码表的编码。

流是一个C标准库级别的抽象概念,和操作系统的文件描述符很相似却又不一样,流具有用户级空间缓冲,fd文件描述符只有内核级缓冲。可以简单理解成流是封装过的fd,而fd是流的底层实现。

文件指针

在<stdio.h>头文件中提供了FILE的定义:

c 复制代码
struct _iobuf {
   char *_ptr;
   int   _cnt;
   char *_base;
   int   _flag;
   int   _file;
   int   _charbuf;
   int   _bufsiz;
   char *_tmpfname;
};
typedef struct _iobuf FILE;

每调用一次fopen都会动态开辟一块空间,在堆上存放这个FILE结构体信息。通过这个FILE结构体可以间接地找到与它所关联的文件。

c 复制代码
#include<stdio.h>

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

文件打开和关闭

c 复制代码
FILE* fopen(const char* filename,const char* mode);
c 复制代码
int fclose(FILE* stream);

fclose返回值表示成功与否

mode

mode 含义 不存在是否新建
"r" 输入 否,打开失败
"w" 输出
"a" 向文件末尾追加内容
"rb" 二进制输入 否,打开失败
"wb" 二进制输出
"ab" 二进制追加
"r+" 读写方式打开
"w+" 读写方式打开
"a+" 文件末尾读写
"rb+" 二进制读写
"wb+" 二进制读写
"ab+" 二进制文件末尾读写

文件顺序读写

函数 功能 适用
fgetc 输入字符 所有输入流
fputc 输出字符 所有输出流
fgets 输入字符串 所有输入流
fputs 输出字符串 所有输出流
fscanf 格式化输入 所有输入流
fprintf 格式化输出 所有输出流
fread 二进制输入 文件输入流
fwrite 二进制输出 文件输出流

为什么二进制输入输出不适用于标准输入输出流呢?标准输入输出流是向终端打印文本信息,使用二进制输入输出可能会产生一些意想不到的后果,比如换行符的解释不同,以及一些控制字符的问题。ctrl + C , ctrl + D

scanf/sscanf/fscanf

printf/sprintf/fprintf之间的区别

scanf从键盘获取内容,sscanf从字符串中获取内容,fscanf从文件中获取内容

printf打印到屏幕,sprintf输出到字符串,fprintf输出到文件。

文件随机读写

fseek

c 复制代码
int fseek(FILE* stream,long int offset,int origin);

从origin开始偏移offset位置

c 复制代码
#include <stdio.h>

int main()
{
	FILE* pfile;
	pfile = fopen("example.txt","wb");
	fputs("This is an apple.",pfile);
	fseek(pfile,9,SEEK_SET);
	fputs(" sam",pfile);
	fclose(pfile);
	return 0;
}

从开始位置偏移9是哪里呢?

光标指向字母n的位置了,从这里开始输出" sam",n被替换成了" ",最终结果是"This is a sample."

ftell

c 复制代码
long int ftell(FILE* stream);

这个返回值和上面的long int offset是一个类型的,说明返回的是偏移量。

使用ftell计算文件大小

c 复制代码
#include <stdio.h>

int main()
{
	FILE* pf = fopen("example.txt","rb");
	
	fseek(pf,0,SEEK_END);
	long int size = ftell(pf);
	fclose(pf);
	printf("%ld\n",size);
	
	return 0;
}

上面程序有个点需要注意,比方说example.txt文件存放内容为:"This is a sample."。那SEEK_END指向的位置是哪里呢?是.吗?还是\0

我们刚刚使用的输出接口是fputs,这是一个专门用于字符串输出的接口,它和fprintf一样,属于**专门用于字符输出的,它们的共同点在于不会将\0输出到文件中,因为\0只是C语言用来表示字符串结束的标志,不属于字符串的有效内容。**那所以SEEK_END指向的位置是.吗?

也不是,SEEK_END实际上指向.后面的EOF。为什么ftell返回SEEK_END相对于文件起始位置的偏移量就是文件的大小而不是文件大小-1?原因就在于SEEK_END并不是指向.而是.后面的EOF
运行结果:

fputs和fprintf都不会向文件中打印\0,那fgets的行为是什么呢?

fgets读到换行符或者第n - 1个字符会停止,然后在末尾加上\0,输入到缓冲区中。也就是会读取\n

fscanf则取决于占位符的类型,它的行为参考scanf的行为。scanf读取内容其实是在缓冲区中读取,比方说输入abcd\n,缓冲区的内容就是abcd\n,如果以%s的形式输入,就会省略\n,\n会被留在缓冲区里面,如果以%c形式输入,则有可能读取到\n。

rewind(倒回)

c 复制代码
void rewind(FILE* stream);

文件读取结束

文件读取结束判断返回值

  • 判断fgetc返回值是否为EOF
  • 判断fgets返回值是否为NULL
  • 判断fscnaf匹配占位符数量是否正确
  • fread判断返回值是否小于要读取的个数

feof

c 复制代码
int feof(FILE* stream);

这是一个极容易误用的函数,原因在于它的实际作用是,当读取文件时已经将EOF读取到后调用该函数才会返回一个非0值
feof实际作用是,文件读取结束时,判断结束原因是否是读取到EOF。或者是ferror(pf)。
举个例子

c 复制代码
#include <stdio.h>

int main()
{
	FILE* pf = fopen("example.txt","rb");
	char buffer[100];
	while(!feof(pf))
	{
		fgets(buffer,sizeof(buffer),pf);
		printf("%s",buffer);
	}
	fclose(pf);
	return 0;
}

假设example.txt内容如下:

当fgets读完第三行内容的时候,此时并没有读到EOF,但是文件实际上已经读完了,再次读取时fgets返回NULL(成功返回传入的缓冲区指针)。但是这里没有判断fgets的返回值,而是判断feof返回值,以为文件还没读完,所以就会出错。

文件缓冲区

使用fflush是刷新到用户级缓冲区中。

相关推荐
MickyCode15 小时前
嵌入式开发调试之Traceback
arm开发·stm32·单片机·mcu
zhangx1234_16 小时前
C语言 数据在内存中的存储
c语言·开发语言
历程里程碑16 小时前
Linux15 进程二
linux·运维·服务器·开发语言·数据结构·c++·笔记
czwxkn16 小时前
3STM32(stdl)外部中断
stm32·单片机·嵌入式硬件
羽获飞16 小时前
从零开始学嵌入式之STM32——6.与GPIO相关的7个寄存器--重要知识
stm32·单片机·嵌入式硬件
嵌入小生00716 小时前
双向链表、双向循环链表之间的异同---嵌入式入门---Linux
linux·c语言·数据结构·链表·嵌入式·小白
棒子陈16 小时前
使用cursor移植单片机的串口驱动(DMA+队列式串口驱动,APM32F103移植到PY32F071)
单片机·嵌入式硬件·cursor·py32f071
dulu~dulu17 小时前
大英赛改错真题记录
笔记·英语·自用·英语改错
BoJerry77717 小时前
数据结构——单链表(不带头)【C】
c语言·开发语言·数据结构
进击的小头17 小时前
设计模式组合应用:智能硬件控制系统
c语言·设计模式