C语言——自定义类型——结构体(从零到一的跨越)

目录

前言

1.什么是结构体

2.结构体类型的声明

2.1结构体的声明

2.2结构体的创建和初始化

2.3结构成员访问操作符

2.3.1结构体成员直接访问

2.3.2结构体成员的间接访问

2.4结构体变量的重命名

2.5结构体的特殊声明

2.6结构的自引用

3.结构体内存对齐

3.1对齐规则

3.2为什么存在内存对齐

3.3修改默认对齐数

4.结构体传参

5.结构体实现位段

5.1位段的声明

5.2位段的内存分配

5.3位段的注意事项



前言

          在学习结构体之前,我们还学习了char ,int,short,float,double等内置类型,他们可以描述一些事物的某一项属性,但是如果我想描述一本书的某些属性,而不是单单的一种属性,该怎么办呢?这时候就会用到自定义类型——结构体,我们就可以创造出属于我们自己的类型。

1.什么是结构体

           结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量、数组、指针,甚至是其他结构体。也就是说结构体是由一些内置类型构成的,这些类型表示事物的某些属性。

2.结构体类型的声明

2.1结构体的声明

struct book
{char name[100];//书名int price;//价格char author[100];//作者
};//分号不能丢

struct是结构体的标志,book是我们自定义的结构体的名字,{}里面的是成员变量

2.2结构体的创建和初始化

按照顺序初始化struct book b1 = { "红心照耀中国",100,"埃德加·斯诺" };指定顺序初始化struct book b2 = { .author = "卡尔·马克思、弗里德里希·恩格斯",.name = "共产党宣言",.price = 50 };

struct book b1和struct book b2是结构体的创建,后面的{}是对这个结构体进行初始化,struct book相当于之前学习的char和int,b1和b2就是变量名。

以上两种是在声明结构体之后进行的初始化,除此之外还可以在声明结构体的时候进行初始化 ,就像这两种方法这样

	struct book
{char name[100];int price;char author[100];
}b1 = { "红心照耀中国",100,"埃德加·斯诺" };struct book
{char name[100];int price;char author[100];
}b2 = { .author = "卡尔·马克思、弗里德里希·恩格斯",.name = "共产党宣言",.price = 50 };

结构体变量的创建和初始化还可以分开进行

比如这个样子:

#include<stdio.h>
struct book
{char name[100];int price;char author[100];
}x;//创建变量int main()
{struct book x = { "红星照耀中国",100,"埃德加·斯诺" };//初始化return 0;
}

2.3结构成员访问操作符

结构体变量创建出来是为了使用的,那他会不会像int,double等类型的使用方法一样呢,关于结构体的使用,这里鱼哥给大家介绍两个操作符(.)结构体成员直接访问,(->)结构体成员简介访问

2.3.1结构体成员直接访问

#include<stdio.h>
struct book
{char name[100];int price;char author[100];
};
int main()
{struct book b1 = { "红心照耀中国",100,"埃德加·斯诺" };printf("%s\n", b1.name);printf("%d\n", b1.price);printf("%s\n", b1.author);
}

使用方法:变量名.成员名

2.3.2结构体成员的间接访问

#include<stdio.h>
struct book
{char name[100];int price;char author[100];
}b1 = { "红心照耀中国",100,"埃德加·斯诺" };
int main()
{
struct book *ptr = &b1;
ptr->price = 10;printf("%d\n", b1.price);return 0;
}

使用方法:结构体指针->成员名

2.4结构体变量的重命名

typedef struct book
{char name[100];int price;char author[100];
}Book;
使用typedef对struct book进行重命名,Book就相当于原来的struct book,也就是他们两个的效果是一样的

2.5结构体的特殊声明

struct 
{char name[100];int price;char author[100];
}x;

上面这种声明属于匿名结构体声明,也就是在结构体声明的时候省略了标签(book)

注意:匿名结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次

2.6结构的自引用

结构体出来能装内置类型,可不可以装结构体呢?

struct book2
{int x;int y;
};struct book
{char name[100];int price;char author[100];struct book2 x1;
};

这样写是OK的,我们可以算出他所占内存空间的大小

能装别的结构体不算nb,如果他能装自己才叫nb

 struct book
{char name[100];int price;char author[100];struct book x1;
};

那么这样写是正确的吗?如果正确,那么他所占内存空间的大小是多少?

经过分析,这样写结构体会无限嵌套下去,这样是无法计算出结构体的大小,他的大小就会无穷大,所以这样的写法是错误的

那么有没有正确的写法呢?答案是有的

 struct book
{char name[100];int price;char author[100];struct book *next;
};

我们可以这样写,既然结构体不能嵌套结构体,那我结构体装结构体指针,通过结构体指针找到结构体


在结构体自引用使用的过程中,掺杂着typedef对匿名结构体类型的重命名,也是容易出现问题的

typedef struct
{
int x;
Book*next;
}Book;

这样的写法也是不行的,因为Book是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Book来创建成员变量,这是不行的

3.结构体内存对齐

3.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));

这个结构体的大小是多少?大家可以自己先算一算。

答案是12,怎么算出来的呢

有人会想,c1一个字节,i四个字节,c2两个字节,应该6个字节才对啊

数字代表偏移量

c1是一个字节,并且要对齐到偏移量为0的地址处

i是四个字节,4小于8,所以对齐数是4,所以i要存储到4的整数倍处

c2是一个字节,对齐数是1,所以c2存到偏移量为8的地址处

最大对齐数是4,所以结构体的总大小是4的倍数,而c1,i,c2占9个字节,所以结构体的大小要大于等于9并且是4的倍数,所以这个结构体的大小是12个字节

 例2.

struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));

 这个结构体的大小是8,你会想,这个结构体和例1的成员变量都一样,只是位置不同,凭什么这个的内存就要小一点呢?

c1从偏移量为0的地址处开始存储

c2的对齐数是1,偏移量1是1的倍数,所以1处存c2

i的对齐数是4,所以i从4的位置开始存储

c1,c2,i占8个字节,而最大对齐数是4,8是对齐数的整数倍,所以这个结构体的大小是8个字节

例3.

struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));

这个结构体的大小是16,有了前面两个例子,鱼哥相信你应该可以算出结果

d从0开始存储,字节大小是8

c的对齐数是1,存储在偏移量为8的位置

i的对齐数是4,存储在偏移量为12的位置

d,c,i占的字节数为16,16又是最大对齐数8的整数倍,所以结构体的大小为16个字节

例4.

struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));

s3是例3的结构体,这个s4结构体的大小是32

c1从偏移量为0开始

s3的最大对齐数是8,所以s3从偏移量为8的位置开始存储,因为例3已经算出了s3的大小,所以s3存16个字节

d的对齐数是8,24是8的倍数,所以d从24开始存储

c1,s3,d占32个字节,32又是8的整数倍,所以s4的大小是32个字节

3.2为什么存在内存对齐

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

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

2.性能原因:

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

总的来说:结构体的内存对齐是拿空间换时间 

对例1和例2分析,我们发现如果让占用空间小的成员尽量集中在一起可以起到节省空间的效果

3.3修改默认对齐数

VS的编译器的默认对齐数是8,在某些情况下,我们觉得默认对齐数不合适,想要修改,就会用到#pragma这个预处理指令

#include<stdio.h>
#pragma pack(2)
struct S
{char c1;int a2;char c2;
};
int main()
{printf("%d\n", sizeof(struct S));return 0;
}

这里将默认对齐数改为2,大家可以通过前面的学习自己算一下答案

学会修改也要学会恢复,怎么做呢?看接下来的代码

#include<stdio.h>
#pragma pack(1)
struct S
{char c1;int a2;char c2;
};
#pragma pack()
int main()
{printf("%d\n", sizeof(struct S));return 0;
}

4.结构体传参

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

上面这个代码进行了两个结构体的传参,那么哪一种传参更好一点呢?

答案是print2的传参更好一点

原因是:

函数传参的时候,参数需要压栈,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降

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

5.结构体实现位段

5.1位段的声明

struct S
{
int a:2;
int c:3;
int f:24;
};

上述代码就是一个简单的位段声明,是不是感觉和结构体比较像

那么他和结构体有什么区别呢?

1.位段的成员必须是int,unsigned int,signed int (整形家族的),在C99中也可以是其他类型

2.位段的成员名后面是一个冒号加一个数字(这个数字代表比特位数)

5.2位段的内存分配

位段的空间上是按照4个字节或者1个字节的方式来开辟的

#include<stdio.h>struct S
{char a : 2;char b : 4;char c : 5;char d : 6;
};
struct S s = { 0 };
int main()
{s.a = 10;s.b = 12;s.c = 13;s.d = 14;printf("%d", sizeof(struct S));return 0;
}

结果为3,来看看数据是怎么在内存中存储的吧

·首先申请一个字节的空间也就是8个比特位,a:两个比特位,将a的值转换位二进制,然后存低位的两个存进去,b是4个比特位,因为申请了8个比特位还剩6个,够用,就接着存,c是5个比特位,不够了,就舍去,重新申请一个字节,存放5个比特位,剩三个,不够d的6个bit位,就有申请一个字节存放d,所以总的申请了3个字节

总结:跟结构体相比,位段可以达到同样的效果,并且可以很好的节省空间

5.3位段的注意事项

位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员

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

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

相关文章

JMH微基准测试框架学习笔记

一、简介 JMH&#xff08;Java Microbenchmark Harness&#xff09;是一个用于编写、构建和运行Java微基准测试的框架。它提供了丰富的注解和工具&#xff0c;用于精确控制测试的执行和结果测量&#xff0c;从而帮助我们深入了解代码的性能特性。 二、案例实战 在你的pom文件…

数据结构从入门到精通——直接选择排序

直接选择排序 前言一、选择排序的基本思想&#xff1a;二、直接选择排序三、直接选择排序的特性总结&#xff1a;四、直接选择排序的动画展示五、直接选择排序的代码展示test.c 六、直接选择排序的优化test.c 前言 直接选择排序是一种简单的排序算法。它的工作原理是每一次从未…

Hadoop大数据应用:HDFS 集群节点缩容

目录 一、实验 1.环境 2.HDFS 集群节点缩容 二、问题 1.数据迁移有哪些状态 2.数据迁移失败 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 主机架构软件版本IP备注hadoop NameNode &#xff08;已部署&#xff09; SecondaryNameNode &#xff08;已部署…

Epuck2机器人固件更新及IP查询

文章目录 前言一、下载固件更新软件包&#xff1a;二、查询机器人在局域网下的IP 前言 前面进行了多机器人编队仿真包括集中式和分布式&#xff0c;最近打算在实物机器人上跑一跑之前的编队算法。但由于Epuck2机器人长时间没使用&#xff0c;故对其进行固件的更新&#xff0c;…

直播预约丨《袋鼠云大数据实操指南》No.1:从理论到实践,离线开发全流程解析

近年来&#xff0c;新质生产力、数据要素及数据资产入表等新兴概念犹如一股强劲的浪潮&#xff0c;持续冲击并革新着企业数字化转型的观念视野&#xff0c;昭示着一个以数据为核心驱动力的新时代正稳步启幕。 面对这些引领经济转型的新兴概念&#xff0c;为了更好地服务于客户…

CTF题型 匿名函数考法例题总结

CTF题型 匿名函数考法&例题总结 文章目录 CTF题型 匿名函数考法&例题总结一 .原理分析二 .重点匿名函数利用1.create_function()如何实现create_function代码注入 2.array_map()3.call_user_func()4.call_user_func_array()5.array_filter() 三.例题讲解1.[Polar 靶场 …

详细分析Python模块中的雪花算法(附模板)

目录 前言1. 基本知识2. 模板3. Demo 前言 分布式ID的生成推荐阅读&#xff1a;分布式ID生成方法的超详细分析&#xff08;全&#xff09; 1. 基本知识 Snowflake 算法是一种用于生成全局唯一 ID 的分布式算法&#xff0c;最初由 Twitter 设计并开源 它被设计用于解决分布式…

【5G NB-IoT NTN】3GPP R17 NB-IoT NTN介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

学生信息管理系统--修改信息(非常详细的修改,更新,撤销,删除逻辑)

目录 概述修改包括的操作修改在每个模块中的应用 详解修改与更新取消删除 特殊概念数据集游标 总结 概述 学生信息管理系统&#xff0c;功能相对简单且代码重复性高&#xff0c;应该采用复用的思想来减少代码的冗余和提高代码的可维护性。然而&#xff0c;对于基础入门项目来说…

wireshark数据捕获实验简述

Wireshark是一款开源的网络协议分析工具&#xff0c;它可以用于捕获和分析网络数据包。是一款很受欢迎的“网络显微镜”。 实验拓扑图&#xff1a; 实验基础配置&#xff1a; 服务器&#xff1a; ip:172.16.1.88 mask:255.255.255.0 r1: sys sysname r1 undo info enable in…

一文读懂!Mj AI作画是什么?5款Midjourney国内版软件必备!

mj ai 作画是什么&#xff1f; mj ai 作画&#xff0c;是 Midjourney ai 作画的缩写&#xff0c;这里的 Midjourney 是海外一款非常出名的 AI 绘画软件&#xff0c;其受欢迎程度和影响力之广&#xff0c;某种程度上让它成了 AI 作画的代名词&#xff0c;正如 ps 在平面设计领域…

D-Star 寻路算法

D-Star 寻路算法 下面简写 D-Star 为 D* D算法&#xff1a;D 算法”的名称源自 Dynamic A Star,最初由Anthony Stentz于“Optimal and Efficient Path Planning for Partially-Known Environments”中介绍。它是一种启发式的路径搜索算法&#xff0c; 适合面对周围环境未知或者…

静态代理IP测试:有何优点?

随着互联网的普及&#xff0c;越来越多的人开始使用动态IP进行上网。但是在某些情况下&#xff0c;我们可能需要使用静态IP进行测试或特定的网络设置。本文将介绍如何获取静态IP进行测试以及静态IP的优点。 一、如何获取静态IP进行测试&#xff1f; 1.联系ISP&#xff08;Int…

Docker Desktop 安装 ClickHouse 超级简单教程

Docker desktop 安装 clickhouse 超级简单 文章目录 Docker desktop 安装 clickhouse 超级简单 什么是 Docker &#xff1f;安装下准备安装Docker配置安装 ClickHouse配置数据库密码DBeaver 测试创建表总结 什么是 Docker &#xff1f; 下载 Docker desktop Docker Desktop …

红外相机和RGB相机标定:实现两种模态数据融合

1. 前期准备 RGB相机&#xff1a;森云智能SG2-IMX390&#xff0c;1个红外相机&#xff1a;艾睿光电IR-Pilot 640X-32G&#xff0c;1个红外标定板&#xff1a;https://item.taobao.com/item.htm?_ujp3fdd12b99&id644506141871&spma1z09.2.0.0.5f822e8dKrxxYI 2.操作步…

Spring MVC开发小练习

1. 加法计算器 需求&#xff1a;输入两个整数&#xff0c;计算和 约定前后端交互接口&#xff1a; 在开发项目前&#xff0c;根据需求先约定好前后端交互接口&#xff0c;双方按照接口文档进行开发&#xff0c;接口文档一旦写好&#xff0c;尽量不要轻易改变&#xff0c;如果…

CPU设计实战-Wishbone总线接口

为什么需要改用总线接口&#xff1f; 1.但是在实际应用中&#xff0c;程序的体积可能非常大&#xff0c;指令存储器就不能再集成在FPGA内部了&#xff0c;一般使用FPGA芯片外部的Flash作为指令存储器。同理,-般使用FPGA芯片外部的SDRAM作为数据存储器。 2.统一接口标准。 很多…

蓝桥杯 2023 省B 飞机降落

首先&#xff0c;这题要求的数据量比较少&#xff0c;我们可以考虑考虑暴力解法。 这题可能难在很多情况的考虑&#xff0c;比如说&#xff1a; 现在时间是10&#xff0c;有个飞机20才到&#xff0c;我们是可以干等10分钟。 #include <iostream> #include <…

0101插入排序-算法基础-算法导论第三版

文章目录 一 插入排序二 循环不变式与插入排序的正确性三 伪代码中的一些约定四 Java代码实现插入排序结语 一 插入排序 输入&#xff1a; n n n个数订单一个序列 ( a 1 , a 2 , ⋯ , a n ) (a_1,a_2,\cdots,a_n) (a1​,a2​,⋯,an​). **输出&#xff1a;**输入序列的一个排…

OpenWRT+zeroTier旁路由组网

前言 我之前写过一篇文章&#xff0c;探究了zeroTier的最基础的玩法&#xff0c;那篇文章结尾我提到了使用zeroTier虽然实现组网了&#xff0c;但是我只能访问局域网中制定的设备&#xff0c;局域网中其他设备无法访问&#xff0c;这篇文章我又研究了一套方案openwrtzeroTier旁…