【C语言】自定义类型:结构体

1、结构体类型的声明

struct tag
{
    member - list;  //成员
} variable-list;    //变量列表

 例如描述一本书:

struct Book
{char name[20];char author[20];float price;char id[13];
};  //分号不能丢

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

#include <stdio.h>
struct Book
{char name[20];char author[20];float price;char id[13];
}b3,b4;//全局变量       //分号不能丢void print(struct Book* ps)
{//第二种打印方式:箭头表示printf("%s %s %.1f %s\n", ps->name, ps->author, ps->price, ps->id);//第三种打印方式:函数+地址//printf("%s %s %.1f %s\n", (*ps).name,(*ps).author,(*ps).price,(*ps).id);
}
int main()
{//按照结构体成员的顺序初始化struct Book b1 = {"鹏哥C语言","鹏哥",18.8,"PG1001"};//局部变量//按照指定的顺序初始化struct Book b2 = { .id = "PG1002",.name = "JAVA入门",.price = 38.8,.author = "刘哥" };print(&b2);// 第一种打印方法  printf("%s %s %.1f %s\n", b1.name, b1.author, b1.price, b1.id);return 0;
}

 1.2 匿名结构体类型

声明结构体的时候,可以不完全声明。

比如:

//匿名结构体
struct
{char c;int i;double d;
}s;struct
{char c;int i;double d;
}a[20],*p;

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

 举例:

//匿名结构体
struct
{char c;int i;double d;
}s = {'x',100,3.14};
int main()
{printf("%c %d %.2lf", s.c, s.i, s.d);return 0;
}

 注意:匿名结构体类型,如果没有对结构体类型重命名的话,基本只能使用一次。

//匿名结构体类型
typedef struct
{char c;int i;double d;
}S;
int main()
{S s;//匿名结构体类型重命名return 0;
}

 但是匿名结构体重命名,意义不大。真正的匿名结构体就是我想使用一次,这次用完就不再用了。因为没有这个名字,没有这个名字以后就不能使用了

匿名结构体类型,不是销毁了,类型是一个模板,没名字,用不了。类型不会创建,不占用内存,所以没有销毁一说

1.3 结构体自引用

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

比如:定义一个链表的节点

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

 上述代码错误,因为一个结构体中再包含一个同类型的结构体变量,这样的结构体变量的大小会无穷大,是不合理的。

 正确的自引用方式:

//结构体自引用
struct Node
{int date;   //数据struct Node* next;   //指针
};

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

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

这是不行的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这样不行的。

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

typedef struct Node
{int date;struct Node* next;
}Node; //类型重命名为Node

匿名结构体类型是不能使用这种结构体自引用的效果的~ 

 

 结构体类型重命名更加方便的写法

每次创建变量要 struct Node n1 太麻烦,可以类型重命名之后,直接创建变量 Node n2

typedef struct Node
{int date;struct Node* next;
}Node; //类型重命名为Node
int main()
{//struct Node n1;//以后不需要struct Node n1了Node n2;//类型重命名之后可以直接 Node n2 创建变量,更加方便
}

有了结构体类型,再重命名:

struct Node
{int date;//数据struct Node* next;
};
typedef struct Node Node;
int main()
{//struct Node n1;Node n2;//类型重命名之后可以直接 Node n2 创建变量,更加方便
}

什么两段代码效果一样。

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

我们已经掌握了结构体的基本使用。

现在我们深入讨论一个问题:计算结构体的大小。

2.1 对齐规则

练习:判断下面结构体大小

struct S
{char c1;  //1int i;    //4char c2;  //1
};
int main()
{struct S s = { 0 };printf("%zd\n", sizeof(s));return 0;
}

 输出结果:

12

 为什么?

 首先得掌握结构体的对齐规则

1、结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处

2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

        对齐数 = 编译器默认的一个对齐数与该成员变量打下的较小值。

        -VS中默认的值为8.

        -Linux中gcc没有默认对齐数,对齐数就是成员自身的大小。

3、结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所以对齐数中最大的)的整数倍。

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

 ​​​​​​​

struct S 的大小计算画图

 练习:计算结构体大小

//练习2:
struct S2
{char c1;char c2;int i;
};
int main()
{struct S2 s2 = { 0 };printf("%zd", sizeof(s2));return 0;
}

//练习3
struct S3
{double d;//0-7char c;//8int i;//12-15
};
int main()
{struct S3 s3 = { 0 };printf("%zd", sizeof(s3));return 0;
}

//练习4
struct S3
{double d;//0-7char c;//8int i;//12-15
};//16
struct S4
{char c1;struct S3 s3;double i;
};
int main()
{struct S4 s4 = { 0 };printf("%zd", sizeof(s4));r

 

2.2 为什么存在内存对齐?

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

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

2、性能原因(重要原因)

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

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

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

  • 让占用空间小的成员尽量集中在一起
/例如:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};

S1和S2类型的成员一模一样,但是所占空间的大小有一些区别。S1占12个字节;S2占8个字节。

 2.3 修改默认对齐数

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

#pragma pack(1)//设置默认对齐数为1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的对齐数,还原为默认
int main()
{struct S s = { 0 };printf("%zd\n", sizeof(s));return 0;
}

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

3、结构体传参

struct S
{int arr[1000];int n;double d;
};
//结构体传参
void print1(struct S tmp)
{int i = 0;for (i = 0; i < 5; i++){printf("%d ", tmp.arr[i]);}printf("%d ", tmp.n);printf("%lf\n", tmp.d);
}
//结构体地址传参
void print2(struct S* ps)
{int i = 0;for (i = 0; i < 5; i++){printf("%d ", ps->arr[i]);}printf("%d ", ps->n);printf("%lf\n", ps->d);
}
int main()
{//对结构体创建变量,并初始化struct S s = { {1,2,3,4,5},100,3.14 };print1(s);  //传结构体print2(&s); //传地址return 0;
}

上面print1和print2函数哪个好?

答案:首选print2函数。

原因:

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

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

4、结构体实现位段

4.1、什么是位段

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

  • 1、位段的成员必须是 int、unsigned int 或 signed int,在C99中位段成员的类型也可以选择其他类型。
  • 2、位段的成员名后边有一个冒号和一个数字。

比如:

//位段式的结构
struct A
{int _a : 2;    //占2个bit位int _b : 5;    //占5个bit位int _c : 10;   //占10个bit位int _d : 20;   //占30个bit位
};

A就是一个位段类型。

那位段A所占内存的大小是多少?

struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 20;
};
int main()
{printf("%zd", sizeof(struct A));return 0;
}

 输出结果:

8

 4.2 位段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;//空间是如何开辟的return 0;
}

4.3 位段跨平台问题

 1、int位段被当成有符号数还是无符号数是不确定的。

2、位段中最大位的数目不能确定,(16位及其最大16,32位机器最大32。写成27,在16位机器会出问题)。

3、位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。(在VS中从右向左)

4、当一个结构包含两个位段。第二个位段成员比较大,无法容纳于第一个位段属于的位时,是舍弃剩余的位还是利用,这是不确定的。

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

4.4 位段的应用

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

4.5 位段使用的注意事项

 位段是几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。

使用不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段成员。

struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{struct A sa = { 0 };scanf("%d", &(sa._b));//这是错误的//不能对位段成员取地址&//正确的⽰范int b = 0;scanf("%d", &b);sa._b = b;return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Orbit 使用指南 08 | 登记注册环境 | Isaac Sim | Omniverse

如是我闻&#xff1a; 在上一个指南中&#xff0c;我们学习了如何创建一个自定义的车杆环境。我们通过导入环境类及其配置类来手动创建了一个环境实例 # create environment configurationenv_cfg CartpoleEnvCfg()env_cfg.scene.num_envs args_cli.num_envs# setup RL envir…

Llama 2 模型

非常清楚&#xff01;&#xff01;&#xff01;Llama 2详解 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/649756898?utm_campaignshareopn&utm_mediumsocial&utm_psn1754103877518098432&utm_sourcewechat_session一些补充理解&#xff1a; 序列化&#xff…

活用 C语言之union的精妙之用

一、union的基本定义 Union的中文叫法又被称为共用体、联合或者联合体。它的定义方式与结构体相同,但意义却与结构体完全不同。下面是union的定义格式: union 共用体名 {成员列表}共用体变量名;它与结构体的定义方式相同,但区别在于共用体中的成员的起始地址都是相同的,…

备考ICA----Istio实验7---故障注入 Fault Injection 实验

备考ICA----Istio实验7—故障注入 Fault Injection 实验 Istio 的故障注入用于模拟应用程序中的故障现象&#xff0c;以测试应用程序的故障恢复能力。故障注入有两种: 1.delay延迟注入 2.abort中止注入 1. 环境准备 kubectl apply -f istio/samples/bookinfo/platform/kube/…

Flask 与小程序 的图片数据交互 过程及探讨研究学习

今天不知道怎么的&#xff0c;之前拿编程浪子地作品抄过来粘上用好好的&#xff0c;昨天开始照片突的就不显示了。 今天不妨再耐味地细细探究一下微信小程序wxml 和flask服务器端是怎么jpg图片数据交互的。 mina/pages/food/index.wxml <!--index.wxml--> <!--1px …

学习添加03(优惠卷)

1.优化卷模块的介绍 整体流程&#xff1a; 优惠卷表设计&#xff1a; 优惠卷范围表设计&#xff1a; 兑换码表设计&#xff1a;

Python核心编程 --- 高级数据类型

Python核心编程 — 高级数据类型 字符串 列表 元组 字典 1.序列 序列&#xff1a;一组按顺序排列的数据集合。 在Python中存在三种内置的序列类型&#xff1a;字符串、列表、元组 优点&#xff1a;可支持索引和切片操作 特点&#xff1a;第一个正索引为0&#xff0c;指…

【vue3.0】实现导出的PDF文件内容是红头文件格式

效果图: 编写文件里面的主要内容 <main><div id"report-box"><p>线索描述</p><p class"label"><span>线索发现时间:</span> <span>{{ detailInfoVal?.problem.createdDate }}</span></p><…

腾讯在GDC 2024展示GiiNEX AI游戏引擎现已投入《元梦之星》中开发使用,展示强大AIGC能力

在近日举行的GDC 2024游戏开发者大会上&#xff0c;腾讯揭开了其AI Lab团队精心打造的GiiNEX AI游戏引擎的神秘面纱。这款引擎依托先进的生成式AI和决策AI技术&#xff0c;为游戏行业带来了革命性的变革。 相关阅读&#xff1a;腾讯游戏出品&#xff01;腾讯研效AIGC&#xff…

hyperf 二十八 修改器 一

教程&#xff1a;Hyperf 一 修改器和访问器 根据教程&#xff0c;可设置相关函数,如set属性名Attribute()、get属性名Attribute()&#xff0c;设置和获取属性。这在thinkphp中也常见。 修改器&#xff1a;set属性名Attribute()&#xff1b;访问器&#xff1a;get属性名Attri…

lora-scripts 训练IP形象

CodeWithGPU | 能复现才是好算法CodeWithGPU | GitHub AI算法复现社区&#xff0c;能复现才是好算法https://www.codewithgpu.com/i/Akegarasu/lora-scripts/lora-trainstable-diffusion打造自己的lora模型&#xff08;使用lora-scripts&#xff09;-CSDN博客文章浏览阅读1.1k次…

什么是RabbitMQ的死信队列

RabbitMQ的死信队列&#xff08;Dead Letter Queue&#xff0c;简称DLQ&#xff09;是一种用于处理消息失败或无法路由的消息的机制。它允许将无法被正常消费的消息重新路由到另一个队列&#xff0c;以便稍后进行进一步处理、分析或排查问题。 当消息对立里面的消息出现以下几…

python网络相册设计与实现flask-django-nodejs-php

此系统设计主要采用的是python语言来进行开发&#xff0c;采用django框架技术&#xff0c;框架分为三层&#xff0c;分别是控制层Controller&#xff0c;业务处理层Service&#xff0c;持久层dao&#xff0c;能够采用多层次管理开发&#xff0c;对于各个模块设计制作有一定的安…

利用API打造卓越的用户体验

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 正文 1. 数据驱动的设计 2. 功能扩展与整合 3. 实时性与响应性 4. 个性化推荐与定制化服务 结语 我的其他博客 正文 随着数字化时代的…

如何让电脑定时开机?这个方法你一定要学会

前言 前段时间小白在上班的时候&#xff0c;个人使用一台台式机和一台笔记本电脑。台式机并不是经常使用&#xff0c;但整个公司的数据中心是建立在小白所使用的那台台式机上。 如果台式机没有开机&#xff0c;同事们就没办法访问数据中心获取自己想要的资料。领导也没办法链…

4核16G服务器租用优惠价格,26.52元1个月,半年149元

阿里云4核16G服务器优惠价格26.52元1个月、79.56元3个月、149.00元半年&#xff0c;配置为阿里云服务器ECS经济型e实例ecs.e-c1m4.xlarge&#xff0c;4核16G、按固定带宽 10Mbs、100GB ESSD Entry系统盘&#xff0c;活动链接 aliyunfuwuqi.com/go/aliyun 活动链接打开如下图&a…

Tkinter 一文读懂

Tkinter 简介 Tkinter&#xff08;即 tk interface&#xff0c;简称“Tk”&#xff09;本质上是对 Tcl/Tk 软件包的 Python 接口封装&#xff0c;它是 Python 官方推荐的 GUI 工具包&#xff0c;属于 Python 自带的标准库模块&#xff0c;当您安装好 Python 后&#xff0c;就可…

爬虫分析-基于Python的空气质量数据分析与实践

概要 本篇文章利用了Python爬虫技术对空气质量网站的数据进行获取&#xff0c;获取之后把数据生成CSV格式的文件&#xff0c;然后再存入数据库方便保存。再从之前24小时的AQI&#xff08;空气质量指数&#xff09;的平均值中进行分析,把数据取出来后&#xff0c;对数据进行数据…

Android Studio 编译报错 ( Could not find com.android.tools.build:gradle:4.2.1.)

检查下根目录下的 build.gradle 配置 , 是否只配置了 jcenter 仓库 &#xff0c;加上 google()mavenCentral() 重新编译试一下

nacos服务注册中心,配置中心

Spring Cloud alibaba: nacos服务注册中心&#xff0c;配置中心 首先搭建Nacos服务注册中心。 在搭建Nacos服务注册中心之前需要搞清楚两个概念&#xff1a;namespace和group。 先创建namespace&#xff0c;然后配置nacos的依赖spring-cloud-alibaba-dependencies&#xff0c;…