结构体,联合体,枚举详解

结构体,联合体,枚举详解

目录

  1. 结构体
  2. 联合体
  3. 枚举常量

1.结构体

在C语言中,系统已经提供了一些数据类型,比如float,int,double,char等,但是我们有时会需要更复杂的类型来描述一个数据。假如我想描述一个学生,我需要知道他的学号,姓名,性别,那么我就需要三个类型来分别保存他的信息,到时候需要信息时还得分别查找,显然这是不方便的。因此有了结构体,我们可以根据自己所需来定义数据类型。

简言之,结构体是由不同的类型数据组成的组合型数据结构。

  • 结构体类型的声明以及定义

    struct 结构体名

    {

    成员列表;

    }结构体变量名;

    这就是结构体声明的一个基本形式,写个结构体给大家看看。

    struct students
    {
        char name [20];
        int xuehao[20];
        char xinbie[5];
    }a,b,c;
    

    struct students 为结构体名,这相当于,我们熟知的int ,char,float,就是类型的意思。

    { }括号里面的是成员列表,需要注意的是这是结构体声明,我们不可以在里面赋值,这从逻辑上就是错误的,我们想要赋值,只能给变量赋值,怎么能给类型赋值呢。

    然后是,a,b,c 这是结构体变量名,我们可以对它们进行赋值。

    还要提一嘴的是一种特殊的定义,就是不加结构体名。

    struct 
    {
      char a[20];
      int b;
    }w,q;
    

    这样定义会导致一种情况,就是无法再继续定义其他结构体变量,就是说有且仅有w,q两个变量。

  • 结构体的初始化和引用

    结构体的初始化有两种方式,一种是再定义结构体时进行初始化,一种是建立结构体变量,在去初始化。

    struct students
    {
        char name [20];
        int xuehao[20];
        char xinbie[5];
    }a={"张三"12345667"男"};
    int main()
    {
        struct students b={"李四"233244"男"}return 0;
    }
    

    这两种方式都可以,唯一的区别在于,a是全局变量,b是临时变量。

    结构体的引用也有两种方式一种是直接引用,另一种是通过指针来引用。

    struct students
    {
        char name [20];
        int xuehao[20];
        char xinbie[5];
    }a={"张三"12345667"男"};
    int main()
    {
        struct students b={"李四"233244"男"}printf("%s %d %s",b.name,b.xuehao,b.xinbie);
        return 0;
    }
    

    这就是直接引用,通过 变量名.成员名 的方式来引用,. 这个点是解引用操作符。

    struct students
    {
        char name [20];
        int xuehao[20];
        char xinbie[5];
    }a={"张三"12345667"男"};
    int main()
    {
        struct students b={"李四"233244"男"};
        struct students *p=&b;
        printf("%s %d %s",p->name,p->xuehao,p->xinbie);
        return 0;
    }
    

    通过指针的形式也可以引用,只不过,需要换个操作符 ->。

  • 结构体内存对齐

    结构体的所占内存的大小取决于什么,我们如果不清楚结构体内存对齐,那么可能会直接将结构体的成员变量所占内存加起来。其实这是错误的。接下来我们就一步一步的解析。

    先上代码。

    #include<stdio.h>
    int main()
    {
    	struct a
    	{
    		char b;//1字节
    		int i;//4字节
    		char c;//1字节
    	};
    	printf("%d ", sizeof(struct a));
    	return 0;
    }
    

    大家可以想一波。

    运行结果如下:
    在这里插入图片描述

    这就用到内存对齐的知识解决了。先说说内存对齐的规则:

    1. 第一个成员变量在与结构体偏移量为0的地址处。
    2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    3. 结构体总大小为最大对齐数的整数倍。
    4. 嵌套了结构体的情况,结构体的总大小就是包括嵌套结构体成员变量的最大对齐数的整数倍。

对齐数=编译器默认的一个对齐数于该成员大小的较小值。

vs环境下,对齐数的默认值为8。

linux中的默认值为4。

了解了规则,我们来说上题:

在这里插入图片描述

首先首成员变量b,放在起始位置(偏移量为0),接下来是i,int i是4个字节,和vs环境默认的对齐数8比较取得较小值,所以对齐数4,要放在偏移量为4的倍数上,所以往下走就到了偏移量为4的位置处。此处浪费了3个字节。char c的字节是1,对齐数也是1,所以直接放在int i变量下面。加起来就是9字节,根据规则总大小为最大对齐数的整数倍,所以就是12字节。

再来一道题,

#include<stdio.h>
int main()
{
	struct a
	{
		char b;//1字节
		char c;//1字节
        int i;//4字节
	};
	printf("%d ", sizeof(struct a));
	return 0;
}

在这里插入图片描述

结果是8。
在这里插入图片描述
加起来是6个字节,但是总大小为最大对齐数的整数倍,所以变成了8个字节。

我们也可以自己设置默认对齐数

#pragma pack(默认对齐数)//设置默认对齐数
#pragma pack()//取消默认对齐数
  • 结构体实现位段

    位段的声明与结构体的声明类似,但需要注意两点:

    1. 位段的成员必须是int,unsigned int或char
    2. 位段的成员名后边有一个冒号和一个数字
    3. 位段的空间开辟是按照需要以4个字节或1个字节的方式来开辟的
struct a
{
  int a:2;
  int b:5;
  int c:10;
  int d:30;    
};

在这里插入图片描述
总共8个字节。解释一下 冒号加个数字 是啥意思,就是说那个整型只占冒号后数字个比特位,也就是只存取冒号后数字个比特位。

那么,我们可以写个代码画画图,来解释一下位段是如何存储以及开辟空间的。

#include<stdio.h>
int main()
{
	struct a
	{
		char a : 2;
		char b : 5;
	    char c : 6;
		char d : 3;
	};
	//初始化一下位段
	struct a s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 7;
	s.d = 15;
	return 0;
}

先文字描述一下,这个位段由四个char组成,a变量只占2bite,b变量占5bite,c变量占6bite,d变量占3bite。一个字节的方式来开辟,那么就是8个比特位的方式开辟。
在这里插入图片描述
根据这副图我们发现它是占了三个字节,并且在内存中储存的十六进制是32,07,07。我们调试一波看看结果。
在这里插入图片描述
发现确实如此,所以在vs下,位段的使用规则大致我们已经了解,我做一个概述,位段根据需要以1字节或者4字节来开辟内存,接着在开辟的空间里放数据,当一字节或4字节的空间不足以容纳下一个变量时,直接再开辟一个1字节或4字节的内存来容纳变量,依次类推。

vs环境下,我们已经知道位段的存储方式,但是,位段是不跨平台的,在其他平台存储方式是不确定的。

  • 结构体传参

    结构体的传参,可以传值(传结构体),也可以传址(传结构体的地址)。函数传参是要压栈的,这就会占用内存,所以一般都用传址的方式进行结构体传参。函数压栈我之前的博客里有写,这里不做过多赘述。

    #include<stdio.h>
     struct a 
    {
    	int a;
    	int b;
    };
     void print1(struct a s)
     {
    	 printf("%d %d\n", s.a, s.b);
     }
     void print2(struct a* p)
     {
    	 printf("%d %d\n", p->a, p->b);
     }
    int main()
    {
    	struct a s = { 4, 3 };
    	print1(s);//传值调用
    	print2(&s);//传址调用
    	return 0;
    }
    

在这里插入图片描述

2.联合体

联合体又称为共用体,意思就是几个变量共用一处地址。

  • 联合体的声明以及定义

    union 联合体名

    {

    成员列表;

    }变量列表;

    这是联合体的基本形式,和结构体基本一致。定义变量也和结构体一样,可以在声明时的变量列表处定义 变量,也可以用union 联合体名 变量名 定义临时变量。

union a
{
  char g;
  int h;  
}b,c,d;//全局变量
int main()
{
 union a w;//局部变量
}
  • 联合体的特点

    1. 联合体的大小为最大成员变量的整数倍。

    2. 联合体相当于同一个地址,被多个变量共用,整型,浮点型等变量,在内存中都是二进制01010110组成的,关键在于他的读取方式不同。到底要把它看成浮点型,还是整型就看你如何使用它了。

union a
{
  float g;
  int h;
}b;
int main()
{
    b.g=1.0;
    printf("%f\n",b.g);
    printf("%d ",b.h);
    return 0;
}

在这里插入图片描述
这是代码的运行结果,你将这段二进制看出浮点型和整型取决于你的使用。

  1. 联合体变量赋值时,最后一次赋值为最终赋值,之前的赋值都被覆盖了。还有只能给一个成员变量赋值,不能多个同时赋值。

    union a
    {
     float h;
     char j;
     int k;   
    }b;
    b={1.0,'a',4};//错误
    b=1;//正确
    
    b.h=4;
    b.j=3;
    b.k=2;
    //最终留下的是最后赋值的2
    

3,枚举常量

枚举常量就是将一些(不看作常量)字符定义成常量。我们知道#define a 3,就是将a定义成立了常量3。枚举的出现使定义常量变得方便,一下可以定义一堆,而且也方便调试。

  • 枚举的定义

    enum 枚举名

    {

    成员列表,(用逗号隔开)

    }变量列表;

  • 枚举的特点

    枚举常量就是{ }括号里的内容,系统会默认从枚举常量的大小从0依次加1,我们也可以给成员赋值,没赋值的就比前一个大1。

enum day
{
  mon,//0
  tus,//1
  wed,//2
  thur,//3
  fri,//4
  sat,//5
  sun,//6 
};
enum a
{
  h=5,
  i,//6
  j=10,
  k=18,
  l;//19  
}

以上就是本期内容,希望大家可以有所收获。

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

动名词

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值