文件基础 IO 操作
文件IO
- 庫函數 IO
- 打開文件 fopen
- 向文件寫入數據 fwrite
- 從文件讀取數據 fread
- 關閉文件 fclose
- 跳轉當前讀寫位置 fseek
- 練習
- 系統調用接口
- open
- write
- read
- lseek
- close
- 重定向
- 文件描述符
- 在 minishell 中實現重定向 > 與 >>
- 靜態庫 & 動態庫
- 靜態庫
- 動態庫
- 生成庫
- 使用庫
庫函數 IO
打開文件 fopen
fopen 返回一個文件的操作句柄,有了句柄才能對指定文件進行操作
FILE fopen(char pathname,char* mode);
mode—文件操作方式:
(1)r----只讀打開(文件不存在報錯)
(2)w----只寫打開(文件不存在會創建,存在則截斷長度為 0 ,丟棄原內容)
(3)a----追加寫打開(文件不存在創建,存在則寫入數據總是寫入文件末尾)
(4)r+ -----讀寫打開(文件不存在報錯)
(5)w+ ----讀寫打開(文件不存在創建,丟棄原內容)
(6)a+ ---- 讀寫打開(不存在創建,數據寫入末尾)
(7)b ---- 以二進制形式打開(默認不使用 b ,則使用文本方式打開-----對特殊字符處理會有不同)
以文本形式打開,有可能會造成讀到的數據與文件實際的數據有差別
向文件寫入數據 fwrite
fwrite
size_t fwrite(char* data,int bsize,int nmem,FILE*fp);
(1)bsize:文件塊個數
(2)nmem:文件塊大小
從文件讀取數據 fread
fread
size_t fread(char* buf,int bsize,int nmem,FILE* fp);
關閉文件 fclose
fclose
int fclose(FILE* fp);
跳轉當前讀寫位置 fseek
fseek
int fseek(FILE* fp,int offset,int whence);
(1)offset:偏移地址
(2)whence:文件起始地址
從 whence 位置進行偏移,whence 可能取值:SEEK_SET(文件起始地址)、SEEK_END(文件末尾)、SEEK_CUR(指定起始位置)
練習
1 #include<stdio.h> 2 #include<string.h>3 4 int main()5 {6 FILE* fp=fopen("./test.txt","w+"); //w+ 讀寫方式打開7 if(NULL==fp){8 perror("fopen error");9 return -1;10 }11 W> 12 char *data="天黑了!\n";13 //fwrite(數據地址,塊大小,塊個數,句柄)14 size_t ret=fwrite(data,1,strlen(data),fp); //返回實際寫入的完整塊個數15 16 //其實就是寫入的字節長度,因為塊大小為 117 18 if(ret!=strlen(data)){19 perror("fwrite error!");20 fclose(fp);21 return -1;22 }23 24 25 fseek(fp,0,SEEK_SET); //從文件起始位置開始跳轉26 27 28 char buf[1024]={0};29 ret=fread(buf,1,1023,fp); //塊大小 1,塊個數=想要讀取的長度,這樣返回值 就是實際讀取到的字節長度30 if(ret==0){31 if(feof(fp)) //判斷是否讀到文件末尾32 printf("讀取到了文件末尾,end of file");33 if(ferror(fp)) //判斷上一次對 fp 的操作是否出錯34 {35 perror("fread error");36 fclose(fp);37 return -1;38 } 39 }40 41 printf("%s\n",buf);42 fclose(fp);43 44 return 0; 45 }運行結果:
以上都是庫函數,庫函數是對系統調用接口的封裝
系統調用接口:
open、write、read、close、lseek
系統調用接口
open、write、read、lseek、close
open
int open(char* pathname,int flag);
int open(char* pathname,int flag,int mode);
(1) pathname:要打開的文件路徑
(2) flag:文件的打開方式
必選其一:
O_RDONLY ----00 可讀
O_WRONLY ----01 可寫
O_RDWR ----- 02 可讀可寫
可選項:
O_CREAT -----文件不存在會創建
O_TRUNC ----- 截斷文件原有內容,丟棄原內容
O_APPEND ---- 追加寫
w+:可讀可寫,文件不存在則會創建,文件已存在則截斷內容為0 ======= O_RDWR | O_CREAT | O_TRUNC
a+:可讀追加寫 ,文件不存在則會創建 ======= O_RDWR | 0=CREAT | O_APPED
(3) mode:當 O_CREAT 被使用時,就一定要設置 mode ,用于設定文件訪問權限 0664
0664 --------- 前邊的 0 不能省略,否則會涉及特殊權限位的設置
給定權限會受到 umask 影響:
實際權限=給定權限 & (~umask),默認 umask=0002
mode_t umask(mode_t mask); 將當前調用的進程掩碼設置位 mask ,并不會影響外部的 umask
返回值:打開成功則返回一個文件描述符(非負整數)作為文件操作句柄;失敗返回 -1
write
ssize_t write(int fd,char* buf,size_t len);
(1) fd: open 打開文件時返回的操作句柄-文件描述符
(2) buf: 要寫入的數據所在空間首地址
(3) len: 要寫入數據的長度(字節為單位)
返回值:成功返回實際寫入數據長度,失敗返回 -1
read
ssize_t read(int fd,char* buf,size_t len);
(1) fd : open打開文件時的操作句柄
(2) buf:一塊空間首地址,用于存放讀取到的數據
(3) len:讀取長度,不能大于buf空間的大小,避免越界
返回值:成功返回實際讀取到的數據長度(字節為單位),失敗返回 -1,0表示讀到文件末尾
lseek
off_t lseek(lint fd,off_t offset,int whence);
(1)fd:文件描述符
(2)offset:偏移量
(3)whence:偏移的起始位置
SEEK_SET; SEEK_CUR; SEEK_END
返回值:當前跳轉后,讀寫位置相當于文件起始位置位置的偏移量(接口有一種另類用法,跳轉到文件末尾,通過返回值確定文件大小)
close
int close(int fd);
fd:文件描述符
說明:
size_t :無符號整型
ssize_t :有符號整型
off_t:整型
使用練習:
1 #include<stdio.h>2 #include<string.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h> 6 7 int main()8 {9 umask(0); //采用默認設置的權限值,只在當前文件內有效10 int fp=open("./tmp.txt",O_RDWR|O_CREAT|O_TRUNC,0777);11 if(fp<0){12 perror("open error");13 return -1;14 }15 16 //打開成功17 18 const char *str="hello world!\n";19 ssize_t ret=write(fp,str,strlen(str));20 if(ret<0){21 perror("write error");22 close(fp);23 return -1;24 }25 26 //寫入數據之后,文件標識到達文件末尾27 lseek(fp,0,SEEK_SET); //起始位置開始28 29 30 //寫入成功,讀取數據31 char buf[1024];32 ret=read(fp,buf,strlen(str));33 34 if(ret<0){35 perror("read error");36 close(fp);37 return -1;38 }39 printf("%s",buf);40 close(fp);41 return 0;42 }運行結果:
庫函數是對系統調用接口的一個封裝,一般來說對于系統調用接口來說是沒有緩沖區的,而庫函數封裝了緩沖區以及系統調用接口(還有文件描述符)--------- 類似于前邊所說的 exit 與 _exit
重定向
將原本要寫入 A 位置的數據不寫入 A ,而是寫入 B
ls > a.txt 標準輸出重定向,將原本要寫入標準輸出打印的數據寫入 a.txt 文件
>:清空重定向,清空原來內容
>>:追加重定向,追加到原內容末尾
./main > /dev/null 2>&1:
將標準輸出與標準錯誤都重定向到 /dev/null 文件中
(1) > /dev/null :將標準輸出重定向到 /dev/null 文件
(2) 2>&1 : &1表示標準輸出,相當于將標準錯誤重定向到標準輸出 ,2 是標準錯誤
./main 2>&1 /dev/null:
將標準錯誤打印到標準輸出,再將標準輸出寫入到 /dev/null 文件當中
文件描述符
0-標準輸入
1-標準輸出
2-標準錯誤
文件描述符是按照最小未使用原則進行分配的,因此應該從 3 開始進行分配:
#include<stdio.h> #include<fcntl.h> //open 頭文件 #include<unistd.h>int main() {int fd=open("tmp.txt",O_RDWR);if(fd<0){perror("open error");return -1;}//打開文件成功 printf("fd = %d\n",fd); //當再打開文件之前并未關閉標準輸入、標準輸出、標準錯誤描述符時,會按照最小未使用原則進行,則此時打開的文件描述符為 3 close(fd); return 0; }(1)close(0); 關閉標準輸入
當關閉了 0 號描述符之后,打開一個新文件,會默認按最小未使用原則,新文件的描述符就會成為 0:
當在打開文件前關閉了標準輸出,則printf 不會打印出來內容:
(2)close(1) ; 關閉標準輸出
printf 是向 stdout 寫入數據,stdout 本質是封裝了 1 號文件描述符信息,printf 打印信息,實際是將數據信息寫入了 stdout 緩沖區,但是 close 關閉文件時會直接將緩沖區的信息直接釋放。
假如在最后對標準輸出進行刷新:
fflush(stdout) ;
則會將要打印的數據寫入到 1 號文件描述符位置,但是當前 1 號文件描述符對應的是 tmp.txt 文件,即將要寫入的數據寫入 tmp.txt 文件當中。
重定向本質原理:
將一個描述符所對應位置的文件描述信息地址,給替換成另一個文件的描述--------實現了在不改變其他邏輯的情況下,改變了所操縱的文件。
int dup2(int oldfd, int newfd);
讓 newfd 對應位置,保存 oldfd 對應位置的信息
將 newfd 重定向到 oldfd
若 newfd 本身代表了一個已經打開的文件,則重定向前會把文件關閉釋放
dup2(fd,1); 將標準輸出的內容輸入到 fd 文件中
在 minishell 中實現重定向 > 與 >>
(1)捕捉用戶輸入,并進行分割;
(2)判斷輸入的命令是否存在重定向符號,一般重定向符號之后內容不是指令內容,而是要重定向到的文件信息;
靜態庫 & 動態庫
靜態庫
庫文件:
將已經實現的代碼進行打包,并不是為了生成可執行程序,而是為了給其他人提供接口使用。
靜態鏈接:
生成可執行程序時,鏈接靜態庫,直接將庫中所用到的函數實現拿到可執行程序當中,不存在依賴,運行效率高,但是生成的可執行程序很大----------------可能庫中代碼在內存中會存在冗余
動態庫
以位置無關代碼打包
動態鏈接:
生成可執行程序時,鏈接動態庫,記錄庫中符號表,生成程序小,并且多個程序運行時在內存中可以共享動態庫內容,運行時依賴動態庫的存在
生成庫
生成庫:把大量代碼打包起來
(1)先把所以源碼進行編譯匯編生成各自的二進制指令 gcc *.c -o *.o
(2)把所有生成的二進制文件打包在一起
:vnew 文件名
新開一個 vim 窗口 ,命名為 當前給定的文件名
ctrl + ww :進行兩個 vim 文件的跳轉
靜態庫: ar -cr
Linux 下靜態庫命名:以 lib 為前綴,以 .a 為后綴
gcc -c child.c -o child.o
ar -cr libmychild.a child.o
生成一個名為 mychild 的靜態庫
動態庫:
linux 下動態庫命名:以 lib 作為前綴,以 .so 作為后綴,中間是庫名。
gcc -c child.c -o child.o
gcc -fPIC -c child.c -o child.o
gcc --shared child.o -o libmychild.so
生成一個名為 mychild 的動態庫
-fPIC:告訴編譯器,在編譯生成指令的時候產生與位置無關代碼(變量指令的地址都是相對偏移量)
一個動態庫被映射到不同進程的虛擬地址空間時,映射的位置也不一定相同,所以在 main 中調用的庫函數都只是記錄一個相對偏移量,最后根據實際映射位置的起始地址進行計算。--------更加靈活
-c:只進行預處理,編譯,匯編完畢后退出,不進行鏈接
–shared:告訴編譯器要生成的是一個庫文件,而不是可執行程序
使用庫
直接運行 main 程序時會報錯,因為未找到 printf_child 的定義:
使用 -l 來告訴編譯器要使用哪個庫
gcc main.c -o main -lmychid
mychild 是要鏈接的庫文件
但是會報錯,因為編譯器會到系統指定目錄下(/usr/lib64)來尋找庫文件
因此我們可以
(1)將庫放到指定位置下 :
x64------/usr/lib64 ; x32-----------/usr/lib
sudo cp libmychild.so /usr/lib64 //但是一般不推薦,因為這樣會污染 /usr/lib64 庫文件(2)設置環境變量:
export LIBRARY_PATH=${LIBRARY_PATH}:./ //將庫文件所在目錄添加到環境變量 //當前要使用的庫文件位于 ./ 當前目錄下 export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:.///添加運行加載庫文件的路徑(3)使用 gcc -L 選項,指定庫文件所在路徑
gcc main.c -o main -L./ -lmychild這種方式只適合在指定路徑下,鏈接靜態庫,因為無法設置運行程序時動態庫的加載路徑
ps:
有任何疑問歡迎評論
留言~
~
總結
以上是生活随笔為你收集整理的文件基础 IO 操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 上海科技大学计算机浙江分数线,上海科技大
- 下一篇: 201771010112罗松《面向对象程