《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语言结构体和联合体详解的主要内容,如果未能解决你的问题,请参考以下文章

C语言用例子一次性讲清楚结构体和联合体区别,xdm看过来!

C和指针之结构体和联合体

c语言 结构和联合

C语言 匿名联合体和匿名结构体

C语言—联合体/共用体

C语言—联合体/共用体