C语言自定义类型:结构体的使用及其内存对齐【超详细建议点赞收藏】

目录

  • 1. 结构体类型的声明
    • 1.1 结构的声明
    • 1.2 结构体变量的创建和初始化
    • 1.3 结构的特殊声明---匿名结构体
    • 1.4 结构的自引用
  • 2.结构体内存对齐(重点!!)
    • 2.1 对齐规则
    • 2.2 例题讲解
    • 2.3 为什么存在内存对齐?
    • 2.4 修改默认对齐数
  • 3. 结构体传参

1. 结构体类型的声明

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

1.1 结构的声明

在这里插入图片描述
注意:

  1. 成员列表可以是不同类型的变量;
  2. 成员后一定要有分号;
  3. 花括号后也有一个分号。

例如描述一个学生:

struct Stu
{
	char name[20];//姓名
	int age;//年龄
	char tele[12];//电话
	char sex[3];//性别
};//分号不能丢

注意:上述代码没有创建变量,也没有初始化,只是声明了一个结构体类型,就像int,float一样,只是一种类型。

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

接下来我们可以用结构体类型创建变量

方式1:
声明类型的同时创建,这是全局变量

struct Stu
{
	char name[20];//姓名
	int age;//年龄
	char tele[12];//电话
	char sex[3];//性别
}s1,s1;

方式2:
我们也可以在函数内部创建局部变量

# include <stdio.h>

struct Stu
{
	char name[20];//姓名
	int age;//年龄
	char tele[12];//电话
	char sex[3];//性别
};

int main()
{
	struct Stu s3;//s3是局部变量

	return 0;
}

我们再对结构体变量进行初始化:
方式1:直接初始化

struct Point
{
	int x;
	int y;
}p1 = { 2,3 };


 struct Stu
{
	char name[20];//姓名
	int age;//年龄
 }s1 = { "zhangsan",27 };
 

方式2:也可以再函数内部进行初始化

struct Stu
{
	char name[20];//姓名
	int age;//年龄
};

int main()
{
	struct Stu s1 = { "zhangsan",24 };

	return 0;
}

当然,也有结构体的嵌套:

struct score
{
	int n;
	char ch;
};

struct Stu
{
	char name[20];//姓名
	int age;//年龄
	struct score s;//嵌套一个结构体变量
};


int main()
{
	struct Stu s1 = { "zhangsan",24 ,{45,'a'} };

	return 0;
}

我们也可以将其打印出来:

struct score
{
	int n;
	char ch;
};

struct Stu
{
	char name[20];//姓名
	int age;//年龄
	struct score s;
};


int main()
{
	struct Stu s1 = { "zhangsan",24 ,{45,'a'} };

	printf("%s %d %d %c\n", s1.name, s1.age, s1.s.n, s1.s.ch);

	return 0;
}

打印结果:
在这里插入图片描述

1.3 结构的特殊声明—匿名结构体

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

struct 
{
	char name[20];//姓名
	int age;//年龄
	char tele[12];//电话
	char sex[3];//性别
};

上面的结构在声明时省略了结构体标签,我们称为匿名结构体

注意:匿名结构体只能通过创建全局变量使用一次!!

# include <stdio.h>

struct 
{
	char name[20];//姓名
	int age;//年龄
	char tele[12];//电话
	char sex[3];//性别
}s1;

int main()
{

	return 0;
}

1.4 结构的自引用

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

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

上述代码正确吗?如果正确,那么sizeof(struct Node)是多少呢?
仔细分析,其实是不行的,因为⼀个结构体中再包含⼀个类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。

正确的自引用方式:

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

这样我们就把一个结点分成两部分,一部分存放数据,叫数据域,另一部分存放下一个结点的地址,叫指针域。

在结构体自引用使用的过程中,夹杂了 typedef 对匿名结体类型重命名,也容易引入问题,看看下面的代码,可行吗?

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

这种写法语法是不支持的。
因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这就产生了"先有鸡还是先有蛋"的问题,这是不行的。

解决方案如下:定义结构体不要使用匿名结构体了

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

2.结构体内存对齐(重点!!)

上面我们了解了结构体的基本使用。
接下来我们再讨论一个问题 :计算结构体的大小。

2.1 对齐规则

首先要知道结构体的对齐规则:

  1. 第一个成员在结构体变量偏移量为0的地址处。

  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    注意:
    (1) 对齐数=编译器默认的对齐数与该成员大小的较小值
    (1)VS中默认值为8(注意:其他编译器是没有默认对齐数的,对齐数就是自身大小。)

  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍

  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐的整数倍处。
    结构体的整体大小就是所有最大对齐数的(含嵌套结构体的对齐数)的整数倍。

2.2 例题讲解

例1:

#include <stdio.h>

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

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

输出结果:
在这里插入图片描述

画图解释:
在这里插入图片描述

例2:

#include <stdio.h>

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

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

输出结果:
在这里插入图片描述

画图解释:
在这里插入图片描述
例3:

#include <stdio.h>

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

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

输出结果:
在这里插入图片描述

画图解释:
在这里插入图片描述
例4:结构体嵌套问题

#include <stdio.h>

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

int main()
{
	struct S4 s4;
	printf("%d\n", sizeof(struct S4));

	return 0;
}

输出结果:
在这里插入图片描述

画图解释:
在这里插入图片描述

2.3 为什么存在内存对齐?

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

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

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

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

让占用空间小的成员尽量集中在⼀起
如例1和例2,s1和s2的成员一模一样,但两者所占空间大小却不同。

2.4 修改默认对齐数

  • #pragma : 预处理指令,可以改变编译器的默认对齐数。
  • 使用时要引用头文件<stddef.h>
  • 注意:一般修改的对齐数都为2的n次方。不会修改为1,3,5……或负数。

例如还是用例1来说明:

#include <stdio.h>
#include <stddef.h>

#pragma pack(1)//设置默认对齐数为1
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma()//取消设置的对齐数,还原为默认

int main()
{
	printf("%d\n", sizeof(struct S1));
}

输出结果:
在这里插入图片描述
修改之前大小是12个字节,修改后变成了6字节。

3. 结构体传参

方式1:传值调用。访问结构体时用点(.)操作符。

#include <stdio.h>

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

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

int main()
{
	struct S s = { {1,2,3},200 };
	print1(s);  //传值调用,直接传变量名
	
	return 0;
}

方式2:传址调用。访问结构体时用箭头(->)操作符。

#include <stdio.h>

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

void print2(const struct S* ps)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", ps->data[i]);
	}
	printf("%d\n", ps->num);
}

int main()
{
	struct S s = { {1,2,3},200 };
	print2(&s); //传址调用,传变量名的地址

	return 0;

输出结果都为:
在这里插入图片描述

上面的两种方式哪个更好呢?
答案是:传址调用更好。

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

结论:
结构体传参的时候,要传结构体的地址

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

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

相关文章

华为全新研发中心即将启用,投资超百亿 | 百能云芯

2月19日 &#xff0c;上海市发改委网站发布了《2024年上海市重大工程清单》&#xff0c;内容涉及科技产业、社会民生、生态文明建设、城市基础设施、城乡融合与乡村振兴等五大类&#xff0c;共191项重大工程。 191项重大工程中&#xff0c;科技产业类占比最多&#xff08;76项&…

Spring Boot打war包部署到Tomcat,访问页面404 !!!

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 Spring Boot打war包部署到Tomcat&#xff0c;访问页面404 &#xff01;&#xff01;&#xff01;解决办法&#xff1a;检查Tomcat版本和Jdk的对应关系&#xff0c;我的Tomcat是6.x&#x…

RK3568平台开发系列讲解(Linux系统篇)通过I2C总线访问客户端方法

🚀返回专栏总目录 文章目录 一、普通I2C通信二、系统管理总线(SMBus)兼容函数三、在开发板配置文件中实例化I2C设备(弃用的旧方式)沉淀、分享、成长,让自己和他人都能有所收获!😄 串行总线事务只是访问寄存器来设置/获取其内容。I2C遵循该规则。I2C内核提供两种API,…

(十二)【Jmeter】线程(Threads(Users))之setUp 线程组

简述 操作路径如下: 作用:在正式测试开始前执行预加载或预热操作,为测试做准备。配置:设置预加载或预热操作的采样器、循环次数等参数。使用场景:确保在正式测试开始前应用程序已经达到稳定状态,减少测试结果的偏差。优点:提供预加载或预热操作,确保测试的准确性。缺…

Code Control Process

代码提交流程&#xff08;Code Control Process&#xff09; VSS&#xff0c;早前定义的版本控制&#xff0c;没有谁对不对&#xff0c;但是要根本解决冲突&#xff0c;特别人多的时候&#xff0c;50个人的时候&#xff0c;处理冲突时非常的麻烦的&#xff0c;改半天还改错了&…

C++模板从入门到入土

1. 泛型编程 如果我们需要实现一个不同类型的交换函数&#xff0c;如果是学的C语言&#xff0c;你要交换哪些类型&#xff0c;不同的类型就需要重新写一个来实现&#xff0c;所以这是很麻烦的&#xff0c;虽然可以cv一下&#xff0c;有了模板就可以减轻负担。 下面写一个适…

重新安装VSCode后,按住Ctrl(or Command) 点击鼠标左键不跳转问题

重新安装VSCode后&#xff0c;按住Ctrl&#xff08;or Command&#xff09; 点击鼠标左键不跳转问题 原因&#xff1a;重新安装一般是因为相应编程语言的插件被删除了或还没有下载。 本次是由于Python相关的插件被删除了&#xff0c;因此导致Python无法跳转。 解决办法 在vs…

人工智能会是第四次工业革命吗?引领第四次工业革命的核心力量

许多专家和学者确实认为人工智能&#xff08;AI&#xff09;将是第四次工业革命的核心。第四次工业革命&#xff0c;也被称为"工业4.0"&#xff0c;是指正在发生的一场以高度数字化和互联网为基础的技术革新。 自18世纪的蒸汽机&#xff0c;到20世纪的电力和信息技术…

YOLO-World初体验:Ultralytics版本,可直接上手,离线运行

YOLOv8官方新增了对YOLO-World的支持&#xff0c;本文利用其提供的模型及接口进行了体验。 关于YOLO-World的详细介绍&#xff0c;见&#xff1a;YOLO-World&#xff1a;实时开放词汇目标检测-CSDN博客 目录 1. 前言 2. 安装&#xff08;更新&#xff09; Ultralytics安装&am…

低代码开发:拖拉拽自定义表单的创新之道

一、前言 在软工圣经《人月神话》一书中&#xff0c;作者Brooks指出了软件发展的一个僵局&#xff1a;在落后的项目中增加人手&#xff0c;只会使进度更加落后。 为了更快完成项目&#xff0c;开发团队会发展的极其庞大&#xff0c;以致于所有的时间都花费在沟通和变更决策上&a…

Apache服务

目录 引言 一、常见的http服务程序 &#xff08;一&#xff09;lls &#xff08;二&#xff09;nginx &#xff08;三&#xff09;Apache &#xff08;四&#xff09;Tomcat 二、Apache特点 三、Apache服务的安装 &#xff08;一&#xff09;yum安装及配置文件 1.配置…

Mybatis速成(二)

文章目录 1. Mybatis基础操作1.1 需求1.2 准备1.3 删除1.3.1 功能实现1.3.2 日志输入1.3.3 预编译SQL1.3.3.1 介绍1.3.3.2 SQL注入1.3.3.3 参数占位符 1.4 新增1.4.1 基本新增1.4.2 主键返回 1.5 更新1.6 查询1.6.1 根据ID查询1.6.2 数据封装1.6.3 条件查询1.6.4 参数名说明 2.…

redis实现消息队列redis发布订阅redis监听key

文章目录 Redis消息队列实现异步秒杀1. jvm阻塞队列问题2. 什么是消息队列3. Redis实现消息队列1. 基于List结构模拟消息队列操作优缺点 2. 基于PubSub发布订阅的消息队列操作优缺点spring 结合redis的pubsub使用示例1. 引入依赖2. 配置文件3. RedisConfig4. CustomizeMessageL…

运维SRE-16 自动化批量管理-ansible2

7.6ansible-软件包管理模块 yum_repository(管理yum源) yum(yum命令) get_url(wget命令)1&#xff09;yum源配置管理模块 yum源模块 yum_repositoryyum源配置文件内容name[epel]yum源中的名字(中括号里面的名字即可)descriptionnamexxxxxxyum源的注释说明baseurlbaseurlyum源…

一位面试了20+家公司的测试工程师,发现了面试“绝杀四重技”!

年少不懂面试经&#xff0c;读懂已是测试人。 大家好&#xff0c;我是一名历经沧桑&#xff0c;看透互联网行业百态的测试从业者&#xff0c;经过数年的勤学苦练&#xff0c;精钻深研究&#xff0c;终于从初出茅庐的职场新手成长为现在的测试老鸟&#xff0c;早已看透了面试官…

尝试一下最新的联合办公利器ONLYOffice

下载下来一起试试吧 桌面安装版下载地址&#xff1a;https://www.onlyoffice.com/zh/download-desktop.aspx) 官网地址&#xff1a;https://www.onlyoffice.com 普通Office对联合办公的局限性 普通Office软件&#xff08;如Microsoft Office、Google Docs等&#xff09;在面对…

【html学习笔记】3.表单元素

1.文本框 1.1 语法 <input type "text">表示文本框。且只能写一行 1.2 属性 使用属性size 设置文本框大小 <input type"text" size"10">2. 使用属性value 来设置文本框的默认文字 <input type"text" size"…

【初始RabbitMQ】延迟队列的实现

延迟队列概念 延迟队列中的元素是希望在指定时间到了之后或之前取出和处理消息&#xff0c;并且队列内部是有序的。简单来说&#xff0c;延时队列就是用来存放需要在指定时间被处理的元素的队列 延迟队列使用场景 延迟队列经常使用的场景有以下几点&#xff1a; 订单在十分…

js设计模式:依赖注入模式

作用: 在对象外部完成两个对象的注入绑定等操作 这样可以将代码解耦,方便维护和扩展 vue中使用use注册其他插件就是在外部创建依赖关系的 示例: class App{constructor(appName,appFun){this.appName appNamethis.appFun appFun}}class Phone{constructor(app) {this.nam…

开放Gemma而非“开源”,谷歌为何转变大模型竞争策略?

开放Gemma而非“开源”&#xff0c;谷歌为何转变大模型竞争策略 开放而非开源&#xff01;&#xff01;一、Gemma开源模型二、Gemma从今天开始在全球范围内提供。以下是关键的详细信息&#xff1a;三、为什么这样做&#xff1f;四、谷歌这一竞争策略如何&#xff1f; 2月21日晚…
最新文章