C语言文件操作

打开文件

  使用fopen函数打开文件,第一个参数是文件的路径,第二个参数是文件的打开模式,如只读、写入、追加;如果文件打开成功,将返回一个文件指针,否则返回NULL

1
FILE *fopen(const char *filename, const char *mode);

  如下代码以只写模式打开了E:\temp\文件夹下的test.txt文件

1
2
3
String filePath = "E:\\temp\\test.txt";
// 以读取模式打开
FILE* file = fopen(filePath, "w");

  文件的打开模式有以下

文件使用方式含义如果指定文件不存在
“r”(只读)打开一个已有的文本文件,只允许读取出错
“w”(只写)打开一个文本文件(清空原有数据),只允许写入建立一个新的文件
“a”(追加)打开一个文本文件,用于追加写入建立一个新的文件
“rb”(只读)打开一个已有的二进制文件,只允许读取出错
“wb”(只写)打开一个二进制文件(清空原有数据),只允许写入建立一个新的文件
“ab”(追加)打开一个二进制文件,用于追加写入建立一个新的文件
“r+”(读写)打开一个已有文本文件,允许读取和写入出错
“w+”(读写)打开一个文本文件(清空原有数据),允许读取和写入建立一个新的文件
“a+”(读写)打开一个文本文件,用于读取和追加写入建立一个新的文件
“rb+”(读写)打开一个已有二进制文件,允许读取和写入出错
“wb+”(读写)打开一个二进制文件(清空原有数据),允许读取和写入建立一个新的文件
“ab+”(读写)打开一个二进制文件,用于读取和追加写入建立一个新的文件

  打开的文件在操作结束后一定要使用fclose函数关闭

1
int fclose(FILE *stream);

写出文件

  通过以下函数写出文件到本地

函数作用
fputc()向文件中写入一个字符
fputs()向文件中写入一行字符串
fprintf()按指定格式向文件中写入数据

fputc

  fputc()用于将一个字符写入指定的文件流中;character是要向文件流中写入的字符;stream是指向文件流的FILE类型的指针,它指定了要写入的文件;该函数返回值是写入的字符,如果发生错误,则返回EOF,这是一个表示文件尾或错误的宏,其代表的值为-1

1
int fputc(int character, FILE *stream);

  如下代码使用fputc函数在本地文件中写下了26个字母

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
typedef char* String;

int main() {
// 指定文件路径
String filePath = "E:\\temp\\test.txt";
// 以只写模式打开文件
FILE* file = fopen(filePath, "w");

// 检查文件是否成功打开
if (file != NULL) {
// 得到小写字母 a 的 ASCII 码
int a_c = 'a';

// 通过 26 次循环,将 26 个字母写入本地
for (int i = 0; i < 26; i++) {
// 当前轮次需要写入的字符
int cc = a_c + i;

// 写入字符到本地,并记录写入结果
int result = fputc(cc, file);

// 判断是否成功写入
if (result != EOF) {
// 输出写入的结果
printf("result = %c\n", result);
} else {
// 输出错误消息
perror("文件写入失败");
return 1;
}
}
printf("文件写入完毕\n");

// 关闭文件流
fclose(file);
file = NULL;
} else {
// 输出错误消息
perror("文件打开失败");
return 1;
}

return 0;
}

fputs

  fputs()用于将一个字符串写入到指定的文件流中;str是向文件流中写入的字符串;stream是指向文件流的FILE类型的指针;如果写入成功,函数返回一个非负整数,发生错误则返回EOF

1
int fputs(const char *str, FILE *stream);

  如下代码使用fputs函数在本地文件中写下了一段字符串

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
#define ARR_LENGTH 4
typedef char* String;

int main() {
// 准备要写入到文件流中的字符串数组
String str_arr[ARR_LENGTH] = {"悟已往之不谏,\n",
"知来者之可追。\n",
"实迷途其未远,\n",
"觉今是而昨非。"};

// 指定文件路径
String filePath = "E:\\temp\\test.txt";
// 以只写模式打开文件
FILE* file = fopen(filePath, "w");

// 检查文件是否成功打开
if (file != NULL) {
// 使用循环将数组中的字符串依次写入本地
for (int i = 0; i < ARR_LENGTH; i++) {
// 写入字符串到输出流,如果写入发生错误时会返回 EOF
int result = fputs(str_arr[i], file);

// 如果写入失败
if (result == EOF) {
// 输出错误消息
perror("文件写入失败");
return 1;
}
}

printf("文件写入完毕\n");

// 关闭文件流
fclose(file);
file = NULL;
} else {
// 输出错误消息
perror("文件打开失败");
return 1;
}

return 0;
}

fprintf

  fprintf()函数用于将格式化后的数据写入到指定的文件流中,格式化方式与printf()类似,stream表示指向文件流的指针,format是包含占位符的字符串,...是可变参数,用以填充字符串中的占位符;如果数据成功写入,返回写入的字符数(非负整数),如果发生错误则返回负值

1
int fprintf(FILE *stream, const char *format, ...);

  如下代码使用fprintf函数在本地文件中写下了一段字符串

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
#define ARR_LENGTH 3
typedef char* String;

// 声明结构体,包含姓名和各科成绩;同时声明结构体数组并初始化
struct Scores {
String name;
int chinese;
int math;
int english;
} scores_arr[ARR_LENGTH] = {"小红", 100, 80, 70,
"小刚", 80, 98, 100,
"小明", 90, 60, 80};

int main() {
// 指定文件路径
String filePath = "E:\\temp\\test.txt";
// 以只写模式打开文件
FILE* file = fopen(filePath, "w");

// 该变量用于统计 fprintf 写入的字符总数
int char_sum = 0;

// 检查文件是否成功打开
if (file != NULL) {
// 使用循环将结构体数组中的数据写入本地
for (int i = 0; i < ARR_LENGTH; i++) {
// 写入字符串到输出流,如果成功写入,该方法将返回写入的字符数
int result = fprintf(file, "姓名:%s,数学:%d,英语:%d,语文:%d\n",
scores_arr[i].name, scores_arr[i].chinese, scores_arr[i].math, scores_arr[i].english);

// 累加字符数
char_sum += result;

// 判断文件写入情况
if (result < 0) {
perror("写入文件时发生错误");
return 1;
}
}

printf("文件写入完毕,共计字符 %d 个\n", char_sum);

// 关闭文件流
fclose(file);
file = NULL;
} else {
// 输出错误消息
perror("文件打开失败");
return 1;
}

return 0;
}

读取文件

  通过以下函数读取本地文件

函数作用
fgetc()从文件中读取一个字符
fgets()从文件中读取一行字符串
fscanf()按指定格式从文件中读取数据

fgetc

  fgetc()函数用于从指定的文件流中读取一个字符,只有一个参数是指向文件流的指针,如果读取成功,返回值被读取的字符的ASCII值,如果到达文件末尾或者发生错误,返回EOF;可以使用feofferror函数来进一步检查是文件末尾还是发生了错误
  文件指针会在每次调用fgetc后移动到下一个字符位置

1
int fgetc(FILE *stream);

  如下代码使用fgetc函数将本地文件中的内容逐个字符读取,并储存至缓冲区输出

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
typedef char* String;

int main() {
// 指定文件路径
String filePath = "E:\\temp\\test.txt";
// 以只读模式打开文件
FILE* file = fopen(filePath, "r");

// 检查文件是否成功打开
if (file != NULL) {

// 储存字符读取结果
int character;
// 作为缓冲器,储存所有字符读取结果
char buffer[1024] = {0};

// 使用循环将所有读取成功的字符储存进 buffer
for (int i = 0; (character = fgetc(file)) != EOF; i++) {
buffer[i] = (char)character;
}

if (feof(file)) { // EOF 发生在文件末尾
// 将 buffer 作为字符串输出
printf("character = %s", buffer);
} else if(ferror(file)) { // EOF 发生在读取过程中
perror("发生了读取错误");
return 1;
}

// 关闭文件流
fclose(file);
file = NULL;
} else {
// 输出错误消息
perror("文件打开失败");
return 1;
}

return 0;
}

fgets

  fgets()函数用于从文件流中读取一行文本;str存储从文件中读取的文本;size代表要读取的最大字符数,限制fgets函数从文件流中每次最多读取size-1个字符(因为最后一个元素要储存\0),直到遇到换行符\n或文件结束符EOF,并在字符数组的末尾添加一个\0字符形成字符串;stream代表要读取的文件流;该函数的返回值是成功读取的字符串的地址,如果读取到文件末尾或者发生错误,返回NULL
  文件指针会在每次调用fgets后移动到读取的字符串的末尾

1
char *fgets(char *str, int size, FILE *stream);

  如下代码展示了fgets函数“每次读取一行”,由于读取的文件中一共有5行内容,而fgets函数换行符\n或文件结束符EOF将会结束本轮的内容读取,并且每行内容的字符没有超出fgets第二个参数限制的字符范围,所以代码中循环语句经过5次循环,把test.txt中的内容读取完毕
  如果把fgets第二个参数限制的字符范围设置为8,即每次从文件流中最多读取7个字符,那么循环语句将经过10次循环读取完毕test.txt中的内容,因为文件中的每行内容都在7~14的范围内

1
2
3
4
5
6
假设test.txt中的内容如下:
First line
Second line
Third line
Fourth line
Fifth line

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
typedef char* String;

int main() {
// 指定文件路径
String filePath = "E:\\temp\\test.txt";
// 以只读模式打开文件
FILE* file = fopen(filePath, "r");

// 检查文件是否成功打开
if (file != NULL) {

// 存储从文件中读取的文本
char str[1024] = {0};

// 循环读取文件中的每一行,当 fgets 返回 NULL 时循环会结束
for (int i = 1; fgets(str, sizeof(str), file) ; i++) {
printf("第 %d 轮读取 = %s\n", i, str);
}

// 关闭文件流
fclose(file);
file = NULL;
} else {
// 输出错误消息
perror("文件打开失败");
return 1;
}

return 0;
}

  如下代码使用fgets函数将本地文件中的内容逐行读取,并储存至缓冲区输出

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
typedef char* String;

int main() {
// 指定文件路径
String filePath = "E:\\temp\\test.txt";
// 以只读模式打开文件
FILE* file = fopen(filePath, "r");

// 检查文件是否成功打开
if (file != NULL) {

// 储存每行读取结果
char str[1024] = {0};
// 作为缓冲器,储存所有行读取结果
char buffer[1024 * 100] = {0};

// 循环读取文件中的每一行
while (fgets(str, sizeof(str), file) != NULL) {
// 拼接字符串,把每一行拼接起来
strcat(buffer, str);
}

// 将 buffer 作为字符串输出
printf("buffer = %s\n", buffer);

// 关闭文件流
fclose(file);
file = NULL;
} else {
// 输出错误消息
perror("文件打开失败");
return 1;
}

return 0;
}

fscanf

  fscanf()函数用于从文件中读取格式化输入,格式化方式与scanf()类似;stream表示要读取的文件流;format表示格式化字符串;...是可变参数列表,用于接收按照format字符串指定格式读取的数据;该函数返回成功读取和匹配的参数个数,如果到达文件末尾或发生读取错误,则返回EOF
  文件指针会在每次调用fscanf后根据成功读取的数据大小移动

1
int fscanf(FILE *stream, const char *format, ...);

  fscanf函数在读取数据时以空白字符(包括空格、制表符和换行符)作为分隔符,如果想要读取多行数据,可以使用循环来多次调用;如下代码从指定文件中读取每一条符合格式要求的数据

1
2
3
4
假设test.txt中的内容如下:
姓名:小红,数学:100,英语:80,语文:70
姓名:小刚,数学:80,英语:98,语文:100
姓名:小明,数学:90,英语:60,语文:80

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
typedef char* String;

int main() {
// 指定文件路径
String filePath = "E:\\temp\\test.txt";
// 以只写模式打开文件
FILE* file = fopen(filePath, "r");

// 检查文件是否成功打开
if (file != NULL) {
// 记录学生姓名
char name[1024] = {0};
// 记录成绩
int chinese = 0, math = 0, english = 0;

// 使用循环读取每一行与格式化字符串中匹配的数据
while (fscanf(file, "姓名:%[^,],数学:%d,英语:%d,语文:%d\n", name, &math, &english, &chinese) != EOF) {
printf("学生姓名:%s,数学成绩:%d,英语成绩:%d,语文成绩:%d\n", name, math, english, chinese);
}

if (feof(file)) { // EOF 发生在文件末尾
printf("文件读取完毕");
} else if (ferror(file)) { // EOF 发生在读取过程中
perror("发生了读取错误");
return 1;
}

// 关闭文件流
fclose(file);
file = NULL;
} else {
// 输出错误消息
perror("文件打开失败");
return 1;
}

return 0;
}

移动指针

  fseek函数用于移动文件指针在文件流中的位置;stream是指向FILE结构的指针,它标识了文件流;offset表示偏移量,它用于确定文件指针在文件流中的新位置;whence表示相对位置的基准,可以取以下三个常量值

  1. SEEK_SET:从文件的起始位置开始偏移offset个字节
  2. SEEK_CUR:从当前文件位置开始偏移offset个字节
  3. SEEK_END:从文件末尾位置开始偏移offset个字节

  fseek的返回值是一个整数,通常用于检查是否定位成功,如果成功,返回值为0;否则返回非零值。

1
int fseek(FILE *stream, long offset, int whence);

  如下代码将文件指针移动到倒数第一个字符之前,那么fgetc将只能读取倒数第一个字符

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
typedef char* String;

int main() {
// 指定文件路径
String filePath = "E:\\temp\\test.txt";
// 以只读模式打开文件
FILE* file = fopen(filePath, "r");

// 检查文件是否成功打开
if (file != NULL) {

// 将指针移动到倒数第一个字符之前
fseek(file, -1, SEEK_END);

// 储存字符读取结果
int character;
// 作为缓冲器,储存所有字符读取结果
char buffer[1024] = {0};

// 使用循环将所有读取成功的字符储存进 buffer
for (int i = 0; (character = fgetc(file)) != EOF; i++) {
buffer[i] = (char)character;
}

if (feof(file)) { // EOF 发生在文件末尾
// 将 buffer 作为字符串输出
printf("character = %s", buffer);
} else if(ferror(file)) { // EOF 发生在读取过程中
perror("发生了读取错误");
return 1;
}

// 关闭文件流
fclose(file);
file = NULL;
} else {
// 输出错误消息
perror("文件打开失败");
return 1;
}

return 0;
}

错误处理

  在进行文件操作时,需要考虑错误处理。可以使用feof函数检查文件是否已经到达末尾,以及使用ferror函数检查文件操作是否发生了错误

1
2
3
4
5
if (feof(file)) {
// 到达文件末尾
} else if (ferror(file)) {
// 文件操作发生错误
}