结构体讲解

目录

一.结构体类型的声明

(1)结构体的声明 

(2)结构体的创建和初始化

(3)匿名结构体

(4)结构体的自引用

二.结构体内存对齐

(1)对齐规则 

(2)为什么存在内存对齐?

(3)结构体传参

三.结构体实现位段 

(1)什么是位段 

(2)位段的内存分配 

(3)位段的跨平台问题 


一.结构体类型的声明

(1)结构体的声明 

struct tag
{member-list;
}variable-list;

结构体是一些值的集合,这些值称为成员变量。结构体的每一个变量可以是不同的类型。

例如使用结构体描述一个学生:

struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别
};//这里可以不写

(2)结构体的创建和初始化

#include<stdio.h>
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别
};//这里可以不写int main()
{struct Stu s1 = { "张三",18 ,"男"};//结构体成员的初始化要按顺序进行。	// struct Stu s1 = { "张三","男",18};//这样就是不行的。printf("%s ",s1.name);printf("%d ",s1.age);printf("%s ",s1.sex);printf("\n");//也可以指定进行初始化struct Stu s2 = {.age = 18,.name = "李四",.sex = "女"};printf("%s ", s2.name);printf("%d ", s2.age);printf("%s ", s2.sex);return 0;
}

(3)匿名结构体

在声明结构体的时候,可以不完全的声明。

struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20], * p;

这两个结构体就省略掉了结构体标签(tag)

那么问题来了?

第一个结构体和第二个结构体的成员变量都是一模一样的。

p = &x;

这个代码是否有问题?

 warning C4133: “=”: 从“*”到“*”的类型不兼容

编译器会发生警告,这两个结构体是不同的类型,所以是非法的。

匿名的结构体类型,如果没有对结构体类型进行重命名的话,基本上是只能使用一次。

(4)结构体的自引用

在一个结构体中包含一个类型为结构体本身的成员是否可以呢?
比如定义一个链表的节点:

struct Node
{int val;struct Node next;
};

 上面的代码正确吗?

结构体的大小应该是固定的,一个结构体包含自己和另一个整形变量,这肯定是不合理的,这样的变量会无穷大。正确的自引用应该是包含一个结构体指针指向下一个结构体。

typedef struct
{int val;Node* next;
}Node;

这个代码可行吗?

答案是不行的,因为Node是对前面的匿名结构体进行了重命名,但是在匿名结构体内存提前使用了Node类型的变量来创建成员变量,这是不行的。

所以在定义结构体的过程中尽量不要使用匿名结构体。

二.结构体内存对齐

我们已经掌握了结构体的基本使用,现在我们可以深入讨论一个问题:结构体的大小?

#include<stdio.h>
struct s1
{int a;char b;
};
struct s2
{char a;int b;char c;
};
int main()
{printf("%zd\n",sizeof(struct s1));printf("%zd\n",sizeof(struct s2));return 0;
}

 思考一下s1和s2所占字节是多少?

输出结果: 

 struct s1的大小是8个字节,为什么不是4(int)+ 1(char)=5个字节呢???

同理为什么struct s2不是6个字节呢?

说明在这个结构体中内存是存在一定的浪费的。 

(1)对齐规则 

偏移量就是距离结构体第一个字节的距离,第一个字节的偏移量是0,第二个偏移量是1,同理依次类推。 

struct s1
{int a;char b;
};

 我们先来计算这个结构体的大小。

规则1:就是结构体的第一个成员要放在最前面的位置

也就是int占据前4个字节。

什么是对齐数呢?

除了第一个成员变量之外,其他的结构体成员是需要对齐到某个数字开始存储的。

在vs2022中的对齐数是8,在Linux,gcc中是没有默认对齐数的,对齐数是成员自身的大小。

对齐数 = 编译器默认的一个对齐数和该成员变量大小的较小值 

例如在vs2022中 char的对齐数就是 1 (1 < 8);int 的对齐数就是4 (4 < 8);

讲解完了对齐数,回到上面那个问题。

第二个成员变量char 是要对齐到它的对齐数的整数倍处。而任何数都是1的整数倍,所以char就放在int的后面即可。

第三条规则:结构体的总大小为最大对齐数的整数倍。

最大对齐数是所有结构体成员变量的对齐数的最大值。

通过前两个规则我们知道, char的对齐数就是 1 (1 < 8);int 的对齐数就是4 (4 < 8),所以最大对齐数是4;而且已经占据了5个字节,但是并不是4的整数倍,所以这个结构体的大小要自动补齐到八个字节。

 所以这个结构体的大小是8个字节。

那么我们现在来计算struct s2 的字节

struct s2
{char a;int b;char c;
};

第一个结构体成员变量发在第一个字节,而第二个结构体成员int对齐到4的整数倍处也就是从偏移量4的字节开始占据4个字节,然后第三个结构体成员变量char对齐1的整数倍处,也就是偏移量8的字节,最大对齐数是4,根据第三条规则结构体的大小就应该是4的整数倍,也就是12了。 

结构体嵌套问题

我们把结构体struct s2 改变一下,

struct s1
{int a;char b;
};
struct s2
{char a;int b;char c;struct s1 d;
};

 那么此时的struct s2 的大小是多少?

规则4:嵌套的结构体要对齐到该结构体成员变量中最大对齐数的整数倍处。

结构体s1的最大对齐数是4,所以d应该对齐到4的整数倍处

这时候已经占据了20个字节,再根据规则三:整个结构体的最大对齐数4,而20刚好是4的倍数,所以不再变大了,这个结构体的大小就是20.

#include<stdio.h>
struct S3
{double d; char c;int i;
};
struct S4
{char c1;struct S3 s3;double d;
};
int main()
{printf("%zd\n", sizeof(struct S3));printf("%zd\n", sizeof(struct S4));
}

struct s3: 

 

不难推断处结构体s3的大小是16;

结构体s3的最大对齐数是8,故对齐到偏移量为8的位置,大小16个字节。s4的 最大对齐数就是8,也要对齐,刚好32就是8的倍数,所以这个结构体的大小就是32;

(2)为什么存在内存对齐?

  1. 平台原因:
    不是所有的硬件平台都能够访问任意的地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定的类型的数据,否则会抛出硬件异常。
  2. 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要进行两次内存的访问;而对齐的内存访问只需要一次。假设一个处理器总是从内存中取八个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作做来读或者写值了。否则,计算机可能需要执行两次内存访问,因为对象可能会被分放在两个8字节的内存块中。

总结:结构体内存对齐是拿空间来换取时间的做法。

那么在我们设计结构体时候,我们既要满足对齐,又要尽可能的节省空间

struct S 
{char a;int b;char c;
};
struct S 
{char a;char c;int b;
};

我们发现第二个结构体的大小明显小于第一个。

让占用空间小的成员尽可能的集中在一起。

(3)修改默认对齐数

#pragma这个预处理指令可以修改编译器的默认对齐数

#include<stdio.h>
#pragma pack(1)//将默认对齐数设置为1
struct S3
{double d; char c;int i;
};
struct S4
{char c1;struct S3 s3;double d;
};
int main()
{printf("%zd\n", sizeof(struct S3));printf("%zd\n", sizeof(struct S4));
}

 再次运行代码我们就会发现结果已经不一样了。

#pragma pack()

再加上这个就可以取消对齐数,还原为默认值。

结构体在对齐方式不合适的时候,我们就可以自己更改默认对齐数。

(3)结构体传参

#include<stdio.h>
struct S
{int arr[1000];int n;double d;
};
void print1(struct S tmp)
{int i = 0;for (i = 0; i < 5; i++){printf("%d ", tmp.arr[i]);}printf("%d ", tmp.n);printf("%lf\n", tmp.d);
}
void print2(const struct S* ps)
{int i = 0;for (i = 0; i < 5; i++){printf("%d ", ps->arr[i]);}printf("%d ", ps->n);printf("%lf ", ps->d);
}
int main()
{struct S s = { {1,2,3,4,5}, 100, 3.14 };print1(s);print2(&s);return 0;
}

观察print1和print2函数,这两个函数都是在打印这个结构体,那么这两个结构体有什么区别呢?或者他们两个哪一个更好?

答案是print2函数

函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的系统的开销比较大,所以会导致性能下降。

所以为了避免这样的情况,结构体传参的时候,要传结构体的地址。

三.结构体实现位段 

不是段位 

(1)什么是位段 

位段的声明和结构是类似的,有两个不同:

  1. 位段的成员必须是int 或者unsigned int或者signed int ,在C99中位段成员的类型也可以选择其他类型
  2. 位段的成员名后面后一个冒号和一个数字。
struct A
{int a:2;int b:5;int c:10;int d:30;
};

 A就是一个位段类型

那么位段A所占内存的大小是多少?

答案是8.为什么呢?

变量名的后面跟着一个冒号数字代表这个数字只占多少个bit位。

比如int c:10,表示这个c变量占据了10个bit位。

而我们知道正常的一个int类型的变量是4个字节,占据32个bit位。

所以这样位段可以节省内存。

首先分配4个字节的空间存储a,还剩30个bit,然后在把b放进去,还剩25个bit,再把c放进去,只剩15个bit了不够容纳d了,所以再分配4个字节的空间,然后把d放进去。总共使用了8个字节,所以这个结构体的大小是8个字节。

(2)位段的内存分配 

struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main() {struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;
}

 在vs2022上,申请到的一块空间是从右向左使用的。但是不同的平台这是不确定的。

10的二进制是1010,但是a只有3位,所以存入010,

12的二进制是1100,b有4位,存入1100

3的二进制是0011,c有5位存入0011

4的二进制0100,存入0100

所以从低到高,十六进制0x62 0x03 0x04,总共占据三个字节

(3)位段的跨平台问题 

  1. int位段被当成有符号数还是无符号数是不确定。
  2. 位段中的最大位数是不能确定的。(16位机器最大是16,32位机器最大32,写成27,在16位机器就会出问题) 
  3. 位段中的成员在内存中从左向右,还是从右向左分配这个是标准为定义的。
  4. 当一个结构体包含两个位段的时候,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总的来说:更结构体相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

(4)位段使用的注意事项

位段的成员共有一个字节,这样有些成员的起始地址并不是某个字节的起始位置,那么这些位置处是没有地址的,内存中的每一个字节分配一个地址,一个字节内部的bit位是没有地址

所以不能使用取地址操作符&利用scanf直接给位段的成员输入值,只是先输入放在一个变量中然后赋值给位段的成员。

#include<stdio.h>
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{struct A sa = { 0 };//scanf("%d", &(sa._b));//这是错误的//正确的示范int b = 0;scanf("%d", &b);sa._b = b;return 0;
}

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

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

相关文章

电脑桌面便签,怎么在电脑桌面上设置便签

在数字化时代&#xff0c;电脑已成为我们日常生活不可或缺的一部分。在我们使用电脑进行各种工作和学习的过程中&#xff0c;经常会遇到需要记录临时信息或提醒自己的情况。这时&#xff0c;设置便签在电脑桌面上就成为了一种非常便捷的方法。那么有一个问题&#xff0c;电脑桌…

2.8、下拉刷新与上拉加载

页面的下拉刷新与上拉加载功能在移动应用中十分常见,例如,新闻页面的内容刷新和加载。这两种操作的原理都是通过响应用户的触摸事件,在顶部或者底部显示一个刷新或加载视图,完成后再将此视图隐藏。 实现思路 以下拉刷新为例,其实现主要分成三步: 监听手指按下事件,记录…

LeetCode热题Hot100-无重复字符的最长子串

一刷&#xff0c;险些被自己绕进去了&#xff0c;哈哈哈 题目描述&#xff1a; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串的长度。 输入: s "abcabcbb" 输出: 3 def lengthOfLongestSubstring(self, s: str) -> int:if len(s) 0:re…

每天学点儿python(1)---print,input和注释

print函数 print语法格式 print(*objects, sep , end\n, filesys.stdout) sep参数默认为 一个空格 end&#xff08;输出末尾&#xff09;参数默认为 回车换行 file默认为 标准输出&#xff08;一般指屏幕&#xff09; 所以&#xff0c;如果想输出各个字段不用空格隔开&a…

使用colab仿李沐的卷积神经网络小代码

import torch from torch import nn #神经网络模块 from d2l import torch as d2l #(drive into deep learning)class Reshape(torch.nn.Module):def forward(self, x):return x.view(-1, 16*3*3)net torch.nn.Sequential( #序列化神经网络# 2d卷积层、输入通道数1&#xff0…

vue3+threejs新手从零开发卡牌游戏(七):创建卡组

在开始前先优化下之前的代码&#xff1a; 在之前hand/p1.vue中为了定位 utils文件夹下新建common.ts&#xff0c;将一些公用方法提取出来放在这里&#xff1a; 在game/Cards.ts中&#xff0c;我们调整下卡牌的厚度&#xff0c;由原来的0.02改为0.005&#xff0c;原因是之前的…

【Ucore操作系统】4. 地址空间

文章目录 【 0. 引言 】背景本章任务 【 1. C 中的动态内存分配 】1.1 C语言的内存分配1.2 kalloc 中的动态内存分配 【 2. 地址空间 】2.1 虚拟地址和地址空间2.1.1 地址虚拟化出现之前2.1.2 加一层抽象加强内存管理2.1.3 增加硬件加速虚实地址转换 2.2 分段内存管理2.2.1 等量…

Docker专题-04 Nginx部署

Docker专题-04 Nginx部署 注&#xff1a; 本教程由羞涩梦整理同步发布&#xff0c;本人技术分享站点&#xff1a;blog.hukanfa.com 转发本文请备注原文链接&#xff0c;本文内容整理日期&#xff1a;2024-03-21 csdn 博客名称&#xff1a;五维空间-影子&#xff0c;欢迎关注…

javaSwing扫雷游戏

一、介绍 1.1 背景 在1964年 有一个叫“方 块”的游戏&#xff0c;这是扫雷最原始的版本。后来&#xff0c;这个游戏被改成了另一种游戏&#xff0c;叫做“Rlogic”。在这个游戏中&#xff0c;玩家扮演了一名军队的军人&#xff0c;接受了一项艰难的任务&#xff1a;为指挥中…

24计算机考研调剂 | 中国科学院深圳

中国科学院深圳先进技术研究院&#xff08;国科大&#xff09;硕士招收调剂&#xff08;仅内部调剂&#xff09; 考研调剂招生信息 中国科学院深圳先进技术研究院集成所神经工程中心刘志远课题组接收2024年中国科学院大学硕士研究生调剂1名&#xff0c;最终录取为全日制专硕 材…

龙芯新世界系统(安同AOCS OS)安装使用HP 1010激光打印机

龙芯新世界系统&#xff08;安同AOCS OS&#xff09;安装及切换到Cinnamon桌面系统以后&#xff0c;缺少一个通用的打印机管理配置程序&#xff0c;因此通过以下命令就可以在控制面板中增加打印机配置项&#xff1a; sudo oma install system-config-printer 但是安装完成后&…

家政服务管理平台设计与实现|SpringBoot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

【c++】类和对象(三)构造函数和析构函数

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 朋友们大家好&#xff0c;本篇文章我们带来类和对象重要的部分&#xff0c;构造函数和析构函数 目录 1.类的6个默认成员函数2.构造函数2.1构造函数其他特性 3.构析函数3.1特性&#xff1a;…

c语言函数大全(C开头)

c语言函数大全(C开头) There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheated should leave quickly. 函数名…

sql——对于行列转换相关的操作

目录 一、lead、lag 函数 二、wm_concat 函数 三、pivot 函数 四、判断函数 遇到需要进行行列转换的数据处理需求&#xff0c;以 oracle 自带的表作为例子复习一下&#xff1a; 一、lead、lag 函数 需要行列转换的表&#xff1a; select deptno,count(empno) emp_num from…

MongoDB 入门简介

什么是 MongoDB&#xff1f; MongoDB 是一个基于分布式文件存储的开源数据库系统。它是一个 NoSQL&#xff08;Not only SQL&#xff0c;意为不仅仅是SQL&#xff09;数据库&#xff0c;使用文档&#xff08;BSON格式&#xff0c;类似于JSON&#xff09;来存储数据。MongoDB 以…

【工具】DataX 数据同步工具

简介 DataX 是阿里云 DataWorks数据集成 的开源版本&#xff0c;在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、Hologres、DRDS, databe…

基于java+springboot+vue实现的图书借阅系统(文末源码+Lw+ppt)23-328

摘 要 伴随着我国社会的发展&#xff0c;人民生活质量日益提高。于是对系统进行规范而严格是十分有必要的&#xff0c;所以许许多多的信息管理系统应运而生。此时单靠人力应对这些事务就显得有些力不从心了。所以本论文将设计一套“期待相遇”图书借阅系统&#xff0c;帮助商…

代码随想录训练营第55天 | LeetCode 583. 两个字符串的删除操作、​​​​​​LeetCode 72. 编辑距离、总结

目录 LeetCode 583. 两个字符串的删除操作 文章讲解&#xff1a;代码随想录(programmercarl.com) 视频讲解&#xff1a;LeetCode&#xff1a;583.两个字符串的删除操_哔哩哔哩_bilibili 思路 ​​​​​​LeetCode 72. 编辑距离 文章讲解&#xff1a;代码随想录(programm…

哪些行业需要在线制作电子证书系统?

哪些行业需要在线制作电子证书系统&#xff1f; 1、教育机构&#xff1a;学校和培训机构需要为学生和培训者颁发证书&#xff0c;您的系统可以帮助他们快速生成和管理这些证书。 2、企业及政府部门&#xff1a;用于员工培训、资质认证等&#xff0c;提高内部管理效率。 3、专…