c语言:结构体(详解)

初识结构体

  • 一.结构体声明
    • 1.结构体的概念
    • 2.声明
  • 二.结构体的基础使用
  • 三.结构体变量的定义和初始化
  • 四.空结构体
  • 五.柔性数组
    • 1.定义
    • 2.使用
  • 六.结构体内存对齐
  • 七.位端

c语言:结构体(详解)_第1张图片

一.结构体声明

1.结构体的概念

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

这里与数组做出区分:数组是一组相同类型元素的集合

结构体主要是用来描述复杂对象,比如一本书,我们需要描述它的内容,作者名,售价…很明显只用int char…类型是不行的

2.声明

c语言:结构体(详解)_第2张图片

至于variable-list是什么请看下文结构体的基础使用

例子:我需要描述一个学生

c语言:结构体(详解)_第3张图片

二.结构体的基础使用

c语言:结构体(详解)_第4张图片

注意结构体的声明只是定义了该结构体的类型(这类型就像是int ,char…是结构体被定义的类型),而s1,s2,s3才是向计算机申请了一块空间

c语言:结构体(详解)_第5张图片

而创建的变量才开辟空间

c语言:结构体(详解)_第6张图片

这里看看variable-list是什么

c语言:结构体(详解)_第7张图片

s1,s2,s3和s4,s5的区别就像是定义一个全局变量i和一个局部变量i的区别

结构体的成员可以是变量,字符,数组甚至是其他结构体

三.结构体变量的定义和初始化

c语言:结构体(详解)_第8张图片

定义其实很简单,一般有三种方法,其中s4,s5,s6是全局变量,s1,s2,s3是局部变量

接下来初始化结构体变量
c语言:结构体(详解)_第9张图片

这里注意括号内的元素要一一与声明内的相对应

那接下来将它打印出来

c语言:结构体(详解)_第10张图片

在这里插入图片描述

这里打印也是需要依次对应的,.操作符就是专门访问结构体

接下来使用指针打印

c语言:结构体(详解)_第11张图片
在这里插入图片描述

这里的道理其实是一样的传的是s1的地址那么*s1就是s1,然后再用.操作符。

其实这样写有些麻烦,所以c语言有->符号专门访问这种传址调用

c语言:结构体(详解)_第12张图片

四.空结构体

c语言:结构体(详解)_第13张图片c语言:结构体(详解)_第14张图片

结论是结构体有多大和平台有关系,接下来来讨论一下空结构体有多大

c语言:结构体(详解)_第15张图片

这里可以看出在VS里,空结构体是不能被定义的(如果是c++项目是可以编过的),它要求至少有一个成员。那么在Linux环境下呢?

c语言:结构体(详解)_第16张图片

c语言:结构体(详解)_第17张图片

可以看出,在Linux环境里空结构体是直接编过了并且大小是0.那么我此时就有些好奇了,空结构体能不能定义变量呢?该变量的大小是多少呢?

c语言:结构体(详解)_第18张图片

c语言:结构体(详解)_第19张图片

我们发现空结构体可以定义变量并且该变量的大小是0。那既然空间是0,可不可以存储数据呢?答案是不行的

c语言:结构体(详解)_第20张图片
c语言:结构体(详解)_第21张图片

换言之,在c语言中可以定义一个大小为0的变量,但是因为该变量没有空间,所以不能赋值并且不同的编译器对空结构体的要求不一样,要从多种环境下看

五.柔性数组

1.定义

c语言:结构体(详解)_第22张图片c语言:结构体(详解)_第23张图片c语言:结构体(详解)_第24张图片

柔性数组不同于变长数组,它只能被定义在结构体中

对于c语言是不能定义一个大小为0的数组的

c语言:结构体(详解)_第25张图片

但对柔性数组来说,我们一般将它的大小填为0并且编译器是能编过的

c语言:结构体(详解)_第26张图片
其实以上就是一个柔性数组的定义,但根据我们习惯,我们尽量把柔性数组放在最后定义,也就是如下

c语言:结构体(详解)_第27张图片

2.使用

首先要明确的是柔性数组不占空间

c语言:结构体(详解)_第28张图片

柔性数组就是帮助我们开辟空间的

c语言:结构体(详解)_第29张图片

c语言:结构体(详解)_第30张图片

其实严格意思上讲,柔性数组就是将结构体变长

c语言:结构体(详解)_第31张图片

六.结构体内存对齐

看一个例子

c语言:结构体(详解)_第32张图片

两个结构体我们只是调整了顺序但是它们的内存大小却截然不同,这就是因为内存对齐。

1.结构体的第一个成员,对齐到结构体在内存中存放位置的0偏移处。

c语言:结构体(详解)_第33张图片

c1存到偏移量0位置。

2.从第二个成员开始,每个成员都要对齐到对齐数的整数倍数。

对齐数是结构体成员自身大小和默认对齐数的较小数。在VS里,默认对齐数是8;在Linux里没有默认对齐数。

接下来存放i,i自身大小是4而VS的默认对齐数是8,4<8,按照4来对齐。偏移量必须是对齐数的倍数,也就是说i必须从偏移量为4的位置开始储存。

c语言:结构体(详解)_第34张图片

接下来c2的分析方法与i一样。

c语言:结构体(详解)_第35张图片

3.结构体的总大小必须是所有成员对齐数中最大的对齐数的整数倍。

这里最大对齐数是4,那么它的总大小应该是4的倍数,而上面一个9个字节很明显不是4的倍数,所有得继续向下走。

c语言:结构体(详解)_第36张图片

所以stu1的总大小就是12。同理stu1按照这么分析就是8.

再来一个例子

c语言:结构体(详解)_第37张图片
在这里插入图片描述

此时在结构体内包含另一个结构体又有新的规则。

4.如果结构体内嵌套结构体,里面的结构体要对其自己最大成员的整数倍。

s3的大小是16(这里不再计算了),而s3里最大对齐数是8,所以s3应该对齐8的整数倍。

c语言:结构体(详解)_第38张图片

接下来就是放d。

c语言:结构体(详解)_第39张图片

从0到31一共32个字节,判断是否为最大对齐数的整数倍(要包含嵌套结构体里的最大对齐数,8)。可以发现最大对齐数是8,而32是8的倍数,所以32就是s4的大小。

为什么要进行结构对齐呢?

c语言:结构体(详解)_第40张图片

下面是内存不对齐情况,a必须读取两次,但在上面内存对齐下,a只需要读取一次就可以了。注意这里内存一次读取多少字节却决于机器。

c语言:结构体(详解)_第41张图片c语言:结构体(详解)_第42张图片

自定义默认对齐数

上面说到,在VS里,默认对齐数是8.但有些时候这种默认对齐数可能并不合适,所以c语言又提供了一种宏#pragma pack来自定义对齐数。

c语言:结构体(详解)_第43张图片
在这里插入图片描述

#pragma pack(1),就是将默认对齐数改为1。而下面的#pragma pack()就是取消修改。意思是4这个修改的对齐数到#pragma pack就结束了。

七.位端

位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int(char也可以,因为char本质上也是整形) 。
2.位段的成员名后边有一个冒号和一个数字。

c语言:结构体(详解)_第44张图片
在这里插入图片描述

位端中的位其实就是比特位。一个整形应该是4个字节,32个比特位。我们看_a后面的2就代表它需要2给比特位,同理_b需要5个比特位,_c需要10个比特位,_d需要30个比特位。总共需要47个比特位,很明显只有两个整形才能装下,故该结构体的大小是8个字节。(位段本身设计就是为了节省空间,所以不用对齐)

内存分配方式

1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
4. 以上面的例子为例,a+b+c为17个比特位,那么一个整形还剩下15个比特位。下面的d需要30个比特位很明显装不下,需要在开辟一个整形空间,但它究竟是会将剩下的15个比特位占满还是直接从新的空间里开辟,c语言没有明确规定,取决于编译器。

c语言:结构体(详解)_第45张图片

位段的跨平台性

1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。

总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

c语言:结构体(详解)_第46张图片

你可能感兴趣的:(#,初识c语言,c语言,c++,数据结构)