一、概述
標(biāo)準(zhǔn)IO:標(biāo)準(zhǔn)I/O是ANSI C建立的一個(gè)標(biāo)準(zhǔn)I/O模型,是一個(gè)標(biāo)準(zhǔn)函數(shù)包和stdio.h頭文件中的定義,不依賴(lài)系統(tǒng)內(nèi)核,所以移植性強(qiáng)。又稱(chēng)為高級(jí)磁盤(pán)I/O,遵循ANSI C相關(guān)標(biāo)準(zhǔn)。只要開(kāi)發(fā)環(huán)境中有標(biāo)準(zhǔn)I/O庫(kù),標(biāo)準(zhǔn)I/O就可以使用。(Linux 中使用的是glibc,它是標(biāo)準(zhǔn)C庫(kù)的超集。不僅包含ANSI C中定義的函數(shù),還包括POSIX標(biāo)準(zhǔn)中定義的函數(shù)。因此,Linux 下既可以使用標(biāo)準(zhǔn)I/O,也可以使用文件I/O)。標(biāo)準(zhǔn)I/O庫(kù)處理很多細(xì)節(jié),例如緩沖分配,以?xún)?yōu)化長(zhǎng)度執(zhí)行I/O等。
文件IO:文件I/O即系統(tǒng)調(diào)用IO,也稱(chēng)為不帶緩沖的I/O(unbuffered I/O)。不帶緩沖指的是每個(gè)read,write都調(diào)用內(nèi)核中的一個(gè)系統(tǒng)調(diào)用。也就是一般所說(shuō)的低級(jí)磁盤(pán)I/O,遵循POSIX相關(guān)標(biāo)準(zhǔn),任何兼容POSIX標(biāo)準(zhǔn)的操作系統(tǒng)上都支持文件I/O?!遣僮飨到y(tǒng)提供的基本IO服務(wù),與OS綁定,特定于Linux或Unix平臺(tái)。
標(biāo)準(zhǔn)IO可以看成在文件I/O的基礎(chǔ)上由標(biāo)準(zhǔn)I/O庫(kù)(stdio.h)封裝并維護(hù)了緩沖機(jī)制。
二、標(biāo)準(zhǔn)IO
頭文件需求:
#include <stdio.h>
1.fopen和fclose
(1)fopen
fopen的函數(shù)功能是打開(kāi)一個(gè)文件。
首先看看fopen的函數(shù)聲明:
FILE *fopen(const char *path, const char *mode);
第一個(gè)參數(shù)path是文件地址,傳入的是不可變的字符串;第二個(gè)參數(shù)是mode是指打開(kāi)方式,傳入的也是不可變的字符串;返回的是FILE指針。
mode的可選項(xiàng)主要有:
"r" Open text file for reading. The stream is positioned at the beginning of the file.
"r+" Open for reading and writing. The stream is positioned at the beginning of the file.
"w" Truncate file to zero length or create text file for writing. The stream is positioned at the beginning of the file.
"w+" Open for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is positioned at the beginning of the file.
"a" Open for appending (writing at end of file). The file is created if it does not exist. The stream is positioned at the end of the file.
"a+" Open for reading and appending (writing at end of file). The file is created if it does not exist. The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file.
(2)fclose
fclose的函數(shù)功能是關(guān)閉一個(gè)文件。
fclose的函數(shù)聲明:
int fclose(FILE *fp);
傳入的參數(shù)是FILE指針,即fopen創(chuàng)建的那個(gè)指針;成功則返回0,否則返回EOF,并將錯(cuò)誤存儲(chǔ)在errno中。
附:Linux中系統(tǒng)調(diào)用的錯(cuò)誤都存儲(chǔ)于 errno中,errno由操作系統(tǒng)維護(hù),存儲(chǔ)就近發(fā)生的錯(cuò)誤,即下一次的錯(cuò)誤碼會(huì)覆蓋掉上一次的錯(cuò)誤。
2.fgetc和fputc
(1)fgetc
fgetc的功能是從stream中讀取下一個(gè)character。
函數(shù)聲明如下:
int fgetc(FILE *stream);
傳入的參數(shù)是stream來(lái)源即FILE指針。
(2)fputc
fputc的功能是將一個(gè)character寫(xiě)入到stream中。
函數(shù)原型是:
int fputc(int c, FILE *stream);
第一個(gè)參數(shù)就是要寫(xiě)入的字符,雖然是int型,但是只用低八位(unsigned char);第二個(gè)參數(shù)即寫(xiě)入到的stream來(lái)源即FILE指針。
3.fgets和fputs
(1)fgets
fgets的功能是從stream中讀取至多一定數(shù)量的字符,并且存入buffer中,遇到'\n'或者EOF就停止讀取。
請(qǐng)看函數(shù)聲明:
char *fgets(char *s, int size, FILE *stream);
第一個(gè)參數(shù)是字符串buffer指針,傳入的是char*,這里當(dāng)然是可變的;第二個(gè)參數(shù)是至多讀取的字符數(shù),傳入的是int;第三個(gè)參數(shù)就是stream來(lái)源即FILE指針。
這個(gè)函數(shù)把獲取到的字符傳入buffer后,還會(huì)自動(dòng)在末尾加上'\0'表示字符串的結(jié)束。
(2)fputs
fputs的功能是將字符串s傳到stream中。
函數(shù)聲明是:
int fputs(const char *s, FILE *stream);
第一個(gè)參數(shù)是不可變的字符串,第二個(gè)參數(shù)就是要寫(xiě)到的stream來(lái)源這里是FILE指針。
字符串被寫(xiě)入stream后,還會(huì)自動(dòng)在末尾加上'\0'表示結(jié)束。
4.fread和fwrite
(1)fread
函數(shù)聲明:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread函數(shù)從stream中讀取nmemb個(gè)元素,其中每個(gè)元素的長(zhǎng)度為size,讀取后存入到本地的ptr所指空間中。返回實(shí)際讀到的數(shù)據(jù)項(xiàng)個(gè)數(shù)nmemb,注意這里不是字節(jié)數(shù),只有當(dāng)函數(shù)參數(shù)size為1時(shí),數(shù)據(jù)項(xiàng)個(gè)數(shù)nmemb才等于字節(jié)數(shù)。
(2)fwrite
函數(shù)聲明:
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
fwrite函數(shù)從ptr指針中讀取nmemb個(gè)元素(由于不涉及修改ptr指向的內(nèi)存,故為const),其中每個(gè)元素的長(zhǎng)度為size,將其寫(xiě)入到stream即FILE指針中去。返回實(shí)際寫(xiě)入的數(shù)據(jù)項(xiàng)個(gè)數(shù)nmemb,注意這里不是字節(jié)數(shù),只有當(dāng)函數(shù)參數(shù)size為1時(shí),數(shù)據(jù)項(xiàng)個(gè)數(shù)nmemb才等于字節(jié)數(shù)。
5.printf和scanf
(1)printf
printf函數(shù)聲明為:
int printf(const char *format, ...);
如果成功的話(huà),則返回已打印的字節(jié)數(shù);如果失敗,會(huì)返回一個(gè)小于0的負(fù)值。
(2)scanf
scanf的函數(shù)聲明為:
int scanf(const char *format, ...);
scanf函數(shù)返回成功讀入的數(shù)據(jù)項(xiàng)數(shù),它可以比實(shí)際輸入的少;如果遇到錯(cuò)誤或者讀入數(shù)據(jù)時(shí)遇到了“文件結(jié)束”則返回EOF。
6.fseek和ftell
(1)fseek
fseek的函數(shù)聲明是:
int fseek(FILE *stream, long offset, int whence);
fseek函數(shù)可以設(shè)置FILE指針stream的位置。
如果執(zhí)行成功,stream將指向以whence為基準(zhǔn),偏移offset(指針偏移量)個(gè)字節(jié)的位置,函數(shù)返回0。如果執(zhí)行失敗(比如offset取值大于等于210241024*1024,即long的正數(shù)范圍2G),則不改變stream指向的位置,返回一個(gè)非0值。
fseek函數(shù)和lseek函數(shù)類(lèi)似,但lseek返回的是一個(gè)off_t數(shù)值,而fseek返回的是一個(gè)整型。lseek是文件IO函數(shù),見(jiàn)下文。
(2)ftell
ftell的函數(shù)聲明是:
long ftell(FILE *stream);
該函數(shù)返回給定流stream的當(dāng)前文件位置。返回的是一個(gè)long型,而long型占4個(gè)字節(jié),考慮到long有符號(hào),即文件最大不超過(guò)2^31bit,即2Gb。文件大小被限制在了這么小。其實(shí)這是一個(gè)歷史遺留問(wèn)題,當(dāng)年的程序設(shè)計(jì)者的年代,他們覺(jué)得long型已經(jīng)夠大,但是他們沒(méi)有預(yù)料到計(jì)算機(jī)硬件的發(fā)展速度之快。
7.fseeko和ftello
(1)fseeko
fseeko的函數(shù)原型是:
int fseeko(FILE *stream, off_t offset, int whence);
(2)ftello
ftello的函數(shù)原型是:
off_t ftello(FILE *stream);
fseeko和ftello兩個(gè)函數(shù)的存在,就是為了解決fseek和ftell這兩個(gè)函數(shù)中文件2G大小限制的歷史遺留問(wèn)題。
8.rewind
函數(shù)聲明如下:
void rewind(FILE *stream);
rewind函數(shù)就是將stream的文件偏移量設(shè)為0,使其指向文件開(kāi)頭。它的功能等同于:
(void) fseek(stream, 0L, SEEK_SET)
9.fflush
函數(shù)聲明如下:
int fflush(FILE *stream);
fflush()會(huì)強(qiáng)迫將緩沖區(qū)內(nèi)的數(shù)據(jù)寫(xiě)回參數(shù)stream 指定的文件中。
10.getline
函數(shù)聲明如下:
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
參數(shù):
getline() reads an entire line from stream, storing the address of the buffer containing the text into *lineptr. The buffer is null-terminated and includes the newline character, if one was found.
返回值:
On success, getline() return the number of characters read, including the delimiter character, but not including the terminating null byte ('\0'). This value can be used to handle embedded null bytes in the line read.
三、文件IO
頭文件需求
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
1.open和close
(1)open
函數(shù)聲明為:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函數(shù)接收文件路徑pathname,返回一個(gè)文件描述符(File Descriptor,簡(jiǎn)稱(chēng)fd),故返回int型。并且返回的是當(dāng)前可用的數(shù)字最小的文件描述符;若打開(kāi)文件時(shí)發(fā)生錯(cuò)誤,open()將返回?1,錯(cuò)誤號(hào) errno 標(biāo)識(shí)錯(cuò)誤原因。。
flags參數(shù)值介紹:


(2)close
函數(shù)聲明為:
int close(int fd);
函數(shù)關(guān)閉一個(gè)文件描述符fd。如果成功了就返回0,失敗則返回-1,錯(cuò)誤號(hào)errno標(biāo)識(shí)錯(cuò)誤原因。
2.read和write
(1)read
函數(shù)聲明:
ssize_t read(int fd, void *buf, size_t count);
read函數(shù)的功能是從文件描述符為fd的文件中中讀取count字節(jié),并存入到buffer中。如果失敗了則返回-1,錯(cuò)誤號(hào)errno標(biāo)識(shí)錯(cuò)誤原因。如果成功的話(huà)則返回讀取到的字節(jié)數(shù)(0就代表已經(jīng)讀到了文件末尾)。即使返回的字節(jié)數(shù)不等于我們要求的字節(jié)數(shù)count,也不代表發(fā)生了錯(cuò)誤,因?yàn)榭赡軙?huì)發(fā)生以下的情況:
- 文件已經(jīng)接近末尾
- 是從pipe或terminal中讀取
- read函數(shù)被signal中斷
(2)write
函數(shù)聲明:
ssize_t write(int fd, const void *buf, size_t count);
write函數(shù)的功能是從buffer中獲得count字節(jié)的數(shù)據(jù)并把它存入到文件描述符為fd的文件中。如果失敗了,則返回-1,錯(cuò)誤號(hào)errno標(biāo)識(shí)錯(cuò)誤原因;如果成功了,則返回寫(xiě)入的字節(jié)數(shù),0即代表什么都沒(méi)被寫(xiě)入。
3.lseek
系統(tǒng)內(nèi)核會(huì)記錄其文件偏移量,有時(shí)也將文件偏移量稱(chēng)為讀寫(xiě)偏移量或指針。文件偏移量是指執(zhí)行下一個(gè) read()或 write()操作的文件起始位置,會(huì)以相對(duì)于文件頭部起始點(diǎn)的文件當(dāng)前位置來(lái)表示。文件第一個(gè)字節(jié)的偏移量為 0。文件打開(kāi)時(shí),會(huì)將文件偏移量設(shè)置為指向文件開(kāi)始,以后每次 read()或 write()調(diào)用將自動(dòng)對(duì)其進(jìn)行調(diào)整,以指向已讀或已寫(xiě)數(shù)據(jù)后的下一字節(jié)。因此,連續(xù)的 read()和 write()調(diào)用將按順序遞進(jìn),對(duì)文件進(jìn)行操作。
函數(shù)聲明為:
off_t lseek(int fd, off_t offset, int whence);
offset 參數(shù)指定了一個(gè)以字節(jié)為單位的數(shù)值,而whence參數(shù)有以下三種:

lseek調(diào)用成功則會(huì)返回新的文件偏移量。如我們想獲得文件偏移量的當(dāng)前位置,有示例如下:
curr = lseek(fd, 0, SEEK_CUR);
4.ioctl
除了上述通用 I/O 模型外,ioctl()系統(tǒng)調(diào)用又為執(zhí)行文件和設(shè)備操作提供了一種多用途機(jī)制。
ioctl函數(shù)聲明如下:
int ioctl(int d, int request, ...);
其中第一個(gè)參數(shù)需要的是一個(gè)打開(kāi)文件的文件描述符int型d,第二個(gè)參數(shù)是設(shè)備依賴(lài)請(qǐng)求碼。ioctl()調(diào)用的第三個(gè)參數(shù)采用了標(biāo)準(zhǔn) C 語(yǔ)言的省略符號(hào)(...)來(lái)表示(稱(chēng)之為 argp),可以是任意數(shù)據(jù)類(lèi)型。ioctl()根據(jù) request 的參數(shù)值來(lái)確定 argp 所期望的類(lèi)型。通常情況下,argp是指向整數(shù)或結(jié)構(gòu)的指針,有些情況下,不需要使用 argp。
文件IO的總結(jié)
為了對(duì)普通文件執(zhí)行 I/O 操作,首先必須調(diào)用 open()以獲得一個(gè)文件描述符。隨之使用read()和 write()執(zhí)行文件的 I/O 操作,然后應(yīng)使用 close()釋放文件描述符及相關(guān)資源。這些系統(tǒng)調(diào)用可對(duì)所有類(lèi)型的文件執(zhí)行 I/O 操作。所有類(lèi)型的文件和設(shè)備驅(qū)動(dòng)都實(shí)現(xiàn)了相同的 I/O 接口,這保證了 I/O 操作的通用性,同時(shí)也意味著在無(wú)需針對(duì)特定文件類(lèi)型編寫(xiě)代碼的情況下,程序通常就能操作所有類(lèi)型的文件。對(duì)于已打開(kāi)的每個(gè)文件,內(nèi)核都維護(hù)有一個(gè)文件偏移量,這決定了下一次讀或?qū)懖僮鞯钠鹗嘉恢?。讀和寫(xiě)操作會(huì)隱式修改文件偏移量。使用 lseek()函數(shù)可以顯式地將文件偏移量置為文件中或文件結(jié)尾后的任一位置。在文件原結(jié)尾處之后的某一位置寫(xiě)入數(shù)據(jù)將導(dǎo)致文件空洞。從文件空洞處讀取文件將返回全 0 字節(jié)。