系统编程学习-文件二

标准IO

流 与 定向 与 缓冲
流:相比于无缓冲IO围绕文件描述符,标准IO库围绕流(stream)进行的
关联:当调用标准IO库创建或打开文件时,就使流和文件关联了
字符:流可用于单字节字符和多字节字符,流的定向 决定了流是单字节还是多字节,当流被创建时,没有定向,在流上使用一个IO函数后,流就被设定为 宽带定向的。改变流定向的函数,freopen(),fwide()

定向:
#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode)
ret: 如果流是宽定向返回正,单字节定向返回-1,未定向返回0;
arg:mode 为负,试图将流设置为字节定向,为正,设置为宽定向,为0不改变
fwide 不能改变已经定向的流
fwide 没有出错返回,要检查,出错,先清除errno,调用后检查errno的值

FILE包括 文件描述符,用于指向流缓冲区的指针,缓冲区长度,缓冲区当前字符数,出错标志
称指向FILE对象的指针称为文件指针

自动定义的流
对应于文件描述符STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO
有三个自动打开的文件指针,stdin,stdout,stderr

缓冲,缓冲的目的是尽可能减少使用read和write的调用次数
三种类型缓冲:
全缓冲:对于磁盘上的文件,通常是全缓冲;在流上第一次执行IO操作时,标准IO函数会调用malloc 获得缓冲区
行缓冲:输入遇到换行符时执行IO,这使得可以fputc(),但是只有当写一行后入才进行实际IO操作。行缓冲限制,行缓冲长度是固定的,如果填满缓冲区,即使还没有写换行符也进行IO。终端设备通常是行缓冲的
无缓冲:标准IO不对字符进行缓冲存储,strerr通常不带缓冲的,
冲洗flush:将缓冲区内容写入。标准IO的冲洗指,将缓冲内容写入磁盘。驱动程序中冲洗指,丢弃缓冲区数据

对于缓冲的设置通常符合以下规律
当标准输入输出不涉及交互,使用全缓冲
标准出错是无缓冲的
设计交互的设备,终端设备是行缓冲,其他是全缓冲

缓冲:
<stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf)
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size)
ret:成功返回0,失败返回-1
使用setbuf设置一个带缓冲IO时,buf的长度必须为BUFSIZ,定义在stdio.h中
setvbuf可以更精确指定所需缓冲类型,用mode实现,_IOFBF,_IOLBF,_IONBF

清除缓冲:
#include<stdio.h>
int fflush(FILE *fp)
ret:成功返回 0,失败返回EOF

读写操作
打开流:
#include <stdio.h>
FILE *fopen(const char* restrict path, const char* restrict type);
FILE *freopen(const char* restrict path, const char* restrict type, FILE *restrict fp);
FILE *fdopen(int fd, const char *type);
ret 成功返回文件指针,出错返回NULL

freopen 通常有两个用途,1.清除流的定向,2.用一个文件作为预定义的流标准输入,输出,出错
fdopen 将描述符与流结合,常用于创建管道,网络通信函数返回的描述符
关于type,是字符串,
"r/rb/"从头读 "w/wb/"从文件头写(trunc creat) "a/ab"(append)
"r+/r+b/rb+"从头读写 "w+/w+b/wb+"从文件头读写(trunc creat) "a+/"
fdopen 的type与其他含义有所不同,使用w时,文件已经打开,不会被截断


限制:
当以读写方式打开文件时(带有+),如果输出后,没有fflush,fseek,fsetpos,rewind,则不能直接跟随输入
如果没有fseek,fsetpos,rewind,或者一个输入操作到达文件尾,则不能直接跟随输出


输入一个字符
int getc(FILE *fp); //这是宏
int fgetc(FILE *fp); //这是函数
int getchar(void); //等价于 getc(stdin)
ret:成功则返回,读取的字符,失败则EOF,
由于不论是出错还是到达文件尾,都返回EOF,需要使用ferror和feof区分这两种情况

判断出错:
#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);

void clearerr(FILE *fp); 清除标记(出错和文件结束);

可以将字符压回流中:
#include<stdio.h>
int ungetc(int c, FILE *fp)
ret:成功则返回c,出错则返回EOF
回送的字符不必是上一次读到的字符。不能回送EOF。调用ungetc会清除文件结束标志
常用于,需要预读取下一个字符才能决定当前字符的处理方式时。
ungetc并没有将将字符写回文件中,只是写回标准IO库的流缓冲区中

输出一个字符
#include<stdio.h>
int putc(int c, FILE *fp); //这是宏
int fputc(int c, FILE *fp); //这是函数
int putchar(int c); //putchar(c) 等效于putc(c, stdout)
ret: 成功返回c,失败则返回EOF


每次一行IO输入
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
ret:成功则返回buf,出错或到达文件尾则返回NULL
对于fgets,最多读取n-1个字符,以null结尾,如果一行没读完,下次接着读取该行
gets不推荐使用,由于无法指定buff长度。
fgets会保留换行符,gets不会

每次一行IO输出
#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
fputs并不要求每次null前必须是换行符,所以可能不会换行

效率:
由于缓冲的存在,标准IO逐个读取字符和逐行读取字符,效率差距不是特别大

二进制IO:
为什么需要二进制IO,fgets 和fputs遇到null会停止,而结构中可能出现null
#include <stdio.h>
size_t fread(void *restrict ptr, size_t size, size_t cnt, FILE *restrcit fp);
size_t fwrite(const void *restrict ptr, size_t size, size_t cnt, FILE *restrict fp);
ret: 返回读写对象的个数

例子:
float data[10];
if(fwrite(&data[2], sizeof(float), 4, fp) != 4) err_sys("fwrite error");

二进制IO分析:
优点,可以处理特殊符号"\n,null",可以直接读写结构
问题,只能读写同一系统上的数据,不同系统可能成员偏移不同,字符的二进制格式也不同,为了解决这个问题,通常使用较高级的协议

定位流:
三种方式:
ftell和fseek
ftello和fseeko
fgetpos和fsetpos 是C标准

#include<stdio.h>
long ftell(FILE *fp);
int fseek(FILE *fp, long offset, int whence);
void rewind(FILE *fp);

#include <stdio.h>
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);


格式化IO
#include <stdio.h>
int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format, ...);
int sprintf(char *restrict buf, const char *restrict format, ...);
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...)
ret:返回存入数组的字符数,出错返回负值


转换说明字符:
以%标志开始
%[flags][fldwidth][prcision][lenmodifier]convtype
flag: -,左对齐输出, +,总是带符号显示,
		(空格),如果第一个字符不是符号,则在其前面加上空格,
		#,指定另一种转换格式,比如十六进制 0x
		0,添加前导0,进行填充
fldwidth: 说明最小宽度,如果转换得到的字符小于他,则用空格填充
precision:精度以‘.’开头,后接一个非负整数,或‘*’,说明数字精度,转换为整数表示位数,转换为小数表示小数点后n位
lenmodifier: hh,h,l,ll,j,z,t,L说明参数长度
convtype:转换类型
	整数: d 十进制,u 无符号十进制, o 八进制,x 十六进制
	浮点数:f double精度, e 指数格式double精度
	字符: c
	字符串: s
	指针: p
	宽字符/宽字符串: C S 等效于 lc ls


格式化输入:
#include <stdio.h>
int scanf(const char *restrict format, ...);
int fscanf(FILE *restrict fp, const char *restrict format, ...);
int sscanf(const char *restrict buf, const char*restrict format,...);
ret:成功时,转化的变量数量, 失败时,返回EOF

转换说明字符:
%[*][fldwidth][lenmodifier]convtype
*表示,会从输入中接收一个匹配的字符,但是不会将其赋值给变量
convtype:
	基本和printf相同。

临时文件
临时文件:
#include<stdio.h>
char *tmpnam(char *ptr)
ret: 返回一个字符串,内容是路径名
最大的调用次数是TMP_MAX,定义在<stdio.h>中
如果ptr是NULL,产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回,下一次再调用,会重写该静态区,
如果ptr不是NULL,则字符数组长度至少为L_tmpnam,定义在<stdio.h>中,生成的路径存放其中

FILE *tmpfile(void)
ret:成功返回文件指针,出错返回NULL
创建一个临时二进制文件类型为(wb+);关闭时文件时自动删除这种文件
tmpfile的实现通常是,调用tmpnam产生一个唯一路径名,然后用该路径创建一个文件,并立刻unlink它,
对文件的unlink不会立刻删除其内容,只有关闭文件时,才删除其内容

#include<stdio.h>
char *tempnam(const char *dir, const char *prefix);
ret:返回唯一路径名指针
该函数允许为产生的文件指明目录和前缀,
目录按照以下条件生成路径名
1.定义了环境变量TMPDIR,则将其作为目录
2.dir不为null,则用其作为目录
3.<stdio.h>中,字符串P_tmpdir作为目录
4.将/tmp作为目录
前缀为最长不超过5个字符的字符串
该函数调用malloc 动态申请空间存放路径,不在使用路径时,要主动调用free释放

#include<stdlib.h>
int mkstemp(char *template);
ret: 成功返回描述符,出错返回-1
template使用路径名,最后六个字符固定为XXXXXX,如果调用成功,则改写XXXXXX,
该文件可读可写
与tempfile不同,mkstemp创建的临时文件不会自动被删除,需要自行unlink

使用tmpnam 和tempnam的缺点在于,返回唯一路径名和用应用程序创建文件之间,有个时间窗口,另一个进程可能创建一个同名文件,
tmpfile和mkstemp函数则不会产生这种问题。

补充

标准IO库的实现:
#include<stdio.h>
int fileno(FILE *fp)
返回与文件指针关联的文件描述符


标准IO库的缺点:
效率不高:存在两次复制,第一次,在内核与标准IO缓冲之间,第二次,标准IO缓冲与用户程序的行缓冲中,
出现了很多提高效率的替代品:fio,sfio,mmap,uClibc,newlibc
posted @   木瓜粉  阅读(47)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示