stlソースコード04リストの分析

1つは、リストの概要です

  • リストを使用するための構文:https //blog.csdn.net/qq_41453285/article/details/105483054
  • 一般的に:循環二重リンクリスト
  • 特徴:
    • 最下層はリンクリストを使用して実装され、双方向の順次アクセスをサポートします
    • リスト内の任意の位置での挿入と削除は高速です
    • ランダムアクセスはサポートされていません。要素にアクセスするには、コンテナ全体をトラバースする必要があります
    • 他のコンテナと比較して、追加のメモリオーバーヘッドが大きい
  • 設計目的:コンテナを任意の位置ですばやく挿入および削除する
  • いつ使用するか:
    • コンテナは常に中央の要素を挿入または削除する必要があります
    • リストが削除または追加されても、リストのイテレータ、参照、およびポインタが無効になることはありません。
  • 他のコンテナとの比較:
ベクター 可変サイズ配列高速ランダムアクセスをサポートします。テールを超えた要素の挿入または削除は遅くなる可能性があります
そして Deque高速ランダムアクセスをサポートします。最初と最後の挿入/削除は高速です
リスト 二重リンクリスト双方向の順次アクセスのみをサポートします。リスト内の任意の位置での挿入と削除は高速です
forward_list 単一リンクリスト一方向の順次アクセスのみをサポートします。リンクリストの任意の位置での挿入および削除操作は高速です
アレイ 固定サイズの配列高速ランダムアクセスをサポートします。要素を追加または削除できません
ストリング vector似ていますが、文字の格納専用のコンテナです。ランダムアクセスは高速です。テールでの高速挿入または削除

2.リストノード(__list_node)

  • リストの各ノードは構造体です。リストのノード構造は次のとおりです。

ソースコード 

template <class T>

struct __list_node {

    typedef void* void_pointer;

    void_pointer prev; //类型为void*。其实可设为__list_node<T>*

    void_pointer next;

    T data;

};

 

  • 下の図は、構造がどのように見えるかです。

3、リストのイテレータ

  • リストは、ノードがストレージスペースに継続的に存在することが保証されいないため、ベクターなどのイテレーターとしてネイティブインジケーターを使用できなくなりました。
  • リストイテレータは、リストのノードをポイントでき、正しいインクリメント、デクリメント、値、メンバーアクセス、およびその他のアクションを実行できる必要がありますいわゆる「リストイテレータの正しいインクリメント、デクリメント、値、およびメンバーアクセス」アクションとは、インクリメント時に次のノードをポイントし、デクリメント時に前のノードをポイントし、ノードのデータ値を取得することを指します。次の図に示すように、値が取得され、メンバーが取得されると、ノードのメンバーが取得されます。

  • リストは二重リンクリスト(二重リンクリスト)であるため、イテレータには前後に移動する機能が必要です。したがって、リストは双方向イテレータを提供します
  • リストには、次の重要なプロパティがあります。
    • 挿入もスプライスも、元のリストイテレータが失敗する原因にはなりませんこれはベクターには当てはまりません。ベクターの挿入アクションによってメモリが再構成され、元のすべてのイテレータが失敗する可能性があるためです。
    • リスト要素の削除アクション(消去)でも、「削除された要素を指す」イテレータのみが無効であり、他のイテレータはまったく影響を受けません。

イテレータのソースコード

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;

    }

};

 

第四に、リストのデータ構造

  • このリストは、双方向シリアルであるだけでなく、循環二重リンクリストでもありますしたがって、リンクリスト全体を完全に表すために必要なのはポインタだけです。

link_type、ノードノード

  • node nodeは、リストの最後のノードへのポインターです
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; // 只要一个指针,便可表示整个环状双向链表

    ...

};

 

begin()やend()などの関数

  • 次の図に示すように、インジケーターノードが意図的に最後に配置された空白ノードを指すようにすると、ノードは「前に閉じて開く」間隔のSTLの要件を満たし、最後のイテレーターになることができます。このように、それはいくつかの機能で簡単に完了することができます:
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()); }

5.リストの構築とメモリ管理(コンストラクター、push_back、挿入)

リストメモリ管理(list_node_allocator)

  • Listは、デフォルトallocをスペースコンフィギュレーターとして使用し、それに応じてlist_node_allocatorを追加で定義して、ノードサイズを構成単位としてより便利に使用できるようにします。
template <class T, class Alloc = alloc> //默认使用alloc为配置器

class list {

protected:

    typedef __list_node<T> list_node;

    // 专属之空间配置器,每次配置一个节点大小:

    typedef simple_alloc<list_node, Alloc> list_node_allocator;

    ...

};
  • したがって、list_node_allocator(n)は、n個のノードスペースを構成することを意味します。次の4つの関数は、ノードの構成、解放、構築、および破棄に使用されます。
protected:

    // 配置一个节点并传回

    link_type get_node() { return list_node_allocator::allocate(); }


    // 释放一个节点

    voidput_node(link_typep){list_node_allocator::deallocate(p);}


    // 产生(配置并构造)一个节点,带有元素值

    link_type create_node(const T& x) {

        link_type p = get_node();

        construct(&p->data, x);//全局函数,构造/析构基本工具

        return p;

    }


    // 摧毁(解构并释放)一个节点

    void destroy_node(link_type p) {

        destroy(&p->data); //全局函数,构造/析构基本工具

        put_node(p);

    }

 

コンストラクタ

  • リストには多くのコンストラクターがあり、そのうちの1つはデフォルトのコンストラクターです。これにより、パラメーターを指定せずに空のリストを作成できます。
public:

    list() { empty_initialize(); } //产生一个空链表

protected:

    void empty_initialize() {

        node = get_node(); //配置一个节点空间,令node指向它

        node->next = node; //令node头尾都指向自己,不设元素值

        node->prev = node;

    }
  • リストが空の場合、ノードの前と次はそれ自体を指します

push_back、insert

  • push_back()を使用してリストの最後に新しい要素挿入すると、この関数は内部でinsert()を呼び出します。
void push_back(const T& x) { insert(end(), x); }
  • insert()はオーバーロードされた関数であり、多くの形式があり、その最も単純なものは次のとおりであり、上記の要件を満たしています。最初にノードを構成して構築し、最後に適切なポインターアクションを実行して、新しいノードを挿入します。
//函数目的:在迭代器 position 所指位置插入一个节点,内容为x

iterator insert(iterator position, const T& x) {

    link_type tmp = create_node(x);//产生一个节点(设妥内容为x)

    //调整双向指针,使 tmp插入进去

    tmp->next = position.node;

    tmp->prev = position.node->prev;

    (link_type(position.node->prev))->next = tmp;

    position.node->prev = tmp;

    return tmp;

}
  • したがって、プログラムが5つのノード(値は0、1、2、3、4)を連続して挿入すると、リストの状態は次の図のようになります。

  • リストのどこかに新しいノードを挿入する場合は、最初に配置位置を決定する必要があります。たとえば、データ値が3のノードにデータ値が99のノードを挿入する場合は、次のことができます。この:
ilite = find(il.begin(), il.end(), 3);

if (ilite!=0)

il.insert(ilite, 99);
  • find()操作については後で説明します。挿入後のリストの状態を下図に示します。挿入が完了すると、新しいノードはPacesetter Iteratorが指すノードの前に配置されることに注意してください (挿入ポイントをマークします)。これは「挿入アクション」のSTL標準仕様ですリストはベクトルのようなものではないため、スペースが不足しているときに再構成およびデータ移動操作を実行できます。したがって、挿入前のすべてのイテレーターは、挿入後も有効です。

6、リストの要素操作

push_front、push_back

//插入一个节点,做为头节点

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


//插入一个个节点,做为尾节点

void push_back(const T& x) { insert(end(), x); }

 

消去

//移除迭代器position所指节点

iterator erase(iterator position) {

    link_type next_node = link_type(position.node->next);

    link_type prev_node = link_type(position.node->prev);

    prev_node->next = next_node;

    next_node->prev = prev_node;

    destroy_node(position.node);

    return iterator(next_node);

}
  •  リストは双方向の循環リンクリストであるため、限界条件を適切に処理し、ヘッドまたはテールに要素を挿入する(push_frontおよびpush_back)限り、アクションはほぼ同じです。ヘッドまたはテールの要素を削除します(pop_front)。およびpop_back))、アクションはほとんど同じです。イテレータが指す要素を消去(消去)することは、ポインタを動かすだけで、複雑ではありません。上の画像を再度検索して削除すると、下の画像のようになります。
ite = find(ilist.begin(), ilist.end(), 1);

if (ite!=0)

    cout << *(ilist.erase(ite)) << endl;

 

pop_front、pop_back 

//移除头节点

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


//移除尾节点

void pop_back() {

    iterator tmp = end();

    erase(--tmp);

}

 

晴れ 

// 清除所有节点(整个链表)

template <class T, class Alloc>

void list<T, Alloc>::clear()

{

    link_type cur = (link_type) node->next; //begin()

    while (cur != node) { //遍历每一个节点

        link_type tmp = cur;

        cur = (link_type) cur->next;

        destroy_node(tmp); // 销毁(析构并释放)一个节点

    }


    //恢复node原始状态

    node->next = node;
    
    node->prev = node;

}

 

削除する 

//将数值为value的所有元素移除

template <class T, class Alloc>

void list<T, Alloc>::remove(const T& value) {

    iterator first = begin();

    iterator last = end();

    while (first != last) { //遍历每一个节点

        iterator next = first;

        ++next;

        if (*first == value)

            erase(first); //找到就移除

        first = next;

    }

}

 

ユニーク 

//移除数值相同的连续元素。注意,只有“连续而相同的元素”,才会被移除剩一个

template <class T, class Alloc>

void list<T, Alloc>::unique()

{

    iterator first = begin();

    iterator last = end();

    if (first == last)

        return; //空链表,什么都不必做

    iterator next = first;

    while (++next != last) { //遍历每一个节点

        if (*first == *next) //如果在此区段中有相同的元素

            erase(next); //移除之

        else

            first = next; //调整指针

    next = first; //修正区段范围

    }

}

 

転送

  • このリストは、要素の連続範囲を特定の位置に移動する前に、いわゆる転送アクション(転送)を提供します。技術的に非常に単純で、ノード間でポインタを移動するだけです
  • このアクションは、スプライス、ソート、マージなどの他の複雑なアクションの優れた基盤となります。
  • 以下は転送のソースコードです。転送はパブリックインターフェイスではありません。
protected:

//将[first,last)内的所有元素搬移到position之前

void transfer(iterator position, iterator first, iterator last) {

    if (position != last) {

        (*(link_type((*last.node).prev))).next = position.node; // (1)

        (*(link_type((*first.node).prev))).next = last.node; // (2)

        (*(link_type((*position.node).prev))).next = first.node; // (3)

        link_type tmp = link_type((*position.node).prev); // (4)

        (*position.node).prev = (*last.node).prev; // (5)

        (*last.node).prev = (*first.node).prev; // (6)

        (*first.node).prev = tmp; // (7)

    }
    
}
  • 上記の7つのアクションを次の図に示します。

スプライス

  • 上記の転送はパブリックインターフェイスではありません。リストが公に提供するのは、いわゆるスプライスアクションです。特定のポイントで要素の連続範囲をあるリストから別の(または同じ)リストに移動します。
  • 以下はデモンストレーションケースです。
int iv[5] = { 5,6,7,8,9 };

list<int> ilist2(iv, iv+5);


//假设ilist的内容为0 2 99 3 4

ite = find(ilist.begin(), ilist.end(), 99);


ilist.splice(ite,ilist2); // 0 2 5 6 7 8 9 99 3 4

ilist.reverse(); // 4 3 99 9 8 7 6 5 2 0

ilist.sort(); // 0 2 3 4 5 6 7 8 9 99
  • 効果がわかりやすいです。次の図は、スプライシングアクションを示しています。テクノロジーは非常にシンプルで、ノード間のポインターの移動だけで、これらのアクションは転送によって完全に実行されています()

  • さまざまなインターフェースの柔軟性を提供するために、list :: spliceには多くのバージョンがあります。
public:

//将x接合于position所指位置之前。x必须不同于*this

void splice(iterator position, list& x) {

    if (!x.empty())

        transfer(position, x.begin(), x.end());

}


//将i所指元素接合于position所指位置之前。position和i可指向同一个list

void splice(iterator position, list&, iterator i) {

    iterator j = i;

    ++j;

    if (position == i || position == j)
        return;

    transfer(position, i, j);

}


//将[first,last) 内的所有元素接合于 position 所指位置之前

//position 和[first,last)可指向同一个list,

//但position不能位于[first,last)之内

void splice(iterator position, list&, iterator first, iterator last) {

    if (first != last)

        transfer(position, first, last);

}

 

  • 以下は、merge()、reverse()、sort()のソースコードです。transfer()があれば、これらのアクションを完了するのは難しくありません。

マージ()

// merge()将x合并到*this身上。两个lists的内容都必须先递增排序

template <class T, class Alloc>

void list<T, Alloc>::merge(list<T, Alloc>& x) {

    iterator first1 = begin();

    iterator last1 = end();

    iterator first2 = x.begin();

    iterator last2 = x.end();


    // 注意:前提是,两个lists都已经经过递增排序

    while (first1 != last1 && first2 != last2)

        if (*first2 < *first1) {

            iterator next = first2; transfer(first1,

            first2, ++next);

            first2 = next;

        }

        else

            ++first1;

    if (first2 != last2)

        transfer(last1, first2, last2);

}

逆行する() 

//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);

    }

}

ソート()

//list不能使用STL算法sort(),必须使用自己的sort() member function,

//因为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]);

 

おすすめ

転載: blog.csdn.net/www_dong/article/details/113845160