STL源码剖析阅读笔记四(序列容器list)

一、概述

  list和上一章的vector都是我们平时开发最常用到的容器。但与vector不一样的是,list并非一个绝对固定的连续空间的容器,之所以设计成这样还是考虑了连续空间删除插入的时间消耗。STL的list事实上是一个双向链表,了解链表概念的应该明白,这种前后可移动的链表结构提供了非常灵活的操作性。

二、定义

2.1 节点形式

  它的节点形式可以比较形象化地展示为下图的样子:
在这里插入图片描述

  而具体的代码也很简单:

template <class T> struct __list_node {
    typedef void* void_pointer;
    void_pointerprev; //型別為void*。其實可設為__list_node<T>* void_pointer next;
    T data;
};

2.2 迭代器

  这里过多地赘述迭代器的介绍,但不得不提的一点是:list的迭代器和vector是有比较大区别的,也就是list不能使用原生指针作为迭代器,而是需要另外设计结构的。
在这里插入图片描述

  它的结构很容易理解,也就是我们需要把拼接工作和抽离工作封装的足够简单即可。

template<class T, class Ref, class Ptr> 
struct __list_iterator {
	typedef __list_iterator<T, T&, T*> 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; // 迭代器內部當然要有一個原生指標,指向 list 的節點
   // constructor
   __list_iterator(link_type x) : node(x) {}
   __list_iterator() {}
   __list_iterator(const iterator& x) : node(x.node) {}
	bool operator==(const self& x) const { return node == x.node; } 
    bool operator!=(const self& x) const { return node != x.node; } 
    
    // 以下對迭代器取值(dereference),取的是節點的資料值。
	reference operator*() const { return (*node).data; }
	
    // 以下是迭代器的成員存取(member access)運算子的標準作法。
    pointer operator->() const { return &(operator*()); }
	
    // 對迭代器累加 1,就是前進一個節點 
    self& operator++()
        node = (link_type)((*node).next);
        return *this;
   	}
	self operator++(int) 
        self tmp = *this; 
		++*this;
		return tmp;
	}
	// 對迭代器遞減 1,就是後退一個節點 
	self& operator--()
    	node = (link_type)((*node).prev);
     	return *this;
   	}
	self operator--(int) self tmp = *this; 
	--*this;
	return tmp;
	} 
};

2.3 节点结构

  上面提到,STL的list是一个双向的链表结构。因此整个完整的链可以展示为以下代码:

template <class T, class Alloc = alloc> // 預設使用 alloc 為配置器 class list {
    protected:
    	typedef __list_node<T> list_node; 
	public:
       typedef list_node* link_type;
    protected:
    	link_type node; // 只要一個指標,便可表示整個環狀雙向串列
	... 
};

  试想我们需要一个不断向后添加节点的标记,那么我们整个操作其实都必须要依赖一个节点的指针去进行。link_type 就是对整个node指针换了个名。然后把所有的头和尾都标记起来,让我们用的时候可以直接找到头尾。

iterator begin() { return (link_type)((*node).next); }
iterator end() { return node; }
bool empty() const { return node->next == node; }
size_type size() const {
	size_type result = 0; 
    distance(begin(),end(),result); //全域函式,第3章。 
    return result;
}
// 取頭節點的內容(元素值)。
reference front() { return *begin(); } 

// 取尾節點的內容(元素值)。
reference back() { return *(--end()); }

  它前后取值的操作都可以形象地展示为下图:
在这里插入图片描述

三、操作中理解list

  最关键的操作就是基于transfer()这个函数来实现的:

// 將 [first,last) 內的所有元素搬移到 position 之前。
void transfer(iterator position, iterator first, iterator last) {
      if (position != last) {
	       (*(link_type((*last.node).prev))).next = position.node;
	       (*(link_type((*first.node).prev))).next = last.node;
	       (*(link_type((*position.node).prev))).next = first.node;
	       link_type tmp = link_type((*position.node).prev);
	       (*position.node).prev = (*last.node).prev;
	       (*last.node).prev = (*first.node).prev;
	       (*first.node).prev = tmp;
	  } 
}

  它的逻辑实际上是下图的形式实现的:
在这里插入图片描述

3.1 环形链表

  前面中提到list是一个双向链表,当我们不断地向后迭代,它会打印出什么情况呢?如列代码,我们放入4个节点后就不断地进行迭代器偏移:

#include <iostream>
#include <list>

int main() {
  std::list<int> l;
  l.push_back(1);
  l.push_back(2);
  l.push_back(3);
  l.push_back(4);

  auto iter = l.begin();
  for(int i=0; i<l.size()*2;i++) {
    std::cout << *iter << " ";
    iter++;
  }
  std::cout << std::endl;



  std::cout << "Hello, World!" << std::endl;
  return 0;
}

  得到的结果是:
在这里插入图片描述
  最后一个节点竟然被打印了两次,事实上是因为最后的一个节点后面还有一个标记结束的end节点,这个节点的内容和最后一个节点的值相同。

#include <iostream>
#include <list>

int main() {
  std::list<int> l;
  l.push_back(1);
  l.push_back(2);
  l.push_back(3);
  l.push_back(4);

  auto iter = l.begin();
  for(int i=0; i<=l.size()*2;i++) {
    std::cout << *iter << " ";
    iter++;
  }
  std::cout << std::endl;
  auto iter2 = l.end();
  std::cout << *iter2 << std::endl;
  l.push_back(5);
  auto iter3 = l.end();
  std::cout << *iter3 << std::endl;


  std::cout << "Hello, World!" << std::endl;
  return 0;
}

在这里插入图片描述

3.2 合并算法

#include <iostream>
#include <list>

int main() {
  std::list<int> l1;
  std::list<int> l2;
  l1.push_back(1);
  l1.push_back(2);
  l2.push_back(3);
  l2.push_back(4);


  l1.merge(l2);
  for(auto iter = l1.begin();iter!=l1.end();iter++) {
    std::cout << *iter << " ";
  }
  std::cout << std::endl;
  std::cout << "Hello, World!" << std::endl;
  return 0;
}

3.3 翻转算法

#include <iostream>
#include <list>

int main() {
  std::list<int> l;
  l.push_back(1);
  l.push_back(2);
  l.push_back(3);
  l.push_back(4);

  for(auto iter = l.begin();iter!=l.end();iter++) {
    std::cout << *iter << " ";
  }
  std::cout << std::endl;
  l.reverse();
  for(auto iter = l.begin();iter!=l.end();iter++) {
    std::cout << *iter << " ";
  }
  std::cout << std::endl;


  std::cout << "Hello, World!" << std::endl;
  return 0;
}

在这里插入图片描述
  它的源码实现为:

// reverse() 將 *this 的內容逆向重置 
template <class T, class Alloc> void 
list<T, Alloc>::reverse() {
// 以下判斷,如果是空白串列,或僅有一個元素,就不做任何動作。
// 使用 size() == 0 || size() == 1 來判斷,雖然也可以,但是比較慢。 
if (node->next == node || link_type(node->next)->next == node)
return;
  iterator first = begin();
  ++first;
  while (first != end()) {
   iterator old = first;
   ++first;
   transfer(begin(), old, first);
} }

  整体的逻辑还是比较简单的。

3.4 排序算法

#include <iostream>
#include <list>

int main() {
  std::list<int> l;
  l.push_back(1);
  l.push_back(3);
  l.push_back(2);
  l.push_back(4);
  l.sort();
  for(auto iter = l.begin();iter!=l.end();iter++) {
    std::cout << *iter << " ";
  }
  std::cout << std::endl;
  std::cout << "Hello, World!" << std::endl;
  return 0;
}

在这里插入图片描述

  实际在STL中使用的是快排来实现排序的,以上都使用了transfer的方法来进行。


//list不能使用STL演算法 sort(),必須使用自己的 sort()memberfunction, // 因為 STL 演算法 sort() 只接受 RamdonAccessIterator.
// 本函式採用 quick sort.
template <class T, class Alloc>
void list<T, Alloc>::sort() {
// 以下判斷,如果是空白串列,或僅有一個元素,就不做任何動作。
// 使用 size() == 0 || size() == 1 來判斷,雖然也可以,但是比較慢。 
if (node->next == node || link_type(node->next)->next == node)
	return;
// 一些新的 lists,做為中介資料存放區 list<T, Alloc> carry;
list<T, Alloc> counter[64];
int fill = 0;
  while (!empty()) {
   carry.splice(carry.begin(), *this, begin());
   int i = 0;
   while(i < fill && !counter[i].empty()) {
     counter[i].merge(carry);
     carry.swap(counter[i++]);
   }
   carry.swap(counter[i]);
   if (i == fill) ++fill;
  }
  for (int i = 1; i < fill; ++i)
    counter[i].merge(counter[i-1]);
  swap(counter[fill-1]);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值