自定义类型:结构体

一:结构体类型的声明结构体类型的声明

前面我们在讲解操作符的时候,已经学习了结构体的知识,这里稍微再说明一下。

1.1结构体回顾

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

1.1.1 结构的声明

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

1.1.2结构体变量的创建和初始化

1.2 结构的特殊声明

在声明结构的时候,可以不完全的声明。
上面的两个结构在声明的时候省略掉了结构体标签(tag)。
那么问题来了?
//在上面代码的基础上,下面代码合法吗?
p  = &x;

警告: 编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次。

typedef是对结构体的重命名,将上面的结构体命名为S,这样结构体就可以使用多次,正常的使用
typedef struct S
{
	int a;
	int b;
	int c;
}s1;
int main()
{
	struct S s = { 10,20,30 };
	struct S s2;


	return 0;
}

1.3 结构的自引用

在结构中包含⼀个类型为该结构本身的成员是否可以

struct Node
{
    int date;
    struct Node next;
};

上述代码正确吗?如果正确,那 sizeof(struct Node) 是多少?

仔细分析,其实是不行的,因为一个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。
正确的自引用方式:
struct Node 
{
   int date;
   struct Node * p;
};

在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看 下面的代码,可行吗?

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

答案是不行的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。

解决方案如下:定义结构体不要使用匿名结构体了

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

二:结构体内存对齐

我们已经掌握了结构体的基本使用了。
现在我们深入讨论一个问题:计算结构体的大小。
这也是⼀个特别热门的考点: 结构体内存对齐。

2.1 对齐规则

首先得掌握结构体的对齐规则:
1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较小值
- VS 中默认的值为 8
- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的
整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
struct S1
{
    char c1;
    int i;
    char c2;
};

2.2 为什么存在内存对齐?

大部分的参考资料都是这样说的:
1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在⼀起

2.3 修改默认对齐数

#pragma 这个预处理指令,可以改变编译器的默认对齐数
一般将对齐数设置为2的几次方或者是偶数。
结构体在对齐方式不合理时,我们可以更改默认对齐数。

三:结构体传参

#include <stdio.h>
struct S
{
	int data[1000];
	int num;
};
struct S s = { {1},10 };
void print1(struct S s1)
{
	printf("%d\n", s.num);
}
void print2(struct S* ps)
{
	printf("%d\n", ps->num);
}
int main()
{
	print1(s);
	print2(&s);
	return 0;
}

结构体名并不是和数组,函数一样时地址,而是和其他变量一样,在函数传参时,形参是实参的临时拷贝。此时如果结构体很大,直接传结构体会造成空间的浪费,不如传结构体地址

原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址。

四: 结构体实现位段

结构体讲完就得讲讲结构体实现 位段 (基于结构体,是二进制位) 的能力。

4.1 什么是位段

位段的声明和结构是类似的,有两个不同:
1. 位段的成员必须是 int unsigned int signed int ,在C99中位段成员的类型也可以
选择其他类型。
2. 位段的成员名后边有⼀个冒号和⼀个数字。
struct A
{
    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;
};
//后面的数字表示一个变量所占的比特位
//_a是变量名
//变量名的组成:字母,数字,下划线,首字符不能是数字

A就是一个位段类型

那A位段所占的内存是多大呢?

A上面的变量总共占47比特位,一个整型int是四个字节,不够,所以要用两个整型,即8个字节

4.2 位段的内存分配

1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
测试VS上的位段是如何排列的?

4.3 位段的跨平台问题

1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当⼀个结构包含两个位段,第二个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃
剩余的位还是利用,这是不确定的。
总结:跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

4.4位段使用的注意事项

位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,一个字节内部的bit位是没有地址的。

所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先入放在一个变量中,然后赋值给位段的成员。

struct A
{
    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;
};
int main()
{
    struct A s ={0};
    scanf("%d",&s.b);//error
    //正确做法
    int b = 0;
    scanf("%d",&b);
    s.b = b;
    return 0;
}

好啦!本篇对于结构体知识点的讲解就到此结束了,如果还有什么问题,可以在评论区里提出来;如果对文章中有错误的地方,也希望可以指出,在评论区告诉我,请各位多多指教!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

玖剹

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

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

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

打赏作者

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

抵扣说明:

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

余额充值