[C语言]--自定义类型: 结构体

news2024/9/25 13:24:40

目录

前言

一、结构体类型的声明

1.结构的声明

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

3.结构的特殊声明 

 4.结构的自引用

二、结构体内存对齐

1.对齐规则

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

三、结构体传参 

四、结构体实现位段

1.什么是位段

2.位段的内存分配

3.位段的跨平台问题

 4.位段的应用

5.位段使用的注意事项 

总结


前言

我们已经将C语言中的内置类型学完了,下面我们来学习一下C语言的自定义类型,结构体。


一、结构体类型的声明

1.结构的声明

结构是一些值的集合,这些值称为成员变量。

结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚至是其他结构体。

struct tag
{
          member-list;
}variable-list;
  • tag : 称呼 ,表示这个结构体的名字
  • member-list : 成员列表 ,是写成员变量的地方
  • variable-list  : 变量列表 ,是写属于这个结构的全局变量的地方,可写可不写

 eg:

struct Stu
 {
    char name[20];
    int age;
    char sex;   
 }s1,s2,s3;

 这里的s1,s2,s3的作用s4相同

Struct Stu s4;

 这里创建了一个Stu类型的全局变量s4

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

#include <stdio.h>

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

int main() {

    //方式一:根据结构体成员顺序书写
    struct Stu s1 = { "张山",32,"男" };

    //方式二:根据指定顺序书写  
    struct Stu s2 = { .name = "李斯" ,.sex = "男" , .age = 23 };

    //输出方式
    printf("name: %d", s1.age);
    return 0;
}

3.结构的特殊声明 

匿名的结构体类型,即省略tag(称呼)

struct
 {
    char name[20];
    int age;
    char sex;   
 }s1;

 这种声明只能用一次

 4.结构的自引用

这里先介绍两种数据结构,顺序表链表

 顺序表可以用数组来实现,没啥好说的

在链表中,这五个方块都是一个节点,分别存储着数据和下个节点的位置,这样才能在纷乱的位置中依次找出数据

那我们可以这这样做吗?

struct INT
{
    int data;
    struct INT n;
};

 可是这样做是错的,因为sizeof(struct INT)是多少呢?

我们无法计算它的大小,它会无穷的大

正确的自引用方式:

struct INT
{
    int data;
    struct INT* n;
};

一个结构体指针变量,这个变量指向下个节点的地址,并且指针变量可以求出大小 ,没有上述的问题

还有,当结构体自引用时,夹杂了typedef对结构体类型重命名的问题

typedef struct INT
{
    int data;
    INT* n;
}INT;

 这里会出现错误,因为结构体内提前使用INT,无法分辨INT是啥类型

解决办法: 定义结构体不要使用匿名结构体

typedef struct INT
{
    int data;
    struct INT* n;
}INT;

二、结构体内存对齐

1.对齐规则

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

举个例子: 

int main() {
	struct S1
	{
		char c1;    //1  8  ->  1
		int i;      //4	 8  ->  4
		char c2;    //1	 8  ->  1
	};
	printf("%zd\n", sizeof(struct S1));
}

 

规则1, 结构体第一个变量c1在结构体变量起始位置偏移量为0处

规则2,根据对齐数,c2的对齐数是4,所以跳过前面,到4处; c3的对齐数是1,到8处

规则3,c1,c2,c3的对齐数分别是1,4,1  最大对齐数是4,所以该结构体总大小为12

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

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

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

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

2. 性能原因:

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

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

当我们要满足对齐,又要节省空间,办法是:

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

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

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

#pragma pack(1)   //设置默认对⻬数为1 

#pragma pack()    //取消设置的对⻬数,还原为默认
 

三、结构体传参 

传参分为两种

struct Stu
{
	char name[1000];
	int num;
};

struct Stu s = { "大明", 1000};

//结构体传参
void print1(struct Stu s)
{
	printf("%s\n", s.name);
}

//结构体地址传参
void print2(struct Stu* ps)
{
	printf("%d\n", ps->num);
}

int main()
{
	print1(s);  //传结构体
	print2(&s); //传地址
	return 0;
}

当然print2 比 print1 好 

原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

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

结论:

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

四、结构体实现位段

1.什么是位段

位段和结构体类似,但有两个不同的地方

  1. 位段的成员类型有要求,只能是int、unsigned int 、signed int ,只是在C99中位段的成员类型有其他类型(如char....)
  2. 位段的成员名后面有一个冒号(:)和一个数字(这个数字表示比特位的数量)

例子:

struct A
{
    int a:2;
    int b:5;
    int c:10;
};

这个结构体的大小是4个字节

为什么呢?

2.位段的内存分配

位段的空间是按照成员类型来分配内存的,我们再举个例子

struct A
{
    char a:2;
    char b:4;
    char c:6;
    char d:7;
};

如: 第一个成员类型是char类型,那么会先给1个字节

 这里还要说明一点,位段的储存顺序是不确定的,可能是从左往右,也可能是从右往左,我们假设是从右往左

在这个字节中,我们将a存入,a占两个比特位,接着存入b,b占4个比特位

但当我们要存入c时,剩下的空间不够了(利用还是舍弃不确定,这里舍弃),于是再根据c的类型,生成一个字节

d同理

 

综上所示,这个结构体的大小就是3个字节 

 位段涉及很多不确定的因素,是无法跨平台的,注重可移植的程序应该避免使用位段

3.位段的跨平台问题

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

总结:

跟结构相比,位段可以达到相同的效果,并且可以很好的节约空间,但有跨平台的问题存在 

 4.位段的应用

下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。

5.位段使用的注意事项 

因为同一个字节中可能会有多个位段成员,而这些成员的起始位置并不是某个字节的起始位置,所以这些位置处是没有地址的

内存中每个字节分配一个地址,但字节内的bit位是没有地址的

因此我们不能对位段的成员使用&操作符


总结

结构体在C语言中并不难,学完后找一些题做做就行啦

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

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

相关文章

【JAVA开源】基于Vue和SpringBoot的影城管理系统

本文项目编号 T 045 &#xff0c;文末自助获取源码 \color{red}{T045&#xff0c;文末自助获取源码} T045&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 用…

通信工程高级职称评审条件详细解读

通信工程只有正高和副高级别的职称&#xff0c;中级通信工程的职称是需要自己参加考试的&#xff0c;并不是评审获得&#xff0c;这个大家需要注意一下&#xff0c;先要考取中级通信工程师之后才能评审副高和正高级通信工程的职称。 下面跟甘建二一起来看看通信专业职称评审条件…

C++ 9.24

作业一&#xff1a;将昨天的My_string类中的所有能重载的运算符全部进行重载、[] 、>、、<、、>、<、!、&#xff08;可以加等一个字符串&#xff0c;也可以加等一个字符&#xff09;、输入输出>>、<<。 main.cpp #include <iostream> #include…

华为昇腾系列-jupyter安装torch_npu

使用背景 国产算力的兴起&#xff0c;异构算力成为各大厂商的选择&#xff0c;以摆脱对英伟达算力过大的依赖&#xff0c;保障算力安全。本文将会讲解如何使用昇腾算力卡来制作一个镜像&#xff0c;然后交给k8s进行算力调度&#xff0c;显示国产算力的真正应用落地。 安装步骤…

微服务配置管理——动态路由

动态路由 网关的路由配置全部是在项目启动时由org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator在项目启动的时候加载&#xff0c;并且一经加载就会缓存到内存中的路由表内&#xff08;一个Map&#xff09;&#xff0c;不会改变。也不会监听路由变更新…

创建游戏暂停菜单

创建用户控件 设置样式 , 加一层 背景模糊 提升UI菜单界面质感 , 按钮用 灰色调 编写菜单逻辑 转到第三人称蓝图 推荐用 Set Input Mode Game And UI , 只用仅UI的话 增强输入响应不了 让游戏暂停的话也可以用 Set Game Paused , 打勾就是暂停 , 不打勾就是继续游戏 , 然后…

0基础如何转行IT

这是一个学习为王的时代&#xff0c;你没有超强的主动学习能力&#xff0c;很容易在千军万马的竞争中落后&#xff0c;甚至被优秀的替代者淘汰。 小白如何转行IT 正所谓业精于专&#xff0c;相较于科班生&#xff0c;非科班转行的在基础方面确实比较薄弱&#xff0c;因此必须…

VMWare虚拟机键盘卡顿

文章目录 环境问题解决办法参考 环境 Windows 11 家庭中文版VMware Workstation 17 ProUbuntu 24.04.1 问题 最近新入手了一台电脑台式机&#xff0c;型号是联想拯救者刃7000K&#xff0c;自带Win11家庭版。主机的CPU是第14代英特尔酷睿i9处理器&#xff0c;异构24核32线程。…

ubuntu 安装minikube,并拉取k8s镜像

虚拟机是vmware17, 系统是ubuntu20.4&#xff0c; minikube是1.23.1&#xff0c; docker是24.0.7&#xff0c; 为什么要装minikube&#xff0c;通常k8s集群是要3台机子以上&#xff0c;而通过minikube&#xff0c;可以在一台机子上搭建出k8s集群&#xff0c;minikube采用的是D…

unraid使用docker安装redis并创建密码

unraid使用docker安装redis并创建密码 一、redis简单介绍 redis基于K-V思路&#xff0c;数据存储在内存中&#xff0c;速度快&#xff0c;高效。 使用时会结合其他数据库如mysql。 二、redis安装 应用市场搜索redis&#xff0c;找下载量最高的一个即可&#xff0c;其中参数只…

5--SpringBoot项目中菜品管理 详解(一)

目录 公共字段自动填充 问题分析 实现思路 代码开发 步骤一 步骤二 功能测试 新增菜品 需求分析和设计 代码开发 文件上传接口 功能测试 公共字段自动填充 问题分析 后台系统的员工管理功能和菜品分类功能的开发&#xff0c;在新增员工或者新增菜品分类时需要设置…

C语言特殊字符串函数和字符函数

特殊字符串函数 strtok(字符串切割函数) 重点&#xff1a;1.delimiters 参数是个字符串&#xff0c;定义了用作分割符的字符集合 2.第一个参数指定一个字符串&#xff0c;里面包含0个或者多个分隔符 3.strtok函数找到str中的分隔符&#xff0c;会把它改成\0&#xff0c;然后…

内衣洗衣机哪个牌子好用?五款业内口碑爆棚产品汇总

内衣裤洗衣机是一种非常实用的洗衣机&#xff0c;可以有效地保护内衣和贴身衣物的质量和卫生&#xff0c;相比于普通的家用大型洗衣机&#xff0c;内衣裤洗衣机在容量、洗涤方式、控制方式和价格等方面有很大的不同之处&#xff0c;如果您经常需要清洗内衣和贴身衣物&#xff0…

无人机蜂群作战会成为未来战争的主要形式吗,该如何反制呢?

无人机蜂群作战在未来战争中确实有可能成为一种重要的作战形式&#xff0c;但是否会成为“主要形式”则取决于多种因素&#xff0c;包括技术发展、战术创新、战略需求以及国际政治和军事格局的变化等。以下是对无人机蜂群作战及其反制措施的详细分析&#xff1a; 一、无人机蜂…

图神经网络(GNN)简单介绍

参考文章:A Gentle Introduction to Graph Neural Networks 仅作为自己学习的笔记 GNN应用领域&#xff1a; 芯片设计 场景分析与问题分析 推荐系统&#xff08;类似抖音&#xff09; 欺诈检测&#xff0c;风控相关 知识图谱 道路交通&#xff0c;动态流量预测 自动驾驶&…

程序员的得力助手:Kimi AI的实战体验引言

引言 作为一名程序员&#xff0c;我们经常需要处理大量信息&#xff0c;从代码调试到文档编写&#xff0c;再到团队协作&#xff0c;每一项任务都需要我们保持高度的专注和效率。在这个过程中&#xff0c;一个得力的助手可以极大地提升我们的工作效率。今天&#xff0c;我想和…

洛谷P2571.传送带

洛谷P2571.传送带 三分模板题 用于单峰函数求极值 一定可以将答案路径分成三段即AE - EF - FD (E和A可能重复&#xff0c;F和D可能重合) E在线段AB上&#xff0c;F在线段CD上 因为有两个不定点EF&#xff0c;因此假设E为参数&#xff0c;三分求F的位置再外层三分求E的位置 …

PMP--三模--解题--1-10

文章目录 9.资源管理1、 [单选] 项目已经准备好开工&#xff0c;资源已经配置好。开发经理随后通知项目经理&#xff0c;由于家庭紧急情况&#xff0c;关键资源不再可用。开发经理表示&#xff0c;所有其他开发人员都被分配到其他项目&#xff0c;任何开发人员都没有能力承担额…

Windows内核编程基础(1)

在前面的文章中&#xff0c;介绍了如何配置开发环境以及如何进行调试。 接下来的几篇文章&#xff0c;将会重点介绍内核编程中所需要了解的一些理论基础。 我写这个系列文章的主要目的是方便以后自己查阅&#xff0c;同时也给正在学习内核开发的小伙伴一些参考&#xff0c;所…

在线PDF转图片怎么转?4种简单转换的方法分享

在线PDF转图片怎么转&#xff1f;在线PDF转图片不仅简化了文档处理流程&#xff0c;还极大地提升了工作效率。无论是教师准备教学材料、学生整理笔记&#xff0c;还是职场人士分享报告&#xff0c;都能通过这一功能轻松实现PDF到图片的转换&#xff0c;确保内容的高清展示与便捷…