在Linux中,所有對設備和文件的操作都是使用文件描述符來進行的。文件描述符是一個非負的整數,它是一個索引值,並指向在內核中每個進程打開文件的記錄表。當打開一個現存文件或創建一個新文件時,內核就向程序返回一個文件描述符;當需要讀寫文件時,也需要把文件描述符作爲參數傳遞給相應的函數,基於文件描述符的I/O操作是Linux中最常用的操作之一!
這篇文章需要C編譯、Linux基礎等相關知識。可以借鑑博主往期的文章,有超詳細超全面的知識圖譜、快捷鍵技巧等乾貨分享。
傳送門:
史上最全的Linux常用命令彙總(超詳細!超全面!)收藏這一篇就夠了
GCC和GDB等GUN工具的使用(內附代碼編譯到運行的詳細過程!)
文件基礎
概念:一組相關數據的有序集合
文件類型:
- 常規文件 — r (routine)
- 目錄文件 — d (directory)
- 字符設備文件 — c (char)
- 塊設備文件 — b (block)
- 管道文件 — p (pipe)
- 套接字文件 — s (socket)
- 符號鏈接文件 — l (link)
注意:操作系統不同,所支持的文件類型也不同
標準I/O
標準I/O由ANSI C標準定義——C庫中一些定義好的用於輸入和輸出的一組函數
標準I/O可以通過緩衝機制減少系統調用,實現更高的效率
流(stream)
標準I/O中的流實際上是一個FILE結構體,用一個結構體類型來存放打開文件的相關信息,標準I/O的所有操作都是圍繞FILE來驚醒操作的。
流的分類:
Windows
二進制流:換行符——\n
文本流: 換行符——\r\n
Linux
換行符——\n
流的緩衝類型:
全緩衝
當流的緩衝區無數據或無空間時才執行實際I/O操作
行緩衝
當輸入和輸出遇到換行符\n
時,進行I/O操作當流和一個終端關聯時,就是典型的行緩衝(打印調試信息時,一定要加上換行符)
無緩衝
數據直接寫入文件,流不進行緩衝
標準I/O定義的流
標準I/O預定義了3個流,程序運行時自動打開
標準輸入流 | 0 | STDIN_FILENO | stdin |
---|---|---|---|
標準輸出流 | 1 | STDOUT_FILENO | stdout |
標準錯誤流 | 2 | STDERR_FILENO | stderr |
打開流
下列函數可用於打開一個標準I/O流:
FILE *fopen(const char *path,const char *mode);
//path是打開文件的路徑
//mode是打開方式
打開成功返回流指針,打開出錯時返回NULL。
mode參數
參數 | 作用 |
---|---|
r 或者rb |
以只讀 方式打開,文件必須存在 |
r+ 或r+b |
以讀寫 方式打開文件,文件必須存在 |
w 或者wb |
以只寫 方式打開文件,若文件存在則文件長度清爲0,若文件不存在則創建 |
w+ 或w+b |
以讀寫 方式打開文件,其他同“w” |
a 或者ab |
以只寫 方式打開文件,若文件不存在則創建;向文件寫入的數據追加到文件末尾 |
a+ 或a+b |
以讀寫 方式打開文件。其他同"a" |
注意:當給定“b”參數時,表示以二進制方式打開文件,Linux下可以忽略該參數
fopen示例:
#include <stdio.h>
int main(int argc,char *argv[])
{
FILE *fp;
if ((fp=fopen("test.tet","r+"))==NULL)//如果沒有文件路徑,默認在當前文件路徑下
{
printf("fopen error\n");//行緩衝,加換行符
return -1;
}
......
return 0;
}
注意:
- fopen()創建文件訪問權限是0666(
rw-rw-rw-
) - Linux系統中umask設定會影響文件的訪問權限,其規則爲(
0666&~umask
) - 用戶可以通過umask函數修改相關設定
- 如果希望usmak不影響文件權限,給umask賦值爲0就可以
處理錯誤信息:
extern int errno;//存放錯誤號
void perror(const char*s);//perror先輸出字符串s,再輸出錯誤號對應的錯誤信息
char *strerror(int errno);//strerror根據錯誤號返回對應的錯誤信息
示例1
#include<stdio.h>
int main(int argc,char *argv[])
{
FILE *fp;
if((fp=fopen("test.txt","r+"))==NULL){
perror("fopen");
return -1;
}
......
return 0;
}
輸出結果示例:
fopen:No such file or directory
示例2:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main(int argc,char *argv[])
{
FILE *fp;
if((fp=fopen("test.txt","r+"))==NULL){
printf("fopen:%s\n",strerror(errno));
return -1;
}
......
return 0;
}
關閉流
int fclose(FILE *stream);
注意:
- fclose()調用成功返回0,失敗返回EOF,並設置errno
- 流關閉時自動刷新緩衝中的數據並釋放緩衝區
- 當一個程序正常終止時,所有打開的流都會被關閉
- 流關閉後就不能執行任何操作
讀寫流
流支持不同的讀寫方式:
- 讀寫一個字符:fgetc()/fputc()一次讀/寫一個字符(效率低)
- 讀寫一行:fgets()和fputs()一次讀/寫一行(適合文本文件)
- 讀寫若干個對象:fread()/fwrite()每次讀/寫若干個對象,而每個對象具有相同的長度
fgrtc——示例(讀入字符):
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);//成功時返回讀入的字符,失敗返回EOF
int getchar(void);//等同於fgetc(stdin)
#include<stdio.h>
int main(int argc,char *argv[])
{
FILE *fp;
int ch,count=0;
if((fp=fopen(argv[1],"r"))==NULL)
{
perror("fopen");
return -1;
}
while((ch==fgetc(fp))!=EOF){//流的末尾時返回EOF
count++;
}
printf("total %d畢業特色\n",count);
return 0;
}
按行讀寫:
下列函數用來按行輸入:char *gets(char *s);//一般不建議使用,沒有指定緩衝區的大小,很可能引起緩衝區溢出
char *fgets(char *s,int size,FILE *stream);
- 成功時返回S,到文件末尾或者出錯時返回NULL
- gets不推薦使用,沒有指定緩衝區大小,容易造成緩衝區溢出
- 遇到‘\n’或者已輸入size-1個字符時返回,總是包含‘\0’
示例:
#include <stdio.h>
#define N 6
int main(int argc,char *argv[])
{
char buf[N];
fgets(buf,N,stdin);從標準輸入讀取
printf("%s",buf);
return 0;
}
運行以上程序發現,當鍵盤輸入爲abcd
時:
當遇到換行符時,不會繼續向下讀取,而是在換行符後加一個\0
鍵盤輸入爲abcdef
時:
由於只能讀取N-1個字符(5個),所以只能讀取a~e,最後一個補\0
。只有當下一次再調用fgets時緩存區中的剩餘內容將讀入到buf中。
按對象讀寫
下列函數用來從流中讀寫若干個對象://參數1:緩衝區的首地址;參數2:流中讀取每個對象佔用的大小;參數3:讀取多少個對象
size_t fread(void *ptr,size_t size,size_t n,FILE *fp);
size_t fwrite(const void *ptr,size_t size,size_t n, FILE *fp);
- 成功時返回讀寫對象的個數,出錯時返回EOF。
- 既可以讀寫文本文件,也可以讀寫數據文件。
示例:
int s[10];
if (fread(s,sizeof(int),10,fp)<0)//從fp中讀取10個整形對象和整形大小到s中
{
perror("fread");
return -1;
}
struct student{
int no;
char name[8];
float score;
}s[]={{1,'yang',96},{2,'huang',99}};
fwrite(s,sizeof(struct student),2,fp);
輸出流
下列函數用來輸出一個字符:
int fputc(int c,FILE *stream);//參數1:要輸出的字符,參數2:向那個流中輸出
int putc(int c,FILE *stream);
int putchar(int c);
- 成功時返回寫入的字符;出錯時返回EOF
- putchar( c )等同於fput(c,stdout)
示例:
#include<stdio.h>
int main(int argc,int argv[])
{
FILE *fp;
int ch;
if((fp=fopen(argv[1],"w"))==NULL){
perro("fopen");
return -1;
}
for(ch='a';ch<='z';ch++){
fputc(ch,fp);
}
return 0;
}
按行輸出
下列函數用來輸出字符串:int puts(const char*s);
int fputs(const char *s,FILE *stream);//寫入緩衝區的字符串寫到流中
- 成功時返回輸出字符串個數;出錯時返回EOF
- puts將緩衝區中的字符串輸出到stdout,並追加
\n
- fputs將緩衝區s中的字符串輸出到stream
fput示例
#include<stdio.h>
int main(int argc,int argv[])
{
FILE *fp;
char buf[]="hello world";
if((fp=fopen(argv[1],"a"))==NULL){//只寫的方式打開流
perro("fopen");
return -1;
}
fputs(buf,fp);//把存放在buf中的字符串寫到流中
return 0;
}
注意:輸出的字符串可以包含\n,也可以不包含
利用輸入流&輸出流進行文件複製——fgetc&fputc
#include <stdio.h>
int main(int argc,char *argv[])
{
FILE *fps,*fpd;
int ch;
if (argc<3)
{
printf("Usage:%s <src_file> <dst_file>\n",argv[0]);
return -1;
}
if((fps=fopen(argv[1],"r"))==NULL)
{
perror("fopen src file");
return -1;
}
if((fpd=fopen(argv[2],"w"))==NULL)
{
perror("fopen dst file");
return -1;
}
while((ch=fgetc(fps))!=EOF)
{
fputc(ch,fpd);
}
fclose(fps);
fclose(fpd);
return 0;
}
利用輸入流&輸出流進行文件複製——freadc&fwritec
#include <stdio.h>
#define N 64
int main(int argc,char *argv[])
{
FILE *fps,*fpd;
char buf[N];
int n;
if(argc<3)
{
printf("usage: %s <src_file> <dst_file>\n",argv[0]);
return -1;
}
if((fps=fopen(argv[1],"r"))==NULL)
{
perro("fopen src file");
return -1;
}
if((fpd=fopen(argv[2],"w"))==NULL)
{
perro("fopen src file");
return -1;
}
while((n=fread(buf,1,N,fps))>0)
{
fwrite(buf,1,N,fpd);//1=sizeof(char)
}
return 0;
}
刷新流
#include <stdio.h>
int fflush(FILE *fp);
- 成功時返回0;出錯時返回EOF
- 將流緩衝區中的數據寫到實際文件中
- Linux下只能刷新輸出緩衝區
#include <stdio.h>
int main()
{
FILE *fp;
if((fp=fopen("test.txt","w"))==NULL)
{
perror("fopen");
return -1;
}
fputc('a',fp);//會把a寫到緩衝區,但是不會寫入到實際文件中
fflush('a',fp);//刷新,強制刷新流並寫入到文件中
while(1);
return 0;
}
定位流
#include <stdio.h>
long ftell(FILE *stream);
long fseek(FILE *stream,long offset,int whence);//offset正時在基準點後面定位,負數時基準點前面定位
void remind(FILE *stream);
- ftell()成功時返回流的當前讀寫位置,出錯時返回EOF
- fseek()定位一個流,成功時返回0,出錯時返回EOF
- whence參數:SEEK_SET/SEEK_CUR/SEEK_END
- offset參數:偏移量,可正可負
- rewind()將流定位到文件開始位置
- 讀寫流時,當前讀寫位置自動後移
示例:文件末尾追加’a’
#include<stdio.h>
int main()
{
FILE *fp=fopen("test.txt","r+");
fseek(fp,0,SEEK_END);//定位到文件末尾
fputc('a',fp);
return 0;
}
示例:獲取文件長度
#include <stdio.h>
int main()
{
FILE *fp;
if ((fp==fopen("test.txt","r+"))==NULL){
perror("fopen");
return -1;
}
fseek(fp,0,SEEK_END);
printf("length is %d\n",ftell(fp));
}
判斷流
#include <stdio.h>
int ferror(FILE *stream);//判斷流是否結束
int feof(FILE *stream);//判斷流是否結束
- ferror()返回1表示流出錯,負責返回0
- feof()返回1表示文件已經到末尾,負責返回0
格式化輸出
#include <stdio.h>
int printf(const char *fmt,...);
int fprintf(FILE *stream,const char *fmt,...);//把一個字符串輸出到指定的流中
int sprintf(char *s,const char *fmt,...);//把一個字符串輸出到特定的緩衝區中
示例:指定格式“年-月-日”分別寫入文件和緩衝區
#include <stdio.h>
int main()
{
int year,month,date;
FILE *fp;
char buf[64];
year=2020;month=6;date=5;
fp=fopen("test.txt","a+");
fprintf(fp,"%d-%d-%d\n"),year,month,date);
sprintf(buf,"%d-%d-%d\n",year,month,date);//buf爲緩衝區首地址
return 0;
}
不積小流無以成江河,不積跬步無以至千里。而我想要成爲萬里羊,就必須堅持學習來獲取更多知識,用知識來改變命運,用博客見證成長,用行動證明我在努力。
如果我的博客對你有幫助、如果你喜歡我的博客內容,記得“點贊” “評論” “收藏”一鍵三連哦!聽說點讚的人運氣不會太差,每一天都會元氣滿滿呦!如果實在要白嫖的話,那祝你開心每一天,歡迎常來我博客看看。