😊前言😊
在初阶C语言中 ,我们已经接触过一些简单的结构体,让大家对结构体有了初步的了解,今天我们来深入研究一下结构体、枚举和联合体。希望大家会有多多少少的收获
🤔结构体🤔
结构体是什么?
结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体声明
结构体定义需要用到关键字 struct 来定义 。
例如我们需要描述一个学生,就要有学生姓名、年龄、性别、学号等等。
我们试着把一个学生的信息用结构体表示出来>
struct Stu { char name[20];//姓名 int age;//年龄 char sex[5];//性别 char id[20];//学号 }s1,s2;//全局变量 int main() { struct Stu s3, s4;//局部变量 return 0; }
也可以这样写>
typedef struct Stu { char name[20];//姓名 int age;//年龄 char sex[5];//性别 char id[20];//学号 }Stu;//typedef之后重定义的名称 Stu s1, s2;//全局变量s1,s2 int main() { Stu s3, s4;//局部变量s3,s4 return 0; }
typedef 重定义将结构体名称定义为Stu,这样一来我们创建结构体变量就会方便很多了,直接Stu后面加上变量名称,就可以很好的创建出一个结构体变量。
匿名结构体
匿名结构体,顾名思义就是没有名字,他必须在定义的时候完成变量的创建,而且只能使用这一次,后面无法再次创建新的变量。
struct //不写标签 { int arr[10]; int size; }str;//str是这个结构体的变量
当然这种结构体我们用的会比较少,一般是这个结构体只是自己用而且只用一次的情况下,我们可以考虑使用匿名结构体。
结构体的自引用
我们在一个结构体中包含一个类型为该结构体本身的成员是否可以呢?
这里我们先给大家介绍一下链表的结构:
链表的每一个节点有两个成员,一个是数值域data,另一个存放的是可以指向链表下一个节点的指针域。
那么了解了链表的这种结构,我们该如何去定义这种结构体呢?
先来看看这样子定义行不行?
struct Node { int data; struct Node next; };
结构体里面有一个data,还有下一个结构体,ok很完美,但是回头我们再来想一想
sizeof(struct Node)是多少呢? 一个int是四个字节,在一个struct Node类型的next,里面又包含了一个int类型和一个struct Node,这样无限循环下去,无限套娃,它所占的字节数也是无限大的。
显然这样定义是不可行的。
那我们试试另一种定义方式>
struct Node { int data; struct Node* next; };
一个结构体里面一个data,还有一个指向下一个结构体类型的结构体指针。
这样一来他所占的字节数就是可控的了。这种定义方式就是结构体的自引用的正确方式。
那么如何使用typedef重命名呢?
typedef struct Node { int data; struct Node* next; }Node;
切忌:不能将结构体中的struct Node*next写成Node*next;
结构体变量的初始化
struct Point { int x; int y; }p1; //声明类型的同时定义变量p1 struct Point p2; //定义结构体变量p2 //初始化:定义变量的同时赋初值。 struct Point p3 = {1,2}; struct Stu //类型声明 { char name[15];//名字 int age; //年龄 }; struct Stu s = { "zhangsan", 20 };//初始化 struct Node { int data; struct Point p; struct Node* next; }n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化 struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化
结构体内存的对齐
到这里我们已经掌握了结构体的基本使用方法
我们再来探讨一个更加深入的问题:计算一个结构体的大小。
这也是一个非常热门的考点:结构体的内存对齐
我们先来看看这些代码>
struct s1 { char c1; int i; char c2; }; struct s2 { char c1; char c2; int i; }; int main() { printf("%d\n", sizeof(struct s1)); printf("%d\n", sizeof(struct s2)); return 0; }
s1和s2里面的变量个数相同 ,那么好他们两个所占的字节数也一定一样咯。我们来运行看看>
咦为什么一个是12一个是8呢?他俩明明变量都一模一样,只不过就是顺序不一样而已,为什么所占的字节数就不一样了呢。而且就算把他们里面的变量单独加起来也就6个字节,那12和8是怎么来的呢?
到这里就不得不提出结构体内存对齐了。那又该怎样去对齐呢?我们一起来研究一下>
这样我们就可以理解为什么s1会是12个字节了。我们再来看看s2>
我们再来练习一道s3>
struct S3 { double d; char c; int i; };
答案是不是16呢我们来验证一下>
学到这里,反过来问一下,内存中国为啥那么会存在对齐呢?他又有什么意义呢?
对于这个问题大部分参考资料是这么说的:
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那我们在设计结构体的时候,我们既要满足对齐,又要节省空间要怎么做到呢?
让占用空间小的成员尽量集中在一起。
就好比如上面的s1、s2他们的成员都是一样的顺序不同他们之间所占字节就相差了4字节。
那这个默认对齐数可以修改吗还是只能是编译器自己规定的呢? 答案是可以修改