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

这是一本非常不错的书!
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;
}
|
运行程序,输出如下:

以上我们知道了结构体的简单用法,下面回到书中,跟作者一起学习如果获取外币牌价信息并保存到文件中。
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个数组组合到一起定义成一种新的数据类型,新的数据可以将这些数组整合成一个整体以便对一组数据进行操作。可以把结构体理解成新的变量模板,就和之前的int、float和double一样,所不同的是在这种模板中可以存储多项数据。
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变量名是全大写的,实际工程实践不建议这样。
ℹ️ 信息
- 课程里大量用全大写(STUDENT、BOOK)
正常,因为:
- 国内很多 C 语言教材、老教师、老课程习惯用全大写
- 他们觉得大写醒目、好区分
- 教学场景怎么清楚怎么来,不讲究工程规范
1
2
3
| typedef struct BOOK {
...
} BOOK;
|
因此,上面这样定义 非常正常,完全是教学风格。
- 但企业 / 真实项目(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个字母(如EUR、CNY等),可以用字符数组存储。为了输出的方便,应为字符串终止符"\0"预留一字节,因此字符数组长度应为4。CurrencyName货币名称,货币名称采用汉字,可以使用字符数组存储,字符数组长度设为33字节,最多允许16个汉字,为字符串终止符"\0"预留一字节。PublishTime发布时间,发布时间为字符串,原始数据格式如“2020-12-12 00:00:05”,这种日期格式需要19字节存储,同时为字符串终止符"\0"预留一字节,所以字符数组长度应为20。BuyingRate现汇买入价,采用双精度浮点型。CashBuyingRate现钞买入价,采用双精度浮点型。SellingRate现汇卖出价,采用双精度浮点型。CashSellingRate现钞卖出价,采用双精度浮点型。MiddleRate中行折算价,采用双精度浮点型。
至此,我们就完成了结构体的定义!
⚠️ 警告
- 注意结构体定义右大括号后面要有分号
;。 - 不要把结构体定义写在任何函数内部,它应该是独立的,不被任何函数包含的!
7.1.2 声明结构体变量
在结构体定义好后,在程序中就可以基于结构体声明结构体变量了,方法如下:
例如,要基于之前定义的结构体EXCHANGE_RATE来定义结构体变量的代码如下:
1
| struct EXCHANGE_RATE USDRate;
|
EXCHANGE_RATE是结构体名称,USDRate是结构体变量名。
- 基于一个结构体可以定义一个或多个结构体变量,也可以定义结构体数组。
7.1.3 访问结构体变量的成员
一个结构体变量声明后,就可以使用它并访问它的成员。访问的方法是在结构体变量后面加点“.”再加成员变量名,然后把它当作普通变量和数组进行赋值、取值、取地址等操作。
在Visual Studio中,在一个结构体变量后输入点“.”,会看到Visual Studio根据结构体的定义给出了输入提示,并根据用户的输入自动完成代码。
可以看到提示非常智能,不仅列出了结构体的成员,还对其数据类型、用途分别进行了说明。
在程序中使用结构体:
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
| // L07_01_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 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 USDRates; // 声明结构体变量
USDRates.BuyingRate = 657.86;
USDRates.CashBuyingRate = 652.51;
USDRates.SellingRate = 660.65;
USDRates.CashSellingRate = 660.65;
USDRates.MiddleRate = 658.09;
// strCopy是将源字符串复制到目标字符串中
strCopy(USDRates.CurrencyCode, "USD");
strCopy(USDRates.CurrencyName, "美元");
strCopy(USDRates.PublishTime, "2020-11-21 10:30:00");
printf("货币代码: %s\n", USDRates.CurrencyCode);
printf("货币名称: %s\n", USDRates.CurrencyName);
printf("发布时间: %s\n", USDRates.PublishTime);
printf("现汇买入价: %f\n", USDRates.BuyingRate);
printf("现钞买入价: %f\n", USDRates.CashBuyingRate);
printf("现汇卖出价: %f\n", USDRates.SellingRate);
printf("现钞卖出价: %f\n", USDRates.CashSellingRate);
printf("中行折算价: %f\n", USDRates.MiddleRate);
return 0;
}
|
此时运行代码,输出如下:

%f 输出浮点数,默认输出6位小数,因此输出时尽量要控制小数点位数输出。%f 默认 = 6 位小数 ,%.2f = 2 位小数(最常用,价格、汇率都用这个) 。这个位置的小数点.就是表示小数点,意思是需要控制小数点后面保留几位小数。
因此,优化一下代码,控制价格输出2位小数。

7.1.4 结构体变量的内存占用和内存对齐
和普通变量一样,结构体变量也需要在内存中占用空间,理论上结构体变量占用内存空间是各成员占用空间的总和。 声明结构体变量时会按照结构体成员定义的次序为它们分配内存空间。
例如,上一个程序每个成员变量理论占用的内存空间大小和合计占用的空间大小统计如下:
| 序号 | 结构体成员 | 理论占用字节数 |
|---|
| 1 | char CurrencyCode[4]; | 4 |
| 2 | char CurrencyName[33]; | 33 |
| 3 | char PublishTime[20]; | 20 |
| 4 | double BuyingRate = 0; | 8 |
| 5 | double CashBuyingRate = 0; | 8 |
| 6 | double SellingRate = 0; | 8 |
| 7 | double CashSellingRate = 0; | 8 |
| 8 | double MiddleRate = 0; | 8 |
| 9 | 合计 | 97 |
- 可以使用
sizeof运算符计算结构体变量占用的空间大小。

可以看到,使用sizeof(struct EXCHANGE_RATE)可以计算如果我定义一个结构体EXCHANGE_RATE变量需要占用多少字节。
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
| // L07_01_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 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 USDRates; // 声明结构体变量
USDRates.BuyingRate = 657.86;
USDRates.CashBuyingRate = 652.51;
USDRates.SellingRate = 660.65;
USDRates.CashSellingRate = 660.65;
USDRates.MiddleRate = 658.09;
// strCopy是将源字符串复制到目标字符串中
strCopy(USDRates.CurrencyCode, "USD");
strCopy(USDRates.CurrencyName, "美元");
strCopy(USDRates.PublishTime, "2020-11-21 10:30:00");
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);
int size = sizeof(struct EXCHANGE_RATE);
printf("结构体EXCHANGE_RATE占用字节数: %d\n", size);
return 0;
}
|
此时通过sizeof(struct EXCHANGE_RATE);获取到的值是104字节,而之前通过理论计算各成员内存占用总计是97字节,这就相差 104-97=7字节,为什么会多7字节!!!
要知道多占的7字节去哪里了,可以先显示每一个结构体成员实际的地址,再次修改以上代码。
增加变量和成员的地址显示:
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
| // L07_01_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 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 USDRates; // 声明结构体变量
USDRates.BuyingRate = 657.86;
USDRates.CashBuyingRate = 652.51;
USDRates.SellingRate = 660.65;
USDRates.CashSellingRate = 660.65;
USDRates.MiddleRate = 658.09;
// strCopy是将源字符串复制到目标字符串中
strCopy(USDRates.CurrencyCode, "USD");
strCopy(USDRates.CurrencyName, "美元");
strCopy(USDRates.PublishTime, "2020-11-21 10:30:00");
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);
int size = sizeof(struct EXCHANGE_RATE);
printf("结构体EXCHANGE_RATE占用字节数: %d\n", size);
int size_v = sizeof(USDRates);
printf("结构体变量USDRates 占用字节数: %d\n", size_v);
printf("USDRates结构体变量的首地址: %p\n", &USDRates);
printf("USDRates结构体成员USDRates.CurrencyCode地址: %p\n", &USDRates.CurrencyCode);
printf("USDRates结构体成员USDRates.CurrencyName地址: %p\n", &USDRates.CurrencyName);
printf("USDRates结构体成员USDRates.PublishTime地址: %p\n", &USDRates.PublishTime);
printf("USDRates结构体成员USDRates.BuyingRate地址: %p\n", &USDRates.BuyingRate);
printf("USDRates结构体成员USDRates.CashBuyingRate地址: %p\n", &USDRates.CashBuyingRate);
printf("USDRates结构体成员USDRates.SellingRate地址: %p\n", &USDRates.SellingRate);
printf("USDRates结构体成员USDRates.CashSellingRate地址: %p\n", &USDRates.CashSellingRate);
printf("USDRates结构体成员USDRates.MiddleRate地址: %p\n", &USDRates.MiddleRate);
return 0;
}
|
运行代码,输出如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 货币代码: USD
货币名称: 美元
发布时间: 2020-11-21 10:30:00
现汇买入价: 657.86
现钞买入价: 652.51
现汇卖出价: 660.65
现钞卖出价: 660.65
中行折算价: 658.09
结构体EXCHANGE_RATE占用字节数: 104
结构体变量USDRates 占用字节数: 104
USDRates结构体变量的首地址: 010FF9D8
USDRates结构体成员USDRates.CurrencyCode地址: 010FF9D8
USDRates结构体成员USDRates.CurrencyName地址: 010FF9DC
USDRates结构体成员USDRates.PublishTime地址: 010FF9FD
USDRates结构体成员USDRates.BuyingRate地址: 010FFA18
USDRates结构体成员USDRates.CashBuyingRate地址: 010FFA20
USDRates结构体成员USDRates.SellingRate地址: 010FFA28
USDRates结构体成员USDRates.CashSellingRate地址: 010FFA30
USDRates结构体成员USDRates.MiddleRate地址: 010FFA38
|

计算结构体元素与下一个元素之间的间隔如下:
| 序号 | 结构体成员 | 理论占用字节数 | 内存地址 | 与下一个元素之间的间隔 |
|---|
| 1 | char CurrencyCode[4]; | 4 | 010FF9D8 | |
| 2 | char CurrencyName[33]; | 33 | 010FF9DC | |
| 3 | char PublishTime[20]; | 20 | 010FF9FD | |
| 4 | double BuyingRate = 0; | 8 | 010FFA18 | |
| 5 | double CashBuyingRate = 0; | 8 | 010FFA20 | |
| 6 | double SellingRate = 0; | 8 | 010FFA28 | |
| 7 | double CashSellingRate = 0; | 8 | 010FFA30 | |
| 8 | double MiddleRate = 0; | 8 | 010FFA38 | |
| 9 | 合计 | 97 | | |
- 可以使用
sizeof运算符计算结构体变量占用的空间大小。
| 序号 | 10进制 | 16进制 | 结构体成员 | 除以8的余数 |
|---|
| 1 | 17824216 | 010FF9D8 | char CurrencyCode[4]; | 0 |
| 2 | 17824217 | 010FF9D9 | | 1 |
| 3 | 17824218 | 010FF9DA | | 2 |
| 4 | 17824219 | 010FF9DB | | 3 |
| 5 | 17824220 | 010FF9DC | char CurrencyName[33]; | 4 |
| 6 | 17824221 | 010FF9DD | | 5 |
| 7 | 17824222 | 010FF9DE | | 6 |
| 8 | 17824223 | 010FF9DF | | 7 |
| 9 | 17824224 | 010FF9E0 | | 0 |
| 10 | 17824225 | 010FF9E1 | | 1 |
| 11 | 17824226 | 010FF9E2 | | 2 |
| 12 | 17824227 | 010FF9E3 | | 3 |
| 13 | 17824228 | 010FF9E4 | | 4 |
| 14 | 17824229 | 010FF9E5 | | 5 |
| 15 | 17824230 | 010FF9E6 | | 6 |
| 16 | 17824231 | 010FF9E7 | | 7 |
| 17 | 17824232 | 010FF9E8 | | 0 |
| 18 | 17824233 | 010FF9E9 | | 1 |
| 19 | 17824234 | 010FF9EA | | 2 |
| 20 | 17824235 | 010FF9EB | | 3 |
| 21 | 17824236 | 010FF9EC | | 4 |
| 22 | 17824237 | 010FF9ED | | 5 |
| 23 | 17824238 | 010FF9EE | | 6 |
| 24 | 17824239 | 010FF9EF | | 7 |
| 25 | 17824240 | 010FF9F0 | | 0 |
| 26 | 17824241 | 010FF9F1 | | 1 |
| 27 | 17824242 | 010FF9F2 | | 2 |
| 28 | 17824243 | 010FF9F3 | | 3 |
| 29 | 17824244 | 010FF9F4 | | 4 |
| 30 | 17824245 | 010FF9F5 | | 5 |
| 31 | 17824246 | 010FF9F6 | | 6 |
| 32 | 17824247 | 010FF9F7 | | 7 |
| 33 | 17824248 | 010FF9F8 | | 0 |
| 34 | 17824249 | 010FF9F9 | | 1 |
| 35 | 17824250 | 010FF9FA | | 2 |
| 36 | 17824251 | 010FF9FB | | 3 |
| 37 | 17824252 | 010FF9FC | | 4 |
| 38 | 17824253 | 010FF9FD | char PublishTime[20]; | 5 |
| 39 | 17824254 | 010FF9FE | | 6 |
| 40 | 17824255 | 010FF9FF | | 7 |
| 41 | 17824256 | 010FFA00 | | 0 |
| 42 | 17824257 | 010FFA01 | | 1 |
| 43 | 17824258 | 010FFA02 | | 2 |
| 44 | 17824259 | 010FFA03 | | 3 |
| 45 | 17824260 | 010FFA04 | | 4 |
| 46 | 17824261 | 010FFA05 | | 5 |
| 47 | 17824262 | 010FFA06 | | 6 |
| 48 | 17824263 | 010FFA07 | | 7 |
| 49 | 17824264 | 010FFA08 | | 0 |
| 50 | 17824265 | 010FFA09 | | 1 |
| 51 | 17824266 | 010FFA0A | | 2 |
| 52 | 17824267 | 010FFA0B | | 3 |
| 53 | 17824268 | 010FFA0C | | 4 |
| 54 | 17824269 | 010FFA0D | | 5 |
| 55 | 17824270 | 010FFA0E | | 6 |
| 56 | 17824271 | 010FFA0F | | 7 |
| 57 | 17824272 | 010FFA10 | PublishTime 在这个位置结束 | 0 |
| 58 | 17824273 | 010FFA11 | 不能被8整除,直接被编译器填充 | 1 |
| 59 | 17824274 | 010FFA12 | 不能被8整除,直接被编译器填充 | 2 |
| 60 | 17824275 | 010FFA13 | 不能被8整除,直接被编译器填充 | 3 |
| 61 | 17824276 | 010FFA14 | 不能被8整除,直接被编译器填充 | 4 |
| 62 | 17824277 | 010FFA15 | 不能被8整除,直接被编译器填充 | 5 |
| 63 | 17824278 | 010FFA16 | 不能被8整除,直接被编译器填充 | 6 |
| 64 | 17824279 | 010FFA17 | 不能被8整除,直接被编译器填充 | 7 |
| 65 | 17824280 | 010FFA18 | double BuyingRate = 0; | 0 |
| 66 | 17824281 | 010FFA19 | | 1 |
| 67 | 17824282 | 010FFA1A | | 2 |
| 68 | 17824283 | 010FFA1B | | 3 |
| 69 | 17824284 | 010FFA1C | | 4 |
| 70 | 17824285 | 010FFA1D | | 5 |
| 71 | 17824286 | 010FFA1E | | 6 |
| 72 | 17824287 | 010FFA1F | | 7 |
| 73 | 17824288 | 010FFA20 | double CashBuyingRate = 0; | 0 |
| 74 | 17824289 | 010FFA21 | | 1 |
| 75 | 17824290 | 010FFA22 | | 2 |
| 76 | 17824291 | 010FFA23 | | 3 |
| 77 | 17824292 | 010FFA24 | | 4 |
| 78 | 17824293 | 010FFA25 | | 5 |
| 79 | 17824294 | 010FFA26 | | 6 |
| 80 | 17824295 | 010FFA27 | | 7 |
| 81 | 17824296 | 010FFA28 | double SellingRate = 0; | 0 |
| 82 | 17824297 | 010FFA29 | | 1 |
| 83 | 17824298 | 010FFA2A | | 2 |
| 84 | 17824299 | 010FFA2B | | 3 |
| 85 | 17824300 | 010FFA2C | | 4 |
| 86 | 17824301 | 010FFA2D | | 5 |
| 87 | 17824302 | 010FFA2E | | 6 |
| 88 | 17824303 | 010FFA2F | | 7 |
| 89 | 17824304 | 010FFA30 | double CashSellingRate = 0; | 0 |
| 90 | 17824305 | 010FFA31 | | 1 |
| 91 | 17824306 | 010FFA32 | | 2 |
| 92 | 17824307 | 010FFA33 | | 3 |
| 93 | 17824308 | 010FFA34 | | 4 |
| 94 | 17824309 | 010FFA35 | | 5 |
| 95 | 17824310 | 010FFA36 | | 6 |
| 96 | 17824311 | 010FFA37 | | 7 |
| 97 | 17824312 | 010FFA38 | double MiddleRate = 0; | 0 |
| 98 | 17824313 | 010FFA39 | | 1 |
| 99 | 17824314 | 010FFA3A | | 2 |
| 100 | 17824315 | 010FFA3B | | 3 |
| 101 | 17824316 | 010FFA3C | | 4 |
| 102 | 17824317 | 010FFA3D | | 5 |
| 103 | 17824318 | 010FFA3E | | 6 |
| 104 | 17824319 | 010FFA3F | | 7 |
注意,可以在excel表里面使用公式对十六进制、十进制进行相互转换:
DEC2HEX,十进制转成十六进制。HEX2DEC, 十六进制转十进制。
通过以上表格计算可以看到,结构体成员PublishTime起始于38行的010FF9FD,由于它要占用20个字节,它最后一个字节是57行的010FFA10, 下一个内存地址是010FFA11,由于下一个成员是double类型的,因此,需要判断这个地址能不能被8整除,010FFA11不能被8整除,直接被编译器填充,必须找到下一个能被8整除的内存地址,也就是65行的010FFA18,刚好从58行到64行一共填充了7个字节,这就是多出来的那7字节!!
这7个字节是浪费的,编译器为了确保double型成员BuyingRate的【内存对齐】以提高内存访问效率,内存对齐的原则是:
- 各成员变量的起始地址相对于结构体的起始地址的偏移量(间隔)必须为该变量的类型所占用的字节数的整数倍。
- 各成员变量在存放时根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,如果某个成员的地址不符合上述原则,则在中间插入实际不使用的字节。
- 结构体变量的大小必须为结构体中占用最大空间的类型大小的位数,如果不足,则在最后加入不用的字节。
- 在32位系统 中,所有内存块的首地址都必须是4的倍数。
为什么要内存对齐:因为CPU从内存中读写数据时,并不能直接、精确地读写任意一字节的内容,而是一次读写一个内存块,内存块的大小 可以是2字节、4字节、8字节、16字节或32字节。如果不使用内存读取,可能会出现内存读写变慢。
ℹ️ 信息
结构体自身对齐:由成员中最大基本类型决定(char=1, int=4, double=8)
- 有 double → 结构体整体按 8 字节对齐
- 首地址必须是 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
| // L07_04_MEMORY_ALIGNMENT.cpp
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#pragma warning(disable: 6031)
//
#include <stdio.h>
struct TEST
{
int X = 0;
char Code[2];
int Y = 0;
};
int main()
{
struct TEST test;
printf("结构体变量test 占用字节数: %d\n", sizeof(test));
printf("test结构体变量的首地址: %p\n", &test);
printf("test结构体变量成员X的首地址: %p\n", &test.X);
printf("test结构体变量成员Code的首地址: %p\n", &test.Code);
printf("test结构体变量成员Y的首地址: %p\n", &test.Y);
return 0;
}
|
运行程序输出:
1
2
3
4
5
| 结构体变量test 占用字节数: 12
test结构体变量的首地址: 0055FB78
test结构体变量成员X的首地址: 0055FB78
test结构体变量成员Code的首地址: 0055FB7C
test结构体变量成员Y的首地址: 0055FB80
|

在EXCEL表中计算:
| 序号 | 十进制 | 十六进制 | 结构体成员 | 除以4的余数 |
|---|
| 1 | 5634936 | 0055FB78 | int X | 0 |
| 2 | 5634937 | 0055FB79 | | 1 |
| 3 | 5634938 | 0055FB7A | | 2 |
| 4 | 5634939 | 0055FB7B | | 3 |
| 5 | 5634940 | 0055FB7C | char Code[2] | 0 |
| 6 | 5634941 | 0055FB7D | | 1 |
| 7 | 5634942 | 0055FB7E | 不能被4整除,直接被编译器填充 | 2 |
| 8 | 5634943 | 0055FB7F | 不能被4整除,直接被编译器填充 | 3 |
| 9 | 5634944 | 0055FB80 | int Y | 0 |
| 10 | 5634945 | 0055FB81 | | 1 |
| 11 | 5634946 | 0055FB82 | | 2 |
| 12 | 5634947 | 0055FB83 | | 3 |
可以看到,在成员数组Code之后,编译器插入了2个无用的字节,如序号7和8所示,确保成员变量Y被放在0055FB80(可以被4整除)开始的内存块中。
综上,结构体的各个成员变量按照它们被声明的顺序在内存中顺序存储。第1个成员的地址和整个结构的地址相同,但编译器会自动对分配给结构体的成员变量的内存进行内存对齐以提高效率。
下面我们再增加一个成员变量。char Other[2];。
1
2
3
4
5
6
7
| struct TEST
{
int X = 0;
char Code[2];
int Y = 0;
char Other[2]; // 增加了这一行
};
|
运行输出:
1
2
3
4
5
| 结构体变量test 占用字节数: 16
test结构体变量的首地址: 004FFBD8
test结构体变量成员X的首地址: 004FFBD8
test结构体变量成员Code的首地址: 004FFBDC
test结构体变量成员Y的首地址: 004FFBE0
|

可以看到,交叉定义int-char-int-char后,结构体占用字节数是16。
我们再对结构体的定义进行优化,将int Y = 0; 放到char Code[2];的上一行去。
1
2
3
4
5
6
7
| struct TEST
{
int X = 0;
int Y = 0; // 这个成员移上来,同类放一起
char Code[2];
char Other[2]; // 增加了这一行
};
|
运行输出:
1
2
3
4
5
| 结构体变量test 占用字节数: 12
test结构体变量的首地址: 00B5FAC8
test结构体变量成员X的首地址: 00B5FAC8
test结构体变量成员Code的首地址: 00B5FAD0
test结构体变量成员Y的首地址: 00B5FACC
|

可以看到,就只对结构体的先后顺序进行一下调整,结构体变量占用的字节数就从16字节变成了12字节!!!少了4字节!
ℹ️ 信息
以后定义结构体的黄金法则:
- 把 “占用字节大” 的放一起,“占用字节小” 的放一起!
推荐排序:
- double (8 字节) → 最大的放最前面
- int /long (4 字节)
- short (2 字节)
- char / 数组 (1 字节) → 最小的放最后
从大到小排,内存最省;穿插乱放,填充一堆
从先到后:
long long / double (8 字节)int / float (4 字节)short (2 字节)char / 单字节数组 (1 字节)
也就是:定义结构体,同类的放在一起,占字节大的成员先定义,占字节小的成员后定义,这样可以尽可能的优化内存占用,提升效率。
7.1.5 使用typedef为结构体创建别名
看7.0节,我用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;
}
|
可以看到,在代码几个函数中用到了结构体,都需要struct Book* book这样写,以及在main函数里面定义一个结构体变量也要用struct Book book1;,每次使用时都要带上struct,不能像使用内置的int、float类型一样直接用,就显得有点麻烦!
这个时候就可以使用typedef关键字来为结构体定义一个别名。
使用别名后,修改后的代码:
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
| // 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")
// 定义结构体:描述一本书的信息
// 书名,作者,出版社,出版时间,定价等属性,
// 在定义结构体时,就使用typedef给结构体struct Book定义一个别名Book (注意,别名是右大括号后面的字符串)
typedef struct Book {
char title[50]; // 书名。如:换个姿势学C语言
char author[15]; // 作者。如:何旭辉
char press[100]; // 出版社。如:清华大学出版社
char date[20]; // 出版时间,如:2022-09-01
float price; // 定价。如:118.00
} Book;
// 1. 初始化书籍
// struct Book* book:这里的 book 是【指针】,不是普通变量!
// 因为定义了别名,此处参数 struct Book* book 就可以直接改成 Book* book
void initBook(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(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(Book* book, float newPrice)
{
book->price = newPrice;
}
int main()
{
// 定义一个普通结构体变量 book1
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;
}
|

可以看到,使用typedef定义结构体别名后,后续使用结构体时就像使用内置类型一样方便,不用每次都带上struct关键字!!
另外也可在在定义结构体后,在后面单独再定义别名:
1
2
3
4
5
6
7
8
9
| struct Book {
char title[50]; // 书名。如:换个姿势学C语言
char author[15]; // 作者。如:何旭辉
char press[100]; // 出版社。如:清华大学出版社
char date[20]; // 出版时间,如:2022-09-01
float price; // 定价。如:118.00
};
typedef struct Book Book;
|
这样也是可以的!!
如果要定义多个别名,可以这样:
1
2
3
4
5
6
7
| struct Book {
// ...
};
// 别名随便起:同名/不同名都行
typedef struct Book Book;
typedef struct Book bk;
typedef struct Book BookInfo;
|
课本上定义了一个外汇的别名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
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
| // L07_01_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 EXCHANGE_RATE
{
char CurrencyCode[4]; // 货币代码
char PublishTime[20]; // 发布时间
char CurrencyName[33]; // 货币名称(中文)
double BuyingRate = 0; // 现汇买入价
double CashBuyingRate = 0; // 现钞买入价
double SellingRate = 0; // 现汇卖出价
double CashSellingRate = 0; // 现钞卖出价
double MiddleRate = 0; // 中行折算价
};
typedef struct EXCHANGE_RATE ExchangeRate;
int main()
{
// struct EXCHANGE_RATE USDRates;
// 直接使用别名 ExchangeRate 代替 struct EXCHANGE_RATE
ExchangeRate USDRates; // 声明结构体变量
USDRates.BuyingRate = 657.86;
USDRates.CashBuyingRate = 652.51;
USDRates.SellingRate = 660.65;
USDRates.CashSellingRate = 660.65;
USDRates.MiddleRate = 658.09;
// strCopy是将源字符串复制到目标字符串中
strCopy(USDRates.CurrencyCode, "USD");
strCopy(USDRates.CurrencyName, "美元");
strCopy(USDRates.PublishTime, "2020-11-21 10:30:00");
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);
int size = sizeof(struct EXCHANGE_RATE);
printf("结构体EXCHANGE_RATE占用字节数: %d\n", size);
int size_v = sizeof(USDRates);
printf("结构体变量USDRates 占用字节数: %d\n", size_v);
printf("USDRates结构体变量的首地址: %p\n", &USDRates);
printf("USDRates结构体成员USDRates.CurrencyCode地址: %p\n", &USDRates.CurrencyCode);
printf("USDRates结构体成员USDRates.CurrencyName地址: %p\n", &USDRates.CurrencyName);
printf("USDRates结构体成员USDRates.PublishTime地址: %p\n", &USDRates.PublishTime);
printf("USDRates结构体成员USDRates.BuyingRate地址: %p\n", &USDRates.BuyingRate);
printf("USDRates结构体成员USDRates.CashBuyingRate地址: %p\n", &USDRates.CashBuyingRate);
printf("USDRates结构体成员USDRates.SellingRate地址: %p\n", &USDRates.SellingRate);
printf("USDRates结构体成员USDRates.CashSellingRate地址: %p\n", &USDRates.CashSellingRate);
printf("USDRates结构体成员USDRates.MiddleRate地址: %p\n", &USDRates.MiddleRate);
return 0;
}
|
7.1.6 获取货币牌价并填充至结构体
现在,我们已经定义了EXCHANGE_RATE这个结构体用于存储某种货币的全部牌价数据,也学习了访问结构体成员变量的方法,还知道了结构体变量声明后结构体的各个成员会按照声明的次序占用一块连续的内存。
准备好了存储美元牌价的结构体变量USDRates后,怎么获得美元牌价数据并填入其中呢?
外汇牌价接口库提供了针对结构体的函数GetRateRecordByCode,在该函数中只要将货币代码和结构体变量的地址作为参数传入,就可以获取这种货币的全部牌价数据并自动填充到结构体变量。其原型为:
1
| int GetRateRecordByCode(const char* code, ExchangeRate* results);
|
我们来自己实现一下。
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
| // L07_06_GET_RATE_RECORD_BY_CODE.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"
// 附加包含目录已经添加 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")
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);
}
else {
printf("网络或服务器异常\n");
}
return 0;
}
|
运行程序输出结果:

可以看到,与中国银行外汇牌价官网数据一致!
现在已经正常获取到美元的外汇牌价,后面就要考虑将这些数据存储到文件中!!
在4.2.3节知道: 日元的货币代码是JPY。
此时,如果我们考虑将29行的USD货币代码换成别的,如日元的货币代码JPY。然后再运行代码:

此时就可以获取到日元的外汇牌价。
哪到底各外汇的货币代码是什么?怎么获取呢。