模板实现动态顺序表(含容量检测函数的详细讲解)

一、模板实现动态顺序表
(1)要实现的成员函数和成员变量

template<typename T>
class SeqList
{
public:
    SeqList();//构造
    SeqList(const T& seqlist);//拷贝构造
    SeqList& operator=(const T& seqlist);//赋值运算符重载
    ~SeqList();//析构
    void PushBack(const T& d);//尾插
    void PopBack();//尾删
    void PushFront(const T& d);//头插
    void PopFront();//头删
    void Sort();//排序
    void Remove(const T& x);//删除第一个出现的特定元素
    void RemoveAll(const T& x);//删除所有出现的特定元素
    void Earse(int index);//删除特定位置的元素
    void Insert(int index,const T&  x);//指定的位置插入某个元素
    void Reserve(int sz);//增容到指定的大小
    void Display();//打印顺序表
private:
    void CheakCapacity();//容量检测函数
private:
    T* _pdata;
    int _sz;
    int _capacity;

(2)模板实现时由于要适用于所有的类型,所以在写容量检测函数时要注意
下面详细讲一下容量检测函数不同写法对某些类型的不适用;

(2.1)方法一:用realloc()开辟空间;
realloc开辟空间,是在原来空间的基础上扩大空间;返回开辟完成的空间地址;即就是原来的空间的地址;或者如果原来空间的后面没有多余的空间开辟,则realloc重新开辟一款大小正确的空间;然后将原来的空间数据拷贝到全新的空间中,将原来的空间释放;将新的空间的首地址返回;所有我们在使用realloc时,不能直接用原来的空间的指针接收realloc的返回值;因为,如果一旦在原来的空间基础上没有开辟成功,又没有多余的空间开辟全新的空间大小,那么realloc会返回NULL;这样一旦用原先的地址指针接收,会将原来的空间丢失;以前空间的数据也会找不到;所以如果用realloc开辟空间;必须建立一个临时变量就收reallo的返回值;然后进行返回值的检查,再决定是否赋给原先的指针;但是,用relloc有一个缺点,就是开辟的空间,不能初始化,这样的话;会在存储某些类型的数据时造成错误的输出;

    void CheakCapacity()
    {
      if (_capacity==_sz)
      {
         int NewCapacity=_capacity*2+3;
         T* tmp=(T*)realloc(_pdata,NewCapacity);
         if (tmp==NULL)
         {
             perror("realloc");
             exit(EXIT_FAILURE);
         }
         else
         {
             _pdata=tmp;
             _capacity=NewCapacity;
         }
     }
    }

(2.2)使用new操作符开辟空间;

    void CheakCapacity()
    {
        if (_capacity==_sz)
        {
            int NewCapacity=_capacity*2+3;
            T* tmp=new T[NewCapacity];
            memcpy(tmp,_pdata,sizeof(T)*_sz);
            delete[] _pdata;
            _pdata=tmp;
            _capacity=NewCapacity;
        }
    }

使用new操作符进行扩容时,当顺序表中存储string类的字符串时,并且当字符串长度大于16字节时,在扩容并同时拷贝元顺序表的内容是,会出现浅拷贝的问题;是的顺序表数据丢失;

我们知道string类对象的对象模型为
这里写图片描述

string类的对象的大小为32字节;
在其对象模型中,如果string字符串的长度小于16,那么这些字符存放在string
对象中的_buf的字符数组中;但是,如果字符串的长度大于16,那么系统会在内存中开辟一块空间,存放字符串,并且把string中的_Ptr指针指向存放字符串的空间;

我上面的写法当执行到memcpy(tmp,_pdata,sizeof(T)*_sz); 这句的时候,由于memcpy();是一个内存拷贝函数;在拷贝存放大于16字节的字符串事只是把_Ptr指针拷贝到了新开的空间;然后接下来执行delete[] _pdata; 这句的时候,会调用析构函数,析构函数会先把以前的空间的对象清理,在清理的过程中会把空间中指针所指向的保留长字符串的内存空间释放;然后再把该对象空间释放;
那么此时新开辟的空间中对象的_Ptr所指向的存放长字符串的空间不见了;所以此时数据丢失;造成错误;

  • 看一个例子:
#include<iostream>
#include<string>
using namespace std;

template<typename T>
class SeqList
{
public:
    SeqList();//构造
    ~SeqList();//析构
    void PushBack(const T& d);//尾插
    void Display();
private:
    void CheakCapacity()
    {
        if (_capacity==_sz)
        {
            int NewCapacity=_capacity*2+3;
            T* tmp=new T[NewCapacity];
            memcpy(tmp,_pdata,sizeof(T)*_sz);
            delete[] _pdata;
            _pdata=tmp;
            _capacity=NewCapacity;
        }
    }
private:
    T* _pdata;
    int _sz;
    int _capacity;
};
template<typename T>
SeqList<T>::SeqList()//构造
               :_pdata(NULL)
               ,_sz(0)
               ,_capacity(0)
{}
template<typename T>
SeqList<T>::~SeqList()//析构
{
       if (_pdata!=NULL)
       {
           delete[] _pdata;
       }
       _sz=0;
       _capacity=0;
}


template<typename T>
void SeqList<T>::PushBack(const T& d)//尾插
{
   CheakCapacity();//检测容量  
   _pdata[_sz]=d;
   _sz++;
}
template<typename T>
void SeqList<T>::Display()
{
    for (int i=0;i<_sz;i++)
    {
        cout<<_pdata[i]<<endl;
    }
}


void test()
{
    SeqList<string>  s1;
    s1.PushBack("abcdef");
    s1.PushBack("aaaaaa");
    s1.PushBack("rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr");
    s1.PushBack("bbbbbb");
    s1.PushBack("eeeeeee");
    s1.Display();
}
int main()
{
   test();
   return  0;
}
  • 结果:
    这里写图片描述

  • 图说:
    这里写图片描述

(2.3)采用for循环挨个赋值,string类型在赋值运算符重载函数中会解决浅拷贝问题;会为开辟一段新的内存储存长字符串

    //方法三:
    void CheakCapacity()
    {
        if (_capacity==_sz)
        {
            int NewCapacity=_capacity*2+3;
            T* tmp=new T[NewCapacity];
            //挨个赋值,string类型在赋值运算符重载函数中会解决浅拷贝问题;
            //会为开辟一段新的内存储存长字符串
            for (int i=0;i<_sz;i++)
            {
                tmp[i]=_pdata[i];
            }
            delete[] _pdata;
            _pdata=tmp;
            _capacity=NewCapacity;
        }
    }

- 结果:

这里写图片描述

二、完整实现

#include<iostream>
#include<string>
#include<assert.h>
using namespace std;

template<typename T>
class SeqList
{
public:
    SeqList();//构造
    ~SeqList();//析构
    void PushBack(const T& d);//尾插
    void PopBack();//尾删
    void PushFront(const T& d);//头插
    void PopFront();//头删
    void Sort();//排序
    void Remove(const T& d);//删除第一个出现的特定元素
    void RemoveAll(const T& d);//删除所有出现的特定元素
    void Earse(int index);//删除特定位置的元素
    void Insert(int index,const T&  d);//指定的位置插入某个元素
    void Reserve(int sz);//增容到指定的大小
    T& operator[](const int index);//重载[]
    void Display();//打印
    void PrintfCapacity();//打印容量
private:
    void CheakCapacity()
    {
        if (_capacity<=_sz)
        {
            int NewCapacity=_capacity*2+3;
            T* tmp=new T[NewCapacity];
            //挨个赋值,string类型在赋值运算符重载函数中会解决浅拷贝问题;
            //会为开辟一段新的内存储存长字符串
            for (int i=0;i<_sz;i++)
            {
                tmp[i]=_pdata[i];
            }
            delete[] _pdata;
            _pdata=tmp;
            _capacity=NewCapacity;
        }
    }
    void CheakCapacity1(int sz)
    {
        if (_capacity<=sz)
        {
            int NewCapacity=sz;
            T* tmp=new T[NewCapacity];
            //挨个赋值,string类型在赋值运算符重载函数中会解决浅拷贝问题;
            //会为开辟一段新的内存储存长字符串
            for (int i=0;i<_sz;i++)
            {
                tmp[i]=_pdata[i];
            }
            delete[] _pdata;
            _pdata=tmp;
            _capacity=NewCapacity;
        }
    }
private:
    T* _pdata;
    int _sz;
    int _capacity;
};

template<typename T>
SeqList<T>::SeqList()//构造
               :_pdata(NULL)
               ,_sz(0)
               ,_capacity(0)
{}
template<typename T>
SeqList<T>::~SeqList()//析构
{
       if (_pdata!=NULL)
       {
           delete[] _pdata;
       }
       _sz=0;
       _capacity=0;
}


template<typename T>
void SeqList<T>::PushBack(const T& d)//尾插
{
   CheakCapacity();//检测容量  
   _pdata[_sz]=d;
   _sz++;
}
template<typename T>
void SeqList<T>::PopBack()//尾删
{
    _sz--;
}
template<typename T>
void SeqList<T>::PushFront(const T& d)//头插
{
     CheakCapacity();
     for (int i=_sz;i>=1;i--)
     {
         _pdata[i]=_pdata[i-1];
     }
     _pdata[0]=d;
     _sz++;
}
template<typename T>
void SeqList<T>::PopFront()//头删
{
    for (int i=0;i<_sz-1;i++)
    {
        _pdata[i]=_pdata[i+1];
    }
    _sz--;

}
template<typename T>
void SeqList<T>::Sort()//排序
{
    if (_pdata==NULL)
    {
        printf("seqlist empty!");
    }
    int mark=0;
    for(int i=0;i<_sz-1;i++)//比较趟数
    {
        for (int j=0;j<_sz-i-1;j++)
        {
            if (_pdata[j]>_pdata[j+1])
            {
                mark=1;
                T tmp=_pdata[j];
                _pdata[j]=_pdata[j+1];
                _pdata[j+1]=tmp;
            }
        }
        if (mark=0)
        {
            break;
        }
    }

}
template<typename T>
void SeqList<T>::Remove(const T& d)//删除第一个出现的特定元素
{
    if (_pdata==NULL)
    {
        printf("seqlist empty!");
        return;
    }
    int pos=0;
    while(pos<_sz&&_pdata[pos++]!=d)//找元素
    {}
    pos-=1;//该元素的位置,一定要注意pos-1;
    if (_pdata[pos]==d)
    {
        if (pos==_sz-1)//处理元素为最后一个
        {
            PopBack();
        }
        for(int i=pos;i<_sz-1;i++)//不是最后一个
        {
            _pdata[i]=_pdata[i+1];
        }
        _sz--;
    }

}
template<typename T>
void SeqList<T>::RemoveAll(const T& d)//删除所有出现的特定元素
{
    if (_pdata==NULL)
    {
        printf("seqlist empty!");
    }
    int pos=0;
    while (pos<_sz+1)
    {       
        while(pos<=_sz&&_pdata[pos++]!=d)//找元素
        {}
        pos-=1;//该元素的位置
        if (_pdata[pos]==d)
        {
            if (pos==_sz-1)//处理元素为最后一个
            {
                PopBack();
            }
            for(int i=pos;i<_sz-1;i++)
            {
                _pdata[i]=_pdata[i+1];
            }
            _sz--;
        }
        if (pos==_sz)
        {
            break;
        }
    }

}
template<typename T>
void SeqList<T>::Earse(int index)//删除特定位置的元素
{
     if (index<0||index>=_sz)
     {
         return;
     }
     if (index==_sz)
     {
         PopBack();
     }
     else
     {
         for (int i=index;i<index-1;i++)
         {
             _pdata[i]=_pdata[i+1];
         }
         _sz--; 
     }
}
template<typename T>
void SeqList<T>::Insert(int pos,const T&  x)//指定的位置之前插入某个元素
{
    CheakCapacity();
    if (pos<0||pos>_sz)
    {
        return ;
    }
    for (int i=_sz;i>pos;i--)
    {
        _pdata[i]=_pdata[i-1];
    }
    _pdata[pos]=x;
    _sz++; 
}
template<typename T>
void SeqList<T>::Reserve(int sz)//增容到指定的大小
{
    CheakCapacity1(sz);
}
template<typename T>
T& SeqList<T>::operator[](const int index)//重载[]
{
    assert(index>=0&&index<_sz);
    return _pdata[index];
}
template<typename T>
void SeqList<T>::Display()//打印
{
    for (int i=0;i<_sz;i++)
    {
        cout<<_pdata[i]<<" ";
    }
    cout<<endl;
}
template<typename T>
void SeqList<T>::PrintfCapacity()
{
    cout<<_capacity<<endl;
}
void test()
{
    SeqList<int> s;
    s.PushBack(1);
    s.PushBack(2);
    s.PushBack(3);
    s.PushBack(4);
    s.PushBack(3);
    s.PushBack(5);
    s.PushBack(3);
    s.PushFront(10);
    s.PushFront(3);
    s.PushFront(11);
    s.PushFront(3);
    s.PushFront(12);
    cout<<"尾插头插以后的原始顺序表"<<endl;
    s.Display();
    s.PopBack();
    s.PopFront();
    cout<<"尾删头删以后的顺序表"<<endl;
    s.Display();
    cout<<"删除第一个出现的3"<<endl;
    s.Remove(3);
    s.Display();
    cout<<"删除所有出现的3"<<endl;
    s.RemoveAll(3);
    s.Display();
    cout<<"删除特定5元素"<<endl;
    s.Earse(5);
    s.Display();
    cout<<"位置2插入元素100"<<endl;
    s.Insert(2,100);
    s.Display();
    cout<<"重载[],答应第4个元素"<<endl;
    cout<<s[4]<<endl;
    cout<<"没使用Reserve()增容之前链表的容量->";
    s.PrintfCapacity();
    s.Reserve(100);
    cout<<"增容到指定100打印容量"<<endl;
    s.Reserve(100);
    s.PrintfCapacity();
    cout<<"最后顺序表的元素"<<endl;
    s.Display();
    cout<<"排序"<<endl;
    s.Sort();
    s.Display();
}
void test1()
{
    SeqList<string> s;
    s.PushBack("a");
    s.PushBack("b");
    s.PushBack("c");
    s.PushBack("d");
    s.PushBack("a");
    s.PushBack("e");
    s.PushBack("f");
    s.PushBack("a");
    s.PushFront("r");
    s.PushFront("ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg");
    s.PushFront("o");
    s.PushFront("a");
    cout<<"尾插头插以后的原始顺序表"<<endl;
    s.Display();
    s.PopBack();
    s.PopFront();
    cout<<"尾删头删以后的顺序表"<<endl;
    s.Display();
    cout<<"删除第一个出现的a"<<endl;
    s.Remove("a");
    s.Display();
    cout<<"删除所有出现的a"<<endl;
    s.RemoveAll("a");
    s.Display();
    cout<<"删除特定位置5的元素"<<endl;
    s.Earse(5);
    s.Display();
    cout<<"位置2插入元素helle world"<<endl;
    s.Insert(2,"helle world");
    s.Display();
    cout<<"重载[],打印第4个元素"<<endl;
    cout<<s[4]<<endl;
    cout<<"没使用Reserve()增容之前链表的容量->";
    s.PrintfCapacity();
    s.Reserve(100);
    cout<<"增容到指定100打印容量"<<endl;
    s.Reserve(100);
    s.PrintfCapacity();
    cout<<"最后顺序表的元素"<<endl;
    s.Display();
    cout<<"排序"<<endl;
    s.Sort();
    s.Display();
}
int main()
{
   test();
   cout<<endl;
   test1();
   return  0;
}

- 测试结果:
这里写图片描述

END!!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值