从零开始探索C语言(十一)----共用体和位域
文章目录
- 1. 共用体
- 1.1 定义共用体
- 1.2 访问共用体成员
- 2. 位域
- 2.1 位域声明
- 2.2 位域的定义和位域变量的说明
- 2.3 位域的使用
- 2.4 位域小结
1. 共用体
共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
1.1 定义共用体
为了定义共用体,必须使用 union 语句,方式与定义结构类似。
union 语句定义了一个新的数据类型,带有多个成员。
union 语句的格式如下:
union [union tag]
{member definition;member definition;...member definition;
} [one or more union variables];
union tag 是可选的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。
在共用体定义的末尾,最后一个分号之前,可以指定一个或多个共用体变量,这是可选的。下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str:
union Data
{int i;float f;char str[20];
} data;
现在,Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。
这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据,我们可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。
共用体占用的内存应足够存储共用体中最大的成员。例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。
下面的实例将显示上面的共用体占用的总内存大小:
#include <stdio.h>
#include <string.h>union Data
{int i;float f;char str[20];
};int main( )
{union Data data; printf( "Memory size occupied by data : %d\n", sizeof(data));return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Memory size occupied by data : 20
1.2 访问共用体成员
为了访问共用体的成员,我们使用成员访问运算符(.),成员访问运算符是共用体变量名称和我们要访问的共用体成员之间的一个句号。
我们可以使用 union 关键字来定义共用体类型的变量。
下面的实例演示了共用体的用法:
#include <stdio.h>
#include <string.h>union Data
{int i;float f;char str[20];
};int main( )
{union Data data; data.i = 10;data.f = 220.5;strcpy( data.str, "C Programming");printf( "data.i : %d\n", data.i);printf( "data.f : %f\n", data.f);printf( "data.str : %s\n", data.str);return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
// linux 系统下
data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming// windows系统下
data.i : 1917853763
data.f : 4122360580327794900000000000000.000000
data.str : C Programming
在这里,我们可以看到共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。
现在让我们再来看一个相同的实例,这次我们在同一时间只使用一个变量,这也演示了使用共用体的主要目的:
#include <stdio.h>
#include <string.h>union Data
{int i;float f;char str[20];
};int main( )
{union Data data; data.i = 10;printf( "data.i : %d\n", data.i);data.f = 220.5;printf( "data.f : %f\n", data.f);strcpy( data.str, "C Programming");printf( "data.str : %s\n", data.str);return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
data.i : 10
data.f : 220.500000
data.str : C Programming
在这里,所有的成员都能完好输出,因为同一时间只用到一个成员。
2. 位域
C 语言的位域(bit-field)是一种特殊的结构体成员,允许我们按位对成员进行定义,指定其占用的位数。
如果程序的结构中包含多个开关的变量,即变量值为 TRUE/FALSE,如下:
struct
{unsigned int widthValidated;unsigned int heightValidated;
} status;
这种结构需要 8 字节的内存空间,但在实际上,在每个变量中,我们只存储 0 或 1,在这种情况下,C 语言提供了一种更好的利用内存空间的方式。如果在结构内使用这样的变量,可以定义变量的宽度来告诉编译器将只使用这些字节。
例如,上面的结构可以重写成:
struct
{unsigned int widthValidated : 1;unsigned int heightValidated : 1;
} status;
现在,上面的结构中,status 变量将占用 4 个字节的内存空间,但是只有 2 位被用来存储值。
如果你用了 32 个变量,每一个变量宽度为 1 位,那么 status 结构将使用 4 个字节,但只要你再多用一个变量,如果使用了 33 个变量,那么它将分配内存的下一段来存储第 33 个变量,这个时候就开始使用 8 个字节。
让我们看看下面的实例来理解这个概念:
#include <stdio.h>
#include <string.h>/* 定义简单的结构 */
struct
{unsigned int widthValidated;unsigned int heightValidated;
} status1;/* 定义位域结构 */
struct
{unsigned int widthValidated : 1;unsigned int heightValidated : 1;
} status2;int main( )
{printf( "Memory size occupied by status1 : %d\n", sizeof(status1));printf( "Memory size occupied by status2 : %d\n", sizeof(status2));return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Memory size occupied by status1 : 8
Memory size occupied by status2 : 4
位域的特点和使用方法如下:
- 定义位域时,可以指定成员的位域宽度,即成员所占用的位数。
- 位域的宽度不能超过其数据类型的大小,因为位域必须适应所使用的整数类型。
- 位域的数据类型可以是 int、unsigned int、signed int 等整数类型,也可以是枚举类型。
- 位域可以单独使用,也可以与其他成员一起组成结构体。
- 位域的访问是通过点运算符(.)来实现的,与普通的结构体成员访问方式相同。
2.1 位域声明
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。
所谓"位域"是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
典型的实例:
用 1 位二进位存放一个开关量时,只有 0 和 1 两种状态。
读取外部文件格式——可以读取非标准的文件格式。例如:9 位的整数。
2.2 位域的定义和位域变量的说明
位域定义与结构定义相仿,其形式为:
struct 位域结构名
{位域列表};
其中位域列表的形式为:
type [member_name] : width ;
下面是有关位域中变量元素的描述:
type 只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。
member_name 位域的名称。
width 位域中位的数量。宽度必须小于或等于指定类型的位宽度。
带有预定义宽度的变量被称为位域。位域可以存储多于 1 位的数,例如,需要一个变量来存储从 0 到 7 的值,您可以定义一个宽度为 3 位的位域,如下:
struct
{unsigned int age : 3;
} Age;
上面的结构定义指示 C 编译器,age 变量将只使用 3 位来存储这个值,如果您试图使用超过 3 位,则无法完成。
struct bs{int a:8;int b:2;int c:6;
}data;
以上代码定义了一个名为 struct bs 的结构体,data 为 bs 的结构体变量,共占四个字节:
对于位域来说,它们的宽度不能超过其数据类型的大小,在这种情况下,int 类型的大小通常是 4 个字节(32位)。
相邻位域字段的类型相同,且其位宽之和小于类型的 sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止。
让我们再来看一个实例:
struct packed_struct {unsigned int f1:1;unsigned int f2:1;unsigned int f3:1;unsigned int f4:1;unsigned int type:4;unsigned int my_int:9;
} pack;
以上代码定义了一个名为 packed_struct 的结构体,其中包含了六个成员变量,pack 为 packed_struct 的结构体变量。
在这里,packed_struct 包含了 6 个成员:四个 1 位的标识符 f1…f4、一个 4 位的 type 和一个 9 位的 my_int。
让我们来看下面的实例,
实例 1:
#include <stdio.h>struct packed_struct {unsigned int f1 : 1; // 1位的位域unsigned int f2 : 1; // 1位的位域unsigned int f3 : 1; // 1位的位域unsigned int f4 : 1; // 1位的位域unsigned int type : 4; // 4位的位域unsigned int my_int : 9; // 9位的位域
};int main() {struct packed_struct pack;pack.f1 = 1;pack.f2 = 0;pack.f3 = 1;pack.f4 = 0;pack.type = 7;pack.my_int = 255;printf("f1: %u\n", pack.f1);printf("f2: %u\n", pack.f2);printf("f3: %u\n", pack.f3);printf("f4: %u\n", pack.f4);printf("type: %u\n", pack.type);printf("my_int: %u\n", pack.my_int);return 0;
}
以上实例定义了一个名为 packed_struct 的结构体,其中包含了多个位域成员。
在 main 函数中,创建了一个 packed_struct 类型的结构体变量 pack,并分别给每个位域成员赋值。
然后使用 printf 语句打印出每个位域成员的值。
输出结果为:
f1: 1
f2: 0
f3: 1
f4: 0
type: 7
my_int: 255
实例 2:
#include <stdio.h>
#include <string.h>struct
{unsigned int age : 3;
} Age;int main( )
{Age.age = 4;printf( "Sizeof( Age ) : %d\n", sizeof(Age) );printf( "Age.age : %d\n", Age.age );Age.age = 7;printf( "Age.age : %d\n", Age.age );Age.age = 8; // 二进制表示为 1000 有四位,超出printf( "Age.age : %d\n", Age.age );return 0;
}
当上面的代码被编译时,它会带有警告,当上面的代码被执行时,它会产生下列结果:

Sizeof( Age ) : 4
Age.age : 4
Age.age : 7
Age.age : 0
计算字节数:
实例
#include <stdio.h>struct example1 {int a : 4;int b : 5;int c : 7;
};int main() {struct example1 ex1;printf("Size of example1: %lu bytes\n", sizeof(ex1));return 0;
}
以上实例中,example1 结构体包含三个位域成员 a,b 和 c,它们分别占用 4 位、5 位和 7 位。
通过 sizeof 运算符计算出 example1 结构体的字节数,并输出结果:
Size of example1: 4 bytes
对于位域的定义尚有以下几点说明:
一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域,也可以有意使某位域从下一单元开始。
例如:
struct bs{unsigned a:4;unsigned :4; /* 空域 */unsigned b:4; /* 从下一单元开始存放 */unsigned c:4
}
在这个位域定义中,a 占第一字节的 4 位,后 4 位填 0 表示不使用,b 从第二字节开始,占用 4 位,c 占用 4 位。
位域的宽度不能超过它所依附的数据类型的长度,成员变量都是有类型的,这个类型限制了成员变量的最大长度,: 后面的数字不能超过这个长度。
位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k{int a:1;int :2; /* 该 2 位不能使用 */int b:3;int c:2;
};
从以上分析可以看出,位域在本质上就是一种结构类型,不过其成员是按二进位分配的。
2.3 位域的使用
位域的使用和结构成员的使用相同,其一般形式为:
位域变量名.位域名
位域变量名->位域名
位域允许用各种格式输出。
实例:
#include <stdio.h>int main(){struct bs{unsigned a:1;unsigned b:3;unsigned c:4;} bit,*pbit;bit.a=1; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */bit.b=7; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */bit.c=15; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* 以整型量格式输出三个域的内容 */pbit=&bit; /* 把位域变量 bit 的地址送给指针变量 pbit */pbit->a=0; /* 用指针方式给位域 a 重新赋值,赋为 0 */pbit->b&=3; /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */pbit->c|=1; /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指针方式输出了这三个域的值 */
}
编译执行结果:
1,7,15
0,3,15
上例程序中定义了位域结构 bs,三个位域为 a、b、c,说明了 bs 类型的变量 bit 和指向 bs 类型的指针变量 pbit,这表示位域也是可以使用指针的。
2.4 位域小结
位域是C语言中的一个高级特性,它允许你在结构体中以位为单位来分配内存,以便有效地存储和操作位数据。位域常用于处理硬件寄存器、压缩数据、或者在嵌入式系统中节省内存。以下是位域的使用详解:
-
位域的定义:位域是通过在结构体或联合体中的成员后面加上冒号和位宽来定义的。例如:
struct Flags {unsigned int flag1 : 1; // 1位用于表示flag1unsigned int flag2 : 2; // 2位用于表示flag2unsigned int flag3 : 3; // 3位用于表示flag3 }; -
位宽:位宽表示了每个位域所占用的位数。它必须是一个非负整数。
-
位域的取值和赋值:你可以像操作普通变量一样来操作位域的值。例如:
struct Flags myFlags; myFlags.flag1 = 1; // 设置flag1为1 myFlags.flag2 = 2; // 设置flag2为2 -
位域的范围:每个位域的位宽决定了它可以表示的数值范围。在上面的例子中,
flag1可以表示0或1,flag2可以表示0、1、2或3,flag3可以表示0到7。 -
注意位域的存储顺序:位域的存储顺序在不同编译器下可能不同。通常,它们从结构体的低位向高位存储,但这并不是C语言标准要求的。如果你需要确保特定的位域顺序,可以使用
#pragma pack等编译指令。 -
位域的注意事项:
- 不同编译器对位域的实现可能有所不同,特别是在不同硬件平台上。
- 位域应谨慎使用,因为它们可能导致可移植性问题。
- 位域不能取地址,也不能用于数组。
- 不同位域不能共享存储位置,因此它们不能重叠。
总的来说,位域是C语言中的一个强大工具,但在使用时需要注意编译器的实现细节和平台兼容性,以确保程序的可靠性和可移植性。
相关文章:
从零开始探索C语言(十一)----共用体和位域
文章目录 1. 共用体1.1 定义共用体1.2 访问共用体成员 2. 位域2.1 位域声明2.2 位域的定义和位域变量的说明2.3 位域的使用2.4 位域小结 1. 共用体 共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体&#…...
【数据结构】算法的时间复杂度
🦄个人主页:修修修也 🎏所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 一.算法时间复杂度定义 二.大O阶渐近表示法 🎏大O阶渐近表示法的定义 🎏推导大O阶方法 三.常见的时间复杂度 📌常数阶 &#x…...
Qt作业五
1、思维导图 https://www.zhixi.com/view/9e899ee0 2、作业 #include <iostream>using namespace std;class Animal { private:string name; public:Animal(){}Animal(string n):name(n){}virtual void perform()0; };class Lion:public Animal { public:void perform…...
【面试】pc寄存器题
目录 1.使用pc寄存器存储字节码指令地址有什么作用?(为什么使用pc寄存器记录当前线程的执行地址?)2.pc寄存器为什么被设定为线程私有的? 1.使用pc寄存器存储字节码指令地址有什么作用?(为什么使…...
ARM按键中断实验
设置按键中断,按键1按下,LED亮,再按一次,灭 按键2按下,蜂鸣器响。再按一次,不响 按键3按下,风扇转,再按一次,风扇停 src/do_irq.c #include "key_it.h" ex…...
C#的值类型和引用类型
不得不说c#的类型系统设计有点意思,不同的编程语言对于类型的设计各有取舍。 值类型: 当我们将一个int类型的值赋值到另一个int类型的值时,它实际上是创建了一个完全不同的副本。换句话说,如果你改变了其中某一个的值࿰…...
YOLOv7改进:极简的神经网络模型 VanillaNet---VanillaBlock助力检测,实现暴力涨点 | 华为诺亚2023
💡💡💡本文属于原创独家改进:极简模块VanillaBlock,以极简主义的设计为理念,网络中仅仅包含最简单的卷积计算,去掉了残差和注意力模块,二次创新引入到YOLOv7中取得了不俗的效果。 极简模块VanillaBlock | 亲测在多个数据集实现涨点; 收录: YOLOv7高阶自研专…...
对验证码的识别爆破
声明:该系列文章首发于公众号:Y1X1n安全,转载请注明出处!本公众号所分享内容仅用于每一个爱好者之间的技术讨论及教育目的,所有渗透及工具的使用都需获取授权,禁止用于违法途径,否则需自行承担&…...
LeetCode【15】三数之和
题目: 解析: 参考:https://zhuanlan.zhihu.com/p/111715985 代码: public static List<List<Integer>> threeSum(int[] nums) {// 先排序Arrays.sort(nums);List<List<Integer>> result new ArrayLis…...
Gossip协议是什么
Gossip协议是什么 Gossip protocol 也叫 Epidemic Protocol (流行病协议), 是基于流行病传播方式的节点或者进程之间信息交换的协议, 也被叫做流言算法, 八卦算法、疫情传播算法等等. 说到 Gossip 协议, 就不得不提著名的六度分隔理论. 简单地说, 你和任何一个陌生人之间所间…...
【java学习】this关键字(27)
文章目录 1. this是什么?2. this的作用 1. this是什么? 在 java 中,this关键字比较难理解,它的作用和其词义很接近。 ①它在方法内部使用,即这个方法所属对象的引用; ②它在构造器内部使用,表示…...
27、元组
区分: 数组:纯粹 一个[]中的数据类型都是一致的 元组:不纯粹 一个[]中可能有不同类型的数据项 意义 当赋值或访问一个已知索引的元素时,可以得到正确的类型 let miao: [string, number] [cat, 18]; miao[0] cat miao[1] 18…...
1km分辨率逐月降雨量和最高温度数据集(1901-2022)--数据处理
1km分辨率逐月降雨量和最高温度数据集(1901-2022)的下载可以参考我的另外一篇博客: 这里的温度和降雨数据集都是NC格式的,需要将其处理为tif格式,我采用的处理软件是MATLAB。 本篇博客以处理温度数据为例,…...
docker入门加实战—docker常见命令
docker入门加实战—docker常见命令 在介绍命令之前,先用一副图形象的展示一下docker的命令: 常见命令 docker的常见命令和文档地址如下表: 命令说明文档地址docker pull拉取镜像docker pulldocker push推送镜像到DockerRegistrydocker pus…...
【C/C++】使用 g++ 编译器编译 C++ 程序的完全指南
本文介绍了 g 编译器的使用方法和常见参数解释,帮助您编译和构建 C 程序。 引言 在 C 程序开发中,选择一个合适的编译器是至关重要的。g 是 GNU 编译器集合(GCC)中的 C 编译器,提供了丰富的功能和选项,帮…...
ARM中断实验
设置按键中断,按键1按下,LED亮,再按一次,灭 按键2按下,蜂鸣器响。再按一次,不响 按键3按下,风扇转,再按一次,风扇停 main.c #include "uart1.h" #include …...
Vue条件渲染
一、使用v-show条件渲染 语法格式: v-show"表达式" // true 或 false 当表达式的值为true的时候就显示,表达式值为false的时候隐藏。 下面是使用v-show实现的一个点击按钮切换显示和隐藏的小案例 : 值得注意的是,使…...
k8s中如何使用gpu、gpu资源讲解、nvidia gpu驱动安装
前言 环境:centos7.9、k8s 1.22.17、docker-ce-20.10.9 gpu资源也是服务器中常见的一种资源,gpu即显卡,一般用在人工智能、图文识别、大模型等领域,其中nvidia gpu是nvidia公司生产的nvidia类型的显卡,amd gpu则是adm…...
VRRP 虚拟路由器冗余协议的解析和配置
VRRP的解析 个人简介 原理和HSRP的差不多,少了一些状态就只有了三种状态 还有不同的就是VRRP严格按照抢占要求 一个VRRP组中具有最高优先级的设备成为Master路由器缺省优先级为100若优先级相同,具有最高接口IP地址最大的路由器成为Master路由器抢占(Pr…...
旅游网站HTML
代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>旅游网</title> </head> <body><!--采用table编辑--> <!--最晚曾table,用于整个页面那布局--><table width&q…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解
一、前言 在HarmonyOS 5的应用开发模型中,featureAbility是旧版FA模型(Feature Ability)的用法,Stage模型已采用全新的应用架构,推荐使用组件化的上下文获取方式,而非依赖featureAbility。 FA大概是API7之…...
倒装芯片凸点成型工艺
UBM(Under Bump Metallization)与Bump(焊球)形成工艺流程。我们可以将整张流程图分为三大阶段来理解: 🔧 一、UBM(Under Bump Metallization)工艺流程(黄色区域ÿ…...
JS红宝书笔记 - 3.3 变量
要定义变量,可以使用var操作符,后跟变量名 ES实现变量初始化,因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符,可以创建一个全局变量 如果需要定义…...
