目录
C/C++文件操作和输出结论
1.1 回顾C文件接口
1.2 os操作文件
那库函数和系统调用有什么联系吗????
语言的文件操作 VS 系统的文件操作
为什么c要封装???
如何理解重定向
输出重定向
输入重定向
使用 dup2 系统调用
理解缓冲区
1. 文件打开和读写的基本过程
2. 什么是缓冲区?
刷新方式
设计一个libc
C/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); }根据上面 下面是介绍 系统调用打开文件 那个宏和我们上述介绍的用法是一致的
#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>intopen(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. 文件描述符的分配规则就是分配值最小的且没有使用的fd!!
b. 重定向的本质就是更改数组特定下表的内容!!
输出重定向
我们看一段代码和现象!!! 输出重定向!!
#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 把fd=1 分配给了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中 数组下表没有变!!!
#include<unistd.h>intdup2(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之后刷新了两遍!!
设计一个libc
mystdio.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; }