C语言超详细讲解结构体与联合体的使用

目录
  • 结构体
  • offsetof-宏
  • 位段
  • 枚举
  • 联合体(共用体)

结构体

结构体内存对齐问题:

当我们在计算结构体的大小时,我们便需要清楚的知道结构体内存对齐是什么。

存在内存对齐的原因可细分为两个:

平台原因:

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

性能原因:

首先内存对齐可以提高程序的性能,当访问未对其的内存空间时,有时候处理器需要进行两次访问,而当访问对齐的内存时,只需要一次就够了。这同时也被叫做 用空间换取时间。

结构体的对齐规则:

1.第一个成员在与结构体变量偏移量为0的地址处。

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

对齐数=编译器默认的一个对齐数 与 该成员大小的较小值。(VS中默认的值为8)

3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

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

举例1:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	struct s1
	{
		char c1; // 1字节
		int i;   // 4字节
		char c2; // 1字节
	};
	printf("%d\n", sizeof(struct s1));
}

输出结果为:

解释如下:

我们易知内存会为结构体开辟一块空间来给结构体存储数据,从而我们可以用下图的方式将该空间给表示出来:

举例2:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	struct s2
	{
        int i;  // 4字节
		char c1;// 1字节
		char c2;// 1字节
	};
	printf("%d\n", sizeof(struct s2));
}

输出结果为:

解释如下:

举例3:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
struct s3
{
	double d; // 8字节
	char c;   // 1字节
	int i;    // 4字节
};
struct s4
{
	char c1;   // 1字节
	struct s3; // 16字节
	double d;  // 8字节
};
int main()
{
	printf("%d\n", sizeof(struct s4));
	return 0;
}

输出结果为:

解释如下:

结论总结:

当我们想内存对齐的同时也想节省空间时,可以将空间小的变量集中在一起!!

offsetof-宏

用途:计算结构体成员相对于起始位置的偏移量的

注意:使用该函数时,应该引用头文件 #include <stddef.h>

举例:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stddef.h>
struct s1
{
	char c1;
	int i;
	char c2;
};
int main()
{
	printf("%u\n", offsetof(struct s1,c1));
	printf("%u\n", offsetof(struct s1, i));
	printf("%u\n", offsetof(struct s1, c2));
}

输出结果为:

位段

位段的成员类型必须为: int、unsigned int、signed int

位段的空间是按照需要以4个字节(int)或者1个字节(char )的方式来开辟的

位段的成员名后边有一个冒号和一个数字!

举例1:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
struct s5
{              // 位段所代表的意思
	int _a : 2; // _a 占 2个bit位
	int _b : 5; // _b 占 5个bit位
	int _c : 10;// _c 占 10个bit位
	int _d : 30;// _d 占 30个bit位
};
int main()
{
	printf("%d\n", sizeof(s5));
	return 0;
}

输出结果为:( 原本的字节大小为 16 字节 =16*8=128 bit  现在的字节大小为 8字节且只占 2+5+10+30 = 47bite)

那为啥不是占用7字节呢?7字节有 7*8=56bite 也够使用啊?

我们便需要根据位段的规定来解释,当为(int)类型时内存空间每次都是以4字节的大小来开辟空间的,当为(char)类型时内存空间每次都是以1字节的大小来开辟空间的,所给例子为int类型,当所定的第一个4字节空间不够用时,便会再开辟一块大小为4字节的空间来供其存储,从而输出结果为8字节。

结论:

我们可以根据所定义的整型数字大小,利用位段来给其分配相适应大小的空间,从而有效的帮我们进行内存空间的节省。

举例2:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

我们想要知道位段在(VS编译器)内存中是如何存储的,便可以打开监控来进行调试

如图所示:

(结构体变量s 刚开始初始化了 3字节)

(结构体变量s 经过赋值之后存储数值的变化)

解释如下图:

位段的跨平台问题:

1. int 位段被当成有符号数还是无符号数是不确定的。

2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32),写成27,在16位机器会出问题。

3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

结论:

位段涉及很多不确定因素,位段是不垮平台的,注重可移植的程序应该避免使用位段。

枚举

枚举类型是某类数据可能取值的集合

例子:一周内星期的取值为7天,可以一一列举出来

定义方式及使用方式:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
enum day
{
	Mon,
    Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
int main()
{
	enum day s1 = Mon;
	enum day s2 = Sat;
	return 0;
}

举例1:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
enum color
{
	blue,
	green,
	yellow
};
int main()
{
	printf("%d\n", blue);
	printf("%d\n", green);
	printf("%d\n", yellow);
	return 0;
}

输出结果为: (由结果知枚举常量会被自动从0开始一次往下赋值)

拓展:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
enum color
{
	blue=4,
	green=7,
	yellow
};
int main()
{
	printf("%d\n", blue);
	printf("%d\n", green);
	printf("%d\n", yellow);
	return 0;
}

输出结果为:(由此可知枚举常量我们可以自定义赋值,未赋值常量为其上一常量的值+1)

#define 也可以用来定义常量,那用枚举来定义常量的优点为:

1.增加代码的可读性和可维护性

2. 和#define定义的标识符比较枚举有类型检查,更加严谨

3.防止了命名污染(封装)

4. 便于调试

5. 使用方便,一次可以定义多个常量

联合体(共用体)

特点:

各成员共享一段内存空间,一个 联合变量的长度等于各成员中最长的长度。

举例1:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
union Un
{
	char c;
	int i;
};
int main()
{
	union Un u1;
	printf("%d\n", sizeof(u1));
	printf("%p\n", &u1);
	printf("%p\n", &u1.c);
	printf("%p\n", &u1.i);
}

输出结果为:

如图所示:

应用:(用来判断编译器是大端存储还是小端存储)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int chek_sys()
{
	union Un //创建一个联合体 char c 和 int i 共用同一块存储空间
	{
		char c;
		int i;
	}u;
	u.i = 1;    // 这里给i赋值为1
	            //若为小端存储时内存中所存储:01 00 00 00(16进制)  为大端存储: 00 00 00 01(16进制)
	return u.c; //这里直接返回 char c 与 int i 所共用空间的值
	            //返回1字节大小的值 即 int i 以16进制方式所存储的前两位数字 ,若值为1则为小端 若值为0则为大端
}
int main()
{
	if (1 == chek_sys())
	{
		printf("小端\n");
    }
	else
	{
		printf("大端\n");
	}
	return 0;
}

输出结果:

举例2:(计算联合体的大小)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
union Un
{
	char arr[5];//5字节
	int i;     //4字节
};
int main()
{
	printf("%d\n", sizeof(union Un));
	return 0;
}

输出结果为:(我们从结果得知联合体也存在内存对齐)

解释:

char类型数组先占用5字节,其对齐数为1字节(char),int i占用4字节与char类型数组公用同一块空间,其对齐数为4字节(int),该联合体所占5字节,但存在内存对齐,需为4字节的倍数,从而要浪费3字节空间使其为8字节。

以上便是关于结构体和联合体的全部内容 !

如有错误或者能改进的地方 请各大佬指出 我会及时改正!!

到此这篇关于C语言超详细讲解结构体与联合体的使用的文章就介绍到这了,更多相关C语言结构体与联合体内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言中结构体、联合体的成员内存对齐情况

    前言 最近项目进行中,遇到一个小问题,在数据协议传输过程中,我为了方便解析,就定义了一个结构体,在数据的指针传入函数的时候,我用定义好的结构体进行强制转化,没想到一直解析失败,调试很久,终于反应过来,在用结构体指针对数据强制转换时,定义结构体我没有注意到数据对齐,因为在底层实现中,我传入的数据buffer是排列整齐的,而强制转化的结构体格式中,我定义的时候没有使用__attribute__((__packed__))或者__packed强制数据对齐,导致结构体成员真实排列会按照成员中最大的变量的

  • C语言自定义类型详解(结构体、枚举、联合体和位段)

    目录 前言 一.结构体 1.结构体类型的声明 2.结构体的自引用 3.结构体变量的定义和初始化 4.结构体内存对齐 5.结构体传参 二.位段 1.位段的定义 2.位段的内存分配 3.位段的应用 三.枚举 1.枚举类型的定义 2.枚举的优点 3.枚举的使用 四.联合体(共用体) 1.联合体的定义 2.联合体的特点 3.联合体的大小计算 总结 前言 一.结构体 1.结构体类型的声明 当我们想要描述一个复杂变量--学生,可以这样声明. ✒️代码展示: struct Stu { char name[20

  • C语言自定义类型超详细梳理之结构体 枚举 联合体

    目录 一.什么是结构体 1.结构体实现 2.匿名结构体类型 3.结构体自引用 4.结构体的内存对齐 5.结构体位段  二.什么是枚举 1.枚举类型的定义 2.枚举的优点 三.联合(共用体) 1.什么是联合(共用体) 2.联合(共用体)的定义 3.联合(共用体)的初始化 总结 一.什么是结构体 结构是一些值的集合,这些值称为成员变量.结构的每个成员可以是不同类型的变量. //结构体声明 struct tag //struct:结构体关键字,tag:标签名,合起来是结构体类型(类型名) { memb

  • C语言结构体,枚举,联合体详解

    目录 1.什么是结构体.枚举.联合体 2.定义结构体 2.1 包含结构体成员变量.variable 2.2 tag.结构体成员变量 2.3 用结构体声名变量 2.4 用typedef 创建新类型 2.5 两个结构体相互包含 2.6 结构体变量初始化 2.7 结构体指针 3.枚举 3.1 定义方式 3.2 为什么用枚举 3.3 枚举变量的定义 3.4 实例 3.5 枚举实际用途 4.联合体 4.1 与结构体区别 4.2 定义 总结 1.什么是结构体.枚举.联合体 结构体(struct)是由一系列具

  • C语言超详细讲解结构体与联合体的使用

    目录 结构体 offsetof-宏 位段 枚举 联合体(共用体) 结构体 结构体内存对齐问题: 当我们在计算结构体的大小时,我们便需要清楚的知道结构体内存对齐是什么. 存在内存对齐的原因可细分为两个: 平台原因: 不是所有的硬件平台都能方位任意地址上的任意数据:某些硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常. 性能原因: 首先内存对齐可以提高程序的性能,当访问未对其的内存空间时,有时候处理器需要进行两次访问,而当访问对齐的内存时,只需要一次就够了.这同时也被叫做 用空间换取

  • C语言 超详细讲解算法的时间复杂度和空间复杂度

    目录 1.前言 1.1 什么是数据结构? 1.2 什么是算法? 2.算法效率 2.1 如何衡量一个算法的好坏 2.2 算法的复杂度 2.3 复杂度在校招中的考察 3.时间复杂度 3.1 时间复杂度的概念 3.2 大O的渐进表示法 3.3 常见时间复杂度计算举例 4.空间复杂度 5. 常见复杂度对比 1.前言 1.1 什么是数据结构? 数据结构(Data Structure)是计算机存储.组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合. 1.2 什么是算法? 算法(Algorit

  • C语言超详细讲解数据结构中双向带头循环链表

    目录 一.概念 二.必备工作 2.1.创建双向链表结构 2.2.初始化链表 2.3.动态申请节点 2.4.打印链表 2.5.销毁链表 三.主要功能 3.1.在pos节点前插入数据 尾插 头插 3.2.删除pos处节点数据 尾删 头删 3.3.查找数据 四.总代码 List.h 文件 List.c 文件 Test.c 文件 五.拓展 一.概念 前文我们已经学习了单向链表,并通过oj题目深入了解了带头节点的链表以及带环链表,来画张图总体回顾下: 在我们学习的链表中,其实总共有8种,都是单双向和带不带

  • C语言超详细讲解队列的实现及代码

    目录 前言 队列的概念 队列的结构 队列的应用场景 队列的实现 创建队列结构 队列初始化 队列销毁 入队列 出队列 队列判空 获取队列元素个数 获取队列头部元素 获取队列尾部元素 总代码 Queue.h 文件 Queue.c 文件 Test.c 文件 前言 队列的概念 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头 队列和前文所学的栈

  • C语言超详细讲解文件的操作

    目录 一.为什么使用文件 二.什么是文件 1.程序文件 2.数据文件 3.文件名 三.文件指针 四.文件的打开和关闭 五.文件的顺序读写 六.文件的随机读写 fseek ftell rewind 七.文件结束判定 一.为什么使用文件 当我们写一些项目的时候,我们应该要把写的数据存储起来.只有我们自己选择删除数据的时候,数据才不复存在.这就涉及到了数据的持久化的问题,为我们一般数据持久化的方法有,把数据存在磁盘文件.存放到数据库等方式.使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久

  • C语言超详细讲解顺序表的各种操作

    目录 顺序表是什么 顺序表的结构体 顺序表的接口函数 顺序表相关操作的菜单 顺序表的初始化 添加元素 陈列元素 往最后加元素 往前面加元素 任意位置加元素 删除最后元素 删除前面元素 删除任意元素 整体代码(fun.h部分) 整体代码(fun.cpp部分) 整体代码(主函数部分) 结果展示 顺序表是什么 顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素.使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数

  • C语言超详细讲解数据结构中的线性表

    目录 前言 一.分文件编写 1.分文件编写概念 2.代码展示 二.动态分布内存malloc 1.初识malloc 2.使用方法 三.创建链表并进行增删操作 1.初始化链表 2.在链表中增加数据 3.删除链表中指定位置数据 四.代码展示与运行效果 1.代码展示 2.运行效果 总结 前言 计算机专业都逃不了数据结构这门课,而这门课无疑比较难理解,所以结合我所学知识,我准备对顺序表做一个详细的解答,为了避免代码过长,采用分文件编写的形式,不仅可以让代码干净利落还能提高代码可读性,先解释部分代码的含义,

  • C语言超详细讲解栈与队列实现实例

    目录 1.思考-1 2.栈基本操作的实现 2.1 初始化栈 2.2 入栈 2.3 出栈 2.4 获取栈顶数据 2.5 获取栈中有效元素个数 2.6 判断栈是否为空 2.7 销毁栈 3.测试 3.1测试 3.2测试结果 4.思考-2 5.队列的基本操作实现 5.1 初始化队列 5.2 队尾入队列 5.3 队头出队列 5.4 队列中有效元素的个数 5.5 判断队列是否为空 5.6 获取队头数据 5.7 获取队尾的数据 5.8 销毁队列 6.测试 6.1测试 6.2 测试结果 1.思考-1 为什么栈用

  • C语言超详细讲解栈的实现及代码

    目录 前言 栈的概念 栈的结构 栈的实现 创建栈结构 初始化栈 销毁栈 入栈 出栈 获取栈顶元素 获取栈中有效元素个数 检测栈是否为空 总代码 Stack.h 文件 Stack.c 文件 Test.c 文件 前言 栈的概念 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作.进行数据插入和删除操作的一端称为栈顶,另一端称为栈底.栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则.有点类似于手枪弹夹,后压进去的子弹总是最先打出,除非枪坏了. 压栈:栈的插入

  • C语言超详细讲解指向函数的指针

    目录 一.函数的指针 二.指向函数的指针变量 三.调用函数的两种方式 四.指向函数的指针的作用 五.用指向函数的指针作函数参数(重点) 六.为什么要将指向函数的指针变量作为函数的形参(重点) 一.函数的指针 首先,函数名代表函数的起始地址,调用函数时,程序会从函数名获取到函数起始地址,并从该地址起执行函数中的代码,函数名就是函数的指针,所以我们可以定义一个指向函数的指针变量,用来存放函数的起始地址,这样一来,就可以通过该变量来调用其所指向的函数. 二.指向函数的指针变量 定义指向函数的指针变量

随机推荐