【C语言】结构体

2023-11-18

结构体

结构体类型的声明

1)结构体的基本概念:结构是一些值的集合,这些值被称为成员变量。结构的每个成员可以是不同类型的变量。
2)结构的声明:

struct tag//tag:结构体标签
{
    number - list;//成员列表
}valiable - list;//变量列表

例如描述一本书:

struct Book
{
    char name[20];//书名
    float price;//价格
    int year;//出版时间
};//分号不能丢    

3)特殊的声明:在声明结构的时候,可以不完全声明。比如:

//匿名结构体类型
struct
{
	int a;
	float b;
	char c;
}s1;

struct
{
	int a;
	float b;
	char c;
}s2, *p;

上面两个结构体在声明的时候省略掉了结构体标签(tag)。那么问题来了,
在上述基础上,下面这个代码合理吗?

int main()
{
	*p = &s1;
	return 0;
}

编译发现是不能通过的:
在这里插入图片描述
这是因为编译器会将上面两个声明当成完全不同的两个类型,因此是非法的。在使用匿名结构体时,需要在声明的同时定义变量,故使用匿名结构体时需谨慎。

结构体的自引用

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

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

如果上述代码可行,那么sizeof(struct Node)的大小是多少呢?
不难看出该结构体在类似递归的不断延伸,像套娃一样,其大小将是无穷大。在实际编译过程中,编译器也会对这段代码报错:在这里插入图片描述
在声明结构体时,结构体并未被定义完成,因此若在结构中包含一个类型未该结构本身的变量时不可行的。
正确的结构体自引用方式是利用结构体指针:

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

这种方式在数据结构中的链表将会被大量使用。
注意区分一下两种代码:

//代码1
typedef struct Node
{
	int data;
	Node* next;
}Node;

//代码2
typedef struct Node
{
	int data;
	struct Node* next;
}Node;

代码1在编译过程中是无法通过的:在这里插入图片描述
这是因为在定义next变量时,将struct Node重命名为Node这一操作还未完成,故编译器无法辨识Node类型,因此无法通过,在实际使用过程中需注意这一点。

结构体变量的定义和初始化

声明了结构体类型后,结构体变量的定义和初始化就变得很简单了。
1)定义结构体变量

struct Point
{
	int x;
	int y;
}p1;//声明结构体的同时定义变量
struct Point p2;//定义结构体变量p2

2)初始化结构体变量

struct Point p3 = { 1,2 };//定义的同时赋初值

struct Node
{
	int data;
	struct Point p;
	struct Node* next;
}n1 = { 3, {7,8}, NULL }; //结构体嵌套初始化

struct Node n2 = {5, {6, 9}, NULL};//结构体嵌套初始化

结构体内存对齐

计算结构体大小

掌握了结构体的基本使用后,接下来讨论计算结构体的大小。先思考一下以下几个结构体的大小是多少:

//结构体1
struct s1
{
	char c1;
	int i;
	char c2;
};
//结构体2
struct s2
{
	char c1;
	char c2;	
	int i;
};
//结构体3
struct s3
{
	double d;
	char c1;
	int i;
};
//结构体4
struct s4
{
	char c;
	struct s3 s;
	long x;
};

在此之前先来了解一下结构体的内存对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    (VS中默认的值为8,Linux没有默认对齐数)
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
    如何理解上述规则呢?我们通过画图来理解结构体在内存中的存储
    在这里插入图片描述

结构体内存对齐的意义

我们可以看到结构体内存对齐的过程中存在这内存的浪费,那么为什么会存在结构体对齐呢?
大部分的参考资料都是如是说的:
1)平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2)性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要访问一次。
什么意思呢?如下图:
在这里插入图片描述
假设编译器以4个字节读取内存。对于内存对齐的结构体,编译器只需要对结构体读取一次内存即可读取所有数据;而对于为对齐内存的结构体,编译器则需要对结构体读取两次才能读取所有数据,故结构体的内存对齐使得编译效率提高。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
因此,在实际设计结构体的时候,我们既要满足对齐,又要节省空间,因此,尽量将占用空间小的成员变量集中在一起,比如结构体1和结构体2,显然结构体2更好一些。

修改默认对齐数

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

#pragma pack(2)//修改默认对齐数为2
struct s5
{
	char c1;
	long i;
	char c2;
};

#pragma pack()//取消修改的默认对齐数,还原为默认

int main()
{
	printf("%d\n", sizeof(struct s5));
	return 0;
}

默认对齐数修改为2,对于结构体s5,c1在偏移量为0的位置,i在偏移量为较小对齐数2的位置,c2在偏移量为6的位置,结构体s5的总大小为2的整数倍8.
在这里插入图片描述
因此,在结构体的对齐方式不合适的时候,我们可以修改默认对齐数。

结构体传参

我们知道函数传参有两种形式,一种是传值,一种是传址。而对于结构体而言,传参最好是传址。因为函数传参是对参数的一份临时拷贝,如果传址那么传的内存只有4或8个字节,而传值的话,若结构体本身很大,那么临时拷贝后占用的内存也很大。因此, 结构体传参的时候,要传结构体的地址。

结构体实现位段

介绍完结构体后就得介绍一下结构体实现位段的能力。

什么是位段

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

  1. 位段的成员必须是int,unsigned int或signed int或char类型。
  2. 位段的成员名后有一个冒号和一个数字。
    比如:
struct A
{
	int a : 5;
	int b : 7;
	int c : 12;
	int d : 28;
};

A就是一个位段类型,那么位段A的大小是多少呢?
在这里插入图片描述

为什么A的大小是8个字节呢?这就要知道位段成员后的数字是什么含义了:由于一个整型变量的大小是4个字节,不难猜出位段成员后的数字应该指的是该成员在内存中所占用的大小,单位是bit。

位段的内存分配

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
    这是什么意思呢?举个例子来说:
struct Test
{
	char a : 2;
	char b : 3;
	char c : 4;
	char d : 5;
};

int main()
{
	struct Test t = { 1,2,3,4 };
	return 0;
}

如果说位段成员在内存中是从高地址往低地址存储的,那么结构体变量t在内存中的存储应该如下:
在这里插入图片描述

调试一下发现:
在这里插入图片描述
结果与我们预期的相同
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。这是因为:
a)int位段有符号数还是无符号数是不确定的
b)位段中最大位的数目不能确定(16位机器中最大16,32位机器中最大32)
c)位段的成员在内存中是从左向右分配还是从右向左分配标准尚未定义
d)当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

  总结:位段与结构相比,节省了空间,但是存在跨平台的问题。

位段的应用

在实际生活中,我们可以将一些本就不大的信息压缩以节省空间,就像下图:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C语言】结构体 的相关文章

  • VMware 15 安装ubuntu 18.04的总结

    换了新电脑 想安装了VMware12 0版本的 结果发现安装包和我现在的电脑并不匹配 所以下载了较新版本的vmware 15 5 网上找到对应版本的序列号注册 在安装完成后激活即可 emsp 从Linux公社的文章 Ubuntu 18 04
  • 一个开源项目维护者的笔记:为什么我关闭 PR

    原文出处 jeffgeerling 译文出处 oschina 局长 我在 GitHub 上和其他地方维护着许多的开源项目 截止本文写作时超过 160 个 在过去几年里 我已经合并 以及 或者 关闭了上千个 Pull Requests PRs
  • Linux 下面编译内核模块的 方法

    1 选择好你的内核版本 内核模块经常是和内核版本紧密相关的 建议尽量选择高内核的版本 否则有可能会遇到很大的问题 2 下载内核代码到 usr src kernel 目录 如果有了 则不用下载 3 mkdir p lib modules 3

随机推荐

  • [FPGA系列] 扩展知识 --- 时钟小结

    一 基本概念 时钟域 由同一个时钟信号控制的区域 时钟抖动 Jitter 相对于理想时钟信号 实际时钟信号存在时而超前 时而之后的偏移 时钟偏斜 Skew 时钟信号到达数字电路各个部分所用时间的差异 时钟漂移 Wander 工程上解释 抖动
  • Day【10】相交链表

    原题链接 思路 代码 public class Solution public ListNode getIntersectionNode ListNode headA ListNode headB if headA null headB n
  • Typora的安装与配置

    Markdown对于码农来讲有多重要就不多说了吧 那么Typora作为一款极好用的生产工具 它的重要性也不言而喻了吧 Typora简介 Typora 是一款支持实时预览的 Markdown 文本编辑器 风格极简 多种主题 支持 macOS
  • Linux操作系统

    一 Linux系统概述 1 1Linux操作系统的组成 Linux操作系统是由Linux系统内核和系统级应用程序两部分组成的 1 内核提供系统核心的功能 如调度CPU 调度内存 调度系统文件 调度网路通讯 调度IO 2 系统级应用程序可以理
  • 【PythonCode】这些题,不会做就选D

    PythonCode 这些题 不会做就选D 前言 在很多高大上的项目中 一个花费很长时间 消耗大量人力物力才查出来的BUG 经常是一个符号错误 一个值传错 一个基本函数的用法没有考虑周到等基本问题 所以基础不牢 地动山摇 真的不是开玩笑 2
  • Qt信号槽自动关联的用法

    Qt除使用connect方法显式的关联信号与槽 还可以通过自动关联机制实现信号与槽函数的连接 自动关联的槽函数命名规则 void on lt 窗口部件名称 gt lt 信号名称 gt lt 信号参数 gt 实现信号与槽函数关联的步骤 1 在
  • Android 安全机制(1)uid 、 gid 与 pid

    1 概述 Android 安全机制来源于Linux 并且以Linux权限管理为基础 要了解Android的安全机制 需要从linux中的安全机制了解开始 而用户的权限管理又是linux安全机制的最基本的一个组成 Android的创新之处是在
  • KVM网卡模式(初探KVM)

    最近在看 KVM虚拟化技术实战与原理解析 这本书 略读了前四章后 开始动手创建并安装一个虚拟机 然后发现一个很严重的问题 就是我没有办法通过ssh连接到虚拟机 linux 也没有办法通过mstsc连到到虚拟机 windows 因为我在创建虚
  • oracle索引监控

    oracle索引是加快查询速度 减少i o操作的 但是如果索引无用的话那他就是浪费资源的 尤其是在做DML操作时 这是 就需要我们查看那些索引是无用的 相关视图 user indexesuser ind columnsv object us
  • java学习03-程序执行三大结构

    一 顺序执行语句 程序运行时 按顺序从上往下执行 二 分支语句 分支运算 if else if 条件 if 条件 else 虽然多个语句块 但是只有一个执行 if 条件 else if 条件 else switch case 不能嵌套 sw
  • 报错Attribute "path" is required and must be specified for element type

    Attribute path is required and must be specified for element type 最近搞一个项目总遇到这个或者类似的问题 其实问题很简单 其实就是batis和MyBatis之间有区别 就是在
  • java8 stream findfirst().get()空指针

    java8 stream findfirst get 空指针 List
  • 【STM32CUBE+IAR+IAP升级】

    STM32CUBE IAR IAP升级 案例应用 利用IAR 串口调试助手对STM32F411VET6进行简单的IAP实现 代码分为两部分 BootLoader APP翻转LED 1 工具 IAR STM32CUBEMX 串口调试助手 IA
  • ResultSet的getDate()、getTime()和getTimestamp()比较

    最近在做一个项目 发现个知识点 记录一下 数据库中存储时间格式为2021 8 11 16 09 28 现在要从ResultSet中取出时间 当然是要包含年月日时分秒的 发现 rs getDate 只是返回日期部分 只精确到天 java sq
  • el-image因src路径问题加载失败

    载入图片目录路径如下 起初 我将路径这样写
  • CDH多租户配置过程中遇到的问题

    多租户是CDH里面非常重要的一部分 从一开始配置KDC到集成KDC 服务使用过程中都有可能会遇到各种各样的问题 下面我举例说下我当时遇过的问题 希望能帮助到大家 服务启动错误 KDC服务配置完成安装完成 CDH集成过程中也没问题 CDH启动
  • WebSocket断开原因分析,再也不怕为什么又断开了

    阅读原文 https wdd js org websocket 1 把错误打印出来 WebSocket断开的原因有很多 最好在WebSocket断开时 将错误打印出来 在线demo地址 https wdd js org websocket
  • 【IP协议(一)】——IP数据报格式及其含义,IP数据报的切分

    个人主页 努力学习的少年 版权 本文由 努力学习的少年 原创 在CSDN首发 需要转载请联系博主 如果文章对你有帮助 欢迎关注 点赞 收藏 一键三连 和订阅专栏哦 IP数据报格式 版本 占4位 指ip协议的版本 首部长度 表示IP数据报中报
  • 短视频矩阵源码

    随着短视频平台的快速发展 越来越多的企业开始关注短视频营销 而矩阵号运营逐渐成为了企业进行短视频营销的常规玩法 那么 矩阵账号如何运作 如何进行短视频矩阵号运营 一 矩阵号怎么搭建 1 选择短视频平台 首先 根据自己企业的特点和目标用户群体
  • 【C语言】结构体

    目录 结构体 结构体类型的声明 结构体的自引用 结构体变量的定义和初始化 结构体内存对齐 计算结构体大小 结构体内存对齐的意义 修改默认对齐数 结构体传参 结构体实现位段 什么是位段 位段的内存分配 位段的应用 结构体 结构体类型的声明 1