C语言中自定义类型:结构体

本文详细介绍了C语言中的自定义类型结构体,包括其声明、成员概念、声明规则,以及结构体变量的创建、初始化、内存对齐原理和性能影响。还探讨了如何通过#pragmapack修改默认对齐数。
摘要由CSDN通过智能技术生成

本篇博客和大家分享一下自定义类型:结构体。这里我们先提一下类型,类型有内置类型(char、short、long、int等)和自定义类型,内置类型创造出来可以直接使用,C语言本身自带的一种类型,当自定义类型不能满足的时候,支持自定义一些类型(当然不是随便定义的):结构体、枚举、联合体。数组(一种相同类型的集合)其实也是一种自定义类型,前面博客已经写过了这里就不提了,接下来介绍自定义类型中的结构体:

1. 结构体类型的声明

1.1 结构体的概念

当我们描述一个人的年龄时会写 int age=10; 但当描述稍微复杂的类型时,直接使用内置类型时不行的,例如我们描述一个人的信息(包括姓名、年龄等)。这时候就需要一个自定义类型来描述,就有了结构体。结构体中可以包含各种类型的数据,用来描述一个复杂对象的各种属性。结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

1.2 结构体的声明

 

struct tag //名字可以自己定义
{
  member-list;//成员列表,可以有多个成员
}variable-list;//变量列表

我们试着使用结构体描述一个学生:

struct student
{
  char name[20];//名字
  int age;//年龄
  char sex[10];//性别 
}

1.3 特殊的声明

在声明结构体的时候,可以不完全声明。例如:

struct 
{
  int a;
  char b;
  float c;
}x;//定义全局变量x并且只能在这里创建变量
strcut
{
  int a;
  char b;
  float c;
}a[10],*p;

上面两个结构在声明的时候省略了结构体的标签(tag)。那么问题来了,在上面代码的基础上,下面的代码合法吗?

p=&x;

很明显是不合法的,原因是:编译器会把上⾯的两个声明当成完全不同的两个类型,所以是非法的。匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次。

1.4 结构的自引用

在结构中包含⼀个类型为该结构本⾝的成员是否可以呢?⽐如,定义⼀个链表的节点:
 

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

这段代码正确吗?如果正确那么sizeof(struct Node)是多少呢?仔细分析一下其实是不⾏的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。正确的自引用方式:

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

在结构体自引用使用的过程中,夹杂了typedef对匿名结构体类型重命名,也容易引入问题,看看下面的代码,可行吗?

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

答案是不行的,因为Node是对前⾯的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。解决方案如下:定义结构体不要使用匿名结构体了。
 

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

2. 结构体的创建和初始化

有了结构体类型,那如何定义变量,其实很简单,结构体变量的初始化使用{}。

struct point
{
  int x;
  int y;
}p1;//声明类型的同时创建变量

struct point p2;//定义结构体变量

struct point p3={1,2};//初始化

struct Node
{
  int data;
  struct point p;
  struct Node *next;
}n={10,{1,2,},NULL};//嵌套初始化

有的就想搞特殊点,初始化可以不按顺序初始化吗?当然是可以滴,指示器初始化,这种方式允许不按照成员顺序初始化:

struct student
{
  char name[10];
  int age;
};

struct student a={"张三",10};//常规初始化
struct student b={.age=15,.name="李四"};//指示器初始化

那我们怎样来打印结构体中的数据呢?打印使用 自己定义的变量名称+.+对应的数据 请看下面这段代码:

#include<stdio.h>
struct student
{
  char name[10];
  int age;
}a={"张三",10};
int main()
{
  printf("%s %d\n",a.name,b.age);
  return 0;
}

 

3. 结构体内存对齐 

我们已经了解了结构体的基本用法了,现在我们深入讨论一个问题:计算结构体的大小,这也是一个比较热门的话题:结构体内存对齐。

3.1 对齐规则

我们来了解一下结构体对齐规则:

1.结构体的第一个成员对齐到相对结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
   对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。(因为我自己使用的VS编     译器,这里我提一下,VS对齐数默认的值为8)

3.结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大     的)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍         处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对⻬数)的整数倍。

这里拿一段代码举个例子:

struct S
{
  char a;
  int b;
  char c;
}a={'s',10,'z'};
int main()
{
  printf("%d\n",sizeof(a));
  return 0;
}

 

这时大家是否有疑问呢?char类型占1个字节,int类型占4个字节,1+4+1算出来不是6吗?为什么是12?别急对照着下面这张图给大家讲解:

 

首先,我们假设为0的那个表格为起始地址,a的地址从0处开始, 那么每个数字所对应的表格就相当于它的偏移量,根据对齐规则第一条,我们可以知道s存放在偏移量为0的位置,又因为它是char类型只占1个字节,所以橘黄色的部分代表它的存储,接下来我们要放第二个成员了,根据对齐规则第二条可以算出对齐数为4,所以10要对齐到偏移量为4的倍数的地方(即为图中偏移量为4的位置),粉色部分代表它的存储,那有人就要问了那前面没涂色的地方怎么办呢?浪费了。同理蓝色部分代表z的存储,然后根据对齐规则的第三条,我们可以算出结构体总大小为12。

这里给大家补充一个函数:offsetof 他是用来计算结构体成员相对于起始位置的偏移量,它需要头文件<stddef.h>,我们看一下cplusplus官网对他的介绍:

那我们通过对下面的代码的调试来了解offsetof函数:

#include<stdio.h>
#include<stddef.h>
struct S
{
  char a;
  int b;
  char c;
};
int main()
{
  printf("%d\n",offsetof(struct S,a));
  printf("%d\n",offsetof(struct S,b));
  printf("%d\n",offsetof(struct S,c));
  return 0;
}

 

3.2 为什么存在内存对齐

这里没有一个特别笃定的原因,大部分参考资料是这么写的:

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

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

2.性能原因 

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

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

3.3 修改默认对齐数 

 #pramga 这个预处理指令,可以修改编译器的默认对齐数,具体操作如下:

#pramga pack(1)//将默认对齐数改为1
#pramga pack()//取消修改的默认对齐数,还原成最初的默认对齐数

本篇博客到这里就结束啦,大家有问题可以评论出来或者私信我哦。 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野生的编程萌新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值