zoukankan      html  css  js  c++  java
  • 第二十五课 静态单链表的实现

    静态单链表是一种新的数据结构类型。

    我们往线性表中添加的元素的个数是固定的,例如最大100个。

    只是这100个元素会经常的变动。

    这时候是顺序表还是单链表合适呢?

    显然是单链表,但是单链表也有问题。

    缺陷:

    解决方案:

    我们在顺序表的内部预留了空间,这片空间用来增加删除数据元素。配合单链表就形成了静态单链表。

    在静态单链表中的操作和普通单链表几乎一样,只有两个函数有差异:create和destroy

    静态单链表的继承层次结构:

    示例:

    编译时,第15行报错,因为第15行的写法中,编译器不知道Node是一个类型还是一个静态变量。因此,我们需要加上typename关键字。

    写成以下方式就可以了:

    1 unsigned char m_space[sizeof(typename LinkList<T>::Node) * N];

    但是这样写太繁琐了,而且第18行的Node也要修改,我们改成以下的形式:

    这样就没有错误了。

    create函数如下:

     获得空间的指针后,由于Node中可能含有自定义类型T对象,因此,我们还需要调用Node的构造函数。通过重载new来实现。

    我们自定义一个类型SNode,并且重载new,在指定的地址上调用构造函数,然后直接返回这个调用的地址即可。

    完整的StaticLinkList.h如下:

     1 #ifndef STATICLINKLIST_H
     2 #define STATICLINKLIST_H
     3 
     4 #include "LinkList.h"
     5 
     6 namespace DTLib
     7 {
     8 
     9 template< typename T, int N >
    10 class StaticLinkList : public LinkList<T>
    11 {
    12 protected:
    13     // Node和泛指类型T有关系,因此,不能直接在子类中使用sizeof(Node),而应该
    14     // sizeof(LinkList<T>::Node)
    15     // unsigned char m_space[sizeof(typename LinkList<T>::Node) * N];  // 预留空间
    16     typedef typename LinkList<T>::Node Node;
    17 
    18     struct SNode : public Node
    19     {
    20         void* operator new(unsigned int size, void* loc)
    21         {
    22             (void)size; // 消除 size没有使用的警告
    23             return loc;
    24         }
    25     };
    26 
    27     unsigned char m_space[sizeof(SNode) * N];  // 预留空间
    28     int m_used[N];   //预留空间的标记数组
    29 
    30     Node* create()
    31     {
    32         SNode* ret = NULL;
    33 
    34         for(int i = 0; i < N; i++)
    35         {
    36             if( !m_used[i] )
    37             {
    38                 ret = reinterpret_cast<SNode*>(m_space) + i;
    39                 ret = new(ret)SNode(); //在指定空间ret上调用SNode类的构造函数。
    40                 m_used[i] = 1;
    41                 break;
    42             }
    43         }
    44 
    45         return ret;
    46     }
    47 
    48     void destroy(Node* pn)
    49     {
    50         SNode* space = reinterpret_cast<SNode*>(m_space);
    51         SNode* psn = dynamic_cast<SNode*>(pn);
    52 
    53         for(int i = 0; i<N; i++)
    54         {
    55             if( psn == (space + i))
    56             {
    57                 m_used[i] = 0;
    58                 psn->~SNode();
    59             }
    60         }
    61     }
    62 
    63 public:
    64     StaticLinkList()
    65     {
    66         for(int i = 0; i < N; i++)
    67         {
    68             m_used[i] = 0;
    69         }
    70     }
    71 
    72     int capacity()
    73     {
    74         return N;
    75     }
    76 };
    77 
    78 }
    79 
    80 #endif // STATICLINKLIST_H

    第39行的程序会调用到自己重载的new(这里是placement new)操作符,在重载的new操作符中返回的还是ret(相当于申请了空间并返回地址)。然后在指定的地址ret上调用构造函数,

    第39行就是placement new的使用,第20行重载的也是placement new。

    对于自定义的类,如果想在指定的地址上调用构造函数,必须要重载placement new,否则编译报错。

    自定义类在指定地址上调用构造函数就和本程序中第39行类似,new的行为是先返回申请的地址(placement new的重载中返回了loc),然后在这个地址上调用构造函数。

    如果我们在SNode中重载普通的new,并且在这个new函数中搜索预留的空间,找到空间后返回首地址。这样的话我们调用new SNode()时就会返回首地址,并且在这个地址上调用构造函数。这样的话在其他成员函数中向获得Node对象时就得使用new SNode()这种操作。而我们想统一接口,使用create返回Node对象,因此,在create中先搜索地址,又调用了placement new,placement new中只返回地址。

    create还有一种可能的实现方式就是,在create函数中直接调用普通的new,在SNode中重载普通的new,在这个new中搜索地址,并返回地址,返回地址到create后会自动调用构造函数。如果是在堆上分配空间,或者预留空间是在静态区上,那么这种实现方式没有问题,而在我们的程序中,m_space和m_used都是普通的成员变量,搜索空间必然会用到这几个变量,而重载的new(placement new)默认就是静态的,因此,在重载new函数中不能使用m_space和m_used,而如果把它们定义成静态的,那就会被所有对象共用,这又不符合逻辑,所以这种create实现方式被否定了。

    测试程序如下:

     1 #include <iostream>
     2 #include "StaticLinkList.h"
     3 
     4 
     5 using namespace std;
     6 using namespace DTLib;
     7 
     8 
     9 int main()
    10 {
    11     StaticLinkList<int, 5> list;
    12 
    13     for(int i = 0; i<5; i++)
    14     {
    15         list.insert(0,i);
    16     }
    17 
    18     //遍历时必须先调用move函数
    19     for(list.move(0); !list.end(); list.next())
    20     {
    21         cout << list.current() << endl;
    22     }
    23 
    24     return 0;
    25 }

    结果如下:

    第二个测试程序如下:

     1 #include <iostream>
     2 #include "StaticLinkList.h"
     3 
     4 
     5 using namespace std;
     6 using namespace DTLib;
     7 
     8 
     9 int main()
    10 {
    11     StaticLinkList<int, 5> list;
    12 
    13     for(int i = 0; i<5; i++)
    14     {
    15         list.insert(0,i);
    16     }
    17 
    18     list.insert(6);
    19 
    20     //遍历时必须先调用move函数
    21     for(list.move(0); !list.end(); list.next())
    22     {
    23         cout << list.current() << endl;
    24     }
    25 
    26     return 0;
    27 }

    结果:

     这是由于,在第18行我们又尝试插入一个元素,而我们定义的空间大小本身就是5,没有了足够的空间,StaticLinkList类中的create函数就返回NULL,这个类中的insert函数是继承自LinkList类的,在insert中会判断指针是否为空,如果为空就抛出异常。这里就和我们的运行结果对应上了。

    更改测试程序:

     1 #include <iostream>
     2 #include "StaticLinkList.h"
     3 
     4 
     5 using namespace std;
     6 using namespace DTLib;
     7 
     8 
     9 int main()
    10 {
    11     StaticLinkList<int, 5> list;
    12 
    13     for(int i = 0; i<5; i++)
    14     {
    15         list.insert(0,i);
    16     }
    17 
    18     try
    19     {
    20         list.insert(6);
    21     }
    22     catch(Exception& e)
    23     {
    24         cout << e.message() << endl;
    25     }
    26 
    27     //遍历时必须先调用move函数
    28     for(list.move(0); !list.end(); list.next())
    29     {
    30         cout << list.current() << endl;
    31     }
    32 
    33     return 0;
    34 }

    结果如下:

     小结:

  • 相关阅读:
    虚拟机中对centOS7.4配置静态ip
    mybatis使用中出现的错误!
    http中get和post方法区别
    java中堆和栈的区别
    struts2工作流程
    springmvc工作流程
    JDBC访问数据库流程
    并行程序设计模式-Master-Worker模式-Guarded Suspension模式-不变模式-生产者-消费者模式的理解
    Future模式个人理解
    分布式系统一致性问题和Raft一致性算法
  • 原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9650906.html
Copyright © 2011-2022 走看看