C++ 学习系列 -- std::vector (未完待续)

一  std::vector 是什么?

   vector 是c++ 中一种序列式容器,与前面说的 array 类似,其内存分配是连续的,但是与 array 不同的地方在于,vector 在运行时是可以动态扩容的,此外 vector 提供了许多方便的操作,比如:插入、删除、查找、排序等。

std::vector - cppreference.com

二  std::vector 的特性与常见面试问题有哪些?

1  特性

1.1  vector 底层是基于分配连续空间的数组,因此 vector的访问既可以通过 容器的迭代器的方式,也可以通过数组的下标方式来访问元素

1.2 vector 可以在运行期间根据需要,进行动态的扩容,当已分配的空间被用满时,会再重新分配一块通常是原来空间两倍的内存,接下来依次将原来内存中的元素依次拷贝过来

1.3 基于底层的原理 ,其相关操作的时间复杂度如下:

       1)访问元素的时间复杂度 与数组相同,是 O(1) ;

       2) 在 vector末尾插入元素或者删除元素 O(1)

       3)   在 vector 中间某个位置插入或者删除一个元素,O(n)

2 常见面试问题

1. vector 的扩容机制 ?

  1.1  扩容倍数一般为 2 倍或者 1.5 倍
// main.cpp

#include
#include

int main()
{

   std::vector  vec2;
    for(int i=0; i<20; i++)
    {
        std::cout << "size: " << vec2.size() << ", capaticy: " << vec2.capacity() << std::endl;
        vec2.push_back(i);
    }

   return 0;
}


其实际存储的元素个数,与开辟空间可以存放的元素个数如下:

C++ 学习系列 -- std::vector (未完待续)_第1张图片

从输出结果可以看出,每当存放的元素将分配的空间耗尽以后,vector 就会再次申请两倍于当前内存大小的空间来使用

   1.2  扩容的具体过程

        当存放元素个数将 vector 分配的内存空间耗尽时,当再放入新元素时,会触发如下过程

  •  重新分配新的连续内存空间,大小一般是原来的 2 倍
  •  将原来内存空间的元素依次拷贝到新空间中, 此过程会触发拷贝构造函数
  •  调用存放在原内存空间中各元素的析构函数
  •  释放掉原空间的内存 

  我们可以通过如下代码的结果看到这个过程

#include
#include

class AAA
{
public:
    AAA(int i):index(i){
        std::cout << "constructor AAA index: " << index << std::endl;
    }
    ~AAA(){
        std::cout << "destructor AAA index: " << index << std::endl;
    }
    AAA(AAA& a):index(a.index){
        std::cout << "copy constructor AAA index: " << index << std::endl;
    }
    AAA(const AAA& a):index(a.index){
        std::cout << "copy constructor const AAA index: " << index << std::endl;
    }
    AAA(AAA&& a):index(a.index){
        std::cout << "move copy constructor AAA index: " << index << std::endl;
        a.index = 0;
    }
    AAA& operator=(AAA& a){
        this->index = a.index;
        std::cout << "equals AAA index: " << index << std::endl;
        return *this;
    }

public:
    int index;
};

int  main()
{
    std::vector vec2;
    for(int i=0; i<7; i++)
    {
        std::cout << "size: " << vec2.size() << ", capaticy: " << vec2.capacity() << std::endl;
        vec2.emplace_back(i);
    }
    return 0;
}

输出:

C++ 学习系列 -- std::vector (未完待续)_第2张图片

2.如何避免扩容导致的效率低下?

    2.1  可以在使用 vector 之前,预估好元素的个数,在使用 vector 之前,将内存空间分配好,而不是在一边插入一边分配新空间与拷贝旧空间的元素

   2.2  若是无法预估,可以在自定义类中实现高效的移动构造函数,然后禁用拷贝构造函数,这样vector 在扩容完毕后的拷贝元素阶段,会自动调用移动构造函数,具体如下:

(c++ 中声明移动构造函数后,会自动禁用拷贝构造函数)

#include
#include

class AAA
{
public:
    AAA(int i):index(i){
        std::cout << "constructor AAA index: " << index << std::endl;
    }
    ~AAA(){
        std::cout << "destructor AAA index: " << index << std::endl;
    }
    AAA(AAA&& a):index(a.index){
        std::cout << "move copy constructor AAA index: " << index << std::endl;
        a.index = 0;
    }
    AAA& operator=(AAA& a){
        this->index = a.index;
        std::cout << "equals AAA index: " << index << std::endl;
        return *this;
    }

public:
    int index;
};

int  main()
{
    std::vector vec2;
    for(int i=0; i<7; i++)
    {
        std::cout << "size: " << vec2.size() << ", capaticy: " << vec2.capacity() << std::endl;
        vec2.emplace_back(i);
    }
    return 0;
}

输出: C++ 学习系列 -- std::vector (未完待续)_第3张图片

3.为什么选择以1.5倍或者2倍方式进行扩容?而不是3倍4倍扩容?

    面试题:C++vector的动态扩容,为何是1.5倍或者是2倍_vector扩容_森明帮大于黑虎帮的博客-CSDN博客

4.vs为什么选择1.5倍,linux为什么选择2倍?

面试题:C++vector的动态扩容,为何是1.5倍或者是2倍_vector扩容_森明帮大于黑虎帮的博客-CSDN博客

三 std::vector 的使用

  1. vector 常见接口与操作

       std::vector - cppreference.com

     1.1  容器构造

构造函数 说明
vector(); 空构造函数

template

vector( size_type count,

                 const T& value);

构造 count 各 value 到vector 中
template< class InputIt >

vector( InputIt first, InputIt last,

        const Allocator& alloc = Allocator() );
利用迭代器构造 vector 
vector( const vector& other ); 拷贝构造函数
vector( vector&& other ); 移动构造函数

       使用例子:

          

#include
#include

void printVector(std::vector& vec)
{
    for(T& v:vec)
    {
        std::cout << v << " ";
    }
    std::cout << "" << std::endl;
}

void testVector()
{
    std::cout << "testVector --" << std::endl;

    // 1. 构造空 vector
    std::vector  vec1;
    for(int i=1;i<7;i++)
    {
        vec1.push_back(i);
    }
    std::cout << "------------ 1" << std::endl;
    printVector(vec1);

    // 2. 构造 count 个 value 到 vector 中
    std::vector vec2(6, 88);
    std::cout << "------------ 2" << std::endl;
    printVector(vec2);

    // 3. 利用迭代器构造 vector
    std::vector vec3(vec2.begin(), vec2.end());
    std::cout << "------------ 3" << std::endl;
    printVector(vec3);


    // 4. 拷贝构造函数
    std::vector vec4(vec1);
    std::cout << "------------ 4" << std::endl;
    printVector(vec4);

    // 5. 移动构造函数
    std::vector vec5(std::move(vec1));
    std::cout << "------------ 5" << std::endl;
    printVector(vec5);
}

int main(int argc, char *argv[])
{
   testVector();

   return 0;
}

输出:

  C++ 学习系列 -- std::vector (未完待续)_第4张图片

     1.2  容器访问

函数 说明
at(size_type pos) 返回位置 pos 上的元素引用
operator[size_type pos] 同上
front 返回 vector 上第一个元素的引用
back 返回 vector 上最后一个元素的引用
data 返回底层存储数组的指针

      示例代码:

#include
#include

void testVector2()
{
    std::cout << "testVector --" << std::endl;

    // 构造 vector
    std::vector  vec1;
    for(int i=1;i<7;i++)
    {
        vec1.push_back(i);
    }
    std::cout << "------------ 1" << std::endl;

    // 1. at
    int pos = 3;
    std::cout << " pos = 3, val = " << vec1.at(pos)<< std::endl;
    // 可以修改数据
    int& tmp1 = vec1.at(pos);
    tmp1 = 88;
    std::cout << " pos = 3, val = " << vec1.at(pos)<< std::endl;


    std::cout << "------------ 2" << std::endl;
    // 2. operator[]
    std::cout << " pos = 3, val = " << vec1[pos]<< std::endl;


    std::cout << "------------ 3" << std::endl;
    // 3. front
    std::cout << " front, val = " << vec1.front() << std::endl;

    std::cout << "------------ 4" << std::endl;
    // 4. back
    std::cout << " back, val = " << vec1.back()<< std::endl;

    std::cout << "------------ 5" << std::endl;
    // 5. data

    int*  tmp5 = vec1.data();

    for(int i=0; i<6;i++)
    {
        std::cout << "i = " << i <<", val = " <<*(tmp5 + i) << std::endl;
    }
}

int main()
{
    testVector2();

    return 0;
}

输出:

  C++ 学习系列 -- std::vector (未完待续)_第5张图片

    1.3  容器空间

函数 说明
empty() vector 是否为空
size() 返回存储的元素个数
reserve(size_type new_cap) 提升vector 容量
capacity() 返回vector分配的空间可以存放的元素个数

  示例如下

#include
#include

void testVector3()
{
    std::cout << "testVector --" << std::endl;

    std::vector vec1;
    std::cout << " empty: " << vec1.empty() << ", size: " << vec1.size() << std::endl;

    for(int i=0; i<5; i++)
    {
        std::cout << "size: " << vec1.size() << ", capaticy: " << vec1.capacity() << std::endl;
        vec1.emplace_back(i+1);
    }
    std::cout << " empty: " << vec1.empty() << ", size: " << vec1.size() << std::endl;

    vec1.reserve(8);
    std::cout << "size: " << vec1.size() << ", capaticy: " << vec1.capacity() << std::endl;
}

int main()
{
    testVector3();
    return 0;
}

输出:

C++ 学习系列 -- std::vector (未完待续)_第6张图片

1.4  容器修改

      

函数 说明
clear() 清空 vector 中的所有元素
insert 在位置 pos 前插入元素 value
emplace 与insert类似,不过该函数可以只传元素类的构造参数,实现原地构造,效率上比 insert 高一些,因为缺少了拷贝函数的调用
push_back 在 vector 的最后append 新的元素,若是append前,vector 的size与capacity相等,那么就会重新分配内存
emplace_back 与 push_back 类似,区别在于该函数可以只传元素类的构造参数,实现原地构造,效率上比 push_back 高一些,因为缺少了拷贝函数的调用
pop_back 将 vector 的最后一个元素移除

      示例如下:

         

#include
#include


void testVector4()
{
    std::cout << "testVector --" << std::endl;
    std::vector vec1;
    for(int i=0; i<5; i++)
    {
        vec1.emplace_back(i+1);
    }
    printVector(vec1);
    // 1. insert
    std::cout << "------------ 1" << std::endl;
    auto iter = std::find(vec1.begin(), vec1.end(), 3);
    vec1.insert(iter,666);
    printVector(vec1);

    // 2. emplace
    std::cout << "------------ 2" << std::endl;
    vec1.emplace(iter,888);

    printVector(vec1);

    // 3. push_back
    std::cout << "------------ 3" << std::endl;
    vec1.push_back(77);
    printVector(vec1);

    // 4. emplace_back
    std::cout << "------------ 4" << std::endl;
    vec1.emplace_back(778);

    printVector(vec1);

    // 5. pop_back
    std::cout << "------------ 5" << std::endl;
    vec1.pop_back();
    printVector(vec1);

    // 6. clear
    std::cout << "------------ 6" << std::endl;
    vec1.clear();
    std::cout << "empyt: " << vec1.empty() << std::endl;
}

int main()
{
    testVector4();
    return 0;
}

 输出:

 C++ 学习系列 -- std::vector (未完待续)_第7张图片

四  std::vector 的简单实现

  接下来我们实现自己简单的  vector 

你可能感兴趣的:(c++,学习,算法)