基础IO的认识

发布时间:2026/10/13 9:31:53
基础IO的认识 目录C/C文件操作和输出结论1.1 回顾C文件接口1.2 os操作文件那库函数和系统调用有什么联系吗语言的文件操作 VS 系统的文件操作为什么c要封装如何理解重定向输出重定向输入重定向使用 dup2 系统调用理解缓冲区1. 文件打开和读写的基本过程2. 什么是缓冲区刷新方式设计一个libcC/C文件操作和输出结论a. 文件 文件的内容 文件的属性 --- 对文件操作就两类 一个是属性 一个是内容b. 访问文件之前必须先打开文件那就需要先找到文件路径 文件名本质是进程操作这个文件 本质是研究进程和文件的关系c. 打开文件是吧文件加载到内存对文件开始操作本质是进程通过CPU访问内存中的文件d. linux存在大量的文件文件分为打开的和没有打开的思考问题1. 为什么不需要路径2. 建好的文件为什么在当前目录#include stdio.h int main() { const char* filename log.txt; FILE* fp fopen(filename, w); if(fp NULL) { perror(fopen); return 1; } fclose(fp); return 0; }回答上面的问题会维持自己当前的工作路径 是继承父进程的1.1 回顾C文件接口我们发现如果我们使用echo 往文件中写东西如echo hello log.txt 如果下次还是用这个就会清空文件重新从头开始写 fopen(filename, w) 这个也是同理 也是清空从头开始写w不存在就创建存在就清空同时a就是追加 在echo中就是 echo bbbb log.txt同时还有r w a 都是读写 用的不是很多 几乎不会读写同时打开#include stdio.h #include unistd.h int main() { printf(我是一个进程: %d\n, getpid()); const char* filename log.txt; FILE* fp fopen(filename, r); if(fp NULL) { perror(fopen); return 1; } while(1) { char buffer[128]; if(!fgets(buffer, sizeof(buffer), fp)) { break; } printf(%s, buffer); } // const char* s hello world\n; // for(int i 0;i 10;i) // { // fputs(s, fp); // } fclose(fp); return 0; }1.2 os操作文件⼀种传递标志位的⽅法如果传的是one | three 就是 0001 | 1000 - 1001传到Print中 flag 就是 1001 与 one 和three 与的话 就是 1001 0001 - 10001001 0001 - 0001 其他的都会得到0 所以会打印#include stdio.h #include unistd.h #define ONE (1 0) //0000 0001 #define TWO (1 1) //0000 0010 #define THREE (1 2) //0000 0100 #define FOUR (1 3) //0000 1000 void Print(int flag) { if (flag ONE) printf(one\n); if (flag TWO) printf(two\n); if (flag THREE) printf(three\n); if (flag FOUR) printf(four\n); } int main() { Print(ONE); Print(ONE|TWO); Print(ONE|TWO|THREE); }根据上面 下面是介绍 系统调用打开文件 那个宏和我们上述介绍的用法是一致的#includesys/types.h#includesys/stat.h#includefcntl.hintopen(const char*pathname,intflags);intopen(const char*pathname,intflags,mode_tmode);pathname:要打开或创建的⽬标⽂件flags:打开⽂件时可以传⼊多个参数选项⽤下⾯的⼀个或者多个常量进⾏“或”运算构成flags。参数:O_RDONLY:只读打开O_WRONLY:只写打开O_RDWR :读写打开这三个常量必须指定⼀个且只能指定⼀个O_CREAT :若⽂件不存在则创建它。需要使⽤mode选项来指明新⽂件的访问权限O_APPEND:追加写返回值成功新打开的⽂件描述符失败-1这样就是open打开文件 写且若文件不存在则创建它int fd open(log.txt, O_WRONLY|O_CREAT 0666);创建出的文件的权限 是664 上述图是权限掩码 0002 - 000 000 000 010 就是去消去 和计网学的子网掩码好像挺像的 0666 - 000 110 110 110 6 6 4我们使用write去往文件中写入内容的时候要不要把\0写到文件中呢#include stdio.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h #include string.h int main() { int fd open(log.txt, O_WRONLY|O_CREAT, 0666); if(fd 0) { perror(open); } printf(fd: %d\n, fd); const char* str abcd; write(fd, str, strlen(str)1); // 这里是故意加1 close(fd); return 0; }这里strlen 1最后乱码了 那是c语言的规定最后加\0 和文件没有关系所以 只需要 strlen(str)就可以其他代码不变 我把write的内容改变const char* str 12; write(fd, str, strlen(str));他还是会从头开始写这时候需要修改open的参数int fd open(log.txt, O_WRONLY|O_CREAT|O_TRUNC, 0666); // 清空 int fd open(log.txt, O_WRONLY|O_CREAT|O_APPEND, 0666); // 追加那库函数和系统调用有什么联系吗其实库函数是调用了系统调用库函数封装了系统调用看下面代码的运行结果#include stdio.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h #include string.h int main() { int fd open(log.txt, O_WRONLY|O_CREAT|O_APPEND, 0666); int fd2 open(log.txt, O_WRONLY|O_CREAT|O_APPEND, 0666); int fd3 open(log.txt, O_WRONLY|O_CREAT|O_APPEND, 0666); if(fd 0) { perror(open); } printf(fd: %d\n, fd); printf(fd1: %d\n, fd2); printf(fd2: %d\n, fd3); const char* str 12; write(fd, str, strlen(str)); close(fd); return 0; }那0,1,2呢 0,1,2被占用了FILE是结构体我们调用fputs fwirte用的都是FILE* 而我们用write和open用的都是文件描述符const char* msg hello i am good person; write(1, msg, strlen(msg));那我们看看stdin stdout 和 stderr的文件描述符printf(stdin- fd:%d\n, stdin-_fileno); printf(stdout- fd:%d\n, stdout-_fileno); printf(stderr- fd:%d\n, stderr-_fileno);语言的文件操作 VS 系统的文件操作文件描述符的本质是什么是数组下表fd_array 被称为文件描述符表write(3, aaa) 其实是拷贝函数 就是把用户缓冲区写到 file中的缓冲区 合适的时候刷新到磁盘中linux中访问打开的文件os只认文件描述符为什么c要封装为什么要封装fwrite fopen fclose 这些呢 为什么不能直接用 open wirte close呢之间用系统调用不具备很好的跨平台性c标准库用的fopen fread 在linux和windows 在macos都封装了一份可以理解成打包成了三份你在linux上用就是linux上的 在windows那就是windows那一份 平台的差异是在c标准库中屏蔽的如何理解重定向a. 文件描述符的分配规则就是分配值最小的且没有使用的fdb. 重定向的本质就是更改数组特定下表的内容输出重定向我们看一段代码和现象 输出重定向#include stdio.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h #include string.h int main() { close(1); int fd open(log1.txt, O_WRONLY|O_CREAT|O_TRUNC, 0666); printf(fd: %d\n, fd); printf(hello\n); return 0; }本该打印在界面上的却出现在log1.txt中 1应该是指向stdout标准输出printf就是往1处写 1就是界面然后我们去open 把fd1 分配给了log1.txt 现在1指向log1.txt 所以就写到了log1.txt输入重定向char buffer[64]; fgets(buffer, sizeof(buffer), stdin); printf(%s, buffer);close(0); int fd open(log1.txt, O_RDONLY); char buffer[64]; fgets(buffer, sizeof(buffer), stdin); // stdin - 0 printf(%s, buffer);这时候本来该从显示屏中而从文件中获取了使用 dup2 系统调用oldfd拷贝到了newfd 只剩old拷贝的不是数组下表 而是内容是oldfd里面的内容是将oldfd的内容拷贝到newfd中 数组下表没有变#includeunistd.hintdup2(intoldfd,intnewfd);int fda open(log1.txt, O_RDONLY); printf(fda: %d\n, fda); dup2(fda, 0); //0 - stdin char buffer[64]; fgets(buffer, sizeof(buffer), stdin); // stdin - 0 printf(%s, buffer);int fda open(log1.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666); printf(fda: %d\n, fda); dup2(fda, 1); printf(hahahahaha\n); printf(hahahahaha\n); printf(hahahahaha\n); printf(hahahahaha\n); printf(hahahahaha\n); fprintf(stdout, hahaha\n);理解缓冲区1. 文件打开和读写的基本过程read函数 本质是拷贝函数read没有访问磁盘加载内容是os做的。write的本质也是拷贝也没有访问磁盘如果我要修改文件内容也要先加载再修改再刷新2. 什么是缓冲区缓冲区是内存空间的⼀部分。也就是说在内存空间中预留了⼀定的存储空间这些存储空间⽤来缓 冲输⼊或输出的数据这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输⼊设备还是输出设备分为输⼊缓冲区和输出缓冲区。我们有用户缓冲区和文件内核缓冲区我们平时写printf 之类的都是写在自己的用户缓冲区的 然后进程结束 或者写满 或者fflush刷新到文件内核缓冲区比如菜鸟驿站发火车送快递 一般是一发发一车装满如果就装几个就发车就很浪费资源了这缓冲区是在FILE 这个结构体中 这结构体中还封装的fd文件描述符刷新方式1. 进程结束的时候会自动刷新2. 如果目标文件是显示器行刷新3. 普通文件一般是全缓冲缓冲区写满了才会刷新接下来我们看几个现象#include stdio.h #include iostream #include fcntl.h #include unistd.h #include sys/types.h #include sys/stat.h int main() { close(1); int fd open(output.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666); printf(yaoxi\n); }这个printf会打印到 output文件中下面这个我最后加入了一行close(fd)#include stdio.h #include iostream #include fcntl.h #include unistd.h #include sys/types.h #include sys/stat.h int main() { close(1); int fd open(output.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666); printf(yaoxi\n); close(fd); }这里导致printf没有写入文件中是因为我们是写入到用户缓冲区close关闭后没有及时调用系统调用将内容拷贝到文件内核缓冲区看下面现象sleep三秒后 才会出现hello int main() { printf(hello); sleep(3); exit(0); }下面exit换成了_exit 看现象int main() { printf(hello); sleep(3); _exit(0); }结果什么也没输出是因为printf打印的留在了用户缓冲区 没有刷新出来exit会自动刷新缓冲区_exit不会冲刷我们向显示屏打印 因为是行刷新所以四个在fork之前就全部打印在显示屏上了int main() { printf(hello\n); fprintf(stdout, hello fprintf\n); const char* s hello fputs\n; fputs(s, stdout); const char* ss hello write\n; write(1, ss, strlen(ss)); fork(); return 0; }重定向到普通文件是全缓冲除了write 其他的都在用户缓冲区中缓存fork之后刷新了两遍设计一个libcmystdio.h#pragma once #include stdio.h #define SIZE 1024 #define NON_BUFFER 1 #define LINE_BUFFER 2 #define FULL_BUFFER 4 #define MODE 0666 typedef struct _myFILE { int fd; int flag; int flush_mode; char outbuffer[SIZE]; int pos; int cap; }myFILE; myFILE *myfopen(const char* pathname, const char* mode); //r w a r void myfclose(myFILE* fp); int myfputs(const char* str, myFILE* fp); void myfflush(myFILE* fp);mystdio.c#include mystdio.h #include string.h #include sys/stat.h #include sys/types.h #include fcntl.h #include stdlib.h #include unistd.h #define TRY_FLUSH 1 #define MUST_FLUSH 2 static void myfflushcore(myFILE *fp, int flag); myFILE *myfopen(const char *pathname, const char *mode) { int fd -1; int flags 0; if (strcmp(mode, r) 0) { flags O_RDONLY; fd open(pathname, flags); } else if (strcmp(mode, w) 0) { flags O_WRONLY | O_CREAT | O_TRUNC; fd open(pathname, flags, MODE); } else if (strcmp(mode, a) 0) { flags O_WRONLY | O_CREAT | O_APPEND; fd open(pathname, flags, MODE); } else { } // todo if (fd 0) return NULL; myFILE *fp (myFILE *)malloc(sizeof(myFILE)); if (fp NULL) return NULL; fp-fd fd; fp-flag flags; fp-flush_mode LINE_BUFFER; fp-cap SIZE; fp-pos 0; return fp; } void myfflush(myFILE* fp) { myfflushcore(fp, MUST_FLUSH); } int myfputs(const char *str, myFILE *fp) { if (strlen(str) 0) return 0; memcpy(fp-outbuffer fp-pos, str, strlen(str)); fp-pos strlen(str); myfflushcore(fp, TRY_FLUSH); return 0; } static void myfflushcore(myFILE *fp, int flag) { if(fp-pos 0) return ; if ((fp-flush_mode LINE_BUFFER) || (flag MUST_FLUSH)) { if ((fp-outbuffer[fp-pos - 1] \n) || (flag MUST_FLUSH)) { write(fp-fd, fp-outbuffer, fp-pos); fp-pos 0; // 清空缓冲区 } } else if(fp-flush_mode FULL_BUFFER) { } else if(fp-flush_mode NON_BUFFER) { } } void myfclose(myFILE *fp) { myfflush(fp); // 强制刷新到磁盘 fsync(fp-fd); // 不是必选 close(fp-fd); free(fp); }main.c#include mystdio.h #include unistd.h int main() { myFILE* fp myfopen(log.txt, a); if(fp NULL) { printf(myfopen error!\n); return 0; } const char* msg 我爱睡觉\n; int cnt 10; while(cnt--) { myfputs(msg, fp); sleep(1); printf(debug: %s, pos %d\n, fp-outbuffer, fp-pos); } myfclose(fp); printf(write file done!\n); return 0; }

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询