深入理解vector 【C++】

                               

一、vector的介绍:

        1.vector是表示可变大小的顺序容器。

        2.就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。

        3.本质讲,vector使用动态分配数组来存储它的元素。当空间满了并且插入新元素的时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。

        4.vector分配空间策略:我们一般会按照两倍进行扩容来避免频繁扩容。

        5.vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增 长。

        6.与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末 尾添加和删除元素相对高效。对于其他不在末尾的删除和插入操作,效率更低。比起list和forward_list 统一的迭代器和引用更好。

总结:它底层实际上和顺序表差不多 可以使用下标来访问 为了提高效率扩容一般是按照两倍扩容

二、vector的定义:

方法一: 构造一个某类型的空容器

        vector<int> v1; // 构造一个int类型的空容器

方法二:构造一个含有n个val值的容器

        vector<int> v2(10, 2); // 构造一个int类型的容器 初始化102

方式三:拷贝构造

        vector<int> v3(v2);

方式四:使用迭代器拷贝

        vector<int> v4(v2.begin(), v2.end()); //迭代器拷贝构造

注:

1.使用别的容器的迭代器也可以拷贝(不是vector 以string为例)

        string s1("hello world");
        vector<char> v5(s1.begin(), s1.end()); // 使用string类的迭代器构造

 2.vector也可以嵌套自己,vector<vector<int>>相当于二维数组

        第一个vector中的指针指向了一块存放vector<int>类型的空间,而之后的每一个vector中的指针又指向存放int类型的空间。(就相当于二维数组,只不过这个是动态的并且有成员函数

三、空间相关

3.1 size

size_type就是size_t

   返回容器中占用的元素个数 

#include <iostream>
#include <vector>

int main()
{
	std::vector<int> myints;
	std::cout << "0. size: " << myints.size() << '\n';

	for (int i = 0; i < 10; i++) myints.push_back(i);
	std::cout << "1. size: " << myints.size() << '\n';

	myints.insert(myints.end(), 10, 100);   //从最后的位置开始插入10个100
	std::cout << "2. size: " << myints.size() << '\n';

	myints.pop_back();
	std::cout << "3. size: " << myints.size() << '\n';

	return 0;
}

   

3.2 capacity

size_type就是size_t

#include <iostream>
#include <vector>

int main()
{
	std::vector<int> myvector;

	// set some content in the vector:
	for (int i = 1; i <= 100; i++)
	{
		myvector.push_back(i);
		if (i % 10 == 0)
		{
			std::cout << "size: " << (int)myvector.size() << '\n';
			std::cout << "capacity: " << (int)myvector.capacity() << '\n';
		}
	}
	return 0;
}

 

        VS保持一贯的作风,先有一段空间存放数据,超了之后按1.5倍扩容。

3.3 empty

        判断vector是否为空(size是否为0)  为空就返回true      不为空返回false

3.4 resize

        resize函数可以改变容器的有效元素个数

                   value_type就是模板类型T ,不传的话,就会去调用对应的 T() 构造函数

使用规则是这样子的

  1. 假设所给值小于当前容器size值 size缩小至所给值
  2. 假设所给值大于当前容器size值并且小于capacity值 size扩大至所给值 扩大的范围赋值(调用对应的构造函数
  3.  假设所给值大于当前容器size值并且大于capacity值 先扩容 后面操作和2一样

 调用对应的构造函数:

    C++规定:内置类型也有对应的构造函数

        int -->  0

        double -->0.0

        int*  -->nullptr

    对应自定义类型,调用对应的构造函数 

// resizing vector
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> myvector;

    // set some initial content:
    for (int i = 1; i < 10; i++) myvector.push_back(i);

    myvector.resize(5);
    myvector.resize(8, 100);
    myvector.resize(12);

    std::cout << "myvector contains:";
    for (int i = 0; i < myvector.size(); i++)
        std::cout << ' ' << myvector[i];
    std::cout << '\n';

    return 0;
}

3.5 reserve

 

  使用规则如下

1.假设所给值小于当前容量值 什么都不做

2 假设所给值大于当前容量值 扩容至所给值

// vector::reserve
#include <iostream>
#include <vector>

int main()
{
    std::vector<int>::size_type sz;//std全局域 vector<int>类 typedef的size_type  就是 size_t
    std::vector<int> foo;
    sz = foo.capacity();
    std::cout << "making foo grow:\n";
    for (int i = 0; i < 100; ++i) {
        foo.push_back(i);
        if (sz != foo.capacity()) {
            sz = foo.capacity();
            std::cout << "capacity changed: " << sz << '\n';
        }
    }

    std::vector<int> bar;
    sz = bar.capacity();
    bar.reserve(100);   // this is the only difference with foo above
    std::cout << "making bar grow:\n";
    for (int i = 0; i < 100; ++i) {
        bar.push_back(i);
        if (sz != bar.capacity()) {
            sz = bar.capacity();
            std::cout << "capacity changed: " << sz << '\n';
        }
    }
    return 0;
}

reserve会先把空间括好,你想要括多少,就传参。 

四、迭代器相关

在前面的string类里面已经讲过迭代器了。这里我就贴一下代码就过了。后面的insert和erase迭代器失效才是重头戏!

// vector::reserve
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> v1; // 构造一个int类型的容器
    for (int i = 0; i < 10; i++)
    {
        v1.push_back(i);
    }

    //正向迭代器
    vector<int>::iterator it = v1.begin();
    while (it != v1.end())
    {
        cout << *it << " ";
        it++;
    }

    cout << endl;

    //反向迭代器
    vector<int>::reverse_iterator rit = v1.rbegin();
    while (rit != v1.rend())
    {
        cout << *rit << " ";
        rit++;
    }
}

 

五、访问数据 

 

5.1 operator[] 对[]运算符进行了重载,一个通过下标来访问元素。

5.2 at和operator[] 一样,通过下标来访问。

5.3 front 访问第一个元素

5.4 back 访问最后一个元素(v.size()-1的下标的元素)

5. data 用于返回指向vector中第一个元素的指针

// vector::data
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> myvector(5);

    int* p = myvector.data();

    *p = 10;
    ++p;
    *p = 20;
    p[2] = 100;

    std::cout << "myvector contains:";
    for (unsigned i = 0; i < myvector.size(); ++i)
        std::cout << ' ' << myvector[i];
    std::cout << '\n';

    return 0;
}

 可以通过p指针来访问vector类中的元素

六、增删查改 

增:

5.1 push_back  向尾部插入一个数据

vector<int> v1;

v1.push_back(1);

v1.push_back(2);

v1.push_back(3); 

就存放了1 2 3三个数据到v1对象中

5.2 insert 指定位置插入数据

 第一种:   (返回值是iterator,在之后讲迭代器失效会提到)

        选择一个位置(迭代器),选择要插入的数据。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> v1; // 创造一个空容器
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    v1.insert(v1.begin(), 100); // 我们再开头插入一个元素100
    vector<int>::iterator it = v1.begin();
    while (it != v1.end())
    {
        cout << *it << " ";
        it++;
    }
	return 0;
}

第二种:

        选择一个位置(迭代器),选择要插入的个数,选择要插入的数据。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> v1; // 创造一个空容器
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    v1.insert(v1.begin(), 10, 5); // 我们头插100个5
    vector<int>::iterator it = v1.begin();
    while (it != v1.end())
    {
        cout << *it << " ";
        it++;
    }
	return 0;
}

 

删:

5.2 pop_back  从尾部删除一个数据

        

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> v1; // 创造一个空容器
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    for (auto u : v1)
    {
        cout << u << ' ';
    }
    cout << endl;

    v1.pop_back();
    v1.pop_back();
    v1.pop_back();
    for (auto u : v1)
    {
        cout << u << ' ';
    }
    cout << endl;

	return 0;
}

5.3 erase 指定位置删除数据

注意这里的返回值是iterator,在之后讲迭代器失效的时候会提到。 

第一种:删除一个指定位置的元素

第二种:删除一段区间的元素

// erasing from vector
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> myvector;

  // set some values (from 1 to 10)
  for (int i=1; i<=10; i++) myvector.push_back(i);

  // erase the 6th element
  myvector.erase (myvector.begin()+5);

  std::cout << "myvector contains:";
  for (unsigned i=0; i<myvector.size(); ++i)
    std::cout << ' ' << myvector[i];
  std::cout << '\n';

  // erase the first 3 elements:
  myvector.erase (myvector.begin(),myvector.begin()+3);

  std::cout << "myvector contains:";
  for (unsigned i=0; i<myvector.size(); ++i)
    std::cout << ' ' << myvector[i];
  std::cout << '\n';

  return 0;
}

查:

5.4   find

 find函数可以找到容器中我们要寻找的值 并且返回迭代器

 find函数有三个参数 迭代器左区间 迭代器右区间 要查找元素 (左闭右开)适用于所有的迭代器。

 为什么string类里面要自己设计一个find函数呢?因为要满足对各种字符串查找的需求。 

        注意!!!!!!

        这里的find函数的头文件是 algorithm !!!!!!!!!

        一定要记住! 不是容器的默认函数

#include <iostream>
#include <vector>
#include<algorithm>//为了能够使用find函数
using namespace std;
int main()
{
    vector<int> v1; // 创造一个空容器
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    // v1.erase(v1.begin(), v1.begin()+2);
    vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);  // 找到3
    v1.erase(pos); // 删除3
    vector<int>::iterator it = v1.begin();
    while (it != v1.end())
    {
        cout << *it << " ";
        it++;
    }
	return 0;
}

 

改:

operator=

// vector assignment
#include <iostream>
#include <vector>
using namespace std;
int main()
{
	std::vector<int> foo(3, 1);
	std::vector<int> bar(5, 2);

	bar = foo;
	foo = std::vector<int>();

    //打印foo中的元素
	for (auto u : foo)
	{
		cout << u << " ";
	}
	cout << endl;
	std::cout << "Size of foo: " << int(foo.size()) << '\n';


    //打印bar中的元素
	for (auto u : bar)
	{
		cout << u << " ";
	}
	cout << endl;
	std::cout << "Size of bar: " << int(bar.size()) << '\n';
	return 0;
}

 这个运算符会改变size大小和内部的变量

七、迭代器失效: 

在使用insert和earse的时候,会出现迭代器失效的问题。

 7.1  实例1:
#include <iostream>
#include <vector>
using namespace std;
void test_1()
{
    vector<int> vec;
    vec.push_back(0);
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
    vec.push_back(4);

    cout << vec.size() << ":" << vec.capacity() << endl;
    vector<int>::iterator it = vec.begin();
    while (it != vec.end())
    {
        if (*it % 2 == 0)
        {
            vec.insert(it, 5);   //这里会出现错误
            ++it;
        }
        ++it;
    }
    cout << vec.size() << ":" << vec.capacity() << endl;
    for (auto e : vec)
    {
        cout << e << " ";
    }
    cout << endl;
}
int main()
{
    test_1();
    return 0;
}

分析:

vec.insert(it, 5);   //这里会出现错误   当你把it传进去的时候,是传值传进去的,

它在insert内部会进行扩容(异地扩容)操作。内部的pos指针还指向原地,就不再准确了。

就会造成野指针问题。

而如果传引用传递的话,会让其他一些函数不能使用。

原因:insert之后vector会分配新的空间,旧迭代器便会失效。

解决方法:取insert函数的返回值为新的迭代器。

 用图片来帮助理解:

      

         

改法:(接收新的it作为返回值)

while (it != vec.end())
{
    if (*it % 2 == 0)
    {
        it=vec.insert(it, 5);//这样子写是错误的,应该怎么改?
        it++;
    }
    ++it;
    
}

7.2   实例2:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> v1; // 创造一个空容器
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    v1.push_back(5);
    v1.push_back(6);
    vector<int>::iterator it = find(v1.begin(), v1.end(), 2); // 意思是我们要删除2
    v1.erase(v1.begin()); //删除头元素
    v1.erase(it); //删除元素2
    return 0;
}

调试看看:

 咦?你看到了没有,it指针指向的位置还是那个位置,但是里面的值却改变了。

删除头元素,就是把数据向前挪动,而it指针指向不变。导致后面出问题了。

解决方法:同样在使用完erase之后,拿it去接收erase函数的返回值

7.3  总结(重点)

1.     insert和erase都会导致迭代器失效,我们都需要用it来接收新的迭代器。

        it=insert(it,5);  //在it位置插入元素5,拿it来接收新迭代器

        it=erase(it);//删除it位置的元素,拿it来接收新迭代器

2.   insert的返回值的it -->指向新插入的元素

     erase的返回值的it  -->指向删除元素的下一个元素


 本篇内容到此结束!感谢大家阅读!!!

  • 28
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值