自定义类型:结构体进阶学习分享

news2024/11/24 18:34:05

自定义类型:结构体进阶学习分享

  • 前言
  • 1 结构体的基础知识
  • 2 结构的声明
  • 3 特殊声明
  • 4 结构的自引用
  • 5 结构体变量的定义和初始化
  • 6 结构体内存对齐
    • 6.1 计算结构体大小相关笔试题(基于VS)
      • 笔试题一:
      • 笔试题二:
    • 6.2 为什么存在内存对齐?
    • 6.3 如何设计结构体减少空间
  • 1.7 修改默然对齐数
  • 8. 结构体传参
  • 9. 结尾

前言

结构体在C语言中具有重要的意义。它不仅可以封装和组织数据,还可以提供抽象和封装的能力,方便数据的传递和操作,提高代码的可读性和可维护性,是C语言中常用的数据类型之一。本篇博客将详细介绍相关知识。

1 结构体的基础知识

在结构体初阶我们以及详细介绍过了有关结构体的基础。

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

2 结构的声明

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

例如描述一个学生:

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

3 特殊声明

在声明结构体的时候,可以不完全声明。(匿名结构体声明)
比如:

//匿名结构体
struct
{
	int a;
	char b;
	float c;
}x;
struct
{
	int a;
	char b;
	float c;
}*px;

上面的两个结构在声明的时候省略掉了结构体标签(tag)。
那问题来了?

//在上面代码的基础上,下面的代码合法吗?
px = &x;

警告:

  • 编译器会将上面的两个声明当成完全不同的两个类型。所以是非法的。

4 结构的自引用

在结构中包含一个类型为该结构本身的成员是否可以呢?

struct Node//err,比如计算其大小sizeof(struct Node)时,大小是不确定的
{
	int date;
	struct Node next;
};

//正确的自引用方法
struct Node
{
	int date;
	struct Node* next ;
};

注意: 上面结构体类型有时过于冗杂,我们可以采用重命名。

//我们知道结构体可以匿名。那是否可以重命名呢?
//答案是否定的。
//例如下面这段代码,结构体中的Node* next变量还没有创建成功,结构体不是完整的,因此不可重命名
typedef struct//err
{
	int date;
	Node* next;
}Node;

//正确使用
typedef struct Node
{
	int date;
	struct Node* next;
}Node;

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

有了结构体类型,那如何定义和初始化呢?其实很简单。

struct Stu	//声明类型
{
	char name[15];//名字
	int age;//年龄
};
struct Stu s = { "zhangsan",20 };//初始化

struct Point
{
	int x;
	int y;
};
struct Node
{
	int date;
	struct Point p;
	struct Node* next;
}n1 = { 10, {4,5}, NULL };//结构体嵌套初始化

6 结构体内存对齐

接下来我们将介绍一个热门考点:结构体内存对齐
结构体内存对齐规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的 较小值
。vs默认的值为8
。Linux中没有默认对齐数,对齐数就是成员自身的大小

3. 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

6.1 计算结构体大小相关笔试题(基于VS)

笔试题一:

#include <stdio.h>

struct S1
{
	char c1;//对齐到偏移量为0处
	int i;//int占4个字节,VS默认对齐数为8;所以对齐数为最小值:4字节
		  //所以int对齐到偏移量为4后在向后移动4个字节即为。该位置即是int在内存中占据的空间
	char c2;//同理char的对齐数为1。在int后移动一个字节即可。
};
//上述结构体struct S1共占据9个字节,而结构体的大小必须是最大对齐数4(本题中,对齐数分别为1,4,1)的整数倍
//所以结构体还需向内存申请3个字节空间大小,及总大小为12字节。

struct S2
{
	char c1;//第一个变量成员对齐到偏移量为0处
	char c2; //该变量对齐数为1(char为1byte, VS默认为9byte,取最小值)
			 //即在变量c1后申请1字节即可
	int i;//该变量对齐数为4(int为4byte, VS默认为9byte,取最小值)
		  //所以变量i对齐到偏移量为4处,在向后申请4个字节即可。
};
 //到此结构体总共向内存申请了8byte, 为最大对齐数4(本题中三个变量对齐数分别为:1,1,4)的整数倍。
 //所以该结构体的最终大小为8
 
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

运行结果:
在这里插入图片描述
—————————————————————————————————————

笔试题二:

struct S3
{
	double d;//第一个成员变量对齐到偏移量为0处,在向后申请8字节(double大小为8)
	char c;//该成员变量对齐数为1(char为1byte,VS默认对齐数为8,取最小值)
		   //所以变量对齐到偏移量为8处,向后申请1byte
	int i;//该成员变量对齐数为4(int为4byte,VS默认对齐数为8,取最小值)
		  //所以该变量首先要对齐到偏移量为12的位置(必须对齐到4的整数倍处),在向后申请4个字节
};
//到此该结构体向内存共申请了16字节,为最大对齐数8的整数倍
//所以该结构体的最终大小为16byte

struct S4
{
	char c1;//第一个成员变量对齐到偏移量为0处,在向后申请1字节(char大小为1)
	struct S3 s3;//嵌套结构体,结构体要对齐到自己最大对齐数8(上面已经求得最大对齐数为:8)的整数倍处。即该成员变量对齐到偏移量为8出,在向后申请16字节(上面已经求出其大小16byte)
	double d;//该成员变量对齐数为8(double为8byte,VS默认对齐数为8,取最小值)
		     //上述两个变量总共为24个字节,所以该成员变量从偏移量为24(刚好为8得整数倍)开始,在向后申请8字节
};
//到此该结构体总共占据32个字节空间,为最大对齐数8(上述3个成员变量对齐数分别为:1,8,8)的整数倍。
//所以最终该结构体总大小为32byte

int main()
{
	printf("%d\n", sizeof(struct S3));
	printf("%d\n", sizeof(struct S4));
	return 0;
}

运行结果:
在这里插入图片描述

6.2 为什么存在内存对齐?

大部分参考资料是这么说的:

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

总的来说:

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

6.3 如何设计结构体减少空间

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

我们前面已经见过这段代码:

struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2; 
	int i;
}
int main()
{
	printf("%d\n", sizeof(struct S3));//12
	printf("%d\n", sizeof(struct S4));//8
	return 0;
}

S1和S2类型的成员一模一样,但是S1和S2所占据的空间大小有一些区别。我们可以得到:

  • 在设计结构体时,尽量让占用空间小的成员尽量集中在一起,从而节省空间。

1.7 修改默然对齐数

#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;
}

运行结果:
在这里插入图片描述
总结:

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

8. 结构体传参

直接上代码:

#include <stdio.h>
struct S
{
	int date[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* s)
{
	printf("%d\n", s->num);
}

int main()
{
	print1(s);
	print2(&s);
	return 0;
}

上面print1和print2函数那个更好?
答案是:首选print2函数。
原因:

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

结论:

结构体传参时,要传结构体指针。

9. 结尾

本篇博客到此就结束了。传作不易,如果对你有帮助记得三连哦!感谢您的支持!!!

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

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

相关文章

【C语言】指针进阶(1)

在前期的文章中&#xff0c;我们已经学习完了指针初阶的内容&#xff0c;这期我们开始学习指针的进阶部分。 指针初阶文章入口&#xff1a; 指针初阶 目录 重点知识概览 前期回顾 字符指针 指针数组 数组指针 数组指针的定义 &数组名VS数组名 数组指针的使用 数组…

Mac电脑文件夹无权限问题

sudo cp 16.5.zip /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport 走到之前的folder &#xff0c;右键选择get info更改權限, 再應用到所有子文件夹 右下解鎖再加自己Read & Write, -右邊拉下應該可以應用到所有子文件 这样就可以…

Java ~ Executor ~ ExecutorCompletionService【总结】

前言 文章 相关系列&#xff1a;《Java ~ Executor【目录】》&#xff08;持续更新&#xff09;相关系列&#xff1a;《Java ~ Executor ~ ExecutorCompletionService【源码】》&#xff08;学习过程/多有漏误/仅作参考/不再更新&#xff09;相关系列&#xff1a;《Java ~ Exe…

如何做需求分析

目录 核心理念&#xff1a; 主要目的&#xff1a; 具体思路&#xff1a; 注意事项&#xff1a; 核心理念&#xff1a; 首先需要想清楚一个问题&#xff1a;作为一个测试&#xff0c;有没有把需求当作产品中的一个组成部分&#xff0c;然后尽到一个测试的责任与义务&#x…

JavaScript中truthy(真值)或者Falsy(假值)

● 在JavaScript中&#xff0c;有五个值是falsy ○ 0 ○ ’ ’ ○ undefined ○ null ○ NaN 除此之外&#xff0c;任何不是空值的都是真值&#xff1b; 假值是什么意思呢&#xff1f;就是转换为布尔值都是false&#xff0c;反则就是true 例如&#xff1a; console.log(Boole…

论文阅读:矩阵乘法GEMM的cache优化,子矩阵的切分方法Anatomy of High-Performance MatrixMultiplication

矩阵乘法优化的知名论文goto paper&#xff1a; 矩阵乘法的优化需要将矩阵切分成子矩阵&#xff0c;用子矩阵相乘的结果组合为原矩阵相乘的结果&#xff1a; 上图是拆分矩阵的方法&#xff0c;M表示矩阵&#xff0c;X方向和Y方向的两个维度都是未知的。P表示横条或竖条&#x…

微信小程序使用ECharts的示例详解

目录 安装 ECharts 组件使用 ECharts 组件图表延迟加载 echarts-for-weixin 是 ECharts 官方维护的一个开源项目&#xff0c;提供了一个微信小程序组件&#xff08;Component&#xff09;&#xff0c;我们可以通过这个组件在微信小程序中使用 ECharts 绘制图表。 echarts-fo…

数据分享|R语言用lme4多层次(混合效应)广义线性模型(GLM),逻辑回归分析教育留级调查数据...

全文链接:http://tecdat.cn/?p22813 本教程为读者提供了使用频率学派的广义线性模型&#xff08;GLM&#xff09;的基本介绍。具体来说&#xff0c;本教程重点介绍逻辑回归在二元结果和计数/比例结果情况下的使用&#xff0c;以及模型评估的方法&#xff08;点击文末“阅读原文…

selenuimecharts——可视化分析csdn新星赛道选手展示头像、展示ip城市和断言参赛信息的有效性(进阶篇)

文章目录 ⭐前言⭐selenuim打开赛道报名界面获取新星赛道选手主页&#x1f496; 获取参赛选手主页思路分析&#x1f496; selenuim获取参数选手代码块&#x1f496; selenuim获取参数选手主页城市&#x1f496;echarts分析选手参数信息断言参赛信息的有效性&#xff1a; ⭐结束…

【技术面试】Java八股文业余选手-下篇(持续更新)

文章目录 5. RocketMQ 消息中间件、RabbitMQ、ActiveMQ【√】5.1 RocketMQ 6. Kafka 大数据量消息中间件、ElasticSearch、ZooKeeper【√】6.1 Kafka【√】6.2 ElasticSearch 7. 分布式、研发提效、高并发、线程安全【√】7.1 分布式与集群【√】7.2 高并发、线程安全【】7.3 研…

【数学建模】为什么存在最优策略?

一、说明 在进行优化回归过程&#xff0c;首先要看看是否存在最优策略&#xff1f; 在有限马尔可夫决策过程 &#xff08;MDP&#xff09; 中&#xff0c;最优策略被定义为同时最大化所有状态值的策略。换句话说&#xff0c;如果存在最优策略&#xff0c;则最大化状态 s 值的策…

PyTorch常用代码段汇总

本文是PyTorch常用代码段合集&#xff0c;涵盖基本配置、张量处理、模型定义与操作、数据处理、模型训练与测试等5个方面&#xff0c;还给出了多个值得注意的Tips&#xff0c;内容非常全面。 PyTorch最好的资料是官方文档。本文是PyTorch常用代码段&#xff0c;在参考资料[1](张…

【AutoSAR 架构介绍】

AutoSAR简介 AUTOSAR是Automotive Open System Architecture&#xff08;汽车开放系统架构&#xff09;的首字母缩写&#xff0c;是一家致力于制定汽车电子软件标准的联盟。 AUTOSAR是由全球汽车制造商、部件供应商及其他电子、半导体和软件系统公司联合建立&#xff0c;各成…

ubuntu 静态IP设置

ubuntu 静态IP设置&#xff1a; 1.输入&#xff1a; sudo vim /etc/netplan/01-network-manager-all.yaml Let NetworkManager manage all devices on this system network: ethernets: ens33: dhcp4: no addresses: [192.168.1.119/24] gateway4: 192.168.1.1 nameservers: …

代码随想录额外题目| 数组02 ●189旋转数组 ●724寻找数组中心索引

#189旋转数组 很快写出来但是用了个新数组&#xff0c;不好 void rotate(vector<int>& nums, int k) {vector<int> res(nums.size(),0);for(int i0;i<nums.size();i){int newiik;if(newi>nums.size()-1) newinewi%nums.size();res[newi]nums[i];}numsr…

结构型设计模式之桥接模式【设计模式系列】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 设计模式系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everythi…

Vue3状态管理库Pinia——核心概念(Store、State、Getter、Action)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

行为型模式 - 迭代器模式

概述 定义&#xff1a; 提供一个对象来顺序访问聚合对象中的一系列数据&#xff0c;而不暴露聚合对象的内部表示。 结构 迭代器模式主要包含以下角色&#xff1a; 抽象聚合&#xff08;Aggregate&#xff09;角色&#xff1a;定义存储、添加、删除聚合元素以及创建迭代器对象…

Mind+积木编程控制小水泵给宠物喂水

前期用scratch&#xff0c;带着小朋友做了大鱼吃小鱼、桌面弹球、小学生计算器3个作品&#xff0c;小朋友收获不小。关键是小家伙感兴趣&#xff0c;做出来后给家人炫耀了一圈后&#xff0c;兴趣大增&#xff0c;嚷嚷着要做更好玩的。 最近&#xff0c;娃妈从抖音上买了个小猫喝…

JMeter 配置环境变量步骤

通过给 JMeter 配置环境变量&#xff0c;可以快捷的打开 JMeter&#xff1a; 打开终端。执行 jmeter。 配置环境变量的方法如下。 Mac 和 Linux 系统 1、在 ~/.bashrc 中加如下内容&#xff1a; export JMETER_HOMEJMeter所在目录 export PATH$JAVA_HOME/bin:$PATH:.:$JME…