【c语言】自定义类型-结构体

结构体

  • 结构体的声明与使用
    • 结构体的声明与初始化
    • 结构体的自引用
  • 结构体的内存对齐
    • 对齐规则
    • 为什么存在内存对齐
    • 修改默认对齐数
  • 结构体的传参
  • 结构体实现位段
    • 什么是位段
    • 位段的内存分配
    • 位段的跨平台问题
    • 位段使用的注意事项

结构体:是一个自定义的类型,成员可以有一个或者多个,类型可以不同。

结构体的声明与使用

结构体的声明与初始化

struct  结构体名
{成员变量;....
};
//结构体初始化
struct Stu
{char name[20];int age;char sex[10]};
int main()
{struct Stu s1={"lisi",18,"man"};return 0;
}

结构体还存在一中特殊声明:匿名结构体

struct 
{成员变量;...
};

这个结构体在声明时不需要写入结构体名,但是在使用时,只能使用一次
比如下面这段代码:

struct 
{int a;int b;float c;
}x;
struct 
{int a;int b;float c;
}*p;
int main()
{p=&x;return 0;
}

这段代码在编译时会报警告,因为编译器会把这两个声明当成两个不同的类型。
在这里插入图片描述
结构体变量的声明与初始化:
直接上代码:

//在声明结构体的同时声明结构体变量并初始化
struct stu
{int a;int b;float c;
}x={1,2,1.0};
int main()
{return 0;
}
///
struct stu
{int a;int b;float c;
};
int main()
{struct 	stu x={1,2,1.0};//声明结构体变量并初始化return 0;
}

对结构体数组成员初始化。

struct Stu
{char name[20];int age;char sex[10]};
int main()
{struct Stu s1={"lisi",18,"man"};printf("%s %d %s",s1.name,s1.age,s1.sex);//输出结构体的值return 0;
}

结构体的自引用

若结构体中包含一个结构体本身的成员(自引用),是否可以?
比如定义一个链表节点:

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

思考一下,这段代码正确吗?
答案当然是“错误”的。因为以这样的方式嵌套定义结构体,那么这结构体的大小将是无穷大的。
正确的自引用应该是下面这样的:

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

这在后面将会学到,这其实是建立了一个链表的节点,定义一个结构体指针,让结构体指针指向结构体的数据域(data),再让被指向的结构体的指针成员(next)指向下一个结构体的数据域(data)。

typedef关键字
typedef关键字使用来对类型进行重命名的。
比如:

typedef int i;int 重命名为i 
typedef char c;char重命名为c。

或许你会有这么一个想法“int、char一个就3个字母,一个就4个字母,我为什么还有重命名?重命名它俩还要多打一个typedef,不是更麻烦吗?”

确实,在这中情况下,确实没必要对数据类型重命名,但是在c语言中除了基本数据类型,还有自定义的数据类型。就如我们这篇文章的所讲的结构体类型,当我们定义了一个结构体类型后,要声明一个结构体变量就需要将struct 结构体名 都打出来,但是如果我们使用typedef关键字对它重命名,就能方便很多了。

typedef struct Student
{int age;char name[20];
}S;
int mian()
{S s1={18,"lisi"};return 0;	
}

但是要注意一点,重命名的类型需要重命名完后才能使用,否则编译器过不了编译,比如:

typedef struct Student 
{int  age;S *next;
}S;

这里,提前使用了结构体struct Student类型重命名后的名字S,是不可以的,因为编译器是从上往下编译代码的,当编译到S*next时,S还没有被编译到,所以对于编译器来说,S类型是不存在的。正确的形式应该像下面这样子。

typedef struct Student
{int age;char name[20];
}S;
int main()
{S s1={18,"lisi"};return 0;
}

结构体的内存对齐

到这里,我们已经掌握了结构体的基本使用,那么我们再接着思考“定义的结构体的大小是多少?int是4字节,char是1字节,那么结构体呢?”
在讨论结构体大小之前,我们先了解一下结构体的对齐规则

对齐规则

结构体对齐规则:是指结构体中每个成员变量的存放地址要满足一定的条件,以提高内存的访问效率。
结构体对齐规则:

  • 结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处
  • 结构体其余成员对齐到对齐数的整数倍的地址处
  • 对齐数=编译器默认的对齐数与该成员变量自身大小两者之间的较小值,vs中默认对齐数为8,Linux中gcc没有默认对齐数
  • 结构体总大小为最大对齐(所以成员变量都有一个对齐数,选最大的那一个)的整数倍
  • 如果结构体2嵌套了一个结构体1则这个嵌套的结构体1的对齐数=自身成员变量的最大对齐数,结构体2的大小就是最大对齐数的整数倍

比如:

struct Stu
{int age;char a;int b;
}S;

则这个结构体的大小为计算过程:
在这里插入图片描述
在思考的过程中,你应该已经发现了:内存对齐存在内存浪费。是的,内存对齐就是一种用空间换时间的做法。

为什么存在内存对齐

硬件平台限制:并不是所有硬件都能够任意读取任意地址处的数据的,某些硬件只能访问某些特定地址下的数据,如果不进行内存对齐,则cpu将不会读取到特定地址处的数据。
性能问题:cpu在读取内存时,是按照一个固定的粒度来读取的(比如一次四字节、8字节),如果结构体不按照一定的规则进行内存对齐,那么cpu在读取一份数据的时候,可能需要读取两次、三次甚至更多次才能把一份数据读完,而进行内存对齐后,cpu则只需要读取一次就行了。

修改默认对齐数

#pragma,这个预处理指令可以修改默认对齐数 。

#pragma(1);//将默认对齐数修改为1
struct Stu
{int age;char [name];
}s;
int main()
{printf("%zd",sizeof(struct Stu));//输出12//若把#pragma(1)注释掉,默认对齐数恢复到8,次数输出6return 0;
}

当结构体的对齐数不合适的时候,我们就可以通过#pragma来修改默认对齐数。

结构体的传参

struct S
{int age;char name[20];float i;
};struct S s = {18,"lisi",1.0};void print1(struct S s)
{printf("%d %s %lf\n",s.age,s.name,s.i);
}void print2(struct S *s)
{printf("%d %s %lf\n",s->age,s->name,s->i);
}int main()
{print1(s);print2(&s);return 0;
}

结构体传参也分为两类:

  • 传值调用
  • 传址调用

在传参时,推荐使用传址调用,因为函数传参的时候,参数是需要压栈的,会有时间与空间上的系统开销。
传值调用,如果传入的参数过大,这个开销也就会变大。
但如果传入地址,地址大小就两种情况:4字节/8字节,所有推荐使用传址调用。

结构体实现位段

什么是位段

什么是位段:是一种数据结构,它允许将数据以位(bit)的形式紧凑地存储,并允许程序员对此结构的位进行操作
位段的声明与结构体类似:

struct A{int _a:2;int _b:5;int _c:10;int _d:30;};

这段代码中,struct A占8个字节:
int占4个字节,所以第一次开辟空间大小为4字节,4字节=32bit,但是struct A里,四个成员共占47bit,所以4字节的空间无法存储struct A类型的数据,,需要在开辟一个Int类型的空间,所以该位段大小为8字节。

位段的内存分配

  1. 位段的成员可以是Int家族,或者char等

  2. 位段空间的分配按照需要以4个字节或者1个字节的方式来开辟的

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

struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};

在这里插入图片描述

给定空间后,空间内存按照从左到右还是从右到左使用呢?这个不确定,标准没有定义

当剩下的空间不足以分配位段成员时,是浪费这部分空间还是继续使用?没有定义

我们假设空间是从右往左使用,空间是被浪费的

此时最后一个字节就完全倍浪费掉了。

位段的跨平台问题

  1. int 位段被当成有符号数还是⽆符号数是不确定的。
  2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会
    出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
  4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃
    剩余的位还是利⽤,这是不确定的。

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

位段使用的注意事项

位段的有些成员的起始位置并不是字节的起始位置,就好比上图中的‘a’,‘b’,我们知道,每个字节都有自己的地址,1字节=8bit,这8bit是没有地址的,所以在使用位段时,我们不能对位段成员使用&(取地址)操作符,也不能进行scanf操作,如果需要给位段成员赋值,需要先给一个变量赋值,在通过这个变量赋值给位段成员

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

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

相关文章

利用CY3-COOH的羧基官能团标记蛋白质-星戈瑞

蛋白质作为生命体系中的关键分子,参与众多生物过程。因此,对蛋白质进行特异性标记和追踪是生物学研究中不可或缺的一环。CY3-COOH作为一种带有羧基官能团的荧光染料,具有强烈且稳定的荧光性质,被应用于蛋白质的标记和可视化。 标…

JavaScript严格模式

1. 严格模式 ECMAScript 5的严格模式是采用具有限制性 JavaScript 变体的一种方式,从而使代码隐式地脱离“马虎模式/稀松模式/懒散模式“(sloppy)模式。严格模式不仅仅是一个子集:它的产生是为了形成与正常代码不同的语义。不支持…

使用Docker辅助图像识别程序开发:在Docker中显示GUI、访问GPU、USB相机以及网络

目录概览 引言安装和配置安装docker安装nvidia-docker在docker中显示GUI在Docker中访问usb相机在Docker镜像中开放端口开启更多的GPU功能支持创建本地镜像中心一些可选参数上传镜像回收空间清理所有的无用镜像清理指定的镜像GPU Docker with Anaconda第一种方式:构建DockerFile…

【微信小程序】事件绑定和事件对象

文章目录 1.什么是事件绑定2.button组件3.事件绑定4.input组件 1.什么是事件绑定 小程序中绑定事件与在网页开发中绑定事件几乎一致,只不过在小程序不能通过on的方式绑定事件,也没有click等事件,小程序中 绑定事件使用bind方法,c…

【ArcGIS微课1000例】0116:将度-分-秒值转换为十进制度值(字段计算器VBA)

相关阅读:【ArcGIS微课1000例】0087:经纬度格式转换(度分秒转度、度转度分秒) 文章目录 一、计算方法二、计算案例一、计算方法 将度分秒转换为十进制度的简单等式: DD = (Seconds/3600) + (Minutes/60) + Degrees如果角度值是负数,则转换方法不同。其中一种方法是:

外贸实战|做外贸要主动,才会跟客户有订单!

很多人对我说过这种情况:客户给我发了一份询盘,但是我回复以后,客户就不理我了,好伤心。 我问:你跟了多久了? 很多人都会回答:几天或者不记得,他不理我,我也不知道怎么…

Spring Boot的@Async注解有哪些坑需要避免

一、什么是Async注解? Async注解是Spring框架提供的一种用于声明异步方法的工具。它可以将标注的方法从调用者的线程中分离出来,另起一个新线程执行,从而避免阻塞调用者的线程,提高系统的并发能力和响应速度。 基本使用方法如下&a…

Redis页面优化

文章目录 1.Redis页面缓存1.思路分析2.首先记录一下目前访问商品列表页的QPS1.线程组配置10000次请求2.请求配置3.开始压测1.压测第一次 平均QPS为6122.压测第二次 平均QPS为6153.压测第三次 平均QPS为617 3.然后记录一下访问商品详情页的QPS1.线程组配置10000次请求2.请求配置…

AIGC简介:如何利用人工智能进行内容生成

目录 一、引言二、AIGC的定义与技术原理1. 定义说明2. 关键技术3. 技术原理 三、AIGC的主要应用领域1. 文本内容生成2. 图像和视频生成3. 音频内容的创建4. 数据分析与报告 四、实施AIGC的步骤和方法1. 定义项目目标2. 数据准备与处理3. 选择合适的工具和技术4. 模型训练与测试…

uni-app学习--基础组件使用、页面生命周期、本地存储、网络请求、条件编译、路由跳转

文章目录 1. 基本组件的使用1. text文本组件的使用2. view视图容器组件的使用3. button按钮组件的使用4. image组件的使用5. map组件 2. uni-app中的样式1. uni-app:px2rpx计算 3. uni-app的数据绑定1. 基本的数据绑定2. v-bind,v-for,v-on 4. uni-app的生命周期1. …

服务器数据恢复—raid5阵列上分配的卷被删除后重建如何恢复被删除卷的数据?

服务器存储数据恢复环境: 某品牌FlexStorage P5730服务器存储,存储中有一组由24块硬盘组建的RAID5阵列,包括1块热备硬盘。 服务器存储故障: 存储中的2个卷被删除,删除之后重建了一个新卷。需要恢复之前删除的一个卷的数…

等保题目分享(60000字版)

文章目录 前言判断题单选题多选题简答题 前言 来源于网友会议 判断题 在对数据进行差异备份前,仍需进行数据库的全量备份。(T) 某应用系统后台连续登录失败3次以后,需要输入验证码才能继续登录,这个应用系统实现了登录失败处理…

flink 状态

状态(State)是一个重要的概念,它允许Flink在处理流数据时跟踪和存储中间结果。这对于实现复杂的计算逻辑和满足应用需求至关重要。 Working with State 1. 状态类型 Flink支持两种主要类型的状态: 1.1 算子状态(Op…

头文件和源文件的一些情况分析

c函数的定义和声明 函数和变量的声明可以有多次,但是定义只能有一次 其实头文件可以写函数的定义,但是在工程里面很多人引用这个头文件很容易造成重复定义的情况 有一个例外情况头文件里面也可以写函数定义或者变量定义 一般情况 //2.h #pragma once int add(int a, int b…

洛谷 P8721 [蓝桥杯 2020 省 AB3] 填空问题(缺少 inc.txt, E 题数据) 题解

题目分析 A 数青蛙 根据青蛙的个数分类计算: 青蛙数小于等于 2 2 2:此时青蛙数、眼睛数、嘴巴数和腿数读出来只读 1 1 1 个字,故此时一句话 14 14 14 个字。青蛙数大于 2 2 2,小于等于 5 5 5:此时青蛙数、眼睛…

百分之九十的人都忽视了JMeter响应断言中的这个实用功能—— Jmeter Variable Name to use

JMeter的响应断言 相信对于使用过JMeter的同学来学,一定都使用过响应断言,在这里我就不相信介绍了,我们可以简单的理解为: JMeter的响应断言是一种用于检查测试中得到的响应数据是否符合预期的工具,旨在保证性能测试…

最高100万!2024年成都市标杆场景项目申报条件对象、奖励和认定材料流程

一、申报条件 (一)申报主体需注册成立两年以上,具备独立法人资格,在成都有固定经营或者生产场地,上两年度主营业务收入年均1000万元以上或上两年度主营业务收入增长率年均10%以上; (二&#x…

java基础选择题--18(转载)

1.设有定义 int a3 &#xff0c;b4&#xff0c;c5&#xff1b; 则以下的表达式中 &#xff0c; 值为 false 的是 &#xff08;&#xff09; 正确答案: D 你的答案&#xff1a;D&#xff08;正确&#xff09; A.a < b && b < c B.a < b C.a < ( b c ) D.!…

基于单片机的电流检测装置

摘  要 : 随着电子技术的发展和进步 , 小信号在电路中的使用愈加广泛 , 在实际应用中对于小电流信号的采集和监控越来越重要。 因此电路中的电流需要能够被简单 、 方便 、 准确 、 实时地测量 。 文中设计并实现了一套基于单片机的电流检测系统。 该系统使用功率放大电路…

2024最新python入门教程|python安装|pycharm安装

前言&#xff1a;在安装PyCharm之前&#xff0c;首先需要明确PyCharm是一款功能强大的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;由JetBrains公司开发。PyCharm旨在通过提供智能代码补全、语法高亮、代码检查、快速导航和重构等丰富的编码辅助工具&#xff0c…