×
Featured image of post 换个姿势学C语言第7章

换个姿势学C语言第7章

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

0. 说明

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

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

这是一本非常不错的书!

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

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

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

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

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

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

7.0 结构体

在学习课本内容前,我们先自己倒腾一下结构体的使用。

之前已经学习过 C 语言的许多基本数据类型,如整型int、浮点float、字符型 char 等,还学习了数组这种构造类型。数组中,所有的数据都是同一类型,调用起来非常方便。

除此以外,有时我们需要定义一些复杂的数据类型,它可能包括多个不同属性,每个属性需要用不同的类型来表示。该怎么实现呢?

C语言中,可以把一些有内在联系的不同变量组织起来,封装成一个整体,即定义成一个结构体(structure),以此来表示一种新的数据类型。之后,就可以像处理基本数据类型那样,对结构体类型进行各种操作。

结构体是一种构造类型,它由若干成员组成。其成员可以是一个基本数据类型,也可以是另一个构造类型。声明一个结构体的过程,就是创建一种新的类型名的过程。

1
2
3
4
5
6
struct 结构体名称
{
    类型 成员名称1;
    类型 成员名称2;   
    ...
};
  • 关键字 struct 表示声明的是一个结构体,“结构体名称”表示要创建的新类型名,大括号中是“成员列表”,一行一个定员,需要包括构成该结构体的所有成员。注意,声明结构体时大括号后的分号“;”不能遗漏。

现在以Book书构建结构体,如书名,作者,出版社,出版时间,价格等属性,并创建几个函数。

 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
// L07_00_BOOK_STRUCT.cpp
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#pragma warning(disable: 6031)
//
#include <stdio.h>
// 附加包含目录已经添加 D:/BC101/Libraries/Mars
// 此处引入自定义的静态库的头文件需要相对路径
#include "str.h"
#include "input.h"
// #pragma comment:VS 编译器专用指令,给编译器发命令
//      lib:表示我要链接一个静态库
//      路径/Mars.lib":库文件在哪里
#pragma comment(lib,"Mars.lib")

// 定义结构体:描述一本书的信息
// 书名,作者,出版社,出版时间,定价等属性,
struct Book {
    char title[50];   // 书名。如:换个姿势学C语言
    char author[15];   // 作者。如:何旭辉
    char press[100];   // 出版社。如:清华大学出版社
    char date[20];   // 出版时间,如:2022-09-01
    float price;   // 定价。如:118.00
};

// 1. 初始化书籍
// struct Book* book:这里的 book 是【指针】,不是普通变量!
void initBook(struct Book* book, const char* title, const char* author,
    const char* press, const char* date, float price)
{
    // -> 解释:
    // book 是指针,不能用 book.price
    // 必须用 book->title 表示:指针指向的结构体变量的成员
    // 等价于 (*book).title,但是 -> 更简洁
    // strCopy是Mars中自定义的静态函数
    strCopy(book->title, title);
    strCopy(book->author, author);
    strCopy(book->press, press);
    strCopy(book->date, date);
    book->price = price;
}

// 2. 打印书籍信息
void printBook(struct Book* book)
{
    printf("书名: %s\n", book->title);
    printf("作者: %s\n", book->author);
    printf("出版社: %s\n", book->press);
    printf("出版时间: %s\n", book->date);
    printf("定价: %.2f\n\n", book->price);
}

// 3. 修改书籍定价
void setPrice(struct Book* book, float newPrice)
{
    book->price = newPrice;
}

int main()
{
    // 定义一个普通结构体变量 book1
    struct Book book1;
    // &book1:取变量的地址,传给函数
    // 函数接收的是指针,所以传 &
    initBook(&book1, "换个姿势学C语言", "何旭辉", "清华大学出版社", "2022-09-01", 118.00);
    printf("===== 书籍1 =====\n");
    printBook(&book1);

    setPrice(&book1, 120.00);
    printf("===== 书籍1修改定价后信息如下 =====\n");
    printBook(&book1);

    return 0;
}

运行程序,输出如下:

Snipaste_2026-04-16_23-02-37.png

以上我们知道了结构体的简单用法,下面回到书中,跟作者一起学习如果获取外币牌价信息并保存到文件中。

7.1 使用结构体存储不同类型的多项数据

在第6章中,我们使用两个字符数组分别存储货币的名称和发布时间,,用一个double型数组分别存储某种外币的现汇买入价、现钞买入价、现汇卖出价、现钞卖出价和中行折算价 。函数GetRatesAndCurrencyNameByCode从服务器上获取最新的牌价并存入这些数组中。

详见5.4节 第5章 获取完整的牌价数据 5.4节 获取和显示货币名称,当时的代码如下:

 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
// L05_19_GET_RATES_AND_CURRENCY_NAME_BY_CODE.cpp
#include "D:/BC101/Libraries/BOCRates/BOCRates.h"
#include <stdio.h>
#pragma comment(lib, "D:/BC101/Libraries/BOCRates/BOCRates.lib")

int main()
{
    // 存储货币的五种价格
    double rates[5] = { 0 };
    // 货币名称
    char currencyName[33] = { 0 };
    // 发布时间
    char publishTime[20] = { 0 };
    int result = GetRatesAndCurrencyNameByCode("USD", currencyName, publishTime, rates);
    printf("%d\n", result);
    if (result == 1) {
        printf("货币名称:%s\n", currencyName);
        printf("发布时间:%s\n", publishTime);
        printf("现汇买入价:%.2f\n", rates[0]);
        printf("现钞买入价:%.2f\n", rates[1]);
        printf("现汇卖出价:%.2f\n", rates[2]);
        printf("现钞卖出价:%.2f\n", rates[3]);
        printf("中行折算价:%.2f\n", rates[4]);
    }
    else {
        printf("网络或服务器异常\n");
    }

    return 0;
}

这3个数组在逻辑上是相关的,但在代码上是独立的。如果需要向一个函数传递这3个数组,就必须要给函数设计3个参数,这会让程序变得冗长。更麻烦的是,如果未来需要新增一个变量或数组用于描述货币的其他信息,几乎需要修改所有相关的函数——为它们增加参数。

在设计程序的数据结构时,我们应尽量将一组相关的数据作为一个整体来处理,尽量避免“散装”。数组是将多项相关类型的数据集合在一起的方式,而结构体是将多个不同类型的数据项“打包”到一起的方法。

一种外币的信息可以用4个数组来描述:

1
2
3
4
5
6
7
8
// 存储货币的五种价格
double rates[5] = { 0 };
// 货币名称
char currencyName[33] = { 0 };
// 发布时间
char publishTime[20] = { 0 };
// 货币代码
char currencyCode[4] = { 0 };

我们可以使用结构体将这4个数组组合到一起定义成一种新的数据类型,新的数据可以将这些数组整合成一个整体以便对一组数据进行操作。可以把结构体理解成新的变量模板,就和之前的intfloatdouble一样,所不同的是在这种模板中可以存储多项数据。

7.1.1 定义结构体

定义结构体相当于定义一种数据类型的模板。未来可以基于这个模板来声明变量。定义结构体的方法很简单:

1
2
3
4
5
6
struct 结构体名称
{
    类型 成员名称1;
    类型 成员名称2;
    ...    
};

定义结构体的规则是:

  • 以struct开始,其后是结构体名称。结构体名称根据用途确定,例如存储学生数据就叫Student,存储员工数据就用Employee,存储牌价数据的结构体就可以叫ExchangeRate
  • 接下来是一对大括号,大括号内列出结构体成员。结构体成员可以是变量,数组,每一个成员占用一行,行末用分号结束。
  • 结构体结束的右大括号后应有分号。

在大括号内应像平时一样声明结构体变量的成员。

下面的结构体用于存储某种货币的牌价信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// L07_01_STRUCT.cpp
#include <stdio.h>

struct EXCHANGE_RATE
{
    char CurrencyCode[4];        // 货币代码
    char CurrencyName[33];       // 货币名称(中文)
    char PublishTime[20];        // 发布时间
    double BuyingRate = 0;       // 现汇买入价
    double CashBuyingRate = 0;   // 现钞买入价
    double SellingRate = 0;      // 现汇卖出价
    double CashSellingRate = 0;  // 现钞卖出价
    double MiddleRate = 0;       // 中行折算价
};

int main()
{
    struct EXCHANGE_RATE er;
    er.BuyingRate = 12.3;

    printf("现汇买入价: %f\n", er.BuyingRate);

    return 0;
}

这里说明一下,以上代码中定义的结构体变量struct EXCHANGE_RATE变量名是全大写的,实际工程实践不建议这样。

ℹ️ 信息
  1. 课程里大量用全大写(STUDENT、BOOK)

正常,因为:

  • 国内很多 C 语言教材、老教师、老课程习惯用全大写
  • 他们觉得大写醒目、好区分
  • 教学场景怎么清楚怎么来,不讲究工程规范
1
2
3
typedef struct BOOK {
 ...
} BOOK;

因此,上面这样定义 非常正常,完全是教学风格。

  1. 但企业 / 真实项目(Redis、Linux、Nginx)

几乎不用全大写!

  • 全大写约定只给宏(#define)用
  • 结构体大写会被认为不规范、容易冲突

企业真实风格是:

1
2
3
4
5
// 公共结构体
typedef struct Book { ... } Book;

// 内部底层结构体
typedef struct book_s { ... } book_t;

并且在Visual Studio 2022中直接输入struct关键字,然后按Tab自动补全是这样的:

1
2
3
4
struct MyStruct
{

};

即结构体名称也没有全用大写,而是用的驼峰首字母大写的形式。

为了与课本一致,我测试也用全大写作为结构体名称!!

需要说明的是,在定义这个结构体时:

  • 不再使用double rates[5]双精度型数组存储5个价格,而是单独定义了5个双精度型变量分别描述这5个价格,这样做是为了提高程序的可读性
  • 为货币代码、货币名称和发布时间定义了3个字符型数组。
  • 结构体的名称全部采用大写字母,单词之间用下划线分隔。

结构体的详细说明如下:

  • CurrencyCode货币代码,货币代码为3个字母(如EURCNY等),可以用字符数组存储。为了输出的方便,应为字符串终止符"\0"预留一字节,因此字符数组长度应为4。
  • CurrencyName货币名称,货币名称采用汉字,可以使用字符数组存储,字符数组长度设为33字节,最多允许16个汉字,为字符串终止符"\0"预留一字节。
  • PublishTime发布时间,发布时间为字符串,原始数据格式如“2020-12-12 00:00:05”,这种日期格式需要19字节存储,同时为字符串终止符"\0"预留一字节,所以字符数组长度应为20。
  • BuyingRate现汇买入价,采用双精度浮点型。
  • CashBuyingRate现钞买入价,采用双精度浮点型。
  • SellingRate现汇卖出价,采用双精度浮点型。
  • CashSellingRate现钞卖出价,采用双精度浮点型。
  • MiddleRate中行折算价,采用双精度浮点型。

至此,我们就完成了结构体的定义!

⚠️ 警告
  • 注意结构体定义右大括号后面要有分号;
  • 不要把结构体定义写在任何函数内部,它应该是独立的,不被任何函数包含的!
Licensed under the GNU General Public License v3.0
最后更新于 Apr 18, 2026 23:29 CST