C++——详细讲解结构体

结构体的初始化,自引用以及你可能犯的错误 

那么怎样来初始化结构体呢?

以下便是一个举例并告诉大家来怎样输出你初始化结构体的内容:

#include <stdio.h>
struct Stu
{
 char name[20];//名字 
 int age;//年龄 
 char sex[5];//性别 
 char id[20];//学号 
};
int main()
{
 //按照结构体成员的顺序初始化 
 struct Stu s = { "张三", 20, "男", "20230818001" };
 printf("name: %s\n", s.name);
 printf("age : %d\n", s.age);
 printf("sex : %s\n", s.sex);
 printf("id : %s\n", s.id);

除了这一种结构体,还有一种匿名结构体

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

那么问题也会随之而来,假设我设置了两个内容一样的匿名结构体,如下面所示:

struct 
{
  int a;
  int b;
  int c;
}*a;
struct 
{
  int a;
  int b;
  int c;
}b;

那么我可以说a=&b;这样来进行吗?

我们会看到发生了这样的错误 ,这是因为编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。

那么我怎样来完成结构体的自引用呢?

我先打一个代码,大家看看是否正确

struct str
{
  int a;
  int b;
  struct str x;
};

这是我们新手第一个想到的代码。但是这个代码是错误的,因为我计算他的内存的话,他是无限大的,因为假设他的内存为4,那么自引用后,内部的结构体便也为4,会形成结构体套结构体无限套娃的情况,那么他的内存便会无限大。

那么我们应该怎样来定义才是正确的呢?

struct str
{
  int a;
  int b;
  struct str*x;
};

只需将这个改成指针的形式便是正确的,因为我们都知道指针在32位的情况下为4个字节,64位的情况下为8个字节。

那么肯定会有人疑问,匿名的结构体可以实现自引用吗?

那么我给他定义一个名字,结构体可以实现自引用吗?请看下面的代码: 

因为Node是对前⾯的匿名结构体类型的重命名产⽣的,但是在匿名结构体内部提前使 ⽤Node类型

来创建成员变量,这是不⾏的。 

当我们了解到初始化时我们需要打上结构体的名字如:struct str a。这样才可以定义,那么我们可以让他简单一点吗?当然可以,这便用到了我们学到的typedef 

我们可以这样写来让我们少一些代码:

typedef struct Node
{
	int data;
}Node;

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

 这是两种书写方式供大家来选择。

结构体怎样在计算机中进行储存 

 那么我们来讲一下如何计算结构体的内存:

首先得掌握结构体的对齐规则:

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

 2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数=编译器默认的⼀个对齐数与该成员变量大小的较小值。(VS 中默认的值为 8   -Linux中gcc没有默认对齐数,对齐数就是成员自身的大小)

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

4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

我会通过几个例子来为大家讲解:(这是在VS编译器的情况下讲解)

struct S3
{
 double d;
 char c;
 int i;
};

 

1. 我们通过第一条规则我们可以知道,不管你是什么,你应该从0开始,占取你所需要的内存,我们知道double类型占8个字节所以我用黄色占了8个字节。

2.我们知道VS编译器下默认值为8字节,但是char占一个字节,所以遵循最小值的原则,所以对齐数是1,但我们知道不管多少都是1的整数倍数,所以char占了8号位。

3.int类型为4个字节,VS默认8个字节,所以应该选择是4的整数倍数的位置进行填补,但是我们都知道9,10,11都不是4的整数倍,所以我从12开始占我要的4个字节。一直到15号位置。

4.根据第三条对齐规则,我一共占的内存应该是最大对齐数的整数倍,在double,int,char中,最大对齐数应该是double的最大对齐数,所以一共占的内存应是8的整数倍,我们数一下从0——15一共占了16个字节,是8的整数倍,所以一共占了16个字节

(空间中没用到的地方在讲完内存后,我会进行讲解。)

struct S4
{
 char c1;
 struct S3 s3;
 double d;
};

这时一个嵌套的例子:(其中的S3便是我上面所举例子的结构体)

 

1.那么我们知道char是1个字节 ,那么不管怎样都是从0开始,所以我用黄色占了一个位子。

2.根据对齐规则的第4条,嵌套时,内存对齐的应该是所嵌套结构体内部的最大对齐数(我们从上面可以知道double的对齐数是8,char是1,int是4),所以我们嵌套结构体的对齐数是8,那么我们应该先找8的整数倍,所以我们从8开始占位置,占取的位置数量是嵌套结构体本身的内存(s3的内存是16个字节)。

3.double是8个字节,VS的默认值是8,最小值还是8,我们应该先找到8的整数倍的位置,所以我从24开始存储double的内存。

4.S4的总内存应该是整个结构体的最大对齐数(包含嵌套结构体的最大对齐数)double对齐数是8,S3的对齐数是8,char的对齐数是1,所以总的内存应是8的整数倍,从0——31一共占了32个字节,是8的整数倍,所以总的内存32个字节。

那么为什么要遵循对齐规则来储存呢?

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

2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要 作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以 用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

 总体来说:结构体的内存对齐是拿空间来换取时间的做法。

在一些编译器中改变初始的默认值的方法

我们可以通过#pragma 这个预处理指令,可以改变编译器的默认对齐数。这样就可以将VS的默认8个改成你想要的了。下面举一个例子

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1 
struct S
{
 char c1;
 int i;
 char c2;
};

 

我们也可以取消这个改变,变成默认 

#pragma pack()//取消设置的对⻬数,还原为默认 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值