《C语言杂记》C语言结构体和联合体详解
Posted Bruceoxl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《C语言杂记》C语言结构体和联合体详解相关的知识,希望对你有一定的参考价值。
1结构体概述
C 语言中有很多数据类型,数据类型决定了变量存储占用的空间,以及如何解释存储的位模式。像 int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它称为复杂数据类型或构造数据类型,它允许存储不同类型的数据项。
例如对于学生信息登记表,姓名为字符串,学号为整数,年龄为整数,所在的学习小组为字符,成绩为小数,因为数据类型不同,显然不能用一个数组来存放。这就可以使用结构体(Struct)来存放学生信息中不同类型的数据。最终学生信息的存储结构定义如下:
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
};
stu 为结构体名,它包含了 5 个成员,分别是 name、num、age、group、score。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。
【注】大括号后面的分号;不能少,这是一条完整的语句。
结构体的定义形式为:
struct 结构体名{
结构体所包含的变量或数组
};
可以说,结构体就是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member)。
1.1结构体的与使用
既然结构体是也是一种数据类型,那么就可以用它来定义变量。
以前文定义的学生为例:
struct stu stu1, stu2;
定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。注意关键字struct不能少。
当然,也可以在定义结构体的同时定义结构体变量:
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
}; stu1, stu2;
将变量放在结构体定义的最后即可。
在实际应用过程中,也可使用关键词typedef来修饰结构体,也就是将结构体类型定义一个新名字,方便使用。
还是以学生为例:
typedef struct{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
}STSTU;
接下来使用定义学生类型:
STSTU stStu;
【注】typedef 本身是一种存储类的关键字,与 auto、extern、static、register 等关键字不能出现在同一个表达式中。
普通结构体使用点号.获取单个成员。获取结构体成员的一般格式为:
结构体变量名.成员名;
通过上面的格式就可以获取成员的值,和给成员赋值,看下面的栗子:
/**
******************************************************************************
* @file main.c
* @author BruceOu
* @version V1.0
* @date 2021-09-05
* @blog https://blog.bruceou.cn/
* @Official Accounts 嵌入式实验楼
* @brief
******************************************************************************
*/
/**Include**********************************************************************/
#include <stdio.h>
typedef struct{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
}STSTU;
/**
* @brief main
* @param None
* @retval int
*/
int main(void)
{
STSTU stStu;
//给结构体成员赋值
stStu.name = "BruceOu";
stStu.num = 01;
stStu.age = 18;
stStu.group = 'A';
stStu.score = 199.5;
//打印结构体成员的值
printf("name: %s, num: %d, age: %d, group: %c, score: %.1f\\n",
stStu.name, stStu.num, stStu.age, stStu.group, stStu.score);
return 0;
}
编译运行结果如下:
结构的使用还很简单的,只是需要注意结构体赋值需要一个一个赋值,和数组是一样的,只是数组的所有类型相同罢了。
当然啦,指针也可以指向一个结构体,只是结构体指针使用点号->获取单个成员。
定义的形式一般为:
struct 结构体名 *变量名;
看个例子吧:
/**
******************************************************************************
* @file main.c
* @author BruceOu
* @version V1.0
* @date 2021-09-05
* @blog https://blog.bruceou.cn/
* @Official Accounts 嵌入式实验楼
* @brief
******************************************************************************
*/
/**Include**********************************************************************/
#include <stdio.h>
typedef struct{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
}STSTU;
/**
* @brief main
* @param None
* @retval int
*/
int main(void)
{
STSTU stStu;
//给结构体成员赋值
stStu.name = "BruceOu";
stStu.num = 01;
stStu.age = 18;
stStu.group = 'A';
stStu.score = 199.5;
STSTU *pstStu = &stStu;
//打印结构体成员的值
printf("name: %s, num: %d, age: %d, group: %c, score: %.1f\\n",
pstStu->name, pstStu->num, pstStu->age, pstStu->group, pstStu->score);
return 0;
}
编译运行结果如下:
其运行结果还是一样的。
1.2结构体存储
结构体和数组不同,结构体中的数据类型大都是不同的,因此长度一般也是不一样的,因此在使用结构体时,特别要注意的结构的存储情况。结构体占用的内存大小,首先和编译器的系统位数有关系,类似于CPU是 64 bits 还是 32 bits 的情形;其次,结构体需要考虑字节对齐的问题。结构体实际上占用的内存大小,可以使用sizeof 进行获取,默认为4字节对齐的大小。
这里还是以学生的例子为例:
/**Include**********************************************************************/
#include <stdio.h>
typedef struct{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
}STSTU;
/**
* @brief main
* @param None
* @retval int
*/
int main(void)
{
STSTU stStu;
printf("size: %d\\n", (int)sizeof(stStu));
return 0;
}
编译运行结果如下:
可以看到STSTU的实际大小并没有24个字节,当时计算机计算出来的大小却是24,这是因为编译器按照4字节进行对其,牺牲空间来换去更多的访问时间。
当然我们也可以关闭填充来压缩空间。
/**Include**********************************************************************/
#include <stdio.h>
#pragma pack(1) //关闭填充
typedef struct{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
}STSTU;
/**
* @brief main
* @param None
* @retval int
*/
int main(void)
{
STSTU stStu;
printf("size: %d\\n", (int)sizeof(stStu));
return 0;
}
编译运行结果如下:
可以看到结构中的char就被压缩了。
2联合体概述
union,中文名“联合体、共用体”,在某种程度上类似结构体struct的一种数据结构,共用体(union)和结构体(struct)同样可以包含很多种数据类型和变量。不过区别也挺明显:结构体(struct)中所有变量是“共存”的;缺点是struct内存空间的分配是粗放的,不管用不用,只要定义了就会分配存储空间。而联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”;但优点是内存使用更为精细灵活,反而节省了内存空间。
2.1联合体的定义与使用
和结构体一样的,也是包含了多种数据类型。
联合体的定义形式为:
union 联合体名{
联合体所包含的变量或数组
};
下面以一个实例说明。
/**
******************************************************************************
* @file main.c
* @author BruceOu
* @version V1.0
* @date 2021-09-05
* @blog https://blog.bruceou.cn/
* @Official Accounts 嵌入式实验楼
* @brief
******************************************************************************
*/
/**Include**********************************************************************/
#include <stdio.h>
typedef union{
char c;
int i;
}UNNUM;
/**
* @brief main
* @param None
* @retval int
*/
int main(void)
{
UNNUM unNum;
unNum.c = 10;
printf("c addr: %p, c = %d\\n", &unNum.c, unNum.c);
printf("i addr: %p, i = %d\\n", &unNum.i, unNum.i);
unNum.i = 10;
printf("c addr: %p, c = %d\\n", &unNum.c, unNum.c);
printf("i addr: %p, i = %d\\n", &unNum.i, unNum.i);
return 0;
}
编译运行结果如下:
可以看到联合体中两种数据类型的地址是相同的,然而给联合体中不同数据赋值结果却又差异,这是因为两种数据类型共用一个存储空间,PC是小端存储,也就是把数值的高位字节放在高位的地址上,低位字节放在低位地址上。联合体变量总是从低地址存储,从这里也可以看出PC是小端存储的。
2.2联合体的存储
前边验证了,union的首地址是固定的,那么,union到底总共有多大?
下面还是通过实际的例子来说明:
/**Include**********************************************************************/
#include <stdio.h>
typedef union{
char c;
int i;
}UNNUM;
/**
* @brief main
* @param None
* @retval int
*/
int main(void)
{
UNNUM unNum;
printf("size: %d\\n", (int)sizeof(unNum));
return 0;
}
编译运行结果如下:
可以看出,这里是按照最大的数据长度计算的。
当然啦,这里的数据优点少,不足以证明,那么换个例子:
/**Include**********************************************************************/
#include <stdio.h>
typedef union{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
}UNSTU;
/**
* @brief main
* @param None
* @retval int
*/
int main(void)
{
UNSTU unStu;
printf("size: %d\\n", (int)sizeof(unStu));
return 0;
}
编译运行结果如下:
可以看到,长度为8,这下没啥疑问了。
3结构体和联合体实用技巧
前面的内容讲解了结构的基本用法。下面将根据结构体的特性来讲解结构体的妙用。
3.1大小端判断
我们可以使用联合体和结构体来判断大小端。因为大端是指低字节存储在高地址;小端存储是指低字节存储在低地址,正好可以利用联合体重不同数据类型共用存储空间来区分大小端。
关于大小端的详细讲解,请看笔者博客:
这里只给出大小端验证的例子。
/**
******************************************************************************
* @file main.c
* @author BruceOu
* @version V1.0
* @date 2021-09-05
* @blog https://blog.bruceou.cn/
* @Official Accounts 嵌入式实验楼
* @brief
******************************************************************************
*/
/**Include**********************************************************************/
#include <stdio.h>
typedef enum{
false,true
}bool;
typedef unsigned int uint32_t;
typedef unsigned char uint8_t;
typedef union
{
uint32_t nNum;
struct
{
uint8_t nByte0;
uint8_t nByte1;
uint8_t nByte2;
uint8_t nByte3;
}stByte;
}UNNUM;
bool IsBigEndian(void)
{
UNNUM unNum;
unNum.nNum = 0x12345678;
if(unNum.stByte.nByte0 == 0x12 )
{
return true;
}
return false;
}
/**
* @brief main
* @param None
* @retval int
*/
int main(void)
{
if(IsBigEndian())
{
printf("Big endian\\n");
}
else
{
printf("Little endian\\n");
}
return 0;
}
编译运行结果如下:
从结果可以看出,PC是小端,和其文的分析是相符和。
3.2数据拆分传输
我门在项目开发过程中,经常需要进行数据通信,包含一些协议头尾、包长、有效数据、校验等内容和浮点数的传输。为了保证数据的完整性,经常需要进行一系列的封装,这时结构体和联合体。
常用协议的传输的结构定义如下:
typedef unsigned char uint8_t;
#define BUFF_SIZE 128
typedef union
{
uint8_t nData[BUFF_SIZE];
struct
{
uint8_t nByte0;
uint8_t nByte1;
uint8_t nByte2;
uint8_t nByte3;
...
}stByte;
}UNDATA;
浮点数传输结构体定义如下:
typedef unsigned char uint8_t;
typedef union
{
float fData;
struct
{
uint8_t nByte0;
uint8_t nByte1;
uint8_t nByte2;
uint8_t nByte3;
}stByte;
}UNDATA;
3.3管理数据结构
在大型项目中,常常有复杂的数据结构。还是举个实际例子吧,某个消息集定义如下:
/* Message Frame for all the message. */
typedef struct _MSG_MessageFrame_st
{
MSG_MessageFrame_ID_en messageid;
union msg_un
{
MSG_BasicSafetyMessage_st msg_bsm;
MSG_MapData_st msg_mapdata;
MSG_RoadSideInformation_st msg_rsi;
MSG_RoadSideSafetyMessage_st msg_rsm;
MSG_SPAT_st msg_spat;
}msg;
}MSG_MessageFrame_st, * MSG_MessageFrame_st_ptr;
联合体中定义了不同消息类型,但是一次只会使用一类消息,因此这样就节省了内存空间,在实际项目中,这样的例子还有很多,这里就不再列举了。
欢迎访问我的网站
BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
BruceOu的知乎
欢迎订阅我的微信公众号
以上是关于《C语言杂记》C语言结构体和联合体详解的主要内容,如果未能解决你的问题,请参考以下文章