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是刷新到用户级缓冲区中。

相关推荐
wdfk_prog9 小时前
[Linux]学习笔记系列 -- 底层CPU与体系结构宏
linux·笔记·学习
逑之9 小时前
C语言笔记15:动态内存管理
c语言·网络·笔记
zfxwasaboy9 小时前
DRM KMS 子系统(3)CRTC
linux·c语言
im_AMBER9 小时前
Leetcode 100 在链表中插入最大公约数
数据结构·c++·笔记·学习·算法·leetcode·链表
今儿敲了吗9 小时前
计算机网络第三章笔记(二)
笔记·计算机网络
2401_863326119 小时前
基于单片机智能光控路灯设计
单片机·嵌入式硬件
逑之10 小时前
C语言笔记12:C语言内存函数
c语言·笔记·算法
清风66666610 小时前
基于单片机的球类比赛专用计分与暂停管理系统设计
单片机·嵌入式硬件·毕业设计·课程设计
ltqshs10 小时前
嵌入式C语言-指针数组和数组指针
c语言·数据结构·算法