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

自定义类型:结构体

  • 1.结构体类型的声明
    • 1.1结构体回顾
      • 1.1.1结构体的声明
      • 1.1.2结构体变量的创建和初始化
    • 1.2结构体的特殊声明
    • 1.3结构体的自引用
  • 2.结构体内存对齐
    • 2.1对齐规则
    • 2.2为什么存在内存对齐
    • 2.3修改默认对齐数
  • 3.结构体传参
  • 4.结构体实现位段
    • 4.1什么是位段
    • 4.2位段的分配
    • 4.3位段的跨平台
    • 4.4位段的应用
    • 4.5位段使用的注意事项

1.结构体类型的声明

1.1结构体回顾

结构体是一些值的集合,这些值被称为成员变量。结构的每个成员可以是不同的类型的变量。

1.1.1结构体的声明

struct MyStruct
{member - list;
}variable-list;

例如描述一个学生:

struct stu
{char name[20];//姓名int age;//年龄char sex;//性别char id[20];//学号
};//分号不能丢

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


#include <stdio.h>//描述一个学生:
struct stu
{char name[20];//姓名int age;//年龄char sex[10];//性别char id[20];//学号
};int main()
{//按照结构体的顺序初始化struct stu s1 = { "zhangsan", 20, "男", "2072855200" };printf("name: %s\n", s1.name);printf("age : %d\n", s1.age);printf("sex : %s\n", s1.sex);printf("id  : %s\n", s1.id);//按照指定的顺序初始化struct stu s2 = { .age = 20, .id = "2072855200", .name = "zhangsan", .sex = "男" };printf("name: %s\n", s2.name);printf("age : %d\n", s2.age);printf("sex : %s\n", s2.sex);printf("id  : %s\n", s2.id);return 0;
}

1.2结构体的特殊声明

在生命结构的时候,可以不完全声明。

比如:

struct 
{int a;char c;float f;
}x;struct 
{int a;char c;float f;
}a[20], *p;

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

那么问题来了?

//上面的代码合法吗?
p = &x;

警告:

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

1.3结构体的自引用

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

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

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类型来创建成员变量,这是不行的。

2.结构体内存对齐

讨论一个问题:计算结构体的大小。

2.1对齐规则

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

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

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

  1. 结构体总大小为最大对齐数(结构体中的每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
  2. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(韩嵌套结构体成员的对齐数)的整数倍。
#include <stdio.h>//练习1:
struct S1
{char c1;int i;char c2;
};//练习2:
struct S2
{char c1;char c2;int i;
};//练习3:
struct S3
{double d;char c;int i;
};//练习4:
struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%zd\n", sizeof(struct S1));printf("%zd\n", sizeof(struct S2));printf("%zd\n", sizeof(struct S3));printf("%zd\n", sizeof(struct S4));return 0;
}

2.2为什么存在内存对齐

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

总的来讲:结构体的对其实拿空间来换取时间的做法。

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

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

//例如:
#include <stdio.h>struct S1
{char c1;int i;char c2;
};struct S2
{char c1; char c2;int i;
};int main()
{printf("%zd\n", sizeof(struct S1));printf("%zd\n", sizeof(struct S2));return 0;
}

S1和S2类型的成员一摸一样,但是S1和S2所占用空间的大小有了一些区别。

2.3修改默认对齐数

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

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

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

3.结构体传参

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

上面的print1print2函数那个更好?

答案是:首选print2函数。

原因:

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

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

4.结构体实现位段

4.1什么是位段

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

  1. 位段的成员必须是 intunsigned intsigned int,在C99中位段成员的类型也可以选择其他类型。
  2. 位段的成员后面有一个冒号和一个数字

比如:

struct A
{int _a : 2;//单位是字节int _b : 3;int _c : 4;int _d : 30;
};

A就是一个位段类型。

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

int main()
{printf("%zd\n", sizeof(struct A));//8return 0;
}

4.2位段的分配

  1. 位段的成员可以是intunsigned intsigned int或者是char等类型。
  2. 位段的空间是按照以4个字节(int)或者1个字节(char)的方式来开辟的。
  3. 位段涉及到很多不确定的因素,位段是不跨平台的,注意可移植的程序应该避免使用位段。
//一个例子:
#include <stdio.h>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,写成17机器会出问题)。
  3. 位段中的成员在内存中从左向右分配还是从右向左分配,标准未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位段时,是舍弃剩余的位段还是利用,这是不确定的。

总结:

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

4.4位段的应用

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

4.5位段使用的注意事项

  • 位段的几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址一个字节内部的bit位是没有地址的。
  • 所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能先输入放在一个变量中,然后赋值给位段的成员。
#include <stdio.h>struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};int main()
{struct A sa = { 0 };//scanf("%d", &sa.a);//这是错误的//先输入一个值放到一个变量中,然后赋值给位段的成员。int b = 0;scanf("%d", &b);sa.b = b;return 0;
}

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

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

相关文章

深入浅出DBus-C++:Linux下的高效IPC通信

目录标题 1. DBus简介2. DBus-C的优势3. 安装DBus-C4. 使用DBus-C初始化和连接到DBus定义接口和方法发送和接收信号 5. dbus-cpp 0.9.0 的安装6. 创建一个 DBus 服务7. 客户端的实现8. 编译和运行你的应用9. 瑞芯微&#xff08;Rockchip&#xff09;的 Linux 系统通常会自带 db…

OpenLayers入门①(引入的是一个高德地图)

OpenLayers入门&#xff08;一&#xff09; - 知乎 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport&qu…

使用RTSP将笔记本摄像头的视频流推到开发板

一、在Windows端安装ffmpeg 1. 下载ffmpeg:下载ffmpeg 解压ffmpeg-master-latest-win64-gpl.zip bin 目录下是 dll 动态库 , 以及 可执行文件 ;将 3 33 个可执行文件拷贝到 " C:\Windows " 目录下 ,将所有的 " .dll " 动态库拷贝到 " C:\Windows\Sy…

期权交割对股市是好是坏?2024期权交割日一览表

期权交割是指期权买方在期权合约到期日或之前行使期权&#xff0c;卖方履行义务&#xff0c;按照约定的价格和数量与期权卖方进行标的物的买卖或现金结算的过程。 交割方式 期权交割可以分为实物交割和现金交割&#xff0c;具体取决于合约规定。 实物交割 实物交割是指期权买…

【深度学习基础(1)】什么是深度学习,深度学习与机器学习的区别、深度学习基本原理,深度学习的进展和未来

文章目录 一. 深度学习概念二. 深度学习与机器学习的区别三. 理解深度学习的工作原理1. 每层的转换进行权重参数化2. 怎么衡量神经网络的质量3. 怎么减小损失值 四. 深度学习已取得的进展五. 人工智能的未来 - 不要太过焦虑跟不上 一. 深度学习概念 先放一张图来理解下人工智能…

Vue阶段练习:初始化渲染、获取焦点

阶段练习主要承接Vue 生命周期-CSDN博客 &#xff0c;学习完该部分内容后&#xff0c;进行自我检测&#xff0c;每个练习主要分为效果显示、需求分析、静态代码、完整代码、总结 四个部分&#xff0c;效果显示和准备代码已给出&#xff0c;我们需要完成“完整代码”部分。 练习…

MySQL__三大日志

文章目录 &#x1f60a; 作者&#xff1a;Lion J &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_69252724 &#x1f389; 主题&#xff1a;Redis__三大日志 ⏱️ 创作时间&#xff1a;2024年04月30日 ———————————————— 对于MySQL来说, 有…

Gateway Predicate断言(谓词)

是什么 Spring Cloud Gateway匹配路由作为Spring WebFlux HandlerMapping基础设施的一部分。 Spring Cloud Gateway包含许多内置的路由谓词工厂。 所有这些谓词都匹配HTTP请求的不同属性。 您可以使用逻辑 and 语句来联合收割机组合多个路由谓词工厂。 Predicate就是为了实现一…

基于EBAZ4205矿板的图像处理:03使用VIO调试输出HDMI视频图像

基于EBAZ4205矿板的图像处理&#xff1a;03使用VIO调试输出HDMI视频图像 在zynq调试时VIO是真的方便&#xff0c;特此写一篇博客记录一下 先看效果 项目简介 下面是我的BD设计&#xff0c;vtc用于生成时序&#xff0c;注意&#xff0c;2021.2的vivado的vtcIP是v6.2版本&…

Stm32CubeMX 为 stm32mp135d 添加 adc

Stm32CubeMX 为 stm32mp135d 添加 adc 一、启用设备1. adc 设备添加2. adc 引脚配置2. adc 时钟配置 二、 生成代码1. optee 配置 adc 时钟和安全验证2. linux adc 设备 dts 配置 bringup 可参考&#xff1a; Stm32CubeMX 生成设备树 一、启用设备 1. adc 设备添加 启用adc设…

JAVA前端快速入门基础_javascript入门(03)

写在前面:本文用于快速学会简易的JS&#xff0c;仅做扫盲和参考作用 本章节主要介绍JS的事件监听 1.什么是事件监听 事件:是指发生在HTML端的事件&#xff0c;主要指以下几种。 1.按钮被点击 2.鼠标移动到元素上 3.按到了键盘 事件监听:当触发了事件时&#xff0c;JS会执行相…

vue3、element-plus递归实现动态菜单

vue3、element-plus递归实现动态菜单 使用场景&#xff1a;动态菜单为什么使用递归递归在动态菜单中的实现 使用场景&#xff1a;动态菜单 动态菜单是指菜单项的数量和层次结构可能是动态的&#xff0c;通常来自后端或用户输入。这些菜单的特征包括&#xff1a; 多层嵌套&…

【webrtc】MessageHandler 3: 基于线程的消息处理:以sctp测试为例

消息处理可以用于模拟发包处理G:\CDN\rtcCli\m98\src\net\dcsctp\socket\dcsctp_socket_network_test.cc 这个实现中,onMessage还是仅对了一种消息进行处理,就是接收则模式下,打印带宽。当然,可能程序有多个消息,分别在不同的onmessage中执行?SctpActor:以一个恒定的速率…

【大语言模型LLM】-基于ChatGPT搭建客服助手(1)

&#x1f525;博客主页&#xff1a;西瓜WiFi &#x1f3a5;系列专栏&#xff1a;《大语言模型》 很多非常有趣的模型&#xff0c;值得收藏&#xff0c;满足大家的收集癖&#xff01; 如果觉得有用&#xff0c;请三连&#x1f44d;⭐❤️&#xff0c;谢谢&#xff01; 长期不…

利用大型语言模型提升个性化推荐的异构知识融合方法

在推荐系统中&#xff0c;分析和挖掘用户行为是至关重要的&#xff0c;尤其是在美团外卖这样的平台上&#xff0c;用户行为表现出多样性&#xff0c;包括不同的行为主体&#xff08;如商家和产品&#xff09;、内容&#xff08;如曝光、点击和订单&#xff09;和场景&#xff0…

C++ 堆结构和堆排序(从顶到底/从底到顶的大顶堆)+ 优化

一、堆结构和堆排序 &#xff08;1&#xff09;heapInsert&#xff0c;向上调整大根堆 和 heapify&#xff0c;向下调整大根堆 // i位置的数&#xff0c;向上调整大根堆 // arr[i] x&#xff0c;x是新来的&#xff01;往上看&#xff0c;直到不比父亲大&#xff0c;或者来到0…

使用Gradio搭建聊天UI实现质谱AI智能问答

一、调用智谱 AI API 1、获取api_key 智谱AI开放平台网址&#xff1a; https://open.bigmodel.cn/overview 2、安装库pip install zhipuai 3、执行一下代码&#xff0c;调用质谱api进行问答 from zhipuai import ZhipuAIclient ZhipuAI(api_key"xxxxx") # 填写…

短视频交友系统搭建重点,会用到哪些三方服务?

在搭建短视频交友系统时&#xff0c;为了确保系统的稳定性、安全性和用户体验&#xff0c;通常需要用到多种第三方服务。以下是搭建短视频交友系统时可能用到的关键第三方服务&#xff1a; 云服务提供商&#xff1a;如阿里云、腾讯云等&#xff0c;提供稳定、可扩展的服务器资源…

如何消除SmartScreen“未知发布者”警告?

在互联网高速发展、应用程序遍地开花的当今时代&#xff0c;作为企业&#xff0c;我们通常会开发自己的应用程序来开展自己的业务&#xff0c;以便与客户建立更深入的联系。不少应用程序所有者可能会面临一个难题&#xff0c;那就是用户下载时&#xff0c;系统会弹出SmartScree…

nuxt3项目服务端bulid后在本地浏览的3种方式(nuxi preview、Node.js Server、PM2)

你也许会问有了开发调试本地浏览&#xff0c;为什么还要服务端构建之后在本地浏览&#xff1f; 举个简单例子 在 Nuxt 3 服务端打包中&#xff0c;由于运行环境不同&#xff0c;无法直接访问 process 对象。服务端打包通常是在 Node.js 环境中进行的&#xff0c;而 process 对象…