歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

Linux 系統應用編程——標准I/O

標准I/O的由來
標准I/O指的是ANSI C 中定義的用於I/O操作的一系列函數。
只要操作系統安裝了C庫,標准I/O函數就可以調用。換句話說,如果程序中使用的是標准I/O函數,那麼源代碼不需要任何修改就可以在其他操作系統下編譯運行,具有更好的可移植性。
除此之外,使用標准I/O可以減少系統調用的次數,提高系統效率。標准I/O函數在執行時也會用到系統調用。在執行系統調用時,Linux必須從用戶態切換到內核態,處理相應的請求,然後再返回到用戶態。如果頻繁的執行系統調用會增加系統的開銷。為避免這種情況,標准I/O在使用時為用戶控件創建緩沖區,讀寫時先操作緩沖區,在合適的時機再通過系統調用訪問實際的文件,從而減少了使用系統調用的次數。
流的含義
標准I/O的核心對象就是流。當用標准I/O打開一個文件時,就會創建一個FILE結構體描述該文件(或者理解為創建一個FILE結構體和實際打開的文件關聯起來)。我們把這個FILE結構體形象的稱為流,我們在stdio.h裡可以看到這個FILE結構體。
[cpp] view
plain copy
typedef struct {
short level; /* fill/empty level of buffer */
unsigned flags; /* File status flags */
char fd; /* File descriptor */
unsigned char hold; /* Ungetc char if no buffer */
short bsize; /* Buffer size */
unsigned char *buffer; /* Data transfer buffer */
unsigned char *curp; /* Current active pointer */
unsigned istemp; /* Temporary file indicator */
short token; /* Used for validity checking */
} FILE; /* This is the FILE object */
這個結構體:1)對 fd 進行了封裝;2)對緩存進行了封裝 unsigned char *buffer; 這而指向了buffer 的地址,實際這塊buffer是cache,我們要將其與用戶控件的buffer分開。
標准I/O函數都是基於流的各種操作,標准I/O中的流的緩沖類型有下面三種:
1)、全緩沖。
在這種情況下,實際的I/O操作只有在緩沖區被填滿了之後才會進行。對駐留在磁盤上的文件的操作一般是有標准I/O庫提供全緩沖。緩沖區一般是在第一次對流進行I/O操作時,由標准I/O函數調用malloc函數分配得到的。
術語flush描述了標准I/O緩沖的寫操作。緩沖區可以由標准I/O函數自動flush(例如緩沖區滿的時候);或者我們對流調用fflush函數。
2)、行緩沖
在這種情況下,只有在輸入/輸出中遇到換行符的時候,才會執行實際的I/O操作。這允許我們一次寫一個字符,但是只有在寫完一行之後才做I/O操作。一般的,涉及到終端的流--例如標注輸入(stdin)和標准輸出(stdout)--是行緩沖的。
3)、無緩沖
標准I/O庫不緩存字符。需要注意的是,標准庫不緩存並不意味著操作系統或者設備驅動不緩存。
標准I/O函數時庫函數,是對系統調用的封裝,所以我們的標准I/O函數其實都是基於文件I/O函數的,是對文件I/O函數的封裝,下面具體介紹·標准I/O最常用的函數:
一、流的打開與關閉
使用標准I/O打開文件的函數有fopen() 、fdopen() 、freopen()。他們可以以不同的模式打開文件,都返回一個指向FILE的指針,該指針指向對應的I/O流。此後,對文件的讀寫都是通過這個FILE指針來進行。
fopen函數描述如下:
所需頭文件#include <stdio.h>函數原型FILE *fopen(const char *path, const char *mode);函數參數path: 包含要打開的文件路徑及文件名
mode:文件打開方式
函數返回值
成功:指向FILE的指針
失敗:NULL
mode用於指定打開文件的方式。
關閉流的函數為fclose(),該函數將流的緩沖區內的數據全部寫入文件中,並釋放相關資源。
fclose()函數描述如下:
所需頭文件#include <stdio.h>函數原型int fclose(FILE *stram);函數參數stream:已打開的流指針
函數返回值
成功:0
失敗:EOF
二、流的讀寫
1、按字符(字節)輸入/輸出
字符輸入/輸出函數一次僅讀寫一個字符。
字符輸入函數原型如下:
所需頭文件#include <stdio.h>函數原型int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar (void);
函數參數stream:要輸入的文件流
函數返回值
成功:讀取的字符
失敗:EOF
函數getchar等價於get(stdin)。前兩個函數的區別在於getc可被實現為宏,而fgetc則不能實現為宏。這意味著:
1)getc 的參數不應當是具有副作用的表達式。
2)因為fgetc一定是一個函數,所以可以得到其地址。這就允許將fgetc的地址作為一個參數傳給另一個參數;
3)調用fgetc所需時間很可能長於調用getc,因為調用函數通常所需的時間長於調用宏。
這三個函數在返回下一個字符時,會將其unsigned char 類型轉換為int類型。說明為什麼不帶符號的理由是,如果是最高位為1也不會使返回值為負。要求整數返回值的理由是,這樣就可以返回所有可能的字符值再加上一個已出錯或已達到文件尾端的指示值。在<stdio.h>中的常量EOF被要求是一個負值,其值經常是-1。這就意味著不能將這三個函數的返回值存放在一個字符變量中,以後還要將這些函數的返回值與常量EOF相比較。
注意,不管是出錯還是到達文件尾端,這三個函數都返回同樣的值。為了區分這兩種不同的情況,必須調用ferror或feof。
[cpp] view
plain copy
#include <stdio.h>
int ferror (FILE *fp);
int feof (FILE *fp);
兩個函數返回值;若條件為真則返回非0值(真),否則返回0(假);
在大多數實現中,為每個流在FILE對象中維持了兩個標志:
出錯標志。
文件結束標志。
字符輸出-函數原型如下:
所需頭文件#include <stdio.h>函數原型int putc (int c ,FILE *stream);
int fputc (int c, FILE *stream);
int putchar(int c);
函數返回值成功:輸出的字符c
失敗:EOF
putc()和fputc()向指定的流輸出一個字符(節),putchar()向stdout輸出一個字符(節)。
2、按行輸入、輸出
行輸入/輸出函數一次操作一行。
行輸入函數原型如下:
所需頭文件#include <stdio.h>函數原型char *gets(char *s);
char *fgets(char *s,int size,FILE *stream);
函數參數s:存放輸入字符串的緩沖區首地址;
size:輸入的字符串長度
stream:對應的流
函數返回值
成功:s
失敗或到達文件末尾:NULL
這兩個函數都指定了緩沖區的地址,讀入的行將送入其中。gets從標准輸入讀,而fgets則從指定的流讀。
gets函數容易造成緩沖區溢出,不推薦使用;
fgets從指定的流中讀取一個字符串,當遇到 \n 或讀取了 size - 1個字符串後返回。注意,fgets不能保證每次都能讀出一行。 如若該行(包括最後一個換行符)的字符數超過size -1 ,則fgets只返回一個不完整的行,但是,緩沖區總是以null字符結尾。對fgets的下一次調用會繼續執行。
行輸出函數原型如下:
所需頭文件#include <stdio.h>函數原型int puts(const char *s);
int fgets(const char *s,FILE *stream);
函數參數s:存放輸入字符串的緩沖區首地址;
stream:對應的流
函數返回值
成功:非負值
失敗或到達文件末尾:NULL
函數fputs將一個以null符終止的字符串寫到指定的流,尾端的終止符null不寫出。注意,這並不一定是每次輸出一行,因為它並不要求在null符之前一定是換行符。通常,在null符之前是一個換行符,但並不要求總是如此。
下面舉個例子:模擬文件的復制過程:
[cpp] view
plain copy
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define maxsize 5
int main(int argc, char *argv[])
{
FILE *fp1 ,*fp2;
char buffer[maxsize];
char *p,*q;
if(argc < 3)
{
printf("Usage:%s <srcfile> <desfile>\n",argv[0]);
return -1;
}
if((fp1 = fopen(argv[1],"r")) == NULL)
{
perror("fopen argv[1] fails");
return -1;
}
if((fp2 = fopen(argv[2],"w+")) == NULL)
{
perror("fopen argv[2] fails");
return -1;
}
while((p = fgets(buffer,maxsize,fp1)) != NULL)
{
fputs(buffer,fp2);
}
if(p == NULL)
{
if(ferror(fp1))
perror("fgets failed");
if(feof(fp1))
printf("cp over!\n");
}
fclose(fp1);
fclose(fp2);
return 0;
}
執行結果如下:
[cpp] view
plain copy
fs@ubuntu:~/qiang/stdio/cp$ ls -l
total 16
-rwxrwxr-x 1 fs fs 7503 Jan 5 15:49 cp
-rw-rw-r-- 1 fs fs 736 Jan 5 15:50 cp.c
-rw-rw-r-- 1 fs fs 437 Jan 5 15:15 time.c
fs@ubuntu:~/qiang/stdio/cp$ ./cp time.c 1.c
cp over!
fs@ubuntu:~/qiang/stdio/cp$ ls -l
total 20
-rw-rw-r-- 1 fs fs 437 Jan 5 21:09 1.c
-rwxrwxr-x 1 fs fs 7503 Jan 5 15:49 cp
-rw-rw-r-- 1 fs fs 736 Jan 5 15:50 cp.c
-rw-rw-r-- 1 fs fs 437 Jan 5 15:15 time.c
fs@ubuntu:~/qiang/stdio/cp$
我們可以看到,這裡將time.c拷貝給1.c ,1.c和time.c大小一樣,都是437個字節;
3、以指定大小為單位讀寫文件
三、流的定位
四、格式化輸入輸出
這裡舉個相關應用例子:循環記錄系統時間
實驗內容:程序每秒一次讀取依次系統時間並寫入文件
[cpp] view
plain copy
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#define N 64
int main(int argc, char *argv[])
{
int n;
char buf
;
FILE *fp;
time_t t;
if(argc < 2)
{
printf("Usage : %s <file >\n",argv[0]);
return -1;
}
if((fp = fopen(argv[1],"a+")) == NULL)
{
perror("open fails");
return -1;
}
while(1)
{
time(&t);
fprintf(fp,"%s",ctime(&t));
fflush(fp);
sleep(1);
}
fclose(fp);
return 0;
}
執行結果如下:
[cpp] view
plain copy
fs@ubuntu:~/qiang/stdio/timepri$ ls -l
total 12
-rwxrwxr-x 1 fs fs 7468 Jan 5 16:06 time
-rw-rw-r-- 1 fs fs 451 Jan 5 17:40 time.c
fs@ubuntu:~/qiang/stdio/timepri$ ./time 1.txt
^C
fs@ubuntu:~/qiang/stdio/timepri$ ls -l
total 16
-rw-rw-r-- 1 fs fs 175 Jan 5 21:14 1.txt
-rwxrwxr-x 1 fs fs 7468 Jan 5 16:06 time
-rw-rw-r-- 1 fs fs 451 Jan 5 17:40 time.c
fs@ubuntu:~/qiang/stdio/timepri$ cat 1.txt
Tue Jan 5 21:14:11 2016
Tue Jan 5 21:14:12 2016
Tue Jan 5 21:14:13 2016
Tue Jan 5 21:14:14 2016
Tue Jan 5 21:14:15 2016
Tue Jan 5 21:14:16 2016
Tue Jan 5 21:14:17 2016
fs@ubuntu:~/qiang/stdio/timepri$
Copyright © Linux教程網 All Rights Reserved