×
Featured image of post 换个姿势学C语言第7.3节 将结构体存入磁盘文件

换个姿势学C语言第7.3节 将结构体存入磁盘文件

换个姿势学C语言 第7章 获取全部外币牌价数据并保存为文件

0. 说明

《换个姿势学C语言》由何旭辉 著,清华大学出版社2022年出版。感谢何老师!

Snipaste_2024-03-10_14-51-10.png

这是一本非常不错的书!

7. 获取全部外币牌价数据并保存为文件

变量、数组都是存储在内存RAM中的,这些数据所占用的内存在程序结束以后会被操作系统回收,其中的数据也就丢失了。因此我们需要将数据保存到外部存储器上(通常是硬盘),以便下次使用。

在一些较底层的语言里,程序员可以直接访问硬盘的某个扇区并进行数据读写,但这种方式一般不被推荐,因为这种方式除了效率比较低外还具有较大的危险,不恰当的磁盘访问可能会引起严重的故障(例如操作系统崩溃或者数据丢失)。

因此通常是以“文件”来组织磁盘上的数据。文件系统由操作系统管理,程序员通过操作系统间接地访问磁盘上的数据,不恰当的文件访问会被操作系统阻止(例如文件被其他程序占用或程序没有访问这个文件的权限),这样一来就安全得多,同时操作系统也会采取一些机制来提高文件访问的效率。

本章将会将取得的外汇牌价数据保存到磁盘文件中,但是在学习磁盘文件访问之前先学习结构体的使用方法。

结构体可以将多种不同类型的数据“组合”到一起,然后再将其存储到磁盘文件中。

7.3 将结构体存入磁盘文件

掌握了文件读写的基本方法,现在就可以将读到的外汇牌价数据存入到磁盘文件中了。

有了7.1和7.2小节的基础,在获取到美元牌价后,就可以将美元外汇牌价信息写入到文件。

⚠️ 警告

注意,将结构体写入到文件时,文件打开模式mode不能是w,而是要用wb以二进制方式写入文件。

如果以w文本方式打开文件,fwrite函数每遇到一个0x0A时,就会在它的前面自动加入0x0D,其他内容不做添加操作。

外汇牌价结构体数据也应以二进制方式保存到文件!

以下是整合后的代码:

  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
// L07_09_SAVE_USD_RATES.cpp
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#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>

// 附加包含目录已经添加 D:/BC101/Libraries/Mars
// 此处引入自定义的静态库的头文件需要相对路径
#include "str.h"
#include "input.h"

// 附加包含目录已经添加  D:/BC101/Libraries/BOCRates/BOCRates.h
// 此处引入作者提供的静态库的头文件
#include "BOCRates.h"
// #pragma comment:VS 编译器专用指令,给编译器发命令
//      lib:表示我要链接一个静态库
//      路径/Mars.lib":库文件在哪里
// 链接器--附加库目录,加上 D:/BC101/Libraries/Mars 和 D:/BC101/Libraries/BOCRates目录
#pragma comment(lib,"Mars.lib")
#pragma comment(lib, "BOCRates.lib")

// 函数声明, 判断文件模式是否有效
bool is_valid_fopen_mode(const char* mode);

int main()
{
    // 定义外汇牌价结构体变量
    EXCHANGE_RATE USDRates;
    // 货币代码
    char code[4];
    strCopy(code, "USD");

    int result = GetRateRecordByCode(code, &USDRates);
    printf("接口返回值: %d\n", result);
    if (result == 1) {
        printf("货币代码:%s\n", USDRates.CurrencyCode);
        printf("货币名称:%s\n", USDRates.CurrencyName);
        printf("发布时间:%s\n", USDRates.PublishTime);
        printf("现汇买入价:%.2f\n", USDRates.BuyingRate);
        printf("现钞买入价:%.2f\n", USDRates.CashBuyingRate);
        printf("现汇卖出价:%.2f\n", USDRates.SellingRate);
        printf("现钞卖出价:%.2f\n", USDRates.CashSellingRate);
        printf("中行折算价:%.2f\n", USDRates.MiddleRate);

        const char* mode = "wb";  // 随便写文件打开模式

        // 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_09_SAVE_USD_RATES/USDRates.txt", mode);
        printf("文件句柄地址:%p\n", fp);
        // 3. 检查文件是否打开成功
        if (fp == NULL)
        {
            printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
            return 1;
        }
        // 4. 打开成功,进行等待
        printf("打开文件成功!\n");

        int count;

        count = fwrite(&USDRates, sizeof(USDRates), 1, fp);
        if (count != 1) {
            printf("\n文件写入异常,写入个数:%d,期望个数:%d\n", count, 1);
            printf("错误编号:%d, 原因: %s\n", errno, strerror(errno));
            fclose(fp);
            return 1;
        }

        printf("\n文件写入完毕,写入个数:%d\n", count);

        // 5. 关闭文件,释放文件句柄
        fclose(fp);
    }
    else {
        printf("网络或服务器异常\n");
    }

    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;  // 没找到 → 非法
}

可以看到,写文件的很多代码是直接从L07_07_FILE_READ_AND_WRITE.cpp复制过来的,而获取美元外汇牌价是从L07_06_GET_RATE_RECORD_BY_CODE.cpp复制来的!

此时,运行代码,外汇牌价数据能正常写入文件:

Snipaste_2026-05-03_23-27-59.png

USDRates.txt的十六进视图显示如下:

Snipaste_2026-05-03_23-28-46.png

普通视图是这样的:

Snipaste_2026-05-03_23-46-14.png

对代码进行模块化处理后,优化后的代码如下:

  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
// L07_09_SAVE_USD_RATES.cpp
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#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>

// 附加包含目录已经添加 D:/BC101/Libraries/Mars
// 此处引入自定义的静态库的头文件需要相对路径
#include "str.h"
#include "input.h"

// 附加包含目录已经添加  D:/BC101/Libraries/BOCRates/BOCRates.h
// 此处引入作者提供的静态库的头文件
#include "BOCRates.h"
// #pragma comment:VS 编译器专用指令,给编译器发命令
//      lib:表示我要链接一个静态库
//      路径/Mars.lib":库文件在哪里
// 链接器--附加库目录,加上 D:/BC101/Libraries/Mars 和 D:/BC101/Libraries/BOCRates目录
#pragma comment(lib,"Mars.lib")
#pragma comment(lib, "BOCRates.lib")

// 函数声明, 判断文件模式是否有效
bool is_valid_fopen_mode(const char* mode);

// 通用二进制写入:任意数据原样存文件
bool save_data_to_file(const char* filename, const void* data, int size);

// 打印外汇牌价结构体信息
void print_rate_info(const EXCHANGE_RATE* rate);

int main()
{
    // 定义外汇牌价结构体变量
    EXCHANGE_RATE USDRates;
    // 货币代码
    char code[4];
    strCopy(code, "USD");

    int result = GetRateRecordByCode(code, &USDRates);
    printf("接口返回值: %d\n", result);
    if (result == 1) {
        print_rate_info(&USDRates);
        // 存结构体到文件中
        save_data_to_file("D:/BC101/Examples/L07/L07_09_SAVE_USD_RATES/USDRates1.txt", &USDRates, sizeof(USDRates));
    }
    else {
        printf("网络或服务器异常\n");
    }

    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;  // 没找到 → 非法
}

// 通用二进制写入:任意数据原样存文件
// 功能:把 任意一块内存数据 写入文件
// 参数:
//   filename  文件名
//   data      任意数据指针(void* 万能)
//   size      数据大小(sizeof(结构体))
// 返回:true 成功 / false 失败
bool save_data_to_file(const char* filename, const void* data, int size)
{
    const char* mode = "wb";  // 二进制写入模式

    // 1. 检查模式对不对
    if (!is_valid_fopen_mode(mode))
    {
        printf("错误:文件打开模式( %s )不合法!\n", mode);
        return false;
    }
    printf("文件打开模式( %s )合法!\n", mode);

    // 2. 打开文件
    FILE* fp = fopen(filename, mode);
    printf("文件句柄地址:%p\n", fp);
    // 3. 检查文件是否打开成功
    if (fp == NULL)
    {
        printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
        return false;
    }
    // 4. 打开成功,进行等待
    printf("打开文件成功!\n");

    int count;

    count = fwrite(data, size, 1, fp);
    if (count != 1) {
        printf("\n文件写入异常,写入个数:%d,期望个数:%d\n", count, 1);
        printf("错误编号:%d, 原因: %s\n", errno, strerror(errno));
        fclose(fp);

        return false;
    }

    printf("\n文件写入成功\n");

    // 5. 关闭文件,释放文件句柄
    fclose(fp);
    return true;
}

// 打印外汇牌价结构体信息
void print_rate_info(const EXCHANGE_RATE* rate)
{
    printf("货币代码:%s\n", rate->CurrencyCode);
    printf("货币名称:%s\n", rate->CurrencyName);
    printf("发布时间:%s\n", rate->PublishTime);
    printf("现汇买入价:%.2f\n", rate->BuyingRate);
    printf("现钞买入价:%.2f\n", rate->CashBuyingRate);
    printf("现汇卖出价:%.2f\n", rate->SellingRate);
    printf("现钞卖出价:%.2f\n", rate->CashSellingRate);
    printf("中行折算价:%.2f\n", rate->MiddleRate);
}

上一个程序是将结构体数据写入到文件中,下面的程序则是从文件中将结构体读取出来,然后打印消息:

  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
// L07_10_READ_RATES_FROM_FILE.cpp
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#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>

// 附加包含目录已经添加 D:/BC101/Libraries/Mars
// 此处引入自定义的静态库的头文件需要相对路径
#include "str.h"
#include "input.h"

// 附加包含目录已经添加  D:/BC101/Libraries/BOCRates/BOCRates.h
// 此处引入作者提供的静态库的头文件
#include "BOCRates.h"
// #pragma comment:VS 编译器专用指令,给编译器发命令
//      lib:表示我要链接一个静态库
//      路径/Mars.lib":库文件在哪里
// 链接器--附加库目录,加上 D:/BC101/Libraries/Mars 和 D:/BC101/Libraries/BOCRates目录
#pragma comment(lib,"Mars.lib")
#pragma comment(lib, "BOCRates.lib")

// 函数声明, 判断文件模式是否有效
bool is_valid_fopen_mode(const char* mode);

// 通用二进制读取:从文件读取任意数据到内存
bool load_data_from_file(const char* filename, void* data, int size);

// 打印外汇牌价结构体信息
void print_rate_info(const EXCHANGE_RATE* rate);

int main()
{
    // 定义外汇牌价结构体变量
    EXCHANGE_RATE USDRates;
    // 从文件中读取数据存到结构体中
    load_data_from_file("D:/BC101/Examples/L07/L07_09_SAVE_USD_RATES/USDRates.txt", &USDRates, sizeof(EXCHANGE_RATE));
    print_rate_info(&USDRates);

    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;  // 没找到 → 非法
}

// 通用二进制写入:任意数据原样存文件
// 功能:把 任意一块内存数据 写入文件
// 参数:
//   filename  文件名
//   data      任意数据指针(void* 万能)
//   size      数据大小(sizeof(结构体))
// 返回:true 成功 / false 失败
bool save_data_to_file(const char* filename, const void* data, int size)
{
    const char* mode = "wb";  // 二进制写入模式

    // 1. 检查模式对不对
    if (!is_valid_fopen_mode(mode))
    {
        printf("错误:文件打开模式( %s )不合法!\n", mode);
        return false;
    }
    printf("文件打开模式( %s )合法!\n", mode);

    // 2. 打开文件
    FILE* fp = fopen(filename, mode);
    printf("文件句柄地址:%p\n", fp);
    // 3. 检查文件是否打开成功
    if (fp == NULL)
    {
        printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
        return false;
    }
    // 4. 打开成功,进行等待
    printf("打开文件成功!\n");

    int count;

    count = fwrite(data, size, 1, fp);
    if (count != 1) {
        printf("\n文件写入异常,写入个数:%d,期望个数:%d\n", count, 1);
        printf("错误编号:%d, 原因: %s\n", errno, strerror(errno));
        fclose(fp);

        return false;
    }

    printf("\n文件写入成功\n");

    // 5. 关闭文件,释放文件句柄
    fclose(fp);
    return true;
}

// 通用二进制读取:从文件读取任意数据到内存
// 参数:
//   filename  文件名
//   data      接收数据的内存地址
//   size      要读取的字节大小(必须和写入时一样)
// 返回:true 成功 / false 失败
bool load_data_from_file(const char* filename, void* data, int size)
{
    const char* mode = "rb";  // 二进制只读模式

    // 1. 检查模式对不对
    if (!is_valid_fopen_mode(mode))
    {
        printf("错误:文件打开模式( %s )不合法!\n", mode);
        return false;
    }
    printf("文件打开模式( %s )合法!\n", mode);

    // 2. 打开文件
    FILE* fp = fopen(filename, mode);
    printf("文件句柄地址:%p\n", fp);
    // 3. 检查文件是否打开成功
    if (fp == NULL)
    {
        printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
        return false;
    }
    // 4. 打开成功,进行等待
    printf("打开文件成功!\n");

    int count;
    // 读取数据
    count = fread(data, size, 1, fp);
    if (count != 1) {
        printf("\n文件读取异常,读取个数:%d,期望个数:%d\n", count, 1);
        printf("错误编号:%d, 原因: %s\n", errno, strerror(errno));
        fclose(fp);

        return false;
    }

    printf("\n文件读取成功\n");

    // 5. 关闭文件,释放文件句柄
    fclose(fp);
    return true;
}

// 打印外汇牌价结构体信息
void print_rate_info(const EXCHANGE_RATE* rate)
{
    printf("货币代码:%s\n", rate->CurrencyCode);
    printf("货币名称:%s\n", rate->CurrencyName);
    printf("发布时间:%s\n", rate->PublishTime);
    printf("现汇买入价:%.2f\n", rate->BuyingRate);
    printf("现钞买入价:%.2f\n", rate->CashBuyingRate);
    printf("现汇卖出价:%.2f\n", rate->SellingRate);
    printf("现钞卖出价:%.2f\n", rate->CashSellingRate);
    printf("中行折算价:%.2f\n", rate->MiddleRate);
}

此时运行程序,输出如下:

Snipaste_2026-05-04_22-50-28.png

此时,可以发现load_data_from_file函数与save_data_to_file函数,在打开文件这里,除了mode不同外,其他内容都是一样的,再就是一个使用的fread读文件,一个使用的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
 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
// L07_10_READ_RATES_FROM_FILE.cpp
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#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>

// 附加包含目录已经添加 D:/BC101/Libraries/Mars
// 此处引入自定义的静态库的头文件需要相对路径
#include "str.h"
#include "input.h"

// 附加包含目录已经添加  D:/BC101/Libraries/BOCRates/BOCRates.h
// 此处引入作者提供的静态库的头文件
#include "BOCRates.h"
// #pragma comment:VS 编译器专用指令,给编译器发命令
//      lib:表示我要链接一个静态库
//      路径/Mars.lib":库文件在哪里
// 链接器--附加库目录,加上 D:/BC101/Libraries/Mars 和 D:/BC101/Libraries/BOCRates目录
#pragma comment(lib,"Mars.lib")
#pragma comment(lib, "BOCRates.lib")

// 函数声明, 判断文件模式是否有效
bool is_valid_fopen_mode(const char* mode);

// 公共工具函数:打开文件(只给内部用,外面不用)
static FILE* open_file(const char* filename, const char* mode);

// 通用二进制写入:任意数据原样存文件
bool save_data_to_file(const char* filename, const void* data, int size);

// 通用二进制读取:从文件读取任意数据到内存
bool load_data_from_file(const char* filename, void* data, int size);

// 打印外汇牌价结构体信息
void print_rate_info(const EXCHANGE_RATE* rate);

int main()
{
    // 定义外汇牌价结构体变量
    EXCHANGE_RATE USDRates;
    // 从文件中读取数据存到结构体中
    load_data_from_file("D:/BC101/Examples/L07/L07_09_SAVE_USD_RATES/USDRates.txt", &USDRates, sizeof(EXCHANGE_RATE));
    print_rate_info(&USDRates);

    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;  // 没找到 → 非法
}

// 公共工具函数:打开文件(仅内部使用,外部不可调用)
// 功能:校验文件打开模式,并按指定模式打开文件
// 参数:
//   filename  要打开的文件路径
//   mode      文件打开模式(如 rb, wb)
// 返回:成功返回有效的文件指针(FILE*),失败返回 NULL
// static关键字表示函数是私有的,只能内部用,本文件私有函数(private)
static FILE* open_file(const char* filename, const char* mode)
{
    // 1. 检查模式对不对
    if (!is_valid_fopen_mode(mode))
    {
        printf("错误:文件打开模式( %s )不合法!\n", mode);
        return NULL;
    }
    printf("文件打开模式( %s )合法!\n", mode);

    // 2. 打开文件
    FILE* fp = fopen(filename, mode);
    printf("文件句柄地址:%p\n", fp);

    // 3. 检查文件是否打开成功
    if (fp == NULL)
    {
        printf("打开文件失败!错误编号:%d, 原因: %s\n", errno, strerror(errno));
        return NULL;
    }

    // 4. 打开成功,进行等待
    printf("打开文件成功!\n");

    // 返回文件句柄
    return fp;
}

// 通用二进制写入:任意数据原样存文件
// 功能:把 任意一块内存数据 写入文件
// 参数:
//   filename  文件名
//   data      任意数据指针(void* 万能)
//   size      数据大小(sizeof(结构体))
// 返回:true 成功 / false 失败
bool save_data_to_file(const char* filename, const void* data, int size)
{
    const char* mode = "wb";  // 二进制写入模式

    FILE* fp = open_file(filename, mode);

    if (!fp) {
        return false;
    }

    int count;

    count = fwrite(data, size, 1, fp);
    if (count != 1) {
        printf("\n文件写入异常,写入个数:%d,期望个数:%d\n", count, 1);
        printf("错误编号:%d, 原因: %s\n", errno, strerror(errno));
        fclose(fp);

        return false;
    }

    printf("\n文件写入成功\n");

    // 5. 关闭文件,释放文件句柄
    fclose(fp);
    return true;
}

// 通用二进制读取:从文件读取任意数据到内存
// 参数:
//   filename  文件名
//   data      接收数据的内存地址
//   size      要读取的字节大小(必须和写入时一样)
// 返回:true 成功 / false 失败
bool load_data_from_file(const char* filename, void* data, int size)
{
    const char* mode = "rb";  // 二进制只读模式

    FILE* fp = open_file(filename, mode);

    if (!fp) {
        return false;
    }

    int count;
    // 读取数据
    count = fread(data, size, 1, fp);
    if (count != 1) {
        printf("\n文件读取异常,读取个数:%d,期望个数:%d\n", count, 1);
        printf("错误编号:%d, 原因: %s\n", errno, strerror(errno));
        fclose(fp);

        return false;
    }

    printf("\n文件读取成功\n");

    // 5. 关闭文件,释放文件句柄
    fclose(fp);
    return true;
}

// 打印外汇牌价结构体信息
void print_rate_info(const EXCHANGE_RATE* rate)
{
    printf("货币代码:%s\n", rate->CurrencyCode);
    printf("货币名称:%s\n", rate->CurrencyName);
    printf("发布时间:%s\n", rate->PublishTime);
    printf("现汇买入价:%.2f\n", rate->BuyingRate);
    printf("现钞买入价:%.2f\n", rate->CashBuyingRate);
    printf("现汇卖出价:%.2f\n", rate->SellingRate);
    printf("现钞卖出价:%.2f\n", rate->CashSellingRate);
    printf("中行折算价:%.2f\n", rate->MiddleRate);
}

此时,同样能运行成功!

  • open_file函数不对外暴露,只本文件可用!
  • 只暴露必须用的函数,如save_data_to_fileload_data_from_file等。
Licensed under the GNU General Public License v3.0
最后更新于 2026年05月05日 22:47