详细讲解c语言结构体、联合体、枚举

目录

1、结构体类型声明

1.1结构体的定义

1.2 结构体变量的声明

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

2、如何使创建的结构体所占内存最小

 2.1 结构体内存对齐-存储

 2.2 通过宏offsetof计算结构体内变量的偏移量

2.3 如何通过宏offsetof计算数组的偏移量

2.4 为什么存在内存对齐

 2.5 修改默认对齐数

3、结构体传参 

 3.1 值传递 - 传结构体

3.2 指针传递 - 传地址

4、联合体类型的声明

​ 4.1 联合体的特点

4.2 相同成员的结构体和联合体对比

4.3 联合体大小的计算

4.4 联合体举例​编辑

5、枚举类型

5.1 枚举类型的声明 

5.2 枚举类型的优点

5.3 枚举类型的使用


1、结构体类型声明

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

1.1结构体的定义

在C语言中,使用 struct 关键字定义结构体。结构体定义的一般语法是:

struct 结构体名称 
{数据类型1 成员1;数据类型2 成员2;// 更多成员声明
};
1.2 结构体变量的声明

(1)定义结构体后,可以使用结构体名称来声明结构体变量。声明结构体变量的一般语法是:

struct 结构体名称 变量名称;
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}; //分号不能丢struct Stu student1;

(2)也可以在定义结构体时直接声明结构体的变量:

struct 结构体名 
{数据类型 成员1;数据类型 成员2;// 其他成员变量
} 变量名;
struct Student 
{char name[20];int age;float score;
} stu1, stu2; // 在结构体定义时声明结构体变量

 这样,在声明结构体变量时,就可以直接使用结构体类型名和变量名来创建结构体实例。

// 修改结构体成员的值
strcpy(stu1.name, "Tom");
stu1.age = 20;
stu1.score = 85.5;// 访问结构体成员的值
printf("Name: %s\n", stu1.name);
printf("Age: %d\n", stu1.age);
printf("Score: %.2f\n", stu1.score);

(3) 还可以同时定义结构体、声明结构体的变量并赋值。

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

初始化方法有两种:①按照结构体成员的顺序初始化,②按照指定的顺序初始化

#include <stdio.h>
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
};
int main()
{//按照结构体成员的顺序初始化struct Stu s = { "张三", 20, "男", "20230818001" };printf("name: %s\n", s.name);printf("age : %d\n", s.age);printf("sex : %s\n", s.sex);printf("id : %s\n", s.id);//按照指定的顺序初始化struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .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;
}

2、如何使创建的结构体所占内存最小

 通过下面代码发现,具有相同成员变量的结构体所占内存大小并不相同。

 2.1 结构体内存对齐-存储

 如何理解上述对其规则?

(1)任何变量创建都需要在系统中开辟一块内存,且每个字节都有对应地址。当创建一个结构体时,同样需要为其开辟一块内存空间,结构体的首地址偏移量为0,之后每个字节为一个偏移量。  (2)分析上述第一个结构体,这个结构体第一个定义的是 char 类型的变量,占一个字节。根据对其规则第一条,从 0偏移量起到 1偏移量存放的是 char 类型变量 c1。                                              (3)根据对其规则第二条,结构体其他成员变量要对齐到 ‘对齐数’ 的整数倍的地址处,同时规定对齐数为该成员变量大小与 8 中的较小值。第一个结构体中第二个成员变量同样是 char 类型,占 1 个字节,1 和 8进行比较,1 < 8 ,所以此时对齐数为 1,所以结构体中第二个成员变量应放在对齐数的整数倍地址处,任何数都是1 的整数倍,所以将其继  c1之后放在偏移量为 1 处即可。          (4)第一个结构体中第三个成员变量为 int 类型,其大小为 4个字节,与 8进行比较,4<8,因此对齐数为 4,应该将该变量放在 4的整数倍的地址处,由于前四个字节处以及存放变量,所以要将该int 类型变量 i 放在偏移量为 4的地址处。                                                                                        (5)根据对其规则第 3条,结构体总大小为最大对齐数的整数倍,而第一个结构体中最大对齐数为 4,所以结构体总大小应该为 4的倍数,而根据前四点的分析,总共用了 8个字节,恰好是 4的倍数,因此第一个结构体的大小为 8。

        定义的第二个结构体 S2同理,第一个成员变量要对其到偏移量为 0的地址处,char 类型占一个字节。但是与第一个结构体不同的是,S2的第二个成员变量为 int类型,占四个字节,4 < 8,因此要将第二个成员变量 i 放在 4的整数倍地址处,然后再存放第三个char 类型成员变量,占一个字节。可以发现存放好三个变量后总共有 9个字节,但是结构体的总大小为最大对齐数的整数倍,该结构体最大对齐数为 4,9不是 4的倍数,因此还要继续占用空间,最终该结构体总共占用 12字节大小的内存,相比于第一个结构体,虽然包含了相同的变量,却使用了更多的内存空间。

那么对于如下情况,结构体内调用结构体的内存大小该如何计算?

#include<stdio.h>
struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%d\n", sizeof(struct S3));printf("%d\n", sizeof(struct S4));return 0;
}

       根据对齐规则第四条,如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,根据上面所学规则,可以计算出结构体 S3 的大小为 16个字节,其中最大的对齐数为 8,那么在结构体 S4中,创建的 S3结构体变量就应该对齐到第 8位的偏移量,然后 S4中的第三个变量为 double类型,占 8个字节,应该对齐到第 24位偏移量处,正好接到 3s后面。最后计算结构体 S4的总大小为最大对齐数的整数倍,最大对齐数为 s3的 16,因此,结构体 S4的总大小为32。

 2.2 通过宏offsetof计算结构体内变量的偏移量

  offsetof 是 C 语言标准库 <stddef.h> 中定义的宏。它用于计算结构体中成员的相较于起始位置的偏移量(offset)。offsetof 宏接受两个参数:第一个参数是一个结构体类型,第二个参数是结构体中的一个成员的名称。它返回指定成员在结构体中的字节偏移量。 

        以上面我们定义的 S3结构体中的 int i 变量以及 S4结构体中的 s3变量和 double d变量为例,可以发现正好与演示图中的位置对应。

2.3 如何通过宏offsetof计算数组的偏移量

       结构体内数组的偏移量是根据每个数组元素来计算的,对齐数为每个元素的大小,而不是整个数组的大小。

2.4 为什么存在内存对齐

 2.5 修改默认对齐数

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

#pragma pack(1)   设置默认对⻬数为1,一般为2的倍数
#pragma pack()   取消设置的对⻬数,还原为默认

3、结构体传参 

 3.1 值传递 - 传结构体

3.2 指针传递 - 传地址

4、联合体类型的声明

       联合体的定义、声明和结构体基本一样,使用的关键字是union。虽然结构上相同但是本质上却大不相同。如下代码所示,虽然联合体和结构体的变量相同,但是所占大小并不相同。

 
4.1 联合体的特点

       通过分析上述代码可以发现,联合体 un 的变量首地址相同,同时char类型变量 c 与 int 类型变量 i 共用第一个字节的空间。所以只有在使用 i 的时候不使用 c,在使用 c 的时候不使用 i 才能满足共存。也说明了联合体的大小至少是那个最大成员的大小。

        调试下面代码可以发现,首先为联合体 int 类型变量 i 赋值,再为 char 类型变量 c 赋值后就会将 int 类型变量 i 的第一个字节覆盖。

联合体的特点:

  1. 节省内存:由于联合体的成员共享同一块内存空间,因此节省了存储空间。

  2. 只能同时存储一个成员的值:联合体的各个成员共用一块内存,因此同一时间只能存储其中的一个成员的值。

  3. 最大成员决定了联合体的大小:联合体的大小取决于最大的成员的大小,不同于结构体,结构体的大小等于所有成员大小之和。

        联合体通常用于需要在不同数据类型之间进行转换或者需要节省内存空间的情况下,但需要注意在使用时确保正确地理解成员的含义和使用场景,避免出现错误。

4.2 相同成员的结构体和联合体对比

4.3 联合体大小的计算

• 联合的大小至少是最大成员的大小。
• 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

4.4 联合体举例
struct gift_list
{//公共属性int stock_number;//库存量double price; //定价int item_type;//商品类型//特殊属性char title[20];//书名char author[20];//作者int num_pages;//⻚数char design[30];//设计int colors;//颜⾊int sizes;//尺⼨
};

struct gift_list
{int stock_number;//库存量double price; //定价int item_type;//商品类型union {struct{char title[20];//书名char author[20];//作者int num_pages;//⻚数}book;struct{char design[30];//设计}mug;struct{char design[30];//设计int colors;//颜⾊int sizes;//尺⼨}shirt;}item;
};

 如何访问上述结构体变量,仔细看下述代码即可:

(方法一)可以使用结构体变量名后跟成员访问运算符.,然后跟上成员变量的名称。对于包含在联合体中的成员变量,可以通过结构体变量名后跟成员访问运算符.,再跟上联合体的成员变量名,然后再跟上成员变量的名称。

struct gift_list myGift;访问 stock_number 变量
myGift.stock_number = 10;访问 price 变量
myGift.price = 29.99;访问 item_type 变量
myGift.item_type = 1;访问 book 结构体中的 title 变量
strcpy(myGift.item.book.title, "The Book Title");访问 book 结构体中的 author 变量
strcpy(myGift.item.book.author, "The Author");访问 book 结构体中的 num_pages 变量
myGift.item.book.num_pages = 200;访问 mug 结构体中的 design 变量
strcpy(myGift.item.mug.design, "The Mug Design");访问 shirt 结构体中的 design 变量
strcpy(myGift.item.shirt.design, "The Shirt Design");访问 shirt 结构体中的 colors 变量
myGift.item.shirt.colors = 3;访问 shirt 结构体中的 sizes 变量
myGift.item.shirt.sizes = 2;

(方法二) 除了使用.操作符来访问结构体中的成员变量之外,还可以使用指针和->操作符来访问结构体成员变量。

struct gift_list myGift;
struct gift_list *ptrGift = &myGift; // 定义一个指向结构体的指针,并指向结构体变量使用指针和 -> 操作符来访问结构体成员变量
ptrGift->stock_number = 10;
ptrGift->price = 29.99;
ptrGift->item_type = 1;访问 book 结构体中的 title 变量
strcpy(ptrGift->item.book.title, "The Book Title");访问 book 结构体中的 author 变量
strcpy(ptrGift->item.book.author, "The Author");访问 book 结构体中的 num_pages 变量
ptrGift->item.book.num_pages = 200;访问 mug 结构体中的 design 变量
strcpy(ptrGift->item.mug.design, "The Mug Design");访问 shirt 结构体中的 design 变量
strcpy(ptrGift->item.shirt.design, "The Shirt Design");访问 shirt 结构体中的 colors 变量
ptrGift->item.shirt.colors = 3;访问 shirt 结构体中的 sizes 变量
ptrGift->item.shirt.sizes = 2;

5、枚举类型

5.1 枚举类型的声明 

enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};
enum Sex//性别
{MALE,FEMALE,SECRET
};
enum Color//颜⾊
{RED,GREEN,BLUE
};

enum Color//颜⾊
{RED = 2,GREEN = 4,BLUE = 8
};
5.2 枚举类型的优点

5.3 枚举类型的使用
enum Color//颜⾊
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//使⽤枚举常量给枚举变量赋值

在下面的示例中,我们定义了一个枚举类型Season,并包含了四个枚举常量:SPRINGSUMMERAUTUMN 和 WINTER。在main函数中,我们声明并初始化了一个枚举变量currentSeason,然后根据不同的季节常量输出相应的信息。

枚举类型的特点包括:

  1. 枚举常量默认从0开始递增,也可以手动指定初始值。
  2. 枚举常量在定义时不能重复命名。
  3. 可以将枚举类型用于变量声明、函数参数等地方,以提高代码的可读性。

需要注意的是,枚举类型虽然提高了代码的可读性,但会占用一定的内存空间,因此在需要大量枚举常量时,应慎重使用。

#include <stdio.h>// 定义枚举类型 Season
enum Season {SPRING,SUMMER,AUTUMN,WINTER
};int main() {// 声明并初始化一个枚举变量enum Season currentSeason = SPRING;// 使用枚举变量if (currentSeason == SPRING) {printf("It's Spring!\n");} else if (currentSeason == SUMMER) {printf("It's Summer!\n");} else if (currentSeason == AUTUMN) {printf("It's Autumn!\n");} else if (currentSeason == WINTER) {printf("It's Winter!\n");}return 0;
}

      那是否可以拿整数给枚举变量赋值呢?在C语⾔中是可以的,但是在C++是不⾏的,C++的类型检查比较严格。
 

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

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

相关文章

吴恩达机器学习笔记 二十四 决策树模型 学习过程 什么时候停止分裂 如何选择结点特征

案例&#xff1a;识别小猫&#xff0c;上面这个分类的特征 x 采用分类值&#xff08;几个离散的值&#xff09; 决策树最顶端的结点称根结点(root node)&#xff0c;除了根结点和叶子结点之外的叫决策结点(decision node)&#xff0c;最底层的叫叶子结点(leaf node)&#xff0c…

华为openEuler系统卸载jdk

华为openEuler系统卸载jdk 1.查看openEuler上已安装的 Java 版本&#xff1a; 在终端中运行以下命令&#xff0c;查看系统中已经安装的 Java 版本。 sudo alternatives --config java这将列出已安装的 Java 版本&#xff0c;你可以看到当前使用的是哪个版本 2.卸载 Java&am…

Python函数学习

Python函数学习 1.函数定义 在函数定义阶段只检查函数的语法问题 2.实参形参 ​​​​总结&#xff1a; &#xff08;1&#xff09;位置参数就是经常用的按照位置顺序给出实参的值&#xff1b; &#xff08;2&#xff09;关键字实参形式&#xff1a;key123&#xff1b;放在…

轻松打造完美原型:9款在线工具推荐

早年&#xff0c;UI设计师选择的工具有限&#xff0c;功能相对单一&#xff0c;大多数在线原型设计工具都是国外的&#xff0c;语言和网络都增加了设计工作的负担。如今&#xff0c;国内外有许多在线原型设计工具&#xff0c;不仅可以在浏览器上使用&#xff0c;而且还具有团队…

基于SpringBoot的后勤管理系统【附源码】

后勤管理系统开发说明 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myecli…

面试算法-51-翻转二叉树

题目 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 解 class Solution {public TreeNode invertTree(TreeNode root) {dfs(root);re…

Hybrid第二次雪梨作业

作业要求: 利用ReactNative和Axios完成以下效果。 注意: 1、RN不能直接引用svg图片,需使用第三方库,可将logo图片换成百度logo 2、搜索框可不要搜索图标 3、“全部”“精华”等前5项类型切换功能要实现(“客户端测试可不写”,接口没给出参数值),调用 https://cnodejs…

arcgis数据导出到excel

将arcgis属性数据导出到excel&#xff1a; 1&#xff09; 工具箱\系统工具箱\Conversion Tools.tbx\Excel\Excel 转表 2&#xff09;用excel打开导出的图层文件中后缀为.dbf的数据&#xff08;方便快捷&#xff0c;但是中文易乱码&#xff09;

phpstudy自定义安装mysql8.3并启动

phpstudy自定义安装mysql8.3并启动 先去官网:https://dev.mysql.com/downloads/下载压缩包文件 然后按下面的图片一步一步操作 选择版本&#xff0c;选择第一个压缩包文件&#xff0c;下载 下载完成后&#xff0c;解压到phpstudy环境目录下&#xff0c;如下图 然后进入mysq…

MySQL | CRUD

目录 1. Create 2. Retrieve 2.1. SELECT列 2.1.1. 全列查询 2.1.2. 指定列查询 2.1.3. 查询字段为表达式 2.1.4. 为查询结果指定别名 2.1.5. 结果去重 2.2. WHERE条件 2.2.1. 年龄小于19的同学 2.2.2. id在2~3的同学 2.2.3. id为1和4的同学 2.2.4. 姓张的同学及张…

集成学习 | 集成学习思想:Bagging思想

目录 一. Bagging思想1. Bagging 算法2. 随机森林(Random Forest)算法 在正文开始之前&#xff0c;我们先来聊一聊什么是集成学习&#xff1f; 集成学习是一种算法思想&#xff1a;将若干个弱学习器分组之后&#xff0c;产生一个新的学习器 弱学习器指预测误差在50%以下的学习器…

VS Code安装Live Server插件搭建web网页结合内网穿透实现公网访问

文章目录 前言1. 编写MENJA小游戏2. 安装cpolar内网穿透3. 配置MENJA小游戏公网访问地址4. 实现公网访问MENJA小游戏5. 固定MENJA小游戏公网地址 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…

性能测试-Jmeter常用元件基础使用

一、Jmeter元件 #线程组 添加HTTP请求 #配置元件 配置元件内的元件都是用于进行初始化的东西 #监听器 监听器主要是用来获取我们使用取样器发送请求后的响应数据相关信息 #定时器 定时器主要用来控制我们多久后执行该取样器&#xff08;发送请求&#xff09; #前置处理器 前置处…

【Python循环3/5】条件循环语句

目录 导入 条件循环 边界条件 while循环 死循环 while循环与for循环的区别 总结 知识图谱 导入 我们已经学习了如何利用for语句实现代码重复执行的循环结构。通过遍历列表&#xff0c;输出其中的每一个元素。 for循环就像是排队办事&#xff0c;一个个进入&#xff0c;轮…

爬虫逆向实战(35)-MyToken数据(MD5加盐)

一、数据接口分析 主页地址&#xff1a;MyToken 1、抓包 通过抓包可以发现数据接口是/ticker/currencyranklist 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现有一个code参数 请求头是否加密&#xff1f; 无 响应是否加密&#xf…

HarmonyOS(鸿蒙)快速入门

一:下载开发工具 鸿蒙的开发工具叫DevEco 下载点击 其他部分都一直next 就行,这个页面出现的install 建议都点击install 然后单独选择安装目录 可能存在的问题 就是之前安装nodejs&#xff08;比如自己开发web或者RN等情况&#xff09;版本低 等情况 所以建议你单独安装一次 …

string的使用和模拟实现| 细致到strcpy ,strstr,strcmp都不放过

string的使用和模拟实现 string的成员变量string的构造方法用法无参的构造方法的实现全缺省的构造参数的实现 strcpy的模拟实现为什么这里的_size要1?为什么这里是默认传空字符串&#xff1f; 赋值运算符重载 析构函数遍历字符串operator[]使用传统c语言字符串下标遍历的缺点 …

【Linux系统编程】进程程序替换

介绍&#xff1a; 进程程序替换是指将一个进程中正在运行的程序替换为另一个全新的程序的过程&#xff0c;但替换不是创建新进程&#xff0c;只是将对应程序的代码和数据进行替换。具体来说&#xff0c;这个替换过程涉及将磁盘中的新程序加载到内存结构中&#xff0c;并重新建立…

防火墙常用功能配置

防火墙&#xff1a;为了限制不同区域之间的流量通信。默认有一条拒绝所有的策略。 现在的防火墙主要作用&#xff1a;是区域隔离和访问控制。 安全防护是核心特性 路由器&#xff1a;ACL列表&#xff0c;控制流量 入侵防御&#xff1a;网络攻击 文件过滤&#xff0c;内容过滤&…

电脑里的图片杂乱无章怎么办?使用汇帮批量重命名一键帮你解决 大量图片如何批量重命名?

当我们面临大量的图片需要重命名时&#xff0c;这无疑是一项繁琐而耗时的任务。然而&#xff0c;通过一些有效的方法和工具&#xff0c;我们可以使这个过程变得更加高效和轻松。以下介绍的这款汇帮批量重命名软件&#xff0c;能够帮助您有效地重命名大量的图片。 想要快速的进…