C语言中常用的自定义数据类型(结构体、枚举、联合)

前言

在程序编写的过程中,我们难免会遇到一些复杂的元素(例如学生:姓名、性别、学号)无法用单一的内置数据类型表示,于是就引入了自定义数据类型来描述这些复杂的元素。

C语言中常见的自定义数据类型主要有:结构体、枚举、联合(结构体中主要解释位段

一:位段

数据的存取一般以字节为单位,但某种情况下存储一个数据不必用一个或多个字节(例如:“真”或“假”用0或1表示只需要1个bit位),正是基于这种考虑,C语言又提供了一种叫做位段的数据结构。

C语言允许在一个结构体中以位为单位来指定其成员所占内存长度, 这种以位为单位的结构称为位段(位域), 位段可以把数据以位的形式紧凑的储存,用较少的位数存储数据,达到节省空间的目的。

1.1 位段的声明

位段的声明和结构体是类似的,但是有两点不同:

  1. 位段的成员必须是int 、unsigned int、signed int、char
  2. 位段的成员名后有一个冒号和一个数字

举个栗子:

struct S{
	// _a、_b、_c 一共占用17个bit 存入一个整型(32bit)的空间中
	int _a:2;	
	int _b:5;
	int _c:10;
	// _d 占用30bit(17+30>32) 得存储在另一整型的空间空
	int _d:30;
};
// S中的成员需要占用2个int的空间
printf("%d\n", sizeof(struct A)):输出结果为 8

注意:位段的宽度不能超过它所依附的数据类型的长度。通俗地讲,成员变量都是有类型的,这个类型限制了成员变量的最大长度,冒号后面的数字不能超过这个长度。

1.2 位段的存储

  1. 相邻成员的类型相同时:

如果它们的位宽之和小于类型的sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止。

如果它们的位宽之和大于类型的sizeof 大小,那么后面的成员将从新的存储单元开始存储

#include <stdio.h>
int main(){
    struct bs{
        unsigned m: 6;
        unsigned n: 12;
        unsigned p: 4;
    };
    
    printf("%d\n", sizeof(struct bs));
    return 0;
}

运行结果:4
  1. 相邻成员的类型不同时:

不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会压缩存储

#include <stdio.h>
int main(){
    struct bs{
        unsigned m: 12;
        unsigned char ch: 4;
        unsigned p: 4;
    };
    
    printf("%d\n", sizeof(struct bs));
    return 0;
}
GCC运行结果:4	VS运行结果:12(三个成员按照各自的类型存储)
  1. 成员之间穿插非位域成员:

如果成员之间穿插着非位域成员,那么不会进行压缩。

#include<stdio.h>
int main(){
	struct bs{
	    unsigned m: 12;
	    unsigned ch;
	    unsigned p: 4;
	};
	
	printf("%d\n", sizeof(struct bs));
	return 0;
}
运行结果:12

注:我们发现位域成员往往不占用完整的字节,有时候也不处于字节的开头位置,因此使用&获取位域成员的地址是没有意义的,C语言也禁止这样做。地址是字节(Byte)的编号,而不是位(Bit)的编号。

1.3 位段的内存分配

  1. 位段的成员可以是int、unsigned int、signed int、char类型
  2. 位段的空间上是按照4个字节(int)或1个字节(char)的方式开辟的
  3. 位段涉及很多不确定因素,位段不跨平台,注重可移植的程序应该避免使用位段

在这里插入图片描述
总结: 和结构体相比,位段可以达到同样的效果,虽然可以很好的节省空间,但是有跨平台问题的存在。

二:枚举

枚举顾名思义就是一一列举,把可能的取值列举出来,比如:星期、性别、月份、颜色等都可以使用枚举类型。

1.1 枚举的定义

1.枚举的定义与结构体的声明十分类似,不过我们要谨记中间的每个常量要用逗号隔开

enum Sex{
	// 以下都是枚举常量
	MALE,
    FEMALE,
    UNKNOWN,
};
// 默认从0开始,一次增长1的进行赋值
// 如果我们对枚举常量进行赋值,则后面的常量会从赋的值开始一次增长1
printf("MALE = %d,FEMALE = %d, UNKNOWN = %d\n", MALE, FEMALE, UNKNOWN);

输出结果:0 1 2 

2.还可以使用枚举类型来定义变量,但是定义的变量的值就必须是枚举常量中出现的常量。

enum Sex{
	MALE,
	FEMALE,
	UNKNOWN,
};
int main(){
	// MALE是枚举类型中的常量
    enum Sex sex = MALE;
    printf("sex = %d\n", sex);
}

运行结果:sex = 0

1.2 枚举的优点

我们可以使用#define定义常量,为什么还要使用枚举呢?

  • 枚举的优点:

1.增加代码的可读性和可维护性
2.枚举有类型检查,更加严谨
3.使用方便,一次可以定义多个常量
4.防止命名污染(封装)

三:联合(共用体)

联合体也叫共用体,是一种较为特殊的自定义类型,这种类型定义的变量共用同一块空间

3.1 联合类型的声明与使用

联合体与结构体定义的语法完全一致,联合大小是根据其中最大成员的大小决定的,它也可以有效节省空间。

注:最大成员大小若不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。

union Un{
	// 联合体c和i共用int的四个字节
	char c;
    int i;
};
int main(){
    union Un un;
    printf("%lu\n",sizeof(un));  输出:4
    un.i = 4;
    un.c = 'a';
    printf("%d\n", un.i);	输出:97
}

由此可见:共用体所占空间的确与结构体是不一样的,并且当我们同时给两个成员变量赋值时另一个成员变量会扰乱其他成员变量的赋值,由此可见它们确实是存储在同一块内存空间上的。

3.2 联合的应用

  • IP地址两种形式的转换
union Ip{
    uint32_t a;
    struct{
        char d1;
        char d2;
        char d3;
        char d4;
    };
};
int main(){
    union Ip ip;
    ip.a = 0x1;
    printf("%d.%d.%d.%d\n", ip.d1,ip.d2,ip.d3,ip.d4);
}

输出结果:1.0.0.0
  • 字节序的判断

详情点击博客:大端存储模式和小端存储模式https://blog.csdn.net/Outtch_/article/details/105613040

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章