【C++修炼之路】list 模拟实现

作者主页:@安 度 因
学习社区:StackFrame
专栏链接:C++修炼之路

文章目录

  • 一、读源码
  • 二、成员
  • 三、默认成员函数
    • 1、构造
    • 2、析构
    • 3、拷贝构造
    • 4、赋值重载
  • 四、迭代器
  • 五、其他接口

如果无聊的话,就来逛逛 我的博客栈 吧!

一、读源码

list 是双向带头循环链表,不了解这个结构可以去看我的双向链表。

list 不支持 [ ] ,因为地址不连续,list 的访问通过迭代器。

迭代器:

template<class T, class Ref, class Ptr>
struct __list_iterator {
  typedef __list_iterator<T, T&, T*>             iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;
  typedef __list_iterator<T, Ref, Ptr>           self;

  typedef bidirectional_iterator_tag iterator_category;
  typedef T value_type;
  typedef Ptr pointer;
  typedef Ref reference;
  typedef __list_node<T>* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

  link_type node;

  // ...
};

迭代器是自定义类型。

成员变量:

template <class T, class Alloc = alloc>
class list {
    // ...
protected:
  link_type node;  
}

查看 link_type 本质:

typedef list_node* link_type;
typedef __list_node<T> list_node;
// 链表节点
template <class T>
struct __list_node {
  typedef void* void_pointer;
  void_pointer next;
  void_pointer prev;
  T data;
};

实质上就是节点类型的指针。

创建节点:

link_type create_node(const T& x) {
    link_type p = get_node();
    __STL_TRY {
        construct(&p->data, x);
    }
    __STL_UNWIND(put_node(p));
    return p;
}

get_node:

link_type get_node() { return list_node_allocator::allocate(); } // 空间配置器开辟空间

construct 实际上就是定位 new 。

list 构造:

list() { empty_initialize(); }
void empty_initialize() { 
    node = get_node();
    node->next = node;
    node->prev = node;
}

list 构造没有调用定位 new ,因为哨兵位上面并不需要存放有效数据,所以只需要开辟空间,确定链接关系;普通节点上,存放自定义类型数据,若赋值空间上存在随机值,对空间进行释放可能会导致崩溃的现象,所以对于普通节点需要调用定位 new 进行构造。

二、成员

list_node 为节点,需要经常访问,开 struct :

template<class T>
    struct list_node
    {
        list_node<T>* _next;
        list_node<T>* _prev;
        T _val;
        
        list_node(const T& val = T())
            :_next(nullptr)
                ,_prev(nullptr)
                ,_val(val)
            {}
    };

list:

template<class T>
    class list
    {
        typedef list_node<T> Node; // 给类内部用的

        // ...
        private:
        Node* _head; // 哨兵位
        size_t _size;
    };

三、默认成员函数

1、构造

给哨兵位完成初始化:

void empty_init()
{
    _head = new Node;
    _head->_prev = _head;
    _head->_next = _head;

    _size = 0;
}

list()
{
    empty_init();
}

2、析构

~list()
{
    clear();

    delete _head;
    _head = nullptr;
}

// 清除除哨兵位的所有节点
void clear()
{
    iterator it = begin();

    while (it != end())
    {
        it = erase(it);
    }

    _size = 0;
}

3、拷贝构造

// list(const list& lt) // 类中类名可以充当类型
list(const list<T>& lt)
{
    // new(this)list; // 定位 new
    empty_init();

    // & 提高效率
    for (auto& e : lt)
    {
        push_back(e);
    }
}

可以使用定位 new 直接调用构造函数,也可以复用 empty_init 。赋值通过 push_back ,push_back 中会完成对自定义类型成员的深拷贝。

4、赋值重载

// void swap(list& lt)
void swap(list<T>& lt)
{
    std::swap(_head, lt._head);
    std::swap(_size, lt._size);
}

// list& operator=(const list& lt)
list<T>& operator=(list<T> lt)
{
    swap(lt);
    return *this;
}

调用拷贝构造,进行交换。

四、迭代器

list 的迭代器是自定义类型,不是原生指针Node*.

若:

typedef Node* iterator;

list<int>::iterator it = lt.begin();

while (it != lt.end())
{
    (*it) += 1;
    cout << *it << " ";
    ++it;
}

那么 *it 获得的是节点类,并不是节点中存储的值。

所以,迭代器为自定义类型,其中 *, ++ 等都是通过运算符重载来完成的。

通过封装自定义类型为迭代器,利用节点指针构造迭代器,用类封装类型,统一迭代器的使用方法,来获得统一的结果。

思考,普通迭代器的模板类型构成:

我们需要如下重载符号:*, 前置++,后置++,前置--,后置--,->, !=, ==,返回类型分别为:T&, 迭代器引用,迭代器拷贝,迭代器引用,迭代器拷贝,T*, 布尔值,布尔值

-> 模拟的行为:

struct A
{
    A(int a1 = 0, int a2 = 0)
        :_a1(a1)
            , _a2(a2)
        {}

    int _a1;
    int _a2;
};

void test_list2()
{
    list<A> lt;
    lt.push_back(A(1, 1));
    lt.push_back(A(2, 2));
    lt.push_back(A(3, 3));
    lt.push_back(A(4, 4));

    list<A>::iterator it = lt.begin();
    while (it != lt.end())
    {
        //cout << (*it)._a1 << " " << (*it)._a2 << endl; // 自定义类型成员访问成员变量
        cout << it->_a1 << " " << it->_a2 << endl; // 打印自定义类型的成员变量

        ++it;
    }
    cout << endl;
}

类比 const 迭代器,const 迭代器与普通迭代器的区别无非是 * 返回的值不能修改,-> 返回的 T 类型指针指向的成员变量不能修改

所以,我们完全可以将类模板写为:

template<class T, class Ref, class Ptr> // T 为任意类型,Ref 为 T& 为数据的引用,Ptr 为 T* 为返回的 T 类型的指针

迭代器根据节点来构造,通过运算符重载来完成统一行为,写出迭代器:

template<class T, class Ref, class Ptr>
	struct _list_iterator
	{
		typedef list_node<T> Node;
		typedef _list_iterator<T, Ref, Ptr> self; // 重命名避免繁琐,selft 就是迭代器本身
		Node* _node;

		_list_iterator(Node* node)
			:_node(node)
		{}

		// 返回引用的值
		Ref operator*()
		{
			return _node->_val;
		}
		
        // 编译器做了优化
        // Ptr 返回的是 T*
        // 调用时 it->_member
        // 那么此刻为 T*_member,原本为 T*->_member
        // 所以其实调用方应该写为 it->->_member,为了符合逻辑,编译器做了优化,只要写 it->_member
		Ptr operator->()
		{
			return &_node->_val;
		}

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		self operator++(int)
		{
			self tmp(*this); // 只需要浅拷贝,希望返回的就是节点对应的位置

			_node = _node->_next;

			return tmp;
		}

		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		self operator--(int)
		{
			self tmp(*this);

			_node = _node->_prev;

			return tmp;
		}
		
        // it != it.end()
        // it.end() 返回的是临时对象,为常量,所以要加 const 修饰
		bool operator!=(const self& it) const
		{
			return _node != it._node;
		}

		bool operator==(const self& it) const
		{
			return _node == it._node;
		}
	};

迭代器的拷贝只要浅拷贝,期望拿到的就是对应节点的 begin;迭代器没有析构,析构工作交给 list 统一处理,迭代器只需要完成它的本质工作:对节点的访问。

普通迭代器和 const 迭代器只需要在 list 中 typedef :

typedef _list_iterator< T, T&, T*> iterator;
typedef _list_iterator< T, const T&, const T*> const_iterator;

注:类中的自定义类型有两方面 - 1. typedef 的内嵌类型 2. 内部类。

iterator begin()
{
    return _head->_next; // 隐式类型转换
}

iterator end()
{
    return _head;
}

const_iterator begin() const
{
    return _head->_next;
}

const_iterator end() const
{
    return _head;
}

返回时,隐式类型转换,例如 begin,实际上是 iterator(_head->_next),单参数的构造函数支持隐式类型转换。

五、其他接口

void push_back(const T& val)
{
    /*Node* newnode = new Node(val);
			Node* tail = _head->_prev;

			newnode->_prev = tail;
			tail->_next = newnode;

			newnode->_next = _head;
			_head->_prev = newnode;*/

    insert(end(), val);
}

void push_front(const T& val)
{
    insert(begin(), val);
}

void pop_back()
{
    erase(--end());
}

void pop_front()
{
    erase(begin());
}

// pos 位置之前插入
iterator insert(iterator pos, const T& x)
{
    Node* newnode = new Node(x);
    Node* cur = pos._node; // 迭代器访问成员变量,获取 Node*
    Node* prev = cur->_prev;

    prev->_next = newnode;
    newnode->_prev = prev;

    newnode->_next = cur;
    cur->_prev = newnode;

    ++_size;

    return newnode; // 插入的位置
}

// 删除 pos 位置
iterator erase(iterator pos)
{
    assert(pos != end()); // 删除的不为头结点

    Node* cur = pos._node;
    Node* prev = cur->_prev;
    Node* next = cur->_next;

    prev->_next = next;
    next->_prev = prev;

    delete[] cur;
    --_size;

    return next; // 删除后的位置
}

size_t size()
{
    return _size;
}

你可能感兴趣的:(C++修炼之路,c++,list,windows)