C语言:自定义类型(结构体)

目录

  • 一、结构的特殊声明
  • 二、结构的自引用
  • 三、结构体内存对齐
    • 1.对齐规则
    • 2.为什么存在内存对齐
      • (1)平台原因 (移植原因):
      • (2)性能原因:
    • 3.修改默认对齐数
  • 四、结构体传参
  • 五、结构体实现位段
    • 1.什么是位段
    • 2.位段的内存分配
    • 3.位段的跨平台问题
    • 4.位段使用的注意事项

一、结构的特殊声明

前面我们在学习操作符的时候,已经学习了结构体的创建和初始化以及声明,这里有不了解的宝子们可以看看我的这篇文章哈:https://blog.csdn.net/weixin_66058866/article/details/136741650
在声明结构的时候,可以不完全的声明。
比如:

//匿名结构体类型
struct//这里省略掉了结构体类型名
{int a;char b;float c;
}x;
struct
{int a;char b;float c;
}a[20], *p;

上面的两个结构在声明的时候省略掉了结构体标签(tag)。
那么问题来了?

//在上⾯代码的基础上,下⾯的代码合法吗?
p = &x;

警告:
编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次。

二、结构的自引用

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

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;

三、结构体内存对齐

我们已经掌握了结构体的基本使用了。
现在我们深入讨论一个问题:计算结构体的大小。
这也是一个特别热门的考点: 结构体内存对齐

1.对齐规则

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

(1) .结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
(2).其他成员变量要对齐到偏移量为自身对齐数的整数倍地址处。

对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值
-VS 中默认的对齐数为 8
-Linux中 gcc 没有默认对齐数,对齐数就是成员自⾝的大小

(3).结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
(4).如果嵌套了结构体的情况,嵌套的结构体对齐到自己成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

//练习1
struct S1
{char c1;int i;char c2;
};
printf("%d\n", sizeof(struct S1));//练习2
struct S2
{char c1;char c2;int i;
};
printf("%d\n", sizeof(struct S2));//练习3
struct S3
{double d;char c;int i;
};
printf("%d\n", sizeof(struct S3));//练习4-结构体嵌套问题
struct S4
{char c1;struct S3 s3;double d;
};
printf("%d\n", sizeof(struct S4));

思考一下大小各是多少呢?

练习1   12
练习2   8
练习3   16
练习4   32

2.为什么存在内存对齐

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

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

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

(2)性能原因:

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

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

//例如:
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};

S1S2 类型的成员一模一样,但是 S1S2 所占空间的大小有了一些区别。

3.修改默认对齐数

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

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{//输出的结果是什么?printf("%d\n", sizeof(struct S));return 0;
}

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

四、结构体传参

struct S
{int data[1000];int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{print1(s); //传结构体print2(&s); //传地址return 0;
}

上面的 print1print2 函数哪个好些?
答案是:首选print2函数。
原因:

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

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

五、结构体实现位段

结构体讲完就得讲讲结构体实现 位段 的能力。

1.什么是位段

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

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

比如:

struct A
{int _a:2;int _b:5;int _c:10;int _d:30;
};
printf("%d\n", sizeof(struct A));

A就是一个位段类型。
那位段A所占内存的大小是多少?

8

2.位段的内存分配

  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;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

空间是如何开辟的?
在这里插入图片描述

3.位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
    总结:
    跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

4.位段使用的注意事项

位段的几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的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.mzph.cn/news/765179.shtml

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

相关文章

Java字符串常量池

引言&#xff1a; 在Java编程中&#xff0c;字符串常量池一直是一个备受关注的话题。本文将从创建对象的思考、字符串常量池、再谈String对象创建等方面深入探讨Java字符串常量池。 一、创建对象的思考 在Java中&#xff0c;我们可以使用new关键字来创建对象&#xff0c;比如&a…

简单函数_素数对

任务描述 两个相差为2的素数称为素数对&#xff0c;如5和7&#xff0c;17和19等&#xff0c;本题目要求找出所有两个数均不大于n的素数对。输入格式: 一个正整数n。1 < n < 10000。输出格式: 所有小于等于n的素数对。每对素数对输出一行&#xff0c;中间用单个空格隔开…

设计模式 模板方法模式

01.如果接到一个任务&#xff0c;要求设计不同型号的悍马车 02.设计一个悍马车的抽象类&#xff08;模具&#xff0c;车模&#xff09; public abstract class HummerModel {/** 首先&#xff0c;这个模型要能够被发动起来&#xff0c;别管是手摇发动&#xff0c;还是电力发动…

39 openlayers 对接地图图层 绘制点线面圆

前言 这里主要是展示一下 openlayers 的一个基础的使用 主要是设计 接入地图服务器的 卫星地图, 普通的二维地图, 增加地区标记 增加 省市区县 的边界标记 基础绘制 点线面园 等等 测试用例 <template><div style"width: 1920px; height:1080px;" &g…

Rancher(v2.6.3)——Rancher部署Mysql(单机版)

[详细说明请查看Rancher部署Mysql说明文档]&#xff1a;https://gitee.com/WilliamWangmy/snail-knowledge/blob/master/Rancher/Rancher%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3.md#2rancher%E9%83%A8%E7%BD%B2mysql ps&#xff1a;如果觉得作者写的还行&#xff0c;能够满足您的…

nginx有哪些安装方法

Nginx 有多种安装方法&#xff0c;适用于不同的操作系统和使用场景。以下是几种常见的安装方式&#xff1a; 通过官方源码编译安装&#xff1a; 从Nginx官网下载源代码包。解压缩并进入源码目录。安装编译所需的依赖包&#xff0c;如pcre、zlib、openssl等。运行./configure配置…

前端canvas项目实战——简历制作网站(六):加粗、斜体、下划线、删除线(上)

目录 前言一、效果展示二、实现步骤1. 视图部分&#xff1a;实现用于切换字体属性的按钮2. 逻辑部分&#xff1a;点击按钮之后要做什么&#xff1f;3. 根据Textbox的属性实时更新按钮的状态 三、Show u the code后记 前言 上一篇博文中&#xff0c;我们实现了对文字的字体、字…

【系统设计】面试问题:设计 Spotify

目录 初始阶段:基础版本估计:数据计算高层设计**数据存储**SQL数据库结构把它们放在一起规模化阶段:5000 万用户、2 亿首歌曲引入 CDN扩展数据库:领导者-跟随者技术推荐超级课程: Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战这是一道系统设计面…

优化选址问题 | 基于灰狼算法求解基站选址问题含Matlab源码

目录 问题代码问题 灰狼优化算法(Grey Wolf Optimizer, GWO)是一种基于自然界中灰狼群体狩猎行为的优化算法。这种算法通过模拟灰狼的社会等级和狩猎行为来寻找问题的最优解。 基站选址问题通常是一个多目标优化问题,涉及到覆盖范围、信号质量、成本等多个因素。使用灰狼算…

量化交易入门(十一)Python开发-数据结构

Python提供了几种内置的数据结构,可以用来存储和组织数据。以下是Python中常见的数据结构:列表&#xff0c;元组&#xff0c;字典&#xff0c;集合&#xff0c;字符串&#xff0c;栈&#xff0c;队列&#xff0c;树&#xff0c;图。我们将介绍这些数据结构&#xff0c;并举例说…

使用JMeter从JSON响应的URL参数中提取特定值

在使用Apache JMeter进行API测试时&#xff0c;我们经常需要从JSON格式的响应中提取特定字段的值。这可以通过使用JMeter内置的JSON提取器和正则表达式提取器来完成。以下是一个具体的例子&#xff0c;展示了如何从一个JSON响应中提取rowId的值&#xff0c;同时处理字符串终止符…

C#使用Poll/Select实现多路I/O复用

在实际的应用中&#xff0c;如果全部采用异步的操作来&#xff0c;会增加代码的复杂程度&#xff0c;某些时候使用Poll/Select来实现单线程多路的I/O复用会更合适一些 一、Poll 原型函数 public bool Poll ( int microSeconds, SelectMode mode ) 1&#xff1a;客户端 pri…

web服务架构

1 Web服务器&#xff08;如Nginx、Apache等&#xff09;和Web应用框架&#xff08;如Flask、Django等&#xff09; Web服务器&#xff08;如Nginx、Apache等&#xff09;和Web应用框架&#xff08;如Flask、Django等&#xff09;在Web应用开发和部署中扮演着不同的角色&#xf…

python turtle库简单应用题

三角形 import turtle for i in range(3):turtle.seth(i * 120)turtle.fd(100) turtle.done()矩形 import turtle angle 90 for x in range(4):turtle.fd(100)turtle.seth(angle)angle 90 turtle.done()五边形 import turtle angle 360/5 for x in range(5):turtle.fd(10…

软考中级 --网络工程师真题试卷 2023下半年

在EIGRP协议中&#xff0c;某个路由器收到了两条路径到达目标网络&#xff0c;路径1的带宽为100Mbps&#xff0c;延迟2ms&#xff0c;路径2的带宽为50Mbps&#xff0c;迟为4ms&#xff0c;如果EIGRP使用带宽和延迟的综合度量标准&#xff0c;那么该路由器选择的最佳路径是(D)。…

Codeforces Round 930 (Div. 2)(A,B,C,D)

比赛链接 C是个交互&#xff0c;D是个前缀和加二分。D还是很难写的。 A. Shuffle Party 题意&#xff1a; 您将得到一个数组 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1​,a2​,…,an​ 。最初&#xff0c;每个 1 ≤ i ≤ n 1 \le i \le n 1≤i≤n 对应 a i i a_ii…

你用对const了吗?C++中const小结

const 修饰普通变量 表示变量的值不能被改变。下面两条语句(第2行和第3行)表示的意思一致。 int a; const int ca 42; //int const ca 42;const 修饰指针 指向常量的指针不能改变其指对象的值。第 5 行代码是错误的。 int a 42;const int * ip &a; int const * ipp…

win10 禁止谷歌浏览器自动更新(操作贼简单)

禁止谷歌浏览器自动更新 &#xff08;1&#xff09;修改 "C:\Windows\System32\drivers\etc\hosts 文件&#xff0c;在最后增加 127.0.0.1 update.googleapis.com&#xff08;2&#xff09;保存后&#xff0c;winr 快捷键&#xff0c;输入cmd &#xff0c;打开命令行 &am…

RK3588 rknpu2及rknn-toolkit2使用说明

RKNN模型推理共有四种方式&#xff1a; 第一种是借助RKNN-Toolkit2的功能在模拟NPU上运行RKNN模型并获取推理结果&#xff08;在PC端&#xff09; 第二种是借助RKNN-Toolkit2的功能, 将板子与PC连接&#xff0c;将RKNN模型分发到指定的NPU设备进行推理并获取推理结果&#xff0…

AJAX踩坑指南(知识点补充)

JWT JSON Web Token是目前最为流行的跨域认证解决方案 如何获取&#xff1a;在使用JWT身份验证中&#xff0c;当用户使用其凭据成功登录时&#xff0c;将返回JSON Web Token(令牌&#xff09; Token本质就是一个包含了信息的字符串 如何获取Token:登录成功之后&#xff0c;服务…