C语言结构体、枚举、联合体

目录

1.0 结构体

1.1 结构体是什么:

1.2 结构体的声明:

1.3 结构体的不完全声明:

1.4 结构体的自引用

1.5 结构体变量的定义和初始化

1.6 结构体内存对齐

1.7 修改默认对齐数

1.8 结构体传参

2.0 位段

2.1 什么是位段

2.2位段的内存分配

2.3 位段的跨平台问题

3.0 枚举 

3.1枚举类型的定义

3.2 枚举的优点

3.3 枚举的使用 

4.0 联合(共用体)

4.1 联合类型的定义

4.2 联合的特点 

4.3 联合的大小计算 


#1.结构体

在现实世界里,常常有一些复杂对象不好用简单的变量或数组去表示,这时候C语言的自定义类型:结构体,就可以帮我们很好的定义这些复杂对象。

1.1 结构体是什么:

结构体是一些值的集合,这些值被称为成员变量,成员变量可以是不同的类型


1.2 结构体的声明:

C语言结构体、枚举、联合体_第1张图片

这个时候我们就声明了一个有两个char类型和一个int类型的结构体类型struct str


1.3 结构体的不完全声明

C语言结构体、枚举、联合体_第2张图片

 与上面的声明不同的是少了结构体标签,所以这个结构体类型只有一个变量s3,不可在函数内重新创建新的变量


1.4 结构体的自引用

结构体是一些值的集合,那结构体本身是不是可以是自己的成员变量呢?

C语言结构体、枚举、联合体_第3张图片

不行。因为如果这样做,那结构体的大小怎么计算,一层套一层根本计算不了。

那怎么自引用呢?正确的自引用方法:

 C语言结构体、枚举、联合体_第4张图片


1.5 结构体变量的定义和初始化

根据前面说的,那定义一个结构体变量其实就很简单了

struct str
{
	int a;
	int b;
}s1;//声明的时候同时定义结构体变量s1
struct str s2;//定义结构体变量s2

struct str s3 = { 3,4 };//初始化结构体变量同时赋初值

struct ptr 
{
    int a;
    int b;
};

struct Node
{
    int a;
    struct ptr s1;
    struct Node* p1;
}n1{3,{4,5},NULL}//结构体嵌套的初始化并赋初值



1.6 结构体内存对齐

前边介绍了结构体的初步使用,接下来介绍深入一点的知识,结构体的大小如何计算?在内存中怎么存储?

struct
{
    char a;
    int b;
    char c;
}s1;//结构体的大小是如何计算的?在内存中是如何存储的?

那到底该怎么计算?

结构体在内存中是需要进行内存对齐的,也就是:

1.结构体的第一个成员放在结构体变量偏移量0的位置

2.第二个和往后所有成员需要对齐到对齐数的整数倍的偏移量处,

对齐数-- --编译器默认的对齐数跟成员大小中的较小值

vs2022中的默认对齐数为 8;

3.整个结构体的大小需要对齐到所有对齐数中较大的那个数的整数倍;

4.在嵌套的结构体中,嵌套的结构体需要对齐到自身成员中较大的对齐数的整数倍处;结构体的整体大小就是所有对齐数中较大的对齐数的整数倍(包括嵌套的结构体)。

 那为什么要存在结构对齐呢?我直接一个个紧挨着还能节省空间呢,你这个搞得这么麻烦

C语言结构体、枚举、联合体_第5张图片

 1.平台(移植)原因:

不是所有的硬件平台都可以随时随地访问随意的地址处的,某些硬件平台只能在某些地址处取某些特定的数据类型,否则则抛出硬件异常

2.性能原因

数据结构(尤其是栈)应该尽可能的对齐到自然边界处,

因为为了访问未对其的内存,处理器需要做两次内存访问,而访问对齐了的内存则只需访问一次。

总结:内存对齐是拿空间换时间的做法。

那我们在设计结构体的时候,我们要满足节省空间,也要满足对齐。

把占用空间小的成员集中放在一起。

1.7 修改默认对齐数

使用#prqgma这个预处理指令可以改变默认对齐数

#pragma(8)//将默认对齐数修改为8
struct str
{
    char a;
    int b;
    int c;
}s1;
pragma();//取消修改的默认对齐数,还原为默认

当结构体在对齐方式不合适的时候,我们可以修改默认对齐数

1.8 结构体传参

struct str//结构体声明
{
	int a[1000];
	int b;
};
struct str s={{1,2,3,4,5,6},1 };//创建一个结构体变量s
void print1(struct str s)
{
	printf("%d ", s.b);
}
void print2(struct str* ps)
{
	printf("%d", s.b);
}
int main()
{
	print1(s);//传值调用
	print2(&s);//传址调用
	return 0;
}

函数的传参无非就是传值调用print1和传址调用print2

那谁好用呢?

        *首选print2函数

因为函数传参时,参数是需要压栈的,会有空间和时间上的系统开销。

如果传递一个结构体对象过大,参数压栈时的系统开销过大,会导致性能的下降。

总结:结构体传参时要传地址。 

2.0 位段

2.1 什么是位段

位段跟结构体的声明是类似的

位段有两个特点:

1.位段的类型必须是int,signed int,unsigned int。

2.位段的成员名后面有一个冒号和数字。

比如

struct A

{
        int _a:3;

        int _b:4;

};

str就是一个位段类型

那位段str的大小是多少呢?

printf("%d",sizeof(struct A));

 C语言结构体、枚举、联合体_第6张图片

 居然是四个字节,为啥?

因为_a后的数字表示要使用的比特位,也就是_a使用3个比特位,_b使用四个比特位。而位段因为时int类型,位段一次开辟四个字节的空间以供使用。

2.2位段的内存分配

1.位段的类型int,signed int,unsigned int或char(属于整型家族)

2.位段的空间是按照四个字节(int)或一个字节(char)来开辟的

3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序时应该避免使用位段

2.3 位段的跨平台问题

1.int被当作有符号还是无符号时未知的

2.位段中最大位的数目不能确定(16位机器是16,32位机器32,最大写成27,在16位机器上会出问题)

3.位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义

4.当一个结构体包含两个位段,第二个位段成员较大,无法容纳与第一个剩余的位时,是舍弃剩余的位还是利用是无法确定的

3.0 枚举 

 枚举,顾名思义就是一一列举,在生活中总有一些事只有那么几个确定的结果。

比如一个星期有七天,可以一一列举

月份有12个月,可以一一列举

像这样就可以使用枚举了。 

3.1枚举类型的定义

C语言结构体、枚举、联合体_第7张图片

 上图定义的enum Day,enum Color都是枚举类型

{}中的是枚举的可能取值,也叫枚举常量。

这些可能取值都是有值的,默认从0开始,依次往后递增

当然也可以赋初值,未赋初值的依次向前一个数递增

C语言结构体、枚举、联合体_第8张图片

3.2 枚举的优点

为什么使用枚举 ?

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

枚举的特点:

1.增加代码的可读性和可维护性

2.与#define定义的标识符相比,枚举有类型检查,更严谨。

3.防止了命名污染(封装)

4.便于调试

5.使用方便,可以一次定义多个常量

3.3 枚举的使用 

enum Color//颜色
{
    RED=5,
    GREEN=9,
    BULE
};

int main()
{
    enum Color e1 = RED;//只能用枚举常量给枚举变量赋值
    //错误的赋值:enum ColorE1=5;

return 0;

}

4.联合(共用体)

4.1 联合类型的定义

 联合也包含一系列的成员,特点是这些成员共用同一块内存空间

C语言结构体、枚举、联合体_第9张图片

 

4.2 联合的特点 

 联合的成员是共用一块空间的,这样联合体的大小至少因该是最大的成员变量的大小。

4.3 联合的大小计算 

1.联合体的大小至少是最大成员的大小

2.当最大成员不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。

union un

{
int a;

short arr[3];

};

最大成员为6,因该对齐到4的倍数处,也就是8

 C语言结构体、枚举、联合体_第10张图片

 

你可能感兴趣的:(C语言,c语言,开发语言,github,git)