C語言-檔案讀寫

sleeeeeping發表於2024-05-20

C語言檔案讀寫

檔案分類:

二進位制檔案:把資料的補碼直接寫入檔案,這種檔案叫二進位制檔案。

​ 優點:讀寫和寫入時不需要進行轉換,所以讀寫速度快,資料安全性高。

​ 缺點:不能使用文字編譯器開啟,無法閱讀。

文字檔案:把資料轉換成字串寫入檔案,也就是把字元的二進位制寫入檔案,這種檔案叫文字檔案。

​ 優點:能被文字編輯器開啟,人類能看的懂,能夠看出資料是否出錯。

​ 缺點:讀寫時需要轉換,讀寫速度慢,資料有被修改的風險。

int num = 12306;
char ch = 'A';
二進位制檔案中的內容:
‭00000000 00000000 00110000 00010010‬
文字檔案中儲存的內容:
先把num的值轉換成字串"12306" 再把字串中的每個字元的ASCII值的二進位制寫入文字檔案
‭00110001‬‭ 00110010 ‭00110011 ‭00110000 ‭00110110
總結:二進位制檔案的大小是確定的,文字檔案會根據內容而變化

開啟/關閉檔案:

FILE *fopen(const char *path, const char *mode);
功能:開啟檔案
path:檔案的路徑
mode:檔案的開啟模式
返回值:檔案結構指標,是後續操作檔案的憑證,失敗則返回NULL。

int fclose(FILE *stream);
功能:關閉檔案
返回值:成功返回0,失敗返回-1。
注意:不能重複關閉,否則會出現double free錯誤,為了避免出現這種錯誤,在檔案關閉及時把檔案指標賦值為NULL,及時關閉檔案可以把緩衝區中的資料寫入到檔案中。

檔案的開啟模式:

"r" 以只讀方式開啟文字檔案,如果檔案不存在,或檔案沒有讀許可權則開啟失敗。 
"r+" 在"r"的基礎上增加了寫許可權。

"w" 以只寫方式開啟文字檔案,如果檔案不存在則建立,如果檔案存在則清空檔案的內容,如果檔案存在但沒有寫許可權,則開啟失敗。
"w+" 在"w"的基礎上增加了讀許可權。
 
"a" 以只寫方式開啟文字檔案,如果檔案不存在則建立,如果檔案存在則新寫入的內容追加到檔案末尾,如果檔案存在但沒有寫許可權,則開啟失敗。
"a+" 在"a"的基礎上增加了讀許可權。

如果要操作二進位制檔案,則在以上模式的基礎上增加b。
檔案的文字開啟方式和二進位制開啟方式的區別:

在 UNIX/Linux 平臺中,用文字方式或二進位制方式開啟檔案沒有任何區別。

在 UNIX/Linux 平臺中,文字檔案以\n(ASCII 碼為 0x0a)作為換行符號;而在 Windows 平臺中,文字檔案以連在一起的\r\n\r的 ASCII 碼是 0x0d)作為換行符號。

在 Windows 平臺中,如果以文字方式開啟檔案,當讀取檔案時,系統會將檔案中所有的\r\n轉換成一個字元\n,如果檔案中有連續的兩個位元組是 0x0d0a,則系統會丟棄前面的 0x0d 這個位元組,只讀入 0x0a。當寫入檔案時,系統會將\n轉換成\r\n寫入。

也就是說,如果要寫入的內容中有位元組為 0x0a,則在寫入該位元組前,系統會自動先寫入一個 0x0d。因此,如果用文字方式開啟二進位制檔案進行讀寫,讀寫的內容就可能和檔案的內容有出入。

因此,用二進位制方式開啟檔案總是最保險的。

文字檔案的讀寫:
int fprintf(FILE *stream, const char *format, ...);
功能:把若干個變數以文字格式寫入到指定的檔案中
stream:要寫入的檔案憑證,必須是fopen的返回值。
format:佔位符+跳脫字元+提示資訊
...:若干個變數
返回值:寫入字元的數量
int fscanf(FILE *stream, const char *format, ...);
功能:從檔案中讀取資料
stream:要讀取的檔案
format:佔位符
...:若干個變數的地址
返回值:成功讀取的變數個數
1、設計一個圖書結構體(書名、版本號、作者、出版社),定義一個圖書結構變數並初始化,以文字方式寫入book.txt檔案中
#include <stdio.h>
typedef struct Date {
    char ch;
    short sh;
    int in;
    double f;
} Date;
int main() {
    Date d = {'a', 33, 889988, 3.1415926};
    FILE* fwp = fopen("test.txt", "w");
    if (NULL == fwp){
        perror("fopen");
        return -1;
    }
    printf("檔案開啟成功\n");
    int ret = fprintf(fwp, "第一個%c %hd %d %lf\n", d.ch, d.sh, d.in, d.f);
    printf("ret = %d\n", ret);
}
2、從book.txt中讀取一條資料到圖書結構變數中
#include <stdio.h>                          
#include <stdlib.h>
typedef struct Date {
    char ch; 
    short sh; 
    int in; 
    double f;
} Date;

int main() {
    FILE* frp = fopen("test.txt", "r");
    if(NULL == frp) {   
        perror("fopen");
        return -1; 
    }   
    printf("檔案開啟成功\n");
    Date* p = malloc(sizeof(Date));
    int ret = fscanf(frp, "第一個%c %hd %d %lf", &p->ch, &p->sh, &p->in, &p->f);
    printf("ret=%d %c %hd %d %lf\n", ret, p->ch, p->sh+88, p->in, p->f);
}
給通訊錄專案增加資料持久化功能(載入資料、儲存資料)

二進位制檔案的讀寫:

size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
功能:把一塊記憶體當作陣列,然後陣列中的內容以二進位制格式寫入到檔案中
ptr:陣列首地址
size:陣列元素的位元組數
nmemb:陣列的長度
stream:要寫入的檔案
返回值:實際寫入的次數

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:把二進位制檔案中的內容讀取的陣列中
ptr:要儲存資料的陣列首地址
size:陣列元素的位元組數
nmemb:陣列的容量
返回值:成功讀取的次數
注意:以二進位制格式讀寫檔案時,最好加上mode最好包含b。
#include <stdio.h>
#include <stdlib.h>
#define  N 15
int arr[N];
int main() {
    for (int i = 0; i < N; ++i) {   
        arr[i] = rand() % 100;
    }
    FILE* fwp = fopen("arr.bin", "wb");
    if(NULL == fwp) {   
        perror("fopen");
        return -1; 
    }
    int ret = fwrite(arr, sizeof(arr[0]), N, fwp);
    printf("成功寫入%d個元素\n",ret);
    fclose(fwp);
    fwp = NULL;
}
#include <stdio.h>
#include <stdlib.h>
#define  N 15
int main() {
    FILE* frp = fopen("arr.bin", "rb");
    if(NULL == frp) {
        perror("fopen");
        return -1;
    }
    int* p = malloc(sizeof(p[0]) * N);
    if(NULL == p) {
        printf("p is NULL\n");
        return 0;
    }
    int ret = fread(p, sizeof(p[0]), N, frp);
    printf("ret=%d\n", ret);
    for (int i = 0; i < N; ++i) {
        printf("%d%c", p[i], " \n"[i == N - 1]);
    }
    fclose(frp);
    frp = NULL;
}
注意:

​ 如果以fwrite/fread讀寫的字元陣列,那麼我們操作的依然是文字檔案。

#include <stdio.h>                                
#include <stdlib.h>
#define  N 15
char arr[N];
int main(int argc,const char* argv[]) {
    for (int i = 0; i < N; ++i) {
        arr[i] = rand() % 26 + 65;
    }
    FILE* fwp = fopen("arr.bin", "wb");
    if (NULL == fwp) {
        perror("fopen");
        return -1;
    }
    int ret = fwrite(arr, sizeof(arr[0]), N, fwp);
    printf("成功寫入%d個元素\n", ret);
    fclose(fwp);
    fwp = NULL;
}
int sprintf(char *str, const char *format, ...);
功能:把若干個變數轉換成字串輸出到str陣列中

int sscanf(const char *str, const char *format, ...);
功能:從字串讀取若干個變數
#include <stdio.h>                       
#include <string.h>
typedef struct Data {
    char ch;
    short sh;
    int i;
    double d;
} Data;
int main() {
    Data d = {88, 1888, 99922233, 4.443322};
    char buf[256] = {};
    //  把資料轉換成字串
    sprintf(buf, "%hhd %hd %d %lf", d.ch, d.sh, d.i, d.d);
    FILE* fwp = fopen("data.bin", "wb");
    if (NULL == fwp) {
        perror("fopen");
        return -1;
    }
    int ret = fwrite(buf, sizeof(buf[0]), strlen(buf), fwp);
    printf("ret=%d\n", ret);
    fclose(fwp);
    fwp = NULL;
}
#include <stdio.h>                                      
#include <string.h>
typedef struct Data {
    char ch; 
    short sh; 
    int i;
    double d;
} Data;
int main() {
    FILE* frp = fopen("data.bin","rb");
    if (NULL == frp) { 
        perror("fopen");
        return -1; 
    }   
    Data d = {}; 
    char buf[256] = {}; 
    int ret = fread(buf, sizeof(buf[0]), sizeof(buf), frp);
    printf("ret=%d\n", ret);
    sscanf(buf, "%hhd %hd %d %lf", &d.ch, &d.sh, &d.i, &d.d);
    printf("%hhd %hd %d %lf\n", d.ch, d.sh, d.i, d.d);
    fclose(frp);
    frp = NULL;
}

檔案位置指標:

​ 檔案位置指標它指向檔案中即將要讀取的資料,以"r"、"r+"方式開啟檔案,檔案位置指標指向檔案的開頭,以"a"、"a+"方式開啟檔案,檔案位置指標指向檔案的末尾。(但是讀取可以在任意位置進行)

​ 讀取資料時會從 檔案位置指標指向 的地方開始讀取,寫入資料時也會寫入到檔案位置指標所指向的地址,並且它會隨著讀寫操作自動移動。

注意:fprintf/fwrite寫入資料後立即讀取,之所以會失敗,是因為檔案位置指標指向著檔案的末尾。

void rewind(FILE *stream);
功能:把檔案的位置指標調整到檔案的開頭。

long ftell(FILE *stream);
功能:返回檔案位置指標指向了檔案中的第幾個位元組

int fseek(FILE *stream, long offset, int whence);
功能:設定檔案的位置指標
stream:要設定的檔案
offset:偏移值(正負整數)
whence:基礎位置
	SEEK_SET 檔案開頭
	SEEK_CUR 當前位置
	SEEK_END 檔案末尾
whence+offset就是檔案指標最終設定的位置。
返回值:成功返回0,失敗返回-1。

檔案操作時的侷限性:

檔案的內容是連續儲存在磁碟上的,所以就導致需要進行以下操作:

向檔案中插入資料:

​ 1、檔案位置指標調整到要插入的位置。

​ 2、把後續的資料整體向後複製n(要插入的資料位元組數)個位元組。

​ 3、檔案位置指標調整到要插入的位置,寫入資料。

從檔案中刪除資料:

​ 1、檔案位置指標調整到要刪除的資料末尾。

​ 2、把後續的資料整體向前複製nn(要刪除的資料位元組數)個位元組。

​ 3、修改檔案的大小。

總結:所以,在程式執行時,建議在一開始就把檔案中的資料全部載入到記憶體中,程式在執行期間只針對這個資料記憶體進行增、刪、改、查等操作,在程式結束之前,再把資料從記憶體寫入到檔案中

檔案管理:

int remove(const char *pathname);
功能:刪除檔案

int rename(const char *oldpath, const char *newpath);
功能:重新命名檔案

int truncate(const char *path, off_t length);
功能:把檔案的內容設定為length位元組數

char *tmpnam(char *name);
功能:生成一個與當前檔案系統不重名的檔名。

int access(const char *pathname, int mode);
功能:檢查檔案的許可權
mode:
	R_OK 讀許可權
	W_OK 寫許可權   
	X_OK 執行許可權
	F_OK 檔案是否存在
返回值:
	檢查的許可權如果存在則返回0,不存在則返回-1。
實現一個mv命令
gcc xxx.c -o MV
./MV file1 file2   效果要等同於 mv file1 file2

以讀開啟file1,讀取資料到記憶體
以寫開啟file2,把從file1讀到的資料從記憶體中寫入到file2中
直到file1讀完寫完結束
刪除file1

main 函式的引數:
為了獲取命令列執行可執行檔案時後面附加的引數資訊
	argc: 代表命令列引數的個數 包括./可執行檔案 本身也算一個引數
	argv:一個儲存若干個字串的陣列,按順序儲存的是所有的命令列引數
#include <stdio.h>
#include <string.h>
#define N 1000010
char str[N];
int main(int argc, const char* argv[]) {
	// for (int i = 0; i < argc; ++i) {
	// 	printf("%s\n", argv[i]);
	// }
	if (strcmp(argv[0], "./MV") == 0) {
		// printf("True");
		FILE* frp = fopen(argv[1], "rb");
		FILE* fwp = fopen(argv[2], "wb");
		fread(str, sizeof str[0], sizeof str, frp);
		fwrite(str, sizeof str[0], sizeof str, fwp);
		fclose(frp), fclose(fwp);
		frp = NULL, fwp = NULL;
		remove(argv[1]);
	} else {
		printf("-1");
	}
}
2、給2048遊戲增加讀檔、存檔功能
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <getch.h>
#include <string.h>
#include <time.h>
#define N 4
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, 1, -1};
int d[N][N];
bool ok = false;
void clear_stdin() {
	stdin->_IO_read_ptr = stdin->_IO_read_end;
}
char get_op(const char start, const char end) {
	clear_stdin();
	printf("請輸入指定選項[%c,%c]\n", start, end);
	while (true) {
		char op = getch();
		if (start <= op && op <= end) {
			printf("%c\n", op);
			return op;
		}
	}
}
void erfile() {
	if (access("data2048.bin", F_OK) == 0) {
		remove("data2048.bin");
	}
}
void readbin(int* score, int* idx) {
	FILE* frp = fopen("data2048.bin", "rb");
	if (frp == NULL) {
		printf("當前沒有存檔,請選擇重新開始遊戲");
		exit(-1);
	}
	fread(d, sizeof(int), N * N, frp);
	fread(score, sizeof score, 1, frp);
	fread(idx, sizeof idx, 1, frp);
	fclose(frp);
	frp = NULL;
	erfile();
}
void writebin(int* score, int* idx) {
	FILE* fwp = fopen("data2048.bin", "wb");
	fwrite(d, sizeof(int), N * N, fwp);
	fwrite(score, sizeof score, 1, fwp);
	fwrite(idx, sizeof idx, 1, fwp);
	fclose(fwp);
	fwp = NULL;
}
void print() {
	system("clear");
	for (int i = 0; i < 9; ++i) {
		if (i % 2 == 0) {
			puts("-----------------------------");
		} else {
			for (int j = 0; j < 4; ++j) if (d[i / 2][j] != 0) {
				printf("|%6d", d[i / 2][j]);
			} else {
				printf("|      ");
			}
			printf("|\n");
		}
	}
	puts("輸入任意數字退出遊戲!!!");
}
bool check() {
	int cnt = 0;
	ok = false;
	for (int i = 0; i < 4; ++i) {
		for (int j = 0; j < 4; ++j) {
			cnt += d[i][j] == 0;
			for (int dir = 0; dir < 4; ++dir) {
				int fx = i + dx[dir], fy = j + dy[dir];
				if (fx >= 0 && fy >= 0 && fx < 4 && fy < 4 && d[fx][fy] == d[i][j] && d[i][j]) {
					ok = true;
					return true;
				}
			}
		}
	}
	return cnt != 0;
}
void get() {
	if (ok) {
		return ;
	}
	int w = rand() % 2;
	w = 1 << w + 1;
	int x = rand() % 4, y = rand() % 4;
	while (d[x][y] != 0) {
		x = rand() % 4, y = rand() % 4;
	}
	d[x][y] = w;
}
int move() {
	int dir = getch() - 183, score = 0;
	if (dir + 183 >= '0' && dir + 183 <= '9') {
		return -1;
	}
	int cpd[4][4], cp[4][4];
	memcpy(cp, d, sizeof d);
	if (dir == 0) {
		for (int j = 0; j < 4; ++j) {
			for (int k = 0; k < 4; ++k) {
				for (int i = 1; i < 4; ++i) if (d[i - 1][j] == 0 && d[i][j]) {
					d[i - 1][j] ^= d[i][j] ^= d[i - 1][j] ^= d[i][j];
				}
			}
		}
		memcpy(cpd, d, sizeof d);
		for (int j = 0; j < 4; ++j) {
			for (int i = 0; i < 4; ++i) {
				int fx = dx[dir] + i, fy = dy[dir] + j;
				if (fx >= 0 && fy >= 0 && fx < 4 && fy < 4 && cpd[fx][fy] == cpd[i][j] && cpd[fx][fy]) {
					d[fx][fy] += d[i][j];
					d[i][j] = 0;
					score += d[fx][fy];
				}

			}
		}
		for (int j = 0; j < 4; ++j) {
			for (int k = 0; k < 4; ++k) {
				for (int i = 1; i < 4; ++i) if (d[i - 1][j] == 0 && d[i][j]) {
					d[i - 1][j] ^= d[i][j] ^= d[i - 1][j] ^= d[i][j];
				}
			}
		}
	} else if (dir == 1) {
		for (int j = 3; j >= 0; --j) {
			for (int k = 0; k < 4; ++k) {
				for (int i = 2; i >= 0; --i) if (d[i + 1][j] == 0 && d[i][j]) {
					d[i + 1][j] ^= d[i][j] ^= d[i + 1][j] ^= d[i][j];
				}
			}
		}
		memcpy(cpd, d, sizeof d);
		for (int j = 3; j >= 0; --j) {
			for (int i = 3; i >= 0; --i) {
				int fx = dx[dir] + i, fy = dy[dir] + j;
				if (fx >= 0 && fy >= 0 && fx < 4 && fy < 4 && cpd[fx][fy] == cpd[i][j] && cpd[fx][fy]) {
					d[fx][fy] += d[i][j];
					d[i][j] = 0;
					score += d[fx][fy];
				}
			}
		}
		for (int j = 3; j >= 0; --j) {
			for (int k = 0; k < 4; ++k) {
				for (int i = 2; i >= 0; --i) if (d[i + 1][j] == 0 && d[i][j]) {
					d[i + 1][j] ^= d[i][j] ^= d[i + 1][j] ^= d[i][j];
				}
			}
		}
	} else if (dir == 2) {
		for (int i = 3; i >= 0; --i) {
			for (int k = 0; k < 4; ++k) {
				for (int j = 2; j >= 0; --j) if (d[i][j + 1] == 0 && d[i][j]) {
					d[i][j + 1] ^= d[i][j] ^= d[i][j + 1] ^= d[i][j];	
				}
			}
		}
		memcpy(cpd, d, sizeof d);
		for (int i = 3; i >= 0; --i) {
			for (int j = 3; j >= 0; --j) {
				int fx = dx[dir] + i, fy = dy[dir] + j;
				if (fx >= 0 && fy >= 0 && fx < 4 && fy < 4 && cpd[fx][fy] == cpd[i][j] && cpd[fx][fy]) {
					d[fx][fy] += d[i][j];
					d[i][j] = 0;
					score += d[fx][fy];
				}
			}
		}
		for (int i = 3; i >= 0; --i) {
			for (int k = 0; k < 4; ++k) {
				for (int j = 2; j >= 0; --j) if (d[i][j + 1] == 0 && d[i][j]) {
					d[i][j + 1] ^= d[i][j] ^= d[i][j + 1] ^= d[i][j];
				}
			}
		}
	} else if (dir == 3) {
		for (int i = 0; i < 4; ++i) {
			for (int k = 0; k < 4; ++k) {
				for (int j = 1; j < 4; ++j) if (d[i][j - 1] == 0 && d[i][j]) {
					d[i][j - 1] ^= d[i][j] ^= d[i][j - 1] ^= d[i][j];
				}
			}
		}
		memcpy(cpd, d, sizeof d);
		for (int i = 0; i < 4; ++i) {
			for (int j = 0; j < 4; ++j) {
				int fx = dx[dir] + i, fy = dy[dir] + j;
				if (fx >= 0 && fy >= 0 && fx < 4 && fy < 4 && cpd[fx][fy] == cpd[i][j] && cpd[fx][fy]) {
					d[fx][fy] += d[i][j];
					d[i][j] = 0;
					score += d[fx][fy];
				}
			}
		}
		for (int i = 0; i < 4; ++i) {
			for (int k = 0; k < 4; ++k) {
				for (int j = 1; j < 4; ++j) if (d[i][j - 1] == 0 && d[i][j]) {
					d[i][j - 1] ^= d[i][j] ^= d[i][j - 1] ^= d[i][j];
				}
			}
		}
	}
	ok = true;
	for (int i = 0; i < 4; ++i) {
		for (int j = 0; j < 4; ++j) if (cp[i][j] != d[i][j]) {
			ok = false;
		}
	}
	return score;
}
char menu() {
	system("clear");
	puts("==============2048===============");
	puts("1.新的遊戲         2.繼續上次    ");
	puts("3.退出系統                       ");
	puts("=================================");
	return get_op('1', '3');
}
char menu_save() {
	system("clear");
	puts("==========是否繼續遊戲===========");
	puts("1.儲存並退出遊戲 2.不儲存退出遊戲");
	puts("3.繼續遊戲                       ");
	puts("=================================");
	return get_op('1', '3');
}
void work(int score, int idx, bool isnew) {
	if (isnew) get();
	print();
	while (check()) {
		idx += 1;
		int suc = move();
		if (suc != -1) {
			score += suc;
		} else {
			int op = menu_save() - '0';
			if (op == 1) {
				writebin(&score, &idx);
				return ;
			} else if (op == 2) {
				break;
			}
		}
		get(), print();
	}
	erfile();
	printf("遊戲已經結束,您的得分是%d, 共用了%d步\n", score, idx);
}
int main() {
	srand(time(0));
	int op = menu() - '0';
	int score = 0, idx = 0;
	if (op == 1) {
		work(score, idx, true);
	} else if (op == 2) {
		readbin(&score, &idx);	
		work(score, idx, false);
	}
}

相關文章