与我一起学C++之list<二>

上一章我们讲解了list_nodelist_iterator,这一章我们主要来讲讲list 本身的实现。

废话不说,直接看代码

1、 构造函数和成员变量

class list
{
public:
    list();
private:
    list_node*  head_;
    list_node*  tail_; 
};
list::list()
{
    head_ = new list_node;
    tail_ = new list_node;
    head_->next = tail_;
    head_->prev = nullptr;
    tail_->prev = head_;
    tail_->next = nullptr;
}

首先我们在list内部封装了两个list_node的指针,作为前哨兵和尾哨兵,并且在构造的时候,给这两个指针申请内存并互相指向。

两个哨兵模式的某个list实例的内存模型可以用下图表示

这里写图片描述

在STL中都是用的一个哨兵的模式,感兴趣的同学可以自行画图表示一下,它实际上是一个环状的链式结构。我们这里用两个哨兵的目的是为了让语义更清楚而已。

2、list是否为空

从上面的图很容易看出来,当head_->next指针直接指向tail_指针时,该list就为空

bool list::empty() const
{
    return head_->next == tail_;
}

3、begin 和 end

同样从上图看,很显然我们可以写出

list_iterator list::begin() { return list_iterator(head_->next); }

list_iterator list::end() { return list_iterator(tail_); }

4、 list的大小

有了begin()end() 迭代器,很容易就可以得到list的大小了。

int list::size() const
{
    int size_ = 0;
    distance(begin(), end(), size_);
    return size_;
}
void list::distance(list_iterator first,list_iterator last,int& result) const
{
    for (auto it = first; it != last; ++it)
        ++result;
}

如果您看过《Effective STL》,那您应该记得有一款条目是说,永远不要用if(size() == 0)而是empty() 来判断一个容器是否为空。从这您就可以端倪来了,是的,对于部分容器来说,调用一次size()的时间复杂度为O(n),而调用一次empty()的时间复杂度为O(1),孰优孰劣可想而知了。

条款 4 : 用empty来代替检查size是否为0 (Effective STL 中文版 25页)

5、front 和 back

与上面类似,通过begin 和 end 很快就可以得到 front 和 back的值

int& list::front() const { return *begin();}

int& list::back() const { return *(--end());}

6、插入和添加元素

6.1 插入元素

在添加元素之前,我们先来考虑怎么插入一个元素。首先,在某个节点处插入一个元素,我们还需要考虑是作为前驱(as prev)还是作为后继(as next)插入。通过查看gnu gcc 提供的源代码,STL中是作为前驱来插入的。

在写代码之前,我们可以通过画图等辅助方式来帮助我们分析,毕竟有图才有真相么…

这里写图片描述

据图可以写出如下代码

private:
list_iterator list::insertAsPerv(list_iterator position,const int &task)
{
    list_iterator tmp(new list_node);
    *tmp = task;
    position.node_->prev->next = tmp.node_;
    tmp.node_->prev = position.node_->prev;
    tmp.node_->next = position.node_;
    position.node_->prev = tmp.node_;
    return tmp;
}
public:
list_iterator list::insert(list_iterator position,const int &task)
{
    return insertAsPerv(position,task);
}

6.2 在前部和后部添加元素

当插入一个元素时,我们是作为前驱插入的,所以对于push_backpush_front我们可以像如下编写

void list::push_back(const int &value){ insert(end(), value); }

void list::push_front(const int &value){ insert(begin(), value); }

7、查找某个元素 find

一般我们用find() 时,通常意味着是在一个无序的数据结构(容器)中查找,对于list中的查找,我们只能遍历所有元素才进行。

list_iterator list::find(list_iterator first, list_iterator last, const int &task)
{
    list_iterator iter = first;
    while (iter != last)
    {
        if (*iter == task)
            return iter;
        ++iter;
    }
    return last;
}

8、测试

写了一些接口之后,我们来写个小demo测试一下我们的程序是否有错误。

#include "list.h"
#include <iostream>
#include <cstdlib>
int main()
{
    std::cout<<"-------------- list test -----------------"<<std::endl<<std::endl;
    yang::list mylist;

    if(mylist.empty())
        std::cout<<"this is the empty list !"<<std::endl<<std::endl;

    for(int i = 0;i < 5; ++i)
        mylist.push_back(i);

    auto it = mylist.begin();

    std::cout<<"elements are :   ";
    for(;it != mylist.end(); ++it)
        std::cout<<*it<<std::ends;
    std::cout<<std::endl<<std::endl;

    mylist.push_front(5);

    if(!mylist.empty())
        std::cout<<"this is not the empty list !"<<std::endl<<std::endl;

    std::cout<<"size is "<<mylist.size()<<std::endl<<std::endl;

    std::cout<<"front is "<<mylist.front()<<std::endl<<std::endl;

    std::cout<<"back is "<<mylist.back()<<std::endl<<std::endl;

    it = mylist.begin();
    ++it;

    mylist.insert(it,-99);

    std::cout<<"elements are :   ";
    for(it = mylist.begin();it != mylist.end(); ++it)
        std::cout<<*it<<std::ends;
    std::cout<<std::endl<<std::endl;

    if(mylist.find(mylist.begin(),mylist.end(),-99) != mylist.end())
        std::cout<<"find the task -99 ! ..." <<std::endl<<std::endl;
    if(mylist.find(mylist.begin(),mylist.end(),100) == mylist.end())
        std::cout<<"can not find the task 100 ! ..." <<std::endl<<std::endl;

    system("pause");
    return 0;
}
// 以上代码均在 win7下,visual studio 2013 测试完成

测试结果如下:

这里写图片描述

从结果来看,我们写的list暂时还是没有错误的,可喜可贺:)无耻….

总结:本章主要讲了list底层的结构和一些最基本的接口函数,限于篇幅原因,还有一些接口的实现如sort,remove,erase等等我们留到下一章再讲解。

参考资料
STL 源码解析 侯捷著
数据结构(C++语言版)第三版 邓俊辉著
gcc 2.95 version source code

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值