
目錄C/C文件操作和輸出結(jié)論1.1 回顧C文件接口1.2 os操作文件那庫函數(shù)和系統(tǒng)調(diào)用有什么聯(lián)系嗎語言的文件操作 VS 系統(tǒng)的文件操作為什么c要封裝如何理解重定向輸出重定向輸入重定向使用 dup2 系統(tǒng)調(diào)用理解緩沖區(qū)1. 文件打開和讀寫的基本過程2. 什么是緩沖區(qū)刷新方式設計一個libcC/C文件操作和輸出結(jié)論a. 文件 文件的內(nèi)容 文件的屬性 --- 對文件操作就兩類 一個是屬性 一個是內(nèi)容b. 訪問文件之前必須先打開文件那就需要先找到文件路徑 文件名本質(zhì)是進程操作這個文件 本質(zhì)是研究進程和文件的關系c. 打開文件是吧文件加載到內(nèi)存對文件開始操作本質(zhì)是進程通過CPU訪問內(nèi)存中的文件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文件接口我們發(fā)現(xiàn)如果我們使用echo 往文件中寫東西如echo hello log.txt 如果下次還是用這個就會清空文件重新從頭開始寫 fopen(filename, w) 這個也是同理 也是清空從頭開始寫w不存在就創(chuàng)建存在就清空同時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); }根據(jù)上面 下面是介紹 系統(tǒng)調(diào)用打開文件 那個宏和我們上述介紹的用法是一致的#includesys/types.h#includesys/stat.h#includefcntl.hintopen(const char*pathname,intflags);intopen(const char*pathname,intflags,mode_tmode);pathname:要打開或創(chuàng)建的?標?件flags:打開?件時可以傳?多個參數(shù)選項?下?的?個或者多個常量進?“或”運算構成flags。參數(shù):O_RDONLY:只讀打開O_WRONLY:只寫打開O_RDWR :讀寫打開這三個常量必須指定?個且只能指定?個O_CREAT :若?件不存在則創(chuàng)建它。需要使?mode選項來指明新?件的訪問權限O_APPEND:追加寫返回值成功新打開的?件描述符失敗-1這樣就是open打開文件 寫且若文件不存在則創(chuàng)建它int fd open(log.txt, O_WRONLY|O_CREAT 0666);創(chuàng)建出的文件的權限 是664 上述圖是權限掩碼 0002 - 000 000 000 010 就是去消去 和計網(wǎng)學的子網(wǎng)掩碼好像挺像的 0666 - 000 110 110 110 6 6 4我們使用write去往文件中寫入內(nèi)容的時候要不要把\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語言的規(guī)定最后加\0 和文件沒有關系所以 只需要 strlen(str)就可以其他代碼不變 我把write的內(nèi)容改變const char* str 12; write(fd, str, strlen(str));他還是會從頭開始寫這時候需要修改open的參數(shù)int fd open(log.txt, O_WRONLY|O_CREAT|O_TRUNC, 0666); // 清空 int fd open(log.txt, O_WRONLY|O_CREAT|O_APPEND, 0666); // 追加那庫函數(shù)和系統(tǒng)調(diào)用有什么聯(lián)系嗎其實庫函數(shù)是調(diào)用了系統(tǒng)調(diào)用庫函數(shù)封裝了系統(tǒng)調(diào)用看下面代碼的運行結(jié)果#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是結(jié)構體我們調(diào)用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 系統(tǒng)的文件操作文件描述符的本質(zhì)是什么是數(shù)組下表fd_array 被稱為文件描述符表write(3, aaa) 其實是拷貝函數(shù) 就是把用戶緩沖區(qū)寫到 file中的緩沖區(qū) 合適的時候刷新到磁盤中l(wèi)inux中訪問打開的文件os只認文件描述符為什么c要封裝為什么要封裝fwrite fopen fclose 這些呢 為什么不能直接用 open wirte close呢之間用系統(tǒng)調(diào)用不具備很好的跨平臺性c標準庫用的fopen fread 在linux和windows 在macos都封裝了一份可以理解成打包成了三份你在linux上用就是linux上的 在windows那就是windows那一份 平臺的差異是在c標準庫中屏蔽的如何理解重定向a. 文件描述符的分配規(guī)則就是分配值最小的且沒有使用的fdb. 重定向的本質(zhì)就是更改數(shù)組特定下表的內(nèi)容輸出重定向我們看一段代碼和現(xiàn)象 輸出重定向#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; }本該打印在界面上的卻出現(xiàn)在log1.txt中 1應該是指向stdout標準輸出printf就是往1處寫 1就是界面然后我們?nèi)pen 把fd1 分配給了log1.txt 現(xiàn)在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 系統(tǒng)調(diào)用oldfd拷貝到了newfd 只剩old拷貝的不是數(shù)組下表 而是內(nèi)容是oldfd里面的內(nèi)容是將oldfd的內(nèi)容拷貝到newfd中 數(shù)組下表沒有變#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);理解緩沖區(qū)1. 文件打開和讀寫的基本過程read函數(shù) 本質(zhì)是拷貝函數(shù)read沒有訪問磁盤加載內(nèi)容是os做的。write的本質(zhì)也是拷貝也沒有訪問磁盤如果我要修改文件內(nèi)容也要先加載再修改再刷新2. 什么是緩沖區(qū)緩沖區(qū)是內(nèi)存空間的?部分。也就是說在內(nèi)存空間中預留了?定的存儲空間這些存儲空間?來緩 沖輸?或輸出的數(shù)據(jù)這部分預留的空間就叫做緩沖區(qū)。緩沖區(qū)根據(jù)其對應的是輸?設備還是輸出設備分為輸?緩沖區(qū)和輸出緩沖區(qū)。我們有用戶緩沖區(qū)和文件內(nèi)核緩沖區(qū)我們平時寫printf 之類的都是寫在自己的用戶緩沖區(qū)的 然后進程結(jié)束 或者寫滿 或者fflush刷新到文件內(nèi)核緩沖區(qū)比如菜鳥驛站發(fā)火車送快遞 一般是一發(fā)發(fā)一車裝滿如果就裝幾個就發(fā)車就很浪費資源了這緩沖區(qū)是在FILE 這個結(jié)構體中 這結(jié)構體中還封裝的fd文件描述符刷新方式1. 進程結(jié)束的時候會自動刷新2. 如果目標文件是顯示器行刷新3. 普通文件一般是全緩沖緩沖區(qū)寫滿了才會刷新接下來我們看幾個現(xiàn)象#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沒有寫入文件中是因為我們是寫入到用戶緩沖區(qū)close關閉后沒有及時調(diào)用系統(tǒng)調(diào)用將內(nèi)容拷貝到文件內(nèi)核緩沖區(qū)看下面現(xiàn)象sleep三秒后 才會出現(xiàn)hello int main() { printf(hello); sleep(3); exit(0); }下面exit換成了_exit 看現(xiàn)象int main() { printf(hello); sleep(3); _exit(0); }結(jié)果什么也沒輸出是因為printf打印的留在了用戶緩沖區(qū) 沒有刷新出來exit會自動刷新緩沖區(qū)_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 其他的都在用戶緩沖區(qū)中緩存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; // 清空緩沖區(qū) } } 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; }