檔案IO操作

sanjiudemiao發表於2024-05-15

檔案操作

cache:是讀的緩衝區,讀內容先讀到cache中,是讀的加速機制

buffer:是寫的緩衝區,寫內容先寫到buff中,是寫的加速機制

對一個檔案的操作有兩種不同的方式,既可以使用由作業系統直接提供的程式設計介面
(API),即系統呼叫,也可以使用由標準C庫提供的標準IO函式。

系統IO

open

功能 開啟一個指定的檔案並獲得檔案描述符,或者建立一個新檔案
標頭檔案 <sys/types.h>
<sys/stat.h>
<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_EXCL:如果使用 O_CREAT 選項且檔案存在,則返回錯誤訊息
- O_NOCTTY:如果檔案為終端,那麼終端不可以作為呼叫 open() 系統呼叫的那個程序的控制終端
- O_TRUNC:如檔案已經存在,則刪除檔案中原有資料
- O_APPEND:以追加方式開啟檔案
- mode:如果檔案被新建,指定其許可權為 mode(八進位制表示法)
返回值 成功:大於等於0的整數(即檔案描述符)
失敗:-1
備註

** 示例程式碼**

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
 
 
int main()
{
    int fd;
    
    //以讀寫的方式建立新檔案    
    fd=open("myhello",O_RDWR | O_CREAT,0777);
 
    //以讀寫的方式建立新檔案,並判斷是否存在    
    //fd=open("myhello",O_RDWR | O_CREAT | O_EXCL,0777);
 
    //以讀寫的方式建立新檔案,若檔案存在則進行截斷(清空檔案內容)  
    //fd = open("myhello", O_RDWR | O_CREAT | O_TRUNC, 0777);
 
    if (fd < 0)
    {
        printf("can not open file %s\n", argv[1]);
        printf("errno = %d\n", errno);
        printf("err: %s\n", strerror(errno));
        perror("open");
    }
    else
    {
        printf("fd = %d\n", fd);
    }
    close(fd);
    return 0;
}

close

功能 關閉一個開啟的檔案描述符
標頭檔案 <unistd.h>
原型 int close(int fd);
引數 fd:要關閉的檔案描述符
返回值 成功:0
失敗:-1
備註 重複關閉一個已經關閉的檔案或者尚未開啟的檔案是安全的

示例程式碼

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int fd;
    // 假設 fd 是已經開啟的檔案描述符
    int ret = close(fd);
    if (ret < 0)
    {
        printf("can not close file descriptor %d\n", fd);
        printf("errno = %d\n", errno);
        printf("err: %s\n", strerror(errno));
        perror("close");
    }
    return 0;
}

read

功能 從指定檔案中讀取資料
標頭檔案 <unistd.h>
原型 ssize_t read(int fd, void *buf, size_t count);
引數 - fd:從檔案 fd 中讀取資料
- buf:指向存放讀到的資料的緩衝區
- count:想要從檔案 fd 中讀取的位元組數
返回值 成功:實際讀到的位元組數
失敗:-1
備註 實際讀到的位元組數小於等於 count
  • 如果返回值是0,說明讀到檔案末尾

write

功能 將資料寫入指定的檔案
標頭檔案 <unistd.h>
原型 ssize_t write(int fd, const void *buf, size_t count);
引數 - fd:將資料寫入到檔案 fd
- buf:指向即將要寫入的資料
- count:要寫入的位元組數
返回值 成功:實際寫入的位元組數
失敗:-1
備註 實際寫入的位元組數小於等於 count

** 示例程式碼 **

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main() {
    // 開啟檔案
    int fd = open("example.txt", O_RDWR | O_CREAT, 0666);
    if (fd < 0) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 寫入資料
    const char *message = "Hello, this is a test message.\n";
    ssize_t bytes_written = write(fd, message, strlen(message));
    if (bytes_written < 0) {
        perror("write");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 讀取資料
    char buffer[100];
    lseek(fd, 0, SEEK_SET); // 將檔案指標移至檔案開頭
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read < 0) {
        perror("read");
        close(fd);
        exit(EXIT_FAILURE);
    }
    buffer[bytes_read] = '\0'; // 新增字串結束符

    // 輸出讀取的資料
    printf("Data read from file: %s", buffer);

    // 關閉檔案
    close(fd);

    return 0;
}

  • 這個程式的作用是開啟一個檔案,向檔案中寫入一條訊息,然後再讀取出來並列印。

dup dup2

功能 複製檔案描述符
標頭檔案 <unistd.h>
原型 int dup(int oldfd);
int dup2(int oldfd, int newfd);
引數 - oldfd:要複製的檔案描述符
- newfd:指定的新檔案描述符
返回值 成功:新的檔案描述符
失敗:-1
備註

dup是英文單詞duplicate的縮寫,意味著複製一個已有的檔案描述符,dup將會返回一個最小未用的檔案描述符作為複製;

dup2則可以透過第二個引數來指定描述符,如果這個描述符已經存在,則將會被覆蓋。

lseek

功能 調整檔案位置偏移量
標頭檔案 <sys/types.h>
<unistd.h>
原型 off_t lseek(int fd, off_t offset, int whence);
引數 - fd:要調整位置偏移量的檔案的描述符
- offset:新位置偏移量相對基準點的偏移
- whence:基準點
- SEEK_SET:檔案開頭處
- SEEK_CUR:當前位置
- SEEK_END:檔案末尾處
返回值 成功:新檔案位置偏移量
失敗:-1
備註 只對普通檔案有效,特殊檔案是無法調整偏移量的

** 示例程式碼 **

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    // 開啟檔案
    int fd = open("example.txt", O_RDWR);
    if (fd < 0) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 設定新的位置偏移量
    off_t new_offset = lseek(fd, 0, SEEK_SET);
    if (new_offset == -1) {
        perror("lseek");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 關閉檔案
    close(fd);

    return 0;
}

ioctl fcntl

功能 檔案控制
標頭檔案 <sys/ioctl.h>
原型 int ioctl(int fd, int request, ...);
引數 - fd:要控制的檔案描述符
- request:針對不同檔案的各種控制命令字
- ...:根據不同的命令字而不同
返回值 成功:一般情況下是0,但有些特定的請求將返回非負整數
失敗:-1
備註

request是一個由底層驅動提供的命令字, 一些通用的命令字被放置在標頭檔案/usr/include,(不同的系統存放位置也許不同)中,後面的變參也由前面的request命令字決定。

ioctl 是裝置驅動程式中裝置控制介面函式,一個字元裝置驅動通常會實現裝置開啟、關閉、讀、寫等功能,在一些需要細分的情況下,如果需要擴充套件新的功能,通常以增設 ioctl() 命令的方式實現。

ioctl 函式用於對裝置進行控制操作,它是一種通用的裝置 I/O 控制介面。透過 ioctl 函式,可以向裝置傳送控制命令,或者獲取裝置的狀態資訊。這些控制命令通常由預定義的常量表示,稱為 IOCTL 命令字。

使用 ioctl 函式可以實現很多裝置相關的功能,例如:

  • 設定裝置引數:如串列埠的波特率、資料位、停止位等;
  • 控制裝置行為:如開啟或關閉裝置的某些功能;
  • 查詢裝置狀態:如獲取裝置的錯誤資訊、緩衝區狀態等。
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <linux/fb.h>


int main(int argc, char const *argv[])
{
	//1.開啟LCD
	int lcd_fd = open("/dev/fb0",O_RDWR);


	//2.利用ioctl函式獲取LCD硬體引數
	struct fb_var_screeninfo lcd_vinfo;
	ioctl(lcd_fd,FBIOGET_VSCREENINFO,&lcd_vinfo);

	//3.輸出LCD的寬、高、色深
	printf("lcd height= %-5d\n", lcd_vinfo.yres); 		//480   
	printf("lcd width= %-5d\n", lcd_vinfo.xres);  		//800
	printf("lcd bpp=%-5d\n",lcd_vinfo.bits_per_pixel);	//32
	
	return 0;
}

  • 透過 ioctl(lcd_fd, FBIOGET_VSCREENINFO, &lcd_vinfo) 呼叫,將請求傳送給 LCD 裝置檔案描述符 lcd_fd,並將 LCD 裝置的硬體引數儲存在結構體 lcd_vinfo 中。

fcntl

功能 檔案控制
標頭檔案 <unistd.h>
<fcntl.h>
原型 int fcntl(int fd, int cmd, .../*arg*/);
引數 - fd:要控制的檔案描述符
- cmd:控制命令字
- .../*arg*/:根據不同的命令字而不同
返回值 成功:根據不同的 cmd,返回值不同
失敗:-1
備註

fcntl 函式用於對檔案描述符進行各種控制操作,例如設定檔案狀態標誌、獲取檔案狀態標誌、複製檔案描述符等。其功能包括但不限於:

  • 修改已開啟檔案的屬性,比如設定檔案的狀態標誌(例如設定非阻塞模式)、檔案描述符的標誌、檔案的讀寫鎖等。
  • 複製檔案描述符,使兩個描述符指向相同的檔案。
  • 獲取已開啟檔案的各種屬性資訊,如獲取檔案狀態標誌、獲取檔案的讀寫鎖資訊等。

這些操作可以透過 cmd 引數來指定。cmd 引數決定了 fcntl 函式的具體行為,例如 F_GETFL 用於獲取檔案狀態標誌,F_SETFL 用於設定檔案狀態標誌,F_DUPFD 用於複製檔案描述符等。

總的來說,fcntl 函式是一個靈活的介面,用於實現對檔案描述符的各種控制操作。

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    // 開啟檔案
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 獲取當前檔案狀態標誌
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl");
        return 1;
    }

    // 設定檔案狀態標誌為非阻塞模式
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        perror("fcntl");
        return 1;
    }

    // 演示讀取檔案,此時設定了非阻塞模式
    char buffer[1024];
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
    if (bytes_read == -1) {
        perror("read");
    } else {
        printf("Read %zd bytes: %s\n", bytes_read, buffer);
    }

    // 關閉檔案
    close(fd);

    return 0;
}

在這個示例中,我們首先使用 open 函式開啟一個檔案,然後使用 fcntl 函式獲取檔案的狀態標誌,接著設定檔案的狀態標誌為非阻塞模式,並最後讀取檔案。

mmap

功能 將檔案或其他物件對映到記憶體
標頭檔案 <sys/mman.h>
原型 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
引數 - addr:指定對映區域的首地址,通常設為 NULL,讓核心選擇適當的地址
- length:對映區域的長度
- prot:保護對映區域的許可權
- PROT_READ:允許讀取對映區域的內容
- PROT_WRITE:允許寫入對映區域的內容
- PROT_EXEC:允許執行對映區域的內容
- PROT_NONE:拒絕對對映區域的訪問
- flags:控制對映區域的屬性
- MAP_SHARED:共享對映,對對映區域的修改會影響到檔案或其他對映該檔案的程序
- MAP_PRIVATE:私有對映,對對映區域的修改不會影響到檔案或其他對映該檔案的程序
- MAP_ANONYMOUS:建立匿名對映,不與檔案關聯,常用於建立匿名記憶體
- fd:要對映的檔案描述符,如果對映的是匿名記憶體,則為 -1
- offset:對映檔案的偏移量,通常設為 0
返回值 成功:對映區域的起始地址
失敗:MAP_FAILED--->(void*)-1
備註 - 如果對映的是檔案,則 fd 引數指定檔案描述符,offset 引數指定檔案的偏移量;如果對映的是匿名記憶體,則 fd 引數為 -1offset 引數無效。
- 對映區域的長度必須是頁面大小的整數倍,通常使用 sysconf(_SC_PAGE_SIZE) 獲取頁面大小。
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    // 開啟檔案
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 獲取檔案大小
    struct stat file_stat;
    if (fstat(fd, &file_stat) == -1) {
        perror("fstat");
        close(fd);
        return 1;
    }
    off_t file_size = file_stat.st_size;

    // 對映檔案到記憶體
    void *addr = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return 1;
    }

    // 讀取對映區域的內容
    printf("Mapped content: %s\n", (char *)addr);

    // 解除記憶體對映
    if (munmap(addr, file_size) == -1) {
        perror("munmap");
        close(fd);
        return 1;
    }

    // 關閉檔案
    close(fd);

    return 0;
}

標準IO

fopen

函式 fopen
功能 開啟一個檔案
標頭檔案 <stdio.h>
原型 FILE *fopen(const char *path, const char *mode);
引數 - path:即將要開啟的檔案路徑名
- mode
- "r": 以只讀方式開啟檔案,要求檔案必須存在。
- "r+": 以讀寫方式開啟檔案,要求檔案必須存在。
- "w": 以只寫方式開啟檔案,檔案如果不存在將會建立新檔案,如果存在將會將其內容清空。
- "w+": 以讀寫方式開啟檔案,檔案如果不存在將會建立新檔案,如果存在將會將其內容清空。
- "a": 以只寫方式開啟檔案,檔案如果不存在將會建立新檔案,且檔案位置偏移量被自動定位到檔案末尾(即以追加方式寫資料)。
- "a+": 以讀寫方式開啟檔案,檔案如果不存在將會建立新檔案,且檔案位置偏移量被自動定位到檔案末尾(即以追加方式寫資料)。
返回值 成功:FILE * ----> 檔案指標
失敗:NULL
備註 最好要以二進位制檔案開啟rb``wb
使用標準IO的時候,是不可以反覆關閉相同的檔案,因為釋放已經被釋放的堆記憶體,會導致段錯誤!

思考:fopen函式的返回值是一個指向被開啟檔案的FILE型別的指標,請問FILE型別是什麼?

回答:FILE型別其實是一個結構體資料型別,它包含了標準 I/O 庫函式為管理檔案所需要的所有資訊,比如包括用於實際I/O 的檔案描述符、指向檔案緩衝區的指標、緩衝區的長度、當前緩衝區中的位元組數以及出錯標誌等。標頭檔案stdio.h中有關於FILE型別的相關描述,

fclose

函式 fclose
功能 關閉一個檔案
標頭檔案 <stdio.h>
原型 int fclose(FILE *stream);
引數 stream:要關閉的檔案流指標
返回值 成功:0
失敗:非0
備註 不能對同一個檔案重複關閉

image-20240514230440287

  • 使用標準IO函式處理檔案的最大特點是,資料將會先儲存在一個標準IO 緩衝區中,而後在一定條件下才被一併flush(沖洗,或稱重新整理)至核心緩衝區,而不是像系統IO那樣,資料直接被flush至核心。 注意到,標準IO函式fopen()實質上是系統IO函式open()的封裝,他們是一一對應的,每一次fopen()都會導致系統分配一個file{ }結構體和一個FILE{}來儲存維護該檔案的讀寫資訊,每一次的開啟和操作都可以不一樣,是相對獨立的,因此可以在多執行緒或者多程序中多次開啟同一個檔案,再利用檔案空洞技術進行多點讀寫。

  • 預設開啟的三個標準檔案,標準輸出入輸出裝置在系統IO是預設被開啟的,在標準IO也是一樣,在程式開始就已經擁有相應的檔案指標

裝置 檔案描述符(int) 檔案指標(FILE *) 標準輸入裝置(鍵盤)
標準輸入裝置 0 STDIN_FILENO stdin
標準輸出裝置 1 STDOUT_FILENO stdout
標準出錯裝置 2 STDERR_FILENO stderr

每次一個字元的讀寫標準IO函式介面

fgetc getc getchar

功能 獲取指定檔案的一個字元
標頭檔案 <stdio.h>
原型 int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
引數 stream:檔案指標
返回值 成功讀取到的字元
失敗:EOF
備註 當返回EOF時,檔案stream可能已達末尾,或者遇到錯誤

fputc putc putchar

功能 將一個字元寫入一個指定的檔案
標頭檔案 <stdio.h>
原型 int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
引數 c:要寫入的字元
stream:寫入的檔案指標
返回值 成功寫入到的字元
失敗:EOF
備註

1,fgec()、getc()和getchar()返回值是int,而不是char,原因是因為他們在出錯或者讀到檔案末尾的時候需要返回一個值為-1的EOF標記,而char型資料有可能因 為系統的差異而無法表示負整數。

2,當fgec()、getc()和getchar()返回EOF時,有可能是發生了錯誤,也有可能是讀到了檔案末尾,需要用 feof(),ferror()來判斷。

feof ferror

功能 判斷一個檔案是否到達檔案末尾
標頭檔案 <stdio.h>
原型 int feof(FILE *stream);
int ferror(FILE *stream);
引數 stream:進行判斷的檔案指標
返回值 feof:如果檔案已達末尾則返回真,否則返回假
ferror:如果檔案遇到錯誤則返回真,否則返回假
備註

每次一行字元的讀寫標準IO函式介面

fgetc gets

功能 從指定檔案讀取最多一行資料
標頭檔案 <stdio.h>
原型 char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
引數 s:自定義緩衝區指標
size:自定義緩衝區大小
stream:即將被讀取資料的檔案指標
返回值 成功:自定義緩衝區指標 s
失敗:NULL
備註 1. gets() 預設從檔案 stdin 讀入資料
2. 當返回 NULL 時,檔案 stream 可能已達末尾,或者遇到錯誤

fputs puts

功能 將資料寫入指定的檔案
標頭檔案 <stdio.h>
原型 int fputs(const char *s, FILE *stream);
int puts(const char *s);
引數 s:自定義緩衝區指標
stream:即將被寫入資料的檔案指標
返回值 成功:非負整數
失敗:EOF
備註 puts() 預設將資料寫入檔案 stdout
  1. fgets()fgetc()一樣,當其返回NULL時並不能確定究竟是達到檔案末尾還是 碰到錯誤,需要用feof()/ferror()來進一步判斷。
  2. fgets()每次讀取至多不超過size個位元組的一行,所謂“一行”即資料至多包含一 個換行符’\n’。
  3. gets()是一個已經過時的介面,因為他沒有指定自定義緩衝區s的大小,這樣很容 易造成緩衝區溢位,導致程式段訪問錯誤。
  4. fgets()fputs()gets()puts()一般成對使用,鑑於gets()的不安全性, 一般建議使用前者。

** 每次讀寫若干資料塊的標準IO函式介面 **

fread fwrite

功能 從指定檔案讀取若干個資料塊
標頭檔案 <stdio.h>
原型 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
引數 ptr:自定義緩衝區指標
size:資料塊大小
nmemb:資料塊個數
stream:即將被讀取資料的檔案指標
返回值 成功:讀取的資料塊個數,等於 nmemb
失敗:讀取的資料塊個數,小於 nmemb 或等於 0
備註 當返回小於 nmemb 時,檔案 stream 可能已達末尾,或者遇到錯誤
功能 將若干塊資料寫入指定的檔案
標頭檔案 <stdio.h>
原型 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
引數 ptr:自定義緩衝區指標
size:資料塊大小
nmemb:資料塊個數
stream:即將被寫入資料的檔案指標
返回值 成功:寫入的資料塊個數,等於 nmemb
失敗:寫入的資料塊個數,小於 nmemb 或等於 0
備註
  1. 如果fread()返回值小於nmemb時,則可能已達末尾,或者遇到錯誤,需要借於feof()/ferror()來加以進一步判斷。
  2. 當發生上述第1種情況時,其返回值並不能真正反映其讀取或者寫入的資料塊數, 而只是一個所謂的“截短值”,比如正常讀取5個資料塊,每個資料塊100個位元組,在執 行成功的情況下返回值是5,表示讀到5個資料塊總共500個位元組,但是如果只讀到499 個資料塊,那麼返回值就變成4,而如果讀到99個位元組,那麼fread()會返回0。因此當 發生返回值小於nmemb時,需要仔細確定究竟讀取了幾個位元組,而不能直接從返回值確定。

** 獲取或設定檔案當前位置偏移量 **

fseek ftell rewind

功能 設定指定檔案的當前位置偏移量
標頭檔案 <stdio.h>
原型 int fseek(FILE *stream, long offset, int whence);
引數 stream:需要設定位置偏移量的檔案指標
offset:新位置偏移量相對基準點的偏移
whence:基準點
- SEEK_SET:檔案開頭處
- SEEK_CUR:當前位置
- SEEK_END:檔案末尾處
返回值 成功:0
失敗:-1
備註
功能 獲取指定檔案的當前位置偏移量
標頭檔案 <stdio.h>
原型 long ftell(FILE *stream);
引數 stream:需要返回當前檔案位置偏移量的檔案指標
返回值 成功:當前檔案位置偏移量
失敗:-1
備註
功能 將指定檔案的當前位置偏移量設定到檔案開頭處
標頭檔案 <stdio.h>
原型 void rewind(FILE *stream);
引數 stream:需要設定位置偏移量的檔案指標
返回值
備註 該函式的功能是將檔案 stream 的位置偏移量置位到檔案開頭處

1,fseek()的用法基本上跟系統IO的lseek()是一致的。

2,rewind(fp)相等於fseek(fp,0L,SEEK_SE);

** 標準格式化IO函式 **

fprintf printf snprintf sprintf

功能 將格式化資料寫入指定的檔案或者記憶體
標頭檔案 <stdio.h>
原型 int fprintf(FILE *restrict stream, const char *restrict format, ...);
int printf(const char *restrict format, ...);
int snprintf(char *restrict s, size_t n, const char *restrict format, ...);
int sprintf(char *restrict s, const char *restrict format, ...);
引數 stream:寫入資料的檔案指標
format:格式控制串
s:寫入資料的自定義緩衝區
n:自定義緩衝區的大小
返回值 成功:成功寫入的位元組數
失敗:-1
備註

fscanf scanf sscanf

功能 從指定的檔案或者記憶體中讀取格式化資料
標頭檔案 <stdio.h>
原型 int fscanf(FILE *restrict stream, const char *restrict format, ...);
int scanf(const char *restrict format, ...);
int sscanf(const char *restrict s, const char *restrict format, ...);
引數 stream:讀出資料的檔案指標
format:格式控制串
s:讀出資料的自定義緩衝區
返回值 成功:正確匹配且賦值的資料個數
失敗:EOF
備註

1,fprintf()不僅可以像printf()一樣向標準輸出裝置輸出資訊,也可以向由stream
指定的任何有相應許可權的檔案寫入資料。
2,sprintf()snprintf()都是向一塊自定義緩衝區寫入資料,不同的是後者第二個參
數提供了這塊緩衝區的大小,避免緩衝區溢位,因此應儘量使用後者,放棄使用前者。
3,fscanf()不僅可以像scanf()一樣從標準輸入裝置讀入資訊,也可以從由stream
指定的任何有相應許可權的檔案讀入資料。
4,sscanf()從一塊由s指定的自定義緩衝區中讀入資料。
5,最重要的一條:這些函式的讀寫都是帶格式的,這些所謂的格式由下表規定:

格式控制符

格式控制符 含義 範例
%d 有符號十進位制整型數 int a = 1; printf("%d", a);
%u 無符號十進位制整型數 int a = 1; printf("%u", a);
%o 無符號八進位制整型數 int a = 1; printf("%o", a);
%x 無符號十六進位制整型數 int a = 1; printf("%x", a);
%c 字元 char a = 'd'; printf("%c", a);
%s 字串 char *a = "xy"; printf("%s", a);
%f 計數法單精度浮點數 float a = 1.0; printf("%f", a);
%e 科學技術法單精度浮點數 float a = 1.0; printf("%e", a);
%p 指標 int *a; printf("%p", a);
%.5s 取字串的前5個字元 char *a = "abcdefghijk"; printf("%.5s", a);
%.5f 取單精度浮點數小數點後5位小數 float a = 1.0; printf("%.5f", a);
%5d 位寬至少為5個字元,右對齊 int a = 1; printf("%5d", a);
%-5d 位寬至少為5個字元,左對齊 int a = 1; printf("%-5d", a);
%hd 半個有符號數十進位制整型數 short a = 1; printf("%hd", a);
%hhd 半半個有符號數十進位制整型數 char a = 1; printf("%hhd", a);
%lf 雙精度浮點數 double a = 1.0; printf("%lf", a);
%Le 科學技術法雙精度浮點數 double a = 1.0; printf("%Le", a);
%Lf 長雙精度浮點數 long double a = 1.0; printf("%Lf", a);
%Le 科學技術法長雙精度浮點數 long double a = 1.0; printf("%Le", a);

** 示例程式碼 **

  • 注意,這一組函式跟之前的標準IO最大的區別是帶有格式控制,因此最適用於有格式 的檔案處理,假設有一個檔案儲存了班級學生的姓名、性別、年齡和身高,如下:

image-20240515000814675

很明顯這個檔案是帶有格式的,假如我們需要將這個檔案讀入程式,再將之列印到螢幕
顯示出來,可以使用fscanf()fprintf()來實現:

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<unistd.h>
#include<string.h>
#include<strings.h>
#include<errno.h>
#include<sys/stat.h>
#include<sys/types.h>

#define NAMELEN 20

// 學生結構體
struct student {
    char name[NAMELEN];
    char sex;
    int age;
    float stature;
    struct student *next; // 用以形成連結串列
};

// 初始化連結串列
struct student *init_list(void) {
    struct student *head = malloc(sizeof(struct student));
    head->next = NULL;
    return head;
}

// 向連結串列中新增學生節點
void add_student(struct student *head, struct student *new) {
    struct student *tmp = head;
    while (tmp->next != NULL)
        tmp = tmp->next;
    tmp->next = new;
}

// 顯示連結串列中的所有學生資訊
void show_student(struct student *head) {
    struct student *tmp = head->next;
    while (tmp != NULL) {
        fprintf(stdout, "%-5s %c %d %.1f\n",
                tmp->name, tmp->sex, tmp->age, tmp->stature);
        tmp = tmp->next;
    }
}

int main(int argc, char **argv) {
    // 開啟檔案
    FILE *fp = fopen("format_data", "r");
    if (fp == NULL) {
        perror("Error opening file");
        return errno;
    }
    
    // 初始化連結串列
    struct student *head = init_list();
    int count = 0;
    // 從檔案中讀取資料並新增到連結串列中
    while (1) {
        struct student *new = malloc(sizeof(struct student));
        if (fscanf(fp, "%s %c %d %f", new->name, &(new->sex), &(new->age), &(new->stature)) == EOF) {
            break;
        }
        add_student(head, new);
        count++;
    }
    // 輸出新增的學生數
    printf("%d students have been added.\n", count);
    // 顯示所有學生資訊
    show_student(head);
    // 關閉檔案
    fclose(fp);
    return 0;
}

** 檔案屬性 **

--- 待補齊

相關文章