关于结构体

目录

一、概念:

二、使用:

1、结构体的声明:

2、结构体特殊声明:

3、结构体另一种说明:

4、结构体变量创建:

(1)、局部变量:

(2)、全局变量:

提示:

5、结构体成员如何操作和初始化??

(1)、 .操作符:

(2)、->操作符:

6、结构体:

1、作为成员的结构体:

2、结构体数组:

三、内存对齐:

为啥内存对齐

1. 平台原因 (移植原因):

2. 性能原因:

总体来说:

1、规则:

1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处

2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。

3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

 5、总结:

2、修改默认对齐数:

3、结构体传参,尽量用地址进行传参;

三、结构体实现位段(有兴趣的了解一下):

1、位段概念:

2、使用条件:

3、空间申请:

 4、位段的跨平台问题

5、注意事项:


一、概念:

        同一种类型的数据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顺序要先给柔性数组申请的空间释放,在释放结构体申请的空间

 

  • 16
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值