结构体让复杂数据的描述变得简单。
案例:描述坐标变换
学习一项知识的最好办法,就是动手实践,下面,让我们参见如下的图片
假设我们希望完成一次坐标旋转变换,我们就需要采取如下的步骤。
x=x∗cosθ−y∗sinθx=x∗cosθ−y∗sinθ
y=x∗sinθ+y∗cosθy=x∗sinθ+y∗cosθ
变换成代码,就如下所示
/*
point.c
Rotation of points
BeginnerC
*/
#include <stdio.h>
#include <math.h>
#define PI 3.1415926
/*
AngleToRad
Calc the Rad
Arugument
angel
The angel want to calc
Return Value
The corresponding rad
*/
double AngleToRad(double angel)
{
return angel * (PI / 180);
}
int main()
{
double x = 12, y = 12;
double angle = 36;
double x_result = 0, y_result = 0;
x_result = x * cos(AngleToRad(angle)) - y * sin(AngleToRad(angle));
y_result = x * sin(AngleToRad(angle)) + y * cos(AngleToRad(angle));
printf("(%lf, %lf) to (%lf, %lf)\n", x, y, x_result, y_result);
return 0;
}
在这个案例中,我们注意到三件事:
- 我们编写了一个函数
在C语言中,自定义函数的基本语法便是
return_value function_name(argument_list)
{
/* Code */
}
而在这个案例中,我们使用 AngleToRad 实现角度到弧度的转换
- 我们定义了一个宏 PI
在预处理阶段,代码里面所有的 PI 都会被替换为 3.1415926
宏的意义在于,赋予一个数字以有用的含义
- 在编译的时候,我们添加了一个选项 -lm
这一选项的含义,在于引入新的函数库。
在C语言中,所有的一切,都是组件,而各个组件合并在一起,就成为了我们的程序。
事实上,这一错误的含义,就在于,编译器没有找到函数的定义(undefined reference),换而言之,我们知道有(math.h 提供了函数的声明),却不知道函数的实现。
解决的办法,就是引入对应的库(库就是由系统或其它第三方提供的函数实现集合,比如C语言标准库),让我们的函数,能够有具体的实现。
言归正传,我们可以明确的发现,对于坐标变换而言,这一实现很明显是不恰当的:
- 它让相互关联的x, y相互分离,让数据变得碎片化
解决的办法,就是引入结构体,让我们的数据,彼此关联。
声明结构体的基本语法
struct struct_name
{
/* value list */
};
定义一个结构体变量
struct struct_name value_name;
使用结构体变量
value.value_list_value;
/*
point_second.c
Rotation of points
BeginnerC
*/
#include <stdio.h>
#include <math.h>
#define PI 3.1415926
struct point
{
double x;
double y;
};
/*
AngleToRad
Calc the Rad
Arugument
angel
The angel want to calc
Return Value
The corresponding rad
*/
double AngleToRad(double angel)
{
return angel * (PI / 180);
}
int main()
{
struct point point = {12, 12}, point_result = {0, 0};
double angle = 36;
point_result.x = point.x * cos(AngleToRad(angle)) - point.y * sin(AngleToRad(angle));
point_result.y = point.x * sin(AngleToRad(angle)) + point.y * cos(AngleToRad(angle));
printf("(%lf, %lf) to (%lf, %lf)\n", point.x, point.y, point_result.x, point_result.y);
return 0;
}
在这一案例之中,我们用结构体对数据进行了包装,并用结构体的语法使用了变量。
事实上,我们可以将这一问题抽象为函数,让问题得到更大的简化。
很明显地发现,在我们抽象出自己的 PointRoute 函数之后,我们的代码可读性得到了很大的提升,顺利地解决了问题。
案例:描述博客文章
我们喜欢专注于写作,而将构建网站这些繁琐的工作,交付给工具去做。
正如您所见,我们需要一种描述每一篇文章的方式,用以实现主页的构建。
抛开上面的“社区书籍"这个标题不说,我们可以把问题专注于下面的标签与书籍名称
事实上,它就由 [TAG]-[TITLE] ,两个部分组成。
而想要描述这么一个简单的数据,其实我们就可以设计出一个如下的结构体。
struct book
{
char tag[256];
char title[256];
char content[256];
};
如您所见,它就有3个变量组成,其中一个描述 标签(tag),一个描述标题(title),还有一个代表文件的内容。
而我们就可以依据这个逻辑,设计一个简单的程序。
/*
community_book.c
Describle some community books
BeginnerC
*/
#include <stdio.h>
struct book
{
char tag[256];
char title[256];
char content[256];
};
int main()
{
struct book book_list[3] = {
{"C语言", "C程序设计语言", "Hello World"},
{"C语言", "C语言入门很简单", "马磊老师是我的编程启蒙人"},
{"伦理学著作", "道德情操论", "人最大的缺点是自欺欺人"}
};
for (int i = 0;i < sizeof(book_list) / sizeof(book_list[0]);i++)
{
printf("[%s]\n\t%s\n\t\t%s\n", book_list[i].tag, book_list[i].title, book_list[i].content);
}
return 0;
}
如您所见,我们构造了一个结构体数组,并对他们做出了循环输出。
在实践中,我们往往会从文件进行读入,并以 HTML 代码的形式输出他们。
结构体的本质
结构体的本质就是对二进制数据的再描述。
让我们来看一个案例。
/*
struct_bit.c
Use the struct to descible the bit
BeginnerC
*/
#include <stdio.h>
struct char_bit
{
unsigned bit_1 : 1;
unsigned bit_2 : 1;
unsigned bit_3 : 1;
unsigned bit_4 : 1;
unsigned bit_5 : 1;
unsigned bit_6 : 1;
unsigned bit_7 : 1;
unsigned bit_8 : 1;
};
int main()
{
struct char_bit bits = {};
char c = 27;
bits = *(struct char_bit*)(void*)(&c);
printf("%d %d %d %d %d %d %d %d\n", bits.bit_1, bits.bit_2, bits.bit_3, bits.bit_4 \
, bits.bit_5, bits.bit_6, bits.bit_7, bits.bit_8);
return 0;
}
在这里,我们使用了两种新的方式来处理我们的数据。
第一种叫做位域,在这里,位域就代表了每一个元素所占用的二进制位,我们这里设定为一位,就代表,只能是 0 / 1
第二种叫做指针,指针的本质是内存地址,类型的本质是数据解析方式,在这里,它经历的三个步骤
- 第一步,我们用 &c 方式,得到变量c的内存地址(数据储存于内存中)
- 第二步,我们将这个地址用通用指针进行描述(void* 指针是一个桥梁,代表“单纯的地址"(带类型的地址往往还决定了它的解析方式,比如,
char*代表我们处理的是字符数据,会采用字符方式进行对待,而 void* 不是,它就是地址)) - 第三步,我们将这个地址(变量c的地址)按照我们设定的内存布局方式进行解析,存入 bits 中
最终,我们非常顺利地得到了 27 的二进制位布局
这验证了我们的结论:C语言的结构体,就是对二进制数据的再描述