【C语言】自定义类型:结构体、枚举、联合(上)

简介: 【C语言】自定义类型:结构体、枚举、联合(上)

前言:

本篇重点为结构体内存对齐与位段的概念,该知识点是一个非常热门的考点,相信本篇文章会给你带来收获,博主会持续更新C语言的进阶知识,感兴趣的读者可以关注博主动态🌟🌟🌟

一、结构体

(1)结构体的特殊声明

我们知道结构体的声明大概为下面的方式:

struct tag
{
    member - list;//成员变量列表
}variable - list;//变量列表
struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
}; //分号不能丢

在声明结构的时候,可以不完全的声明。

比如:

//匿名结构体类型
struct
{
    int a;
    char b;
    float c;
}x;
struct
{
    int a;
    char b;
    float c;
}a[20], * p;

上面的两个结构在声明时省略了结构体标签tag,这种被称为匿名结构体

我们发现两个结构体的成员变量是完全一致的,那么我们可不可以认为上面的两个结构体是相同类型的呢?

//在上面代码的基础上,下面的代码合法么?

p = &x;

答案是不能,因此该代码也不合法,原因在于编译器会把上面的两个声明当成完全不同的两个类型。

(2)结构体的自引用

这里的自引用指的是指针,即结构体中可以包含该结构体类型的指针,如:

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

而不可以是:

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

原因在于其自身类型大小无法计算

即sizeof(struct Node)是多少? 你会发现他会无穷的一直计算下去。

刚才学习了匿名结构体,那么我们看下面的代码是否可行:

//代码3
typedef struct
{
  int data;
  Node* next;
}Node;//err

答案是不行的,原因在于该匿名结构体省略了结构体标签tag,

也就是说,Node的声明在Node* next之后,即还没有创建Node,就要使用。

(3)结构体嵌套初始化

struct Point
{
  int x;
  int y;
}p1;
struct Node
{
  int data;
  struct Point p;
  struct Node* next;
}n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化
struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化

像上面这种结构体中包含了另一个结构体初始化的情况就叫做结构体嵌套初始化。

(4)结构体内存对齐

接下来我们来学习如何计算结构体的大小,其涉及到了结构体内存对齐的概念,也是本篇最为重要的内容。

首先给出两个结构体,你认为他的大小为多少?

//练习1
struct S1
{
  char c1;
  int i;
  char c2;
};//6?
//练习2
struct S2
{
  char c1;
  char c2;
  int i;
};//6?

在学习结构体内存对齐前, 你可能认为两个都为6,但是实际上为12和8

也就是说结构体中有的成员变量之间存在空隙,浪费了一定的空间。

那么空隙如何计算呢?

宏:offsetof( type, member )(函数不能传参类型,但宏可以)

使用此宏需引用头文件stddef.h,该宏可以计算结构体成员相较于结构体起始位置的偏移量。

如图:

我们发现结构体大小的计算不是那么简单,那么结构体大小遵从什么样的规则呢?

结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

对齐数=编译器默认的一个对齐数与该成员大小的较小值。

这里数组比较特殊,它的大小为数组元素类型的大小,而不是整个数组的大小,比如char c[5],他的大小为1,若默认对齐数为8,则对齐数取较小为1。

VS中默认的值为8。gcc编译器无默认对齐数,对齐数就是结构体成员自身大小。

学习了如何计算,那我们来尝试两道题:

//练习1
struct S1
{
    double d;
    char c;
    int i;
};
//练习2-结构体嵌套问题
struct S2
{
    char c1;
    struct S1 s1;
    double d;
};

答案:S1大小16,偏移量分别为0、8、12;

          S2大小32,偏移量分别为0、8、24。

练习1分析:

练习2分析:

目录
相关文章
|
1天前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
5天前
|
编译器 C语言 C++
C语言结构体
C语言结构体
15 5
|
11天前
|
存储 C语言 C++
深入C语言,发现多样的数据之枚举和联合体
深入C语言,发现多样的数据之枚举和联合体
深入C语言,发现多样的数据之枚举和联合体
|
1天前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。
|
6天前
|
编译器 Linux C语言
C语言 之 结构体超详细总结
C语言 之 结构体超详细总结
9 0
|
11天前
|
存储 编译器 Linux
深入C语言:探索结构体的奥秘
深入C语言:探索结构体的奥秘
|
10天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
25 3
|
1天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
17 10
|
5天前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。
|
11天前
|
C语言
c语言回顾-函数递归(上)
c语言回顾-函数递归(上)
27 2