自定义类型—结构体

目录

1 . 结构体类型的声明

1.1 结构的声明

1.2 结构体变量的创建与初始化

1.3  结构体的特殊声明

1.4 结构体的自引用

2. 结构体内存对齐

2.1 对齐规则

2.2 为什么存在内存对齐

2.3 修改默认对齐数

3. 结构体传参

4.结构体实现位段

4.1 位段的内存分配


1 . 结构体类型的声明

1.1 结构的声明

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

假如描述一个学生

struct Stu 
{
  int age;
  char name[20];
  char sex[5];
  char id[20];
}

1.2 结构体变量的创建与初始化

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};
int main()
{
	//按照结构体成员的顺序初始化
	struct Stu s = { "张三", 20, "男", "20230818001" };
	printf("name: %s\n", s.name);
	printf("age : %d\n", s.age);
	printf("sex : %s\n", s.sex);
	printf("id : %s\n", s.id);

	//按照指定的顺序初始化
	struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥" };
	printf("name: %s\n", s2.name);
	printf("age : %d\n", s2.age);
	printf("sex : %s\n", s2.sex);
	printf("id : %s\n", s2.id);
	return 0;
}

1.3  结构体的特殊声明

在声明结构体的时候,可以进行不完全声明(又称匿名结构体)

struct
{
	int a;
	char b;
	float c;
}x;

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

上述两个结构体在声明的时候省略了结构体标签

在某些情况下,如果只是想使用一次结构体,就可以使用匿名结构体

在此基础上,下面代码合法吗

p = &x;
编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次。

1.4 结构体的自引用

结构体中包含一个类型为该结构体本身成员是否可行?

看下列代码

struct Node
{
 int data;
 struct Node next;
}

 分析一下不难发现,其实是不行的,因为一个结构体中再包含一个同类型的结构体变量,这样结

构体变量的大小就会无穷大。

struct S
{
	int n;
	struct S* next;
};

但是如果时包含和自己相同类型的指针,是可行的,在x86或x64的环境下,指针的大小无非就是

4/8字节。

 在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看

下面的代码,可行吗
typedef struct
{
	int data;
	S* next;
}S;
是不行的,因为S是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使
用Node类型来创建成员变量,这是不行的。

2. 结构体内存对齐

看一段代码

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

int main()
{
  printf("%zd \n",sizeof(s));
  return 0;
}

如果只按成员的大小来看的话,该结构体应该只占用6个字节就够了

但程序运行起来后可以发现是12个字节。

这就涉及到结构体内存对齐。

2.1 对齐规则

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。
VS 中默认的值为 8
- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的
整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
一共9个字节,按照规则3来说,为最大对齐数的整数倍,那就是3 * 4 = 12个字节
再来看几个例子
struct S2
{
	char c1;
	char c2;
	int i;
};

一共8个字节,按照规则3来说,为最大对齐数的整数倍,那就是2* 4 = 8个字节

struct S3
{
	double d;
	char c;
	int i;
};

一共15个字节,按照规则3来说,为最大对齐数的整数倍,那就是4* 4 = 16个字节

struct S4
{
	char c1;
	struct S3 s3;
	double d;
};

其中嵌套了s3,已经知道s3的大小是16个字节,其中最大对齐数是8,那么就从偏移量8开始占16个字节。

一共32个字节,按照规则3来说,为最大对齐数的整数倍,那就是4* 8 = 32个字节

2.2 为什么存在内存对齐

1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器
需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字
节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,
那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可
能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
个人观点:主要原因还是第二个
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占⽤空间小的成员尽量集中在⼀起
如上例 S1,S2
struct S1
{
	char c1;
	int i;
	char c2;
}s;

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

同样的成员类型,我们可以发现S1占12个字节,S2就只占8个字节了。

2.3 修改默认对齐数

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

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
	printf("%d\n", sizeof(struct S));
	return 0;
}

3. 结构体传参

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更好,因为print1在传参时是传值调用,这个值有多大就得开辟多大的空间,仅是一个data数

组就要了4000个字节的空间。

而print2在传参的时候是传址调用,传地址过去,大小无非就是4 / 8个字节,效率更高。

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

4.结构体实现位段

4.1 位段的定义

位(二进制位)

位段的声明和结构是类似的,有两个不同:
1. 位段的成员必须是 int unsigned int signed int ,在C99中位段成员的类型也可以选择其他类
型。
2. 位段的成员名后边有⼀个冒号和⼀个数字。
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

struct B
{
	int _a;
	int _b ;
	int _c ;
	int _d ;
};
int main()
{
	printf("%zd\n", sizeof(struct A));
	printf("%zd\n", sizeof(struct B));
	return 0;
}
A就是⼀个位段类型。
后面跟的数字是代表分配多少比特位
我们来看看效果
可见 ,位段是专门用来节省内存空间的。
但是 ,如果只按照分配的比特位来看,2+5+10+30 = 47 ,应该只分配6个字节就够了,为什么是8
个。这就涉及到了位段的内存分配

4.1 位段的内存分配

1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型

2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
一个例子
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;
	printf("%zd\n", sizeof(s));
	return 0;
}

4.2 位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会
出问题。)
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当⼀个结构包含两个位段,第⼆个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃
剩余的位还是利用,这是不确定的。
结论:跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存
在。

4.3 位段的应用

下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这

里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小⼀些,对

网络的畅通是有帮助的。

4.4 位段的使用注意事项

位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些
位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。
所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输⼊值,只能是先输
入放在⼀个变量中,然后赋值给位段的成员。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

12.C++常用的算法_遍历算法

文章目录 遍历算法1. for_each()代码工程运行结果 2. transform()代码工程运行结果 3. find()代码工程运行结果 遍历算法 1. for_each() 有两种方式&#xff1a; 1.普通函数 2.仿函数 代码工程 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<vect…

数据结构之树的性质总结

节点的度&#xff1a;该节点拥有的孩子个数 叶子节点&#xff1a;度为0的节点 层数&#xff1a;根节点为第一层&#xff0c;根的子节点为第二层&#xff0c;以此类推 所有树的性质&#xff1a;所有节点的总度数等于节点数减一 完全m叉树性质 完全m 叉树&#xff0c;节点的…

【Canvas与艺术】绘制磨砂黄铜材质Premium Quality徽章

【关键点】 渐变色的使用、斜纹的实现、底图的寻觅 【成果图】 ​​​​​​​ 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><tit…

若依Vue3:新一代前后端分离权限管理系统

随着技术的不断进步&#xff0c;前后端分离的开发模式逐渐成为主流&#xff0c;特别是在构建权限管理系统时。在这样的背景下&#xff0c;若依Vue3应运而生&#xff0c;作为基于Spring Boot、Spring Security、JWT、Vue3、Vite和Element Plus的全新解决方案&#xff0c;它在技术…

Ubuntu20.04安装ROS过程记录以及常见报错处理

sudo sh -c ‘echo “deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main” > /etc/apt/sources.list.d/ros-latest.list’ sudo apt-key adv --keyserver ‘hkp://keyserver.ubuntu.com:80’ --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 sudo apt-…

linux启动流程(s3c2400)

概述 大致流程&#xff1a;内核&#xff08;kernel&#xff09;都是由bootloader程序引导启动的&#xff0c;所以我们应该先烧进去bootloader程序。然后可以通过保存的内核代码或者通过远程连接&#xff08;nfs/tftp&#xff09;的主机下载再运行&#xff0c;再挂载根文件系统。…

ABAP 前导0的处理

前导0这个东西真的很烦&#xff0c;经常因为前导0导致连接条件有问题&#xff0c;出不来数据&#xff0c;这里就总结一下前导0 在sql语句中的添加和去除 文章目录 ABAP 前导0的处理添加前导0-自适应运行结果 去除前导0方法一、使用SUBSTRING截取运行结果 方法二、去零法运行结…

小白新手学习 Python 使用哪个 Linux 系统更好?

对于小白新手学习Python&#xff0c;选择哪个Linux系统是一个很重要的问题&#xff0c;因为不同的Linux发行版&#xff08;distribution&#xff09;有着不同的特点、优势和适用场景。在选择时&#xff0c;需要考虑到易用性、学习曲线、社区支持等因素。 Ubuntu Ubuntu 是一个…

【AcWing】蓝桥杯集训每日一题Day16|哈希|FloodFill算法|字典序最小|映射|1402.星空之夜(C++)

1402.星空之夜 1402. 星空之夜 - AcWing题库难度&#xff1a;中等时/空限制&#xff1a;1s / 64MB总通过数&#xff1a;3415总尝试数&#xff1a;7434来源&#xff1a;usaco training 5.1算法标签Flood Fill哈希DFSBFS 题目内容 夜空深处&#xff0c;闪亮的星星以星群的形式出…

Spring Cloud系列(二):Eureka Server应用

系列文章 Spring Cloud系列(一)&#xff1a;Spirng Cloud变化 Spring Cloud系列(二)&#xff1a;Eureka Server应用 目录 前言 注册中心对比 Nacos Zookeeper Consul 搭建服务 准备 搭建 搭建父模块 搭建Server模块 启动服务 测试 其他 前言 前面针对新版本的变化有了…

Linux--进程的概念(二)

目录 一、进程的优先级1.1 基本概念1.2 查看进程优先级1.3 PRI&NI1.4 如何更改进程的优先级1.4.1 用top命令更改进程的nice1.4.2 用renice命令更改进程的nice 1.5 其他概念 二、环境变量2.1 基本概念2.2 常见的环境变量2.3 查看环境变量2.3.1 测试PATH2.3.2 测试HOME2.3.3 …

安全操作代码优化思路

理论依据 数据增强和样本选择 在训练阶段&#xff0c;您可以考虑添加数据增强来提升模型的鲁棒性和泛化能力。针对人脸检测任务&#xff0c;可以尝试以下改进&#xff1a; 对输入图像进行随机裁剪、缩放、旋转、翻转等数据增强操作&#xff0c;以增加数据的多样性。 使用难样…

【堡垒机】堡垒机的介绍

目前&#xff0c;常用的堡垒机有收费和开源两类。 收费的有行云管家、纽盾堡垒机&#xff1b; 开源的有jumpserver&#xff1b; 这几种各有各的优缺点&#xff0c;如何选择&#xff0c;大家可以根据实际场景来判断 什么是堡垒机 堡垒机&#xff0c;即在一个特定的网络环境下&…

革命性突破:Stability AI发布全新12B参数Stable LM 2模型,颠覆AI界!

Stability AI已推出其Stable LM 2语言模型系列的最新成员&#xff1a;一个120亿参数的基础模型和一个经过指令调优的变体。这些模型在七种语言上训练&#xff0c;包括英语、西班牙语、德语、意大利语、法语、葡萄牙语和荷兰语&#xff0c;训练数据达到了令人印象深刻的两万亿个…

JavaScript(1)神秘的编程技巧

大家都感兴趣的箭头函数 箭头函数在许多场景中都可以发挥作用&#xff0c;尤其适用于简化函数声明和提高代码的可读性。以下是箭头函数可以使用的一些常见方面&#xff1a; &#xff08;1&#xff09;回调函数&#xff1a; 箭头函数特别适合作为回调函数&#xff0c;例如在事…

【教程】App打包成IPA文件类型的四种方法

摘要 本教程总结了将App应用程序打包为IPA包的四种常用方法&#xff0c;包括Apple推荐的方式、iTunes拖入方法、自动编译脚本和解压改后缀名方法。每种方法都有其特点和适用场景&#xff0c;在实际开发中可以根据需求选择合适的方式进行打包。通过本教程&#xff0c;您将了解到…

微服务(狂神)

什么是微服务&#xff1a; 微服务方案&#xff1a; 1. SpringCloud NetFlix 2. Dubbo 3. SpringCloud Alibaba 解决了什么问题&#xff1a; 1. 服务过多&#xff0c;客户端怎么访问 2. 服务过多&#xff0c;服务间怎么传值 3. 服务过多&#xff0c;如何治理 4. 服务过多…

【随笔】Git 高级篇 -- 最近标签距离查询 git describe(二十一)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

JVM基础第一篇

内存结构 程序计数器 1.定义 在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;每个线程都有一个独立的程序计数器&#xff0c;它是线程私有的&#xff0c;不会被线程切换所影响。 2.作用 记住下一条jvm指令的执行地址 3.特点 是线程私有的不会存在内存溢出 虚拟机…

nginx配置证书和私钥进行SSL通信验证

文章目录 一、背景1.1 秘钥和证书是两个东西吗&#xff1f;1.2 介绍下nginx配置文件中参数ssl_certificate和ssl_certificate_key1.3介绍下nginx支持的证书类型1.4 目前nginx支持哪种证书格式&#xff1f;1.5 nginx修改配置文件目前方式也会有所不同1.6 介绍下不通格式的证书哪…