Python数据结构与算法篇(四)-- 链表的实现

news2024/11/17 4:27:48

        实现线性表的另一种常用方式就是基于链接结构,用链接关系显式表示元素之间的顺序关联。基于链接技术实现的线性表称为链接表或者链表。

        采用链接方式实现线性表的基本想法如下:

  • 把表中的元素分别存储在一批独立的存储块(称为表的结点)里。
  • 保证从组成表结构中的任一个结点可找到与其相关的下一个结点。
  • 在前一结点里用链接的方式显式地记录与下一结点之间的关联。

        这样,只要能找到组成一个表结构的第一个结点,就能顺序找到属于这个表的其他结点。从这些结点里可以看到这个表中的所有元素。

        链接技术是一类非常灵活的数据组织技术,实现链表有多种不同的方式。下面首先讨论最简单的单链表,其中在每个表结点里记录着存储下一个表元素的结点的标识(引用/链接)。后面将介绍另外一些结构的链表,它们各有所长,支持不同的需要。在下面的讨论中,将把“存储着下一个表元素的结点”简称为“下一结点”。

1 单链表

        单向链接表(下面将简称为单链表或者链表)的结点是一个二元组,形式如下图a所示,其表元素域 elem 保存着作为表元素的数据项(或者数据项的关联信息),链接域 next 里保存同一个表里的下一个结点的标识。

        在最常见形式的单链表里,与表里的n个元素对应的n个结点通过链接形成一条结点链,如上图b所示。从引用表中首结点的变量(上图b中变量p)可以找到这个表的首结点,从表中任一结点可以找到保存着该表下一个元素的结点(表中下一结点),这样,从p出发就能找到这个表里的任一个结点。

        要想掌握一个单链表,就需要(也只需要)掌握这个表的首结点,从它出发可以找到这个表里的第一个元素(即在这个表结点里保存的数据,保存在它的 elem域中),还可以找到这个表里的下一结点(有关信息保存在这个结点的 next 域中)。按照同样的方式继续下去,就可以找到表里的所有数据元素。

        也就是说,为了掌握一个表,只需要用一个变量保存着这个表的首结点的引用(标识或称为链接)。今后将把这样的变量称为表头变量或表头指针

小结一下:

  • —个单链表由一些具体的表结点构成。
  • 每个结点是一个对象,有自己的标识,下面也常称其为该结点的链接。
  • 结点之间通过结点链接建立起单向的顺序联系。

        为了表示一个链表的结束,只需给表的最后结点(表尾结点)的链接域设置一个不会作为结点对象标识的值(称为空链接),在 Python 里自然可以用系统常量 None 表示这种情况,在上图里用接地符号“丄”表示链表结束,下面将一直这样表示。

        通过判断一个(域或变量的)值是否为空链接,可知是否已到链表的结束。在顺序扫描表结点时,应该用这种方法确定操作是否完成。如果一个表头指针的值是空链接,就说明“它所引用的链表已经结束”,这是没有元素就已结束,说明该表是空表。

        在实现链表上的算法时,并不需要关心在某个具体的表里各结点的具体链接值是什么(虽然保存在表结构里的值都是具体的),只需要关心链表的逻辑结构。对链表的操作也只需要根据链表的逻辑结构考虑和实现。

        为方便下面的讨论,现在定义个简单的表结点类:

class ListNode:
    def __init__(self, elem=0, next=None):
        self.elem = elem    
        self.next = next

1.1 基本操作

         创建空链表: 只需要把相应的表头变量设置为空链接,在Python语言中将其设置为None,在其他语言里也有惯用值,例如有的语言里用0作为这个特殊值。

         删除链表: 应丢弃这个链表里的所有结点。这个操作的实现与具体的语言环境有关。在一些语言(如C语言)里,需要通过明确的操作释放一个个结点所用的存储。在Python语言中这个操作很简单,只需简单地将表指针赋值为None,就抛弃了链表原有的所有结点。Python解释器的存储管理系统会自动回收不用的存储。

         判断表是否为空: 将表头变量的值与空链接比较。在Python语言中,就是检查相应变量的值是否为None.

         判断表是否满: 一般而言链表不会满,除非程序用完了所有可用的存储空间。

加入元素

        现在考虑给单链表加入元素的操作,同样有插入位置问题,可以做首端插入、尾端插人或者定位插人。不同位置的操作复杂度可能不同。

        首先应该注意,在链表里加入新元素时,并不需要移动已有的数据,只需为新元素安排一个新结点,然后根据操作要求,把新结点连在表中的正确位置。也就是说,插入新元素的操作是通过修改链接,接入新结点,从而改变表结构的方式实现的。

        表首端插入: 首端插入元素要求把新数据元素插入表中,作为表的第一个元素,这是最简单的情况。这一操作需要通过三步完成:

  1. 创建一个新结点并存入数据(下图a表示要向表头变量 head 的链表加入新首元素13,为它创建了新结点,变量q指着该结点。这是实际插入前的状态)。
  1. 把原链表首结点的链接存入新结点的链接域 next,这一操作将原表的一串结点链接在刚创建的新结点之后。

  2. 修改表头变量,使之指向新结点,这个操作使新结点实际成为表头变量所指的结点,即表的首结点(上图b表示设置链接的这两步操作完成后的状态,新结点已成为 head 所指链表的首结点,13成为这个表的首元素。注意,示意图中链接指针的长度和形状都不表示任何意义,只有图中的链接指向关系有意义)。

        注意,即使在插入前head指向的是空表,上面三步也能正确完成工作。这个插人只是一次安排新存储和几次赋值,操作具有常量时间复杂度。

示例代码段:

q = ListNode(13)
q.next = head.next
head = q

         一般情况的元素插入: 要想在单链表里的某位置插入一个新结点,必须先找到该位置之前的那个结点,因为新结点需要插入在它的后面,需要修改它的next 域。如何找到这个结点的问题将在后面讨论,先看已经找到了这个结点之后怎样插入元素。

        设变量pre已指向要插入元素位置的前一结点,操作也分为三步:

  1. 创建一个新结点并存入数据(下图a是实际插入前的状态)。
  2. 把 pre 所指结点 next 域的值存入新结点的链接域 next,这个操作将原表在 pre 所指结点之后的一段链接到新结点之后。
  3. 修改 pre 的 next 域,使之指向新结点,这个操作把新结点链入被操作的表,整个操作完成后的状态如下图b所示。

        注意,即使在插入前 pre 所指结点是表中最后一个结点,上述操作也能将新结点正确接入,作为新的表尾结点。

q = ListNode(13)
q.next = pre.next
pre.next = q

删除元素

        删除链表中元素,也可通过调整表结构删除表中结点的方式完成。这里也区分两种情况:删除表头结点的操作可以直接完成,删除其他结点也需要掌握其前一结点。

         删除表首元素: 删除表中第一个元素对应于删除表的第一个结点,为此只需修改表头指针,令其指向表中第二个结点。丢弃不用的结点将被Python解释器自动回收。

head = head.next

        一般情况的元素删除:一般情况删除须先找到要删元素所在结点的前一结点,设用变量pre指向,然后修改pre的next域,使之指向被删结点的下一结点。

pre.next = pre.next.next

        显然,这两个操作都要求被删结点存在。

        如果在其他编程语言里删除结点,还可能要自己释放存储。Python的自动存储管理机制能自动处理这方面的问题,使编程工作更简单,也保证了安全性。

扫描、定位和遍历

        在一般情况下插入和删除元素,都要求找到被删结点的前一结点。另外,程序里也可能需要定位链表中的元素、修改元素、逐个处理其中元素等。这些操作都需要检查链表的内容,实际上是检查表中的一些(或全部)结点。

        由于单链表只有一个方向的链接,开始情况下只有表头变量在掌握中,所以对表内容的一切检查都只能从表头变量开始,沿着表中链接逐步进行。这种操作过程称为链表的扫描,这种过程的基本操作模式是:

p = head
while p is not None and 还需继续的其他条件:
    对p所指结点里的数据做所需操作
    p = p.next

        根据Python语言的规定,这里的 p is not None 可以简单地只写一个 p

        循环的继续(或结束)条件、循环中的操作由具体问题决定。循环中使用的辅助变量p称为扫描指针。注意,每个扫描循环必须用一个扫描指针作为控制变量,每步迭代前必须检查其值是否为None,保证随后操作的合法性。这与连续表的越界检查类似。

        上面表扫描模式是最一般的链表操作模式,下面介绍几个常用操作的实现。

         按下标定位: 按 Python 惯例,链表首结点的元素应看作下标0,其他元素依次排列。确定第i个元素所在结点的操作称为按下标定位,可以参考表扫描模式写出:

p = head
while p is not NOne and i > 0:
    i -= 1
    p = p.next

        假设循环前变量i已有所需的值,循环结束时可能出现两种情况:或者扫描完表中所有结点还没有找到第i个结点,或者p所指结点就是所需。通过检查 p 值是否为None可以区分这两种情况。显然,如果现在需要删除第k个结点,可以先将i设置为k-1,循环后检查i是0且p.next不是None就可以执行删除了。

         按元素定位: 假设需要在链表里找到满足谓词pred的元素。同样可以参考上面的表扫描模式,写出的检索循环如下:

p = head
while p is not None and not pred(p.elem):
    p = p.next

        循环结束时或者p是None;或者 pred(p.elem) 是 True,找到了所需元素。

        完整的扫描称为遍历,这样做通常是需要对表中每个元素做一些事情,例如:

p = head
while p is not None:
    print(p.elem)
    p = p.next

        这个循环依次输出表中各元素。只以条件 p is not None 控制循环,就能完成一遍完整的遍历。同样模式可用于做其他工作。

链表操作的复杂度
        总结一下链表操作的时间复杂度。

  • 创建空表: O ( 1 ) O(1) O(1)

  • 删除表:在Python里是 O ( 1 ) O(1) O(1)。当然,Python 解释器做存储管理也需要时间。

  • 判断空表: O ( 1 ) O(1) O(1)

  • 加入元素(都需要加一个T(分配)的时间)

    • 首端加入元素: O ( 1 ) O(1) O(1)
    • 尾端加入元素: O ( n ) O(n) O(n)、因为需要找到表的最后结点。
    • 定位加人元素: O ( n ) O(n) O(n),平均情况和最坏情况。
  • 删除元素:

    • 首端删除元素: O ( 1 ) O(1) O(1)
    • 尾端删除元素: O ( n ) O(n) O(n)
    • 定位删除元素: O ( n ) O(n) O(n),平均情况和最坏情况。
    • 其他册除:通常需要扫描整个表或其一部分 O ( n ) O(n) O(n)

        扫描、定位或遍历操作都需要检查一批表结点,其复杂度受到表结点数的约束,都是 O ( n ) O(n) O(n) 操作。其他在工作中有此类行为的操作也至少具有 O ( n ) O(n) O(n) 时间复杂度。

求表的长度

        在使用链表时,经常需要求表的长度,为此可以定义一个函数:

p, n = head, 0
while p is not None:
    n += 1
    p = p.next
return n

        这个函数采用表扫描模式,遍历表中所有结点完成计数。显然,这个求表长度的算法所用时间与表结点个数成正比,具有 O ( n ) O(n) O(n) 时间复杂度。

实现方式的变化

        以求表的长度为例,如果程序经常需要调用上面函数, O ( n ) O(n) O(n) 复杂度就可能成为效率问题。如果表很长,执行该函数就可能造成可察觉的停顿。解决这个问题的一种方法是改造单链表的实现结构,增加一个表长度记录。显然,这个记录不属于任何表元素,是有关表的整体的信息。表示这件事的恰当方法是定义一种链表对象,把表的长度和表中的结点链表都作为这个表对象的数据成分,如下图所示。

        图中变星p指向表对象,这个对象的一个数据域记录表中元素个数(图中的20表示这个表当时有20个结点),另一个域引用着该表的结点链。采用了这种表示方式,求表长度的操作就可以简单返回元素计数域的值。但另一方面,这种表的每个变动操作都需要维护计数值。从整体看有得有失。这种调整消除了一个线性时间操作,可能在一些应用中很有意义。

1.2 单链表类的实现

自定义异常

        为能合理地处理一些链表操作中遇到的错误状态(例如,方法执行时遇到了无法操作的错误参数),首先为链表类定义一个新的异常类:

class LinkedListUnderflow(ValueError):
    pass

        这里把LinkedListUnderflow定义为标准异常类valueError的子类,准备在空表访问元索等场合抛出这个异常。在这些情况下抛出 valueError 也没问题,但定义了自己的异常类,就可以写专门的异常处理器,在一些情况下可能有用。

LList类的定义,初始化函数和简单操作

        现在基于结点类 ListNode 定义一个单链表对象的类,在这种表对象里只有一个引用链接结点的_head域,初始化为None表示建立的是一个空表。判断表空的操作检查_head;在表头插入数据的操作是prepend,它把包含新元素的结点链接在最前面;操作 pop 删除表头结点并返回这个结点里的数据:

class LList:
    def __init__(self):
        self._head = None

    def is_empty(self):
        return self._head is None

    def prepend(self, elem):
        self._head = ListNode(elem, self._head)

    def pop(self):
        if self._head is None:      # 无结点,引发异常
            raise LinkedListUnorderflow("in pop")
        e = self._head.elem
        self._head = self._head.next
        return e

        这里把 LList 对象的 _head 域作为对象的内部表示,不希望外部使用。上面定义里的几个操作都很简单,只有 pop 操作需要检查对象的状态,表中无元素时引发异常。

后端操作

        在链表的最后插入元素,必须先找到链表的最后一个结点。其实现首先是一个扫描循环,找到相应结点后把包含新元素的结点插入在其后。下面是定义:

def append(self, elem):
    if self._head is None:
        self._head = ListNone(elem)
        return 
    p = self._head
    while p.next is not None:
        p = p.next
    p.next = ListNode(elem)

        这里需要区分两种情况:如果原表为空,引用新结点的就应该是表对象的_head域,否则就是已有的最后结点的next域。两种情况下需要修改的数据域不一样。许多链表变动操作都会遇到这个问题,只有表首端插入/删除可以统一处理。

        现在考虑删除表中最后元素的操作,也就是要删除最后的结点。前面说过,要从单链表中删除一个结点,就必须找到它的前一结点。在尾端删除操作里,扫描循环应该找到表中倒数第二个结点,也就是找到p.next.next为None的p。下面定义的pop_last函数不仅删去表中最后元素,还把它返回(与pop统一)。

        在开始一般性扫描之前,需要处理两个特殊情况:如果表空没有可返回的元素时应该引发异常。表中只有一个元素的情况需要特殊处理,因为这时应该修改表头指针。一般情况是先通过循环找到位置,取出最后结点的数据后将其删除:

def pop_last(self):
    if self._head is None:      # 空表
        raise LinkeListUnderflow("in pop_last")
    
    p = self._head
    if p.next is None:          # 表中只有一个元素
        e = p.elem
        self._head = None
        return e
    while p.next.next is not None:  # 直到p.next是最后结点
        p = p.next
    e = p.next.elem
    p.next = None
    return e

        LList类的下一个方法是找到满足给定条件的表元素。这个方法有一个参数,调用时通过参数提供一个判断谓词,该方法返回第一个满足谓词的表元素。显然,这个操作也需要采用前面的基本扫描模式。定义如下:

def find(self, pred):
    p = self._head
    while p is not None:
        if pred(p.elem):
            return p.elem
        p = p.next

        最后一个方法非常简单,但实际中可能很有用。在开发一个表类的过程中,人们会经常想看看被操作的表的当时情况:

def print_all(self):
    p = self._head
    while p is not None:
        print(p.elem, end='')
        if p.next is not None:
            print(', ', end='')
        p = p.next
    print('')

2 循环单链表

        单链表的另一常见变形是循环单链表(简称循环链表),其中最后一个结点的next域不用None,而是指向表的第一个结点,如下图a所示。但如果仔细考虑,就会发现在链表对象里记录表尾结点更合适(如下图b),这样可以同时支持 O ( 1 ) O(1) O(1) 时间的表头/表尾插入和 O ( 1 ) O(1) O(1) 时间的表头删除。当然,由于循环链表里的结点连成一个圈,哪个结点算是表头或表尾,主要是概念问题,从表的内部形态上无法区分。

        循环单链表操作与普通单链表的差异就在于扫描循环的结束控制。易见,一些不变操作的实现也要修改,如 printall。

        这种表对象只需一个数据域 _rear,它在逻辑上始终引用着表的尾结点。前端加入结点,就是在尾结点和首结点之间加人新的首结点,尾结点引用不变。通过尾结点引用很容易实现这个操作。另一方面,尾端加入结点也是在原尾结点之后(与首结点之间)插人新结点,只是插入后要把它作为新的尾结点,因此需要更新尾结点引用。这两个操作都要考虑空表插入的特殊情况。对于输出表元素的操作,关键在于循环结束的控制。下面实现中比较扫描指针与表头结点的标识,到达了表头结点就结束。前端弹出操作也很容易实现,后端弹出操作需要通过一个扫描循环确定位置。

        下面循环单链表类定义只实现了几个典型方法,供参考:

class LCList:                   # 循环单链表类
    def __init__(self):
        self._rear = None

        def is_empty(self):
            return self._rear is None
        
        def prepend(self, elem):    # 前端插入
            p = LNode(elem)
            if self._rear is None:
                p.next = p          # 建立一个结点的环
                self._rear = p
            else:
                p.next = self._rear.next
                self.rear.next = p
        
        def append(self, elem):     # 尾插入
            self.prepend(elem)
            self._rear = self._rear.next

        def pop(self):  # 前端弹出
            if self._rear is None:
                raise LinkedListUnderflow("in pop of CLList")
            p = self._rear.next
            if self._rear is p:
                self._rear = None
            else:
                self._rear.next = p.next
            return p.elem
        
        def printall(self):     # 输出元素
            if self.is_empty():
                return
            p = self._rear.next
            while True:
                print(p.elem)
                if p is self._rear:
                    break
                p = p.next

3 双链表

        单链表只有一个方向的链接,只能做一个方向的扫描和逐步操作。即使增加了尾结点引用,也只能支持 O ( 1 ) O(1) O(1) 时间的首端元素加入/删除和尾端加入。如果希望两端插入和删除操作都能高效完成,就必须修改结点(从而也是链表)的基本设计,加入另一方向的链接。这样就得到了双向链接表,简称双链表。有了结点之间的双向链接,不仅能支持两端的高效操作,一般结点操作也会更加方便。当然,这样做也需要付出代价:每个结点都需要增加一个链接域,增加的空间开销与结点数成正比,是 O ( n ) O(n) O(n)。如果每个表结点里的数据规模比较大,新增加的开销可能就显得不太重要了。

        为了支持首尾两端的高效操作,双链表应该采用下图所示的结构,包含一个尾结点引用域。易见,从双链表中任一结点出发,可以直接找到其前后的相邻结点(都是 O ( 1 ) O(1) O(1) 操作)。而对单链表而言,只能方便地找到下一个结点,要找前一结点,就必须从表头开始逐一检查(通过一次扫描)。

        可以直接找到当前结点的前后结点,使得双链表的许多操作都很容易地进行。下面假定结点的下一结点引用域是next,前一结点引用域是prev。

结点操作

        先考虑结点删除。实际上,只要掌握着双链表里一个结点,就可以把它从表中取下,并把其余结点正确链接好。下图说明了这个操作。示例代码是:

p.prev.next = p.next
p.next.prev = p.prev

        这两个语句使p所指结点从表中退出,其余结点保持顺序和链接。如果要考虑前后可能无结点的情况,只需增加适当的条件判断。

        在任一结点的前后加入结点的操作也很容易局部完成,只需掌握确定加入位置的这个结点。易见,加入一个结点需要做四次引用赋值。

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

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

相关文章

刷题记录:牛客NC51112Stars in Your Window 扫描线

传送门:牛客 题目描述: Here comes the problem: Assume the sky is a flat plane. All the stars lie on it with a location (x, y). for each star, there is a grade ranging from 1 to 100, representing its brightness, where 100 is the brightest and 1 is the we…

数据库大量数据导出导入的操作

基于上一篇文章,我想到如果在数据库之间拥有大量数据的表数据的导入导出,该如何快速完成表的导入导出工作呢? 思路一:使用db软件工具导出数据,然后向新数据库导入数据。我用的是dbserver22.3.4,不出意外的是&#xff0…

【读书笔记】《深入浅出数据分析》第二章 检验你的理论

文章目录一,相关分析方法1,相关系数二,相关性不等于因果关系三,证明因果关系,“控制变量法”?本章主要说明了两个问题: 1,相关性不等于因果关系 2,如何判断两种数据之间是相关性&am…

深圳/东莞/惠州师资比较强的CPDA数据分析认证

深圳/东莞/惠州师资比较强的CPDA数据分析认证培训机构 CPDA数据分析师认证是中国大数据领域有一定权威度的中高端人才认证,它不仅是中国较早大数据专业技术人才认证、更是中国大数据时代先行者,具有广泛的社会认知度和权威性。 无论是地方政府引进人才、…

Android 高性能列表:RecyclerView + DiffUtil

文章目录背景介绍一般刷新 notifyDataSetChanged()局部刷新实现调用代码准备工作创建 MyDiffUtilCallback 类继承 DiffUtil.Callback 抽象类MyAdpter 类代码实现步骤总结通过 log 证实 diffutil 的局部刷新diffutil 优化后台线程参考主线程参考diff 更新优化后写法相关参考背景…

Spring的一些知识点

什么是Spring? Spring是一种轻量级的开发框架,旨在提高开发人员的开发效率以及系统的可维护性。 Spring的核心模块 Spring Core是基础模块,可以说Spring的其他功能都要依赖于该类库,主要提供IOC的依赖注入功能; Spri…

动手学深度学习v2—01数据操作+数据预处理

此次用到的虚拟环境:pytorchmwy项目名称:limuAI所需框架和工具:pytorch,pandas一、创建CSV文件所需工具:pandas在与项目同等目录下创建一个文件夹名为data,其中文件名称为house_tiny.csv。代码如下&#xf…

Java基础:拼图小游戏

涉及到的知识: 1.图形用户接口GUI(Graphical User Interface)用图形化的方式显示操作界面 两个体系: AWT包和Swing包 2.界面会用到JFrame类 3.界面中的菜单会用到JMenuBar, JMenu, JMenuItem 4.添加图片 在设置完JLabel的location之后还需要获得展示内容的窗体, 通过setLay…

吃鸡用什么蓝牙耳机效果好?手游吃鸡公认最好的几款蓝牙耳机

蓝牙耳机的作用很多,几乎每个人都需要一副很棒的耳机在通勤或锻炼途中使用,并且玩游戏也少不了它,手游近几年十分的流行,下面整理了几款性能不错的蓝牙耳机。 第一款:南卡小音舱蓝牙耳机 蓝牙版本:5.3 发…

【Linux】如何将ntfs硬盘挂载到home目录下并具有读写权限

步骤1. 查看当前挂载的硬盘及其挂载点2. 查看需要挂载到home下的磁盘类型信息3. 在home下新建一个空的文件夹作为该磁盘的新挂载点4. 以ntfs类型的硬盘为例,使用mount命令进行挂载5. 问题1:进程占用了磁盘6. 问题2:磁盘权限为只读的7. 永久挂…

[AI助力] 2022.2.23 考研英语学习 2010 英语二翻译

[AI助力] 2022.2.23 考研英语学习 2010 英语二翻译 文章目录[AI助力] 2022.2.23 考研英语学习 2010 英语二翻译2010年英语二翻译真题自己写的积累🧐看看AI的翻译,学习学习(把自己当成阅卷老师来康康hhh🤠DeepL谷歌翻译ReadPaper里…

JavaUDP通信程序

2 UDP通信程序 2.1 UDP通信原理 UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象, 但是这两个Socket只是发送,接收数据的对象因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念 Java提供了DatagramSocket类作为基…

低代码选型,论协同开发的重要性

Git是一款用于分布式版本控制的免费开源软件: 它可以跟踪到所有文件集中任意的变更,通常用于在软件开发期间,协调配合程序员之间的代码程序开发工作。 Git 最初诞生的原因源于Linux 内核的开发,2005年Linus Torvalds 编写出了Git。其他内核开…

AI作画—中国画之山水画

山水画,简称“山水”,中国画的一种,描写山川自然景色为主体的绘画。山水画在我国绘画史中占有重要的地位。 山水画形成于魏晋南北朝时期,但尚未从人物画中完全分离。隋唐时始终独立,五代、北宋时趋于成熟,…

Solon2 之基础:四、应用启动过程与完整生命周期

串行的处理过程(含六个事件扩展点 两个函数扩展点),代码直接、没有什么模式。易明 提醒: 启动过程完成后,项目才能正常运行(启动过程中,不能把线程卡死了)AppBeanLoadEndEvent 之前…

【C++】类和对象(完结篇)

文章目录1. 再谈构造函数1.1 初始化列表1.2 explicit关键字2. static 成员2.1 静态成员变量2.1 静态成员函数2.3 练习2.4 总结3. 匿名对象4. 友元4.1 友元函数4.2 友元类5. 内部类6. 拷贝对象时编译器的一些优化7. 再次理解类和对象这篇文章呢,我们来再来对类和对象…

TypeScript学习笔记(一)编译环境、数据类型、函数类型、联合类型

文章目录编译环境基本类型函数类型函数重载联合类型和函数重载编译环境 TypeScript最终会被编译成JavaScript来运行,所以我们需要搭建对应的环境。 首先我们要全局安装typescript # 安装命令 npm install typescript -g # 查看版本 tsc --version⭐️ 方式一&…

【2023-2-23】FastDeploy 安装教程

【2023-2-22】FastDeploy 安装编译教程 该测试 FastDeploy CPU版本。 1. fastDeploy库编译 1.1 官方预编译库下载 预编译库下载安装 1.2 自定义CPU版本库编译 官方编译FastDeploy教程 CMakeGUI VS 2019 IDE编译FastDeploy 本人编译教程 CMAKE_CONFIGURATION_TYPES 属性设…

(三十一)大白话MySQL如果事务执行到一半要回滚怎么办?再探undo log回滚日志原理

之前我们已经给大家深入讲解了在执行增删改操作时候的redo log的重做日志原理,其实说白了,就是你对buffer pool里的缓存页执行增删改操作的时候,必须要写对应的redo log记录下来你做了哪些修改 如下图所示: 这样万一要是你提交事…

渗透测试之DNS域名信息探测实验

渗透测试之DNS域名信息探测实验实验目的一、实验原理1.1 域名1.2 .域名的构成1.3 域名的基本类型1.4 域名级别二、实验环境2.1 操作机器三、实验步骤1. 使用sp查询域名信息2. 进行探测实验实验目的 掌握使用nslookup进行DNS域名信息探测的原理和方式了解子域名查询网站 一、实…