Liunx下用C语言模拟实现 —— 封装文件操作接口

一、前提

本篇博客将循序渐进的模拟实现C语言中,简单版的对FILE和一些简单的文件操作接口的实现,在上一篇文件操作原理篇我们知道,实际的实现就是对系统接口的封装,为了巩固和练习之前学到的概念,我们也来简单的实现对接口的封装设计,要是对接口不太熟悉或者对原理还不太清楚,可以参考我上一篇的内容整理,链接:

http://t.csdnimg.cn/l5KbAicon-default.png?t=N7T8http://t.csdnimg.cn/l5KbA

二、代码设计

1.前提准备

先创建好一个工程的基本模版,采用声明和定义分离的方式,先创建好基本的文件

main.c 、 mystdio.c 、mysdtio.h 、 makefile

然后把makefile写好

myfile:main.c mystdio.cgcc -o $@ $^.PHONY:clean
clean:rm -f myfile

2.头文件的设计

首先,我们的目的是自己封装一个FILE类型(简单版)去实现一些简单的文件操作c接口,所以先设计一下头文件中的声明

我们需要自己封装一个MY_FILE类型的结构体,里面至少要有fd,还有一个缓冲区,以及管理缓冲区内容的参数,还有就是缓冲区的刷新方式

然后就是相关的接口声明,这里简单实现三个基本接口,fopen、fwrite、fclose

最后是我们一定会用到的一个接口,fflush

#pragma once#include<stdio.h>
//缓冲区大小
#define NUM 1024
//刷新策略
#define BUFF_NONE 0x01
#define BUFF_ROW 0x02
#define BUFF_ALL 0x04typedef struct MY_FILE
{int _fd;char _buff[NUM];//缓冲区int _buffsize;int _flags;//刷新策略
}MY_FILE;MY_FILE* my_fopen(const char* path,const char *mode);
size_t my_fwrite(const void* ptr,size_t size,size_t nmemv,MY_FILE* stream);
int my_fclose(MY_FILE *fp);
int my_fflush(MY_FILE* fp);

3.接口实现

(1)my_fopen

我们本质要做的是对open函数的封装,因此在代码设计上,首先我们要比较我们一下fopen的参数和open的参数对比

fopen的使用是给路径和使用方式参数

open则是要给路径,并且带上标志位表示以哪种使用方式打开,然后是在创建新文件的时候要给到权限码

因此我们要做的就是根据fopen传的第二个参数,将其转换成open认识的参数形式,这里模拟实现简单一点,只实现三个最常见的“r”、“w”、“a”,我们用一个flags去根据不同情况去选择标志位,最后,再判断一下是否需要创建新的文件即可

MY_FILE* my_fopen(const char* path,const char* mode)
{int flag = 0;//标志位//根据不同情况记录标志位if(strcmp(mode,"r") == 0) flag |= O_RDONLY;else if(strcmp(mode,"w") == 0) flag |= (O_CREAT | O_WRONLY | O_TRUNC);else if(strcmp(mode,"a") == 0) flag |= (O_CREAT | O_WRONLY | O_APPEND );else{// other operator...}//默认权限码0666;mode_t m = 0666;//打开文件int fd = 0;if(flag & O_CREAT) fd = open(path,flag,m);else open(path,flag);//打开失败if(fd < 0) return NULL;//打开后需要设计返回值,FILE,初始化MY_FILE* ret = (MY_FILE*)malloc(sizeof(MY_FILE));if(ret == NULL){close(fd);//这里如果malloc失败,记得关闭文件perror("malloc error");return NULL;}ret->_fd = fd;ret->_flags = BUFF_ROW;memset(ret->_buff,'\0',sizeof(ret->_buff));ret->_buffsize = 0;return ret;
}

(2)my_fclose

第二个实现的我们选择关闭文件,这样实现完成后可以选择局部先简单测试是否有问题

fclose函数主要做了四个事情:

1.清空缓冲区

这里我们单独实现一个my_fflush函数去完成,因为后续我们还会用到刷新缓冲区的操作、

2.关闭文件

close直接关闭即可

3.释放指针堆空间

free释放掉堆空间

4.将指针置空

fp = NULL;

int my_fclose(MY_FILE* fp)
{assert(fp);//1.清空缓冲区if(fp->_buffsize > 0) my_fflush(fp);//2.关闭文件close(fp->_fd);//3.释放堆空间free(fp);//4.指针置空fp = NULL;return 0;
}

(3)my_fwrite

首先我们先根据参数设计一下如何写代码

fwrite的参数设计是,第一个参数是数据所在的空间地址,第二个是数据块的大小,第三个是数据块的个数(这里可以简单理解,第二第三个参数相乘就是需要传入数据的字节数大小),第四个是传入的文件地址FILE

write的参数设计是第一个是文件描述符(fd),第二个是要传入数据的起始地址,第三个是数据字节大小

两者的参数设计类似,但是c语言中,fwrite得到的数据并不是直接通过write写入到文件中,而是会先到C语言自己封装的缓冲区中,根据不同文件而有不同的缓冲策略,但是这里我们简单模拟实现,默认行缓冲。

综上,代码设计的思路就是:

1.首先我们需要将得到的数据写入到缓冲区中,写入缓冲区时注意不要忘记调整相关的参数_buffsize(增删都要)

ps:我们简化了写入的策略,实际写入中,当数据超过缓冲区大小时,系统会自动帮我们清空缓冲区,并多次调用,直到将内容写完,不需要外部多次调用,这里我们做一个简化,我们规定,当数据量大于缓冲区剩余空间时,我们将其写满后停下,并且将写了多少字节的信息返回(实际返回的是成功写入的数据块个数)

2.检测当前的刷新策略,根据不同策略进行刷新

对应行刷新的策略,我们正常是需要检测这个字符内容中哪部分具有'\n'的,但这里也简单做一个简化,只检测倒数最后一个字符是否为“\n”

3.返回成功写入的字节数(简化调整)

//返回值简化一下,返回成功写入的字节数
size_t my_fwrite(const void *ptr,size_t size,size_t nmemb,MY_FILE* stream)
{//先检查缓冲区是否已经满了,满了就刷新if(stream->_buffsize == NUM) my_fflush(stream);int my_size = size*nmemb;//用户要输入的数据大小int buff_capacity = NUM - stream->_buffsize;//缓冲区剩余的空间大小int ret = 0;if(buff_capacity >= my_size){memcpy(stream->_buff + stream->_buffsize,ptr,my_size);stream->_buffsize += my_size;ret = my_size;}else{memcpy(stream->_buff + stream->_buffsize,ptr,buff_capacity);stream->_buffsize += buff_capacity;ret = buff_capacity;}//简化版的刷新策略if(stream->_flags & BUFF_ALL){if(stream->_buffsize == NUM){my_fflush(stream);}}else if(stream->_flags & BUFF_ROW){if(stream->_buff[stream->_buffsize-1] == '\n'){my_fflush(stream);}}return ret;
}

(4)my_fflush

我们发现,实际真正写入到文件,是刷新缓冲区的时候,刷新缓冲区的步骤很简单,直接根据用write,将缓冲区的内容写入到文件中即可,稍微检测一下,当缓冲区里没内容时就不写,注意,写完后要调整管理缓冲区大小的参数_buffsize,将其置为0

int my_fflush(MY_FILE* fp)
{assert(fp);if(fp->_buffsize != 0){write(fp->_fd,fp->_buff,fp->_buffsize);}fp->_buffsize = 0;return 0;
}

三、测试结果

其实,测试这个步骤是在写代码的过程中,每完成一个小模块就要去测试的,这里我们简单的用main函数去测试一下基本的文件读写功能,若是发现问题就慢慢调试找bug

四、参考代码

1.mystdio.h

#pragma once#include<stdio.h>
//缓冲区大小
#define NUM 1024
//刷新策略
#define BUFF_NONE 0x01
#define BUFF_ROW 0x02
#define BUFF_ALL 0x04typedef struct MY_FILE
{int _fd;char _buff[NUM];//缓冲区int _buffsize;int _flags;//刷新策略
}MY_FILE;MY_FILE* my_fopen(const char* path,const char *mode);
size_t my_fwrite(const void* ptr,size_t size,size_t nmemv,MY_FILE* stream);
int my_fclose(MY_FILE *fp);
int my_fflush(MY_FILE* fp);

2.mystdio.c

#include"mystdio.h"
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<malloc.h>
#include<unistd.h>
#include<assert.h>MY_FILE* my_fopen(const char* path,const char* mode)
{int flag = 0;if(strcmp(mode,"r") == 0) flag |= O_RDONLY;else if(strcmp(mode,"w") == 0) flag |= (O_CREAT | O_WRONLY | O_TRUNC);else if(strcmp(mode,"a") == 0) flag |= (O_CREAT | O_WRONLY | O_APPEND );else{// other operator...}mode_t m = 0666;int fd = 0;if(flag & O_CREAT) fd = open(path,flag,m);else open(path,flag);if(fd < 0) return NULL;MY_FILE* ret = (MY_FILE*)malloc(sizeof(MY_FILE));if(ret == NULL){close(fd);perror("malloc error");return NULL;}ret->_fd = fd;ret->_flags = BUFF_ROW;memset(ret->_buff,'\0',sizeof(ret->_buff));ret->_buffsize = 0;return ret;
}//返回值简化一下,返回成功写入的字节数
size_t my_fwrite(const void *ptr,size_t size,size_t nmemb,MY_FILE* stream)
{//先检查缓冲区是否已经满了,满了就刷新if(stream->_buffsize == NUM) my_fflush(stream);int my_size = size*nmemb;//用户要输入的数据大小int buff_capacity = NUM - stream->_buffsize;//缓冲区剩余的空间大小int ret = 0;if(buff_capacity >= my_size){memcpy(stream->_buff + stream->_buffsize,ptr,my_size);stream->_buffsize += my_size;ret = my_size;}else{memcpy(stream->_buff + stream->_buffsize,ptr,buff_capacity);stream->_buffsize += buff_capacity;ret = buff_capacity;}//简化版的刷新策略if(stream->_flags & BUFF_ALL){if(stream->_buffsize == NUM){my_fflush(stream);}}else if(stream->_flags & BUFF_ROW){if(stream->_buff[stream->_buffsize-1] == '\n'){my_fflush(stream);}}return ret;
}int my_fflush(MY_FILE* fp)
{assert(fp);if(fp->_buffsize != 0){write(fp->_fd,fp->_buff,fp->_buffsize);}fp->_buffsize = 0;return 0;
}int my_fclose(MY_FILE* fp)
{assert(fp);//1.清空缓冲区if(fp->_buffsize > 0) my_fflush(fp);//2.关闭文件close(fp->_fd);//3.释放堆空间free(fp);//4.指针置空fp = NULL;return 0;
}

3.main.c(这个可以自己测试)

#include"mystdio.h"
#include<stdio.h>
#include<string.h>int main()
{MY_FILE* fp = my_fopen("test.txt","w");if(fp == NULL){perror("open error");return -1;}const char* s = "hello haha\n";my_fwrite(s,strlen(s),1,fp);my_fclose(fp);return 0;
}

4.makefile

myfile:main.c mystdio.cgcc -o $@ $^.PHONY:clean
clean:rm -f myfile

总结

本次简单的模拟实现了一下FILE的封装和一些简单c库中文件操作接口的实现,加深对文件操作各个接口和系统调用的理解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/650568.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day05 51单片机-外部中断、定时器

1 外部中断——按键控制LED亮灭 1.1 需求描述 本案例通过检测SW3触发的外部中断实现P00对应LED的亮灭。 1.2 硬件设计 1.2.1 中断简介 单片机中断是一种重要的计算机编程概念,用于处理在程序执行过程中突然发生的事件或条件。这些事件可以是外部硬件触发的,如按下按钮、…

【Leetcode每日一题】 穷举vs暴搜vs深搜vs回溯vs剪枝_全排列 - 子集(难度⭐⭐)(65)

1. 题目解析 题目链接&#xff1a;78. 子集 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 算法思路详解&#xff1a; 为了生成数组 nums 的所有子集&#xff0c;我们需要对数组中的每个元素进行“选择”或“不选择…

【面试经典 150 | 回溯】电话号码的字母组合

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;回溯 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容进行回顾…

低代码技术与仓储管理的新纪元:革命性的供应链变革

引言 在当今数字化时代&#xff0c;企业对于创新和效率的追求越发迫切。在这样的背景下&#xff0c;低代码技术应运而生&#xff0c;成为企业数字化转型的重要工具之一。低代码技术的崛起为企业提供了一种快速、灵活、成本效益高的开发方式&#xff0c;大大缩短了软件开发周期…

人工智能好多人都在用,那么用户画像要怎么看?

用户画像是通过对用户行为、偏好、兴趣等数据进行分析和整理&#xff0c;从而形成的关于特定用户群体的描述和模型。在人工智能应用中&#xff0c;用户画像可以起到指导个性化推荐、精准营销、产品设计等方面的作用。以下是用户画像在人工智能应用中的几个重要方面&#xff1a;…

SecuPress Pro 专业级WordPress网站安全防护插件优化版

下载地址&#xff1a;SecuPress Pro 专业版.zip SecuPress Pro&#xff1a;专业的WordPress安全解决方案 如果您没有时间进行每周扫描&#xff0c;SecuPress Pro将是您的理想选择。SecuPress Pro提供了所有SecuPress Free的功能&#xff0c;同时还增加了一些高级选项&#xff…

java 词法分析练习

import parser.Parser;import java.util.ArrayList; import java.util.Arrays; import java.util.List;public class Main {public static void main(String[] args) {// 关键词List<String> keyList new ArrayList<>(Arrays.asList("int","String…

一次违法网站的渗透经历

0x01 前言 在一次攻防演练中&#xff0c;我发现了一个有趣的渗透路径。在信息收集阶段&#xff0c;我注意到目标网站和用户资产网站共享相同的IP网段。这意味着它们可能在同一台服务器上托管&#xff0c;或者至少由同一家互联网服务提供商管理。这种情况为我们的渗透测试提供了…

Java本地缓存技术选型(Guava Cache、Caffeine、EhCache)

前言 对一个java开发者而言&#xff0c;提到缓存&#xff0c;第一反应就是Redis。利用这类缓存足以解决大多数的性能问题了&#xff0c;我们也要知道&#xff0c;这种属于remote cache&#xff08;分布式缓存&#xff09;&#xff0c;应用的进程和缓存的进程通常分布在不同的服…

046、注意力机制

之——从心理学出发 杂谈 动物需要在复杂环境下有效关注值得注意的点。 人类根据随意线索和不随意线索选择关注的点。 正文 1.线索 不随意&#xff08;随着意识&#xff09;线索&#xff08;不主动&#xff09;&#xff0c;随意&#xff08;随着意识&#xff09;&#xff08;主…

【opencv 加速推理】如何安装 支持cuda的opencv 包 用于截帧加速

要在支持CUDA的系统上安装OpenCV&#xff0c;您可以使用pip来安装支持CUDA的OpenCV版本。OpenCV支持CUDA加速&#xff0c;但需要安装额外的库&#xff0c;如cuDNN和NVIDIA CUDA Toolkit。以下是一般步骤&#xff1a; 安装NVIDIA CUDA Toolkit: 首先&#xff0c;您需要安装NVID…

分享一些实用的工具

1、amCharts5&#xff1a;模拟航线飞行/业务分布图/k线/数据分析/地图等 网址&#xff1a; JavaScript mapping library: amCharts 5https://www.amcharts.com/javascript-maps/ Demo地址&#xff1a;Chart Demos - amChartshttps://www.amcharts.com/demos/#maps 他分为amC…