C 语言笔记: 链表节点实现技巧--struct的妙用

    链表节点实现技巧–struct的妙用

作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢!


废话

C 语言虽然只提供了非常简单的语法,但是丝毫不影响 C 语言程序员使用 C 来实现很多让人叹为观止的高级功能.

本文介绍一项在 C 语言中非常常见的链表节点实现的一个技巧.

也许你看过了好几本 C 语言的书籍,也看到过相关的介绍,但是你却没有很在意,那么这里我们来详细的学习一下.

接下来,我们将描述一个链表节点的实现,先不要失望,它的实现可能并不像所想的那么简单.

节点的定义

typedef struct _LIST_ENTRY {struct _LIST_ENTRY *Next;
} LIST_ENTRY, *PLIST_ENTRY;

LIST_ENTRY 代表双向链表的一个节点. Next 是指向下一个节点的指针.

但是对于上述节点,我们没法使用它, 因为它除了能表示一个节点之外, 无法包含其他任何额外的信息.

好,这里我们假设我们想创建一个表示学生的链表,我们先定义一下学生结构吧.

typedef struct _STUDENT {char name[64];int  age;
} STUDENT, *PSTUDENT;

我们随手就写出来一个表示学生的结构体,它很简单,是因为这里我们只是用它来说明我们如果使用 LIST_ENTRY, 而并不想讲解如果构建一个学生管理系统.

为了让 STUDENT 结构可以成为链表的一个节点,我们需要将他们合并一下. 然后我们的 STUDENT 结构就变成了这样:

typedef struct _STUDENT {LIST_ENTRY list_entry;char name[64];int  age;
} STUDENT, *PSTUDENT;

注意,我们将 LIST_ENTRY 结构嵌套在 STUDENT 结构的开始位置,这将使得后续的实现简单很多. 放在其他位置当然也是可以的,但是却会把事情搞得复杂起来.

使用

既然结构体定义好了,下面我们就来看看,我们如何使用这个结构体,以及这个结构体体的巧妙之处,这也是本文想要表达的东西.

再次重申一下,本文是想描述这个结构体用法的妙处,无意于实现一个完整的链表. 因此只给出了最简陋的版本.

#define GET_STUDENT(address, type, field) ((type *)( \(char *)(address) - \(char *)(&((type *)0)->field)))PLIST_ENTRY list_header = NULL; // 链表头// 在链表的尾部添加一个新的节点
int add_student(char* name, int age) {// create a student with the given parametersPSTUDENT student = malloc(sizeof(STUDENT));if (student == NULL)return -1;memset(student, 0, sizeof(STUDENT));strcpy(student->name, name);student->age = age;if (list_header == NULL) {list_header = &student->list_entry;} else {PLIST_ENTRY p = list_header;while (p->Next) {p = p->Next;}p->Next = &student->list_entry;// student->list_entry.Next is NULL}
}int main() {// 添加两个节点add_student("student abc", 22);add_student("student ijk", 25);// 遍历整个链表请注意这里!!!!/for (p = list_header; p != NULL; p = p->Next) {// get the student structPSTUDENT student = GET_STUDENT(p, STUDENT, list_entry);// PSTUDENT student = (PSTUDENT)(((char*)p - (char*)(&((PSTUDENT)0)->list_entry)));printf("student name: %s, student age: %d\n", student->name, student->age);}/// 省略释放内存的代码return 0;
}

解析

如果至此,你已经看到了它的巧妙之处,就不需要再浪费时间看接下来的部分了.

上述代码的重点在哪儿呢?
重点就是 GET_STUDENT 那个宏.

为了方便调试,我们在提供了 42 行的宏展开之后的形式以方便调试.

  1. 首先需要注意的时,我们的链表中每一个节点的类型 STUDENT,而不是 LIST_ENTRY.
  2. 但是需要注意,我们的 STUDENT 结构中第一个字段是 LIST_ENTRY,这是我们 GET_STUDETN 正常工作的前提.
  3. 那么为什么这样子就能工作呢?
    首先,在 42 行加个断点调试一下,我们得到了如下结果:

在这里插入图片描述

请注意,我们此时 p 的地址和 student 的地址是一样的. 这是因为我们 LIST_ENTRY 放在 STUDENT 结构体的第一个位置,而我们在往链表中添加新节点的时候添加的都是 STUDETN 结构,在这种情况下,我们可以将一个 STUDENT 结构的指针赋值给一个 LIST_ENTRY 的指针.

这里我们在看一样整个 student 结构的内部布局:
在这里插入图片描述

这里首先看到我们 student 的内存开始地址为 0x00000000600049fb0, 这个值和我们上图中显示的一致的,因为我的计算机是 64 位机器,因此地址占用 8 的字节.
这里我们详细解析一下这些字节的意义:
(1) 因为我们 student 的第一个字段是 LIST_ENTRY,而 LIST_ENTRY 中仅包含一个指向链表下一个节点的 Next 指针. 因此毫无疑问前八个字 10a00400 06000000表示当前字节的下一个节点的首地址. 注意这里是小段表示,因此和第一张图片中看到的地址字节序列正好相反, 最高有效位在最后.
(2) 解析来的字节值止倒数第三个字节的内存都是用来保存 student->name
(3) 最后两个字节表示 student->age,它的值是小段编码的 16.
4. 接着,我们让循环继续,定位链表的第二个节点,看一下内存布局:

在这里插入图片描述

验证一下我们上面说的对不对. 第二个节点的地址为 0x0000000060004a010, 它正好与 10a00400 06000000 吻合,因为我们知道第一个节点的 list_entry->Next 指向的正是当前节点. 它的前两个字节为 0,是因为当前节点的 Next 是空. 接下来的字段与 (2)(3) 小结相同,这里我们不再解释.

总结, LIST_ENTRY 与 STUDENT 的结构体巧妙的使用了 C 语言结构体内存布局的特点, 将 STUDENT 结构体置入一个 LIST_ENTRY 的链表. 它的优点是什么呢?

这就使得我们可以将任何结构放入我们使用 LIST_ENTRY 定义的链表中,而我们不必为每一个需要放入链表的结构单独定义相关的字段使得他们得以互联. 这样做之后我们等于将链表相关的逻辑从真正用来保存信息的结构体中抽离出来,我们在编写操作链表的方法时几乎可以不关注存入链表中的真正的 student 类型.

欢迎交流任何想法.

End…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499686.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

协议簇: Media Access Control(MAC) Frame 解析

Media Access Control(MAC) Frame 解析 前言 千里之行,始于足下。 因为个人从事网络协议开发,一直想深入的学习一下协议族,从这篇开始,我将开始记录分享我学习到的网络协议相关的知识 简介 引用百度百科的描述: 数…

协议簇:Ethernet Address Resolution Protocol (ARP) 解析

简介 前面的文章中,我们介绍了 MAC Frame 的帧格式。我们知道,在每个 Ethernet Frame 中都分别包含一个 48 bit 的源物理地址和目的物理地址. 对于源地址很容易理解,该地址可以直接从硬件上读取. 但是对于一个网络节点,他怎么知道…

协议簇:IPv4 解析

简介 IP 是一种无连接的协议. 操作在使用分组交换的链路层(如以太网)上。此协议会尽最大努力交付数据包。 尽最大努力意味着: IP 协议不保证数据的可靠传输, 没有流量控制机制, 不保证传输序列(意味着 IP 数据包会在传输过程中乱序), 没有…

协议簇:ICMP 解析

简介 ICMP 是 Internet Control Message Protocol 的简写. 它主要用来调试网络通信环境中存在的问题. 比如,当 IP 数据包总是无法正常的发送到目的地址, 当网关没有足够的 buffer 来转发对应的数据包 等问题. 值得一提的是,它属于网络层,不属…

协议簇:TCP 解析: 基础

简介 本文我们将从 RFC 学习一下 RFC793 中描述的 TCP 协议. 这将区别于通常讲解计算机网络书籍中所描述的 TCP. 但他们必然是相统一的,不会互相冲突. 系列文章 协议簇:TCP 解析:基础 协议簇:TCP 解析:建立连接 协议…

协议簇:TCP 解析: 建立连接

简介 接前文 协议簇:TCP 解析: 基础, 我们这篇文章来看看 TCP 连接建立的过程,也就是众所周知的”三次握手“的具体流程. 系列文章 协议簇:TCP 解析:基础 协议簇:TCP 解析:建立连接 协议簇&a…

协议簇:TCP 解析: 连接断开

简介 接前文 协议簇:TCP 解析: 建立连接, 我们这篇文章来看看 TCP 连接断开的过程,也就是众所周知的”四次挥手“的具体流程. 系列文章 协议簇:TCP 解析:基础 协议簇:TCP 解析:建立连接 协议…

协议簇:TCP 解析: Sequence Number

简介 序列号(Sequence Number) 是 TCP 协议中非常重要的一个概念,以至于不得不专门来学习一下。这篇文章我们就来解开他的面纱. 在 TCP 的设计中,通过TCP协议发送的每个字节都对应于一个序列号. 由于每个字节都有自己的序列号&a…

CodeTank iOS App Technical Support

CodeTank iOS App Technical Support For All Email: z253951598outlook.com TEL: 86-17782749061 App Screen Shoots

CentOS 7 防火墙命令

查看防火墙状态 systemctl status firewalld如果已经开启,状态为 active 如果未开启,状态为 inactive 开启防火墙 systemctl start firewalld关闭防火墙 systemctl stop firewalld查看当前防火墙的配置 firewall-cmd --list-all这里,我…

QTcpSocket connectToHost 建立连接速度慢问题

问题场景 在使用 QT 开发一个客户端 App 的时候,我们通过 QTcpSocket 与后台服务器进程通信。 后台程序使用其他语言编写。 问题: 在客户端启用之后尝试建立与后台程序的 TCP 连接的时候,发现连接速度非常慢(肉眼可见的慢&#x…

GTank iOS App Technical Support

GTank iOS App Technical Support For All Email: z253951598outlook.com TEL: 86-17782749061 App Screen Shoots ​​

证书体系: CSR 解析

原文同时发布于本人个人博客: https//kutank.com/blog/cert-csr/ 简介 CSR 全称 “证书签名请求”(Certificate Signing Request). 本文我们将来详细的学习 CSR 的知识,重点集中在 CSR 所包含的信息,及其意义。 CSR 的作用: CSR 通常由想要获…

模拟网页行为之工具篇二

先说360浏览器,打开开发者选项,可以看到界面提供了几个功能选项,如图: 这个图片的第一个搜索图标点中过后,再去选中网页你感兴趣的部分就可以在Element选项中跳转到你感兴趣的代码。也可以直接ctrlF2搜寻你感兴趣网页元…

模拟网页行为之实践篇三

现在来谈下验证码图片的获取方式,带有验证码的地方都会附带有个刷新按钮,而刷新按钮的地方就是获取验证码网址代码。如果看过前面写的《模拟网页行为之工具篇》就会很容易定位到代码位置。定位到代码位置后看下图: 基本可以看到的是获取验证码…

SHA-256算法实现

SHA-256 算法输入报文的最大长度不超过2^64 bit,输入按512-bit 分组进行处理,产生 的输出是一个256-bit 的报文摘要。该算法处理包括以下几步: STEP1:附加填充比特。对报文进行填充使报文长度与448 模512 同余(长度…

ecc算法入门介绍

一、从平行线谈起。 平行线,永不相交。没有人怀疑把:)不过到了近代这个结论遭到了质疑。平行线会不会在很远很远的地方相交了?事实上没有人见到过。所以“平行线,永不相交”只是假设(大家想想初中学习的平行…

Intel Hex概述

什么是Intel Hex文件 Intel HEX文件时遵循Intel HEX文件格式的ASCII文本文件。在Intel HEX文件的每一行都包含了 一个HEX记录。这些记录是由一些代表机器语言代码和常量的16进制数据组成的。Intel HEX文件常用来传输要存储在ROM 或者 EPROM中的程序和数据。大部分的EPROM编程器…

AndroidStudio+ideasmali动态调试smali汇编

0x00 前言 之前对于app反编译的smali汇编语言都是静态分析为主,加上一点ida6.6的动态调试,但是ida的调试smali真的像鸡肋一样,各种不爽,遇到混淆过的java代码就欲哭无泪了。后来知道IDEA用一款插件也可以实现smali的动态调试&a…

使用IDA Pro动态调试SO文件

(1)在IDA的安装路径中找到android_server文件。 (2)将android_server拷贝到手机的/data/local/tmp目录下面。 (3) 将手机插上电脑,打开命令提示符, 先输入”adb shell”,然后输入”…