一.结构体类型的声明
1.1结构体的引入
C语言提供了内置类型如:char、short、int、long、float、double等,这些通常用来修饰变量,当我们想用多个内置类型来修饰一个变量时,这些远远不够,假如我想描述一个学生,这时需要他的学号(字符串)、姓名(字符串)、年龄(整形)等,这些数据类型不同但又要结合到一起,这时我们就需要引入一个⾃定义类型----------结构体。结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量如:标量、数组、指针,甚⾄是其他结构体。
1.2结构体的声明
这个声明描述了一个由字符串表示的名字、整形表示的年龄、字符串表示的学号组成的结构体,注意声明结构体时并没有创建空间,只是描述了一个结构体,所以又称为模板。只有结构体变量才分配地址,而结构体的定义是不分配空间的。
下面介绍一下结构体:
1.struct是关键字,表示是一个结构体。
2.student是结构体标签名,struct student就是结构体类型。
3.接下来就是一个括号,括起了结构体成员列表,及就是每个成员变量,使用的都是其自己的声明方式来描述,用分号来结束描述
1.3结构体变量的创建与初始化
1.3.1结构体变量的创建和初始化
之前结构体类型的声明相当与只是告诉编译器一个结构体,并没有创建空间,这时我们需要创建结构体变量才能使用结构体,创建一个结构体变量;struct student s;s就是结构体变量,这里的struct student就和int等基本数据类型一样。
这里有两种结构体变量创建的方法,结果是一样的。
1.3.2结构体变量的初始化
1.3.2.1按照结构体成员的顺序初始化
这种方法与基本数据类型和数组类型的初始化相似,也是使用花括号括起来,用逗号分隔的初始化好项目列表,注意每个初始化项目必须要和要初始化的结构体成员类型想匹配。
struct Student s = { "张三", 20, "20230818001" };
1.3.2.2按照指定的顺序初始化
这种方法可以随意按顺序初始化,方式比较简单 , 点操作符(.)+成员名即可
struct Student s2 = { .age = 18, .name = "lisi", .student card = "20230818002"}
1.4结构成员访问操作符
1.4.1结构成员的直接访问
结构体成员的直接访问是通过点操作符(.)访问的。点操作符接受两个操作数,使⽤⽅式:结构体变量.成员名
1.4.2结构体成员的间接访问
有时候我们得到的不是⼀个结构体变量,⽽是得到了⼀个指向结构体的指针。
使⽤⽅式:结构体指针->成员名
1.5结构体的特殊声明
在声明结构的时候,可以不完全的声明,又称为匿名结构体
上⾯的一个结构在声明的时候省略掉了结构体标签(tag)
//注意这里不再是定义声明结构体类型,而是直接创建结构体变量了,这个编译器会分配内存的;
//这样的确可以省略结构体标签也就是结构体名,但是只能使用一次;
1.6结构体的自引用
在结构中包含⼀个类型为该结构本⾝的成员是否可以呢?
当结构体内包含一个结构体时,sizeof(struct Node)是否能算出来呢?答案是不能,仔细想想一个结构体内包含一个结构体时,一直这样那他的大小是无尽的。
那我们如何成功的自引用呢?这时候就要运用到指针。
二.结构体的内对齐
结构体学到现在我们怎么计算结构体的内存呢?
2.1结构体对齐规则
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
VS 中默认的值为 8 。- Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
例题举例
结构体第一个成员在偏移量为0处占一个字节空间,第二个成员的对齐数为4所以放置的地方为对齐数的整数倍,即是偏移量为4的地方,占用4个字节,第三个成员对齐数为1,则放在偏移量为8的地方,结构体总⼤⼩为最⼤对⻬数的整数倍,所以该结构体的大小为12个字节
注意虽然其中有的空间没放字节,但也属于结构体的内存。
2.2为什么存在内存对⻬?
1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定 类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要 作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以 ⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两 个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到:
让占⽤空间⼩的成员尽量集中在⼀起
2.3修改默认对⻬数
#pragma 这个预处理指令,可以改变编译器的默认对⻬数。
结构体在对⻬⽅式不合适的时候,我们可以⾃⼰更改默认对⻬数。
三.结构体传参
有以种两种传参方式,⾸选print2函数
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。
结论: 结构体传参的时候,要传结构体的地址。
四. 结构体实现位段
4.1 什么是位段?
位段的声明和结构是类似的,有两个不同:
1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以 选择其他类型。
2. 位段的成员名后边有⼀个冒号和⼀个数字(作用是表示该成员的二进制保存多少位)。
如int a=2 对应的二进制为0000 0000 0000 0010. int a:4;即二进制保留为 0010
4.2 位段的内存分配
1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
4.3位段的跨平台问题
1. int 位段被当成有符号数还是⽆符号数是不确定的。
2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会 出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃 剩余的位还是利⽤,这是不确定的。
总结: 跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。