C语言自定义类型中结构体、结构体声明、结构体自引用、结构体变量的定义和初始化、结构体内存对齐,结构体传参,位段等的介绍

文章目录

  • 前言
  • 一、结构体
  • 二、 结构体声明
  • 三、 特殊的声明----匿名结构体类型
  • 四、 结构体的自引用
    • (1)数据结构
    • (2)结构体的自引用
  • 五、 结构体变量的定义和初始化
  • 六、 结构体内存对齐
    • `1. 结构体的对齐规则`
      • (1)结构体大小案例1
      • (2)结构体大小案例2
      • (3)结构体大小案例3
    • 2. 为什么存在内存对齐?
      • 1. 平台原因(移植原因)
      • 2.性能原因:
    • 3. 总体来说
  • 七、修改默认对齐数
  • 八、结构体传参
  • 九、位段
    • 1. 什么是位段
    • 2. 位段的内存分配
    • 3. 位段的跨平台问题
    • 4. 位段的使用
  • 总结


前言

C语言自定义类型中结构体、结构体声明、结构体自引用、结构体变量的定义和初始化、结构体内存对齐,结构体传参,位段等的介绍


一、结构体

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

二、 结构体声明

    1. 结构体类型的模板
struct tag
{
	member-list;
}variable-list;
  • struct 是 结构体类型的关键字
  • tag 是结构体类型的标签
  • member-list是 结构体的成员
  • variable-list是 基于当前结构类型创建的变量
  • 例如
struct Point
{
	int x;
	int y;
}; 

struct Stu
{
	char name[20];
	int age;
	char tele[12];
}s1,s2;
    1. 结构体嵌套声明
struct Score
{
	float math;
	float english;
	float chinese;
	int C;
};

struct Person
{
	char name[20];
	int grade;
	struct Score s;
};

三、 特殊的声明----匿名结构体类型

  • 匿名结构体类型
  • 定义结构体类型时可以带标签,也可以不带标签。
  • 这种特殊的声明只能使用一次,即 基于当前结构体类型创建的变量。
struct
{
	int n;
	char ch;
	float pi;
}num1, num2;
  • 程序中只能使用基于这个结构体类型创建的 num1 和 num2变量,不能重新创建变量。
struct
{
	int a;
	char b;
	float c;
}x;
struct
{
	int a;
	char b;
	float c;
}a[20], * p;
  • 上述代码,两个匿名结构体的成员一样。
  • 第二个struct 和 * 结合表示 p是个指针变量
  • 但是编译器认为 &x 和 p 是两个不同类型的指针。

四、 结构体的自引用

(1)数据结构

  • 数据结构就是数据在内存中的存储结构
  • 数据结构有 线形(顺序表、链表等)、树形(二叉树等)等
  • 链表如下:
    在这里插入图片描述

(2)结构体的自引用

struct Node
{
	int date;
	struct Node* next;
};
  • 结构体中包含同类型的结构体指针,就叫做结构体自引用

typedef

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

// 此时
struct Node n1;
Node n2; 
// 等价的

五、 结构体变量的定义和初始化

  1. 在创建类型的同时,进行初始化。
struct Point
{
	int x;
	int y;
}p1 = {3,4};
  1. 使用声明的结构体类型进行初始化
struct Point
{
	int x;
	int y;
};

int main()
{

	struct Point p2 = { 1 , 2 };
	return 0;
}
  1. 嵌套结构体类型的初始化
struct Score
{
	float math;
	float english;
	float chinese;
	int C;
};

struct Person
{
	char name[20];
	int grade;
	struct Score s;
};

#include <stdio.h>

int main()
{
	struct Person p1 = { "zhangsan", 6, {99.5, 60.5,100.0, 98} };

	printf("%s %d %f %f  %f %d", p1.name, p1.grade, p1.s.math, p1.s.english, p1.s.chinese, p1.s.C);
	// 输出结果为 zhangsan 6 99.500000 60.500000  100.000000 98

	return 0;
}

六、 结构体内存对齐

1. 结构体的对齐规则

  1. 第一个成员在与结构体变量的偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
    - VS 中默认的值为 8。
    - 只有 VS 编译器有默认对齐数,其他编译器上的对齐数就是成员大小。
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
    offsetof 它是一个宏,它是用来 求 结构体成员在类型中距离起始位置的偏移量 offsetof( type, member)
    使用offsetof 宏 需要调用头文件 <stddef.h>

(1)结构体大小案例1

#include <stdio.h>
#include <stddef.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
int main()
{

	printf("%d\n", sizeof(struct S1));// 12
	printf("%d\n", offsetof(struct S1, c1)); // 0
	printf("%d\n", offsetof(struct S1, i)); // 4
	printf("%d\n", offsetof(struct S1, c2)); // 8

	return 0;
}


在这里插入图片描述

(2)结构体大小案例2

#include <stdio.h>
#include <stddef.h>
struct S2
{
	char c1;
	char c2;
	int i;
};

int main()
{
	printf("%d\n", sizeof(struct S2)); // 8
	printf("%d\n", offsetof(struct S2, c1)); // 0
	printf("%d\n", offsetof(struct S2, c2)); // 1
	printf("%d\n", offsetof(struct S2, i)); // 4
	return 0;
}

在这里插入图片描述

(3)结构体大小案例3

#include <stdio.h>
#include <stddef.h>
struct S2
{
	char c1;
	char c2;
	int i;
};
struct S3
{
	char c1;
	struct S2 s2; 
	double d;
};
int main()
{
	printf("%d\n", sizeof(struct S3)); // 24
	printf("%d\n", offsetof(struct S3, c1)); // 0
	printf("%d\n", offsetof(struct S3, s2)); // 4
	printf("%d\n", offsetof(struct S3, d)); // 16
	return 0;
}
  • 因为如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

  • char c1位于 偏移量 0 处。

  • 由上可知, struct S2 s2 自己的最大对齐数是 4 , 所以它距离起始地址的偏移量为 4,大小为 8 个字节。

  • double d 位于 8 个倍数处, 即 偏移量为 16 的位置。大小 为 8 个字节。

  • 整个结构体大小为 所有最大对齐数的整数倍,所以 为 8 的倍数,即 24 个字节大小。

  • 由此可见,结构体的内存对齐会有空间的浪费。

2. 为什么存在内存对齐?

1. 平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据的;
某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2.性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对其的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

在这里插入图片描述

3. 总体来说

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

在设计结构体的时候,我们既要满足对齐,又要省空间, 如何做到:

让空间小的成员尽量集中在一起

七、修改默认对齐数

使用 #pragma 这个预处理命令,可以修改默认对齐数

#pragma pack(1) // 设置默认对齐数为 8 
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack() // 取消设置的默认对齐数, 还原为默认
  • 结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。

八、结构体传参

#include <stdio.h>

struct S 
{
	int data[1000];
	int count;
};

void print1(struct S ss)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", ss.data[i]);
	}
	printf("%d\n", ss.count);
}

void print2(struct S* ss)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", ss->data[i]);
	}
	printf("%d\n", ss->count);
}
int main()
{
	
	struct S s1 = { {1,2,3}, 100 };
	print1(s1); // 1 2 3 100
	print2(&s1); // 1 2 3 100
	return 0;
}

两种结构体传参都可以,但是传值调用,函数形参会重新开辟空间,降低性能。
但是 传址调用 只接收地址,不重新开辟空间,性能较好。
  • 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
  • 如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能下降。
  • 总之,结构体传参的时候,要传结构体的地址。

九、位段

1. 什么是位段

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

  1. 位段的成员必须是 int、unsigned int、signed int、或者char即整型家族。
  2. 位段的成员后边有一个冒号和数字。
#include <stdio.h>
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

int main()
{
	printf("%d\n", sizeof(struct A)); // 8
	return 0;
}
  • 位段 的成员变量 : 后边的数字指的是 这个变量占用 的比特位的个数。

2. 位段的内存分配

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

3. 位段的跨平台问题

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

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

4. 位段的使用

  • 位段的在网络中有较好的作用,如 ip数据包。

总结

C语言自定义类型中结构体、结构体声明、结构体自引用、结构体变量的定义和初始化、结构体内存对齐,结构体传参,位段等的介绍

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

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

相关文章

华为eNSP综合实验-网络地址转换

实验完成之后,在AR1的g0/0/1接口抓包,查看地址转换 实现私网pc访问公网pc 实验命令展示 SW1: vlan batch 12 #创建vlan interface e0/0/1 #进入接口配置vlan端口 port link-type access port default vlan 12 q interface e0/0/2 #进入接口配置vlan端口 port link-type ac…

iphone忘记锁屏密码怎么解锁?这些解锁方法你必须知道!

在使用iPhone的过程中经常会遇到很多问题&#xff0c;比如忘记了iPhone的锁屏密码。面对这样的情况&#xff0c;许多用户可能会感到手足无措。别担心&#xff0c;本文将为您详细介绍iPhone忘记锁屏密码的解锁方法&#xff0c;让您轻松解决这一烦恼。 一、使用iTunes备份恢复 如…

盘点一下4种常见的微信的广告类型,在微信上打广告要花多少钱?

微信是一款社交媒体应用&#xff0c;集Facebook、Instagram和Snapchat的功能于一身。该应用拥有超过12亿的月活跃用户&#xff0c;其中约7亿为日活跃用户。由于其在中国网民中的成功和广泛的通信工具&#xff0c;微信是推广您业务的绝佳平台。 在这篇博客文章中&#xff0c;我…

Centos7完全卸载与安装mysql8.0+

Centos7卸载与安装mysql8.0 1、mysql8完全卸载2、mysql8安装 1、mysql8完全卸载 sudo systemctl stop mysqldsudo yum remove mysql-community-serversudo rm -rf /var/lib/mysql sudo rm -rf /etc/my.cnfsudo groupdel mysql sudo userdel mysqlsudo rm -rf /var/log/mysql s…

nginx代理原理(端口复用)探究

前言&#xff1a;对于一些常用的插件&#xff0c;我们应该学会如何使用。同时&#xff0c;其实现原理也要进行深究&#xff0c;可以为其他的项目开发做借鉴。 探究方案&#xff1a; 一、发布两个不同的服务&#xff0c;这两个服务的端口不致 二、配置nginx&#xff0c;让这两…

3W 3KVDC 隔离单、双输出 DC/DC 电源模块——TPH-3W 系列

TPH-3W系列是一款3W,单、双输出隔离电源模块&#xff0c;特别适合板上只有一种电压而要求有正负电源的场合&#xff0c;工业级温度范围–40℃到105℃&#xff0c;在此温度范围内都可以稳定输出2W&#xff0c;并且效率非常高&#xff0c;高达86%&#xff0c;温升非常低&#xff…

剁手党必看——转转红包使用规则与最优组合计算全解析

​ 1、省钱攻略基础之“了解平台红包使用规则” 2、举个栗子 3、最优红包组合计算方法进化过程 3.1、初代“笛卡尔乘积”版 3.2、二代“边算边比较Map聚合”版 3.3、三代“边算边比较数组索引定位”版 4、总结 1、省钱攻略基础之“了解平台红包使用规则” 规则一&#x…

浙大×移动云,携手点亮AI新时代

近年来&#xff0c;中国移动依托强大的算网资源优势&#xff0c;围绕大模型训练、推理和应用三大场景&#xff0c;打造了一站式智算产品体系。该体系旨在为客户提供覆盖资源、平台、应用的AI全链路服务。目前&#xff0c;一站式智算产品体系已在浙江大学智算中心和许昌中原智算…

后端常用技能:基于easy-poi实现excel一对多、多对多导入导出【附带源码】

0. 引言 在业务系统开发中&#xff0c;我们经常遇到excel导入导出的业务场景&#xff0c;普通的excel导入导出我们可以利用 apache poi、jxl以及阿里开源的easyexcel来实现&#xff0c;特别easyexcel更是将excel的导入导出极大简化&#xff0c;但是对于一些负载的表格形式&…

新能源汽车热管理方案现状与未来发展趋势

前言 新能源汽车的热管理技术在提高电池寿命、提高能量利用效率和确保车辆运行安全方面起着至关重要的作用。 一 新能源汽车热管理技术方案 1 电池热管理系统 电池热管理系统是电动汽车中至关重要的一部分&#xff0c;它通过冷却液循环、加热器、散热片等方式控制电池温度&…

【解决Android Studio】cmake报错找不到vulkan包

1 报错信息 CMake Error at D:/Android/project/cmake/3.10.2.4988404/share/cmake-3.10/Modules/FindPackageHandleStandardArgs.cmake:137 (message): Could NOT find Vulkan (missing: Vulkan_LIBRARY) Call Stack (most recent call first): 2. 错误原因 minSdk版本不对&am…

【Linux网络编程】DNS、ICMP、NAT技术、代理服务器+网络通信各层协议总结

DNS、ICMP、NAT技术、代理服务器网络通信总结 1.DNS2.ICMP协议2.1ping命令2.2traceroute命令 3.NAT技术4.NAT和代理服务器5.网线通信各层协议总结 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&…

uniapp 小程序图片懒加载组件 ImageLazyLoad

预览图 组件【ImageLazyLoad】代码 <template><viewclass"image-lazy-load":style"{opacity: opacity,borderRadius: borderRadius rpx,background: background,transition: opacity ${time / 1000}s ease-in-out,}":class"image-lazy-loa…

白话机器3:PCA与SVM详细数学原理

一、PCA数学原理 1.数据标准化 首先&#xff0c;需要对原始数据进行标准化处理&#xff0c;使得每个特征的均值为0&#xff0c;方差为1。假设有一个的数据矩阵X&#xff0c;其中每一列是一个样本&#xff0c;每一行是一个特征。 标准化公式如下&#xff1a; 其中&#xff0c;…

Observability:监控与可观察性不同的 3 个原因

作者&#xff1a;来自 Elastic Elastic Observability Team 监控和可观察性通常可以互换使用&#xff0c;但它们并不完全相同。 监控是可观察性的重要组成部分&#xff0c;但可观察性远远超出了传统监控实践的范围。 主要区别&#xff1a;监控从各个组件收集数据 —— 时间和内…

【北京迅为】《iTOP-3588开发板快速烧写手册》-第8章 TF启动

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

PyQt:进度条实现(下载、复制)实时进度显示

一、实现思路 源文件:①被复制的文件&#xff08;一般在客户端自身PC上&#xff09;&#xff1b;②被下载的文件&#xff1b;&#xff08;一般在服务器上&#xff09;。 缓存文件&#xff1a;正在粘贴/下载获取中的文件&#xff0c;粘贴/下载完成前&#xff0c;一般是不完整的…

什么是CE认证?

目录 一、什么是CE认证&#xff1f; 二、CE认证对于企业来说有什么重要性&#xff1f; 三、企业在申请CE认证时&#xff0c;需要满足哪些条件和要求&#xff1f; 一、什么是CE认证&#xff1f; CE认证&#xff0c;即只限于产品不危及人类、动物和货品的安全方面的基本安全要…

鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行

本篇有相当的难度&#xff0c;涉及用户栈和内核栈的两轮切换&#xff0c;CPU四次换栈&#xff0c;寄存器改值&#xff0c;将围绕下图来说明. 解读 为本篇理解方便&#xff0c;把图做简化标签说明: user:用户空间kernel:内核空间source(…):源函数sighandle(…):信号处理函数&a…

炫酷Chrome:插件大礼包

Chrome浏览器以其强大的功能和丰富的扩展插件库而闻名。 其中&#xff0c;有些插件专为提升用户的浏览体验而设计&#xff0c;例如更换Chrome网页背景图、自定义鼠标点击样式&#xff0c;以及提供便捷的页面跳转工具等。 最近&#xff0c;有一款被称为“宝藏插件包”的工具引…