目录
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
一、概念:
同一种类型的数据de集合是数组,那么所谓的结构体就是是多种类型数据的集合
二、使用:
1、结构体的声明:
无论在那里用到结构体都少不了结构体的关键字 struct
如下:我们与学生姓名和身高为例子,student 是结构名;花括号里面的就是结构体的成员,里面的成员可以是任意的类型;而 struct student 就是结构体类型名;
以如下结构命名的类型名 struct 结构名
注意:struct和结构名之间要用空格隔开!!!!!!!!!!!!!!!
另外,结构体成员,前面的成员地址较小,后面的成员地址较高:
2、结构体特殊声明:
如下所示,缺少结构名,这种声明叫做——匿名结构体;只能使用一次,当第二次使用时会出错;若是使用的话,通过s(s的位置可以是你自己来命名的标识符)来使用即可;
3、结构体另一种说明:
使用 typedef 关键字,对结构体类型改名,可以简化类型名,直接用下面改的名字进行变量创建;
以下两种方式都可以;
4、结构体变量创建:
(1)、局部变量:
在主函数中(需要使用结构体的函数中)创建结构体变量
(2)、全局变量:
直接在结构体声明后进行创建;
提示:
结构体的声明就像是为我们提供制作章鱼烧的模具(告诉我们中间需要什么“材料”(什么类型数据)),但是光有模具是不行的,真正要吃的章鱼烧必须作为变量(对象)生成,然后里面每种配料加什么都是在此基础上进行啦!
5、结构体成员如何操作和初始化??
(1)、 .操作符:
形如 a . b的形式;表示结构体a的成员b;初始化和使用如下:
初始化与数组相似,对每个对应的成员(数组中叫元素)根据类型进行值的给定;若是后面未初始化也会给0(与数组相似)
(2)、->操作符:
->操作符用在结构体指针里面进行访问
指针解引用访问指向的内容, * 的优先级低于 . 因此,要加个括号,因为嫌麻烦和易出错;
接下来就有 -> 操作符
a -> b 用指访问结构体 a 的成员 b;
总而言之——>(*str).name == str->name;
6、结构体:
1、作为成员的结构体:
使用方法和上面大差别不大,此时相当于结构体里面套了一个结构体,总成员有3个
改代码只是略微编译,其实还有很多内容没写,因为只是为了便于理解如何使用作为成员的结构体,所以就简单写了点,看看即可
2、结构体数组:
还是以上面例子为例,这次,我们需要记录2个学生的姓名和身高
由上图看出:初始化与二维数组相似:
3、结构体的自引用:(在顺序表中会用到)
在结构体中包含一个类型为结构体本身的成员
这样写是错误示范,因为这样的话你的结构体大小就无法确定了,就是无穷大
那么,我们可以考虑用结构体指针来进行自引用,引入指针就可以确定结构体大小了
三、内存对齐:
为啥内存对齐
1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定 类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要 作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以 ⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两 个8字节内存块中。
总体来说:
结构体的内存对⻬是拿空间来换取时间的做法。
1、规则:
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
我们去计算结构体大小时这么画地址即可从0开始
2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。记住这个公式
VS 中默认的值为 8
- Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
编译环境为 vs 以下面例子为例:
int 占 4 (对齐数为 4)———— char 占 1(对齐数为1) 所以 最大对齐数为 4; ,最后面的值取值必须是4的整数倍
结果一致——>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
看下图·——》》》》》》》》》》》》》
也是一步一步画出来——》》》》》》》》》》》》》》》》》》》
答案是20;
5、总结:
先求各个类型对齐数,然后成员按顺序进行内存划分,第一个成员从0开始,往下走,走到对齐数的宽度,接下来的成员找到自己对齐数的整数倍处(就是自己的地址)开始,也是走自己的宽度,当所有成员找完自己的内存以后,再次往后找到最大对齐数的整数倍就是结构体大小!!!!
若第一个成员不是结构体,其他成员中有结构体成员的话,则走到结构体成员的第一个成员时,要找到嵌套结构体的最大对齐数的整数倍数,其他就和上面的走法一样的
2、修改默认对齐数:
#pragma 这个预处理指令,可以改变编译器的默认对⻬数。你可以填想要的默认对齐数,但是尽量填2的次方数;在结构体前面设置#pragma pack(填数字),在结构体后面再写一个#pragma pack(不填),则表示这个结构体之后取消设置的默认对齐数;
#pragma pack()
看我下面的代码:默认对齐数为1,那么下面的char和int的对齐数就是1了
//设置默认对齐数:
#pragma pack(1)
struct S
{
char c1;
int i;
char c2;
};
//取消设置的默认对齐数
#pragma pack()
int main()
{
int ret = sizeof(struct S);
printf("%d\n", ret);
return 0;
}
这句话——>>>”对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。“
所以最大对齐数也是 1 >>>画图就是下面所示:
3、结构体传参,尽量用地址进行传参;
原因: 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降
用同结构体指针即可,注意类型相同
三、结构体实现位段(有兴趣的了解一下):
1、位段概念:
为了节省空间,控制数据占据几个比特位,将几个成员共用一个字节的空间(当然存在一些问题)
注意:不可以跨平台使用
2、使用条件:
(1)、成员:必须位 int usigned int /signed int char (在c99可以选择其他类型)
(2)、位段成员名后面有一个 冒号 和 数字(占据几个比特位)
3、空间申请:
注意:不同平台,不同
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的
VS:先申请一块内存,在内存块中,从右向左申请空间,剩下的空间不足下个成员,使用时VS是浪费(不同平台标准不同)
看结果:
4、位段的跨平台问题
1. int 位段被当成有符号数还是⽆符号数是不确定的。
2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会 出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃 剩余的位还是利⽤,这是不确定的。
5、注意事项:
位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位 置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。 所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊ 放在⼀个变量中,然后赋值给位段的成员。
四、补充柔性数组:(涉及了动态内存申请)
C99中引入了柔性数组:在结构体中的最后一个元素允许是一个未知大小的数组(柔性数组)成员
特点:1、前面必须有一个成员
2、sizeof 计算大小时 不会将柔性数组加进去
如何用:
包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤ ⼩,以适应柔性数组的预期⼤⼩
如下:记住要也要判断和释放哦!!!!!!
这是第二种方案:
struct S
{
int n;
int *arr;//柔性数组
};
int main()
{
struct S* ps=NULL;
//后面+这部分就是对数组空间进行调整
//ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
ps = (struct S*)malloc(sizeof(struct S));
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->arr = (int*)malloc(5 * sizeof(int));
if (ps->arr == NULL)
{
perror("malloc");
return 1;
}
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
return 0;
}
注意:free顺序要先给柔性数组申请的空间释放,在释放结构体申请的空间