换个姿势学C语言 第7章 获取全部外币牌价数据并保存为文件
0. 说明
《换个姿势学C语言》由何旭辉 著,清华大学出版社2022年出版。感谢何老师!

这是一本非常不错的书!
7. 获取全部外币牌价数据并保存为文件
变量、数组都是存储在内存RAM中的,这些数据所占用的内存在程序结束以后会被操作系统回收,其中的数据也就丢失了。因此我们需要将数据保存到外部存储器上(通常是硬盘),以便下次使用。
在一些较底层的语言里,程序员可以直接访问硬盘的某个扇区并进行数据读写,但这种方式一般不被推荐,因为这种方式除了效率比较低外还具有较大的危险,不恰当的磁盘访问可能会引起严重的故障(例如操作系统崩溃或者数据丢失)。
因此通常是以“文件”来组织磁盘上的数据。文件系统由操作系统管理,程序员通过操作系统间接地访问磁盘上的数据,不恰当的文件访问会被操作系统阻止(例如文件被其他程序占用或程序没有访问这个文件的权限),这样一来就安全得多,同时操作系统也会采取一些机制来提高文件访问的效率。
本章将会将取得的外汇牌价数据保存到磁盘文件中,但是在学习磁盘文件访问之前先学习结构体的使用方法。
结构体可以将多种不同类型的数据“组合”到一起,然后再将其存储到磁盘文件中。
7.2 文件访问的基础知识
磁盘上的数据是以文件的形式来存储的,程序员对文件的操作主要包括读和写。
- 写文件的本质,就是将内存中的一块数据复制到磁盘上来实现长期保存;而读文件的本质就是将磁盘上的一块数据复制到内存中。
读写文件的步骤如下:
- 第1步:创建(或打开)文件。
- 第2步:向文件中写数据或从文件中读数据。
- 第3步:关闭文件。
C语言标准库提供了访问文件的函数,较常用的如下:
fopen函数–用于创建或打开文件;fread函数–用于从文件中读数据;fwrite函数–用于写数据到文件;fclose函数–用于关闭文件。
7.2.1 使用fopen函数创建或打开文件
将数据保存到磁盘文件时首先要创建文件。如果一个文件已经创建,只需要打开它(有时候是直接覆盖它,这由具体情况来决定)。在C语言中创建和打开文件都使用fopen函数,fopen函数的原型如下:
1
| FILE* fopen( const char* filename, const char* mode );
|
- 第一个参数是
filename,它是一个指定文件名的字符串; - 第二个参数是
mode,也是一个字符串,它的作用是决定打开这个文件的方式。
第二个参数,表示打开文件的模式,有以下几种:
| 模式 | C 语言 fopen | Python open() | 意思 |
|---|
| 只读 | "r" | "r" | 打开读(文件必须存在) |
| 只写 | "w" | "w" | 清空创建写(文件不存在就创建) |
| 追加 | "a" | "a" | 末尾追加写 |
| 读写 | "r+" | "r+" | 可读可写 |
| 读写创建 | "w+" | "w+" | 创建读写,文件存在则会清空 |
| 追加读写 | “a+” | “a+” | 写到末尾 + 可以读 |
- 当指定的路径不存在,试图打开 的文件不存在或文件被其他程序占用、对指定路径的访问权限不够、磁盘空间已满都可能会引起
fopen函数打开或创建文件失败! fopen函数在操作失败时会返回NULL,以便程序员知晓这些错误并采取处理措施。- 如果
fopen函数打开文件成功,则会返回一组关于这个文件的数据,这些数据被存储在一个结构体中,这个结构体是标准库中已经定义好的,别名为FILE。 - 我们不用关心这个结构体各项成员的含义,因为在不同的库中实现这个结构体的定义不同。
1
| FILE* fp = fopen("data.json", "r");
|
这行代码中的fp是一个指向这种结构体的指针。fopen函数内部在打开文件成功后就会创建这个结构体,将文件的相关数据存入其中并返回这个结构体的指针。后面的程序要访问该文件就需要通过指针fp获取结构体存储的数据。习惯上,我们把这个结构体指针称为文件句柄。
此处写fopen的伪代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // 这是 fopen 内部的伪代码!!!
FILE* fopen(const char* filename, const char* mode)
{
// 1. 打开文件(系统调用)
int handle = os_open_file(filename, mode);
// 2. 自己 malloc 一个 FILE 结构体!!!
FILE* file = (FILE*)malloc(sizeof(FILE));
// 3. 把信息存进去
file->handle = handle;
file->position = 0;
file->eof = 0;
...
// 4. 返回指针给你!
return file;
}
|
就是说,当我使用FILE* fp = fopen("data.json", "r");去打开一个文件时,是fopen函数在内部创建了一个结构体,并将这个文件句柄返回,FILE* fp只是接收了fopen返回的这个内存地址。
下面演示fopen文件占用情况:
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
| #define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable: 6031)
#include <stdio.h>
#include <windows.h>
int main()
{
// 1. 打开文件
FILE* fp = fopen("data.json", "r");
if (fp == NULL)
{
printf("打开文件失败!\n");
return 1;
}
printf("打开文件成功!此时你不能删除文件\n");
// 在Windows环境下,最常用的延时函数是Sleep函数。
// Sleep函数定义在windows.h头文件中,它的参数是延时的毫秒数
// 程序暂停期间,文件句柄一直被程序占用,你不能删除文件
Sleep(1000 * 3600);
// 2. 关闭文件
fclose(fp);
return 0;
}
|
运行程序后,尝试手动删除文件,会提示无法删除:

7.2.1.1 模拟无权限打开文件
给文件设置「只读 + 拒绝读取权限」:
- 右键
data.json → 属性 - 切到 安全 选项卡 → 点 编辑
- 选中当前登录的用户 / 管理员组
- 在【 拒绝】 那一列,勾选: 读取 & 执行 和- 读取
- 确定保存

先上代码:
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
| #define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable: 6031)
#include <stdio.h>
// windows.h提供 Sleep 函数
#include <windows.h>
// errno.h 提供错误码,如 EACCES 13
#include <errno.h>
// string.h 提供 strerror 函数,获取错误码对应的异常说明
#include <string.h>
int main()
{
// 1. 打开文件
FILE* fp = fopen("data.json", "r");
printf("文件句柄地址:%p\n", fp);
if (fp == NULL)
{
printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
return 1;
}
printf("打开文件成功!此时你不能删除文件\n");
// 在Windows环境下,最常用的延时函数是Sleep函数。
// Sleep函数定义在windows.h头文件中,它的参数是延时的毫秒数
// 程序暂停期间,文件句柄一直被程序占用,你不能删除文件
Sleep(1000 * 3600);
// 2. 关闭文件
fclose(fp);
return 0;
}
|
运行程序,输出如下:
1
2
| 文件句柄地址:00000000
打开文件失败!错误编号:13, 原因: Permission denied
|

说明因为权限异常,读取不到文件!
修改文件属性,将刚才选择的拒绝勾选去掉,然后再执行程序,就可以正常读文件:

7.2.1.2模拟文件不存在打开文件
直接将data.json文件重命名为data1.json,然后再运行程序,此时输出为:
1
2
| 文件句柄地址:00000000
打开文件失败!错误编号:2, 原因: No such file or directory
|

将data1.json命名重新改回为data.json。
7.2.1.3 文件打开模式异常的处理
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-170 这里面定义了fopen的打开模式:

如果我们使用一个非法的模式,如将模式"r"换成"invalid",尝试运行程序:

程序直接崩溃了,提示Expression:(“Invalid file open mode”,0) 无效的文件打开模式!
因此,在打开文件前,应对文件打开模式进行校验!
下面是优化后的代码,对文件打开模式提前进行校验:
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
| #define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable: 6031)
#include <stdio.h>
// windows.h提供 Sleep 函数
#include <windows.h>
// errno.h 提供错误码,如 EACCES 13
#include <errno.h>
// string.h 提供 strerror 函数,获取错误码对应的异常说明
#include <string.h>
// stdbool.h 提供布尔逻辑true和false
#include <stdbool.h>
// 函数声明
bool is_valid_fopen_mode(const char* mode);
int main()
{
const char* mode = "invalid"; // 随便写文件打开模式
// 1. 检查模式对不对
if (!is_valid_fopen_mode(mode))
{
printf("错误:文件打开模式( %s )不合法!\n", mode);
return 1;
}
printf("文件打开模式( %s )合法!\n", mode);
// 2. 打开文件
// 直接用/替换\\作为路径分隔符
FILE* fp = fopen("D:/BC101/Examples/L07/L07_07_FILE_READ_AND_WRITE/data.json", mode);
printf("文件句柄地址:%p\n", fp);
// 3. 检查文件是否打开成功
if (fp == NULL)
{
printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
return 1;
}
// 4. 打开成功,进行等待
printf("打开文件成功!此时你不能删除文件\n");
// 在Windows环境下,最常用的延时函数是Sleep函数。
// Sleep函数定义在windows.h头文件中,它的参数是延时的毫秒数
// 程序暂停期间,文件句柄一直被程序占用,你不能删除文件
Sleep(1000 * 3600);
// 5. 关闭文件,释放文件句柄
fclose(fp);
return 0;
}
// 检查文件打开模式是否合法
bool is_valid_fopen_mode(const char* mode)
{
// 合法模式列表
const char* valid_modes[] = {
// 文本模式
"r", "r+",
"w", "w+",
"a", "a+",
// 二进制 b 模式
"rb", "rb+", "r+b",
"wb", "wb+", "w+b",
"ab", "ab+", "a+b"
};
// 计算一共有多少个合法模式
int len = sizeof(valid_modes) / sizeof(valid_modes[0]);
printf("合法字符个数:%d\n", len);
// 逐个比对
for (int i = 0; i < len; i++)
{
// strcmp 也是 string.h 中的,表示对两个字符串进行比较,相等就等于0
if (strcmp(mode, valid_modes[i]) == 0)
{
return true; // 找到合法模式 → 返回真
}
}
return false; // 没找到 → 非法
}
|
运行程序,可以看到,对非法模式进行了拦截:

此时,如果将invalid改成r,然后再启动程序,可以看到能正常运行:

7.2.4 使用fread读取文件内容
在使用fread前,我们先用fgets一行一行读取文件内容。
7.2.4.0 使用fgets一行一行读取文件内容
基本用法:
fgets函数用于从文件中读取一行数据,并将其存储在指定的缓冲区中。其原型如下:
1
| char *fgets(char *str, int n, FILE *stream);
|
str:指向字符数组的指针,用于存储读取的字符串。n:要读取的最大字符数,包括空字符\0。stream:指向FILE对象的指针,表示要读取的文件。
返回值:
- 成功时返回
str指针。 - 遇到文件结束符(
EOF)或读取错误时返回NULL。
注意事项:
- 当
buf设置的要读取的最大字符数n越小时,如果文件内容一行越长,一行越会分几次读取。应尽量将buf设置大一点。
看示例,如果将buf的长度设置为8,代码如下:
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
| #define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable: 6031)
#include <stdio.h>
// windows.h提供 Sleep 函数
#include <windows.h>
// errno.h 提供错误码,如 EACCES 13
#include <errno.h>
// string.h 提供 strerror 函数,获取错误码对应的异常说明
#include <string.h>
// stdbool.h 提供布尔逻辑true和false
#include <stdbool.h>
// 函数声明
bool is_valid_fopen_mode(const char* mode);
int main()
{
const char* mode = "r"; // 随便写文件打开模式
// 1. 检查模式对不对
if (!is_valid_fopen_mode(mode))
{
printf("错误:文件打开模式( %s )不合法!\n", mode);
return 1;
}
printf("文件打开模式( %s )合法!\n", mode);
// 2. 打开文件
// 直接用/替换\\作为路径分隔符
FILE* fp = fopen("D:/BC101/Examples/L07/L07_07_FILE_READ_AND_WRITE/data.json", mode);
printf("文件句柄地址:%p\n", fp);
// 3. 检查文件是否打开成功
if (fp == NULL)
{
printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
return 1;
}
// 4. 打开成功,进行等待
printf("打开文件成功!此时你不能删除文件\n");
char buf[8] = { 0 };
// 循环逐行读取, fgets(缓冲区, 缓冲区大小, 文件指针)
printf("sizeof(buf): %d\n", sizeof(buf));
while (fgets(buf, sizeof(buf), fp) != NULL)
{
printf("%s", buf);
printf("开始读一行\n");
}
// =============================================================
printf("\n文件读取完毕\n");
// 在Windows环境下,最常用的延时函数是Sleep函数。
// Sleep函数定义在windows.h头文件中,它的参数是延时的毫秒数
// 程序暂停期间,文件句柄一直被程序占用,你不能删除文件
// Sleep(1000 * 3600);
// 5. 关闭文件,释放文件句柄
fclose(fp);
return 0;
}
// 检查文件打开模式是否合法
bool is_valid_fopen_mode(const char* mode)
{
// 合法模式列表
const char* valid_modes[] = {
// 文本模式
"r", "r+",
"w", "w+",
"a", "a+",
// 二进制 b 模式
"rb", "rb+", "r+b",
"wb", "wb+", "w+b",
"ab", "ab+", "a+b"
};
// 计算一共有多少个合法模式
int len = sizeof(valid_modes) / sizeof(valid_modes[0]);
// 逐个比对
for (int i = 0; i < len; i++)
{
// strcmp 也是 string.h 中的,表示对两个字符串进行比较,相等就等于0
if (strcmp(mode, valid_modes[i]) == 0)
{
return true; // 找到合法模式 → 返回真
}
}
return false; // 没找到 → 非法
}
|
运行程序,输出如下:

可以看到,当第2-5行的长度比较长时,fgets每次读8-1=7个字符,一行就会被分几次读取!
将buf长度设置成1024,然后再运行代码,此时输出如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 文件打开模式( r )合法!
文件句柄地址:015263B0
打开文件成功!此时你不能删除文件
sizeof(buf): 1024
{
开始读一行
"sites": [
开始读一行
{ "name":"菜鸟教程" , "url":"www.runoob.com" },
开始读一行
{ "name":"google" , "url":"www.google.com" },
开始读一行
{ "name":"微博" , "url":"www.weibo.com" }
开始读一行
]
开始读一行
}开始读一行
文件读取完毕
|
7.2.4.1 使用fread读取文件
fread函数基本用法:
fread函数用于从文件中读取数据块。其原型如下:
1
| size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
|
ptr:指向用于存储读取数据的内存块的指针。size:每个数据块的字节数。count:要读取的数据块数量。stream:指向FILE对象的指针,表示要读取的文件。
返回值:
- 成功时返回读取的数据块数量。
- 出错或遇到文件结束符时返回
0。
示例:
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
| #define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable: 6031)
#include <stdio.h>
// windows.h提供 Sleep 函数
#include <windows.h>
// errno.h 提供错误码,如 EACCES 13
#include <errno.h>
// string.h 提供 strerror 函数,获取错误码对应的异常说明
#include <string.h>
// stdbool.h 提供布尔逻辑true和false
#include <stdbool.h>
// 函数声明
bool is_valid_fopen_mode(const char* mode);
int main()
{
const char* mode = "r"; // 随便写文件打开模式
// 1. 检查模式对不对
if (!is_valid_fopen_mode(mode))
{
printf("错误:文件打开模式( %s )不合法!\n", mode);
return 1;
}
printf("文件打开模式( %s )合法!\n", mode);
// 2. 打开文件
// 直接用/替换\\作为路径分隔符
FILE* fp = fopen("D:/BC101/Examples/L07/L07_07_FILE_READ_AND_WRITE/data.json", mode);
printf("文件句柄地址:%p\n", fp);
// 3. 检查文件是否打开成功
if (fp == NULL)
{
printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
return 1;
}
// 4. 打开成功,进行等待
printf("打开文件成功!此时你不能删除文件\n");
//char buf[1024] = { 0 };
//// 循环逐行读取, fgets(缓冲区, 缓冲区大小, 文件指针)
//printf("sizeof(buf): %d\n", sizeof(buf));
//while (fgets(buf, sizeof(buf), fp) != NULL)
//{
// printf("%s", buf);
// printf("开始读一行\n");
//}
char buf[1024] = { 0 };
int count;
while ((count = fread(buf, sizeof(char), sizeof(buf) - 1, fp)) > 0)
{
buf[count] = '\0'; // 手动加字符串结束符,避免乱码
printf("%s", buf);
}
// =============================================================
printf("\n文件读取完毕\n");
// 在Windows环境下,最常用的延时函数是Sleep函数。
// Sleep函数定义在windows.h头文件中,它的参数是延时的毫秒数
// 程序暂停期间,文件句柄一直被程序占用,你不能删除文件
// Sleep(1000 * 3600);
// 5. 关闭文件,释放文件句柄
fclose(fp);
return 0;
}
// 检查文件打开模式是否合法
bool is_valid_fopen_mode(const char* mode)
{
// 合法模式列表
const char* valid_modes[] = {
// 文本模式
"r", "r+",
"w", "w+",
"a", "a+",
// 二进制 b 模式
"rb", "rb+", "r+b",
"wb", "wb+", "w+b",
"ab", "ab+", "a+b"
};
// 计算一共有多少个合法模式
int len = sizeof(valid_modes) / sizeof(valid_modes[0]);
// 逐个比对
for (int i = 0; i < len; i++)
{
// strcmp 也是 string.h 中的,表示对两个字符串进行比较,相等就等于0
if (strcmp(mode, valid_modes[i]) == 0)
{
return true; // 找到合法模式 → 返回真
}
}
return false; // 没找到 → 非法
}
|
可以看到,fread与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
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
| #define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable: 6031)
#include <stdio.h>
// windows.h提供 Sleep 函数
#include <windows.h>
// errno.h 提供错误码,如 EACCES 13
#include <errno.h>
// string.h 提供 strerror 函数,获取错误码对应的异常说明
#include <string.h>
// stdbool.h 提供布尔逻辑true和false
#include <stdbool.h>
// 函数声明
bool is_valid_fopen_mode(const char* mode);
int main()
{
const char* mode = "rb"; // 随便写文件打开模式
// 1. 检查模式对不对
if (!is_valid_fopen_mode(mode))
{
printf("错误:文件打开模式( %s )不合法!\n", mode);
return 1;
}
printf("文件打开模式( %s )合法!\n", mode);
// 2. 打开文件
// 直接用/替换\\作为路径分隔符
FILE* fp = fopen("D:/BC101/Examples/L07/L07_07_FILE_READ_AND_WRITE/data.json", mode);
printf("文件句柄地址:%p\n", fp);
// 3. 检查文件是否打开成功
if (fp == NULL)
{
printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
return 1;
}
// 4. 打开成功,进行等待
printf("打开文件成功!\n");
// 接收读取数据的缓冲区
char buf[1024] = { 0 };
int count;
while ((count = fread(buf, sizeof(char), sizeof(buf) - 1, fp)) > 0)
{
buf[count] = '\0'; // 手动加字符串结束符,避免乱码
printf("%s", buf);
}
// =============================================================
printf("\n文件读取完毕\n");
// 在Windows环境下,最常用的延时函数是Sleep函数。
// Sleep函数定义在windows.h头文件中,它的参数是延时的毫秒数
// 程序暂停期间,文件句柄一直被程序占用,你不能删除文件
// Sleep(1000 * 3600);
// 5. 关闭文件,释放文件句柄
fclose(fp);
return 0;
}
// 检查文件打开模式是否合法
bool is_valid_fopen_mode(const char* mode)
{
// 合法模式列表
const char* valid_modes[] = {
// 文本模式
"r", "r+",
"w", "w+",
"a", "a+",
// 二进制 b 模式
"rb", "rb+", "r+b",
"wb", "wb+", "w+b",
"ab", "ab+", "a+b"
};
// 计算一共有多少个合法模式
int len = sizeof(valid_modes) / sizeof(valid_modes[0]);
// 逐个比对
for (int i = 0; i < len; i++)
{
// strcmp 也是 string.h 中的,表示对两个字符串进行比较,相等就等于0
if (strcmp(mode, valid_modes[i]) == 0)
{
return true; // 找到合法模式 → 返回真
}
}
return false; // 没找到 → 非法
}
|
此时运行程序,输出如下:

7.2.2 使用fwrite函数写入数据到文件
7.2.1节学会了使用fopen打开文件,现在我们考虑如何将数据写入创建的磁盘文件,fwrite函数用于向已经打开的文件中写入数据。写入数据到磁盘文件的本质是将内存中的数据复制到磁盘文件,因此fwrite函数至少需要3项信息才能将文件写入磁盘。
要复制的数据从哪里开始?
- 你需要指定数据在内存中的起始位置,这往往是一个指针。它可以是数组的首地址、结构体的首地址,或者使用
malloc函数分配的内存地址。
要写入多少字节?
- 指针只能说明首地址,接下来要指定有多少字节要传输到磁盘文件。
fwrite函数使用2个参数来指定写入的字节数。
要写到哪个文件里去?
- 程序中可能同时打开了多个文件,
fwrite函数当然不能自动判断要写到哪个文件里,此时可以使用之前创建的文件句柄来标识要写入的文件。
fwrite函数的原型是:
1
| size_t fwrite( const void* buffer, size_t size, size_t count, FILE* stream );
|
第1个参数const void* buffer,参数名名buffer,类型是const void*,这个参数的作用是指定要写入文件的数据的内存地址。void*表示一个无类型指针,const限定符表示这个指针指向一个只读的内存区域。
第2个参数size_t size,指定单个数据项的大小。
第3个参数size_t count,表示要写入磁盘文件的数据项个数,单个数据项的大小乘以数据项的个数,即为要写入磁盘文件的总字节数。
第4个参数FILE* stream,即为文件句柄,标识要写入的文件。
fwrite的返回值:fwrite函数返回成功写入磁盘文件的数据项个数。如果fwrite的返回值不等于参数count,则意味着写入的过程中发生了错误。
以下是一个fwrite写文件的示例:
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
| #define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable: 6031)
#include <stdio.h>
// windows.h提供 Sleep 函数
#include <windows.h>
// errno.h 提供错误码,如 EACCES 13
#include <errno.h>
// string.h 提供 strerror 函数,获取错误码对应的异常说明
#include <string.h>
// stdbool.h 提供布尔逻辑true和false
#include <stdbool.h>
// 函数声明
bool is_valid_fopen_mode(const char* mode);
int main()
{
const char* mode = "w"; // 随便写文件打开模式
// 1. 检查模式对不对
if (!is_valid_fopen_mode(mode))
{
printf("错误:文件打开模式( %s )不合法!\n", mode);
return 1;
}
printf("文件打开模式( %s )合法!\n", mode);
// 2. 打开文件
// 直接用/替换\\作为路径分隔符
FILE* fp = fopen("D:/BC101/Examples/L07/L07_07_FILE_READ_AND_WRITE/data.txt", mode);
printf("文件句柄地址:%p\n", fp);
// 3. 检查文件是否打开成功
if (fp == NULL)
{
printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
return 1;
}
// 4. 打开成功,进行等待
printf("打开文件成功!\n");
int count;
char buf[1024] = { 0 }; // 超大缓冲区,安全
strcpy(buf, "ABCDEFG\n"); // 变量赋值
int expect_len = strlen(buf); // 计算真实要写入的长度(不含\0)
count = fwrite(buf, sizeof(char), expect_len, fp);
if (count != expect_len) {
printf("\n文件写入异常,写入个数:%d,期望个数:%d\n", count, expect_len);
printf("文件写入失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
fclose(fp);
return 1;
}
printf("\n文件写入完毕,写入个数:%d\n", count);
// 5. 关闭文件,释放文件句柄
fclose(fp);
return 0;
}
// 检查文件打开模式是否合法
bool is_valid_fopen_mode(const char* mode)
{
// 合法模式列表
const char* valid_modes[] = {
// 文本模式
"r", "r+",
"w", "w+",
"a", "a+",
// 二进制 b 模式
"rb", "rb+", "r+b",
"wb", "wb+", "w+b",
"ab", "ab+", "a+b"
};
// 计算一共有多少个合法模式
int len = sizeof(valid_modes) / sizeof(valid_modes[0]);
// 逐个比对
for (int i = 0; i < len; i++)
{
// strcmp 也是 string.h 中的,表示对两个字符串进行比较,相等就等于0
if (strcmp(mode, valid_modes[i]) == 0)
{
return true; // 找到合法模式 → 返回真
}
}
return false; // 没找到 → 非法
}
|
查看写入的文件内容:

代码中关键信息说明:
int expect_len = strlen(buf); 获取真实要写入到文件的长度。count = fwrite(buf, sizeof(char), expect_len, fp);写字符串写入到fp文件句柄对应的文件中,返回写入的个数count。if (count != expect_len) 判断实际写入数和期望数是否相同,相同则说明写入正常。
以下是一些可能导致写入失败的原因:
7.2.3 文件缓冲区
内存是速度很快的电子器件,CPU可以直接访问 内存中的数据,但诸如硬盘这些外部存储器的速度远远不如内存。
因此当我们要往磁盘文件中多次写入数据时,每一次写入操作后需要等待较长时间才能将数据写入磁盘,在这段时间里无法进行下一次写入。这样一来程序的速度就被降低了!读取数据也一样,每次读取数据都要等到硬盘上的磁头运动完毕才能读取完成。
为了减少这种等待以提高访问外存的速度,操作系统使用了“文件缓冲区”技术,即通过在RAM中占用一块空间以换得文件读写性能的提升。
文件缓冲区是操作系统为每一个打开的文件开辟的一块内存区域。
在打开一个文件后,磁盘文件的部分或全部会被读入缓冲区,读取文件内容时优先从缓冲区中读取,只有文件缓冲区中不存在要读取的内容时才从磁盘上读入。缓冲区命中率越高,读取文件的速度就越快。
同样的,程序在写文件时,也是优先写到缓冲区中(并不一定及时写入硬盘),由于RAM的读写速度远远高于对磁盘的读写速度,使用文件缓冲区会大大提高磁盘文件的读写的效率。
文件缓冲区由操作系统管理,程序员一般不用对其进行干预。但需要注意的是,在一个文件读写文件后,应该使用fclose函数关闭文件缓冲区。关闭缓冲区使操作系统将最新的缓冲区数据写入磁盘。防止因意外事故造成数据丢失。