Linux-C 檔案操作
一、簡述
二、系統I/O
三、标準I/O
四、标準輸入/輸出重定向
五、檔案操作補充
六、上述檔案操作函數代碼示例
一、簡述
**
基于Linux環境下C語言程式設計的檔案操作。
兩種操作檔案的方式:
1、系統I/O:系統調用接口,`open(), read(), write(), lseek(), close()`。是作業系統直接提供的程式設計接口(API)。
2、标準/IO:标準庫的I/O函數,fopen(), fread(), fwrite(), fseek(), fclose(),是對系統調用接口進一步封裝。
系統I/O常用于硬體級别,可以設定讀緩沖區,一般沒有寫緩沖區;
标準I/O常用于軟體級别,自帶讀寫緩沖區。
1、檔案基本概念
C程式把檔案分為ASCII檔案和二進制檔案,ASCII檔案又稱文本檔案,二進制檔案和文本檔案(也稱ASCII碼檔案)二進制檔案中,數值型資料是以二進制形式存儲的,
而在文本檔案中,則是将數值型資料的每一位數字作為一個字元以其ASCII碼的形式存儲,是以,文本檔案中的每一位數字都單獨占用一個位元組的存儲空間,
而二進制檔案則是把整個數字作為一個二進制數存儲的,并非數值的每一位數字都占用單獨的存儲空間,無論一個C語言檔案的内容是什麼,
它一律把資料看成是位元組構成的序列,即位元組流,對檔案的存取也是以位元組為機關的,輸入/輸出的的資料流僅受程式控制而不受實體符号(如回車換行符)的控制,是以說C語言檔案為流式檔案
C語言的檔案存取有兩種方式:順序存取和直接存取
(C語言有緩沖型和非緩沖型兩種檔案系統,緩沖型檔案系統是指系統自動自動在記憶體中為每一個正在使用的檔案開辟一個緩沖區,
作為程式與檔案之間資料交換的中間媒介,也就是讀檔案時,資料先送到緩沖區,再傳給C語言程式或則外存上,緩沖檔案系統利用檔案指針辨別檔案,
而非緩沖檔案系統是不會自動設定檔案緩沖區,緩沖區必須由程式員自己設定,緩沖型中的檔案操作,也稱進階檔案操作,
進階檔案操作函數大多是ANSIC定義的可移植的檔案操作函數,具有跨平台和可移植能力,可解決大多數檔案操作問題)
##二、系統I/O
**2.1 open()函數**
功能 打開一個指定的檔案并獲得檔案描述符,或者建立一個新檔案
頭檔案
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
原型
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
***參數***
pathname 要打開的檔案路徑名稱
**flags**
O_RDONLY 隻讀方式打開檔案 這三個參數互斥
O_WRONLY 隻寫方式打開檔案
O_RDWR 讀寫方式打開檔案
O_CREAT 如果檔案不存在,則建立該檔案
O_TRUNC 如檔案已經存在,則删除檔案中原有資料
O_APPEND 以追加方式打開檔案
mode 如果檔案被建立,指定其權限為mode(八進制表示法)
傳回值 成功 大于等于0 的整數(即檔案描述符)
失敗 -1,并且errno會被設定
**補充說明:**
1)flags 的各種取值可以用位或的方式疊加起來,例如建立檔案的時候需要滿足這樣的
選項:讀寫方式打開,不存在要建立,如果存在了則清空。則flags 的取值應該是:O_RDWR | O_CREAT | O_TRUNC。
2)mode 是八進制權限,比如0644,或者0755 等。也可以是使用系統已定義的
S_IRWXU 00700 user (file owner) has read, write, and execute permission //使用者可讀寫執行權限
S_IRUSR 00400 user has read permission//使用者寫權限
S_IWUSR 00200 user has write permission//使用者寫權限
S_IXUSR 00100 user has execute permission//使用者執行權限 (更多選項可查詢man手冊:man 2 open)
比如新建立的檔案權限隻需要讀寫權限:S_IRUSR | S_IWUSR
**3)檔案描述符**
其實是一個數組的下标值,在核心中打開的檔案是用 file 結構體來表示的,每一個結構體都會有一個 指針來指向它們,這些指針被統一存放在一個叫做 fd_array 的數組當中,而這個數組被存 放在一個叫做 files_struct 的結構體中,該結構體是程序控制塊 task_struct 的重要組成部分。
**2.2 close()函數**
功能:關閉檔案并釋放相應資源
頭檔案
#include <unistd.h>
原型
int close(int fd);
**參數**
fd 要關閉的檔案的描述符
傳回值
成功
失敗
-1
備注
重複關閉一個已經關閉了的檔案或者尚未打開的檔案是安全的。
**2.3 read()函數**
功能
從指定檔案中讀取資料
頭檔案
#include <unistd.h>
原型
ssize_t read(int fd, void *buf, size_t count);
參數
fd
從檔案 fd 中讀資料,(fd是某個檔案的描述符)
buf
指向存放讀到的資料的緩沖區,(就是放資料的記憶體首位址)
count
想要從檔案 fd 中讀取的位元組數
傳回值
成功
實際讀到的位元組數
失敗
-1
備注
實際讀到的位元組數小于等于 count
補充說明:
ssize_t :是類型重定義,為了跨平台相容。比如說long在32位系統可能是4位元組,64位系統可能是8位元組,嵌入式開發有的隻有16位,那麼int隻有2個位元組。
但是在程式設計時往往需要根據類型大小來進行操作資料,例如在64位系統程式設計時8位元組使用long類型,如果移植到32位系統時long隻有4位元組,那麼就需要改動好多個地方,是以重定義一種類型,然後根據實際系統再指定,友善改動移植。例如:重定義ssize類型是8位元組的,在32位系統,我可以将long long重定義為ssize類型,如果是64位系統可以定義為long類型,這樣隻需改動一處。
**2.4 write()函數**
功能
将資料寫入指定的檔案
頭檔案
#include <unistd.h>
**原型**
ssize_t write(int fd, const void *buf, size_t count);
**參數**
fd
将資料寫入到檔案fd 中 (fd是某個檔案的描述符)
buf
指向即将要寫入的資料,(要寫的資料的記憶體首位址)
count
要寫入的位元組數
傳回值
成功
實際寫入的位元組數
失敗 : -1
備注
實際寫入的位元組數小于等于 count
**2.5 lseek()函數**
功能
調整檔案位置偏移量
頭檔案
#include <sys/types.h>
#include <unistd.h>
原型
off_t lseek(int fd, off_t offset, int whence);
**參數**
fd
要調整位置偏移量的檔案的描述符
offset
相對基準點的偏移大小
`
whence:基準點
SEEK_SET:檔案開頭處
SEEK_CUR:目前位置
SEEK_END:檔案末尾處
傳回值
成功
新檔案位置偏移量(相對于檔案開頭的偏移)
失敗
-1
備注
可用 file-size= lseek(fd,0,SEEK_END);來測量檔案大小
示例代碼:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char* argv[])
{
int fd;
int wr_ret;
int rd_ret;
unsigned long file_size;
char wr_buf[100] = “hello world”;
char rd_buf[100];
fd = open("a.txt", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IROTH);//等價于fd = open("a.txt", O_RDWR | O_CREAT | O_TRUNC, 0x604);
if(fd == -1)
{
perror("open file error:");//隻有上面的函數設定了error全局錯誤号,才可使用,會根據error輸出對應的錯誤資訊
return -1;
}
printf("fd = %d\n", fd);
wr_ret = write(fd, wr_buf, sizeof(wr_buf));
if(wr_ret == -1)
{
perror("write file error:");
return -1;
}
printf("wr_ret = %d\n", wr_ret);
lseek(fd, 0, SEEK_SET);//上面的寫操作,檔案位置偏移量也會相應的移動,此處将檔案偏移到檔案開始位置,然後才能讀取剛剛輸入的内容
rd_ret = read(fd, rd_buf, sizeof(rd_buf));
if(rd_ret == -1)
{
perror("read file error:");
return -1;
}
printf("rd_ret = %d\n",rd_ret);
printf("content=%s\n", rd_buf);
file_size = lseek(fd, 0, SEEK_END);
printf("file_size = %lu\n", file_size);
close(fd);//關閉檔案
return 0;
}
**2.5 mmap()函數**
功能
将實體記憶體映射為虛拟記憶體,為了提高效率。(比如将一個檔案映射到虛拟記憶體,以操作記憶體的方式進行資料的讀寫,但是不會改變檔案的大小,一般不用于普通檔案)
頭檔案
#include <sys/mman.h>
原型
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
**參數**
addr 從虛拟記憶體的哪個位址開始去映射這片記憶體
NULL代表由系統決定映射起始位址
length 映射的記憶體長度(位元組為機關)
port
決定這塊記憶體的操作權限,以下數值可以相或
PROT_EXEC Pages may be executed. 執行權限
PROT_READ Pages may be read. 讀權限
PROT_WRITE Pages may be written. 寫權限
PROT_NONE Pages may not be accessed. 無權限
flags 操作标志
MAP_SHARED:在多程序中把這塊記憶體共享給其他程序
MAP_PRIVATE:不共享記憶體
fd
将資料寫入到檔案fd 中 (fd是某個檔案的描述符)
offset
基于檔案頭偏移多少機關開始映射
傳回值
成功
成功傳回映射的虛拟位址
失敗
失敗傳回MAP_FAILED,其實就是個-1, errno會被設定
備注
取消映射 int munmap(void *addr, size_t length);
addr:映射的起始位址
length:取消的長度
成功傳回0,失敗傳回-1
更多請檢視man手冊:man 2 mmap
示例代碼:
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h> //mencpy()
int main(void)
{
int fd;
char *map_ptr;
int retval;
fd = open("a.txt",O_RDWR);
if(fd == -1)
{
perror("open failed:");
return -1;
}
map_ptr = mmap( NULL, 100, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(map_ptr == MAP_FAILED)
{
perror("map memory failed:");
return -1;
}
printf("%s", map_ptr);
memcpy(map_ptr, "haha", 5);
retval = munmap(map_ptr, 100);
if(retval == -1)
{
perror("munmap failed:");
return -1;
}
close(fd);
return 0;
}
**三、标準I/O**
**3.1 fopen()函數**
功能
擷取指定檔案的檔案指針
頭檔案
#include <stdio.h>
原型
FILE *fopen(const char *path, const char *mode);
**參數**
path
要打開的檔案的路徑名稱
mode
“r” : 以隻讀方式打開檔案,要求檔案必須存在。
“r+” : 以讀寫方式打開檔案,要求檔案必須存在。
“w” : 以隻寫方式打開檔案,檔案如果不存在将會建立新檔案,如果存
在将會将其内容清空。
“w+” : 以讀寫方式打開檔案,檔案如果不存在将會建立新檔案,如果存在将會将其内容清空。
“a” : 以隻寫方式打開檔案,檔案如果不存在将會建立新檔案,且檔案位置偏移量被自動定位到檔案末尾(即以追加方式寫資料)。
“a+” : 以讀寫方式打開檔案,檔案如果不存在将會建立新檔案,第一次用于寫資料則檔案位置偏移量被自動定位到檔案末尾(即以追加方式寫資料),如果第一次用于讀資料,檔案位置偏移位置會定位到檔案開始。
傳回值
成功
檔案指針
失敗 NULL
備注
傳回的檔案指針是一種指向結構體 FILE{}的指針,檔案描述符就被封裝在FILE結構體裡面
程式一開始預設打開3個檔案
裝置
檔案描述符(int)
檔案指針(FILE *)
标準輸入裝置(鍵盤)
0
STDIN_FILENO
stdin
标準輸出裝置(螢幕)
1
STDOUT_FILENO
stdout
标準出錯裝置(螢幕)
2
STDERR_FILENO
stderr
**3.2 fclose()函數**
功能
關閉指定的檔案并釋放其資源
頭檔案
#include <stdio.h>
原型
int fclose(FILE *fp);
**參數**
fp
即将要關閉的檔案
傳回值
成功
0
失敗
EOF
備注
fclose( )不能對一個檔案重複關閉
**3.3 fread()函數**
功能
從指定檔案讀取若幹個資料塊
頭檔案
#include <stdio.h>
原型
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
**參數**
ptr
自定義緩沖區指針,(通俗的說就是要存放 讀取出來的資料 的地方)
size
資料塊大小
nmemb
資料塊個數
stream
即将被讀取資料的檔案指針
傳回值
成功
讀取的資料塊個數,等于 nmemb
失敗
讀取的資料塊個數,小于 nmemb 或等于 0
備注
當傳回小與 nmemb 時,檔案 stream 可能已達末尾,或者遇到錯誤
3.4 fwrite()函數
功能
将若幹塊資料寫入指定的檔案
頭檔案
#include <sys/ioctl.h>
原型
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
**參數**
ptr:自定義緩沖區指針(要寫入的資料的首位址)
size:資料塊大小
nmemb:資料塊個數
stream:即将被寫入資料的檔案指針
傳回值
成功
寫入的資料塊個數,等于 sinmembze
失敗
寫入的資料塊個數,小于 nmemb 或等于 0
備注
無
3.5 fseek()函數
功能
設定指定檔案的目前位置偏移量
頭檔案
#include <sys/ioctl.h>
原型
int fseek(FILE *stream, long offset, int whence);
**參數**
stream:需要設定位置偏移量的檔案指針
offset:新位置偏移量相對基準點的偏移
whence:基準點
SEEK_SET:檔案開頭處
SEEK_CUR:目前位置
SEEK_END:檔案末尾處
傳回值
成功
0
失敗
-1
備注
無
3.6 ftell()函數
功能
擷取指定檔案的目前位置偏移量
頭檔案
#include <sys/ioctl.h>
原型
long ftell(FILE *stream);
**參數**
stream:需要傳回目前檔案位置偏移量的檔案指針
傳回值
成功
目前檔案位置偏移量
失敗
-1
備注
無
3.7rewind()函數
功能
将指定檔案的目前位置偏移量設定到檔案開頭處
頭檔案
#include <sys/ioctl.h>
原型
void rewind(FILE *stream);
**參數**
stream:需要設定位置偏移量的檔案指針
傳回值
無
備注
該函數的功能是将檔案 strean 的位置偏移量置位到檔案開頭處。
簡單例子:
#include <stdio.h>
int main(int argc,char* argv[])
{
int wr_ret;
int rd_ret;
FILE *fp;
unsigned long file_size;
char wr_buf[100] = “hello world”;
char rd_buf[100];
fp = fopen( "a.txt", "a+" );//檔案追加,可讀可寫,檔案不存在則建立
if(fp == NULL)
{
perror("open file error:");//隻有上面的函數設定了error全局錯誤号,才可使用,會根據error輸出對應的錯誤資訊
return -1;
}
wr_ret = fwrite( wr_buf, sizeof(wr_buf), 1, fp);
printf("wr_ret = %d\n", wr_ret);
rewind(fp);//上面的寫操作,檔案位置偏移量也會相應的移動,此處将檔案偏移到檔案開始位置,然後才能讀取剛剛輸入的内容
rd_ret = fread(rd_buf, sizeof(rd_buf), 1, fp);
printf("rd_ret = %d\n",rd_ret);
printf("content=%s\n", rd_buf);
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
printf("file_size = %lu\n", file_size);
fclose(fp);//關閉檔案
return 0;
}
———————————————
#**四、标準輸入/輸出重定向**
實際上,對于終端裝置,系統會自動打開3個标準檔案:标準輸入、标準輸出和标準錯誤輸出,相應的,系統定義了3個特别的檔案指針常數:stdin、stdout、stderr,
分别指向标準輸入、标準輸出和标準錯誤檔案,這3個檔案都以标準終端裝置作為輸入/輸出對象,在預設情況下,标準輸入裝置時鍵盤,标注輸出裝置是螢幕
fprintf()是printf()的檔案操作版,二者的差别在于fprintf()多了一個FILE *類型的參數fp,如果為其提供的第1個參數時stdout,那麼它就和printf()完全一樣,
同理可推廣到fputc()和putchar()等其他函數,
例如:
putchar(c);和fputc(c,stdout);等價
getchar();和fgetc(stdin);等價
puts(str)和fputs(str,stdout);等價
但函數fgets()與gets()不同,從如下函數原型可知其差別在于fgets()還多了一個參數size
char *fgets(char *s,int size,FILE *stream);
char *gets(char *s)
;
fgets()用其第二個參數size來說明輸入緩沖區的大小,使讀入的字元數不能超過限定緩沖區的大小,進而達到防止緩沖區溢出攻擊的目的,
假如已定義一個有32位元組的緩沖區buffer[32],那麼在下面兩條讀字元串的語句中,後者的安全性更高
gets(buffer);
fgets(buffer,sizeof(buffer),stdin);//安全性更高
雖然系統隐含的I/O是指終端裝置,但其實标準輸入和标準輸出是可以重新定向的,作業系統可以重新定向它們到其他檔案或具有檔案屬性的裝置,隻有标準錯誤輸出不能進行一般的輸出重定向,
例如,在沒有顯示器的主機上,把标準輸出定向到列印機,各種程式不用做任何改變,輸出内容就自動從列印機輸出
這裡用“<”表示輸入重定向,用“>”表示輸出重定向,例如:假設exefile時可執行程式檔案名,執行該程式時,需要輸入資料,現在如果要從檔案file.in中讀取資料,而非鍵盤輸入,
于是exefile的标準輸入就被“<”重定向到了file.in,c此時程式exefile隻會專心緻志地從檔案file.in中讀取資料,而不再理會你此後按下的任何一個按鍵,
于是,exefile的标準輸出就被“>”重定向到了檔案file.out中,此時程式exefile的所有輸出内容都被輸出到了檔案file.out中,而螢幕上沒有任何顯示
例:
複制代碼
1 #include <stdio.h>
2
3 main()
4 {
5 int c;
6
7 scanf_s("%d", &c);
8 printf("%d", c);
9
10 return 0;
11 }
将exe檔案移到E盤,建立一個te.txt輸入10然後儲存,打開DOS指令行,轉到E盤,輸入test1.exe<te.txt回車,則te.txt檔案中的10作為輸入值,輸出值為10
#**五、檔案操作補充**
因為在Microsoft Visual C++ 2010 Express中使用fopen,fscanf等函數編譯器會顯示警告,
This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
所有在檔案操作時将改用fopen_s、fscanf_s等函數
fopen_s()函數
函數原型:`errno_t fopen_s( FILE** pFile, const char *filename, const char *mode );`
pFile----檔案指針将接收到打開檔案指針指向的指針
infilename----檔案名
mode----允許通路的類型
fopen_s()打開檔案成功傳回0值,否則傳回非0值
須定義另外一個變量errno_t err
例:
1 FILE *fp;
2 errno_t err;
3 err = fopen(&fp,“E:\ww.txt”,“r”);
這裡的errno_t是int的别名,在編譯器crtdef.h頭檔案中有typedef int errno_t;
fopen()與fopen_s()的差別
fopen_s()函數比fopen()函數多了一個溢出檢測,安全性上有所提升,在使用形式上fopen_s()比fopen()多使用了一個參數,
需要特别注意的是:
fopen的傳回值是FILE *,傳回的是指向結構體類型的指針
而fopen_s的傳回值是errno_t,傳回的是errno_t(int)類型的數值
例:**fopen()函數**
1 fp = fopen("E:\\ww.txt", "r");
fopen_s()函數
1 errno_t err;
2 err = fopen_s(&fp,"E:\\ww.txt","r");
**fscanf_s()函數**
函數原型:`fscanf_s(_Inout_ FILE * _File, _In_z_ _Scanf_s_format_string_ const char * _Format, ...);`
fscanf_s和fscanf的差別
在使用形式上fopen_s()比fopen()多使用了一個參數,第四個參數是位元組數(注意長度(strlen)和位元組數(sizeof)的差別)
例:fscanf()函數
1 fscanf(fp, "%c", &c);
fscanf_s()函數
1 fscanf_s(fp, "%c", &c,sizeof(char));
#**六、上述檔案操作函數代碼示例**
(前提條件:在E盤根目錄下建立一個txt文檔命名為ww.txt,内容輸入about儲存)
fopen()函數----fopen(檔案路徑, 檔案使用方式);
1 FILE *fp;
2 fp = fopen(“E:\ww.txt”, “r”);
fopen_s()函數----fopen_s(指向該檔案指針的指針, 檔案路徑, 檔案使用方式);
1 errno_t err;
2 err = fopen_s(&fp, “E:\ww.txt”, “r”);
fread()函數----fread(記憶體首位址, 資料塊大小, 資料塊個數, 檔案指針);
1 char ss[20];
2 fread(ss, sizeof(char), 4, fp);
fwrite()函數----fwrite(記憶體首位址, 資料塊大小, 資料塊個數, 檔案指針);
1 char ss[20] = “aabb”;
2 fwrite(ss, sizeof(char), 4, fp);
fscanf_s()函數----fscanf_s(檔案指針, 格式參數, 存入位址, 位元組數)
1 fscanf_s(fp, "%c", &c,sizeof(char));
fprintf()函數----fprintf(檔案指針, 格式參數, 輸出清單)
1 char c = ‘a’;
2 fprintf(fp, “%c”, c);
fseek()函數----fseek(檔案指針, 指針偏移量, 起始位置);
1 fseek(fp, sizeof(char), SEEK_SET);
fgets()函數----fgets(記憶體首位址, 字元數, 檔案指針);
1 char ss[20];
2 fgets(ss, 20, fp);
fgetc()函數----fgetc(檔案指針);
1 char ss[20];
2 ss[0] = fgetc(fp);
3 printf("%c", ss[0]);
fputc()函數----fputc(變量名, 檔案指針)
1 int c = ‘a’;
2 fputc(c, fp);
也可寫成
fputc(‘c’, fp);
fputs()函數----fputs(字元串, 檔案指針)
1 char ss[20] = “aaaa”;
2 fputs(ss, fp);
也可寫成
1 fputs(“aaaa”, fp);