文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );
对比一组函数:
scanf/fscanf/sscanf
printf/fprintf/sprintf
int fseek( FILE *stream, long offset, int origin );
//第一个参数:流
//第二个参数:偏移量
//第三个参数:->
//SEEK_CUR:文件指针指在当前位置
//SEEK_SET:文件指针指在文件开始
//SEEK_END:文件指针指在文件结束
long ftell( FILE *stream );
//参数是流
//返回:相对于起始位置的偏移量。
void rewind( FILE *stream );
//参数:流
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
fgetc 判断是否为 EOF .
fgets 判断返回值是否为 NULL .
#include
#include
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if(!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
}
二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数。
#include
enum { SIZE = 5 };
int main(void)
{
double a[SIZE] = {1.,2.,3.,4.,5.};
FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式
fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组
fclose(fp);
double b[SIZE];
fp = fopen("test.bin","rb");
size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组
if(ret_code == SIZE) {
puts("Array read successfully, contents: ");
for(int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
putchar('\n');
} else { // error handling
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
}
fclose(fp);
}
1,空文件 ,也要在磁盘占用空间。(文件属性值也有空间)
2,文件 = 文件内容 + 文件属性
3,文件操作 = 对内容的操作 or 对属性的操作 or 对内容和属性的操作
4,文件标定:必须要使用:文件路径+文件名称(唯一性)
5,如果没有指明对应的文件路劲,默认是在当前路劲进行文件的访问。
6,这里的当前路径是进程当前路径。(chdir可修改进程当前路径)
7,当我们把fopen,fclose,fread,fwrite等接口写完后,编译成可执行程序,但是没有运行,文件的操作也是没有被执行的。
8所以对文件的操作是进程对文件的操作。
9,一个文件如果没有被打开,不能被直接访问。
10,换句话说,一个文件要被访问,首先必须先被打开。是被进程+OS打开的。进程调用OS接口,OS打开文件。
11,所以,文件操作的本质就是: 进程 和 被打开文件 的关系。
1,不仅c语言有文件操作接口,c++,java,python,php,go 等语言都有文件操作接口(函数)。并且接口都不一样。
2,文件在磁盘中,属于外设的一种,想访问磁盘就绕不开OS,所有语言都必须使用OS提供的接口,来访问文件。
3,OS只有一个,说以,无论上层语言怎么变化,OS调用接口都是一样的。语言的库函数可以千变万化,但是底层调用的OS文件接口是一样的。
上面我也已经复习了c语言的文件操作。建议好好复习一下再看,下面内容。
#include
#include
#include
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
//pathname: 要打开或创建的目标文件
//flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
//参数:
//O_RDONLY: 只读打开
//O_WRONLY: 只写打开
//O_RDWR : 读,写打开
//这三个常量,必须指定一个且只能指定一个
//O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
//O_APPEND: 追加写
//O_TRUNC : 清空文件。
//返回值:
//成功:新打开的文件描述符
//失败:-1
在C语言中fopen我们打开文件返回的是FILE* (文件指针)。
open打开文件后返回一个文件描述符。
上面的flags就是标记位传参,什么是标记为传参呢?
我们知道一个整数有32个比特位,我们可以通过比特位来传递选项,不同的比特位,代表不同的选项。
一个比特位,代表一个选项
如何使用比特位传递选项?
//一个宏对应一个比特位,彼此不重叠
#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
void fun(int flags){
if(flags & ONE) printf("one\n");
if(flags & TWO) printf("two\n");
if(flags & THREE) printf("three\n");
if(flags & FOUR) printf("four\n");
}
int main()
{
fun(ONE);
printf("---------------------\n");
fun(ONE|TWO);
printf("---------------------\n");
fun(ONE|TWO|THREE);
printf("---------------------\n");
fun(ONE|TWO|THREE|FOUR);
return 0;
}
这就是标记为传参,可以合理运用‘&’ 和 ‘ | ’可以实现参数的合理传递。
打开文件open的参数标记位使用
O_WRONLY:只读打开
O_CREAT:不存在则创建,必须传入第三个参数mode,指明初始权限。
默认使用系统中的umask,也可以调用系统接口umask自己指定.
不同的打开方式写入的时候会有不同的效果
#include
ssize_t write(int fd, const void* buf, size_t count);
//第一个参数:
//fd要写入文件的文件描述符
//第二个参数:
//buf:写入数据的来源(void*类型不分文本还是二进制)
//第三个参数
//写入长度
//返回值
//写入的字节个数
在C语言中我们以w方式打开的时候,会自动清空文件
清空文件(O_TRUNC)
假如程序没有输入O_TRUNC ,写入的时候是直接覆盖式的写入
看到文件的后面不是我们想要的结果。
加上O_TRUNC才是我们想要的结果
追加打开(O_APPEND)
#include
ssize_t read(int fd,void* buf, size_t const);
//参数
//buf读取数据存放的位置.
//count希望读取的字节数(可能读不到那么多文件就结束了)
//返回值
//ssize_t是系统定制的类型,本质式 long int 类型
//读取到的数据的实际字节数
//返回-1 就是错误.
C语言中 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口
可以认为库函数接口都是对系统接口的封装
通过对open函数的学习,我们知道了文件描述符就是一个小整数
系统中同时会被打开很多的文件,被打开的文件,要被管理起来,OS会先描述再管理.所OS为了管理对应的打开文件,必须要位文件创建对应的内核数据结构.来表示文件.
这个内核结构体叫struct file{ },内部包含了文件的属性。
上面我们发现一个现象:我们打开的文件描述符都是3, 那1 & 2 & 0,去哪里了
所以文件描述符就是进程中文件描述符表的下标。文件描述符表是一个指针(struct_file*)数组,指向OS打开文件的struct_file对象,一个下标对应一个文件。
Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
0,1,2对应的物理设备一般是:键盘,显示器,显示器
所有输入输出还可以这样表示:
#include
#include
#include
#include
#include
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file(struct_file)结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件
FILE是一个结构体,是C语言对文件描述符的封装.
里面一定有一个字段是文件描述符fd.
stdin->_fileno
上面介绍我们知道,系统会默认的打开三个文件描述符占据0 & 1 & 2 三个位置,我们自己的文件打开后默认都是按照3,4,5---,一直往后面排的。
先来一个小实验
发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在fifiles_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
上面我们关闭的是标准输入,标准错误,那如果关闭1呢?我们把显示器都关闭了,会有什么效果呢?
看代码:
重定向的本质:上层用的fd不变(printf用的fd ==1),内核更改fd==1对应的struct_file* 地址。
此时,我们发现,本来应该输出到显示器上的内容,输出到了新打开的文件当中,其中,fd=1。这种现象叫做输出重定向。
常见的重定向有:
输出重定向:>
追加重定向: >>
输入重定向: <
int dup2 (int oldfd, int newfd);
如果重定向成功返回newfd的值,所以此函数就是把 oldfd下标对应的指针,拷贝给newfd下标对应的位置。并返回newfd的值。重定向失败返回-1。
重定向分为:
输出重定向:>
追加重定向: >>
输入重定向: <
//我们通过父进程创建子进程的时候,子进程会拷贝进程的文件描述符表,不会父进程公用
//父进程打开的文件不会被子进程拷贝过去,只是拷贝三个默认的文件(0 & 1 & 2)。
//我们对子进程进行重定向不会影响父进程。
//进程具有独立性
//进程的程序替换的时候,不会影响曾经进程打开的文件和重定向的文件。
//进程替换的是代码和数据,进程的文件描述符表和pcb(tast_struct)是内核数据结构。是OS管理的
//进程替换的时候不会影响他们。
源码:(可能不完善,大家可以自己完善完善)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//宏函数
#define trimSpace(start) do{\
while (isspace(*start)) start++; \
}while (0)
//'\'是续行符。
//再内核中经常使用do { } while(0) ,来包含一个语句块。
//isspace () 是判断是不是空格的意思,是空格返回真
#define NUM 1024
//标记
#define NONE 0 //没有重定向
#define INPUT 1 //输入重定向
#define OUTPUT 2 //输出重定向
#define APPEND 3 // 追加重定向
char linecommand[NUM];
char* argv_[60];
int lastcode = 0;
int lastsig = 0;
int redirTYpe = NONE;//重定向方式
char* redirFile = NULL;//重定向的文件名称
//重定向命名行处理
void commandCheck(char* commands)
{
assert(commands);
char* start = commands;
char* end = commands + strlen(commands);
while (start < end)
{
if (*start == '>')
{
*(start) = '\0';
start++;
if (*start == '>')
{
start++;
redirTYpe = APPEND;
}
else {
redirTYpe = OUTPUT;
}
trimSpace(start);
redirFile = start;
break;
}
else if (*start == '<')
{
*start = '\0';
start++;
//去空格
trimSpace(start);//宏函数
redirFile = start;
redirTYpe = INPUT;
break;
}
else {
start++;
}
}
}
int main()
{
while (1)
{
//对全局变量进行初始化
redirFile = NULL;
redirTYpe = NONE;
//打印提示符
printf("用户名@主机名 当前路劲#");
fflush(stdout);
//获取用户输入
char* str = fgets(linecommand, NUM - 1, stdin);
assert(str != NULL);
(void)str;
//清楚最后一个\n的字符,
//因为我们输入完成后会敲入一个回车键,回车也会被获取到linecommand中
linecommand[strlen(linecommand) - 1] = '\0';//消除最后的回车键
//printf("%s\n",linecommand);//获取成功
//处理输出重定向 >
//追加重定向 >>
//输入重定向 <
//以前我们读取的命令行一般是 ls -a -l -i
//假如有了重定向符号 就变成了 ls -a -l -i > test.txt
//这就要我们加以处理了,怎么把输出结果 重定向到tets.txt 里面 (运用dup2函数)
commandCheck(linecommand);
//分割linecommand,
argv_[0] = strtok(linecommand, " ");
int i = 1;
//处理ls的颜色和缩写
if (strcmp(argv_[0], "ll") == 0) { argv_[0] = (char*)"ls"; argv_[i++] = (char*)"-l"; }
if (strcmp(argv_[0], "ls") == 0) { argv_[i++] = (char*)"--color=auto"; }
while ((argv_[i++] = strtok(NULL, " ")) != NULL);
//处理cd 和 echo这样的内建命令
if (argv_[0] != NULL && strcmp(argv_[0], "cd") == 0) {
if (argv_[1] != NULL)chdir(argv_[1]);
continue;
}
if (argv_[0] != NULL && argv_[1] != NULL && strcmp(argv_[0], "echo") == 0 && getenv(((char*)argv_[1]) + 1) == NULL) {
if (strcmp(argv_[1], "$?") == 0)
printf("code:%d sig:%d\n", lastcode, lastsig);
else
printf("%s\n", argv_[1]);
continue;
}
//创建子进程,进行程序替换
pid_t id = fork();
assert(id != -1);
if (id == 0)//重定向的工作一定是子进程完成的
//但是如何重定向,是父进程要给子进程提供信息的。
{
//开始重定向
//重定向不会影响父进程。
//进程具有独立性
switch (redirTYpe)
{
case NONE://没有重定向
break;
case INPUT://输入重定向
{
int fd = open(redirFile, O_RDONLY);
if (fd < 0)
{
perror("open");
exit(errno);
}
fd = dup2(fd, 0);
}
break;
case OUTPUT://输出重定向
{
int fd = open(redirFile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
perror("open");
exit(errno);
}
fd = dup2(fd, 1);
break;
}
case APPEND://追加重定向
{
int fd = open(redirFile, O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd < 0)
{
perror("open");
exit(errno);
}
fd = dup2(fd, 1);
break;
}
default:
printf("error & bug !!1\n");
}
execvp(argv_[0], argv_);
exit(1);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);//阻塞等待
//进程的程序替换的时候,不会影响曾经进程打开的文件和重定向的文件
//进程替换的是代码和数据,进程的文件描述符表和pcb(tast_struct)是内核数据结构。是OS管理的
//进程替换的时候不会影响他们。
assert(ret > 0);
(void)ret;
lastcode = ((status >> 8) & 0xff);
lastsig = (status & 0x7f);
}
}
printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了myfifile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成重定向。
一张图告诉你为什么在Linux下,任何硬件都可以当作文件看待。
一般将各种硬件对应的struct_file那一层称作VFS 虚拟文件系统。
有兴趣的可以去看看源码:在stat_struct(pcb)里面,肯定有一个struct file_struct* files;
在struct file_struct* files 里面有一个数组是 struct file* fd_array [ ]
这就是进程的文件描述符表。不同的机器数组长度不同。
struct file里面有各种各样的属性,
1, 引用计数(有多少指针指向此文件),所以我们在关闭文件的是只是引用计数--;当的引用计数减到0的时候,OS会关闭此文件。
2, f_mode文件权限
3, f_pos文件的写入位置
......
我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!
1,一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
2,printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据
的缓冲方式由行缓冲变成了全缓冲。而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
但是进程退出之后,会统一刷新,写入文件当中。
3,但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
4,write 没有变化,说明没有所谓的缓冲。
printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。
缓冲区本质就是一块内存!!就是内存中的一块内存区。
结论:以上的演示的缓冲区是语言级别的缓冲区,是C语言标准库提供的,在系统调用之上。然后通过一系列的刷新策略,将缓冲区的数据刷新到磁盘。
我们知道进程把数据拷贝到缓冲区,缓冲区并不是你拷贝一次我就刷新一次,这样和没有缓冲区是一样的效果,所以缓冲区数据积累到一定的大小,才刷新,写入磁盘。
缓冲区一般是多次写入,尽量少的刷新,因为IO的过程很慢。例如IO的过程有99%的时间在等待磁盘就绪。
缓冲区会结合具体的设备,定制自己的刷新策略。通常C语言的缓冲区有三种刷新方式。
a,立即刷新---无缓冲
b,行刷新 ----行缓存 ---- 显示器
c,缓冲区满 ---全刷新 -----磁盘文件
还有两种特殊情况:
d,用户强制刷新
e,进程退出 --一般都要进行缓冲区刷新。
从上文的案例中我们发现,C语言缓冲区不在内核中,所以我们以前谈到的缓冲区都是指的语言层面给我们提供的缓冲区
这个缓冲区和文件有关,因为Linux一切皆文件,在用户看来,所有的外设都是文件,都要文件描述符fd,在语言层面,fd会被封装为FILE的一个结构体,这是语言层面和IO有关的结构。
所以:FILE 里面有fd && 缓冲区
这就解释了为什么,我们fflush(文件指针)要传入一个文件指针,flclose(文件指针)也要传入一个文件指针,因为fflush 和 fclose 都要刷新缓冲区而缓冲区就在文件指针FILE中。
我们可以打开c标准库中找到对应的FILE 结构体查看一下。
typedef struct _IO_FILE FILE;
在/usr/include/libio.h中可以找到。
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
所以我们现在,就可以很清楚的上文案例中出现现象。
在第二种情况下,我们看到在没有重定向(>) 的时候打印的是一份,当我们重定向之后,C语言库函数打印两份,系统调用打印一份。
1,stdout默认使用的行刷新,三个函数拷贝到缓冲区的数据都带了\n,每一条都直接刷新了缓冲区,在子进程创建前,FILE的缓冲区的数据已经被刷新到显示器(外设)了,在FILE的缓冲区内部已经不存在数据了,
2,如果我们进行了重定向(>) ,写入文件不再是显示器,而是普通磁盘文件,普通的磁盘文件,刷新策略是缓冲区满刷新(全刷新),之前的三个函数虽然带了\n,但是不足以将stdout缓冲区放满,数据没有刷新。!!
3,创建子进程的时候,stdout属于都进程,创建子进程后,子进程和父进程都会刷新自己stdout对应的缓冲区,谁先退出,谁就一定要先进行缓冲区的刷新,(刷新缓冲区的本质就是:修改FILE的缓冲区中对应的数据,将数据写入文件,就是修改缓冲区的数据)。此时一定会发生写时候拷贝。数据最终会显示两份。
4,为什么write没有呢?上面的过程和wirte无关,wirte没有FILE,而用的是fd,就没有C语言库提供的缓冲区。
#include
#include
#include
#include
#include
#include
#include
#include
#define SIZE 1024
#define NOW 1 //立即刷新
#define LINE 2 //行刷新
#define FULL 4 //满刷新
typedef struct _FILE {
int flags;//刷新方式
int fileno;//文件描述符
char buf[SIZE];//缓冲区
int size;//存入的字符数
int cap;//buf最大容量
}_FILE;
_FILE* _fopen(const char* path_name, const char* mode);
void _fclose(_FILE* fp);
void _fwrite(const char* ptr, size_t num, _FILE* fp);
void _fflush(_FILE* fp);
//以上接口可能和C语言库函数中的不一样,是我为了让自己更好理解,进而简化过的接口
#include "test.h"
_FILE* _fopen(const char* path_name, const char* mode)
{
int fd = -1;
int flag = 0;
if (strcmp(mode, "r") == 0)
{
flag |= O_RDONLY;
}
else if (strcmp(mode, "w") == 0)
{
flag |= (O_WRONLY | O_CREAT | O_TRUNC);
}
else if (strcmp(mode, "a") == 0)
{
flag |= (O_WRONLY | O_APPEND | O_CREAT);
}
else {
//NULL;
}
if (flag & O_CREAT) fd = open(path_name, flag, 0666);
else fd = open(path_name, flag);
if (fd < 0) {
//文件打开失败
write(2, strerror(errno), strlen(strerror(errno)));
return NULL;
//这就是为什么文件打开失败会返回NULL
}
_FILE* fp = (_FILE*)malloc(sizeof(_FILE));
assert(fp != NULL);
fp->fileno = fd;
fp->flags = LINE;
//打开不同的文件这个flag需要填入不同的数据
//需要我们判断一下,但是今天我们就不写那么复杂了
//演示行刷新就行。
fp->size = 0;
fp->cap = SIZE;
memset(fp->buf, 0, SIZE);//给buf初始化为0
return fp;
}
void _fflush(_FILE* fp)
{
//fflush就是把缓冲区的文件刷新到磁盘文件。
//并且清空缓冲区
if (fp->size > 0)
{
write(fp->fileno, fp->buf, fp->size);
fsync(fp->fileno);//强制刷新内核缓冲区。
fp->size = 0;
}
}
void _fclose(_FILE* fp)
{
//语言层面的关闭文件就是刷新缓冲区,然后关闭文件
_fflush(fp);
close(fp->fileno);
}
void _fwrite(const char* ptr, size_t num, _FILE* fp)
{
//C语言库函数写入的本质是:将数据拷贝到FILE结构中的缓冲区buf中
//然后更具不同的刷新规则检查释放需要将,缓冲区刷出到磁盘文件。
memcpy(fp->buf + fp->size, ptr, num);
fp->size += num;
//这里简化不考虑缓冲区溢出的问题。
//如果想考虑就需要判断大小,其实也不难
//今天是为了演示原理。
//根据不同的刷新方式检查是否刷新
if (fp->flags & NOW)
{
_fflush(fp);
}
else if (fp->flags & LINE)
{
if (fp->buf[fp->size - 1] == '\n')
{
_fflush(fp);
}
//这里并不完善,例如:assd\nasda 这种情况就会出问题
//但是今天不考虑
}
else if (fp->flags & FULL)
{
if (fp->size == fp->cap)
{
_fflush(fp);
}
}
else {
//error
}
}
测试用例1:
测试用例2:
其实在OS内核中,为了效率,也存在一个内核缓冲区,它全权由操作系统控制。
作用就是:强制刷新内核缓冲区。将数据刷新到磁盘文件/各种硬件。
不做过多的解释,这个接口用于,比较重要的IO数据。