17、struct结构体类型详解

a. 结构体类型的声明

结构是一些值得集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量

struct tag
{
    member-list;
}variable-list;

例如描述学生

struct Student
{
    // 成员变量
    char name[20];// 姓名
    char tele[12];//电话
    char sex[10];// 性别
    int age;// 年龄
};// ;不可以省略

如何使用:创建的结构体变量

struct Student s1;

也可以在声明的时候创建变量,但是是全局变量,如下:

struct Student
{
    // 成员变量
    char name[20];// 姓名
    char tele[12];//电话
    char sex[10];// 性别
    int age;// 年龄
} s4, s5, s6; // 这是全局变量

匿名结构体类型(不建议使用)

struct
{
    int a;
    int b;
    float c;
}x;// 因为没有结构体名字,因此创建变量只能这样创建
b. 结构体的自引用

以链表为例

struct Node
{
    int data;
    struct Node n;
}; 
// 上面的定义是有问题的,因为如果此时
sizeof(struct Node);
// 这里怎么求Node所占空间呢,嵌套着定义是计算不出占用空间的,未来是不能创建这种变量的

因此,虽然结构体中可以声明各种类型的成员变量,但是不能声明自己本身的结构体成员变量

那如何定义链表呢?

虽然我们不能定义结构体变量,因为结构体变量的大小是无法确定的。但是指针变量的大小是确定的,都是4个字节。所以我们 可以定义结构体变量指针,用于指向下一个节点的位置。

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

注意点:

有时候我们经常使用typedef来简化结构体的声明;但是在这里最好还是不要简化;

typedef struct Node
{
    int data;
    //Node* next; 这里其实是还没有对这个结构体重命名为Node,就使用了Node这个简化的名称。所以这个struct是不可以省略的
    struct Node* next;
}Node;

// 另一种情形是,匿名结构体
tepedef struct
{
    int data;
    Node* next;// 这里和上面原因是一样的,并且这个Node是个匿名结构体
}Node;

所以我们在搞不清楚的情况下,最好不要偷懒省略这些

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

初始化:定义变量的同时赋初值

struct Point
{
    int x;
    int y;
}p1; // 声明类型的同时定义变量p1
struct Point p2; // 定义结构体变量p2
struct Point p3 = {x, y}; // 初始化
d. 结构体内存对齐
struct S1
{
    char c1;
    int a;
    char c2;
};

struct S2
{
    char c1;
    char c2;
    int a;
};

sizeof(s1); // 12
sizeof(s2); // 8

如何计算结构体内存大小呢?

结构体对齐规则

  1. 第一个成员在与结构体变量偏移量为0的地址处。也就是说结构体变量的所占空间最开始的空间就是第一个成员变量。(结构体变量从哪开始,第一个成员就放哪)

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

对齐数

编译器默认的一个对齐数与该成员大小的较小值。VS中默认的值为8。对于数组来说,对齐数是数组中元素的大小,而不是数组的总大小。

  1. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍

  2. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

上面的文字可能不好理解,现在使用图解的方法来说明

s1结构体变量的大小

在这里插入图片描述

s2结构体内存大小

在这里插入图片描述

需要注意的是,这里是在vs编译器中,默认对齐数是8,而在gcc中,是没有默认的这个数字的!!!这里的较小值就是其本身的大小

嵌套结构体的计算

struct S3
{
    double d; // 对齐数是8
    char c; // 对齐数是1
    int i;  // 对齐数是4
}; // 根据上面不难算出这里的S3的大小是16

struct S4
{
    char c1;
    // 这里嵌套了结构体,根据上面的规则4,如果有嵌套结构体
    // 那么这里的对齐数,是嵌套的这个结构体s3里面成员变量的最大对齐数
    // 而不是整个s3所占用的空间大小!!!
    struct S3 s3;
    double d;
};

在这里插入图片描述

为什么存在内存对齐????

  • 平台原因(移植原因)

    不是所有的硬件平台都能访问任意地址上的任意数据的;

    某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

    比如,有些平台规定取整形数据,我们只能在4的倍数上取,如果不是4的倍数,就异常

  • 性能原因

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

    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;

    而对齐的内存访问仅仅需要一次访问。

    比如在32位机器上,有32根地址线,也就是说有32根数据线。在传输数据的时候,一次可以读取4个字节。如果此时不按照对齐方式

    struct S
    {
        char c;
        int a;
    }
    

    对于成员变量a来说c 01 00 00 | 00 00 00 00就会被隔开了,我们需要先读取c 01 00 00找到a的前半部分01 00 00,再读取一次找到后半部分00

总体来说

​ 结构体的内存对齐是拿空间换时间的做法。

如何设计成员变量占用更少的空间

让占用空间小的成员尽量集中在一起

修改默认对齐数

#pragma这个预处理命令,就可以修改默认对齐数

#include <stdio.h>
#pragma pack(4)  // 设置默认对齐数是4
struct s
{
 char c;
 double d;
};
#pragma pack()  // 取消设置的默认对齐数 也就是这个范围内是4

// 更改后的大小是16 -- > 12

offsetof

计算成员变量的偏移量。这个不是函数,是宏

size_t offsetof (structName, memberName)

使用前引入头文件stddef.h

printf(offsetof(struct s, c)); 就打印出来了其偏移量。

e. 结构体传参
struct S
{
    int a;
    char c;
    double d;
}

struct S s = {0};
// s.a = 100;
// s.c = 'w';
// s.d = 3.14;
Init(s); // 实现初始化函数,完成上面的赋值
// 如果使用的是结构体来接收,如下这样
void Init(struct S tmp)
{
    tmp.a = 100;
    tmp.c = 'w';
    tmp.d = 3.14;
}
// 并不能修改s的内容,因为是值传递,只是对s的一个临时拷贝。相当于创建了一个副本进行操作。
// 调试时可以看出 &s 和 &tmp 地址是不一样的

// 如果函数内部想改变函数外部的值,就要传地址
void Init(struct S* ps)
{
    ps->a = 100;
    ps->c = 'w';
    ps->d = 3.14;
}
// 调用时传递地址
Init(&s);

// 另外对于打印函数,不需要改变结构体内部内容的时候,传递值也是可以的。

平时我们使用结构体时,最好使用传递地址的形式,因为结构体一般占用内存空间比较大,时间空间效率比较低

另外,函数传参时,参数需要压栈。(参数传递是从右向左传的)。先为形参开辟空间,拷贝实参的数据进去,然后为该函数开辟栈空间。如果结构体过大,为形参开辟空间,压栈的系统开销比较大,会导致性能下降

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今儿背单词吗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值