前言
我们知道fopen、fwrite这些函数底层封装了open、write,那我们可以自己用操作系统提供的接口来实现fopen这些函数,简单实现一个Mystdio.h的库。本次内容会封装fopen,fwrite,fflush,fclose,当然FILE类型也需要自己封装,是对前几次知识的综合。 另外还会有个popen,是啥不重要,进程间通信再说。
一、_FILE类型
我们知道FILE类型一定有fd,缓冲区,为了方便这里只搞输出缓冲区,也需要相应的pos记录缓冲区中字符串写到了哪,当然也还需要缓冲类型,全缓冲、行缓冲还是无缓冲。
定义三个宏表示缓冲方式。其余接口均按照库函数标准来的。
cpp
#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__
#include<string.h>
#define FLUSH_NOW 1
#define FLUSH_LINE 2
#define FLUSH_ALL 4
#define SIZE 1024
typedef struct IO_FILE{
int _fileno;
int flag;
//char inbuff[SIZE];
//int in_pos
char outbuff[SIZE]; //输出缓冲区
int out_pos;
}_FILE;
_FILE *_fopen(const char* filename,const char* flag);
int _fwrite(const void *ptr,size_t size,int n,_FILE* fp);
void _fclose(_FILE* fp);
#endif
cpp
#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__
#endif
这个是条件编译,防止头文件被重复包含,一定要加endif.
二、函数实现
_fopen
FILE*是底层给我们malloc出来的,我们也打开文件之后,malloc一个就可以。
至于flag, w就是O_CREAT|O_WRONLY|O_TRUNC ,其他都差不多。
默认给行缓冲了,这里不额外判断。
cpp
_FILE*_fopen(const char* filename,const char* flag)
{
int f = 0;
int fd = -1;
if(strcmp(flag,"w") == 0)
{
f = (O_CREAT|O_WRONLY|O_TRUNC);
fd = open(filename,f,0666);
}
else if(strcmp(flag,"a") == 0)
{
f = (O_CREAT|O_WRONLY|O_APPEND);
fd = open(filename,f,0666);
}
else if(strcmp(flag,"r") == 0)
{
f = (O_RDONLY);
fd = open(filename,f);
}
else return NULL;
if(fd == -1) {
return NULL;
}
_FILE* fp = (_FILE*)malloc(sizeof(_FILE));
if(fp == NULL) return NULL;
fp->_fileno = fd;
fp->flag = FLUSH_LINE;
fp->out_pos = 0;
return fp;
}
_fwrite
fwrite要考虑缓冲方式,进行判断 if(flag & ...) ()
这里没有考虑n,一般传过来的都是1,先拷贝到缓冲区,给out_pos相应的值。
这里行缓冲的方式稍微有点问题,毕竟有可能是一行字符串中间带换行,自行修改即可。
cpp
int _fwrite(const void *ptr,size_t size,int n,_FILE* fp)
{
//先不考虑n
memcpy(&fp->outbuff[fp->out_pos],ptr,size);
fp->out_pos += size;
if(fp->flag & FLUSH_NOW)
{
write(fp->_fileno,fp->outbuff,fp->out_pos);
fp->out_pos = 0;
}
else if((fp->flag & FLUSH_LINE ))
{
if(fp->outbuff[fp->out_pos - 1] == '\n')
{
write(fp->_fileno,fp->outbuff,fp->out_pos);
fp->out_pos = 0;
}
else{
return size;
}
}
else if(fp->flag & FLUSH_ALL)
{
if(fp->out_pos == SIZE)
{
write(fp->_fileno,fp->outbuff,fp->out_pos);
fp->out_pos = 0;
}
else{
return size;
}
}
return size;
}
fflush 和 fclose
fflush相当于进行一次无缓冲写,比较好写。
注意_fclose的时候要进行一次刷新缓冲区,关闭文件、释放自己malloc出来的FILE*
cpp
void _fflush(_FILE* fp)
{
if(fp->out_pos > 0)
{
write(fp->_fileno,fp->outbuff,fp->out_pos);
fp->out_pos = 0;
}
}
void _fclose(_FILE* fp)
{
if(fp == NULL) return;
_fflush(fp);
close(fp->_fileno);
free(fp);
}
测试
cpp
#include<bits/stdc++.h>
#include<unistd.h>
#include<sys/types.h>
#include "Mystdio.h"
using namespace std;
int main()
{
_FILE *fp = _fopen("test.txt","w");
if(fp == NULL) return 1;
const char* msg = "hello world\n";
// _fwrite(fp,msg,strlen(msg));
int cnt = 10;
while(cnt--)
{
_fwrite(msg,strlen(msg),1,fp);
//fflush(fp);
sleep(1);
}
_fclose(fp);
return 0;
}
注意:如果使用makefile生成可执行需要两个文件,一个是测试文件,一个是实现文件。

三、popen
cpp
#pragma once
#include<unistd.h>
#include<cstring>
#include<stdlib.h>
#include<bits/stdc++.h>
#include<cstdio>
FILE* Mypopen(const char* cmd,const char* mod)
{
if(strcmp(mod,"r") != 0) {
return nullptr;
}
int pipe_fd[2];
pid_t pid;
FILE* fp = NULL;
if(pipe(pipe_fd) == -1)
{
perror("create pipe fail") ;
return nullptr;
}
pid = fork();
if(pid < 0)
{
perror("fork fail");
close(pipe_fd[0]);
close(pipe_fd[1]);
return nullptr;
}
if(pid == 0)
{
dup2(pipe_fd[1],STDOUT_FILENO);
close(pipe_fd[0]);
close(pipe_fd[1]);
char* tmp[] = {const_cast<char*>(cmd),nullptr};
execvp(cmd,tmp);
perror("execvp fail");
}
else
{
close(pipe_fd[1]);
fp = fdopen(pipe_fd[0],mod);
if(fp == nullptr)
{
//
return nullptr;
}
}
return fp;
}
具体是干什么的后面再说,进程间通信中会提到。