Linux下標準I/O的這些操作必須懂

       在Linux中,所有對設備和文件的操作都是使用文件描述符來進行的。文件描述符是一個非負的整數,它是一個索引值,並指向在內核中每個進程打開文件的記錄表。當打開一個現存文件或創建一個新文件時,內核就向程序返回一個文件描述符;當需要讀寫文件時,也需要把文件描述符作爲參數傳遞給相應的函數,基於文件描述符的I/O操作是Linux中最常用的操作之一!
       這篇文章需要C編譯、Linux基礎等相關知識。可以借鑑博主往期的文章,有超詳細超全面的知識圖譜、快捷鍵技巧等乾貨分享。
       傳送門:
       史上最全的Linux常用命令彙總(超詳細!超全面!)收藏這一篇就夠了
       GCC和GDB等GUN工具的使用(內附代碼編譯到運行的詳細過程!)

文件基礎

       概念:一組相關數據的有序集合
       文件類型

  1. 常規文件 — r (routine)
  2. 目錄文件 — d (directory)
  3. 字符設備文件 — c (char)
  4. 塊設備文件 — b (block)
  5. 管道文件 — p (pipe)
  6. 套接字文件 — s (socket)
  7. 符號鏈接文件 — 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
  • 流關閉時自動刷新緩衝中的數據並釋放緩衝區
  • 當一個程序正常終止時,所有打開的流都會被關閉
  • 流關閉後就不能執行任何操作

讀寫流

流支持不同的讀寫方式:

  1. 讀寫一個字符:fgetc()/fputc()一次讀/寫一個字符(效率低)
  2. 讀寫一行:fgets()和fputs()一次讀/寫一行(適合文本文件)
  3. 讀寫若干個對象: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;
}

       不積小流無以成江河,不積跬步無以至千里。而我想要成爲萬里羊,就必須堅持學習來獲取更多知識,用知識來改變命運,用博客見證成長,用行動證明我在努力。
       如果我的博客對你有幫助、如果你喜歡我的博客內容,記得“點贊” “評論” “收藏”一鍵三連哦!聽說點讚的人運氣不會太差,每一天都會元氣滿滿呦!如果實在要白嫖的話,那祝你開心每一天,歡迎常來我博客看看。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章