C语言结构体

目录

前言

1.结构体的声明与创建

2.结构体的自引用

3.结构体的初始化

4.内存对齐

这种内存会带来什么影响?

5.为什么会存在内存对齐

6.如何设计结构体

7.修改默认对齐数

8.结构体和位段


前言

结构体属于自定义类型,那么我们为什么需要自定义这个类型?

现实生活中的对象往往不是一个单一元素,如描述一个人,可以有身高,年龄,性别,体重等等,

对于C语言任意一种自带类型,如int,double等等都是不能描述的,因此便有了诸如结构体这种自定义的类型包括了多种类型的结构

1.结构体的声明与创建

与变量一样,结构体也需要声明,初始化等等操作

struct Stu 
{
    numebr_list//成员列表(包括各种类型)
};//不能省略最后的分号!!!!本质上这是一个语句,是用struct声明结构体的操作,是需要断开的
//像函数的声明一样最后需要分号

用struct这个关键字,声明一个结构体,此时这种结构体的类型是struct Stu。

上述的是正常情况下的声明,C语言还有一种特殊的声明方式——不完全声明

struct //没有Stu这个名字
{
    numebr_list//成员列表(包括各种类型)
}s1;

这种方式有几个问题需要注意:

1.这种声明只能在一开始就创建结构体也就是s1,因为缺少名字,也就不能再后面创建

2.这种声明方式在编译器眼里是独立类型

 上述代码用不完全声明的方式创建了结构体s1,结构体指针s2,然后让s2取s1的地址,只要二者的结构体类型一样,就不会有任何问题,可是这里VS2019报了警告说类型不兼容,由此可以看出用这种方式创建的结构体在编译器眼里是独立的类型,哪怕它们内部的成员都一样。

结构体的创建

有两种方式,一种是s1在定义的时候创建,而是单独用结构体的类型创建

#include<stdio.h>
struct Stu
{
    int n;
}s1;
int main()
{
    struct Stu s2;
    return 0;
}

注意s1创建在main函数外面,是全局变量,不初始化默认0,s2是局部变量

2.结构体的自引用

指的是结构体内部的一个特殊成员

例如链表

struct Stu
{
    int date;
    struct Stu* next;
};

两个成员一个是存放数据,一个存放下一个存放数据的结构体的地址,这个就是结构体的自引用

或许会有人好奇为什么是存放地址,而不是直接存放结构体,也就是

struct Stu
{
    int date;
    struct Stu next;
};

这种写法会导致这个结构体类型的大小无法计算,可以理解成一直套娃,无穷大

写成指针不仅节约空间,而且操作方便,可以计算大小

3.结构体的初始化

#include<stdio.h>
struct Stu
{
    int date;
    char name[10];
}s1={20,"zhang"};

int main()
{
    struct Stu s2={10,"wang"};
    return 0;
}

可以声明的时候直接初始化,也可以单独初始化

特殊情况:嵌套初始化,结构体内部有结构体

struct next
{
    int n;
    char name[10];
};
struct Stu
{
    int date;
    struct next date;
}s1={20,{10,"wang"}};

一个结构体内部成员需要用 {   }括起来。

不完全初始化

#include<stdio.h>

struct Stu
{
    int date;
    char name[10];
};

int main()
{
    struct Stu b={.name="wang"};
    return 0;
}

初始化是用  .  加成员名并赋初值,其他的编译器会自动根据类型赋0,字符就是'\0',指针是NULL,其他int,double等等都是0

4.内存对齐

首先:结构体的大小如何计算,这可不是单纯的直接结算a4字节+b,c两字节等于6字节,它们在内存上的存放是有一定偏移的,也就是我们所说的内存对齐

struct Stu
{
    int a;
    char b;
    char c;
};

下面是内存对齐的原则

什么是偏移量?

我们可以用一个offsetof函数来观察结构体中成员的偏移情况(也就是想较于起始地址的偏移字节数)

根据例子解读

如果是涉及到嵌套了结构体的结构体计算呢?

也是一样的计算方式,计算出结构体占用的内存,以及最大的对齐数,然后在新的结构体计算中与编译器默认的对齐数相比较,取小值

这种内存会带来什么影响?

struct Stu
{
	char c1;
	char c2;
    int i;
};

   

上面的排列算出来是8,下面的算出来是12

struct Stu
{
	char c1;
	int i;
	char c2;
};

 

 有没有发现明明成员一样但是s1消耗的内存空间大一点,这就涉及到我们结构体应该怎么写更好的问题了

5.为什么会存在内存对齐

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

解读:有些硬件不能随意访问内存,只能访问特定类型的数据,比如int型在4的倍数处获取,因此为了便于移植统一放在对齐后的位置

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,对于访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

设定 char c; int i;
解读:我们计算机假如是32位机器,也就是一次传输4字节内容(32位),如果不对齐,单是传递i需要传递两次,第一次访问c与c后面的三个字节,也就是i的前三个字节,第二次访问i的第一个字节及后面未知的三个字节,然后组合

对齐后可以直接一次传输到 i  的前面,第二次传输 i 的4字节,也就是i的传递只用了一次

 内存对齐本质就是利用空间换取效率的方式,占用空间固然大了,但是效率也高了

6.如何设计结构体

通过上面对内存对齐的阐述,其实浪费的空间就是一个小的类型分散了,造成了大类型不断开创新的空白内存而不放东西,改进措施就是把小的类型集中到一起(不是单纯的把大类型放前面或后面),多填补大类型造成的空白内存,而不是开创新的空白内存

7.修改默认对齐数

利用宏修改

#pragrma pack()

 这里把Stu结构体内部的对齐数修改成4,最后记得把它返回原本的默认值,没有返回那这个程序的默认对齐数就一直是4

一般两者是配套使用的,用来修改你想要修改的部分

8.结构体和位段

在存在内存对齐后,结构体就会占用很大的内存空间来提高效率,一个int型的1,(00 00 00 01)就占了4字节,这时候完全可以认为控制他只占一个字节就足以存储数据了,位段这个概念便是如此,位指的是二进制位。

struct Stu
{
    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;
};

意思是结构体Stu中的a变量只占用两位(而不是4字节),b 5位,c 10位。

解读一下代码是如何开辟空间的

首先a变量是int,以int的形式开辟,也就是32位(4字节),然后a取2,取5,c取10,此时还剩下15位,但是15位不够d用,再开辟一次,多出32位,注意此时d是取前面剩下的15位以及新开的15位还是直接取新开的30位这是不确定的,不同编译器是不同的

注意事项:

1.位成员只能是int,signd int ,unsignd int ,char(整型家族),来开辟1字节或者是4字节

2.位段的最大位数是4字节也就是32位16位机器的int是2字节,也就是16位

3.书写形式是变量名+冒号+数字,来控制所占用的位数

位段的出现可以大大降低结构体所占用内存的大小

位段虽好,但是有几个问题需要注意

移植性差,

因为位段内部成员被当成无符号型还是有符号型是取决于编译器的,

最大位数是不能确定的,

位段的成员在内存中分配是未知的,(不知道是高地址到低还是低地址到高)

位段成员是否填充空出内存区域是未知的,这一切都取决于编译器,编译器不同可能方式不同

(在上述红色字体有提到)

所以需要移植或者通用的代码不要用位段写

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值