• STL vector简单理解


    最近在知乎上看到一个回答:

    https://www.zhihu.com/question/49995099

    怎样读《二十四史》。重点在于苏轼有个读书的方法,“每书数过,一意求之”。读书要带着目的读,有所取舍,反复多次,才得到的多。

    我一想,写代码看这些技术书也是啊。一次只看一个侧重点,反复多次。而不是每次都从头看,这样效率低下,学不到东西。

     

    工作中经常使用vector,具体细节并未了解。通常情况下,如果长度固定的数据结构会使用数组,长度不定则使用vector。

    最近又翻看了《STL源码剖析》,发现以前看不懂的地方可以看懂了。这次看源码剖析,就看vector。

     

    首先需要有一个整体的概念。所谓vector,是一个动态的数组。使用的时候不需要像数组一样关心长度(但是同样有越界)。

    使用[]操作符,同样需要考虑越界。不需要关心长度指的是往里面增加元素时,push_back时vector会动态增加。

     

    数组和vector都是连续的内存空间。区别在于数组配置了就不能改变,需要变大,需要另外申请空间,把数据从原来的地方搬到新的地方。

    这些工作需要用户(程序员)自己做。而vector不需要,它封装了这些操作。vector有很多实现版本。核心难点在于空间满载时,扩充空间时的三个操作,

    申请新空间--数据迁移--释放旧空间

    简单看一下vector有哪些内容:

    // simple vector
    template <class T, class Alloc = alloc>
    class YVector
    {
    public:
        typedef T value_type;
        typedef value_type* pointer;
        typedef value_type* iterator;
        typedef value_type& reference;
        typedef size_t size_type;
        typedef ptrdiff_t difference_type;
    
        iterator begin() { return start; }
        iterator end() { return finish; }
        size_type size() const { return size_type(end() - begin()); }
        size_type capacity() const { return size_type(end_of_storage - begin()); }
        bool empty() { return begin() == end(); }
        reference operator[](size_type n) { return *(begin() + n); }
    
        reference front() { return *begin(); }
        reference back() { return *(end() - 1); }
        void push_back(const T& x);
        void pop_back();
        iterator erase(iterator first, iterator last);
        iterator erase(iterator position);
        void resize(size_type n, const T& x);
        void resize(size_type n);
        void clear() { erase(begin(), end());  }
    
        YVector() :start(0), finish(0), end_of_storage(0) {}
        YVector(size_type n, const T& value) { fill_initialize(n, value);  }
        YVector(int n, const T& value) { fill_initialize(n, value); }
        YVector(long n, const T& value) { fill_initialize(n, value); }
        explicit YVector(size_type n) { fill_initialize(n, T());  }
    
        ~YVector()
        {
            destroy(start, finish);
            deallocate();
        }
    
    protected:
        typedef simple_alloc<value_type, Alloc> data_allocator;
    
        iterator start;
        iterator finish;
        iterator end_of_storage;
    
        void insert_aux(iterator position, const T& x);
        void deallocate();
        void fill_initialize(size_type n, const T& value);
        void allocate_and_fill(size_type n, const T& value);
    };

    看到有很多关于alloc的,这其实是内存分配的问题,在源码剖析里第二章专门讲了这部分。由于目前阶段只需要会用vector就行了,不需要理解到太深。

    所有我想关于内存的地方,不深究实现代码,只大概明白做了什么就行。接下来就来具体看几个常用的,而且能看懂的部分。

    1.iterator

    typedef value_type* iterator;

    vector的迭代器是一个简单的指针。6哦朋友。迭代器是个复杂的东西。在源码剖析第三章详细介绍。

    为什么vector的迭代器是个简单的指针,因为vector迭代器需要的操作*,->,++,--,+=,-=,+,-这些普通的指针都具备。

    2.size和capacity

    vector的大小和容量是怎么界定的。如图:

    关注3个指针:

    iterator start;
    iterator finish;
    iterator end_of_storage;

    start是起始位置,finish是结束位置,end_of_storage是申请空间的结束位置。有了这3个指针之后,很好理解一些操作:

    iterator begin() { return start; }    //起始位置
    iterator end() { return finish; }      //结束位置
    size_type size() const { return size_type(end() - begin()); }    //可用大小
    size_type capacity() const { return size_type(end_of_storage - begin()); }    //申请的内存大小
    bool empty() { return begin() == end(); }    //是否为空
    reference operator[](size_type n) { return *(begin() + n); }    //[]操作
    reference front() { return *begin(); }        //首元素
    reference back() { return *(end() - 1); }    //尾元素

    size()其实是finish和start之间的大小,capacity是end_of_storage到start之间的大小。

    3.push_back

    写一段代码测试:

    #include <stdlib.h>
    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    void print_vec(vector<int> &vec)
    {
        cout << "elem : ";
        for (int i = 0; i < (int)vec.size(); ++i)
        {
            cout << vec[i] << " ";
        }
        cout << endl;
        cout << "size = " << vec.size() << endl;
        cout << "capacity = " << vec.capacity() << endl;
        cout << endl;
    }
    
    int main()
    {
        vector<int> vec(2, 1);
        print_vec(vec);
    
        vec.push_back(2);
        print_vec(vec);
    
        vec.push_back(2);
        print_vec(vec);
    
        vec.push_back(3);
        print_vec(vec);
    
        vec.push_back(4);
        print_vec(vec);
    
        vec.push_back(5);
        print_vec(vec);
    
        system("pause");
        return 0;
    }

    按书上说的需要扩充vector大小时,是capacity变成2倍大小。但是VS里面并不是

    而在ubuntu里面g++编译下:

    试过之后发现g++才是扩充2倍。VS是特殊版本的STL吧。

    template <class T, class Alloc = alloc>
    void YVector::push_back(const T& x)
    {
        if (finish != end_of_storage)
        {
            construct(finish, x);
            ++finish;
        }
        else
        {
            insert_aux(finish, x);
        }
    }

    有备用空间时,把最后一个元素构造成X,finsih指针调整。没有备用了,调用内部insert_aux函数。insert_aux暂时不深入了吧。有点复杂。


    Tips1:全局constuct函数。

    void construct( pointer p, const _Ty& value ) 
    {
         new(static_cast<void*>(p)) _Ty(value);
    }

    这是Vs里的版本。注意到是使用的placement new。c++里有3种new
    1.new operator。申请空间,调用构造函数。不能被重载。

    2.operator new。只申请空间,不调用构造函数。可重载。

    3.placement new。一个特殊的operator new,在一块已经分配好的内存上构造对象,返回这块内存的首地址。

    因为finish到end_of_storage之间是早已经申请好的空间,所以使用placement new。

    4.pop_back,erase,clear

    template <class T, class Alloc = alloc>
    void YVector::pop_back()
    {
        --finish;
        destroy(finish);
    }
    
    template <class T, class Alloc = alloc>
    iterator erase(iterator first, iterator last)
    {
        iterator i = copy(last, finish, first);
        destroy(i, finish);
        finish = finish - (last - first);
        return first;
    }
    
    template <class T, class Alloc = alloc>
    iterator erase(iterator position)
    {
        if (position + 1 != end())
        {
            copy(position + 1, finish, position);
        }
        --finish;
        destroy(finish);
        return position;
    }
    
    void clear() { erase(begin(), end());  }

    destroy()和copy()都是内存操作相关。destroy()相对于construct()。
    iterator copy(first, last, result),copy负责拷贝,即将迭代器区间[first,last)的元素复制到由复制目标result给定的区间[result,result+(last-first))中,返回一个已复制区间的最后一个位置。

    erase前和erase后的对比,如图:

    5.resize和reserve

    源码剖析里,好像没有特别讲。但是常用,容易弄错。写一段代码来理解怎么用

    void print_vec(vector<int> &vec)
    {
        cout << "size = " << vec.size() << endl;
        cout << "capacity = " << vec.capacity() << endl;
        cout << endl;
    }
    
    int main()
    {
        vector<int> vec;
        print_vec(vec);
    
        vec.resize(100);
        print_vec(vec);
    
        vec.reserve(200);
        print_vec(vec);
    
        system("pause");
        return 0;
    }

    可以看到,resize是改变size和capacity的大小,而reserve是改变capacity大小。

    引用:

    1.《STL源码剖析》

    2. http://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html

    3. http://blog.csdn.net/jerryjbiao/article/details/7376088

  • 相关阅读:
    利用Oracle分析函数row_number和sys_connect_by_path实现多行数据合并为一行
    public string err属性
    table冻结表头和列[转]
    转:Js日期操作
    ASP.NET中JSON的序列化和反序列化
    javascript Date format(js日期格式化) [转]
    刚开通的博客
    调用图片
    散记兼容,需要整理
    ie6、div高度低于字体大小、则继承fontsize
  • 原文地址:https://www.cnblogs.com/yao2yaoblog/p/6708105.html
Copyright © 2020-2023  润新知