自定义类型:结构体

news2025/1/20 6:02:17

ok,兄弟们,今天来写关于自定义类型的博客,先来看结构体。

结构体

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

1.结构体类型的声明

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

以上就是结构体声明的格式。比如说我们定义一个学生变量可以这样来。

struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
}; //分号不能丢

 当然,结构体中也存在一些特殊的声明。

比如说匿名类型

//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;
struct
{
 int a;
 char b;
 float c;
}a[20], *p;

可以看到,上面两个结构体并没有名字,因此被称为匿名结构体。那么问题就来了,我们可以很明白的看到上面的两个结构体里面所包含的变量是相同的,那么,这两个结构体是否相同呢?其实啊,编译器会将这两个结构体看成是两个完全不相同的类型,所以他们并不是相同的。

2.结构的自引用

顾名思义,结构体的自引用就是结构体自己引用自己。

先来看一个自引用的栗子

struct Node
{
 int data;
 struct Node next;
};

好像这就完成了自引用,但是,这样写会不会出现问题呢?

是的,这样写的话,这个结构体的大小是未知的。所以我们需要一个换一种写法

struct Node
{
 int data;
 struct Node* next;
};

 下面再来看一种写法是否可行

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

很明显,这是不行的,因为Node的调用在Node之前。

所以我们可以采取typedef来改一下

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

3.结构体变量的定义和初始化

ok,下面我们来学习一下关于结构体变量的定义和初始化

struct Point
{
 int x;
 int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};
struct Stu        //类型声明
{
 char name[15];//名字
 int age;      //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
 int data;
 struct Point p;
 struct Node* next; 
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
//或者是使用 . ->来单个赋值,这里就不在展示了。

4.结构体内存对齐

下面呢,我们来学一下关于结构体大小的求法,不妨先来看一个栗子

struct S1
{
 char c1;
 int i;
 char c2;
};
printf("%d\n", sizeof(struct S1));

从结果来看,很明显,结构体的大小就肯定不是简单的各变量求和了

这就不得不提到结构体的内存对齐了,通常来讲,内存对齐的规则如下

1. 第一个成员在与结构体变量偏移量为 0 的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的 较小值
VS 中默认的值为 8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

下面,我将通过画图的方式来详细的讲解内存对齐

从0开始存储c1,然后下一个i是Int类型,大小是4(小于vs的最大对齐数),但是存储完c1之后的下一个偏移量是1,不是4的倍数,因此要浪费一些空间来找到int的偏移量,然后从偏移量为4开始存储,存四个字节之后再继续,存完i之后的偏移量为8,是char的整数倍,因此在下一个存储8 ,然后所有的对齐数最大的是4,因此对吼的总大小应该是4的整数倍,所以答案是12。

到这里肯定就有人会有疑问了,哎呀,这个结构体这样存,那浪费的空间不来大了啊。其实,结构体这样存储是有道理的,我们不妨来看一下

1. 平台原因 ( 移植原因 )
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因
数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

所以,总的来说

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

所以啊,我们在设计结构体变量的时候,要尽可能的减少他的大小。

那就是让占用空间小的成员尽量集中在一起。  

比如

struct S1
{
 char c1;
 int i;
 char c2;
};
struct S2
{
 char c1;
 char c2;
 int i;
};

其实在C语言提供了一种修改默认对齐数的方法,那就是使用#pragma 这个预处理指令

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
    return 0;
}

 

 其实上述的代码就是将S1的对齐数改成了8,将S2的对齐数改成了1,因此答案会出现12和6

5.结构体传参

我们还是直接来看代码吧

struct S
{
 int data[1000];
 int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
 printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
 printf("%d\n", ps->num);
}
int main()
{
 print1(s);  //传结构体
 print2(&s); //传地址
 return 0;
}

在上面的代码中,我们首先的print2函数,因为

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

6.结构体实现位段(位段的填充&可移植性)

首先我们闲来了解一下位段的概念

1. 位段的成员必须是 int unsigned int signed int ,或者是char,unsigned char
2. 位段的成员名后边有一个冒号和一个数字。

我们还是用一个栗子来看一下位段的定义

struct A
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};

 在上面学习结构大小的计算的时候我们了解到,结构体有时候会存在很多的内存空间浪费,所以,我们给出了位段这个概念,目的就是为了减少内存浪费,比如上述代码的 int _a:2的意思就是给_a分类两个比特的空间。也就是说我们可以根据要存储的数据的大小不同,来灵活的改变变量的大小来起到节约空间的作用。

不妨将两个代码做个比较看一下

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
}A;
struct B
{
	int a;
	int b;
	int c;
	int d;
}B;
int main()
{
	printf("A=%d B=%d\n", sizeof(A), sizeof(B));
	return 0;
}

很明显,在使用位段之后的结构体大小明显的减少了。

下面,我们将主要来研究一下关于位段的大小计算。

1. 位段的成员可以是 整形家族类型。
2. 位段的空间上是按照需要以 4 个字节( int )或者 1 个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段

我们不妨通过一个栗子来详细的研究一下关于位段的内存空间开辟(这里以小端存储为栗)

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

 因为位段的限制,所以变量能存储的比特位数也就被限制了。

关于位段的跨平台问题。

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

因此,跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

关于位段的一些应用,这个暂作了解,详情以后再谈

好的,在最后呢,给大家展示一些关于结构体和位段的练习,并且我会在之后的博客中进行讲解。

//练习1
struct S2
{
 char c1;
 char c2;
 int i;
};
printf("%d\n", sizeof(struct S2));
//练习2
struct S3
{
 double d;
 char c;
 int i;
};
printf("%d\n", sizeof(struct S3));
//练习3-结构体嵌套问题
struct S4
{
 char c1;
 struct S3 s3;
 double d;
};
printf("%d\n", sizeof(struct S4));

 练习4

struct _Record_Struct
{
  unsigned char Env_Alarm_ID : 4;
  unsigned char Para1 : 2;
  unsigned char state;
  unsigned char avail : 1;
}*Env_Alarm_Record;
struct _Record_Struct *pointer = (struct _Record_Struct*)
malloc(sizeof(struct _Record_Struct) * MAX_SIZE);

 当A=2, B=3时,pointer分配几个字节的空间?

ok,今天的博客就到这里了,我们下期债见。

答案:1. 8   2. 16   3. 32   4. 9

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

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

相关文章

学习 Python 之 Pygame 开发魂斗罗(十六)

学习 Python 之 Pygame 开发魂斗罗&#xff08;十六&#xff09; 完成最终的魂斗罗1. 创建Sound类2. 添加背景音乐3. 添加玩家发射子弹音效4. 增加击中boss要害音效5. 击中敌人音效6. 加入进场动画7. 解决玩家掉出地图死亡问题8. 完善玩家游戏失败函数9. 总结 完成最终的魂斗罗…

道达天际首次亮相军博会,“天网融合”引爆全场

4月23日,第十届中国指挥控制大会暨第八届中国(北京)军事智能技术装备博览会(军博会)落下帷幕。北京道达天际科技股份有限公司(简称道达天际)携DAODAJ2天网情报产品体系首次亮相,全面展示天网融合技术、产品、行业解决方案等成果,最新技术应用备受现场观众瞩目,“天网融合”创新…

性能优化对于Android程序员的重要性,看完你就明白

前言 相信我们都使用过Android手机&#xff0c;然后在使用的过程中经常会遇到手机卡顿&#xff0c;应用闪退&#xff0c;画面不流畅等问题&#xff1b;正因为如此&#xff0c;就导致用户体验非常差&#xff0c;最后选择不再使用Android手机。对此&#xff0c;很多公司对Androi…

【软考备战·希赛网每日一练】2023年4月27日

文章目录 一、今日成绩二、错题总结第一题第二题第三题第四题 三、知识查缺 题目及解析来源&#xff1a;2023年04月27日软件设计师每日一练 一、今日成绩 二、错题总结 第一题 解析&#xff1a; ADSL Modem 上网拨号方式有3种&#xff0c;即 专线方式&#xff08;静态IP&#…

半导体运动台基于dsp+fpga+ad+endac的高速数据采集FPGA设计(二)

4 系统 FPGA 程序的设计 4.1 设计方法及逻辑设计概述 4.1.1 开发环境与设计流程 Quartus II 是 Altera 公司综合开发工具&#xff0c;它集成了 FPGA/CPLD 开发过程中所设计 的所有工具和第三方软件接口&#xff0c;支持多时钟分析&#xff0c; LogicLock 基于块的…

Linux内核阅读自学精简教程目录(必读)

学习Linux内核需要一定的计算机基础知识&#xff0c;包括操作系统&#xff0c;计算机网络等。 以下是学习Linux内核的步骤&#xff1a; 了解Linux内核的基本概念和架构&#xff0c;学习Linux内核源代码的组成和结构。学习C语言和汇编语言&#xff0c;这是深入理解Linux内核的…

ssh设置别名 ,登录

1. ssh设置别名&#xff1b; 可以使用alias命令来给ssh命令起别名&#xff0c;例如&#xff1a; alias mysshssh这样就可以使用myssh命令来代替ssh命令了。如果想要永久生效&#xff0c;可以将上述命令加入到~/.bashrc文件中。 &#xff0c;如果没有 ~/.bashrc 此文件&#…

第十八章 迭代器模式

文章目录 前言一、迭代器模式基本介绍二、迭代器模式应用实例完整代码Department 系ComputerCollegeIterator 计算机学院迭代器InfoColleageIterator 信息工程学院迭代器College 学院接口ComputerCollege 计算机学院InfoCollege 信息工程学院OutPutImpl 操作迭代器Clint 测试 三…

一个恶意下载器的逆向分析

Die查壳, 发现没有加壳, 是使用VC编写的64位程序 丢入VT用杀毒引擎和沙箱扫, 爆红基本可以确定其属于恶意软件: 查看其PE节区发现其包含了资源节, 内部可能藏有隐藏模块 查看一下这个程序导入的dll中发现了如下特别的地方 并且其还使用了LoadLibrary和GetProcAddre…

使用aardio写一个基于pyocd的单片机下载器

1 新建工程 最开始本来是打算调用pyocd 的python api的&#xff0c;但是一个是内嵌包一直安装出问题&#xff0c;一个是考虑到本地pack不想重复安装和管理&#xff0c;于是就转做pyocd的前端了&#xff0c;也就是直接调用pyocd&#xff0c;根据返回数据解析&#xff0c;然后执…

NumPy之矩阵、向量、线性代数等的操作

NumPy之矩阵、向量、线性代数 NumPy矩阵和向量矩阵向量创建向量创建矩阵访问元素转置矩阵矩阵加减乘除矩阵向量乘法矩阵求逆矩阵的迹向量点积向量范数 NumPy线性代数计算矩阵乘积计算矩阵的逆解线性方程组 NumPy矩阵和向量 矩阵 在NumPy中&#xff0c;矩阵可以看作是一个二维数…

【Django】Django ORM Cookbook--20230427

英文版http://books.agiliq.com/projects/django-orm-cookbook/en/latest/ 中文版https://django-orm-cookbook-zh-cn.readthedocs.io/zh_CN/latest/query.html 查询和筛选 1. 如何查看Django ORM查询集的原生SQL&#xff1f; >>> queryset Event.objects.all() &…

JVM内存模型和结构

JVM是Java Virtual Machine&#xff08;Java虚拟机&#xff09;的缩写&#xff0c;JVM是一个虚构出来的计算机&#xff0c;有着自己完善的硬件架构&#xff0c;如处理器、堆栈等。 为什么需要JVM&#xff1f; Java语言使用Java虚拟机屏蔽了与具体平台相关的信息&#xff0c;使…

QT笔记——第三方开源库停靠窗口类似QDockWidget

我们想要一个类似于Visual Studio 2019的dockwidget 停靠窗口 一个开源库&#xff1a;类似于Visual Studio 2019 dockwidget 的开源库 下载&#xff0c;解压下来 使用vs qt 插件打开src文件夹下的 src.pro 生成如下&#xff1a; 我们来运行它的例子&#xff1a; 使用vs qt …

【Vue工程】001-Vite 创建 Vue-TypeScript 项目

【Vue工程】001-Vite 创建 Vue-TypeScript 项目 文章目录 【Vue工程】001-Vite 创建 Vue-TypeScript 项目一、环境二、创建项目1、pnpm 创建 Vite 项目2、设置项目名3、选择vue4、选择 TypeScript5、创建完成6、安装与启动7、访问 http://localhost:5173/8、默认生成的项目结构…

LeetCode0718.最长重复子数组 Go语言AC笔记

时间复杂度&#xff1a;O(n)&#xff0c;空间复杂度&#xff1a;O(n) 解题思路 动态规划思想。令dp[i][j]表示两数组以nums1[i]和nums2[j]为起始元素的公共前缀最大长度&#xff0c;所以如果nums1[i]和nums2[j]元素相同&#xff0c;那么dp[i][j]dp[i1][j1]&#xff0c;否则dp[…

Baumer工业相机堡盟工业相机如何联合BGAPI SDK和OpenCVSharp合并偏振相机4个角度的图像并显示(C#)

Baumer工业相机堡盟工业相机如何联合BGAPI SDK和OpenCVSharp合并偏振相机4个角度的图像并显示&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机偏振相机的技术背景代码案例分享1&#xff1a;引用合适的类文件2&#xff1a;回调函数里联合BGAPI SDK和OpenCVSharp合并偏…

Leetcode刷题日志2.0

目录 前言&#xff1a; 1.数字的补数 2.最大连续 1 的个数 3.下一个更大元素 I 4.斐波那契数 5.提莫攻击 6.557. 反转字符串中的单词 III 前言&#xff1a; 今天就分享一下最近刷到的leetcode习题吧 &#xff0c;编程语言&#xff1a;Python3。废话不多说&#xff0…

【备战蓝桥杯国赛-国赛真题】2022

思路 题目的一是就是从1~2022这2022个数中挑选十个数&#xff0c;使其的总和为2022&#xff0c;如果做过背包问题&#xff0c;那么思路就很好出来了&#xff0c;每个数无非就是选与不选&#xff0c;每个数有他们的权值&#xff0c;权值就等于它们本身的值&#xff0c;抽象成背…

java调用百度的接口获取起始位置的距离

需求:校验收货地址是否超出配送范围 重要: 做该需求的思路就是通过卖家和卖家具体的地址信息,来获取到二者的经纬度, 此时可以使用百度的 "地理编码服务",即可获取对应的经纬度 第二步,就是通过二者的经纬度,按照百度接口的要求,发送,即可获取到包含二者距离的JSON串…