自定义类型:结构体

自定义类型:结构体

  • 一:引入
  • 二:结构体类型的声明
    • 1:正常声明
    • 2:特殊声明
  • 三:结构体变量的创建和初始化
    • 1:结构体变量的创建
    • 2:结构体变量的初始化
  • 三:结构体访问操作符
  • 四:结构体内存对齐
    • 1:对齐规则
    • 2:为什么存在内存对齐?
    • 3:修改默认对齐数
  • 五:结构体传参
  • 六:结构体实现位段
    • 1:位段的声明:
    • 2:位段的内存分配:
    • 3:位段的跨平台问题
    • 4:位段的应用
    • 5:位段的使用注意事项:

一:引入

C语言中有内置类型,如int ,char,float,long,long long等,这些内置类型可以直接使用。
如果我们想要描述一个学生,比如学生的名字,年龄,分数等,只有一个内置类型是无法描述清楚的,所以引入结构体的使用。

二:结构体类型的声明

1:正常声明

结构体是自定义类型
结构体是一些值的集合,这些值称为成员变量,结构体的每个成员可以是不同类型的变量。
例如描述一个同学:

struct Stu//
{char name[20];//名字int age;//年龄float score;//分数};

{}里面的内容就是结构体的成员。

2:特殊声明

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

struct
{int a;char b;float c;
}s = { 0 };//这种特殊的声明只能此时初始化
struct
{int a;char b;float c;
}*ps;
int main()
{ps = &s;//error,虽然结构类型的成员一模一样,但编译器依然认为=两边是不同的指针类型return 0;
}

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

1:结构体变量的创建

#include <stdio.h>
struct stu
{char name[20];int age;float score;
}s4, s5;//结构体变量的创建s4,s5
int main()
{struct stu s1, s2, s3; //结构体变量的创建s1,s2,s3return 0;
}

2:结构体变量的初始化

#include <stdio.h>
struct stu
{char name[20];int age;float score;
}s2 = { "lisi",21,89.5f };//结构体变量的创建及初始化
int main()
{struct stu s1 = { "zhangsan",20,85.5f };//结构体变量的创建及初始化struct stu s2={.age=22,.name="wanger",.score=98.5f};//特殊初始化return 0;}
struct Stu//struct是结构体类型的关键字//struct Stu是用户定义的结构体类型
{//name,age,score都是都是结构体成员名char name[20];//名字int age;//年龄int score;//分数}s;//声明的同时定义结构体变量s
//结构体类型不占用空间,相当于盖房子时所使用的图纸
struct Stu s = { "zhangsan",20,84 };//结构体变量的初始化
//结构体变量占用空间,相当于盖好了的房子

三:结构体访问操作符

结构体成员访问操作符有两个,一个是 . 一个是->
形式如下:
结构体变量.成员变量名
结构体指针->成员变量名

#include <stdio.h>
struct stu
{char name[20];int age;int score;
};
int main()
{struct stu s = { "zhangsan",20,90 };struct stu* p = &s;printf("%d %d\n", s.age,s.score);printf("%d %d\n", p->age, p->score);return 0;
}

四:结构体内存对齐

结构体的大小与结构体内存对齐有关。

1:对齐规则

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

#include <stdio.h>
struct S1
{char c1;char c2;int a;
};struct S2
{char c1;int a;char c2;
};int main()
{struct S2 s2 = { 'a', 100, 'b'};printf("%zd\n", sizeof(s2));struct S1 s1 = { 'a', 'b', 100 };printf("%zd\n", sizeof(s1));return 0;
}

在这里插入图片描述
在这里插入图片描述
例2:结构体的嵌套

#include <stdio.h>
struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%zd\n", sizeof(struct S3));//?printf("%zd\n", sizeof(struct S4));//?return 0;
}

在这里插入图片描述
在这里插入图片描述

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

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

#include <stdio.h>
struct S1
{char c1;char c2;int a;
};struct S2
{char c1;int a;char c2;
};int main()
{struct S2 s2 = { 'a', 100, 'b'};printf("%zd\n", sizeof(s2));//12struct S1 s1 = { 'a', 'b', 100 };printf("%zd\n", sizeof(s1));//8两个char类型的数据放在了一起return 0;
}

3:修改默认对齐数

#pragma这个预处理命令可以改变编译器的默认对齐数。
#pragma pack()//取消设置的默认对齐数,还原为系统默认的对齐数

#include <stdio.h>
#pragma pack(1)//将默认对齐数设置为1
struct s
{char c1;int i;char c2;
};int main()
{printf("%d ", sizeof(struct s));return 0;
}

五:结构体传参

#include <stdio.h>
struct S
{int age;int num;
};
struct S s = { 20,100 };
void print1(struct S t)
{printf("%d\n", t.num);
}
void print2(struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{print1(s);//传递的是结构体变量,也就是传值调用print2(&s);//传递的是结构体变量的地址,也就是传址调用return 0;
}

传递结构体变量的时候,形参是实参的一份临时拷贝,形参会单独占用空间,传数据
也需要时间,如果实参数据较大,那么浪费的空间和时间将非常大。
函数传参的时候,参数需要压栈,会有时间和空间上的系统开销,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,会导致性能的下降。

六:结构体实现位段

位段是基于结构体的,结构体可能浪费空间,而位段可以节省空间。位段的位指的是二进制位。

1:位段的声明:

#include <stdio.h>
struct A
{int a : 2;//a占2个bit位int b : 4;//b占4个bit位int c : 7;//c占7个bit位
};
int main()
{printf("%d ", sizeof(struct A));return 0;
}

2:位段的内存分配:

1:位段的成员可以是int ,unsigned int,signed int,char类型。
2:位段的空间上是按照需要4个字节(int)或一个字节(char)来开辟的。
3:位段涉及很多不确定的因素,位段是不可以跨平台的,如果想要跨平台需要使用不同的代码。(当开辟了内存后,内存中的每个bit从左向右使用还是从右向左使用,不确定;当前面的内存使用后,剩下的空间不能够放下一个成员时,剩下的空间是否使用,也不确定)。
下面主要讲述在VS编译器下位段的内存分配。
在VS编译器下,每个bit位从右向左使用,使用后剩余的空间不能够放下下一个元素时,该剩余空间将不再使用,需要再开辟一个字节的空间,放下一个元素。

#include <stdio.h>
struct S
{char a : 3;//开辟第一个字节,a占3个bit位,还剩5个比特位char b : 4;//剩下的5个bit位能够放下b,还剩下1个bit位char c : 5;//剩下的1个比特位 不能够放下c,再开辟一个新的字节放c,//放c后还剩下3个字节,剩下的3个字节放不下dchar d : 4;//再开辟一个新的字节放d
};int main()
{struct S s = { 0 };printf("%d ", sizeof(s));//3return 0;
}

3:位段的跨平台问题

1:int位段被当成有符号数还是无符号数是不确定的。
2:位段中最大的数目不能确定(16位机器下最大是16,32位机器下是32,写成33,在16位和32位的机器下都会报错)
在这里插入图片描述
3:位段中的成员在内存中是从左向右分配还是从右向左分配不确定。
4:当一个结构包含2个位段,第二个位段的成员比较大,第一个位段剩下的空间无法容纳第二个位段时,是舍弃剩余的位还是使用,这是不确定的。
总之:位段可以节省空间,但有跨平台问题,想要解决跨平台问题,需要写不同的代码

4:位段的应用

在网络协议中,IP数据报的格式,其中很多的属性只需几个bit位就能够描述,在这里使用位段,可以节省很多空间,网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。
在这里插入图片描述

5:位段的使用注意事项:

内存中一个字节有一个编号(地址),位段可能几个成员共用一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置是没有地址的。所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能先输入到一个变量里,然后赋值给位段的成员。
在这里插入图片描述

#include <stdio.h>
struct S
{char a : 3;char b : 5;int c : 7;
};
int main()
{struct S s = { 0 };int c = 0;//scanf("%d", &s.c);scanf("%d", &c);s.c = c;printf("c=%d ", s.c);return 0;
}

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

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

相关文章

【C语言】每日一题(半月斩)——day3

目录 一&#xff0c;选择题 1.已知函数的原型是&#xff1a; int fun(char b[10], int *a); 2、请问下列表达式哪些会被编译器禁止【多选】&#xff08; &#xff09; 3、以下程序的输出结果为&#xff08; &#xff09; 4、下面代码段的输出是&#xff08; &#xff09;…

大数据学习1.1-Centos8虚拟机安装

1.创建新的虚拟机 2.选择稍后安装OS 3.选择Linux的CentOS8 4.选择安装路径 5.分配20g存储空间 6.自定义硬件 7.分配2g内存 8.分配2核处理器 9.选择镜像位置 10.开启虚拟机安装 推荐密码设置为root

61、SpringBoot -----跨域资源的设置----局部设置和全局设置

★ 跨域资源共享的意义 ▲ 在前后端分离的开发架构中&#xff0c;前端应用和后端应用往往是彻底隔离的&#xff0c;二者不在同一个应用服务器内、甚至不再同一台物理节点上。 因此前端应用和后端应用就不在同一个域里。▲ 在这种架构下&#xff0c;前端应用可能采用前端框架&a…

序列化和反序列化:将数据变得更加通用化

序列化与反序列化简介 序列化和反序列化是计算机领域中常用的概念&#xff0c;用于将对象或数据结构转换为字节序列&#xff08;序列化&#xff09;和将字节序列转换回对象或数据结构&#xff08;反序列化&#xff09;。 序列化是指将对象或数据结构转换为字节序列的过程。通…

前端VUE---JS实现数据的模糊搜索

实现背景 因为后端实现人员列表返回&#xff0c;每次返回的数据量在100以内&#xff0c;要求前端自己进行模糊搜索 页面实现 因为是实时更新数据的&#xff0c;就不需要搜索和重置按钮了 代码 HTML <el-dialogtitle"团队人员详情":visible.sync"centerDi…

uni-app跳转到另一个app

第一步&#xff1a; 首先要知道 app的包名 获取方式如下 第二步&#xff1a; 在第一个 demo1 app 一个页面中需要一个按钮去跳转 方法如下 <template><view class"content"><button click"tz">跳转</button></view> </…

如何在微软Edge浏览器上一键观看高清视频?

编者按&#xff1a;视频是当下最流行的媒体形式之一。但由于视频压缩、网络不稳定等原因&#xff0c;我们常常可以看到互联网上的很多视频其画面质量并不理想&#xff0c;尤其是在浏览器端&#xff0c;这极大地影响了观看体验。不过&#xff0c;近期微软 Edge 浏览器推出了一项…

FPGA纯verilog实现8路视频拼接显示,提供工程源码和技术支持

目录 1、前言版本更新说明免责声明 2、我已有的FPGA视频拼接叠加融合方案3、设计思路框架视频源选择OV5640摄像头配置及采集静态彩条视频拼接算法图像缓存视频输出 4、vivado工程详解5、工程移植说明vivado版本不一致处理FPGA型号不一致处理其他注意事项 6、上板调试验证并演示…

Jmeter接口测试简易步骤

使用Jmeter接口测试 1、首先右键添加一个线程组&#xff0c;然后我们重命名接口测试 2、在线程组上添加一个Http默认请求&#xff0c;并配置服务器的IP地址端口等信息 3、在线程组中添加一个HTTP请求&#xff0c;这里我们重命名“增加信用卡账户信息接口” 4、配置接口请求信息…

使用延迟队列解决分布式事务问题——以订单未支付过期,解锁库存为例

目录 一、前言 二、库存 三、订单 一、前言 上一篇使用springcloud-seata解决分布式事务问题-2PC模式我们说到了使用springcloud-seata解决分布式的缺点——不适用于高并发场景 因此我们使用延迟队列来解决分布式事务问题&#xff0c;即使用柔性事务-可靠消息-最终一致性方…

Kotlin simple convert ArrayList CopyOnWriteArrayList MutableList

Kotlin simple convert ArrayList CopyOnWriteArrayList MutableList Kotlin读写分离CopyOnWriteArrayList_zhangphil的博客-CSDN博客Java并发多线程环境中&#xff0c;造成死锁的最简单的场景是&#xff1a;多线程中的一个线程T_A持有锁L1并且申请试图获得锁L2&#xff0c;而多…

Redis缓存实现及其常见问题解决方案

随着互联网技术的发展&#xff0c;数据处理的速度和效率成为了衡量一个系统性能的重要指标。在众多的数据处理技术中&#xff0c;缓存技术以其出色的性能优化效果&#xff0c;成为了不可或缺的一环。而在众多的缓存技术中&#xff0c;Redis 以其出色的性能和丰富的功能&#xf…

flutter开发实战-长按TextField输入框cut、copy设置为中文复制、粘贴

flutter开发实战-长按TextField输入框cut、copy设置为中文复制、粘贴 在开发过程中&#xff0c;需要长按TextField输入框cut、copy设置为中文“复制、粘贴”&#xff0c;这里记录一下设置的代码。 一、pubspec.yaml设置flutter_localizations 在pubspec.yaml中设置flutter_l…

23下半年学习计划

大二上学期计划 现在已经是大二了&#xff0c;java只学了些皮毛&#xff0c;要学的知识还有很多&#xff0c;新的学期要找准方向&#xff0c;把要学的知识罗列&#xff0c;按部就班地完成计划&#xff0c;合理安排时间&#xff0c;按时完成学习任务。 学习node.js&#xff0c…

企业架构LNMP学习笔记48

数据结构类型操作&#xff1a; 数据结构&#xff1a;存储数据的方式 数据类型 算法&#xff1a;取数据的方式&#xff0c;代码就把数据进行组合&#xff0c;计算、存储、取出。 排序算法&#xff1a;冒泡排序、堆排序 二分。 key&#xff1a; key的命名规则不同于一般语言…

Android 12 源码分析 —— 应用层 六(StatusBar的UI创建和初始化)

Android 12 源码分析 —— 应用层 六&#xff08;StatusBar的UI创建和初始化) 在前面的文章中,我们分别介绍了Layout整体布局,以及StatusBar类的初始化.前者介绍了整体上面的布局,后者介绍了三大窗口的创建的入口处,以及需要做的准备工作.现在我们分别来细化三大窗口的UI创建和…

GitLab使用的最简便方式

GitLab介绍 GitLab是一个基于Git版本控制系统的开源平台&#xff0c;用于代码托管&#xff0c;持续集成&#xff0c;以及协作开发。它提供了一套完整的工具&#xff0c;以帮助开发团队协同工作、管理和部署代码。 往往在企业内部使用gitlab管理代码&#xff0c;记录一下将本地代…

redis 集群(cluster)

1. 前言 我们知道&#xff0c;在Web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999% 等等&#xff09;。但是在Redis语境中&#xff0c;高可用的含义似乎要宽泛一些&#xf…

6.3 字符数组

思维导图&#xff1a; 前言&#xff1a; 主要内容&#xff1a; 前言内容整理 字符型数据和存储 字符型数据是依据字符的ASCII代码存储在内存单元中&#xff0c;通常占用一个字节的空间。ASCII代码可以被认为是整数&#xff0c;因此在C99标准中&#xff0c;字符类型被归类为整…

现在进入广告行业好做吗?

广告行业真的很好&#xff0c;大家快来…… 在这里你可以无限发挥你的创意和想象力&#xff0c;有趣的同事&#xff0c;不刻板的工作内容&#xff0c;与爱豆合作&#xff0c;偶尔见见明星&#xff0c;出入城市CBD&#xff0c;一身名牌&#xff0c;精美PPT挥斥方遒&#xff0c;…