自定义类型之结构体

目录

结构体创建

结构体初始化及成员访问

匿名结构体

typedef定义结构体类型

结构体大小的计算

那么为什么要引入对齐这种东西?

结构体实现位段(位域)

什么是位域?

为什么使用位域?

位域的用法

注意事项

内存如何为位段开辟空间?



结构体初始化及成员访问

匿名结构体

匿名结构体是指在声明结构体变量时不给出结构体的名称。主要用于临时性的数据组织或者在某些特定的情况下。

#include 

int main() {
    // 声明一个匿名结构体变量
    struct {
        int x;
        int y;
    } point;

    // 初始化结构体变量
    point.x = 10;
    point.y = 20;

    // 输出结构体变量的成员
    printf("%d, %d\n", point.x, point.y);

    return 0;
}

当我们创建两个成员相同的匿名结构体,其实相当于创建两个不同的结构体而不是一个,因为对于匿名结构体,每次声明时都会分配新的内存空间,因此它们的地址是不同的。即使两个结构体变量的成员相同,它们仍然是不同的类型。

#include 

int main() {
    // 声明第一个匿名结构体变量
    struct {
        int x;
        int y;
    } point1;

    // 声明第二个匿名结构体变量
    struct {
        int x;
        int y;
    } point2;

    // 初始化第一个匿名结构体变量
    point1.x = 1;
    point1.y = 2;

    // 初始化第二个匿名结构体变量
    point2.x = 3;
    point2.y = 4;

    // 比较两个结构体变量的地址
    if (&point1 == &point2) {
        printf("同一类型\n");
    } else {
        printf("不同类型\n");
    }
//输出结果不同类型
    return 0;
}

typedef定义结构体类型

#include 

// 使用 typedef 定义结构体类型
typedef struct {
    int x;
    int y;
} Point;

int main() {
    // 使用自定义类型创建变量
    Point p1 = {1, 2};
    Point p2 = {3, 4};

    // 输出结构体变量的成员
    printf("Point 1: (%d, %d)\n", p1.x, p1.y);
    printf("Point 2: (%d, %d)\n", p2.x, p2.y);

    return 0;
}

自定义了一个结构体类型,就不需要再创建结构体变量时加struct,就和我们声明基本类型一样

结构体大小的计算

有以下简单代码请思考结果

#include 

// 使用 typedef 定义结构体类型
typedef struct {
  char a;  
  int x;
} Point;

int main() {
  
    printf("%d", sizeof(Point));

    return 0;
}

答案:8

为什么呢?因为结构体变量需要对齐,那么什么是对齐

  1. 成员偏移对齐: 结构体的每个成员都有自己的内存地址。对齐规则要求每个成员的地址能够被其大小整除。例如,如果一个 int 类型的成员的起始地址不是4的倍数,则需要在其前面填充一些字节,直到满足对齐要求。

  2. 结构体填充: 有些编译器会在结构体的末尾添加额外的填充字节,以保证下一个结构体变量的对齐要求。这些填充字节不属于任何成员,仅仅是为了对齐要求而存在的。

自定义类型之结构体_第1张图片

当我们创建结构体变量时内存中存在这么一块空间,数字0,1...10是相对于结构体的的地址的偏移量,当其中有着两个成员变量int x 和char a时,先声明的变量从0开始开辟也就是char a像这样自定义类型之结构体_第2张图片

当我们去创建第二个变量x时,x为int类型大小为4个字节,所以x的起始位置要为4的整数倍,也就是成员偏移对齐,所以应该从偏移位置为4的地方开始也就是这样

自定义类型之结构体_第3张图片

做完以上步骤还需要确认一下结构体大小是否为结构体最大变量所占字节的整数倍,不够的补齐到整数倍,这里最大变量为x为int类型占4个字节,目前的结构体大小是8正好是4的两倍所以不用补。

那当我们再增加一个变量char c 时,c大小为1个字节就是1的整数倍开始放也就是随便放,这时结构体大小变成了9并不是4的整数倍这时候就会发生’’结构体填充’’也就是这样,最后总共浪费了6个字节

自定义类型之结构体_第4张图片

节省策略:把类型大小相同的变量集中在一起,小的放前面

补充:

1.vs编译器有默认对齐数8,也就是一旦某个变量大小超过8那他的起始地址按8的整数倍算,也就是说vs编译器最大对齐数不会超过8

2.如果结构A中存在结构体B类型的变量,那么结构体B类型的变量的对齐数是它里面变量中最大的对齐数,像这种

#include 
int main() {
    struct B {
        char a;//1
        int x;//4
        char c;//1
        //最大对齐数4
    };
    struct A {
        char a;//1
        struct B b;//4
        double d;//8
    }p ;
   
    printf("%d", sizeof(p));
    return 0;
}

那么为什么要引入对齐这种东西?

结论:空间换时间,以及不是所有的平台都能任意的访问内存

cpu读取内存是按块来读取,块的大小可以是2,4,6,8个字节等来读取,当我们访问一块未对齐的内存块时,处理器读取的位置可能与变量的起始位置不相同导致一个数据明明只需一次读取而我却进行了两场读取,假设我的cpu一次读取四个字节内存中存在两个这样未对齐的数

自定义类型之结构体_第5张图片

当我们想读取x时明明只需一次而我们却使用了两次,可能你觉得这样算不上什么,那如果x后面还要10000个变量,x未对齐那之后访问后面的变量自然也不会对齐,这样读取速度就会大幅下降,所以内存对齐成为一种很好的方法,通过牺牲少量空间提高我们的访问读取速度

结构体实现位段(位域)

位域:C语言中的内存节省神器

什么是位域?

位域与正常声明变量不同,他是以位(bit)为单位来申请内存,位域是C语言中的一种数据结构,它允许程序员在一个字节(或更大的存储单元)中定义多个数据成员,且所占位的大小可以由自己定义。通常情况下,数据类型如 intchar 等被用来表示位域。

为什么使用位域?

  1. 节省内存空间: 位域允许将多个布尔值或小范围的整数值打包到一个字节或更大的内存单元中,从而节省内存空间。

  2. 提高存取效率: 位域可以提高数据的存取效率,因为它们允许多个数据成员在同一个字节中存储,减少了内存访问的次数。

位域的用法

struct BitField {
    dataType memberName : width;
};
  • dataType 表示成员的数据类型,通常是 intchar
  • memberName 是成员的名称。
  • width 是成员所占用的位数。

例如:

struct Flags {
    unsigned int isOn : 1;
    unsigned int mode : 3;
    unsigned int errorCode : 8;
};
  • isOn:1位,用来表示某个开关状态。
  • mode:3位,用来表示某种模式。
  • errorCode:8位,用来表示某种错误代码。

这样我们原本需要12个字节变成了只需要4个字节大大节省了我们的内存空间,在一些内存空间不大的硬件上位段就能发挥很大的作用,例如单片机。

注意事项

  • 位域的行为受到具体的编译器和平台的影响,可能会有一些不确定的行为,就是不具有移植性。
  • 位域成员的访问受到一些限制,比如不能使用地址运算符 &*,也不能被取地址。

内存如何为位段开辟空间?

位段的空间通常是以4个字节(int)或者1个字节(char)的方式来开辟的

位段的大小如何计算

按当前结构体最大类型开辟空间,int就四个字节(32),如果后边的位段变量放不下就重新申请一片32位内存

例如:把上面代码改成这样

    unsigned int isOn : 31;
    unsigned int mode : 4;
    unsigned int errorCode : 8;

结果就是占8个字节,如图所示

自定义类型之结构体_第6张图片

你可能感兴趣的:(c语言,数据结构)