跟我学C++中级篇——STL的容器List

236 篇文章 94 订阅
本文深入探讨STL中的List和ForwardList,解释了链表数据结构的原理,对比了List与其他顺序容器如Vector的区别,并展示了List的源码实现。文章还介绍了如何在C++11中使用ForwardList,它是一个单向链表,更加节省内存和操作高效。此外,通过示例代码展示了List的基本操作,如插入、删除和合并。最后,文章强调了理解STL底层实现的重要性。
摘要由CSDN通过智能技术生成

 

一、顺序容器list

学过数据结构或者说学习过基础的C语言的人都接触过链表。链,同学们都知道,铁链,锁链…,表,一排排的小格子;那么组合在一起呢,就是这些小格子用链条一个个串起来,通过第一个入口的链条就可以不断的迭代查找到所有的小格子。把这些小格子换成数据,把链条换成指针,就是链表。链表有很多种,有单向链表,只能朝着一个方向前进;有双向链表,既可以向前查找,也可以向后查找;还有循环链表,链表整体构成一个环,诸如此等等。

 

循环链表就是把首尾连接起来,它更多的适用于环形缓冲区。
那么STL中的List和上面的链表有啥不同呢?本质是一样的,不同的在于实现的方式,正如前面的“STL介绍”中所说,使用不同的设计方式和思路,辅以各种算法,形成了STL风格的链表,就是List,它是一个双向链表。
List和前面介绍的向量和Array的不同在于,前者在内存中是平坦的,连续的,而链表不是,他在内存中可能是平坦的,更可能是不平坦的,也有可能是一会儿平坦,一会儿不平坦。有点拗口,一般就认为他不是平坦的。这意味着什么呢?这意味着List不能用下标索引来访问,即不能够随机读取。
但是List也有自己的优势,在平坦空间中删除和移动(含插入)元素,成本是非常大的,而在List中就容易多了,原则上只要把指针指向的位置改变一下,就可以轻松的达到目的,而不必像Vector等一样,要考虑空间的重组并且需要处理因为可能带来的空间的回收和增加。List处理元素,要么成功,要么失败,没有其它。
在新的c++11中增加了Forward List,一个单向的链表,估计有点是为了补全整个STL的覆盖范围,所以它的特点是和List的范围一致,只是提供了一个尽乎于快速版本的List,更节省内存,操作速度更快并且它有一个特点,没有Size()函数。它的目标就是赶超大牛们手码的单向链表。

二、源码

先看一下List的定义:

template <class _Value_type, class _Voidptr>
struct _List_node { // list node
    using value_type = _Value_type;
    using _Nodeptr   = _Rebind_pointer_t<_Voidptr, _List_node>;
    _Nodeptr _Next; // successor node, or first element if head
    _Nodeptr _Prev; // predecessor node, or last element if head
    _Value_type _Myval; // the stored value, unused if head

    _List_node(const _List_node&) = delete;
    _List_node& operator=(const _List_node&) = delete;

    template <class _Alnode>
    static _Nodeptr _Buyheadnode(_Alnode& _Al) {
        const auto _Result = _Al.allocate(1);
        _Construct_in_place(_Result->_Next, _Result);
        _Construct_in_place(_Result->_Prev, _Result);
        return _Result;
    }

    template <class _Alnode>
    static void _Freenode0(_Alnode& _Al, _Nodeptr _Ptr) noexcept {
        // destroy pointer members in _Ptr and deallocate with _Al
        static_assert(is_same_v<typename _Alnode::value_type, _List_node>, "Bad _Freenode0 call");
        _Destroy_in_place(_Ptr->_Next);
        _Destroy_in_place(_Ptr->_Prev);
        allocator_traits<_Alnode>::deallocate(_Al, _Ptr, 1);
    }

    template <class _Alnode>
    static void _Freenode(_Alnode& _Al, _Nodeptr _Ptr) noexcept { // destroy all members in _Ptr and deallocate with _Al
        allocator_traits<_Alnode>::destroy(_Al, _STD addressof(_Ptr->_Myval));
        _Freenode0(_Al, _Ptr);
    }

    template <class _Alnode>
    static void _Free_non_head(
        _Alnode& _Al, _Nodeptr _Head) noexcept { // free a list starting at _First and terminated at nullptr
        _Head->_Prev->_Next = nullptr;

        auto _Pnode = _Head->_Next;
        for (_Nodeptr _Pnext; _Pnode; _Pnode = _Pnext) {
            _Pnext = _Pnode->_Next;
            _Freenode(_Al, _Pnode);
        }
    }
};
template <class _Ty, class _Alloc = allocator<_Ty>>
class list { // bidirectional linked list
private:
    template <class>
    friend class _Hash;
    template <class _Traits>
    friend bool _Hash_equal(const _Hash<_Traits>&, const _Hash<_Traits>&);
#if !_HAS_IF_CONSTEXPR
    template <class _Traits>
    friend bool _Hash_equal_elements(const _Hash<_Traits>& _Left, const _Hash<_Traits>& _Right, false_type);
#endif // _HAS_IF_CONSTEXPR

    using _Alty          = _Rebind_alloc_t<_Alloc, _Ty>;
    using _Alty_traits   = allocator_traits<_Alty>;
    using _Node          = _List_node<_Ty, typename allocator_traits<_Alloc>::void_pointer>;
    using _Alnode        = _Rebind_alloc_t<_Alloc, _Node>;
    using _Alnode_traits = allocator_traits<_Alnode>;
    using _Nodeptr       = typename _Alnode_traits::pointer;

    using _Val_types = conditional_t<_Is_simple_alloc_v<_Alnode>, _List_simple_types<_Ty>,
        _List_iter_types<_Ty, typename _Alty_traits::size_type, typename _Alty_traits::difference_type,
            typename _Alty_traits::pointer, typename _Alty_traits::const_pointer, _Ty&, const _Ty&, _Nodeptr>>;

    using _Scary_val = _List_val<_Val_types>;

public:
    static_assert(!_ENFORCE_MATCHING_ALLOCATORS || is_same_v<_Ty, typename _Alloc::value_type>,
        _MISMATCHED_ALLOCATOR_MESSAGE("list<T, Allocator>", "T"));

    using value_type      = _Ty;
    using allocator_type  = _Alloc;
    using size_type       = typename _Alty_traits::size_type;
    using difference_type = typename _Alty_traits::difference_type;
    using pointer         = typename _Alty_traits::pointer;
    using const_pointer   = typename _Alty_traits::const_pointer;
    using reference       = value_type&;
    using const_reference = const value_type&;

    using iterator                  = _List_iterator<_Scary_val>;
    using const_iterator            = _List_const_iterator<_Scary_val>;
    using _Unchecked_iterator       = _List_unchecked_iterator<_Scary_val>;
    using _Unchecked_const_iterator = _List_unchecked_const_iterator<_Scary_val>;

    using reverse_iterator       = _STD reverse_iterator<iterator>;
    using const_reverse_iterator = _STD reverse_iterator<const_iterator>;

    list() : _Mypair(_Zero_then_variadic_args_t{}) {
        _Alloc_sentinel_and_proxy();
    }

    explicit list(const _Alloc& _Al) : _Mypair(_One_then_variadic_args_t{}, _Al) {
        _Alloc_sentinel_and_proxy();
    }

private:
    template <class _Any_alloc>
    explicit list(_Move_allocator_tag, _Any_alloc& _Al) : _Mypair(_One_then_variadic_args_t{}, _STD move(_Al)) {
        _Alloc_sentinel_and_proxy();
    }
......
public:
    void push_front(_Ty&& _Val) { // insert element at beginning
        _Emplace(_Mypair._Myval2._Myhead->_Next, _STD move(_Val));
    }

    void push_back(_Ty&& _Val) { // insert element at end
        _Emplace(_Mypair._Myval2._Myhead, _STD move(_Val));
    }

    iterator insert(const_iterator _Where, _Ty&& _Val) { // insert _Val at _Where
        return emplace(_Where, _STD move(_Val));
    }
    ......
    void merge(list& _Right) { // merge in elements from _Right, both ordered by operator<
    _Merge1(_Right, less<>());
}

void merge(list&& _Right) { // merge in elements from _Right, both ordered by operator<
    _Merge1(_Right, less<>());
}

template <class _Pr2>
void merge(list& _Right, _Pr2 _Pred) { // merge in elements from _Right, both ordered by _Pred
    _Merge1(_Right, _Pass_fn(_Pred));
}

template <class _Pr2>
void merge(list&& _Right, _Pr2 _Pred) { // merge in elements from _Right, both ordered by _Pred
    _Merge1(_Right, _Pass_fn(_Pred));
}
......
}
//std::forward_list
template <class _Ty, class _Alloc = allocator<_Ty>>
class forward_list { // singly linked list
private:
    using _Alty          = _Rebind_alloc_t<_Alloc, _Ty>;
    using _Alty_traits   = allocator_traits<_Alty>;
    using _Node          = _Flist_node<_Ty, typename _Alty_traits::void_pointer>;
    using _Alnode        = _Rebind_alloc_t<_Alloc, _Node>;
    using _Alnode_traits = allocator_traits<_Alnode>;
    using _Nodeptr       = typename _Alnode_traits::pointer;
......
}

三、例程

先看例子:

#include "list_t.h"
#include <list>
#include <forward_list>
#include <iostream>

void PrintList(const std::list<int>& l)
{
    std::cout << "cur list:" << std::endl;
    for (auto& au : l)
    {
        std::cout << "item value:" << au << std::endl;
    }
    std::cout << "print end!" << std::endl;
}
void TestCURD()
{
    std::list<int> l;
    std::list<int> l2;
    for (int num = 0; num < 10; num++)
    {
        l.emplace_back(num);
        l.emplace_front(num);

        l2.push_back(num + 30);
    }

    PrintList(l);

    l.insert(++l.begin(), 3,100);
    PrintList(l);

    int head = l.front();
    l.pop_front();
    int end = l.back();
    l.pop_back();

    PrintList(l);

    std::list<int>::reverse_iterator it;
    std::cout << "reverse list:" <<std::endl;
    for (it = l.rbegin(); it != l.rend(); it++)
    {
        std::cout << "item value:" << *it << std::endl;
    }

    std::cout << "reverse list size:"<<l.size() << std::endl;

    l.erase(++l.begin());
    l.remove(100);
    l.remove_if([](int n) {return n == 9; });

    PrintList(l);
    l.unique();
    PrintList(l);

    l.sort();
    l2.sort();
    l.merge(l2);
    PrintList(l);

    l.unique();
    PrintList(l);

    auto ait = std::find(l.begin(),l.end(),8);
    std::cout << "find item:" << *ait << std::endl;

}
void TestFList()
{
    std::forward_list<int> fl;
    for (int num = 0; num < 10; num++)
    {
        fl.emplace_front(num);
        fl.emplace_after(fl.before_begin(), 9 - num);
    }
    std::cout << "forward list print:"  <<std::endl;
    for (auto& au : fl)
    {
        std::cout << "forward list item:" << au << std::endl;
    }

    std::cout << "forward list size:" << fl.max_size() << " other:" << std::distance(fl.begin(),fl.end()) << std::endl;
}

int main()
{
    TestCURD();
    TestFList();
    return 0;
}

运行结果是:

item value:9
item value:8
item value:7
item value:6
item value:5
item value:4
item value:3
item value:2
item value:1
item value:0
item value:0
item value:1
......
cur list:
item value:0
item value:1
item value:1
item value:2
item value:2
item value:3
item value:3
item value:4
......
item value:38
item value:39
print end!
cur list:
item value:0
item value:1
item value:2
item value:3
item value:4
item value:5
item value:6
item value:7
......
print end!
......
forward list item:7
forward list item:2
forward list item:8
forward list item:1
forward list item:9
forward list item:0
forward list size:536870911 other:20

打印的代码略作删除,有兴趣可以直接跑一遍。

四、总结

在实际的开发中,对内存的处理常用的基本上就两大块,一块是需要一段比较大的平坦的空间,一块是需要很大但是可以不连续的空间,它们表现的形式前者可以是数组(Vector)和今天的List。只要掌握了这两块,基本上常见的基础的数据结构的操作,就算是掌握了。
链表的难度在于底层的实现和各种的实际增删改查的方法,但有了STL,等同于别人给你已经实现了,你只要会调用就行,在屏蔽了底层实现后,虽然让你开发变得相对容易,但确实也屏蔽了学习底层技术的相关技术细节,特别是STL的相关底层实现和传统的教学中的实现的不同,让初学者是大费周张,但实际你在掌握了模板等知识后回头再看这些代码,除了一些特殊的用法之处,基本和手撸的代码是一样的,就犹如你造了一个小木头拉车,那么即使再大,再功能多的,也不过是对技术的更多的合理的设计和应用。
如此而已!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值