模拟指针

折腾了相当长一段时间,今天终于把代码整理好了,小编是个自学的beginner,可以看到有些文字还只是摘抄的,好不容易才弄出一篇有自己想法的东西。我目前的学习策略是,先把目标算法的相关代码实现了,然后整体阅读代码,在代码中加上自己的看法。代码书上是有的,但是结构分散,有些细节的东西并没有说明清楚,这对初学者相当考验。关于模拟指针,在网上搜索一下,不知道是自己搜索方式不好还是怎么,得到的中文信息较少,基本上也是把书上的东西贴上。我没有搜索英文方面的文章。

由于小编水平有限,发表的看法可能会有误,有不好的地方请指教。

有时候采用一个节点数组以及对该数组进行索引的模拟指针,可以使设计更方便、更高效。

假定采用一个数组node,该数组的每个元素中都包含两个域:data和link。数组中的节点分别是:node[0]、node[1]、…、node[NumberOfNodes-1]。以下用节点i来代表node[i]。如果一个单向链表c由节点10,5和24按序构成,将得到c=10 (指向链表c的第一个节点的指针是整数类型),node[10].link=5 (指向第二个节点的指针),node[5].link=24(指向下一个节点的指针),node[24].link=-1(表示节点24是链表中的最后一个节点)。
这里写图片描述

模拟指针的类定义及其操作
Allocate从存储池中取出节点,每次取出一个。Deallocate则将节点放入存储池中,每次放入一个。存储池为可用空间表。
这是SimSpace类,模拟可用空间表

#pragma once
#include<iostream>
//#include"SimulChain.h"
using namespace std;

template <class T>
class SimSpace 
{
    friend class SimChain<T>;
public:
    SimSpace(int MaxSpaceSize = 100);
    ~SimSpace() { delete[] node; }
    int Allocate(); //分配一个节点
    void Deallocate(int& i); //释放节点i
    template<class T>
    class SimNode   //刚开始并不知道把SimNode类作为SimSpace类的嵌套类,
                    //这两个类彼此有利用,把谁放到前面都不合适。
    {
        friend class SimSpace<T>;
        friend class SimChain<T>;
    private:
        T data;
        int link;
    };

private:
    int NumberOfNodes, first;
    SimNode<T> *node;//节点数组
};

template<class T>
SimSpace<T>::SimSpace(int MaxSpaceSize)
{
    // 构造函数
    NumberOfNodes = MaxSpaceSize;
    node = new SimNode<T>[NumberOfNodes];
    // 初始化可用空间表
    // 创建一个节点链表
    for (int i = 0; i < NumberOfNodes - 1; i++)
        node[i].link = i + 1;     //第(i)0个节点的link是1,最后一个节点的link是-1
                                  //link和i之间的联系就是,这个节点的link是下个节点的i
    // 链表的最后一个节点
    node[NumberOfNodes - 1].link = -1;
    // 链表的第一个节点
    first = 0;
};

template<class T>
int SimSpace<T>::Allocate()
{
    //这个操作相当于删除可用空间表中的第一个节点,拿出来用了
    // 分配一个自由节点
    if (first == -1) throw NoMem();
    int i = first; //分配第一个节点
    first = node[i].link; //first指向下一个自由节点
    return i;
};

template<class T>
void SimSpace<T>::Deallocate(int& i)
{
    //这个函数相当于在可用空间表中添加一个节点,存进去了
    // 释放节点i .
    // 使i 成为可用空间表的第一个节点
    node[i].link = first;   //first此时指向可用空间表的第一个节点
    first = i;
    //i = -1;  //这一步是为什么?这个注释与否对目前的测试代码的结果表面上没有影响。
};
//使用两个空间表的部分函数版本,需要把first1和first2设置位SimSpace的私有成员
//template<class T>
//SimSpace<T>::SimSpace(int MaxSpaceSize)
//{ 
//  // 使用两个可用空间表的构造函数
//  NumberOfNodes = MaxSpaceSize;
//  node = new SimNode<T>[NumberOfNodes];
//  // 初始化可用空间表
//  firstl = 0;
//  first2 = -1;     //模拟指针和C++指针的区别
//};
//
//template<class T>
//int SimSpace<T>::Allocate()
//{
//  // 分配一个自由节点
//  if (first2 == -1) {// 第2个表为空
//      if (firstl == NumberOfNodes) throw NoMem();
//      return firstl++;
//      }
//  // 分配链表中的第一个节点
//  int i = first2;
//  first2 = node[i].link;
//  return i;
//}

————————
采用可用空间表模式分解一个链表将比采用C + +指针更高效。例如,如果一个单向链表的首部和尾部分别为f 和e,可以采用如下语句来释放链表中的所有节点:

node[e].link = first; first = f;  //既然所有节点都在链表中,那么可用空间表中的自由节点为零,所以first等于-1?

————————
如果c 是一个循环链表,则采用如下程序释放表中所有节点

template<class T>
void SimSpace<T>::DeallocateCircular(int& c)
{
    // 释放一个循环链表c
    if (c != -1) {
        int next = node[c].link;
        node[c].link = first;
        first = next;
        c = -1;
    }
};

这里写图片描述

这是SimChain类。

#pragma once
#include<iostream>
//#include"SimulSpace.h"  //这里包不包含都对下面SimChain类使用SimSpace类无帮助
                          //一直都是不会划红线但是编译会报奇怪的错误,
using namespace std;

template<class T> class SimSpace;   //在这里添加了这句,编译就没有报出那个错误了
                                    //但是类模板声明和定义不是应该放在一起的吗?

template<class T>
class SimChain 
{
public:
    SimChain() { first = -1; }
    ~SimChain() { Destroy(); }
    void Destroy(); // 使表为空
    int Length() const;
    bool Find(int k, T& x) const;
    int Search(const T& x) const;
    SimChain<T>& Delete(int k, T& x);
    SimChain<T>& Insert(int k, const T& x);
    void Output(ostream& out) const;
private:
    int first; // 第一个节点的索引
    static SimSpace<T> S;
};

template<class T>
void SimChain<T>::Destroy()
{
    // 释放链表节点
    int next;
    while (first != -1)
    {
        next = S.node[first].link;  //next指向第二个节点
        S.Deallocate(first);
        first = next;
    }
}

template<class T>
int SimChain<T>::Length() const
{
    // 返回链表的长度。我们可以算出链表的长度,
    //但是无法计算出可用空间的长度?计算可用空间的长度也没有意义?
    int current = first, // 链节点的当前位置
        len = 0; //元素计数
    while (current != -1) 
    {
        current = S.node[current].link;
        len++;
    }
    return len;
}

template<class T>
bool SimChain<T>::Find(int k, T& x) const
{
    // 取第k个元素至x
    // 如果不存在第k个元素,函数返回false,否则返回true
    if (k < 1) return false;
    int current = first, // 链节点的当前位置
        index = 1; //当前节点的索引
    // 移动current至第k个节点
    while (index < k && current != -1) 
    {
        current = S.node[current].link;
        index++;
    }
    // 验证是否到达了第k个节点
    if (current != -1)
    {
        x = S.node[current].data;
        return true;
    }
    return false; // 不存在第k个元素
}

template<class T>
SimChain<T>& SimChain<T>::Delete(int k, T& x)
{
    // 把第k个元素取至x,然后删除第k个元素
    // 如果不存在第k个元素,则引发异常OutOfBounds
    if (k < 1 || first == -1)
        throw OutOfBounds(); // 不存在第k个元素
    // p最终将指向第k个节点
    int p = first;
    // 将p移动至第k个节点,并从链表中删除该节点
    if (k == 1) // p已经指向第k个节点
        first = S.node[first].link; // 从链表中删除
                                    //这里表明k与数值node中的下标i是没有必然联系的。
                                    //k==1定位到SimChain链表中的第一个节点,first指向这个链表
                                    //中的第一个节点,但是node[first]可以是数组中的任何位置的元素
    else 
    { 
        // 使用q指向第k - 1个元素
        int q = first;
        for (int index = 1; index < k - 1 && q != -1; index++)
            q = S.node[q].link;
        // 验证第k个元素的存在性
        if (q == -1 || S.node[q].link == -1)
            throw OutOfBounds(); // 不存在第k个元素
        // 使p指向第k个元素
        p = S.node[q].link;
        // 从链表中删除第k个元素
        S.node[q].link = S.node[p].link;//再次强调,这里用p、q的目的之一是取出所要的节点在node
                                        //数组中的下标,传给Deallocate函数的也是这个下标,
                                        //从这里可以得到一条经验:传给SimSpace表相关操作
                                        //的参数应该都是node数组的下标。而k这里是SimChain链表
                                        //索引。
    }
        // 保存第k个元素并释放节点p
        x = S.node[p].data;
        S.Deallocate(p);  //释放这个已经删除的节点的目的就是为了让这个节点回归到
                          //可用空间表中。否则,它就处于“游离”状态,下次就没法使用,
                          //相当于“内存泄漏”了。
        return *this;
}

template<class T>
SimChain<T>& SimChain<T>::Insert(int k, const T& x)
{
    // 在第k个元素之后插入x
    // 如果不存在第k个元素,则引发异常OutOfBounds
    // 如果没有足够的空间,则传递NoMem异常
    if (k < 0)
        throw OutOfBounds();
    // 定义一个指针p,p最终将指向第k个节点
    int p = first;
    // 将p移向第k个节点
    for (int index = 1; index < k && p != -1; index++)
        p = S.node[p].link;
    // 验证第k个节点的存在性
    if (k > 0 && p == -1)
        throw OutOfBounds();
    // 为插入操作分配一个新节点
    int y = S.Allocate();
    S.node[y].data = x;
    // 向链表中插入新节点
    // 首先检查新节点是否要插到链表的首部
    if (k) 
    {
        //在p之后插入
        S.node[y].link = S.node[p].link;
        S.node[p].link = y;
    }
    else 
    {
        // 作为链表首节点
        S.node[y].link = first; first = y;
    }
    return *this;
}

template<class T>
int SimChain<T>::Search(const T& x) const
{
    // 寻找x,如果发现x,则返回x的地址
    // 如果x不在链表中,则返回0
    int current = first,  //链节点的当前位置
        index = 1; // current的索引
    while (current != -1 && S.node[current].data != x)
    {
        current = S.node[current].link;
        index++;
    }
    if (current != -1)
        return index;
    return 0;
}

template<class T>
void SimChain<T>::Output(ostream& out) const
{
    // 将链表元素送至输出流
    int current;
    for (current = first; current != -1; current = S.node[current].link)
        out << S.node[current].link <<":"<<S.node[current].data << " ";//输出link是为了方便
                                                            //比较link与插入序位k之间的不同
}

// 重载<<
template <class T>
ostream& operator<<(ostream& out, const SimChain<T>& x)
{
    x.Output(out);
    return out;
}

异常处理

#pragma once
#include <iostream>
using namespace std;
// 内存不足
class NoMem
{
public:
    NoMem() {}
};
// 使new引发NoMem异常而不是xalloc异常
void my_new_handler()
{
    throw NoMem();  //什么意思
}
new_handler Old_Handler_ = set_new_handler(my_new_handler);

class OutOfBounds
{
public:
    OutOfBounds() {}
};

主函数

//头文件包含的顺序很重要,在两个头文件中分别不包含彼此但有相互引用
//的情况下,Space.h对Chain.h的依赖比反之更强。在目前这种包含关系下,编译
//产生的错误跟之前的错误一样,即在两个头文件都彼此包含的情况下编译产生的错误。
//这也就是说,在头文件和源文件都包含了相关文件的情况下,编译器选择了按源文件中的包含顺序
//来执行?那我现在把源文件中的相关头文件注释掉,然后在两个自编头文件中包含彼此,会怎么样?
//原文件一注释掉就划红线,所以不能这样。现在搞清楚了一个关系,同一个项目中的头文件,只需要
//在源文件中包含就行了,但这要注意顺序!

//不得不注意,SimChain类和SimSpace类中都有一个first变量,应该区分清楚,
//SimChaim.first初始化为-1,SimSpace初始化为0。
//这是很自然的,从各个类的结构中看,实例化后,可用空间充满自由节点,
//它的first理应指向第一个节点(first=0);而SimChain链表还没有节点,所以它的first=-1。

#include <iostream>
#include "SimulChain.h"
#include "SimulSpace.h"
#include "Xcept.h"

using namespace std;

SimSpace<int> SimChain<int>::S; //实例化可用空间S(调用SimSpace类构造函数),
                                //这时存在一个足够大的数组,存放很多节点,
                                //每个节点的link已经在构造函数中指定,
                                //第0个节点node[0]的link部分是1,...

void main(void)
{
    int x;
    SimChain<int> c;
    cout << "Chain length is" << c.Length() << endl;
    c.Insert(0, 2).Insert(1, 6);
    cout << "Chain length is" << c.Length() << endl;
    c.Find(1, x);
    cout << "First element is" << x << endl;
    c.Delete(1, x);
    cout << "Deleted" << x << endl;
    cout << "New length is" << c.Length() << endl;
    cout << "Position of 2 is" << c.Search(2) << endl; //此处返回了0,表示不已经在链表中
    cout << "Position of 6 is" << c.Search(6) << endl;
    c.Insert(0, 9).Insert(1, 8).Insert(2, 7);
    cout << "Current chain is" << c << endl;
    cout << "Its length is" << c.Length() << endl;
    cout << "Position of 2 is" << c.Search(2) << endl;
    cout << "Position of 6 is" << c.Search(6) << endl;
}

以上内容部分整理自网络电子资料,仅供学习交流用,勿作商业用途。转载请注明来源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值