【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分析:

目录
相关文章
|
23天前
|
存储 C语言
C语言程序设计核心详解 第九章 结构体与链表概要详解
本文档详细介绍了C语言中的结构体与链表。首先,讲解了结构体的定义、初始化及使用方法,并演示了如何通过不同方式定义结构体变量。接着,介绍了指向结构体的指针及其应用,包括结构体变量和结构体数组的指针操作。随后,概述了链表的概念与定义,解释了链表的基本操作如动态分配、插入和删除。最后,简述了共用体类型及其变量定义与引用方法。通过本文档,读者可以全面了解结构体与链表的基础知识及实际应用技巧。
|
28天前
|
C语言
c语言中的结构体
本文档详细介绍了C语言中结构体的使用方法,包括结构体的基本定义、变量声明与赋值、数组与指针的应用,以及结构体嵌套、与`typedef`结合使用等内容。通过示例代码展示了如何操作结构体成员,并解释了内存对齐的概念。
|
2月前
|
C语言
C语言结构体赋值的四种方式
本文总结了C语言结构体的四种赋值方式,并通过示例代码和编译运行结果展示了每种方式的特点和效果。
52 6
|
2月前
|
编译器 程序员 Linux
【C语言篇】结构体和位段详细介绍
跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
|
2月前
|
存储 编译器 C语言
【C语言篇】自定义类型:联合体和枚举详细介绍
像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型。
|
2月前
|
存储 C语言
C语言------结构体和共用体
这篇文章是关于C语言中结构体和共用体的实训,通过示例代码演示了结构体的定义、赋值、使用,以及如何使用结构体变量进行数据的组织和操作,包括输入、排序、求平均分和查找学生信息等功能。
C语言------结构体和共用体
|
20天前
|
存储 Serverless C语言
【C语言基础考研向】11 gets函数与puts函数及str系列字符串操作函数
本文介绍了C语言中的`gets`和`puts`函数,`gets`用于从标准输入读取字符串直至换行符,并自动添加字符串结束标志`\0`。`puts`则用于向标准输出打印字符串并自动换行。此外,文章还详细讲解了`str`系列字符串操作函数,包括统计字符串长度的`strlen`、复制字符串的`strcpy`、比较字符串的`strcmp`以及拼接字符串的`strcat`。通过示例代码展示了这些函数的具体应用及注意事项。
|
23天前
|
存储 C语言
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
|
23天前
|
存储 C语言
C语言程序设计核心详解 第七章 函数和预编译命令
本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
|
28天前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
下一篇
无影云桌面