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

目录

1. 前言

2. 结构体初识

3. 结构体创建变量

3.1 方法一

3.2 方法二

4. 结构体初始化

5. 结构体自引用

6. 结构体的大小

6.1 结构体对齐规则

6.2 常规结构体

6.3 结构体成员含数组

6.4 结构体嵌套结构体

6.5 为什么存在结构体对齐?

6.6 修改默认对齐数

7. 结构体传参

8. 结构体实现位段

8.1 位段基本讲解

8.2 位段的作用


1. 前言

C语言中有很多种类型,例如:
int ,float ,char ,double, long  ,long long ,long double等等,这些叫C语言的内置类型

但是有的时候描述一个人或物只用某一个类型是明显不够的

这时候C语言提供了自定义类型:结构体

2. 结构体初识

我们可以在结构体里面自己使用C语言的内置类型构造我们想要的自定义类型

比如我们需要描述一个学生

struct student
{
    char name[20];
    int num;
    int age;
};

学生有姓名,学号,年龄等等属性,并不是一个int或者char就能表示的,所以我们可以自定义一个类型,里面有姓名,学号,年龄,这些属于结构体的成员,这个struct student 就是我们自己定义的类型

结构体的最后有个分号一定不能漏掉

有了类型,我们就可以跟其它的内置类型一样,创建变量

3. 结构体创建变量

int a = 10;
char c = 'l';

上面我们就用了int 类型和 char类型创建了两个变量a 和 c,然后给它们赋值

结构体也是如此

3.1 方法一

struct student
{
    char name[20];
    int num;
    int age;
};

int main()
{
    struct student stu;
    return 0;
}

我们用自己创建的类型后面加上变量名字,这样就创建好了一个变量stu

3.2 方法二

struct student
{
    char name[20];
    int num;
    int age;
}stu;

在创建好的结构体后面分号前面直接加变量名字即可

4. 结构体初始化

#include<stdio.h>
#include<string.h>

struct student
{
    char name[20];
    int num;
    int age;
};

int main()
{
    struct student stu;
    strcpy(stu.name, "xiaoming");
    stu.age = 18;
    stu.num = 1;
    return 0;
}

我们要在结构体变量的后面加个. 才能找到结构体里的成员

然后我们如果给name这个字符数组初始化的话需要使用string.h库里的strcpy来给它赋值

然后下面的age 和 num一样都是用 变量+.操作符找到成员

int 类型就正常赋值即可

然后可以正常打印输出了

printf("名字:%s\n", stu.name);
printf("学号:%d\n", stu.num);
printf("年龄:%d\n", stu.age);

5. 结构体自引用

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

上面的结构体成员里嵌套了一个自身结构体成员next

这个就是结构体嵌套,自引用

但是这样的写法是错误的❌

因为在我们计算结构体大小的时候,还要计算里面的next的大小,但是里面的next又包含了下一个data和下一个next,这样就无限循环下去了导致结构体大小无穷大,是不合理的

正确写法✔:

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

 这也是链表的写法

错误写法❌:

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

 因为这个结构体只有创建完成了才会重命名为Node,所以在创建完成前成员里使用Node是不对的

 正确写法✔:

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

6. 结构体的大小

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

猜猜上面代码的结果是什么?

如果你觉得char占1个字节,int占4字节,char占1个字节总共6个字节那么就掉坑了

结构体其实是有它自己的一个对齐规则的

6.1 结构体对齐规则

规则:

1.结构体第一个成员对齐到结构体变量起始位置偏移量为0的地址处

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处        

对齐数 = 编译器默认对齐数 与 该成员变量大小的较小值

3.结构体大小为最大对齐数的整数倍(选所有对齐数中最大的那一个)

4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

先来一个个解释一下

6.2 常规结构体

如果这是我们的内存空间,那么我们在存放的时候会先从结构体的第一个成员所占的空间开始占用第一个0

按照上面我们写的结构体,那么第一个char就会占用一个0

然后根据第二个对齐规则,其他成员要对齐到对齐数的整数倍

那么我们要先算对齐数,下一个是int类型,所以算int类型的对齐数

对齐数 = 编译器默认对齐数 与 该成员变量大小的较小值

如果用的是VS,编译器默认对齐数就为8,这是固定的

该成员(int类型)大小为4个字节

所以选择小的作为对齐数所以对齐数就是4

所以我们要占用从4的整数倍开始的4个字节

下一个类型是char,char的字节是1,取最小值作为对齐数当然对齐数就是1了

所以下一个8就是1的倍数,所以直接占用即可

再根据第三个规则

我们现在全部的对齐数最大的为int类型的4,所以整个结构体的大小要为4的整数倍

但是我们看到上图中从0-8有9个字节占用了

所以我们还要再次浪费三个字节的空间满足这个规则

所以最终如图:

所以最后从0-11总共是用了12个字节,那么最终输出的结果当然就是12了

6.3 结构体成员含数组

如果是结构体成员里含数组的情况,其实跟常规的结构体一样

struct S1
{
 char c1;
 int i;
 char c2[5];
};
printf("%d\n", sizeof(struct S1));

 这个char数组其实没什么特别的,就把它看成5个char类型的东西即可

然后这5个char类型都是对齐数为1,然后最后再根据最大对齐数int的4对齐结构体总大小即可

如图:

所以是16个字节

下面来看看嵌套:

6.4 结构体嵌套结构体

struct S
{
 char c1;
 struct S1 s1;
 double d;
};

老样子char先占用0偏移量

然后我们上面已经计算过了结构体S1的最大对齐数为4,所以结构体S1对齐数为4

根据第4条对齐规则,可以画出如图:

然后double是8个字节,所以从8的倍数就是16开始到23的这个位置

在最后计算结构体总大小对齐的位置时,嵌套的结构体并不会用总大小参与,而是用它的成员来与其他成员比较,里面明显是double的大小最大,所以按8个字节的整数倍来定义结构体的大小

图中0-23正好是24(8的整数倍),所以最终答案是24

6.5 为什么存在结构体对齐?

我们看到上面的每一个结构体几乎都浪费了很多空间,但这么做其实是有好处的

这是一个以空间换时间的做法

假设一个处理器总是一次性从内存中取8个字节,则拿到的地址对应的都是8的倍数

如果我们能够保证能将double类型的数据都对齐成8的倍数

那么处理器一次扫描就能够将整个double全部取出

而不是扫描两次

这样效率就快了很多

如果我们想要两者兼得,如何做到呢?

目前没什么特别好的方法,但是我们可以尽可能让空间小的成员尽可能的聚在一起

例如:

struct S1
{
    char c1;
    int i;
    char c2;
};

struct S2
{
    char c1;
    char c2;
    int i;
};

它们两个的成员一模一样,但是

第一个结构体需要12个字节的空间,而第二个只需要8个

6.6 修改默认对齐数

 我们其实是可以自己更改默认对齐数的

#pragma pack(1)//默认对齐数改为1
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消修改默认对齐数

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

结构体成员的对齐数都为1那结构体的总大小就是全部成员的大小相加

所以答案为: 

7. 结构体传参

结构体也是和其他内置类型一样作为参数传递给函数的

我们之前有写到这个代码

#include<stdio.h>
#include<string.h>

struct student
{
    char name[20];
    int num;
    int age;
};

int main()
{
    struct student stu;
    strcpy(stu.name, "xiaoming");
    stu.age = 18;
    stu.num = 1;
    return 0;
}

现在可以用一个Print函数来打印试试看

#include<stdio.h>
#include<string.h>

struct student
{
    char name[20];
    int num;
    int age;
};

void Print(struct student s)
{
    printf("%s\n", s.name);
    printf("%d\n", s.age);
    printf("%d\n", s.num);
}

int main()
{
    struct student stu;
    strcpy(stu.name, "xiaoming");
    stu.age = 18;
    stu.num = 1;
    Print(stu);
    return 0;
}

我们创建了一个 struct student类型的形参s

然后对这个形参进行打印

在传参的时候,我们只需要将我们之前初始化过的 stu传进去即可 

这样一样是可以的

8. 结构体实现位段

struct A
{
 int a:2;
 int b:5;
 int c:10;
 int d:30;
};

我们在成员之后加了个:+数字就是位段

什么意思呢?

8.1 位段基本讲解

这些数字表示当前成员所占用的bit的大小

总所周知,一个int类型为4个字节也就是32个bit

所以我们:后面的数字一定不能超过32,因为一个int类型最多32个bit位

8.2 位段的作用

它可以帮我们节省空间,如果我们要开装下成员a和b和c,那么我们只需要开一个int类型即可

32个空间足以装下 2+5+10 = 17的空间

注意事项:
位段尽量不要跨平台使用,因为C语言并未完全的定义位段如何使用,所以不同的编译器都会有它自己的处理方式,所以不要跨平台使用!!!


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

idea创建angular项目

1.idea创建项目 idea&#xff1a;2023.2.3版本 不做赘述&#xff0c;我这里是创建模块&#xff0c;创建项目的话大同小异 2.创建完成后注意一下红色部分&#xff0c;后期需要 3.进入项目根目录 注意&#xff1a;一定要进入项目根目录&#xff0c;就是我们上面红色方框部分&a…

Java安全 反序列化(4) CC1链-LazyMap版

Java安全 反序列化(4) CC1链-LazyMap版 实验环境:存在漏洞的版本 commons-collections3.1-3.2.1 jdk 8u71之后已修复不可利⽤ 文章目录 Java安全 反序列化(4) CC1链-LazyMap版一.跟踪挖掘CC1_LazyMap原理二.完整CC1_Lazy版Poc 接着上一篇文章我们通过ChainedTransFormer实现任意…

vue3+threejs新手从零开发卡牌游戏(二):初始化场景

在删掉初始化中一些没用的代码后&#xff0c;在views目录下新建game文件夹&#xff0c;在里面新建一个index.vue&#xff0c;这里就当成游戏的主入口。 目录结构如下&#xff1a; 下面开始尝试创建场景&#xff1a; 一、添加一个div作为threejs的画布对象&#xff0c;之后整个…

电网的正序参数和等值电路(一)

本篇为本科课程《电力系统稳分析》的笔记。 本篇为第二章的第一篇笔记。 电力系统正常运行中&#xff0c;可以认为系统的三相结构和三相负荷完全对称。而对称三相的计算可以用一相来完成&#xff0c;其中所有给出的标称电压都是线电压的有效值&#xff0c;假定系统全部是Y-Y型…

如何将软件大规模部署到基于 Linux 的 IoT 设备

物联网( IoT) 改变了我们与世界互动的方式&#xff0c;将无数设备连接到互联网&#xff0c;从我们家中的智能恒温器到制造工厂的工业传感器。这些 IoT 设备的很大一部分依赖于 Linux 操作系统&#xff0c;因为它具有灵活性、稳健性和开源特性。 将软件大规模部署到基于 Linux …

react-jsx

react04 jsx语法 - 01 基础知识&#xff1a; jsx javascript xml(html) 把js和heml标签混合到一起 react视图编写及构建的简要流程 &#xff1a; 如何在react中使vs code支持格式化和快捷键提示&#xff1a;1, 2,修改文件后缀为jsx&#xff0c;因为webpack的打包规则中可以…

【蓝桥杯】RMQ(Range Minimum/Maximum Query)

一.概述 RMQ问题&#xff0c;是求区间最大值或最小值&#xff0c;即范围最值问题。 暴力解法是对每个询问区间循环求解&#xff0c;设区间长度n&#xff0c;询问次数m&#xff0c;则复杂度是O ( nm )。 一般还可以使用线段树求解&#xff0c;复杂度是O(mlogn)。 但还有一种…

守护数据安全,远离.locked勒索病毒:有效防御策略分享

导言&#xff1a; 随着信息技术的飞速发展&#xff0c;网络空间的安全问题日益凸显&#xff0c;其中勒索病毒便是一种严重的网络安全威胁。近年来&#xff0c;.locked勒索病毒逐渐进入人们的视野&#xff0c;其强大的破坏性和高隐蔽性使得许多个人和企业深受其害。本文将对.lo…

比堆垛机方案省电65% 实施快50% 四向车系统柔性化建设进程异军突起

对物流企业来说&#xff0c;供应链的数智化升级并非“赶时髦”&#xff0c;它需要找到一个既懂物流行业&#xff0c;又有数字化技术作基础的仓储方案提供商。而河北沃克基于AI底层技术、软硬一体化产品体系和技术创新行业经验双轮驱动的业务团队等“技术产品人才”三位一体优势…

AndroidStudio 由dolphin升级到giraffe,出现“gradle project sync failed“

1 现象描述 将AS由之前的dolphin版本升级到giraffe之后&#xff0c;接着打开以前的Android project&#xff0c;出现了"Gradle project sync failed…"的异常提示&#xff0c;在build面板中并没有出现project sync过程中报错的日志。 异常提示如下图所示&#xff1a…

知识蒸馏——深度学习的简化之道 !!

文章目录 前言 1、什么是知识蒸馏 2、知识蒸馏的原理 3、知识蒸馏的架构 4、应用 结论 前言 在深度学习的世界里&#xff0c;大型神经网络因其出色的性能和准确性而备受青睐。然而&#xff0c;这些网络通常包含数百万甚至数十亿个参数&#xff0c;使得它们在资源受限的环境下&…

【OpenGL手册19】几何着色器

目录 一、说明 二、渲染管线的逻辑 三、几何着色器 四、使用几何着色器 五、造几个房子 六、几何着色器渲染爆破物体 一、说明 如果说用顶点和片段着色器干了什么&#xff0c;其实不多。加入几何着色器&#xff0c;能够加大渲染能力&#xff0c;简化数据结构&#xff0c;…

前端项目部署后,如何提示用户版本更新

目录 前言解决方案1、public目录下新建manifest.json2、写入当前时间戳到manifest.json3、检查版本更新4、woker线程5、入口文件引入 可能出现的问题好书推荐 前言 项目部署上线后&#xff0c;特别是网页项目&#xff0c;提示正在操作系统的用户去更新版本非常 important。一般…

Java并发

目录 线程 什么是线程 进程和线程的区别 线程的生命周期 什么是多线程 并发与并行 多线程的三种实现方式 继承Thread类 1.创建类继承Thread类 2.重写run&#xff08;&#xff09;方法 3.创建对象启动线程 实现Runnable接口 1.自己定义一个类实现Runnable接口 2.重…

java-11-openjdk-11.0.xxx/lib/tzdb.dat (No such file or directory)

项目用的是JAVA 11 build 的时候报错 ava-11-openjdk-11.0.xxx/lib/tzdb.dat (No such file or directory)这个问题困扰了很久&#xff0c;最终在redhat 上找到了root case: 该版本JDK 有bug 别挣扎了直接升级JDK

进程创建,程序加载运行,以及进程终止,什么是僵尸进程,什么是孤儿进程

进程控制 创建进程&#xff0c;撤销进程&#xff0c;实现进程转换&#xff08;必须一气呵成&#xff0c;使用原语&#xff09; 原语不被中断是因为有关中断指令 创建进程 撤销进程 进程创建fork fork&#xff08;&#xff09;函数会创建一个子进程&#xff0c;子进程会返…

HarmonyOS 应用开发案例

本帖下方集中了HarmonyOS Next应用开发时&#xff0c;会遇到的常见应用案例。后续会持续更新大量案例&#xff0c;帮助开发者快速学习。欢迎感兴趣的同学加入Q&#xff1a;454901491 72.手写绘制及保存图片案例&#xff08;0319更新&#xff09;&#xff08;点此查看源码实现&…

数字孪生与智慧城市:重塑城市生活的新模式

随着信息技术的迅猛发展&#xff0c;数字孪生作为一种新兴的技术理念&#xff0c;正在逐渐改变城市建设和管理的传统模式。智慧城市作为数字孪生技术应用的重要领域&#xff0c;正在以其独特的优势和潜力&#xff0c;重塑着城市生活的方方面面。本文将从数字孪生的概念、智慧城…

Nginx:部署及配置详解(linux)

Nginx&#xff1a;部署及配置详解&#xff08;linux&#xff09; 1、nginx简介2、安装编译工具及库文件3、安装 pcre4、nginx安装5、nginx配置文件nginx.conf组成6、nginx配置实例-反向代理7、nginx 配置实例-负载均衡 &#x1f496;The Begin&#x1f496;点点关注&#xff0c…

计算机组成原理 双端口存储器原理实验

一、实验目的 1、了解双端口静态随机存储器IDT7132的工作特性及使用方法 2、了解半导体存储器怎样存储和读出数据 3、了解双端口存储器怎样并行读写&#xff0c;产生冲突的情况如何 二、实验任务 (1)按图7所示&#xff0c;将有关控制信号和和二进制开关对应接好&#xff0c;…