C语言结构体的简单学习

128 阅读5分钟

结构体让复杂数据的描述变得简单。

案例:描述坐标变换

学习一项知识的最好办法,就是动手实践,下面,让我们参见如下的图片
在这里插入图片描述

假设我们希望完成一次坐标旋转变换,我们就需要采取如下的步骤。

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),还有一个代表文件的内容。

而我们就可以依据这个逻辑,设计一个简单的程序。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D5X6uNiL-1683190688600)(https://foruda.gitee.com/images/1677973746069701764/eac47084_871414.png "1673178019416.png")]

/*
    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语言的结构体,就是对二进制数据的再描述