目录
3.2、fileno()、feof()、ferror()、clearerr()、perror()
一、缓冲文件系统
1.1、目的
▪ 尽量减少使用read/write的调用
1.2、定义
▪ 系统自动的在内存中为每一个正在使用的文件开辟一个缓冲区, 从内存向磁盘输出数据必须先送到内存缓冲区,装满缓冲区在一起送 到磁盘中去。
▪ 从磁盘中读数据,则一次从磁盘文件将一批数据读入到 内存缓冲区中,然后再从缓冲区逐个的将数据送到程序的数据区。
1.3、分类
a、全缓存
▪ 当填满I/O缓存后才进行实际I/O操作,或者满足一定条件后,系统通过调用malloc来获得所需要的缓冲区域
▪ 当缓冲区满了,或者满足一定的条件后,就会执行刷新操作。
b、行缓存
▪ 当在输入和输出中遇到新行符(‘\n’)时,进行I/O操作
c、不缓存
▪ 标准I/O库不对字符进行缓冲,例如stderr。
▪ 很多的人机交互界面要求不可全缓存。
▪ 标准出错决不会是全缓存的。
d、在任何时刻,可以使用fflush强制刷出缓存区
二、EOF,stdin、stdout、stderr
extern FILE *stdin; 标准输入
extern FILE *stdout; 标准输出
extern FILE *stderr; 标准错误
//EOF end of file 文件结束符
三、标准IO函数
3.1、fopen()、fclose()
a、函数定义
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
int fclose(FILE *stream);
b、参数含义
▪ pathname:路径
绝对路径:"D:/test01/a.txt"
相对路径 "./a.txt"
▪ mode:访问方式
w write 只写 文件不存在则创建,文件存在则清空
r read 只读 不能创建
a apped 以追加的方式写 文件不存在则创建
b binary 以二进制的方式操作 wb rb ab
+ 可读可写 w+ r+ a+
c、 注意事项
- fopen():成功返回文件指针,失败返回NULL
- fclose():成功返回0,失败返回EOF
- 在该文件被关闭之前,刷新缓存中的数据。
- 当一个进程正常终止时,则所有带未写缓存数据的标准I / O流都被刷新,所有打开的标准I / O流都被关闭。
- 在调用fclose()关闭流后对流所进行的任何操作,包括再次调用fclose(),其 结果都将是未知的
3.2、fileno()、feof()、ferror()、clearerr()、perror()
a、函数定义
#include <stdio.h>
int fileno(FILE *stream);
void clearerr(FILE *stream);
int feof(FILE *stream);
int ferror(FILE *stream);
void perror(const char *s);
b、注意事项
- fileno()、把标准文件描述符转为文件IO描述符 返回文件IO描述符
- feof()检测文件结束符,
如果文件结束,返回非0值,否则返回0(一般先读取再判断)
- ferror()检测错误标识符,如果有返回非0值
,否则返回0 (如:读取只写;返回非0值)
clearerr()清除文件结束符和
错误标识符 且这两个只能由其清除- perror()在错误信息打印之前,输出自定义信息
3.3、getc()、getchar()、fgetc()
a、函数定义
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
b、 注意事项
- 三个函数都是从流中获取一个字符、返回值为int型
- 成功返回获取的字符,若已处于文件尾端或出错则返回EOF,要区分这两种不同的情况,必须调用ferror()或feof()。
- getc()等价于fgetc(),只是getc()的实现是一个宏,而fgetc()是一个函数。
- 函数getchar()等同于getc(stdin)
3.4、putc()、fputc()、putchar()
a、函数定义
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
b 、注意事项
- 成功返回写入的字符,失败返回EOF
- putc()等价于fputc(),只是putc()的实现是一个宏,而fputc()是一个函数。
- putchar('c')等价于putc('c',stdout)。
3.5、puts()、fputs()
a、函数定义
#include <stdio.h>
int puts(const char *s);
int fputs(const char *s, FILE *stream);
b、注意事项
- 成功返回非负数(不是写入的字符数),失败返回EOF
- 两个都是遇到 \0 就停止写入,不会将 \0 写入 , 遇到 \n 不会停止写入
- fputs不会主动添加\n符号到目标流内()
- puts会主动添加\n符号到标准输出中
3.6、gets()、fgets()
a、函数定义
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
b、注意事项
- 成功返回缓存的首地址(即 s),若已处文件尾端或出错则为null
- fgets最多读取size-1个字符 并在后面加上 \0 ,当遇到 \n 会结束读取,并将 \n 写入
- 不推荐使用 gets(),因为gets()不能指定缓存的长度,这样就可能造成缓存越界;
3.7、fread()、fwrite()
a、函数定义
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
b、参数含义
- ptr:要读/写内容的首地址
- size:一般为ptr所指向类型的大小
假如: char a[10]; char *ptr = a; 则size = sizeof(char)
size和nmemb不固定,只要 size*nmemb = sizeof(a) 就行
- nmemb:要读/写的条目数
- stream:要读/写的文件
c、注意事项
- 成功返回读/写入的 nmemb 的值(不一定为nmemb),错误或到达文件末尾返回0
- 可以以二进制的方式读/写
3.8、fprintf()、 fscanf()
a、函数定义
#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
b、注意
- 成功返回写入的字节数,失败返回 EOF
- prinf() == fprintf(stdout,)
- scanf() == fscanf(stdin,)
3.9、fseek()、ftell()、rewind()
a、函数定义
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
- fseek
用于重置stream流的位置,调用成功返回0,失败返回 -1
offset:偏移量,每一读写操作所需要移动的距离,单位是字节的 ,可正可负(向前移,向后移)
whence :
SEEK_SET:当前位置为文件的开头,新位置为偏移量 的大小。
SEEK_CUR:当前位置为文件指针的位置,新位置为 当前位置加上偏移量。
SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小。
- ftell
用于获取当前文件位置,调用成功返回当前偏移量。失败返回 -1
- rewind
将stream 流设置在文件开头 无返回值
相当于 fseek(stream, 0L, SEEK_SET)
四、一些简单实践
4.1、文本文件的拷贝
int FileCopy(const char *src_path,const char*dest_path){
//获取源文件的文件指针
FILE *src_fd = fopen(src_path,"r");
if(src_fd == NULL){
perror("open src ");
return -1;
}
//获取目标文件的文件指针
FILE *dest_fd = fopen(dest_path,"w");
if(dest_fd == NULL){
perror("open dest ");
return -1;
}
/*利用标准IO的字符IO,实现2个文件拷贝*/
// fputc(fgetc(src_fd),dest_fd);
// while (!feof(src_fd)){
// fputc(fgetc(src_fd),dest_fd);
// }
/*利用标准IO的行IO,实现2个文件拷贝*/
// char buf[1024];
// while( fgets(buf, sizeof(buf),src_fd) != NULL){
// fputs(buf,dest_fd);
// }
/*利用标准IO的块IO,实现2个文件拷贝*/
char buf[1024];
unsigned long ret = 0;
ret = fread(buf, 1,sizeof(buf),src_fd);
while( ret != 0){
fwrite(buf, 1,ret,dest_fd); //读到多少就写入多少
printf("%ld\n",ret);
ret = fread(buf, 1,sizeof(buf),src_fd);
}
fclose(src_fd);
fclose(dest_fd);
return 0;
}
4.2、打印日志文件
编程读写一个文件 test.txt,每隔 1 秒向文件中写入一行数据,类似这样
0001, 2022-05-08 15:16:42
0002, 2022-05-08 15:16:43
该程序应该无限循环,直到中断程序。下次再启动程序写文件时可以追加到原 文件之后,并且序号能够接续上次的序号,比如:
0001,2022-05-08 15:16:42
0002, 2022-05-08 15:16:43
0003, 2022-05-08 15:19:02
0004, 2022-05-08 15:19:03
0005, 2022-05-0815:19:04
void PrintLogData(const char *path){
//追加的方式获取文件描述符
FILE *fd = fopen(path,"a+");
if(fd == NULL){
perror("open ");
return;
}
//获取行号
int num = 0;
char buf[1024];
while(fgets(buf, sizeof(buf),fd) !=NULL){
num++;
}
//重置一下fd
fseek(fd,0,SEEK_CUR);
//往日志文件写数据
int year,mon,day,hour,min,sec;
while(num >= 0){
//获取当前时间
time_t now;
struct tm *tm_now;
time(&now);
tm_now = localtime(&now);
year = tm_now->tm_year+1900;
mon = tm_now->tm_mon+1;
day = tm_now->tm_mday;
hour = tm_now->tm_hour;
min = tm_now->tm_min;
sec = tm_now->tm_sec;
//%04 表示占4位,不足4位 在前面补0
fprintf(fd,"%04d, %02d-%02d-%02d %02d:%02d:%02d\n"
,num,year, mon,day,hour,min,sec);
fflush(fd);//强制刷出缓存
//在控制台实时打印
fprintf(stdout,"%04d, %02d-%02d-%02d %02d:%02d:%02d\n"
,num,year, mon,day,hour,min,sec);
sleep(1);
num++;
}
fclose(fd);
}
4.3、查看文件的大小
long SizeOfFile(const char *path){
long size = 0L;
FILE *fd = fopen(path,"r");
fseek(fd,0,SEEK_END);//偏移到文件末尾
size = ftell(fd);
fclose(fd);
return size;
}
4.4、完整测试代码
#include <stdio.h>
#include <time.h>
#include <unistd.h>
//文本文件拷贝
int FileCopy(const char *src_path,const char*dest_path);
//输出日志文件
void PrintLogData(const char *path);
//获取文件大小
long SizeOfFile(const char *path);
int main() {
char* src_path="./src.txt";
char* dest_path="./dest.txt";
char* log_path = "log.txt";
// PrintLogData(log_path);
// FileCopy(src_path,dest_path);
// printf("%ld\n", SizeOfFile(src_path));
return 0;
}
int FileCopy(const char *src_path,const char*dest_path){
//获取源文件的文件指针
FILE *src_fd = fopen(src_path,"r");
if(src_fd == NULL){
perror("open src ");
return -1;
}
//获取目标文件的文件指针
FILE *dest_fd = fopen(dest_path,"w");
if(dest_fd == NULL){
perror("open dest ");
return -1;
}
/*利用标准IO的字符IO,实现2个文件的字节拷贝*/
// fputc(fgetc(src_fd),dest_fd);
// while (!feof(src_fd)){
// fputc(fgetc(src_fd),dest_fd);
// }
/*利用标准IO的行IO,实现2个文件的字节拷贝*/
// char buf[1024];
// while( fgets(buf, sizeof(buf),src_fd) != NULL){
// fputs(buf,dest_fd);
// }
/*利用标准IO的块IO,实现2个文件的字节拷贝*/
char buf[1024];
unsigned long ret = 0;
ret = fread(buf, 1,sizeof(buf),src_fd);
while( ret != 0){
fwrite(buf, 1,ret,dest_fd); //读到多少就写入多少
printf("%ld\n",ret);
ret = fread(buf, 1,sizeof(buf),src_fd);
}
fclose(src_fd);
fclose(dest_fd);
return 0;
}
void PrintLogData(const char *path){
//追加的方式获取文件描述符
FILE *fd = fopen(path,"a+");
if(fd == NULL){
perror("open ");
return;
}
//获取行号
int num = 0;
char buf[1024];
while(fgets(buf, sizeof(buf),fd) !=NULL){
num++;
}
fseek(fd,0,SEEK_CUR);
//往日志文件写数据
int year,mon,day,hour,min,sec;
while(num >= 0){
//获取当前时间
time_t now;
struct tm *tm_now;
time(&now);
tm_now = localtime(&now);
year = tm_now->tm_year+1900;
mon = tm_now->tm_mon+1;
day = tm_now->tm_mday;
hour = tm_now->tm_hour;
min = tm_now->tm_min;
sec = tm_now->tm_sec;
//%04 表示占4位,不足4位 在前面补0
fprintf(fd,"%04d, %02d-%02d-%02d %02d:%02d:%02d\n"
,num,year, mon,day,hour,min,sec);
fflush(fd);//强制刷出缓存
fprintf(stdout,"%04d, %02d-%02d-%02d %02d:%02d:%02d\n"
,num,year, mon,day,hour,min,sec);
sleep(1);
num++;
}
fclose(fd);
}
long SizeOfFile(const char *path){
long size = 0L;
FILE *fd = fopen(path,"r");
fseek(fd,0,SEEK_END);//偏移到文件末尾
size = ftell(fd);
fclose(fd);
return size;
}