自定义类型——结构体(一)

简介: 自定义类型——结构体

dec28ec0a0ef480a894fbc5a90699066.png

结构体


1. 结构体的基本知识


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


2.结构体的声明


声明模板:


struct tag
{
member-list;(成员变量)
}variable-list(结构体变量列表);


假定我们声明一个学生类对象:


struct Student
{
  char name[20];//姓名
  int age;//年龄
  char sex[2];//性别
};//这里的分号不能丢


结构体声明时,大括号最后的分号不能丢。


3.特殊的结构体声明


下面来看一种省略了tag的结构体:匿名结构体


//匿名结构体的声明
struct
{
  int a;
  char b;
  float c;
}x;
struct
{
  int a;
  char b;
  float c;
}a[20], * p;


那么请问,下面这段代码合法吗?


p = &x;


a1cdf216701531c914655cc5c9836e59_02f7ef8e144b4b0a94b0678fafc72a78.png


编译器会弹出警告,所以这种方式是非法的。


4.结构体的自引用


结构体既然可以定义不同类型的数据,那么能否在结构体中包含自己呢?也就是结构体的自引用。


//代码1
struct Node
{
    int data;
    struct Node next;
};


假如这样写,那么struct Node结构体的大小是多少呢??


e403a13d27e310c656a9a7be9ca9ee03_18b7d53e84014e19951cf40752cf13f9.png


运行起来是会报错的:假如这样定义结构体的话,想要计算结构体的大小,分析下去,会发现结构体的大小是无穷无尽的,然而这是不可能的,所以编译器直接就给禁止这种定义方式。


正确的自引用方式:(这种定义方式第二个元素是一个结构体类型的指针,就类似于链表的形式,第二个元素指向和自己相同类型的结构体。)


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


(下面这幅图是便于理解,实际上是有小瑕疵的,后面链表部分会细讲的)


348e2f273dda1325d74e0b8ff33e2787_39695d80f7614ce2943c3e08dbf672b0.png


有时候结构体的类型太长了,我们可以使用typedef关键字对类型进行重命名操作,来看下面这种方式是否正确?


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


这样写是编译不通过的,因为结构体先声明好了之后才能被重命名,这里结构体在声明的过程中就提前使用了重命名之后的名字,显然是不合理的。


改进方法:(在声明结构体的时候使用typedef之前的类型名)


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


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


结构体变量的定义:结构体变量的定义有两种方式:


第一种:声明结构体的时候在变量列表定义结构体变量


struct Point
{
    int x;
    int y;
}p1; //声明类型的同时定义变量p1


第二种:声明结构体之后,不在变量列表定义,而是单独定义:


struct Point
{
    int x;
    int y;
};
struct Point p2; //定义结构体变量p2


结构体变量的初始化:根据两种不同的定义方式,初始化方式也分为两种:


第一种:在变量列表创建结构体变量时初始化:


struct Student
{
  char name[20];
  int age;
}s1 = { "zhangshan",23 };


第二种:在单独创建时进行初始化:


struct Student
{
  char name[20];
  int age;
};
struct Student s1 = { "lisi",22 };


结构体的嵌套初始化:(在结构体中包含其他的结构体类型)


struct Point
{
    int x;
    int y;
};
struct Node
{
    int data;
    struct Point p;
    struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL}; //结构体嵌套初始化


6.结构体的内存对齐


前面学会了结构体的声明与定义,那么该如何计算结构体的大小呢?这就涉及到结构体内存对齐的知识了,先看两个案例:


//练习1
struct S1
{
    char c1;
    int i;
    char c2;
};
printf("%d\n", sizeof(struct S1));
//练习2
struct S2
{
    char c1;
    char c2;
    int i;
};
printf("%d\n", sizeof(struct S2));


运行结果:


e942bdccf1fc9a26655b6ab6a8921a47_4cdb5356644d495891b23108345e134d.png


两个成员个数和成员类型都相同,但是就因为顺序不同,导致了其占用的内存大小不一致。


1.offsetof宏

offsetof是一个宏,可以查看结构体中的某一个元素相对于起始位置的偏移量。


注意:offsetof宏需要包含 stddef.h头文件


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gcbjcLQp-1691404555003)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230807001812982.png)]


//计算S1中每个元素的偏移量:
int main()
{
    printf("%d ", offsetof(struct S1, c1));
    printf("%d ", offsetof(struct S1, i));
    printf("%d ", offsetof(struct S1, c2));
  return 0;
}

8a855764a4d667c6d1071d24aa06700d_2ceceb10102e4c9ea2925c1a573a0583.png


2.内存对齐规则

假定在结构体中,内存是由上到下进行增长的:结构体变量一旦被创建之后,就会选择内存中的一个位置作为偏移量为0的位置开始为结构体开辟空间,此时就需要遵守内存对齐规则了。


内存对齐规则:


  1. 结构体的第一个成员永远从偏移量为0的位置开始存储。
  2. 从第二个成员开始,往后的每一个成员都要对齐到偏移量为该成员的对齐数的整数倍位置处,并从当前位置开始存储该成员。(成员的对齐数的计算方法:结构体成员自身大小和默认对齐数的较小值。当成员中含有数组时,该数组的对齐数就是数组元素的大小和默认对齐数的较小值。在visual studio中,默认对其数是8,但是在gcc中就没有对齐数,所以在gcc中,成员的对齐数就是成员本身的大小)
  3. 整个结构体的大小必须是最大对齐数的整数倍。(最大对齐数:第一个成员在内的所有成员的对齐数的最大值)
  4. 假如在一个结构体S中嵌套的别的结构体P的情况,P则对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是最大对齐数(此时要将嵌套的结构体的最大对齐数也包含进来)的大小。


分析:以S1为例:先计算处每个成员的对齐数,方便后续使用,第一个成员c1,对齐到偏移量为0位置处。接着第二个变量i对齐到4的整数倍的偏移量处,也就是偏移量为4的位置处开始,占用四个字节。第三个变量c3,对齐到1的整数倍数,向下的偏移量为8的位置是满足需求的。这样算下来,结构体占用的空间就是9字节。但是不满足内存对齐的最后一条规则,所有结构体要接着向后包含三个空间(黄色部分),用于凑齐4的倍数。最终,S1结构体占用了12个字节的空间。分析结果与offsetof宏计算的结果也是吻合的。


但是这12个字节空间有6个空间都是被“浪费”掉了的。只有6个空间存储的有效数据。


2016d8abd3a5ade629c23199dbfcf28a_c6eb1dea726c40cf99a4ed5e840ca8a6.png


根据内存对齐规则,不难算出,S2则占用了8个字节的空间。


3.练习

//Test01
struct S1
{
    double d;
    char c;
    int i;
};
//Test02 结构体嵌套问题
struct S2
{
    char c1;
    struct S1 s1;
    double d;
};
int main()
{
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
    return 0;
}


运行结果:


01eee9dfe353a3f1d3bd9a0042376eb8_3c2832f11c1b4b689108f9aa50be0112.png


结果分析:


相关文章
|
6月前
|
存储 网络协议 编译器
自定义类型结构体(下)
自定义类型结构体(下)
38 0
|
6月前
|
编译器 Linux C++
自定义类型结构体(中)
自定义类型结构体(中)
22 0
|
6月前
|
C语言 C++
自定义类型结构体(上)
自定义类型结构体(上)
42 0
|
4月前
|
存储 编译器 Linux
结构体,自定义类型
结构体,自定义类型
29 0
|
9月前
|
存储 编译器
自定义类型——结构体(二)
自定义类型——结构体
|
5月前
|
存储 开发框架 .NET
自定义类型:联合体和枚举类型(联合体与结构体的区别)
自定义类型:联合体和枚举类型(联合体与结构体的区别)
|
5月前
|
网络协议 编译器 C语言
自定义类型:结构体
自定义类型:结构体
61 0
|
5月前
|
存储 编译器 C语言
自定义类型:结构体-2
自定义类型:结构体
38 0
|
5月前
|
存储 编译器 Linux
自定义类型:结构体-1
自定义类型:结构体
25 0
|
6月前
|
编译器
自定义类型【结构体篇】
自定义类型【结构体篇】