【C语言】结构体、共用体、位域

news2024/9/28 17:34:52

结构体

1、 结构体的声明方法


struct struct_name {
	data_type member1;
	data_type member2;
	.
	.
};

这是其中一种声明方式~


2、定义一个结构体变量


struct struct_name variable;

3、访问成员变量


. 运算

一个结构体变量访问其成员时,使用的是 . 运算

下面的代码就用到了 . 运算,可以运行观察一下结果:

#include "stdio.h"
#include "stdint.h"

struct rectangle {
    uint32_t length;
    uint32_t width;
};

int main()
{
    //定义变量的同时并初始化成员的值
    struct rectangle rec = { 20,30 };
    //使用 . 运算打印其成员的值
    printf("length = %d\nwidth = %d\n", rec.length, rec.width);
    //修改成员的值
    rec.length = 30;
    rec.width = 15;
    printf("修改之后的值:\n");
    printf("length = %d\nwidth = %d\n", rec.length, rec.width);

    return 0;
}

-> 运算

这个 -> 运算主要是结构体指针变量访问其成员时用到的,这是一个 减号 -大于号> 组成的

将上面的那个例子修改为:

#include "stdio.h"
#include "stdint.h"
#include "malloc.h"

struct rectangle {
    uint32_t length;
    uint32_t width;
};

int main()
{
    //定义一个结构体指针,并为其开辟空间
    struct rectangle* rec_ptr = (struct rectangle*)malloc(sizeof(struct rectangle));
    //初始化成员的值
    rec_ptr->length = 30;
    rec_ptr->width = 15;

    //定义一个结构体变量
    struct rectangle rec;
    //初始化结构体变量rec成员的值
    rec.length = 40;
    rec.width = 20;
    //打印成员的值
    printf("结构体变量 rec:\n");
    printf("length = %d\nwidth = %d\n", rec.length, rec.width);
    printf("结构体指针 rec_ptr:\n");
    printf("length = %d\nwidth = %d\n", rec_ptr->length, rec_ptr->width);

    return 0;
}

上面的代码中用到了 malloc 这个函数,这个函数的作用是动态内存申请

计算机中分为栈区与堆区两部分,在C语言中基本数据类型声明的变量、函数等等都是存储在栈上,而malloc函数申请的内存是在堆区上的


4、结构体对齐方式


结构体的一般对齐方式是按照结构体中成员类型字节数最大的来对齐

看下面的结构体:

struct A {
    char c;
    int n;
};

由于最大的类型为 int 所以该结构体以 4 字节对齐

使用关键字 _Alignof 来输出一下对齐字节数

printf(“%zd\n”, _Alignof(struct A));

运行结果与预期的一样:4

我们再看一下这个结构体:

struct B {
    char c;
    char x;
    int n;
    float f;
    double lf;
};

还是按照刚才的思路分析,先找到最大的类型的成员,我们可以得到最大类型的为 double 8 字节,所以推测出结构体 B8 字节对齐

使用关键字 _Alignof 来验证一下对齐字节数是否为 8 字节对齐

printf(“%zd\n”, _Alignof(struct B));

运行结果与预期的一致:8

综上,所以说这个结论: 结构体的一般对齐方式是按照结构体中成员类型字节数最大的来对齐 是正确的

想一下对齐字节数的大小会影响什么呢?


5、结构体大小


如何计算一个结构体所占的字节数呢?
例如下面这个结构体:

struct C {
    char c;
    char x;
    int n;
};

我想应该有人会说是6字节,计算方法就是把结构体中所有成员的类型字节之和

其实再仔细想想,然后再结合一下结构体对齐方式,此时你就会感觉算错了

一个结构体所占用的字节大小与结构体的对齐方式相关

首先我们需要先确定结构体 C 是以几字节对齐的,由上面的结论可以明显知道是以 4 字节对齐

此时我们知道了以 4 字节对齐,但有什么用呢? 别急,我们一步一步来

结构体是以 4 字节对齐,那成员呢? 结构体中的成员是按照成员的类型来进行对齐,若是不足结构体对齐的整数倍则编译器会自动填充(这部分不太会表达),所以成员 c、x是按照 1 字节对齐,二者共占用 2 个字节,此时不足 4 的倍数则由编译器进行填充,所以成员 c、x 占用了 4 个字节;成员 n 本身是 int 类型,是4的整数倍则不需要编译器进行填充,综上结构体 C 的所占字节数为:8

下图为成员的分布情况:
在这里插入图片描述
若是我们更换一下成员的定义顺序会不会改变结构体的大小呢? 答案是会的

还以结构体 C 为例,我们更改一下成员定义次序:

struct C {
    char c;
    int n;
    char x;
};

此时成员的分布情况:
在这里插入图片描述
由图可以清楚的看到结构体大小变成了 12

由此可见,合理的规划成员的定义次序可以减少内存的使用

以上两个例子可以使用关键字: sizeof 打印看一下


6、结构体及成员的地址情况


在数组中我们知道,数组名就是数组的起始地址,而在结构体中,结构体的地址与结构体中第一个成员的地址一样
运行一下以下代码即可辨别真伪:

#include "stdio.h"

struct D {
    char c;
    int n;
    char x;
};

int main()
{
    struct D tmp;
    printf("struct adder:   %p\n", &tmp);
    printf("member_1 adder: %p\n", &tmp.c);
    return 0;
}

运行结果如下:

struct adder: 0x7ffd493eb520
member_1 adder: 0x7ffd493eb520

那第二个成员地址呢?第三个呢?第四个呢? …

其实想得到也不难,但是又牵扯到字节对齐问题了,第二个成员地址是第一个成员地址加上对齐字节数,我们可以验证一下:

    struct D tmp;
    printf("struct adder:   %p\n", &tmp);
    printf("member_1 adder: %p\n", &tmp.c);
    printf("member_2 adder: %p\n", &tmp.n);
    printf("member_3 adder: %p\n", &tmp.x);

输出结果如下:

struct adder: 0x7ffeb1a6f63c
member_1 adder: 0x7ffeb1a6f63c
member_2 adder: 0x7ffeb1a6f640
member_3 adder: 0x7ffeb1a6f644

此时如果我们更换一下成员定义次序,又会出现什么情况呢?
将代码更改为:

struct D {
    char c;
    char x;
    int n;

};

再运行一下这段代码:

    struct D tmp;
    printf("struct adder:   %p\n", &tmp);
    printf("member_1 adder: %p\n", &tmp.c);
    printf("member_2 adder: %p\n", &tmp.x);
    printf("member_3 adder: %p\n", &tmp.n);

此时输出结果如下:

struct adder: 0x7ffd95a5c6a0
member_1 adder: 0x7ffd95a5c6a0
member_2 adder: 0x7ffd95a5c6a1
member_3 adder: 0x7ffd95a5c6a4

结果又发生了变化,联想一下前面的成员分布情况可以明白为啥得出这个结果

因此,结构体开辟的内存也是连续的


不行了,语言组织能力太弱了,就这样吧,结构体相关的就到此结束,下面再讨论一下共用体

共用体

在C语言中,共用体也是一种构造类型

共用体,共用体,它的特点就体现在 共用 二字上,所谓的共用就是一块地址,共用体内的所有成员共同使用,而这个地

址的大小则有成员中最大数据类型的那个决定,举一个我们都知道的例子:

我们可以把共用体想象成一个QQ群,群主与管理员相当于共用体成员,这个群能加入的人员数目上限相当于共用体的内存
在这个例子中,群主就是"老大",决定着“人员数目”的上限,群主的“身份”改变,就意味着“人员数目”上限的改变
群主与管理员都可以对人员数目进行"管理",但是决定“人员数目”上限的权利还是掌握在群主手里


共用体的声明、访问成员的值、改变成员的值等等都与结构体类似,把关键 struct 更换成 union 即可,就不一一解释了,重点是看一下二者的不同

1、内存大小


我们先声明好一个共用体:

union Data {
	char c;
	short s;
	int x;
};

首先按照共用体的定义先分析一下上面共用体 Data 的一些参数,根据定义可知共用体的内存大小由:成员中最大数据类型的那个决定,所以推断出共用体Data的内存大小就是 4

printf("%d\n", sizeof(union Data));

通过printf运行的结果可得,我们的推断成立


2、改变成员的数据

还用上面那个共用体,定义一个共用体变量,然后进行初始化:

int main()
{
	union Data tmp;
	tmp.c = 'A';
	tmp.s = 72;
	tmp.x = 99;

	printf("%c %d %d\n", tmp.c, tmp.s, tmp.x);

	return 0;
}

先别看下面的分析,自己先分析一下运行结果~

我们运行之后发现结果是: c 99 99

是不是很惊讶,先别惊讶,我们一步一步分析

这个对应定义中的: 共用体中的所以成员共同使用同一块内存,第一步先往这块地址写入字符’A’,然后又写入 77,最后又写入99,最终这块地址上的值就是99了,正好对应输出结果

通过上面的结果可以总结为一句话:在修改共用体内的某个成员的值时,其他的成员的值也会跟着改变

是不是感觉共用体没多大用处,刚开始我也是这样认为的,当我接触到嵌入式开发时就发现了它的骚操作,会在下一章节来说明它的一个用处

位域

有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如在单片机开发中,LED有点亮与熄灭两种状态,用0和1进行表示即可,此时可以使用位域这一数据结构。

在定义结构体时,我们可以指定某个成员所占用的二进制位数,使用方法如下:

struct domain {
	unsigned int word;
	unsigned char status: 1;
	unsigned int half: 16;
};

: 后面的数字代表成员所占用的二进制位数,对于上面的代码来说,成员word没有被限制位数,所以word是32位的大小,成员status、half被 : 后面的数字限制了,依次为1bit、16bit

被限制后的成员存储的数据范围会发生变化,成员word没有被限制,能存储的数范围为:0~2^32,成员status被 : 限制为1bit,因此只能存储0或者1,同理成员half能存储的范围为:0~2^16。若是在赋值时,超出范围会报这种错误:

struct domain test;
test.status = 8;

main.c: In function ‘main’:
main.c:13:23: warning: unsigned conversion from ‘int’ to ‘unsigned char:1’ changes value from ‘8’ to ‘0’ [-Woverflow]
   13 |         test.status = 8;
      |  

接下来我们来看一下此时的结构体domain所占用的内存与内存分布情况

由上面的介绍我们可以直接得出结构体domain是以4字节对齐,内存分布情况是这样的:

在这里插入图片描述
所占内存大型为8字节。

可以使用sizeof计算一下:

printf(“size = %ld\n”, sizeof(struct domain));

输出结果:size = 8

接着我们改变一下成员的声明顺序,然后再看一下内存分布与内存大小,我们改变成下面这样:

struct domain {
	unsigned char status : 1;
	unsigned int word;
	unsigned int half : 16;
};

此时的内存分布情况是这样的:
在这里插入图片描述

printf(“size = %ld\n”, sizeof(struct domain));

输出结果:size = 12

由此可见,合理的规划成员的定义次序可以减少内存的使用

以上就是对结构体、共用体、位域的相关介绍,有问题的地方请在评论区指出

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/130562.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BM35 判断是不是完全二叉树

题目 给定一个二叉树,确定他是否是一个完全二叉树。 完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树。&a…

深入理解ConcurrentHashMap1.7源码

1. 概述 HashMap在我们的日常生活中使用很多,但是它不是线程安全的。我们可以使用HashTable来代替,主要实现方式是在方法中加入synchronized,所以效率也比较低。因此,对于键值对,我们可以尝试使用ConcurrentHashMap来…

实验室规划设计方案SICOLAB

一、实验室规划设计 喜格提供实验室布局方案 根据实验室性质、实验室定位、实验室功能、实验类型、实验工艺流程以及国家相关标准合理的规划布局。 喜格提供仪器摆放布局方案 根据该实验流程来确定仪器的种类、数量、规格型号、外形尺寸、电压功率等参数以及摆放位置以及提…

【Linux】tee、tail、killall、|、||、、命令学习

|、||、&、&&辨析 竖线‘|’在linux中是管道符的意思,将‘|’前面命令的输出作为’|后面的输入; 双竖线‘||’,用双竖线‘||’分割的多条命令,执行的时候遵循如下规则:如果前一条命令为真,则…

还在喷农民歌唱家大衣哥吗?他的一个不经意间的举动却造福了乡里

农民歌唱家大衣哥,一直以来都饱受争议,有人说他是炒货专家,然而事实真的如此吗?事实上,大衣哥也做了很多好事,像修桥补路等都不说了,单就他的一个不经意间的举动,就造福了四乡八邻。…

Windows内核--CPU和内核(1.7)

Windows内核支援哪些CPU? Intel x86/x86_64 IA64已不再支持. AMD amd64 ARM (Windows On Arm: WOA) ARM具备低功耗优势, 除了高通, 还有Broadcom/NXP等都支援ARM架构. 苹果自研M系列开了头,ARM不仅有低功耗,同样有性能,Windows也想分一杯羹…

【vue系列-03】vue的计算属性,列表,监视属性及原理

vue的核心属性一,vue核心属性1,计算属性2,监视属性3,样式绑定3.1,class样式绑定3.2,style样式绑定4,条件渲染5,列表渲染5.1,遍历列表5.2,key的作用5.3&#x…

2022年全国职业院校技能大赛中职组网络安全竞赛——隐写术应用解析(超详细)

2022年全国职业院校技能大赛中职组网络安全竞赛——隐写术应用解析(超详细) B-8任务八:隐写术应用 *任务说明:仅能获取Server8的IP地址 环境需求私信博主 1.找出文件夹1中的文件,将文件中的隐藏信息作为Flag值提交; 解题步骤如下 2.找出文件夹2中的文件,将文件中的隐藏信息…

基于Vue的数据可视化设计框架,数据大屏可视化编辑器

开发文档(★★★★★) 请访问 https://lizhensheng.github.io/vue-data-view/ 完整代码下载地址:基于Vue的数据可视化设计框架,数据大屏可视化编辑器 简介 DataView是一个基于Vue的数据可视化设计框架提供用于可拖拽的控件提供…

Spring之IOC入门案例

目录 一:IOC入门案例实现思路分析 1.IOC容器管理什么? 2. 如何将被管理的对象告知 IOC 容器 ? 3.被管理的对象交给 IOC 容器,要想从容器中获取对象,就先得思考如何获取到 IOC 容器 ? 4.IOC 容器得到后,如何从容…

C++首超Java

TIOBE 公布了 2022 年 12 月的编程语言排行榜。 TIOBE 将于下个月揭晓其 2022 年度编程语言,目前共有 3 个候选者:Python、C 和 C。TIOBE CEO Paul Jansen 指出,虽然 Python 和 C 已多次斩获该头衔,而 C 仅在 2003 年获得过一次&a…

Android---开发笔记

ListView控件 <ListViewandroid:id"id/main_iv"android:layout_width"match_parent"android:layout_height"match_parent"android:layout_below"id/main_top_layout"android:padding"10dp"android:divider"null&qu…

彩色圣诞树圣诞树

目录 一、圣诞介绍 二、技术需要 三、效果展示 四、实现步骤 五、颜色的更改 六、源码 一、圣诞介绍 基督教纪念耶稣诞生的重要节日。亦称耶稣圣诞节、主降生节&#xff0c;天主教亦称耶稣圣诞瞻礼。耶稣诞生的日期&#xff0c;《圣经》并无记载。公元336年罗马教会开始在…

JavaScript对象与类的创建

1、面向过程与面向对象 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候再一个一个的依次调用就可以了。面向对象是把事务分解成为一个个对象&#xff0c;然后由对象之间分工与合作。 面向过程与面向对象对比 面…

LeetCode11.盛水最多的容器

11. 盛最多水的容器 该题用的是贪心的思想&#xff0c;也即每一步都以更加靠近最值为目标&#xff0c;用双指针维护height数组&#xff0c;接下来我用我自己通俗的语言尽可能解释双指针这种做法的正确性&#xff1a; 首先双指针指向数组两端&#xff0c;从两端开始&#xff0…

必看!Salesforce发布2023年全球科技重要趋势和预测

Salesforce领导者处于影响企业的最新趋势、技术和挑战的前线&#xff0c;通过市场分析、客户对话等方面带来专业知识和洞察力。距离2023年还剩不到一周的时间&#xff0c;未来一年企业应该如何布局&#xff0c;技术会如何发展&#xff0c;值得我们密切关注。 企业需要了解的5项…

IB分数如何影响您的大学申请?

参加 2022 年 11 月 IB 考试的学生将于 1 月 2 日收到IBDP&IBDP相关课程的分数。 大多数在新加坡提供 IB 课程的国际学校都在每年 5 月参加考试。 但是有些学校在 11 月参加 IBDP 考试。其中包括圣约瑟国际学院(SJII)、英华学校(ACS)、澳大利亚国际学校(SAIS)、华中国际学校…

牛客竞赛每日俩题 - 动态规划3

目录 类01背包问题&#xff0c;选or不选 变种走方格 类01背包问题&#xff0c;选or不选 不同的子序列_牛客题霸_牛客网 问题翻译&#xff1a; S有多少个不同的子串与T相同 S[1:m]中的子串与T[1:n]相同的个数 由S的前m个字符组成的子串与T的前n个字符相同的个数 状态&#xf…

SHA3算法笔记

文章目录1 INTRODUCTION2 GLOSSARY3 KECCAK-p Permutations3.1 State3.1.2 Converting Strings to State Arrays3.1.3 Converting State Arrays to Strings3.1.4 Labeling Convention for the State Array3.2 Step Mappingsthetarhopichiiota3.3 KECCAK-p[b, n~r~]3.4 KECCAK-f…

如何使用 Blackbox Exporter 监控 URL?

前言 监控域名和 URL 是可观察性的一个重要方面&#xff0c;主要用于诊断可用性问题。接下来会详细介绍如何使用 Blackbox Exporter 和 Prometheus 在 Kubernetes 中实现 URL 监控。 Blackbox Exporter 简介 Blackbox Exporter 是 Prometheus 的一个可选组件&#xff0c;像其…