1.结构体类型的声明和初始化
结构体是一堆数据类型的集合体(与数组不同的是它可以是不同的数据类型)。结构体声明的是一个图纸,并不向内存申请空间,只有在设置变量的时候我们才进行划分空间给变量。结构体的变量数据类型可以理解成struct (结构体名),在初始化时我们就要牢记这个原则。
下面是结构体声明的示例:
struct s
{
int a;
char n[3];
};
下面是结构体变量的初始化和建立有两种,第一种优点是简单,缺点是这种变量是全局变量;第二种优点是局部变量,缺点是书写较复杂,推荐第二种。
struct s
{
int a;
char n[3];
}h = { 8,"akd" };
struct s
{
int a;
char n[3];
};
int main()
{
struct s f = { 9,"hak" };
return 0;
}
还有一种特别的声明可以忽略其结构体标签,只能使用上面第一种的形式来初始化变量,还有只能这种结构体使用一次。下面是使用示例
struct
{
int g;
short f;
char d;
}you={9,5,'f'};
还有一种结构体套结构体的形式如下,此外我们还可以在主函数的内部进行声明结构体都可以。注意完全相同的两个不完全声明对于编译器来说是二者类型是完全不同的如下图。
struct
{
int g;
short f;
char d;
struct s j;
}you = { 9,5,'f',{9,"aas"} };
2.结构体传参
结构体传参有两种形式一种是传值传参,另一种是传址传参;
void test1(struct s g)
{
;
}
void test2(struct s* p)
{
;
}
int main()
{
struct s f = { 9,"hak" };
test1(f);
test2(&f);
return 0;
}
传址传参较传值传参更有优越性,它可以防止函数内存空间占用过多,而传值传参会在函数对于结构体进行一次临时拷贝。
3.结构体的自引用和内存对齐
结构体自引用就是结构体声明内部结构体自己引用自己,在我们还要设置一定的限制条件否则会形成死循环。结构体自引用在形式上非常像函数递归。示例如下
struct d
{
int b;
struct d* next;
};
上面中结构体中又引用自己,添加了指针作为限制条件防止出现死循环。
结构体的内存对齐与结构体在内存中的存储有关。
结构体存在一个结构体变量偏移量,结构体在内存中开辟一片空间这一片空间是第一个字节的内存偏移量是0,第二个是1,后面以此类推。对齐数就是默认对齐数和数据类型大小的较小值(如果没有默认对齐数直接就是数据类型大小)。在存储时,不同数据结构体成员只能存储在它对应的对齐数的整数倍的内存偏移量上,以此为起点向后存储几个字节。结构体里面有结构体情况下,我们计算的对齐数是这个结构体那么最大对齐数,然后我们按照普通结构体成员进行对待就行。存储结构体是从上到下。结构体的数据大小是这个结构体最大对齐数的整数倍。
struct d
{
char a;
int b;
char c[10];
};
对于上面那个结构体而言,对于a对齐数是1占偏移量为0的字节,对于b由于对齐数为4,所以占用偏移量为4~7的字节,对于c对齐数为1,所以占用的是8~17的字节,整个结构体最大对齐数为4,所以这个结构体占用为20个字节。
看看输出结果是
内存对齐优点:1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访 问。
总体来说: 结构体的内存对齐是拿空间来换取时间的做法。
那么我们为了使用更加节省空间,我们可以将小字节的类型放在前面。
我们还可以修改默认对齐数使用#program pack(默认对齐数),括号那么填写默认对齐数就行,使用完要用#program pack()回到默认对齐数。
4.位段
位段定义时与结构体类似,但是位段里面只能有整形家族的数据类型,还有在位段里面每一个数据类型都是 int a:7的形式。下面是使用示例
struct g
{
int h : 7;
int v : 8;
};
h后面的数字代表了h开辟的内存大小是多少比特,不能超过最大int整形数所占用的字节。
位段在内存 中存储时先开辟一个字节,然后更据h所占用情况和编译器不同环境,决定是否要进行在一个字节内部是从左至右还是从右至左;决定当一个字节有空位时不满足下一个成员是否要先填充这个字节然后再填充下一个字节还是直接进行下一个字节。
位段有其本身的优点时可以将内存空间大大省略,缺点是跨平台问题,一个代码在不同平台有不同的解释,造成困难。
5.枚举常量
枚举常量是一个常量将一个以此命名的变量规范了他的取值范围,在枚举常量成员中每一个都有自己的编号,默认第一个是0,第二个是1,后面以此类推(我们可以修改第一个,达到修改整体的效果,我们还可以修改每一个,达到赋值效果)。只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。下面是使用示例:
枚举常量优点:1. 增加代码的可读性和可维护性 2. 和#define定义的标识符比较枚举有类型检查,更加严谨。 3. 防止了命名污染(封装) 4. 便于调试 5. 使用方便,一次可以定义多个常量
6.联合体(共用体)
联合体就是一个空间,联合体里面成员大家都可以使用这个空间假如int a和char b是共用体成员,int a是4个字节,char b就占用了int a第一个字节(低地址),下面是使用示例
union po
{
char a;
int b;
};
int main()
{
union po gh;
return 0;
}
联合体的存储空间至少大于最大空间字节数,当最大字节数不等于对齐数的整数倍时,空间就是对齐数的整数倍。
上面的开辟空间数是4个字节。
标签:进阶,struct,int,C语言,内存,对齐,字节,结构 From: https://blog.csdn.net/2401_86372508/article/details/141095230