学习数据结构的手册1---五十万字总结阅读数据结构(用c语言描述+包含三大主流教材+考研工作均可+正在更新)

声明
**本文档不做任何商业用途,是作者个人与团队的学习数据结构的心得笔记以及在考研备考中的学习回顾,加以整理,仅用于学习交流,任何人不得进行有偿销售、 本文档的著作权归作者或团队所有,文中部分引用的图片说明来源,特此感谢。任何人使用本文档所述内容所衍生的风险与责任均由其自行承担,本文档的作者或团队不承担任何因此产生的直接或间接损失或责任。同时,本文档的内容仅代表作者或团队的观点和理解,并不代表其他任何组织或个人的观点和立场。读者在阅读和使用本文档时,请自行判断其内容的正确性、准确性和实用性,十分欢迎读者批评指正、提出建议意见,不足之处,多多包涵。 **
团队微信公众号:CodeLab代码实验室
作者CSDN博客:Tech行者

前言

本文结合数据结构三大主流的教材以及课本例题、一些数据结构考研真题,同时在总结的同时学习了来自中国大学生MOOC平台上来自西北大学耿国华教授团队的数据结构精品课程以及B站哔哩大学计算机学院比特就业课航哥的数据结构零基础教程数据结构与算法,同时也对考研机构在B站上分享的数据结构的课也做了对比。本文档中我们跳过了绪论,直接从线性表开始,本文C语言代码为主要叙述,但是也会对类C语言对比,让读者有更好的认识。

很感谢这个伟大时代, 无论身处何方,我们都能够通过现代科技和互联网获得各种知识和资源,并与学术大佬互相交流、分享经验。借助这些先进工具和平台,站在他人的肩膀上,打开知识的大门,拓展自己的视野和思路。

Thank you very much for this great era. No matter where we are, we can acquire all kinds of knowledge and resources through modern science and technology and the Internet, and exchange and share experiences with academic experts. With the help of these advanced tools and platforms, stand on the shoulders of others, open the door of knowledge, and expand their horizons and ideas.

同时,也正是由于这种开放性和自由性,我们需要对所获得的知识和信息进行筛选和分辨,避免被错误、虚假或误导的信息误导。因此,我们在获取知识的过程中需要保持清醒、理性,注重学习信源和提高自身的鉴别能力。只有这样才能真正受益于知识的力量。

At the same time, it is precisely because of this openness and freedom that we need to screen and distinguish the acquired knowledge and information to avoid being misled by wrong, false or misleading information. Therefore, we need to be sober and rational in the process of acquiring knowledge, pay attention to learning information sources and improve our own identification ability. Only in this way can we truly benefit from the power of knowledge.

文档说明

1.文档采用教材

image.png

2.文档采用课程链接

以下课程均不收费,全程都可以学,感谢感谢!
B站破千万的C语言课程
中国大学生MOOC平台上来自西北大学耿国华教授团队的数据结构精品课程
B站哔哩大学计算机学院比特就业课航哥的数据结构零基础教程数据结构与算法

3.基本知识的储备

在阅读本文之前,大家需要掌握一定基础的C语言,较为了解基本的指针知识以及结构体,在这里我们给与相关的博客回顾,来源于CSDN:Tech行者
此文主要介绍了指针在C中的应用,包括指针的概要、一级指针和二级指针的区别、指针的动态内存分配、指针参数的传递等。同时,通过一个生动的例子展示了使用二级指针来计算班级平均分的过程,并讲解了顺序表结构体中指针的应用。在实际开发中,指针是一个非常重要的工具,对于深入理解C,C++编程语言的原理以及更加高效地进行程序开发都具有重要意义。
C语言中的结构体是一种自定义数据类型,它可以将不同变量组合在一起形成一个新的结构类型,方便程序设计和数据管理。本文详细介绍了C语言中结构体的定义、声明与初始化、成员的访问、指针使用、作为函数参数等基础以及结构体在数据结构中的应用。结构体能够方便地构建复杂的数据类型,提高程序可读性和可维护性。在实际开发过程中,合理地运用结构体可以让代码更加简洁、清晰和易于维护。此外,文章还介绍了typedef的使用,它可以将复杂的类型名称重新命名为一个简洁易懂的新类型名称,使得代码更加简洁易读,提高了代码的可维护性和可读性。

4.教材中特别注意点

以上三本教材,尽管都是用C语言描述的,但是代码不相同,这很正常,但是内在的逻辑是相通的,我们还可以采用Java、Python等各种语言来描述,但是如果读者是要准备考研的话,还是建议使用C,不过在这里,需要特别注意的是,在这三本教材中,耿国华老师的教材是采用纯C语言来描述,严蔚敏等老师的教材中的代码为了具有更高的可读性,结合了C++当中引用的知识来叙述的,c++是包含C的,所以我们可以看到教材所给出的代码也是.cpp结尾的,不过只涉及一定C++知识,主要包括C++的引用,和输入输出。在这里我们详细解释一下区别,帮助大家更好的理解。

4.1以线性表的单链表为例

4.1.1第一种区别 存储结构

typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;

这段代码定义了一个结构体SListNode,并使用typedef将其重新命名为SLTNode。该结构体中包含两个成员:SLTDataType类型的data和指向下一个节点的指针next,其中SLTDataType是一个自定义的数据类型。
这种结构体经常用于单链表的实现,每个节点都包含要存储的数据以及指向下一个节点的指针。在单链表中,每个节点只指向其后继节点,而不会指向前驱节点,因此可以通过依次遍历链表中的每个节点来访问链表中的所有元素。

typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode,*PLTNode;

这段代码定义了一个结构体ListNode,并使用typedef将其重新命名为LTNode,同时也定义了一个指向该结构体的指针PLTNode。该结构体中包含两个成员:整型变量val和指向下一个节点的指针next。
这种结构体经常用于链表的实现,其中每个节点都包含所存储的数据以及指向下一个节点的指针,通过这些指针可以访问链表中的所有元素。

LTNode,*PLTNode;

这里是定义了指向LTNode类型对象的指针类型PLTNode,它是一个指向LTNode类型的指针。 在一些情况下我们需要使用指针类型来访问链表中的元素,比如在查找、插入或删除链表中的节点时。

两种描述的区别
这两段代码都是通过typedef对结构体进行重命名,以达到简化代码和提高可读性的目的。

第一段代码定义了一个名为SListNode的结构体,包含一个SLTDataType类型的成员变量data和一个指向下一个节点的指针next。使用typedef将其重新命名为SLTNode。这种方式通常用于实现单链表中的节点结构体。在程序中使用SLTNode代替SListNode,在声明和操作节点时,可以让代码更加简洁易读。
第二段代码定义了一个名为ListNode的结构体,包含一个整型成员变量val以及一个指向下一个节点的指针next。与第一段代码不同的是,这里使用了另一种形式的typedef,将ListNode重命名为LTNode,同时还定义了一个名为PLTNode的指向LTNode类型对象的指针类型。这种方式通常也是用于实现链表中的节点结构体,但它具有更广泛的适用范围,可以用于实现各种类型的链表,而不仅限于单链表。在代码中使用LTNode和PLTNode代替ListNode和指向ListNode类型对象的指针,可以使代码更加清晰易懂。

总之,这两种方式通过将结构体和指针类型重新命名,使代码更加简洁易读,提高了可维护性和可读性。在实际编程中,根据具体情况选择适合的方式是很重要的。

4.1.2第二种区别 插入操作

void SLTPushFront(SLTNode** pphead, SLTDataType x);
void CreateFromHead(LinkList L);
void ListInsert(List &L, int e);

这三个代码均与链表相关,但它们的功能和用法略有不同。
SLTPushFront(SLTNode** pphead, SLTDataType x);//比特航哥版本,采用第一种存储结构

是一个在单链表头部插入新节点的函数。其中,pphead是一个指向指针类型的指针,指向第一个节点的地址,x是要插入的元素值。它的作用是:将新元素封装成一个新的节点,然后将该节点添加到单链表的头部,使其成为新的头节点。这个函数可以用于构建一个单链表。

CreateFromHead(LinkList L);//耿国华老师版本,采用第二种存储结构

是一个从链表头部创建链表的函数进行插入。其中,L是一个LinkList类型的参数,表示链表。该函数的作用是:从链表头部开始逐个添加新的节点,直到链表结束,并返回链表的头节点。这个函数也可以用于构建一个单链表。

ListInsert(List &L, int e);//严蔚敏老师版本,采用第二种存储结构,这个在.c语言中编译不过。

是一个在链表中插入一个新元素的函数。其中,L是一个List类型的引用,表示链表,e是要插入的新元素。这个函数实现的是插入操作,它会在链表中查找给定位置的节点,然后在该节点前插入一个新节点,以达到插入新元素的目的。这个函数可用于在链表的任意位置插入新元素。

在 C 语言中,并不存在引用类型(Reference Type),因此也无法使用 “&” 运算符定义引用类型。相反,在 C 语言中使用指针类型(Pointer Type)来实现引用的功能。因此,在 C 语言中,函数参数传递时应该采用指针作为形参来实现对变量的引用。

我们可以看到,这本书配套的代码是C++程序
image.png
包括输出输出时也采用了C++的用法,在初学时看书时,也不明白为什么会这样,简单把&理解成取地址符号,这里是不对的,等学到C++时才恍然大悟,考研中C和类C语言一般来说都可以,这个要看具体院校的规定和历年的真题。
image.png
因此,这三个函数的区别在于它们的具体功能和用法不同。SLTPushFront用于在单链表头部插入新节点,CreateFromHead用于从链表头部创建链表,ListInsert用于在链表中插入新元素。
王道考研课程中也详细的介绍了,它们采用的是严蔚敏老师版本的代码,但是大家一定一定要知道为什么可以这样使用、怎么使用、区别是什么,知其然知其所以然。

4.2符号的解释

**1. 在 C 和 C++ 中,"" 和 “->” 运算符都是用于访问指针所指向的内存地址上存储的值,但它们的使用方式有所不同。 **
首先,"
" 运算符主要用于解引用指针,获取指针所指向内存地址上存储的值。例如:

int a = 1;
int* p = &a; // 将变量 a 的地址赋值给指针变量 p
int b = *p; // 解引用指针 p,获取指针所指向的内存地址上存储的值,并将其赋值给变量 b

在上述代码中,我们通过 “*” 运算符解引用指针 p,获取了变量 a 的值,并将其赋值给变量 b。
其次,"->" 运算符主要用于通过指针访问结构体或类的成员变量或成员函数。例如:

struct Student {
int id;
char name[20];
};

Student s;
s.id = 1;
strcpy(s.name, "Tom");

Student* p = &s;
p->id = 2;
strcpy(p->name, "Jerry");

在上述代码中,我们定义了一个结构体 Student,然后实例化了一个结构体对象 s,通过点运算符".“访问结构体成员变量 id 和 name。接着,我们使用指针 p 指向了结构体对象 s 的地址,然后通过箭头运算符”->"来访问结构体成员变量 id 和 name。

:::info
因此,总的来说,"" 运算符用于指针的解引用,而"->" 运算符用于通过指针访问结构体或类的成员变量或成员函数。同时需要注意的是,在 C++ 中也可以使用 "" 运算符来定义指针类型变量。
:::
** 2.&在c和c++中的区别与引用 **
在 C 和 C++ 中,"&" 运算符都可以用来获取变量的地址。但是,在 C++ 中 “&” 运算符还有一个重要的应用,即用来定义引用类型(reference)。
引用是一种特殊类型的变量,它是某个已存在变量的别名。通过引用可以直接访问所引用的变量,而不需要使用指针。具体来说,定义引用时需要在变量名前面添加 “&” 运算符。
以下是一个简单的示例:

int a = 1;
int& b = a; // 定义引用类型变量 b,其值为变量 a 的别名

b = 2; // 直接修改 b 的值,也就是修改了 a 的值

std::cout << a << std::endl; // 输出 2

在上述代码中,我们定义了一个整型变量 a,并且将引用类型变量 b 定义为 a 的别名。因此,如果我们修改 b 的值,也就相当于修改了 a 的值。同时,我们还可以直接输出变量 a 的值。

需要注意的是,在 C 语言中并不存在引用类型,因此不能使用 “&” 运算符定义引用类型。但是,在 C 语言中我们仍然可以使用 “&” 运算符来获取变量的地址,并将其赋值给指针类型变量。

5.数据结构的意义

数据结构是计算机科学中的基本概念之一,它是指在计算机中组织和存储数据的方式。数据结构为程序提供了一种有组织、高效的方法来访问和处理数据。在软件开发过程中,使用合适的数据结构可以大大提高程序性能、可靠性和可维护性。
数据结构包括各种线性结构(如数组、链表、栈、队列…)、树形结构(如二叉树、红黑树、B树…)以及图等复杂结构,不同的数据结构适用于不同的应用场景,可以优化数据的访问、插入、删除等操作。掌握数据结构的概念和原理,能够有效地解决现实中的问题,并帮助我们更好地理解和设计算法,提高程序的质量和效率。
简而言之,数据结构是计算机科学和软件工程领域中非常重要的一个概念,对于任何数据驱动的系统来说都具有重要意义,熟练运用各种数据结构能够提高程序性能,降低开发成本,提高开发效率。

6.刷题的渠道

《数据结构教程(第 6 版 )》(李春葆主编,清华大学出版社出版)是“十二五”普通高等教育本科国家级规划教材,高等学校数据结构课程系列教材,此 LeetBook 为教材配套在线编程实训册

开启救赎之道

无标题.jpg

第一章 绪论

本文档中我们跳过了绪论,直接从线性表开始

第二章 线性表

2.1线性表的定义

线性表是指具有相同数据类型的有限序列。它是一种基本的数据结构,通常用数组或链表来实现。

线性表顺序存储时,数据元素存储在一组地址连续的存储单元中,可以随机访问每个元素;
线性表链式存储时,数据元素存储在任意的存储单元中,通过指针互相连接,不支持随机访问,但支持灵活的动态操作。
线性表常见的操作有插入、删除、查找、遍历等,可以用于解决各种问题,如数组、队列、栈等基础算法和数据结构问题,还可以应用于图论、字符串匹配、并查集等高级算法和数据结构问题。

2.1.1线性表的特点

线性表的特点主要有以下几个:

  1. 存储结构简单:线性表的存储结构可以通过数组或链表来实现,这两种方式都比较简单易懂。
  2. 元素具有相同数据类型:线性表所有元素都属于同一种数据类型,因此可以进行直接比较和排序等操作。
  3. 元素之间有序排列:线性表中的元素按照一定顺序排列,顺序可以是任意的,也可以是固定的。
  4. 可以随机访问:线性表的顺序存储结构中,元素存储在地址连续的存储单元中,可以通过下标随机访问元素,时间复杂度为 O(1)。
  5. 支持插入和删除操作:线性表中的元素可以进行插入和删除操作,插入和删除的时间复杂度通常为 O(n),与元素数量有关。

综上所述,线性表是一种非常基础的数据结构,具有简单、有序、可访问和可修改等特点,应用广泛。
耿国华老师版本的教材定义了线性表的三个特点

同一性:线性表由同类数据元素组成,每一个ai必须属于同一数据对象。
有穷性:线性表由有限个数据元素组成, 表长度就是表中数据元素的个数。
有序性:线性表中表中相邻数据元素之间存在着序偶关系<ai, ai+1>。

严蔚敏老师教材中的定义

(1)存在惟一的一个被称做“第一个”的数据元素;
(2)存在惟一的一个被称做“最后一个"的数据元素;
(3)除第一个之外,集合中的每个数据元素均只有一个前驱;
(4)除最后一个之外,集合中每个数据元素均只有一个后继。

三个注意点

在三个课本中均涉及了到了
:::info
在稍复杂的线性表中,一个数据元素可以由若干个数据项(item)组成。在这种情况,常把数据元素称为记录(record),含有大量(类型相同)记录的线性表又称文件(file)。
:::

2.1.2线性表的长度

:::info
线性表的长度是指其包含元素的个数,也就是线性表中存储数据的长度。通常用 n 表示线性表的长度。
:::
线性表的长度可以在创建时确定,也可以在运行过程中动态变化。例如,在数组中,线性表的长度就是数组的长度;而在链表中,线性表的长度可以随着节点的插入和删除而动态变化。
线性表的长度对于程序设计来说很重要,它需要在内存中分配足够的空间来存储元素,并对线性表进行访问和操作。在实际应用中,需要对线性表的长度进行限制,防止内存溢出和其他错误。例如,在 C 语言中,需要使用 malloc() 函数动态分配内存,并在程序结束时使用 free() 函数释放空间,避免内存泄漏问题。

malloc() 函数是一个标准库函数,用于在内存的动态存储区域中分配一个指定大小的连续空间,并返回该空间的首地址。它的函数原型如下:

void *malloc(size_t size);

其中,size_t 是一种数据类型,表示无符号整数,通常使用 unsigned int 或 unsigned long 进行定义。
malloc() 函数可以动态地为程序提供内存空间,解决了静态内存分配的不足。程序员可以根据需要在运行过程中申请和释放内存空间,灵活控制内存的使用,避免了内存空间的浪费和溢出。
使用 malloc() 函数分配内存的步骤如下:

  1. 调用 malloc() 函数,并将需要分配的字节数作为参数传递给它。
  2. 如果调用成功,malloc() 函数将返回分配内存的首地址;如果失败,则返回 NULL。
  3. 分配的内存空间可以使用指针来进行访问和操作,直到不再需要时再调用 free() 函数将其释放。

需要注意的是,在使用 malloc() 函数分配内存时,需要根据实际需求计算所需内存的大小,并确保程序具有合理的内存使用方式,避免内存泄漏和其他问题。同时,malloc() 函数返回的空间地址不一定是连续的,需要使用指针进行管理和操作。
free() 函数是一个标准库函数,用于释放之前使用 malloc() 或 calloc() 分配的动态内存空间。它的函数原型如下:

void free(void* ptr);

其中,ptr 是需要释放的内存空间的指针。需要注意的是,该指针必须指向由 malloc() 或 calloc() 分配的内存空间才能被释放。如果 ptr 为空指针,则 free() 函数将没有任何效果。
使用 free() 函数释放内存空间的步骤如下:

  1. 调用 free() 函数,并将要释放的内存空间的指针作为参数传递给它。
  2. 如果调用成功,则指定的内存空间将被释放并标记为可供重新分配的状态。
  3. 在释放内存后,应该将指针设置为 null,避免出现野指针问题。

需要注意的是,一旦内存空间被释放,就不能再继续访问它。因此,在释放内存时,应该确保程序中不再使用该内存空间,以避免潜在的运行时错误和数据损坏。同时,也需要注意避免内存泄漏问题,即在程序运行过程中不释放不再使用的内存空间,导致内存的浪费和程序性能下降。

2.2线性表抽象数据类型的定义

在这里我们以耿国华老师版本的教材为例进行说明,在文档说明中已经给予了说明,在教材中特别注意点给予了解释。

ADT  LinearList{
数据元素: D={ai| ai∈D0, i=1, 2, …,n, n≥0 , D0为某一数据对象}
关系:S={<ai, ai+1> | ai,  ai+1∈D0,i=1, 2,, n-1}
基本操作:
(1) InitList(L)
操作前提: L为未初始化线性表。 
操作结果: 将L初始化为空表。 
(2DestroyList(L)
操作前提: 线性表L已存在。 
操作结果: 将L销毁。 
(3ClearList(L)
操作前提: 线性表L已存在 。 
操作结果: 将表L置为空表。 
(4) EmptyList(L)
操作前提: 线性表L已存在。 
操作结果: 如果L为空表则返回真, 否则返回假。
(5) ListLength(L)
操作前提: 线性表L已存在。 
操作结果: 如果L为空表则返回0, 否则返回表中的元素个数。 
(6Locate(L, e)
操作前提:  表L已存在, e为合法元素值。 
操作结果:  如果L中存在元素e, 则将“当前指针”指向元素e所在位置并返回真, 否则返回假。 
(7GetData(L, i)
操作前提: 表L存在, 且i值合法, 即1≤i≤ListLength(L)。
 操作结果: 返回线性表L中第i个元素的值。 
(8InsList(L, i, e)
操作前提:表L已存在,e为合法元素值且1≤i≤ListLength(L)+1。 
操作结果:在L中第i个位置插入新的数据元素e,L的长度加1。 
(9DelList(L, i, &e)
操作前提: 表L已存在且非空,  1≤i≤ListLength(L)。 
操作结果: 删除L的第i个数据元素, 并用e返回其值, L的长度减1} ADT  LinearList

在本文档的前几章中,我们任然强调*和&的区别,以顺序表的插入操作为例子,注意:

int InsLIist(seqList*L,int i,ElemType e)//耿国华老师版本
status InsLIist(SqList &L,int i,ElemType e) //严蔚敏老师版本

这两个函数的主要区别在于参数传递方式和返回值类型:

  • InsList(seqList* L, int i, ElemType e) 是通过指针方式传递顺序表,使用 int 类型作为返回值类型。调用该函数后,如果插入成功,返回值为 1;如果插入失败,返回值为 0。
  • InsList(SqList &L, int i, ElemType e) 是通过引用方式传递顺序表,使用 status 类型作为返回值类型(status 类型是一个枚举类型,表示操作的状态)。调用该函数后,如果插入成功,返回值为 OK;如果插入失败,返回值为 ERROR。

另外,这两个函数可能所指的顺序表结构体类型不同,导致其它实现细节也可能存在差异。需要根据具体情况进行选择和使用。

2.3 顺序表中基本操作的实现

2.3.1注意的关键字

typedef 是 C 语言中的一个关键字,它可以用来为一种数据类型定义一个新的类型名。
使用 typedef 可以让代码更加简洁、易读,并且方便了程序员对数据类型的修改。typedef 的一般语法格式为:

typedef original_type new_type;

其中,original_type 是已有的数据类型,new_type 是新的类型名。

2.3.2动态顺序表与静态顺序表的差异

动态顺序表和静态顺序表是两种顺序存储结构,在实现方式和特点上存在一定的差异。

静态顺序表是指在程序编译时已经确定了线性表的大小,从而可以使用静态数组来存储数据。由于静态数组的大小是固定的,因此当需要插入或删除元素时,就需要移动大量的元素,效率较低。另外,如果线性表的数据量超过了静态数组的容量,则需要重新定义更大的数组,甚至需要修改程序,才能继续存储数据。
而动态顺序表是采用动态内存分配技术来实现的,可以根据实际需要动态地分配和释放存储空间。当需要插入或删除元素时,可以通过重新分配内存空间来完成操作,不会像静态顺序表一样需要大量地移动元素。因此,相对于静态顺序表,动态顺序表具有更好的灵活性和效率。

另外,由于动态顺序表采用的是堆内存分配,因此需要手动管理内存的分配和释放。如果内存泄漏或者野指针等问题,会导致程序出现严重的错误,因此动态顺序表的实现过程需要更加谨慎和耐心。
在代码实现上,静态顺序表和动态顺序表最主要的区别是在内存空间的分配和释放方式上。
对于静态顺序表,一般使用数组来实现,并且在程序编译期间就已经确定了数组的长度,在定义时需要指定数组的大小,如下所示:

#include <stdio.h>
// 定义静态顺序表
#define MAXSIZE 100
typedef struct {
    int data[MAXSIZE];
    int length;
} SqList;

int main() {
    SqList L = {{1, 2, 3}, 3}; // 初始化静态顺序表
    printf("The length of list L is %d.\n", L.length); // 输出顺序表的长度
    return 0;
}

而动态顺序表则需要借助动态内存分配实现。在 C 语言中,可以使用 malloc 函数在堆上申请一段连续的内存空间,返回指向该空间的指针,如下所示:

#include <stdio.h>
#include <stdlib.h>

// 定义动态顺序表
typedef struct {
    int *data;
    int length;
} SeqList;

int main() {
    SeqList L;
    L.data = (int *)malloc(sizeof(int) * 100); // 在堆上分配 100 个 int 类型大小的空间
    if (L.data == NULL) { // 判断是否分配成功
        printf("Failed to allocate memory.\n");
        exit(1); // 退出程序
    }
    L.length = 0; // 初始化动态顺序表的长度
    printf("The length of list L is %d.\n", L.length); // 输出动态顺序表的长度
    free(L.data); // 释放动态分配的内存空间
    return 0;
}

在实现动态顺序表时,需要注意手动管理内存空间的分配和释放,避免内存泄漏和野指针等问题。

2.4深入理解一些指针的问题

:::warning
这下面的内容有点一点易混淆的地方,大家一定要将知识内化,或者先跳过,看后面的代码实现。我也是整理和内化了很久,站在巨人的肩膀上,有了一点点自己的认识,如有错误,还请指正!
:::

2.4.1深入理解L.length 和 L->length

其中L.length 和 L->length 都是表示线性表的长度,但它们的访问方式略有不同。

在 C 语言中,结构体变量可以使用“.”符号和“->”符号来访问结构体成员。其中,
“.”符号用于访问结构体变量的成员,
“->”符号用于访问结构体指针变量的成员。
L-> 相当于 (*L). 的简化写法,表示访问结构体指针变量 L 所指向的结构体的成员 length。
总之,L.length 是访问静态顺序表结构体变量的方式,而 L->length 是访问动态顺序表结构体指针变量的方式。但它们都可以用来表示线性表的长度。

但是我还是不太理解,于是打开了CSDN查看有无写的博客
image.png

2.4.2什么是结构实例的指针和结构的实例?

结构实例的指针和结构的实例的区别在于它们所存储的数据类型不同。

结构实例通常指结构体类型的一个具体实例对象,它在内存中是以连续的一段空间存储的,可以直接使用点号(.)来访问结构体中的成员。

例如,定义一个包含年龄和名字成员的 Person 结构体,并创建一个 p1 的结构实例对象:

typedef struct {
    int age;
    char name[20];
} Person;

Person p1 = {20, "Tom"};

上面的代码中创建了一个名为 p1 的 Person 类型的结构实例,p1 中的 age 成员被初始化为 20,name 成员被初始化为 “Tom”。使用点号操作符 . 可以方便地访问结构体成员:

printf("Age: %d, Name: %s\n", p1.age, p1.name);

结构实例的指针则是指向结构体类型变量的指针,它存储着该类型变量的地址,通常使用运算符 * 和 & 来定义和访问。对结构实例的指针进行解引用操作(即使用 * 运算符)可以获得指向的结构体实例本身。

例如,创建一个名为 p2 的 Person 类型的结构实例指针,然后赋值:

Person *p2;
p2 = &p1;

p2 指向 p1 这个结构实例的地址。使用运算符 * 将 p2 解引用即可使用 p1 结构实例的成员:

printf("Age: %d, Name: %s\n", (*p2).age, (*p2).name);

或者可以使用 -> 运算符来访问:

printf("Age: %d, Name: %s\n", p2->age, p2->name);

总之,结构实例是由结构体类型定义而来的具体实例对象,可以直接使用点号操作符 . 访问成员;
而结构实例指针则是指向结构体类型变量的指针,通常使用运算符 * 和 & 定义和访问,使用箭头操作符 -> 访问成员。

2.4.3回到最初的问题L.length 和 L->length 在顺序表中究竟代表什么?

L-> 相当于 (*L).

在顺序表中,L.length 和 L->length 都代表顺序表的长度。

顺序表是一种在物理内存地址上连续存储的线性数据结构,其每个元素占用相同大小的存储空间。在 C 语言中,我们通常使用结构体来描述顺序表的存储结构。

以下是一个结构体定义示例:

#define MAXSIZE 100 // 定义顺序表的最大长度

typedef struct {
    int data[MAXSIZE]; // 存储元素的数组
    int length;        // 当前顺序表的长度
} SeqList, *SeqListPtr;

SeqList 是顺序表的别名,它包含了一个固定大小为 MAXSIZE 的数组 data,用于存储顺序表的元素;还包含一个整数变量 length,用于表示当前顺序表中实际存储的元素个数。

/访问 length 变量时,可以使用结构体变量的成员访问方式 L.length,也可以使用指向结构体变量的指针访问方式 L->length,这两种形式是等价的。区
别在于,使用 L->length 前需要先给其赋值一个指向结构体的指针。

例如,创建一个 SeqList 类型的顺序表对象 list:

SeqList list;
list.data[0] = 1;
list.length = 1;

则可以使用 list.length 或 (&list)->length 访问当前顺序表的长度;也可以创建指向结构体变量的指针 pList,并对其赋值:

SeqListPtr pList = &list;
pList->data[0] = 1;
pList->length = 1;

然后就可以使用 pList->length 或 (*pList).length 访问当前顺序表的长度。

需要注意的是,在这个示例中,SeqList 和 SeqListPtr 实际上是等价的。SeqListPtr 是 SeqList 的指针类型定义,用于定义指向顺序表对象的指针变量。如果我们直接定义 SeqList 类型的变量 list,则无需使用指针访问方式,可以直接使用 list.length 访问当前顺序表的长度。

// 这段代码定义了一个名为 Person 的结构体类型,其中包含了 age 和 name 两个成员变量。
// 接着创建了一个名为 p1 的 Person 类型的结构体实例,将其 age 成员赋值为 20,name 成员
// 赋值为 "Tom"。然后通过使用点号操作符 . 访问结构体成员,输出了 p1 的 age 和 name 成员。
// 接下来创建了一个名为 p2 的 Person 类型的结构体实例指针,并将其赋值为 p1 的地址。
// 使用箭头操作符 -> 访问结构体成员,输出了 p2 所指向的结构体实例的 age 和 name 成员。

// 定义一个名为 Person 的结构体类型,包含年龄和名字成员
typedef struct {
    int age;
    char name[20];
} Person;

// 创建一个 p1 的 Person 类型的结构实例对象,并进行初始化
Person p1 = {20, "Tom"};
// 使用点号操作符 . 访问结构体成员
printf("Age: %d, Name: %s\n", p1.age, p1.name);

// 创建一个名为 p2 的 Person 类型的结构实例指针,并将其赋值为 p1 的地址
Person *p2;
p2 = &p1;
// 使用箭头操作符 -> 访问结构体成员,等价于 (*p2).age 和 (*p2).name
printf("Age: %d, Name: %s\n", p2->age, p2->name);

2.4.4以航哥的代码与两位老师版本的代码做个比较更加深入理解L.length 和 L->length以及&地址符号。

2.4.4.1航哥的代码
// 定义动态数组的结构体
typedef struct SeqList {
SLDataType* a;       // 指向动态数组的指针
size_t size;        // 动态数组的大小(元素个数)
size_t capacity;    // 动态数组的容量(元素最大数量)
}SL;

// 初始化动态数组
void SLInit(SL* ps)
{
assert(ps); // 断言:确保传入的指针不为空

// 为动态数组分配内存空间,大小为 INIT_CAPACITY 个 SLDataType 类型,并将其指针赋值给成员变量 a
ps->a = (SLDataType*)malloc(sizeof(SLDataType)* INIT_CAPACITY);
if (ps->a == NULL)  // 判断分配内存空间是否成功
{
    perror("malloc fail");  // 如果失败,输出错误信息
    return; // 直接退出
}

ps->size = 0;       // 将动态数组的大小初始化为 0
ps->capacity = INIT_CAPACITY;   // 将动态数组的容量初始化为 INIT_CAPACITY

}

// 销毁动态数组
void SLDestroy(SL* ps)
{
assert(ps); // 断言:确保传入的指针不为空

free(ps->a);    // 释放动态数组元素所占用的内存空间
ps->a = NULL;   // 将动态数组指针 a 置为 NULL
ps->capacity = ps->size = 0;    // 将动态数组的大小和容量都初始化为 0

}
int main()
{
	SL s;
	SLInit(&s);
	return 0;
}

1.这里的ps->a?

ps->a 是对结构体变量 ps 中的成员 a 的访问,其中 ps 是指向顺序表结构体的指针。也就是说,ps->a 表示顺序表结构体中的存储数组的首地址。这样可以通过 ps->a 访问到顺序表中存储的各个元素。

2. 这里可以使用ps.a吗 ?

:::info
不可以直接使用 ps.a,因为 ps 是一个指针变量,而不是结构体变量。使用 .(点号)操作符访问结构体成员是针对结构体变量的,而不是针对指针变量的。如果想要访问结构体指针变量中的结构体成员,需要使用 ->(箭头)操作符。所以应该使用 ps->a 来访问顺序表结构体中的存储数组。
:::

3.SL*ps代表什么 ?

SL* ps 是一个指向动态数组结构体的指针,其中SL是定义的动态数组结构体类型,ps是该结构体指针的名称,通常用于在函数中操作动态数组。通过这个指针,我们可以方便地访问和修改动态数组的元素,管理动态数组的大小和容量等信息。

4. *ps和ps代表什么 ?

:::info
其中
*ps是指针变量ps所指向的动态数组结构体的实体,表示通过指针间接访问和修改结构体中的成员变量。比如,ps->size表示获取动态数组大小的方式,可以写成(ps).size的形式。
ps本身是一个指针变量,并存储了动态数组结构体变量的内存地址。我们可以通过ps的值来访问或修改动态数组元素的值,例如获取第i个元素的值可以写成ps->a[i],其中a是一个指向动态数组首元素的指针。
:::
总结
:::warning
航哥在这里,用 SL定义了一个自定义类型的结构体,代表了一个动态数组。该结构体中包含了动态数组的元素指针a、动态数组大小size和容量capacity等信息 ,SL
ps 是一个指向动态数组结构体的指针,在这里使用ps—>a,,通过SL类型的指针变量ps来访问和操作动态数组元素 ,这里使用ps.a是错误的, 编译器会报错提示“成员 ‘a’ 不是一个结构体或联合体的成员 ,因为.代表的是仅仅是访问结构体变量的成员,这里并不存在。
:::

在阅读书籍中,我们发现耿国华老师版本的采用的是静态的顺序表,而严蔚敏老师在是一个动态的顺序表,我们都把他拿来类比,以此说明。兼听则明。
严蔚敏老师动态顺序表的一版,和耿国华老师静态的一版进行说明,以此对比

2.4.4.2耿国华老师

这一版书上没有初始化代码,我们在慕课上引用了 一个实现合并两个有序顺序表的程序 ,从中取出来。

#include "stdio.h"

#define MAXSIZE 100
typedef int ElemType;  // 定义顺序表中存放的元素类型为整型

// 定义顺序表结构体
typedef struct {
    ElemType elem[MAXSIZE]; // 存放元素的数组
    int last;   // 记录最后一个元素的下标
} SeqList;

/* 函数声明 */
void initList(SeqList *L); // 初始化顺序表函数

/* 主函数 */
int main()
{
    SeqList La;  // 定义一个顺序表 La
    initList(&La); // 调用初始化顺序表函数,将 La 置为空表
    return 1;
}

/* 函数定义 */
void initList(SeqList *L)
{
    L->last = -1;   // 将顺序表 L 的最后一个元素下标初始化为-1,即将顺序表置为空表。
}

程序中定义了结构体SeqList来表示一个顺序表,其中elem数组用来存储元素,last表示当前顺序表中的最后一个元素的下标。可以看到,程序中使用了宏定义MAXSIZE来定义了顺序表的最大长度,使用ElemType来表示顺序表中的元素类型,目前为整型。
在主函数中,定义了一个SeqList类型的变量La,然后通过调用initList函数对其进行初始化。initList函数接收一个指向顺序表的指针参数L,将其last成员变量设为-1,表示该顺序表目前为空表。

&La是取变量La的地址,即指向该变量的指针。将其作为参数传递给initList函数,就可以在函数内部通过该指针来访问和操作La所表示的顺序表。
:::warning
在这里并不能使用 *La ,*La 表示指针变量所指向的对象,而不是该对象的地址。而在函数调用时,我们需要传递顺序表的地址,以便在函数内部对其进行操作 。一定一定要注意区分,这里不同于4.1.2第二种区别 插入操作!
:::

2.4.4.3严蔚敏老师

第一版中多数还是采用C的函数,第二版中很多用到c++的函数,如下图,所以我们还是以第一版为主。
image.png

/-----线性表的动态分配顺序存储结构-----
    #detine LIST-INIT_SIZE 100
    #define LISTINCRINBKT  10
typedaf struct {
    BlemType *elem;
    int length;
    int listsize;
}SqList;
Status InitList.Sq(SqList &L){
    //构造一个空的线性表L。
    L.elem =(ElemType *)malloc(LIST.INIT.SIZE*sizeof(ElemType));
    if(!L.elem)exit(OVERELOW); //存储分配失败
    L.length = 0; //空表长度为0
    L.listsize = LIST.INIT.SIZB; //初始存储容量
    return OK;
}// InitList.Sq

这段代码是一个 C 语言的线性表动态分配顺序存储结构的实现。
首先,我们定义了一个结构体 SqList,其中包含三个成员变量:elem 指向线性表的存储空间,length 表示线性表的当前长度,listsize 表示线性表当前所分配的存储容量。
然后我们定义了一个名为 InitList_Sq() 的函数,它的主要作用是初始化一个线性表,将其置为空表。函数的参数是一个引用参数 SqList &L,表示传递给函数的是 SqList 结构体类型的变量的内存地址。
接下来,函数使用 malloc() 函数为线性表分配一块大小为 LIST_INIT_SIZE 的连续内存空间,并将返回的指针赋值给 L.elem 成员变量。如果内存分配失败,函数将通过 exit() 函数立即退出程序,并返回一个预定义的错误代码 OVERFLOW。
否则,函数将会将线性表的长度 length 成员变量赋值为 0,表示这个线性表是空的。并将分配的存储容量 listsize 设置为 LIST_INIT_SIZE,即初始存储容量。
最后,函数返回枚举类型 Status 中的 OK 值,表示线性表的初始化成功。这个函数的返回值可以用于检查线性表是否成功地初始化。

1.&注意点

再次提醒!
:::warning
Status InitList_Sq(SqList &L) 这段代码不是 C 语言代码,而是线性表的抽象数据类型(Abstract Data Type)中通用的函数声明格式。
具体来说,这个函数是一个 C 语言格式的函数声明,它可以被 C++ 代码调用。在这个函数声明中,SqList &L 表示函数的参数列表,&L 是指针类型的形式参数,它通过引用传递的方式将一个 SqList 结构体变量的地址传递给了函数。
需要注意的是,C++ 和 C 语言的函数声明格式略有不同。C++ 中,函数参数也可以通过引用传递,但是它们需要在参数列表中显式地加上 & 符号;而 C 语言使用指针来实现类似的操作。
:::

在 C 语言和 C++ 中,& 表示取地址运算符,它可以返回给定变量的内存地址。
在 C++ 中,& 符号还有另一个含义,即表示引用标识符。在变量名前加上 &,可以定义一个引用类型的变量。引用可以看作是在原有变量的基础上,又建立了一个别名(即引用变量),通过这个别名来修改原有变量的值。如果对引用变量赋值,实际上是对原有变量进行了修改。需要注意的是,在 C 语言中没有引用类型,所以不存在引用标识符的概念。

2.4.4.5总结

1.关于动态数组的初始化和销毁操作

航哥其中使用了 SL 结构体来表示动态数组,包含指向动态数组的指针 a、动态数组的大小 size 和容量 capacity 等信息。SL* ps 是一个指向动态数组结构体的指针,在这里使用 ps->a 通过 SL 类型的指针变量 ps 来访问和操作动态数组元素。
在耿国华老师的静态顺序表实现中,使用宏定义 MAXSIZE 来定义顺序表的最大长度,使用 ElemType 来表示顺序表中的元素类型,目前为整型。主函数中定义了一个 SeqList 类型的变量 La,然后通过调用 initList 函数对其进行初始化。initList 函数接收一个指向顺序表的指针参数 L,将其 last 成员变量设为 -1,表示该顺序表目前为空表。
而在严蔚敏老师的动态顺序表实现中,使用了 SqList 结构体来表示动态数组,其中包含元素指针 elem、当前长度 length 和存储容量 listsize 等信息。InitList_Sq 函数使用 malloc 函数为动态数组分配内存空间,并将长度 length 初始化为 0,存储容量 listsize 初始化为 LIST_INIT_SIZE。

需要注意的是,严蔚敏老师的第一版书上的动态顺序表使用 C 语言实现,不同于耿国华老师的静态顺序表和严蔚敏老师的第二版书上的动态顺序表,可能需要在程序中手动增加相关头文件以支持编译。

静态顺序表和动态顺序表

静态顺序表和动态顺序表都是线性表的一种,它们都是用数组来实现的。静态顺序表在定义时需要指定数组的大小,因此其长度在程序运行期间不可改变。而动态顺序表则可以动态地增加或删除元素,因为其在定义时并没有指定数组的大小,而是使用了动态申请内存的形式来管理数组。
在静态顺序表中,由于数组大小的限制,当线性表中的元素个数超过其定义时的大小时,就不能再插入元素了。而动态顺序表可以根据需要自动扩容,因此可以存储更多的元素。但是,动态顺序表的实现相对复杂,需要使用动态内存分配的技术,因此较静态顺序表来说可能会更加耗费计算机资源。
在实现动态顺序表时,可以使用 C 语言中的 malloc 函数来申请内存空间。申请完空间后,将其包装成一个结构体,并在结构体中记录当前动态数组的长度、存储容量等信息。在进行插入、删除等操作时,如果需要增加或减小动态数组的长度,则需要重新申请内存空间,并将原有数据复制到新的内存空间中。

相比之下,静态顺序表的实现相对简单,可以使用数组来存储元素,因此程序的执行速度可能会更快一些。但是其最大缺点是数组容量固定,不能轻易扩展。因此,对于需要动态调整长度的线性表而言,动态顺序表可能是更好的选择。

2.4.5 c语言->和.以及&相关知识

2.4.5.1->和.的原理

-> 用于访问指向结构体的指针变量的成员,
. 用于访问结构体变量的成员。

两者的核心区别在于前者需要先解引用指针,再使用成员操作符访问成员,而后者直接通过偏移计算访问结构体成员。

& 用于获取变量的地址。其原理是将变量在内存中的地址取出来,生成相应的指令返回给程序

2.4.5.2在底层的区别
  1. -> 操作符

-> 操作符底层实现一般是通过以下步骤实现的:

  • 首先对指针进行解引用操作,得到指针指向的结构体;
  • 然后根据成员偏移量,计算出目标成员的地址;
  • 最后读取或写入目标成员的数
  1. .操作符

相比之下,. 操作符的底层实现要简单一些,因为它直接根据结构体变量的地址和成员偏移量就能够计算出目标成员的地址。一般来说,编译器会按照成员在结构体中的声明顺序,计算每个成员变量的偏移量。

  1. & 操作符

& 操作符底层实现的过程也比较简单:它直接返回操作数的地址。

  • 2.4.5.3在编译上的区别
  • -> 和 . 在编译时都会被转换成相应的汇编指令,用于访问结构体成员数据;
  • & 在编译时也会被转换成相应的汇编指令,用于获取变量地址。

2.4.6C语言和类C语言

类C语言是一种类似于C语言的编程语言,它与C语言非常相似,但是在某些方面有所改进和增强。类C语言的语法和语义与C语言非常接近,因此C语言程序员可以很轻松地学习和使用类C语言。同时,类C语言还在C语言的基础上加入了一些新特性和功能,使得它更加适合现代编程需求。
类C语言主要的特点包括:

  1. 语法和语义上与C语言非常相似,易于上手。
  2. 与C语言兼容,可以调用C语言编写的函数库。
  3. 增加了面向对象编程的特性,如结构体、封装、继承、多态等。
  4. 增加了泛型编程的特性,可以实现模板类、模板函数等。
  5. 增加了异常处理机制,可以有效地避免程序崩溃。
  6. 支持标准库和第三方库,可以方便地实现各种功能。

类C语言最初是由C++之父Bjarne Stroustrup和其他一些C++开发者共同开发的,其目的是为了提供一种简单而又高效的编程语言,既保留了C语言的优点,又引入了C++的新特性和功能。类C语言受到了广泛关注和支持,目前已经成为了一种被广泛应用的编程语言之一。
类C语言和C++之间有很多联系,同时也存在一些区别。
相同点:

  1. 语法和语义上类似:类C语言的语法和语义与C++非常接近,尤其是在面向过程编程方面。
  2. 支持面向对象编程:类C语言和C++都支持面向对象编程,包括类、对象、继承、多态等特性。
  3. 支持泛型编程:类C语言和C++都支持泛型编程,包括模板类、模板函数等。
  4. 兼容C语言:类C语言和C++都兼容C语言,可以使用C语言的代码和库函数。
  5. 提供标准库和第三方库: 类C语言和C++都提供标准库和大量的开源库。

不同点:

  1. 概念上的区别:C++是一门完整的面向对象编程语言,而类C语言是一种扩展了C语言的面向对象编程特性的编程语言。
  2. 变量声明的方式:C++中需要显式地指定变量的类型,而类C语言中则可以省略变量的类型。
  3. 异常处理的方式:C++中使用 try-catch 语句来捕获和处理异常,而类C语言则使用 setjmp 和 longjmp 函数实现异常处理。
  4. 面向对象编程的支持:尽管两种语言都支持面向对象编程,但是C++更加完备和成熟,提供了更多的面向对象编程特性,如 operator overloading 等。
  5. 其他区别:C++有自己的 STL 库和标准库,而类C语言则借鉴了C语言的标准库和一些流行的开源库,如 GLib 等。

总的来说,类C语言与C++非常相似,其实现方式和语法也很接近。但是,在面向对象编程方面,C++提供了更完整和成熟的支持,而类C语言则更注重于对C语言的扩展和改进。
类C语言是一种编程语言,可以使用类C语言编写程序。编写好的类C语言程序需要通过编译器将其转换成可执行的机器码。类C语言可以使用多种编译器来进行编译,这些编译器包括:

  1. GCC(GNU Compiler Collection):GCC 是一个免费、开源的编译器集合,支持多种操作系统和多种编程语言,包括 C、C++ 和类C语言等。
  2. LLVM(Low Level Virtual Machine):LLVM 是一个开源的编译器基础设施,支持多种编程语言和多种操作系统,包括 C、C++ 和类C语言等。
  3. Clang:Clang 是基于 LLVM 架构的 C、C++ 和类C语言编译器,具有高速、低内存使用量和易于扩展等优点。
  4. Visual C++:Visual C++ 是微软公司开发的一款 C++ 编译器,同时也支持类C语言的编译。
  5. Borland C++ Builder:Borland C++ Builder 是一款商业级别的 C++ 编译器,同时也支持类C语言的编译。

以上是常见的几款类C语言编译器,我一般用1和4,当然还有其他的编译器可供选择。无论使用哪种编译器,编译过程主要包括预处理、编译、汇编和链接四个步骤,最终生成可执行的二进制程序。
:::info
顺序表一般来说,以 .c 结尾的文件通常是用 C 语言编写的源代码文件,而以 .cpp 结尾的文件通常是用 C++ 语言编写的源代码文件。C 和 C++ 两种语言虽然非常相似,但是它们在语法和功能上还是有一些差别的。因此,在命名源代码文件时使用不同的后缀可以方便我们区分这些文件是用哪种语言编写的。
:::
当然,有时候我们也会看到一些用 .c 后缀名编写的 C++ 源代码文件,或者用 .cpp 后缀名编写的 C 源代码文件。这主要是因为编译器能够根据文件的内容自动识别并编译这些代码,所以在具体的开发中,文件后缀名的选择并不是一定要严格遵循上述约定,而是根据具体情况和个人习惯来确定。不过,为了保持代码的清晰性和可读性,建议还是尽量遵循后缀名的习惯。
本文中的类C语言基本采用C++

2.5线性表的顺序表表示代码实现

线性表代码实现说明

期待很久了,终于来了!
IMG_20220801_233553.png
image.png
在这里我们使用类C语言来描述,也就是市面上通俗的代码(简单理解c中夹杂这C++,算了为了保持知识的系统性,还是回到上一节详细说一下)🤣
这是是我们思考了很久,主要考虑到了以下原因:

1.教材,以严蔚敏老师的为准,因为在大多数情况下都是以清华大学出版社严蔚敏老师的教材为准
2.考研,例如王道都是以严蔚敏教材为准
3.工作中其实往往不止会一门语言
4.面对初学者可能只学了C,不过不要担心,我们只要画不到半天的时间,对于C++一些基础的基础的东西听一下就可以了,如果对前面掌握的比较好的话,那我们也可以试着转换一下。

不过我们会做一对比,这些老师所编写的都是非常不错的,我们要融会贯通、兼听则明、摸清内在的逻辑和算法思想是最重要的

2.5.1线性表的动态分配的顺序表存储结构

//typedef int SLDataType;
//#define N 100000
//
 静态顺序表 -- 开少了不够用 开多了浪费
//struct SeqList
//{
//	SLDataType a[N];
//	int size;
//};
#define LIST-INIT_SIZE 100//线性表存储空间的初始分配量
#define INIT_CAPACITY 10  // 定义顺序表的初始增量
// 定义一个顺序表的数据结构
typedef int ElemType; // 定义 SQDataType 为 int 类型
typedef struct SqList {
SQDataType *elem; // 指向动态数组的指针
int length; // 顺序表的长度
int listsize; // 顺序表的总容量
} SqList;

“SQDataType *data” 是结构体 SqList 中的一个成员变量,是一个指向动态数组的指针。

在顺序表中,使用动态数组来存储元素。由于顺序表在创建时无法确定数据量大小,因此需要动态分配内存空间来存储元素。动态数组具有可以动态改变大小的特性,因此可以根据实际需求来扩充或缩小存储空间。

指针 “SQDataType *data” 存储动态数组的起始地址,通过该指针可以访问到动态数组中存储的元素。

例如,在进行插入操作时,可以通过指针找到要插入的位置,并将该位置后面的元素依次往后移动,为新元素腾出空间;在进行删除操作时,也可以通过指针找到要删除的位置,并将该位置后面的元素依次往前移动,覆盖掉被删除的元素。

因此,这个指针非常重要,是顺序表实现基本操作的关键。
本来想用墨刀做一个动态演示最后因为时间成本,所以用了PPT,如果后面没时间作图了,大家可以看到课本,或者比特航哥在gitee上分享的图,等我有时间了一定更新!!!
image.png

2.5.2线性表的动态分配的顺序表初始化

status InitList_Sp(SqList &L){

// 为动态数组分配内存空间,大小为 INIT_CAPACITY 个 SLDataType 类型,
//并将其指针赋值给成员变量 a
L.elem= (SLDataType*)malloc(sizeof(ElemType)* LIST-INIT_SIZE);
	if(!L.elem)
    	exit(OVERFLOW);
     L.length=0;
     L.listsize= LIST-INIT_SIZE;
    return OK;
}

这是一段 C 语言代码,因为它使用了 C 标准库的 malloc 函数来分配内存。虽然这段代码的文件扩展名是 .cpp,但是它的编译器可能仍然将其视为 C 代码进行编译。此外,这段代码还使用了 exit 函数来退出程序,它也是 C 标准库中的一个函数。因此,我们可以将其归类为 C 代码或者类 C 代码。

在这段 C++ 代码中,我们看到一个名为 InitList_Sp 的函数,它的作用是对一个顺序表进行初始化。但是需要注意的是,这段代码中使用了 C 标准库的 malloc 函数来分配内存空间,而不是 C++ 中常用的 new 关键字。
在初始化顺序表时,我们需要为动态数组分配内存空间,并将其指针赋值给成员变量 elem。这里使用了 malloc 函数来分配内存空间,大小为 sizeof(ElemType) * LIST-INIT_SIZE,并将其强制转换为 SLDataType* 指针类型。
如果内存分配失败,就会调用 exit(OVERFLOW) 函数退出程序。注意,OVERFLOW 这个状态码的含义应该和之前介绍的相同,表示内存分配失败。
最后,将顺序表的长度 length 和列表大小 listsize 初始化为0和 LIST-INIT_SIZE,然后返回 OK 状态码,表示初始化成功。
在使用 malloc 函数分配内存时,这段代码中并没有使用 free 函数来释放内存,因此可能会导致内存泄漏的问题。

我们再次分析一下status InitList_Sp(SqList &L)

int main()//假设 InitList_Sp返回值为0
{
    SqList L;
    status ret = InitList_Sp(L);
    if (ret == 0) {
        printf("SqList initialized successfully!\n");
    } else {
        printf("Failed to initialize SqList!\n");
    }
    return 0;
}

这段代码中的 status InitList_Sp(SqList &L) 应该是一个函数的声明或定义。其中,status 是一个类型名,表示程序执行状态,通常以整数表示成功或失败。InitList_Sp 则是函数的名称,这个函数可能是用来初始化顺序表 L 的。SqList &L 表示顺序表 L 的引用,即传递顺序表 L 的地址,从而可以在函数内直接修改顺序表 L 中的内容。
:::danger
在代码中,& 是引用类型变量的符号,在 SqList &L 中 & 表示传递 L 的地址,即定义了一个 SqList 类型的引用变量 L。这意味着在函数中,对 L 的任何修改都会影响原始的 SqList 对象,而不是创建一个新的副本。
:::
在第二版中,采用了c++的关键字new

new 和 malloc 都是动态内存分配的方式,用于在程序运行时动态地申请指定大小的内存。
不同之处在于,new 是 C++ 关键字,而 malloc 是 C 标准库函数。在 C++ 中,new 可以用来申请指定类型的内存空间,并返回指向该类型对象的指针,而 malloc 只能返回 void* 类型的指针,需要进行类型转换后才能使用。此外,new 在执行失败时会抛出异常(std::bad_alloc),而 malloc 在失败时返回 NULL。
另一个关键的区别是,new 会自动调用类型构造函数,而 malloc 只是分配内存空间,不会执行任何初始化操作。如果使用 malloc 分配动态内存,就必须手动调用对象的构造函数进行初始化操作。同样,当使用 delete 删除动态分配的对象时,会自动调用其析构函数,而 free 函数不会。

2.5.3线性表的动态分配的顺序表在某个位置上的插入

//插入
status ListInsert_Sp(SqList &L,int i,ElemType e){
	if(i<1||i>L.length+1)
        return ERROR;
    if(L.length>=L.listsize){
     newbase=(ElemType*)realloc(L.Elem,(L.listsize+INIT_CAPACITY)
             *sizeof(ElemType));
     if(!newbase)
         exit(OVERFLOW);
     L.elem=newbase;
     L.listsize+=INIT_CAPACITY;
    }
    q=&(L.elem[i-1]);
    for(p=&(L.elem[L.length-1]);p>=q;--p)
        *(p+1)=*p;
    *q=e;
    L.length++;
    return OK;
}

2.5.2.1头名称的解释

status ListInsert_Sp(SqList &L, int i, ElemType &e) 是函数头,它是函数的定义部分,包括了函数名、参数列表和返回值类型三个部分。

  • status 表示函数的返回值类型,这里是一个自定义的类型,一般用于表示函数执行的状态。在这个函数中,如果插入成功,则返回值为 OK(等价于 0),表示操作成功;如果插入失败,则返回值为 ERROR(等价于 1),表示操作失败。
  • ListInsert_Sp 是函数名,这里表示该函数实现了在顺序表中插入元素的功能。
  • (SqList &L, int i, ElemType &e) 是参数列表,其中 SqList &L 表示一个引用类型的 SqList 对象,int i 表示要插入元素的位置下标(从 1 开始计数),而 ElemType &e 表示要插入到顺序表中的元素。

因此,status ListInsert_Sp(SqList &L, int i, ElemType &e) 表示定义了一个名为 ListInsert_Sp 的函数,它有三个参数 L、i 和 e,并且返回一个状态码 status,用于表示函数执行的状态情况。

2.5.2.2 L.length ? 为什么不可以写出L->length

L.length 表示顺序表的当前长度,即表中元素的个数。在插入、删除等操作时,需要根据表的长度来确定合法的操作位置。在创建顺序表时,应该将 L.length 初始化为 0,表示表中没有任何元素。在后续的操作中,每次新增或删除元素时,都需要更新 L.length 的值。

L 是一个顺序表类型的变量,它是通过引用传递(&)给函数的形参中的 SqList &,因此在函数内部可以通过 L 来访问顺序表的成员。
而 L->length 是指针访问方式,即将指向 SqList 类型变量的指针 L 解引用来访问 length 成员。在本题中并没有使用指针变量,所以不能使用 L->length 的方式来访问顺序表的成员,而应该使用 L.length 的方式来访问。

在 C++ 中,定义了一个 SqList 类型的变量时,应该使用 . 运算符来访问成员变量,例如 L.length、L.listsize 等。而如果使用指向 SqList 类型对象的指针,则应使用 -> 运算符来访问成员变量,例如 L->length、L->listsize 等。

2.5.2.3 把类C代码修改成C代码

将 C++ 代码转换为 C 代码的过程中,可以修改以下内容:

  1. 将 SqList 改为 struct SqList,因为在 C 中没有类的概念。
  2. 将 & 改成 *,即将函数参数中的 SqList &L 改为 struct SqList *L,因为 C 使用指针传递参数。
  3. 将 status、ElemType 等定义改为对应的类型,例如 status 可以定义为 int,ElemType 可以定义为具体的数据类型,比如 int 或 char 等。
int ListInsert_Sp(struct SqList *L, int i, ElemType e) {
    if (i < 1 || i > L->length + 1)
        return -1;
    if (L->length >= L->listsize) {
        ElemType *newbase = (ElemType*)realloc(L->elem, 
            (L->listsize + INIT_CAPACITY) * sizeof(ElemType));
        if (!newbase)
            exit(-2); // OVERFLOW 的定义需要根据实际情况修改
        L->elem = newbase;
        L->listsize += INIT_CAPACITY;
    }
    ElemType *q = &(L->elem[i - 1]);
    for (ElemType *p = &(L->elem[L->length - 1]); p >= q; --p) {
        *(p + 1) = *p;
    }
    *q = e;
    ++L->length;
    return 0; // OK 的定义需要根据实际情况修改
}

2.5.4线性表的动态分配的顺序表在某个位置上的删除

status ListDelete_Sq(SqList &L,int i,EeleType &e){
	if(i<1||(i>L.length))
        return ERROR;
    p=&(L.elem[i-1]);
    e=*p;
    q=L.elem+L.length-1;
    for(++p;p<=q;++p)
        *(p-1)=*p;
    --L.length;
    return OK;
}

未完待续!!!正在呕心沥血的写!!!


  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Tech行者

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

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

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

打赏作者

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

抵扣说明:

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

余额充值