zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    HDU 1124 Factorial
    hdu 1690 Bus System
    hdu 1113 Word Amalgamation
    POJ 2482 Stars in Your Window
    hdu 1385 ZOJ 1456 Minimum Transport Cost(经典floyd)
    hdu 1907 John
    VMware 虚拟机 安装 UBuntu 9.10 命令模式转换成窗口模试
    #pragma CODE_SEG __NEAR_SEG NON_BANKED详解
    Ubuntu 下Hadoop 伪分布式 hadoop0.20.2.tar.gz 的安装
    文件拷贝代码以及疑问
  • 原文地址:https://www.cnblogs.com/yao2yaoblog/p/6708105.html
Copyright © 2011-2022 走看看