C语言文件操作

概述

本文档详细介绍C语言中文件操作的使用方法,包括文件的打开和关闭、文本文件的读写、二进制文件的读写、文件定位以及错误处理等内容。文件操作是C语言中重要的功能之一,它允许程序与外部存储设备进行数据交换,实现数据的持久化存储。

文件操作的基本概念

文件指针

在C语言中,文件操作通过文件指针来进行,文件指针是一个指向FILE类型的指针变量。FILE是C语言标准库中定义的一个结构体,用于存储文件相关的信息。

文件打开模式

在打开文件时,需要指定文件的打开模式,常用的文件打开模式如下:

模式 说明
"r" 只读模式,打开已存在的文本文件
"w" 只写模式,创建新的文本文件或截断已存在的文本文件
"a" 追加模式,向文本文件末尾添加数据
"rb" 只读模式,打开已存在的二进制文件
"wb" 只写模式,创建新的二进制文件或截断已存在的二进制文件
"ab" 追加模式,向二进制文件末尾添加数据
"r+" 读写模式,打开已存在的文本文件
"w+" 读写模式,创建新的文本文件或截断已存在的文本文件
"a+" 读写模式,打开已存在的文本文件并在末尾追加数据
"rb+" 读写模式,打开已存在的二进制文件
"wb+" 读写模式,创建新的二进制文件或截断已存在的二进制文件
"ab+" 读写模式,打开已存在的二进制文件并在末尾追加数据

文件的打开与关闭

使用fopen函数打开文件,使用fclose函数关闭文件:

1
2
FILE *fopen(const char *filename, const char *mode);
int fclose(FILE *stream);

创建和写入文本文件

文本文件写入函数

C语言提供了多种写入文本文件的函数:

函数 功能 示例
fprintf 格式化写入 fprintf(file, "姓名:%s\n", name)
fputs 写入字符串 fputs("Hello\n", file)
fputc 写入单个字符 fputc('A', file)
fwrite 二进制写入(也可用于文本文件) fwrite(buffer, sizeof(char), length, file)

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 创建和写入文本文件
void create_text_file() {
FILE *file;

// 以写入模式打开文件(如果文件不存在则创建,存在则截断)
file = fopen("test.txt", "w");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 使用fprintf写入格式化数据
fprintf(file, "姓名:张三\n");
fprintf(file, "年龄:%d\n", 25);
fprintf(file, "成绩:%.2f\n", 95.5);

// 使用fputs写入字符串
fputs("这是使用fputs写入的一行文本。\n", file);
fputs("这是第二行。\n", file);

// 使用fputc写入单个字符
fputc('A', file);
fputc('B', file);
fputc('C', file);
fputc('\n', file);

// 关闭文件
fclose(file);

printf("文本文件创建并写入成功!\n");
}

补充示例:向文件追加内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 向文本文件追加内容
void append_to_text_file() {
FILE *file;

// 以追加模式打开文件
file = fopen("test.txt", "a");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 追加内容
fprintf(file, "\n=== 追加的内容 ===\n");
fprintf(file, "这是追加的第一行。\n");
fprintf(file, "这是追加的第二行。\n");

// 关闭文件
fclose(file);

printf("内容追加成功!\n");
}

读取文本文件

文本文件读取函数

C语言提供了多种读取文本文件的函数:

函数 功能 示例
fscanf 格式化读取 fscanf(file, "姓名:%s", name)
fgets 读取一行字符串 fgets(buffer, sizeof(buffer), file)
fgetc 读取单个字符 fgetc(file)
fread 二进制读取(也可用于文本文件) fread(buffer, sizeof(char), length, file)

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 读取文本文件
void read_text_file() {
FILE *file;
char buffer[256];
char ch;

// 以读取模式打开文件
file = fopen("test.txt", "r");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

printf("=== 使用fgets读取文件内容 ===\n");
// 使用fgets逐行读取
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer);
}

// 重置文件指针到开头
rewind(file);

printf("\n=== 使用fgetc读取文件内容 ===\n");
// 使用fgetc逐个字符读取
while ((ch = fgetc(file)) != EOF) {
putchar(ch);
}

// 重置文件指针到开头
rewind(file);

printf("\n=== 使用fscanf读取格式化数据 ===\n");
char name[50];
int age;
float score;

// 使用fscanf读取格式化数据
fscanf(file, "姓名:%s\n", name); // 读取姓名,直到换行符
fscanf(file, "年龄:%d\n", &age); // 读取年龄
fscanf(file, "成绩:%f\n", &score); // 读取成绩

printf("姓名:%s\n", name);
printf("年龄:%d\n", age);
printf("成绩:%.2f\n", score);

// 关闭文件
fclose(file);
}

补充示例:读取带空格的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 读取带空格的字符串
void read_string_with_spaces() {
FILE *file;
char buffer[256];

// 以读取模式打开文件
file = fopen("test.txt", "r");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 读取带空格的字符串
printf("=== 读取带空格的字符串 ===\n");

// 使用fgets读取整行,包括空格
while (fgets(buffer, sizeof(buffer), file) != NULL) {
// 移除换行符
buffer[strcspn(buffer, "\n")] = '\0';
printf("读取的行:%s\n", buffer);
}

// 关闭文件
fclose(file);
}

二进制文件操作

二进制文件的特点

二进制文件与文本文件的主要区别在于:

  • 文本文件以字符形式存储数据,每个字符对应一个或多个字节
  • 二进制文件以数据的原始二进制形式存储,直接反映数据在内存中的表示

二进制文件读写函数

二进制文件通常使用fwritefread函数进行读写:

1
2
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 测试结构体(用于二进制文件操作)
struct Person {
char name[50];
int age;
float height;
};

// 二进制文件操作
void binary_file_operations() {
FILE *file;

// 创建测试数据
struct Person persons[] = {
{"张三", 25, 175.5},
{"李四", 30, 180.0},
{"王五", 28, 172.3}
};

int num_persons = sizeof(persons) / sizeof(persons[0]);

// =========================
// 写入二进制文件
// =========================
printf("写入二进制文件...\n");

// 以二进制写入模式打开文件
file = fopen("persons.dat", "wb");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 使用fwrite写入数据
fwrite(persons, sizeof(struct Person), num_persons, file);

fclose(file);

// =========================
// 读取二进制文件
// =========================
printf("读取二进制文件...\n");

// 以二进制读取模式打开文件
file = fopen("persons.dat", "rb");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 创建一个数组来存储读取的数据
struct Person read_persons[3];

// 使用fread读取数据
fread(read_persons, sizeof(struct Person), num_persons, file);

// 打印读取的数据
for (int i = 0; i < num_persons; i++) {
printf("\nPerson %d:\n", i + 1);
printf("姓名:%s\n", read_persons[i].name);
printf("年龄:%d\n", read_persons[i].age);
printf("身高:%.2f\n", read_persons[i].height);
}

fclose(file);
}

补充示例:追加到二进制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 追加到二进制文件
void append_to_binary_file() {
FILE *file;

// 以二进制追加模式打开文件
file = fopen("persons.dat", "ab");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 要追加的数据
struct Person new_person = {"赵六", 35, 178.8};

// 追加数据
fwrite(&new_person, sizeof(struct Person), 1, file);

fclose(file);

printf("数据追加成功!\n");

// 验证追加结果
printf("验证追加结果...\n");

file = fopen("persons.dat", "rb");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 移动文件指针到末尾,计算文件中的记录数
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
int total_persons = file_size / sizeof(struct Person);

// 重置文件指针到开头
rewind(file);

// 读取所有数据
struct Person *all_persons = (struct Person *)malloc(file_size);
fread(all_persons, sizeof(struct Person), total_persons, file);

// 打印所有数据
for (int i = 0; i < total_persons; i++) {
printf("\nPerson %d:\n", i + 1);
printf("姓名:%s\n", all_persons[i].name);
printf("年龄:%d\n", all_persons[i].age);
printf("身高:%.2f\n", all_persons[i].height);
}

// 释放内存
free(all_persons);
fclose(file);
}

文件定位

文件定位函数

C语言提供了以下文件定位函数:

函数 功能 示例
fseek 移动文件指针到指定位置 fseek(file, 0, SEEK_SET)
ftell 获取当前文件指针位置 long position = ftell(file)
rewind 将文件指针重置到文件开头 rewind(file)
fgetpos 获取当前文件指针位置(更精确) fgetpos(file, &pos)
fsetpos 设置文件指针位置(更精确) fsetpos(file, &pos)

whence参数

fseek函数的第三个参数whence指定了偏移量的参考位置:

说明
SEEK_SET 文件开头
SEEK_CUR 当前位置
SEEK_END 文件末尾

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 文件定位示例
void file_positioning() {
FILE *file;
char buffer[256];

// 以读取模式打开文件
file = fopen("test.txt", "r");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 获取当前文件指针位置
long position = ftell(file);
printf("初始文件指针位置:%ld\n", position);

// 读取一行
fgets(buffer, sizeof(buffer), file);
printf("读取的第一行:%s", buffer);

// 获取当前文件指针位置
position = ftell(file);
printf("读取第一行后位置:%ld\n", position);

// 使用fseek移动文件指针到指定位置
fseek(file, 0, SEEK_SET); // 移动到文件开头
position = ftell(file);
printf("移动到文件开头后位置:%ld\n", position);

// 再次读取第一行
fgets(buffer, sizeof(buffer), file);
printf("再次读取的第一行:%s", buffer);

// 移动到文件末尾
fseek(file, 0, SEEK_END);
position = ftell(file);
printf("移动到文件末尾后位置:%ld\n", position);

// 向前移动10个字节
fseek(file, -10, SEEK_CUR);
position = ftell(file);
printf("向前移动10个字节后位置:%ld\n", position);

// 读取剩余内容
printf("从当前位置读取的内容:\n");
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer);
}

// 使用rewind重置文件指针到开头
rewind(file);
position = ftell(file);
printf("rewind后文件指针位置:%ld\n", position);

// 关闭文件
fclose(file);
}

补充示例:随机访问文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 随机访问文件
void random_access_file() {
FILE *file;
char buffer[256];

// 以读写模式打开文件
file = fopen("test.txt", "r+");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 移动到文件中间位置
fseek(file, 50, SEEK_SET);
printf("移动到位置50后,当前位置:%ld\n", ftell(file));

// 读取从当前位置开始的内容
printf("从位置50读取的内容:\n");
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer);
}

// 移动到文件开头并写入内容
fseek(file, 0, SEEK_SET);
fprintf(file, "=== 覆盖的内容 ===\n");

// 关闭文件
fclose(file);

printf("文件随机访问操作完成!\n");
}

错误处理

错误处理函数

C语言提供了以下文件错误处理函数:

函数 功能 示例
ferror 检查文件操作是否发生错误 if (ferror(file)) { /* 处理错误 */ }
feof 检查是否到达文件末尾 if (feof(file)) { /* 到达文件末尾 */ }
perror 打印系统错误信息 perror("fopen")
clearerr 清除文件错误标志 clearerr(file)

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 错误处理示例
void error_handling() {
FILE *file;
char ch;

// 尝试打开一个不存在的文件
printf("尝试打开一个不存在的文件:\n");
file = fopen("non_existent_file.txt", "r");

if (file == NULL) {
perror("fopen"); // 打印系统错误信息
printf("无法打开文件!\n");
} else {
fclose(file);
}

// 正确打开文件并演示ferror和feof
printf("\n演示ferror和feof函数:\n");

file = fopen("test.txt", "r");

if (file == NULL) {
perror("fopen");
return;
}

// 读取文件内容
printf("读取文件内容:\n");
while ((ch = fgetc(file)) != EOF) {
putchar(ch);
}

// 检查是否到达文件末尾
if (feof(file)) {
printf("\n已到达文件末尾(feof返回非零值)\n");
}

// 检查是否发生错误
if (ferror(file)) {
printf("读取文件时发生错误\n");
} else {
printf("文件读取成功,无错误\n");
}

// 清除错误标志
clearerr(file);

// 再次检查错误标志
if (ferror(file)) {
printf("错误标志未清除\n");
} else {
printf("错误标志已清除\n");
}

// 关闭文件
fclose(file);
}

补充示例:处理文件读写错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 处理文件读写错误
void handle_file_errors() {
FILE *file;
char buffer[256];

// 尝试写入文件
file = fopen("protected_file.txt", "w");

if (file == NULL) {
perror("无法打开文件进行写入");
return;
}

// 尝试写入数据
if (fputs("测试数据\n", file) == EOF) {
perror("写入文件时发生错误");
} else {
printf("数据写入成功\n");
}

// 关闭文件
if (fclose(file) == EOF) {
perror("关闭文件时发生错误");
} else {
printf("文件关闭成功\n");
}

// 尝试读取文件
file = fopen("protected_file.txt", "r");

if (file == NULL) {
perror("无法打开文件进行读取");
return;
}

// 尝试读取数据
if (fgets(buffer, sizeof(buffer), file) == NULL) {
if (feof(file)) {
printf("文件为空\n");
} else {
perror("读取文件时发生错误");
}
} else {
printf("读取的数据:%s", buffer);
}

// 关闭文件
if (fclose(file) == EOF) {
perror("关闭文件时发生错误");
} else {
printf("文件关闭成功\n");
}
}

文件操作的高级应用

文件复制

文件复制是一种常见的文件操作,它将一个文件的内容复制到另一个文件。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 文件复制函数
void copy_file(const char *source_path, const char *destination_path) {
FILE *source_file, *dest_file;
char buffer[1024];
size_t bytes_read;

// 打开源文件
source_file = fopen(source_path, "rb");
if (source_file == NULL) {
perror("无法打开源文件");
return;
}

// 创建目标文件
dest_file = fopen(destination_path, "wb");
if (dest_file == NULL) {
perror("无法创建目标文件");
fclose(source_file);
return;
}

// 复制文件内容
while ((bytes_read = fread(buffer, 1, sizeof(buffer), source_file)) > 0) {
if (fwrite(buffer, 1, bytes_read, dest_file) != bytes_read) {
perror("写入目标文件时发生错误");
fclose(source_file);
fclose(dest_file);
return;
}
}

// 检查读取过程中是否发生错误
if (ferror(source_file)) {
perror("读取源文件时发生错误");
} else {
printf("文件复制成功!\n");
}

// 关闭文件
fclose(source_file);
fclose(dest_file);
}

文件加密/解密

文件加密/解密是一种保护文件内容的方法,它通过某种算法改变文件的内容,使得只有知道密钥的人才能读取。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 简单的文件加密/解密函数(使用异或操作)
void encrypt_decrypt_file(const char *input_path, const char *output_path, char key) {
FILE *input_file, *output_file;
int ch;

// 打开输入文件
input_file = fopen(input_path, "rb");
if (input_file == NULL) {
perror("无法打开输入文件");
return;
}

// 创建输出文件
output_file = fopen(output_path, "wb");
if (output_file == NULL) {
perror("无法创建输出文件");
fclose(input_file);
return;
}

// 加密/解密文件内容
while ((ch = fgetc(input_file)) != EOF) {
// 使用异或操作进行加密/解密
ch ^= key;
fputc(ch, output_file);
}

// 检查读取过程中是否发生错误
if (ferror(input_file)) {
perror("读取输入文件时发生错误");
} else {
printf("文件加密/解密成功!\n");
}

// 关闭文件
fclose(input_file);
fclose(output_file);
}

补充示例:文件行号统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 统计文件的行数
int count_lines(const char *filename) {
FILE *file;
int count = 0;
int ch;

// 打开文件
file = fopen(filename, "r");
if (file == NULL) {
perror("无法打开文件");
return -1;
}

// 统计行数
while ((ch = fgetc(file)) != EOF) {
if (ch == '\n') {
count++;
}
}

// 检查文件是否以换行符结束
if (count > 0) {
fseek(file, -1, SEEK_END);
if (fgetc(file) != '\n') {
count++;
}
}

// 关闭文件
fclose(file);

return count;
}

// 使用行数统计函数
void test_count_lines() {
const char *filename = "test.txt";
int lines = count_lines(filename);

if (lines >= 0) {
printf("文件 %s 包含 %d 行\n", filename, lines);
}
}

文件操作的最佳实践

  1. 始终检查文件打开是否成功:使用fopen函数后,一定要检查返回值是否为NULL
  2. 使用完毕后关闭文件:使用fclose函数关闭文件,释放系统资源
  3. 处理文件操作错误:使用ferrorfeof等函数检查文件操作是否成功
  4. 使用适当的文件打开模式:根据实际需求选择正确的文件打开模式
  5. 注意文件指针的位置:在进行文件读写操作时,要注意文件指针的当前位置
  6. 使用二进制模式处理二进制文件:对于二进制文件,使用rbwb等二进制模式
  7. 合理使用缓冲区:使用适当大小的缓冲区进行文件读写,提高效率
  8. 避免文件锁定:尽量减少文件打开的时间,避免长时间占用文件
  9. 处理大文件时注意内存:对于大文件,应分块读写,避免一次性加载到内存
  10. 使用临时文件:对于需要修改的文件,考虑使用临时文件,避免直接修改原文件

综合示例

示例:学生信息管理系统(文件版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// 学生信息管理系统(文件版)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 学生结构体
typedef struct {
char name[50];
int id;
float score;
} Student;

// 函数原型
void add_student();
void view_students();
void update_student();
void delete_student();
void save_students(Student students[], int count);
int load_students(Student students[]);

#define MAX_STUDENTS 100

int main() {
int choice;

do {
printf("\n学生信息管理系统\n");
printf("1. 添加学生\n");
printf("2. 查看学生\n");
printf("3. 更新学生信息\n");
printf("4. 删除学生\n");
printf("0. 退出\n");
printf("请选择:");
scanf("%d", &choice);

switch (choice) {
case 1:
add_student();
break;
case 2:
view_students();
break;
case 3:
update_student();
break;
case 4:
delete_student();
break;
case 0:
printf("退出系统\n");
break;
default:
printf("无效选择\n");
}
} while (choice != 0);

return 0;
}

// 添加学生
void add_student() {
Student students[MAX_STUDENTS];
int count = load_students(students);

if (count >= MAX_STUDENTS) {
printf("学生数量已达到上限\n");
return;
}

// 输入学生信息
printf("请输入学生姓名:");
scanf("%s", students[count].name);
printf("请输入学生ID:");
scanf("%d", &students[count].id);
printf("请输入学生成绩:");
scanf("%f", &students[count].score);

count++;
save_students(students, count);

printf("学生添加成功\n");
}

// 查看学生
void view_students() {
Student students[MAX_STUDENTS];
int count = load_students(students);

if (count == 0) {
printf("没有学生信息\n");
return;
}

printf("\n学生信息:\n");
for (int i = 0; i < count; i++) {
printf("姓名:%s, ID:%d, 成绩:%.1f\n", students[i].name, students[i].id, students[i].score);
}
}

// 更新学生信息
void update_student() {
Student students[MAX_STUDENTS];
int count = load_students(students);
int id, found = 0;

if (count == 0) {
printf("没有学生信息\n");
return;
}

printf("请输入要更新的学生ID:");
scanf("%d", &id);

for (int i = 0; i < count; i++) {
if (students[i].id == id) {
printf("找到学生:%s\n", students[i].name);
printf("请输入新的姓名:");
scanf("%s", students[i].name);
printf("请输入新的成绩:");
scanf("%f", &students[i].score);
found = 1;
break;
}
}

if (found) {
save_students(students, count);
printf("学生信息更新成功\n");
} else {
printf("未找到ID为%d的学生\n", id);
}
}

// 删除学生
void delete_student() {
Student students[MAX_STUDENTS];
int count = load_students(students);
int id, found = 0;

if (count == 0) {
printf("没有学生信息\n");
return;
}

printf("请输入要删除的学生ID:");
scanf("%d", &id);

for (int i = 0; i < count; i++) {
if (students[i].id == id) {
// 从当前位置开始,后面的元素向前移动
for (int j = i; j < count - 1; j++) {
students[j] = students[j + 1];
}
found = 1;
count--;
break;
}
}

if (found) {
save_students(students, count);
printf("学生删除成功\n");
} else {
printf("未找到ID为%d的学生\n", id);
}
}

// 保存学生信息到文件
void save_students(Student students[], int count) {
FILE *file = fopen("students.dat", "wb");

if (file == NULL) {
perror("无法打开文件进行写入");
return;
}

fwrite(&count, sizeof(int), 1, file);
fwrite(students, sizeof(Student), count, file);
fclose(file);
}

// 从文件加载学生信息
int load_students(Student students[]) {
FILE *file = fopen("students.dat", "rb");
int count = 0;

if (file == NULL) {
// 文件不存在,返回0
return 0;
}

fread(&count, sizeof(int), 1, file);
fread(students, sizeof(Student), count, file);
fclose(file);

return count;
}

总结

C语言中的文件操作是实现数据持久化的重要手段,通过本文档的学习,我们了解了:

  1. 文件操作的基本概念:文件指针、文件打开模式等
  2. 创建和写入文本文件:使用fprintffputsfputc等函数
  3. 读取文本文件:使用fscanffgetsfgetc等函数
  4. 二进制文件操作:使用fwritefread函数
  5. 文件定位:使用fseekftellrewind等函数
  6. 错误处理:使用ferrorfeofperror等函数
  7. 文件操作的高级应用:文件复制、加密/解密、行号统计等
  8. 文件操作的最佳实践:检查文件打开是否成功、关闭文件、处理错误等

掌握文件操作对于编写实用的C程序至关重要,它允许程序与外部存储设备进行数据交换,实现数据的持久化存储。通过合理使用文件操作函数,我们可以编写出更加灵活、功能强大的C程序。

file_operations.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
* @brief 文件操作示例程序
* @details 这个程序演示了C语言中各种文件操作,包括文件的打开和关闭、
* 文本文件的读写、二进制文件的读写、文件定位以及错误处理等。
* @return 0 表示程序成功执行
*/

// =========================
// 函数原型声明
// =========================
void create_text_file(); // 创建和写入文本文件
void read_text_file(); // 读取文本文件
void binary_file_operations(); // 二进制文件操作
void file_positioning(); // 文件定位示例
void error_handling(); // 错误处理示例

/**
* @brief 测试结构体(用于二进制文件操作)
*/
struct Person {
char name[50];
int age;
float height;
};

/**
* @brief 主函数
* @return 0 表示程序成功执行
*/
int main() {
// =========================
// 创建和写入文本文件
// =========================
printf("=== 创建和写入文本文件 ===\n");
create_text_file();

printf("\n");

// =========================
// 读取文本文件
// =========================
printf("=== 读取文本文件 ===\n");
read_text_file();

printf("\n");

// =========================
// 二进制文件操作
// =========================
printf("=== 二进制文件操作 ===\n");
binary_file_operations();

printf("\n");

// =========================
// 文件定位
// =========================
printf("=== 文件定位 ===\n");
file_positioning();

printf("\n");

// =========================
// 错误处理
// =========================
printf("=== 错误处理 ===\n");
error_handling();

return 0;
}

/**
* @brief 创建和写入文本文件
* @details 演示使用fopen、fprintf、fputs和fclose函数
*/
void create_text_file() {
FILE *file;

// 以写入模式打开文件(如果文件不存在则创建,存在则截断)
file = fopen("test.txt", "w");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 使用fprintf写入格式化数据
fprintf(file, "姓名:张三\n");
fprintf(file, "年龄:%d\n", 25);
fprintf(file, "成绩:%.2f\n", 95.5);

// 使用fputs写入字符串
fputs("这是使用fputs写入的一行文本。\n", file);
fputs("这是第二行。\n", file);

// 使用fputc写入单个字符
fputc('A', file);
fputc('B', file);
fputc('C', file);
fputc('\n', file);

// 关闭文件
fclose(file);

printf("文本文件创建并写入成功!\n");
}

/**
* @brief 读取文本文件
* @details 演示使用fopen、fscanf、fgets、fgetc和fclose函数
*/
void read_text_file() {
FILE *file;
char buffer[256];
char ch;

// 以读取模式打开文件
file = fopen("test.txt", "r");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

printf("=== 使用fgets读取文件内容 ===\n");
// 使用fgets逐行读取
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer);
}

// 重置文件指针到开头
rewind(file);

printf("\n=== 使用fgetc读取文件内容 ===\n");
// 使用fgetc逐个字符读取
while ((ch = fgetc(file)) != EOF) {
putchar(ch);
}

// 重置文件指针到开头
rewind(file);

printf("\n=== 使用fscanf读取格式化数据 ===\n");
char name[50];
int age;
float score;

// 使用fscanf读取格式化数据
fscanf(file, "姓名:%s\n", name); // 读取姓名,直到换行符
fscanf(file, "年龄:%d\n", &age); // 读取年龄
fscanf(file, "成绩:%f\n", &score); // 读取成绩

printf("姓名:%s\n", name);
printf("年龄:%d\n", age);
printf("成绩:%.2f\n", score);

// 关闭文件
fclose(file);
}

/**
* @brief 二进制文件操作
* @details 演示使用fwrite和fread函数进行二进制文件操作
*/
void binary_file_operations() {
FILE *file;

// 创建测试数据
struct Person persons[] = {
{"张三", 25, 175.5},
{"李四", 30, 180.0},
{"王五", 28, 172.3}
};

int num_persons = sizeof(persons) / sizeof(persons[0]);

// =========================
// 写入二进制文件
// =========================
printf("写入二进制文件...\n");

// 以二进制写入模式打开文件
file = fopen("persons.dat", "wb");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 使用fwrite写入数据
fwrite(persons, sizeof(struct Person), num_persons, file);

fclose(file);

// =========================
// 读取二进制文件
// =========================
printf("读取二进制文件...\n");

// 以二进制读取模式打开文件
file = fopen("persons.dat", "rb");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 创建一个数组来存储读取的数据
struct Person read_persons[3];

// 使用fread读取数据
fread(read_persons, sizeof(struct Person), num_persons, file);

// 打印读取的数据
for (int i = 0; i < num_persons; i++) {
printf("\nPerson %d:\n", i + 1);
printf("姓名:%s\n", read_persons[i].name);
printf("年龄:%d\n", read_persons[i].age);
printf("身高:%.2f\n", read_persons[i].height);
}

fclose(file);

// =========================
// 追加到二进制文件
// =========================
printf("\n追加到二进制文件...\n");

// 以二进制追加模式打开文件
file = fopen("persons.dat", "ab");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 要追加的数据
struct Person new_person = {"赵六", 35, 178.8};

// 追加数据
fwrite(&new_person, sizeof(struct Person), 1, file);

fclose(file);

// 验证追加结果
printf("验证追加结果...\n");

file = fopen("persons.dat", "rb");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 移动文件指针到末尾,计算文件中的记录数
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
int total_persons = file_size / sizeof(struct Person);

// 重置文件指针到开头
rewind(file);

// 读取所有数据
struct Person *all_persons = (struct Person *)malloc(file_size);
fread(all_persons, sizeof(struct Person), total_persons, file);

// 打印所有数据
for (int i = 0; i < total_persons; i++) {
printf("\nPerson %d:\n", i + 1);
printf("姓名:%s\n", all_persons[i].name);
printf("年龄:%d\n", all_persons[i].age);
printf("身高:%.2f\n", all_persons[i].height);
}

// 释放内存
free(all_persons);
fclose(file);
}

/**
* @brief 文件定位示例
* @details 演示使用fseek、ftell和rewind函数
*/
void file_positioning() {
FILE *file;
char buffer[256];

// 以读取模式打开文件
file = fopen("test.txt", "r");

if (file == NULL) {
printf("无法打开文件!\n");
return;
}

// 获取当前文件指针位置
long position = ftell(file);
printf("初始文件指针位置:%ld\n", position);

// 读取一行
fgets(buffer, sizeof(buffer), file);
printf("读取的第一行:%s", buffer);

// 获取当前文件指针位置
position = ftell(file);
printf("读取第一行后位置:%ld\n", position);

// 使用fseek移动文件指针到指定位置
fseek(file, 0, SEEK_SET); // 移动到文件开头
position = ftell(file);
printf("移动到文件开头后位置:%ld\n", position);

// 再次读取第一行
fgets(buffer, sizeof(buffer), file);
printf("再次读取的第一行:%s", buffer);

// 移动到文件末尾
fseek(file, 0, SEEK_END);
position = ftell(file);
printf("移动到文件末尾后位置:%ld\n", position);

// 向前移动10个字节
fseek(file, -10, SEEK_CUR);
position = ftell(file);
printf("向前移动10个字节后位置:%ld\n", position);

// 读取剩余内容
printf("从当前位置读取的内容:\n");
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer);
}

// 使用rewind重置文件指针到开头
rewind(file);
position = ftell(file);
printf("rewind后文件指针位置:%ld\n", position);

// 关闭文件
fclose(file);
}

/**
* @brief 错误处理示例
* @details 演示使用ferror、feof和perror函数
*/
void error_handling() {
FILE *file;
char ch;

// 尝试打开一个不存在的文件
printf("尝试打开一个不存在的文件:\n");
file = fopen("non_existent_file.txt", "r");

if (file == NULL) {
perror("fopen"); // 打印系统错误信息
printf("无法打开文件!\n");
} else {
fclose(file);
}

// 正确打开文件并演示ferror和feof
printf("\n演示ferror和feof函数:\n");

file = fopen("test.txt", "r");

if (file == NULL) {
perror("fopen");
return;
}

// 读取文件内容
printf("读取文件内容:\n");
while ((ch = fgetc(file)) != EOF) {
putchar(ch);
}

// 检查是否到达文件末尾
if (feof(file)) {
printf("\n已到达文件末尾(feof返回非零值)\n");
}

// 检查是否发生错误
if (ferror(file)) {
printf("读取文件时发生错误\n");
} else {
printf("文件读取成功,无错误\n");
}

// 清除错误标志
clearerr(file);

// 再次检查错误标志
if (ferror(file)) {
printf("错误标志未清除\n");
} else {
printf("错误标志已清除\n");
}

// 关闭文件
fclose(file);
}