EP14:结构体,位段,联合体(c语言中十分重要的知识模块(c语言学习笔记))

目录

1.结构体组成解析

2.结构体的引用

3.结构体的自引用 

4.结构体内存对齐 

 4.1 结构体内存对其的规则

5.offsetof(结构体成员变量计算函数)

6. 结构体传参

7.位段

7.1 位段简介

7.2 位段在内存空间中的分配

7.3 位段在实际生活中的应用

7.4 对位段成员变量的访问

8.联合体

8.1 联合体大小的计算

 8.2 联合体的使用

 9. 枚举


1.结构体组成解析

struct stu 
{
    char name[20]; 
    int age; 
    char sex[5]; 
    char id[20]; 
};

首先需要知道的,结构体和数组都是样的类型,都是属于自定义类型 

struct:是一个关键字,用来设置结构体的重要组成部分

struct stu:结构体的类型.(stu是自定义的)

char name[20]; 
    int age; 
    char sex[5]; 
    char id[20]; 都是结构体变量的成员名.

2.结构体的引用

引用上述的结构体的方法

方法一:按照结构体成员的顺序初始化(这里面的顺序必须和结构体成员名的顺序完全一致)

//    struct Stu s = { "张三", 20, "男", "20230818001" }; 

 方法二:使用点操作符进行初始化(无需按照结构体成员名顺序,直接"点操作符+结构体成员名名称+想要设定的值")

ps:何为点操作符:点操作符是C语言中的一种结构体访问运算符,用于访问结构体和联合体中的成员

//    struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥" };

3.结构体的自引用 

如何在结构中包含一个类型为该结构本身的成员.

比如,定义一个链表的节点:

这里涉及一些数据结构的相关知识,由于笔者属于编程萌新,这里不介绍,先注意一下,以后会用到.

struct node
{
    int age;
    struct node* next;
};

匿名结构体不做介绍,因为用到的情况极少,感兴趣的可以自行查阅资料进行相关的学习与了解.

4.结构体内存对齐 

如果将一个结构体中成员变量的位置改变,那么它在内存中的占用空间也会随之发生改变,这里就涉及到了结构体的内存对齐.(简而言之就是计算结构体的内存大小)

 4.1 结构体内存对其的规则

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

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

- MSVS的默认对齐数是8,

- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小

所以在MSVC中,对齐数就是结构体成员变量所占空间和8进行比较,取较小的一个值

栗子

struct s
{
    char a;//1和8,对齐数(实际所占空间)就是1
    int b;//4和8,取4
};

如何用大脑求一个结构体的大小

1.首先,将结构体中所有成员中所有的实际占用空间一一求出,并且将占用最大空间的成员标注出来a1

2.然后再将结构体中所有的成员变量实际占用空间求和S1

3.结构体的总大小便是一个大于S1并且是a1整数倍的整数

总而言之,结构体的大小大于每个成员变量实际所占空间之和且是最大成员变量的整数倍.

如何用图形形象表示结构体在内存空间中的分配呢

以上述的结构体 struct a 为例

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

根据结构体的内存对齐规则

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

(int i的对齐数是4,c1之后从4的最小倍数开始,从4开始存储. )

空出的空间会被浪费掉,这样做有助于内存读取的效率的提升

Q;如何尽可能地节省内存空间

A:尽量将占用内存空间小的成员变量写在一起

因为一个 int 型数据的大小为4,根据上述结构体内存对齐规则2, i 从地址4开始,占用4,5,6,7四个空间

这三个成员变量占用空间之和为9,最大成员变量是4,比9大且最小的4的倍数是12,所以这个结构体

的总大小是12

5.offsetof(结构体成员变量计算函数)

offsetof (type,member)

头文件:#include <stddef.h>

意思就是offsetof(结构体类型,结构体成员名)

               offsetof(struct s1, c1)

6. 结构体传参

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stddef.h>
struct s
{
    int data[1000];
    int num;
};

struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
    printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
    printf("%d\n", ps->num);
}
int main()
{
    print1(s); //传结构体
    print2(&s); //传地址
    return 0;
}

上面的 print1 和 print2 函数哪个好些?

答案是 : 首选print2函数。

原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下 降。

结论:

结构体传参的时候,要传结构体的地址。

7.位段

7.1 位段简介

位段和结构体有点类似,但是内存空间分配的方式大大不同

struct A
{
    int a : 2;
};

struct A
{
    int a;
};

注意看,结构体的成员变量是 int a;而位段是 int a : 2;

位段的int a : 2;意思是开辟一个大小是32bit的空间并且分配2bit大小的空间给成员变量a.

位段存在的意义 :节省了内存空间,是内存空间的分配更为合理.

7.2 位段在内存空间中的分配

首先要知道的是

1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型

2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

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

来看下列一个代码

struct A
{
    int a : 2;
    int b : 5;
    int c : 10;
    int d : 30;
};

1. int a : 2;首先分配一个int大小的空间(4字节=32比特)

如图

2.这里开辟了一个32bit大小的空间,然后给2bit大小给成员变量a,这里有一个不确定性,就是是从左向

右还是从右向左存放变量,这个是要根据程序员的IDE确定的 

3.根据操作平台(IDE)按照一个方将将成员变量abcd以及放进去,放到d的时候,发现放不下了,这里便

出现了第二个不确定性,就是这个空间到底是继续利用还是放弃并且重新开辟一个int大小的空间.

ps:至少在MSVS的环境下,位段的存储是从左向右,并且遇到放不下的就舍弃然后从新开辟一块新的空间.

7.3 位段在实际生活中的应用

下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据大小也会较小⼀些,对网络 的畅通是有帮助的。

7.4 对位段成员变量的访问

struct A
{
    int a : 2;
    int b : 5;
    int c : 10;
    int d : 30;
};

int main()
{
    struct A m;//对位段的命名
    int i = 0;
    scanf("%d", &i);
    m.b = i;//和结构体一样,对其访问,并且给其赋值
    return 0;
}

相关注意事项:

位段的几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位 置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。

所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员。

8.联合体

8.1 联合体

像结构体一样,联合体也是由一个或者多个成员构成,这些成员可以不同的类型。

但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同⼀块内存空间。

所以联合体也叫:共用体。

给联合体其中一个成员赋值,其他成员的值也跟着变化。

union u
{
    int a;
    char b;
};


int main()
{
    union u uu;
    printf("%d\n", sizeof(uu));

    printf("%p\n", (&uu));
    printf("%p\n", (&uu.a));
    printf("%p\n", (&uu.b));

    return 0;
}

从这里看出,结构体变量的成员是共用一块空间的.

8.1 联合体大小的计算

• 联合的大小至少是最大成员的大小。

• 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

union un1
{
    char c[5];
    int i;
};

union un2
{
    short c[7];
    int i;
};

int main()
{
    printf("%d", sizeof(union un1));
    printf("%d", sizeof(union un2));
    return 0;
}
 

总而言之,union也存在内存对齐,只是一个union的总大小是大于最大成员变量(公用的空间嘛,所以

用最大才放得下),且是最小成员变量的整数倍 .

 8.2 联合体的使用

struct gift_list
{
    int stock_number;//库存量
    double price; //定价
    int item_type;//商品类型

    union {
        struct
        {
            char title[20];//书名
            char author[20];//作者
            int num_pages;//页数
        }book;
        struct
        {
            char design[30];//设计
        }mug;
        struct
        {
            char design[30];//设计
            int colors;//颜色
            int sizes;//尺寸
        }shirt;
    }item;

 9. 枚举

 先来看一段代码

enum hello
{
    C,
    I,
    A,
    L,
    O
};
int main()
{
    printf("%d\n", C);
    printf("%d\n", I);
    printf("%d\n", A);
    printf("%d\n", L);
    printf("%d\n", O);

    return 0;
}

 

运行结果:

可以看出,这个枚举(enum)将里面的成员变量从"0"开始依次赋值,所以个人觉得enum是一

个#define(宏)和数组下标结合的东西,是#define的上位替代,更加好用,可以批量定义宏.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值