探索C语言中的联合体与枚举:数据多面手的完美组合!

​ pFp8UCq.jpg

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C语言学习
贝蒂的主页:Betty‘s blog

1. 联合体的定义

联合体又叫共用体,它是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。给联合体其中⼀个成员赋值,其他成员的值也跟着变化。

2. 联合体基础

2.1 联合体声明

联合体的结构类似于结构体,由关键字union和多个成员变量组成。格式如下:

union [union tag]
{
member definition;
member definition;

member definition;
} [one or more union variables];

  • union tag 是你自己定义的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在共用体定义的末尾,最后一个分号之前,您可以指定一个或多个共用体变量,这一点和结构体类似。
(1) 普通联合体
union data
{int n;char ch;
};
(2) 嵌套联合体

联合体也是可以嵌套使用的。

union Un1
{char c[5];int i;
};
union Un2{int n;union Un1 u1;
};
(3) 匿名联合体

匿名联合体是一种特殊联合体,省略了联合体名称,这种联合体只能在其定义的代码块内使用一次。例如,如果你在一个函数内部定义了一个匿名联合体,则该联合体只能在该函数内部使用。当代码块执行完毕后,该联合体将不再可见。

union
{int n;char ch;
};
(4) typedef联合体

我们也可以使用typedef简化联合体。

typedef union Un1
{char c[5];int i;
}Un1;//之后可以使用Un1代替union Un1

2.2 联合体变量的创建与初始化

联合体变量创建除了在创建联合体时候定义,也可以在主函数内定义并且同时能够对齐初始化。

用例如下:

union Un
{char c;int i;
};
int main()
{//联合体的初始化union Un u1 = { 'a',0 };//错误union Un u2 = { 0 };//正确return 0;
}
  • 联合体的初始化只能使用一个值,因为联合体的所有成员共享同一块内存空间。

2.3 访问联合体

为了访问联合体的成员,我们使用成员访问运算符(.)。成员访问运算符是联合体变量名称和我们要访问的共用体成员之间的一个句号。下面是一个实例:

#include<string.h>
typedef union Un1
{char c[10];int i;
}Un1;int main()
{Un1 u = { 0 };printf("%d ", u.i);printf("%s ", strcpy(u.c, "abcdef"));return 0;
}

输出结果:

3. 联合体的内存存储

3.1 联合体的大小

联合体的大小是其成员变量大小之和,还是和结构体一样遵循某种特殊规律呢?我们通过以下代码实验一下。

union Un
{char c[5];int i;
};
int main()
{union Un u2 = { 0 };printf("大小为%zd", sizeof(union Un));return 0;
}

输出结果:

通过验证我们知晓联合体的大小并不是其成员变量大小之和,也是遵循某种特定的规律。

那么这种规律到底是什么呢?其实很简单

  • 联合的⼤⼩⾄少是最⼤成员的⼤⼩。
  • 当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍。
  • 对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。(VS 中默认的值为 8 ,Linux中gcc没有默认对齐数,对⻬数就是成员⾃⾝的⼤⼩)

3.2 存储形式

知道了联合体的大小,我们也就会很容易知道它的内存存储方式了。下面有具体四个样例:

(1) 样例一
#include <stdio.h>
union Un
{char c;int i;
};
int main()
{//联合变量的定义union Un un = { 0 };// 下⾯输出的结果是⼀样的吗?printf("%p\n", &(un.i));printf("%p\n", &(un.c));printf("%p\n", &un);return 0;
}

输出结果:

  • 通过这次实验我们联合体从起始位置开始共用的
(2) 样例二
#include <stdio.h>
//联合类型的声明
union Un
{char c;int i;
};
int main()
{//联合变量的定义union Un un = { 0 };un.i = 0x11223344;un.c = 0x55;printf("%x\n", un.i);return 0;
}

输出结果:

示意图:

  • 蓝色为共用部分,绿色为非共用部分
  • VS编译器为小端存储
(3) 样例三
#include <stdio.h>
union Un1
{char c;int i;
};
int main()
{// 下⾯输出的结果是什么?printf("大小为%d\n", sizeof(union Un1));return 0;
}

输出结果:

示意图:

解析:

  1. c的大小为一个字节,i的大小为四个字节,他们共用一个字节。
  2. 最大对齐数为4,结构体大小此时刚好为4,是最大对齐数的整数倍。
(4) 样例四
#include <stdio.h>
union Un2
{short c[7];int i;
};
int main()
{// 下⾯输出的结果是什么?printf("大小为%d\n", sizeof(union Un2));return 0;
}

输出结果:

示意图:

解析:

  1. short大小为2,c中有7个大小为14,i大小为4,共用四个字节。
  2. 最大对齐数为4,联合体大小为最大对齐数的整数倍,为16。

4. 利用联合体判断大小端

我们早在学习数据在内存中如何存储时就已经了解过一种判断大小端的方法,今天就为大家介绍另一种方法——通过联合体判断大小端,

还是这幅图,我们要判断大小端就需要判断第一位存储到底是01还是00。

那如何取出第一位呢?除了通过指针,我们也能利用联合体共用同一块内存这一性质判断。

代码如下:

int check_sys()
{union{int i;char c;}un;un.i = 1;return un.c; //返回1是⼩端,返回0是⼤端
}
int main()
{int ret = check_sys();if (ret == 1){printf("⼩端\n");}else{printf("⼤端\n");}return 0;
}

5. 联合体的应用

通过联合体我们可以节省一部分内存。比如:我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书杯⼦衬衫。每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。

其他信息:

图书:书名、作者、⻚数
杯⼦:设计
衬衫:设计、可选颜⾊、可选尺⼨

我第一想法是通过一个结构体定义:

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; //尺⼨
};

上述的结构其实设计的很简单,⽤起来也⽅便,但是结构的设计中包含了所有礼品的各种属性,这样使得结构体的⼤⼩就会偏⼤,⽐较浪费内存。但是对于礼品兑换单中的商品来说,只有部分属性信息是常⽤的。⽐如:商品是图书,就不需要design、colors、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;
};

6. 枚举的定义

在 C 语言中,枚举(enum)是一种用户定义的数据类型,用于定义一个由标识符列表组成的整数常量集合。枚举类型通过关键字 enum来定义。

在实际应用中我们经常把能够且便于一一列举的类型用枚举来表示。就比如:一周的星期、一年的月份……,其基本语法如下:

enum 枚举类型名
{
标识符1,
标识符2,

};

  • 枚举类型名受自己定义,如:week,year…,标识符就是其中的枚举常量,如Mon,Tues,Wed…
  • 每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。

7. 枚举基础

7.1 枚举的声明

(1) 普通枚举

接下来我们举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名:

#define MON  1
#define TUE  2
#define WED  3
#define THU  4
#define FRI  5
#define SAT  6
#define SUN  7

这个看起来代码量就比较多,接下来我们看看使用枚举的方式:

enum DAY
{MON=1, //指定从1开始,否则默认从0开始TUE,WED,THU, FRI, SAT, SUN
};
(2) 匿名枚举

和匿名结构体与匿名联合体类似,枚举也有匿名类型。

enum
{APPLE,BANANA,ORANGE
};
(3) typedef枚举

我们也可以使用typedef简化枚举。

typedef enum DAY
{MON = 1, //指定从1开始,否则默认从0开始TUE,WED,THU,FRI,SAT,SUN
}DAY;

7.2 打印枚举常量

typedef enum DAY
{MON, TUE,WED,THU,FRI,SAT,SUN
}DAY;
int main()
{for (int i = MON; i < SUN; i++){printf("%d ", i);}return 0;
}

输出结果:

  • 这也间接证明枚举是一个常量,默认从0开始。

7.3 枚举变量的创建与初始化

我们可以利用定义的枚举常量对枚举变量进行赋值。

typedef enum DAY
{MON, TUE,WED,THU,FRI,SAT,SUN
}DAY;
int main()
{DAY a = MON;//最好用枚举常量赋值return 0;
}
  • 那是否可以拿整数给枚举变量赋值呢?在C语⾔中是可以的,但是在C++是不⾏的,C++的类型检查⽐较严格。

8. 枚举常量的大小

枚举常量的大小同 int 的大小一样,都是四个字节。

我们可以通过以下代码来实验:

#include <stdio.h>
enum color1
{RED,GREEN,BLUE
};enum color2
{GRAY = 0x112233445566,YELLOW,PURPLE
};int main()
{printf("enum color1: %d\n", sizeof(enum color1));printf("enum color2: %d\n", sizeof(enum color2));return 0;
}

输出结果:

9. 枚举的优点

乍一看,我们可能会感觉枚举有点画蛇添足的感觉,那使用枚举到底有哪些优点呢?

优点:

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符⽐较枚举有类型检查,更加严谨。
  3. 便于调试,预处理阶段会删除 #define 定义的符号
  4. 使⽤⽅便,⼀次可以定义多个常量
  5. 枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使⽤

10. 枚举的应用

枚举的使用常与switch语句联系起来。

#include <stdio.h>
int main()
{enum color { red = 1, green, blue };enum  color favorite_color;/* 用户输入数字来选择颜色 */printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): \n");scanf("%d", &favorite_color);/* 输出结果 */switch (favorite_color){case red:printf("你喜欢的颜色是红色\n");break;case green:printf("你喜欢的颜色是绿色\n");break;case blue:printf("你喜欢的颜色是蓝色\n");break;default:printf("你没有选择你喜欢的颜色\n");}return 0;
}

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

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

相关文章

leetCode二叉树的堂兄弟节点

题目描述 在二叉树中&#xff0c;根节点位于深度 0 处&#xff0c;每个深度为 k 的节点的子节点位于深度 k1 处。 如果二叉树的两个节点深度相同&#xff0c;但 父节点不同 &#xff0c;则它们是一对堂兄弟节点。 我们给出了具有唯一值的二叉树的根节点 root &#xff0c;以…

从概念到实践:数字孪生在智慧城市建设中的应用与挑战

一、引言 随着科技的飞速发展&#xff0c;数字孪生这一概念逐渐受到广泛关注。数字孪生通过建立物理世界的数字模型&#xff0c;为城市的管理和规划提供了前所未有的可能性。智慧城市作为现代城市发展的重要趋势&#xff0c;正逐渐成为人们关注的焦点。本文将探讨数字孪生在智…

编译原理实验3——自下而上的SLR1语法分析实现(包含画DFA转换图、建表、查表)

文章目录 实验目的实现流程定义DFA状态实现代码运行结果测试1测试2测试3 总结 实验目的 实现自下而上的SLR1语法分析&#xff0c;画出DFA图 实现流程 定义DFA状态 class DFA:def __init__(self, id_, item_, next_ids_):self.id_ id_ # 编号self.item_ item_ # productio…

Redis保证数据不丢失的手段

Redis 保证数据不丢失的主要手段有两个&#xff1a; 持久化集群运行 我们分别来看它们两的具体实现细节。 # 1.Redis 持久化 持久化是指将数据从内存中存储到持久化存储介质中&#xff08;如硬盘&#xff09;的过程&#xff0c;以便在程序重启或者系统崩溃等情况下&#xf…

C++完成使用map Update数据 二进制数据

1、在LXMysql.h和LXMysql.cpp分别定义和编写关于pin语句的代码 //获取更新数据的sql语句 where语句中用户要包含where 更新std::string GetUpdatesql(XDATA kv, std::string table, std::string where); std::string LXMysql::GetUpdatesql(XDATA kv, std::string table, std…

机器学习之指数分布

指数分布&#xff1a; 指数分布可以用来表示独立随机事件发生的时间间隔。如果一个随机变量X的概率密度函数满足以下形式&#xff0c;就称X服从参数λ的指数分布&#xff0c;记作X ~ E(λ)或X~Exp&#xff08;λ&#xff09;。指数分布只有一个指数参数&#xff0c;且λ>0&a…

c++多态(3) -- 虚析构函数

代码: enum class _ANIMALS_TYPE {CAT,DOG,ANIMAL_COUNT };class Animal { public:Animal(_ANIMALS_TYPE type, int age,const char* name);~Animal();virtual void eat()const 0; private:_ANIMALS_TYPE type; // 动物类型int age; // 动物年龄char* na…

【Spring源码解读!底层原理进阶】【上】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《Spring 狂野之旅&#xff1a;底层原理高级进阶》 &#x1f680…

解决CORS错误(Spring Boot)

记录一下错误&#xff0c;以博客的形式 前言 跨域&#xff08;Cross-Origin&#xff09;是指在Web开发中&#xff0c;当一个Web应用试图从一个源&#xff08;域名、协议、端口组合&#xff09;获取资源时&#xff0c;该请求的目标与当前页面的源不同。具体来说&#xff0c;当一…

SpringBoot之事务源码解析

首先事务是基于aop的&#xff0c;如果不了解aop的&#xff0c;建议先去看下我关于aop的文章: Spring之aop源码解析  先说结论&#xff0c;带着结论看源码。首先&#xff0c;在bean的生命周期中&#xff0c; 执行实例化前置增强&#xff0c;会加载所有切面并放入缓存&#xff0…

Django模板(三)

一、标签URL 返回与给定视图和可选参数相匹配的绝对路径引用(不含域名的 URL) {% url some-url-name v1 v2 %} 第一个参数是url模式名称,后面跟着的是参数,以空格分隔可以使用关键字: {% url some-url-name arg1=v1 arg2=v2 %}如果您想检索命名空间的URL,请指定完全限定…

最大子数组和[中等]

一、题目 给定一个长度为n的环形整数数组nums&#xff0c;返回nums的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上&#xff0c;nums[i]的下一个元素是nums[(i 1) % n]&#xff0c;nums[i]的前一个元素是nums[(i - 1 n) % n]。 子数…