自定义类型知识总结(结构体,位段,枚举,联合)

结构体

结构体是一些值的集合,这些值称为成员变量。结构体的每一个成员都可以是不同的变量。
1,结构体的声明

struct tag
{
      member_llist;
}variable_list;

注意:在声明结构体时也可以不完全声明,可以声明匿名结构体类型,但声明的匿名结构体无法定义变量。(如上例中的程序不要tag这个结构体名),在C语言中,结构体类型必须要有至少一个成员(即member_list不能为空)

2,结构体的自引用

struct Node
{
    int data;
    struct Node next;
};

当结构体中包含一个类型为该结构体本身的成员时,这样是不合法的,由于在定义变量时无法知道应为该结构体开辟多大的空间,因此,结构体的自引用是不合法的。
所以,当需要结构体自引用时,就需要用指向该结构体的指针来替代,指针的大小是确定的(在32位平台下,所有指针的大小是4字节)

struct Node
{
    int data;
    struct Node *next;
};

3,结构体变量的初始化
结构体变量的初始化和数组类似,可以被整体初始化,但不能被整体赋值。
(1)在声明结构体的同时定义变量并进行初始化
(2)先声明结构体,后定义并初始化

4,结构体的内存对齐问题
当我们计算结构体的大小是,并不是简单的把结构体中各成员变量的类型大小相加就行的,由于一些平台原因(由于硬件的原因,并不是所有的硬件平台都能访问任意地址的任意数据的,某些硬件平台只能在某些特定的地址处取某些特定的值),所以我们在计算结构体大小时就不得不考虑内存对齐问题。

内存对齐是一种拿空间来换时间的做法:
如果我们假设计算机在读取数据是只能在4的整数倍读取,那么

struct s1
{
   char c1;
   int i;
   char c2;
};

当我们读取i时,如果数据在内存中是连续排布的,那么i就不可避免的需要被读取两次,这对计算机来说,耗费了许多时间。

那么,如何进行内存对齐呢
结构体内存对齐的规则:
1,结构体变量的第一个成员不需要内存对齐,第一个成员在与结构体变量偏移量为0的地址处。
2,其他成员要对齐到某个数字(对齐数)的整数倍的地址处
对齐数是编译器指定的一个对齐数与该成员大小的较小值(如果不指定,那默认对齐数就是成员变量的大小)
可以通过#pragma这个预处理指令来指定对齐数

#pragma pack(8)//设置对齐数为8

怎么进行对齐呢
对齐:当除第一个成员变量的其他成员放入结构体时,起始偏移量能整除自身对齐数,就称为对齐。
3,结构体总大小为最大对齐数的整数倍,每一个成员变量都有一个对齐数,其中最大的那被称为最大对齐数。
4,如果结构体中嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数,在没有指定对齐数的情况下,嵌套结构体的对齐数就是其自身的大小)的整数倍。

eg:计算上面那个结构体的大小

由于没有指定对齐数,因此我们就不必拿其与成员变量的大小进行比较了
struct s1
{
   char c1;//第一个成员不用对齐,其偏移量=0,对齐数=1
   int i;//偏移量=1,对齐数=4,无法整除需要将偏移量移到4的位置才能进行整除,移动后偏移量=4
   char c2;//偏移量=8,对齐数=1,可以整除
};

最后我们可以将整体的偏移量计算出来,该题的整体偏移量是9,最大对齐数为4,有9不能整除4,因此我们需要将整体偏移量扩展到可以整除最大对齐数,所以,s1这个结构体的大小为12字节。

struct B
{
	int x;
	char y;
	double z;
};
struct A
{
	char a;
	int b;
	struct B c;
	
};
int main()
{
	printf("%d", sizeof(A));//输出结果为24
	system("pause");
	return 0;
}

5,结构体的传参问题
由于结构体在传参时,都是整体拷贝,当结构体成员中含有很大的数组(arr[1024 * 1024 * 10] )时,结构体过大,每次传参时都要形成临时拷贝,形成栈帧,会有时间和空间上的系统开销,所以会导致性能下降。

我们一般采用结构体地址传参

struct S
{
    int data[10000};
    int num;
};
struct S s={{1,2,4,5},1000};
void printf(struct s *ps)
{
   printf("%d",ps->num);
}
int main()
{
  printf(&s);
  return 0;
}

位段

位段的声明与结构体类似,但有以下两个区别:
1,位段的成员必须是int,unsigned int ,signed int
2,位段的成员名后面有一个冒号和一个数字,数字用来表明该成员占有的比特位数

struct A
{
   int _a:2;
   int _b:5;
   int _c:10;
   int _d:30;
};

该位段的大小为8字节,其内存分布如下图

在这里插入图片描述
位段涉及许多不确定因素,位段是不跨平台的,可移植性差,但可以节省空间。

枚举

定义一个枚举类型,{ }中的内容都是枚举类型的可能取值,也叫做枚举常量。这些可能取值都是有值的,默认从0开始,一次递增1,也可以在定义是赋初值。

enum Day
{
   Mon,//注意以,结尾
   Tues,
   Wed=8,
   Thur,
   Fri,
   Sat,
   Sun
};

枚举的优点:
1,增加了代码的可读性和可维护性
2,和#define定义的标识符相比枚举有类型检查,而不是仅仅的简单替换
3,防止了命名污染
4,便于调试
5,使用方便,一次可以定义多个常量

联合体(共用体)

联合是一种特殊的自定义类型,这种类型也包含一系列成员,但这些成员公用同一块内存空间。其声明如下:

union Un
{
   char c;
   int i;
};

由于联合体是共用一块内存空间的,所以一个联合体变量的大小至少是其最大成员的大小,要保证联合至少有能力保存最大的那个成员。它的成员变量都是该联合的第一个成员,所以联合体的变量和联合体内成员的地址都是一样的。

联合体的内存对齐问题
联合体只需要能整除自身的最大对齐数即可

union Un1
{
   char c[5];//对齐数=1
   int i;//对齐数=4
};

共用一块内存,其最大成员的内存大小为5,但5不能整除4,要扩展到能整除4的最小值,因此,Un1的大小为8字节。

联合体的应用
判断计算机的大小端
在这里插入图片描述

#include<stdio.h>
#include<windows.h>
union Un
{
	int i;
	char c;
};
int main()
{
	union Un un;
	un.i = 1;
	printf("%d", un.c);//输出结果为1,则表示为小端机器
	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44930562/article/details/94543187