第七章:vector类

系列文章目录



前言

vector是可变大小数组的类


vector的介绍及使用

vector的介绍

vector的文档介绍

在这里插入图片描述

  1. vector是表示可变大小数组的序列容器
  2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
  3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。**其做法是,分配一个新的数组,然后将全部元素移到这个数组。**就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
  5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
  6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好
  7. vector和string的区别:vector里面给char,虽然他们底层都是数组中存char但是还是不一样的,s对象中指向的空间结尾有’\0’,string与vector都有自己专用的需求(接口),所以功能是不一样的。

vector的使用

1. vector的定义

在这里插入图片描述
在这里插入图片描述

(constructor)构造函数声明 接口说明
vector()(重点) 无参构造
vector(size_type n, const value_type& val = value_type()) 构造并初始化n个val
vector (const vector& x); (重点) 拷贝构造
vector (InputIterator first, InputIterator last); 使用迭代器进行初始化构造

vector的构造代码演示

void test_vector1()
{
    
    
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	for (size_t i = 0; i < v.size(); i++)
	{
    
    
		cout << v[i] << " ";
	}
	cout << endl;

	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
    
    
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto ch : v)
	{
    
    
		cout << ch << " ";
	}
	cout << endl;
  
	vector<int> copy_v(v);
	for (auto ch : copy_v)
	{
    
    
		cout << ch << " ";
	}
	cout << endl;
}

int main()
{
    
    
	test_vector1();
	return 0;
}
void test_vector2()
{
    
    
	vector<int> v1(10, 1);
	for (auto e : v1)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	vector<int> v2(v1.begin(), v1.end());//迭代器区间左闭右开
	for (auto e : v2)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
  
	string s1("hello world");
	vector<int> s2(s1.begin(), s1.end());//可以传其他容器的迭代器
	for (auto e : s2)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}

2. vector iterator 的使用

除了string和vector,剩下的容器都用迭代器
在这里插入图片描述

iterator的使用 接口说明
begin +end(重点) 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置 的iterator/const_iterator
rbegin + rend 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

在这里插入图片描述

在这里插入图片描述

vectot的迭代器使用代码演示

void test_vector3()
{
    
    
	vector<int> v;
	//vector<int> ::reverse_iterator rit = v.rbegin();
	auto rit = v.rbegin();
	while (rit != v.rend())
	{
    
    
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

3. vector 空间增长问题

在这里插入图片描述

容量空间 接口说明
size 获取数据个数
capacity 获取容量大小
empty 判断是否为空
resize(重点) 改变vector的size
reserve (重点) 改变vector的capacity
  1. capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
  2. reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
  3. resize在开空间的同时还会进行初始化,影响size。
  4. 尽量不要缩容,C++不支持原地缩容,一般会重新开辟一块空间,再将数据拷贝过来,最后释放原空间。
void test_vector4()
{
    
    
	vector<char> v;
	cout << v.max_size() << endl;
}
void TestVectorExpand()
{
    
    
	size_t sz;
	vector<int> v;
	sz = v.capacity();
	cout << "making v grow:\n";
	for (int i = 0; i < 100; ++i)
	{
    
    
		v.push_back(i);
		if (sz != v.capacity())
		{
    
    
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}
//结果
making v grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141

//g++运行结果:linux下使用的STL基本是按照2倍方式扩容
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{
    
    
	vector<int> v;
	size_t sz = v.capacity();
	v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
	cout << "making bar grow:\n";
	for (int i = 0; i < 100; ++i)
	{
    
    
		v.push_back(i);
		if (sz != v.capacity())
		{
    
    
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

4. vector 增删查改

在这里插入图片描述

vector增删查改 接口说明
push_back(重点) 尾插
pop_back (重点) 尾删
find 查找。(注意这个是算法模块实现,不是vector的成员接口)
insert 在position之前插入val
erase 删除position位置的数据
swap 交换两个vector的数据空间
operator[] (重点) 像数组一样访问

vector插入和删除操作代码演示

在这里插入图片描述
在这里插入图片描述

5. vector 的其他接口

在这里插入图片描述

可以给所有容器用

void test_vector6()
{
    
    
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	vector<int>::iterator pos = find(v.begin(), v.end(), 3);

	if (pos != v.end())
	{
    
    
		v.insert(pos, 20);
	}

	for (auto ch : v)
	{
    
    
		cout << ch << " ";
	}
	cout << endl;

	pos = find(v.begin(), v.end(), 3);
    //迭代器失效要重新找
	v.erase(pos);

	for (auto ch : v)
	{
    
    
		cout << ch << " ";
	}
	cout << endl;
}

迭代器的区间是左闭右开的

6. vector 在OJ中的使用

1. 杨辉三角OJ

在这里插入图片描述

在这里插入图片描述

2. 电话号码字母组合OJ

vector深度剖析

内置类型构造函数

内置类型也是有构造函数的,用于实现区间构造

	template<class T>
	void f()
	{
    
    
		T x = T();//调用默认构造
		cout << x << endl;
	}

	void test_vector2()
	{
    
    
		//内置类型有构造函数
		int i = int();
		int j = int(1);
		
		//int* p = int* ();

		f<int>();
		f<int*>();
		f<double>();
	}
//[first, last)
template <class InputIterator>//允许类的成员函数再是函数模板
vector(InputIterator first, InputIterator last)
{
    
    
    while (first != last)
    {
    
    
        push_back(*first);
        ++first;
    }
}

迭代器失效问题

inert()

		void insert(iterator pos, const T& val)
		{
    
    
			assert(pos <= _finish);
			assert(pos >= _start);

			if (_finish == _end_of_storage)
			{
    
    //扩容
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				//迭代器pos失效
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
    
    
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			++_finish;

		}

在这里插入图片描述

		void insert(iterator pos, const T& val)
		{
    
    
			assert(pos <= _finish);
			assert(pos >= _start);

			if (_finish == _end_of_storage)
			{
    
    //扩容
				size_t = len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);

				//扩容后更新,解决pos失效问题,让pos指向新空间的元素地址
				pos = _start + len; 
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
    
    
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			++_finish;

		}
void insert(iterator pos, const T& val)
{
    
    //不能用引用来传入拷贝的临时对象,因为其具有常性
    assert(pos <= _finish);
    assert(pos >= _start);

    if (_finish == _end_of_storage)
    {
    
    
        size_t  len = pos - _start;
        reserve(capacity() == 0 ? 4 : capacity() * 2);
        pos = _start + len; 
    }

    iterator end = _finish - 1;
    while (end >= pos)
    {
    
    
        *(end + 1) = *end;
        --end;
    }
    *pos = val;
    ++_finish;
}	

void test_vector4()
{
    
    
  vector<int> v1;
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  v1.push_back(5);
  
  for (size_t i = 0; i < v1.size(); ++i)
  {
    
    
        cout << v1[i] << " ";
  }
  cout << endl;

  yyf::iterator pos = find(v1.begin(), v1.end(), 2);
  v1.insert(pos, 6);

  for (size_t i = 0; i < v1.size(); ++i)
  {
    
    
        cout << v1[i] << " ";
  }
  cout << endl;

  (*pos)++;//想让2+1,但是pos指向了新插入的元素6

  for (size_t i = 0; i < v1.size(); ++i)
  {
    
    
        cout << v1[i] << " ";
  }
  cout << endl;

}

在这里插入图片描述

iterator insert(iterator pos, const T& val)
{
    
    //不能用引用来传入拷贝的临时对象,因为其具有常性
    assert(pos <= _finish);
    assert(pos >= _start);

    if (_finish == _end_of_storage)
    {
    
    //扩容
        size_t  len = pos - _start;
        reserve(capacity() == 0 ? 4 : capacity() * 2);

        //扩容后更新,解决pos失效问题
        pos = _start + len; 
    }

    iterator end = _finish - 1;
    while (end >= pos)
    {
    
    
        *(end + 1) = *end;
        --end;
    }
    *pos = val;
    ++_finish;

    return pos;
}

但最好不要这要做,所以insert以后我们认为pos失效了,不能再使用

erase()

void test_vector5()
	{
    
    
		std::vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

		for (size_t i = 0; i < v1.size(); ++i)
		{
    
    
			cout << v1[i] << " ";
		}
		cout << endl;

		auto pos = find(v1.begin(), v1.end(), 2);

		if (pos != v1.end())
		{
    
    
			v1.erase(pos);
		}
		(*pos)++;
		//vs下std的pos失效了,不能访问并且进行强制检查

		for (size_t i = 0; i < v1.size(); ++i)
		{
    
    
			cout << v1[i] << " ";
		}
		cout << endl;

	}
void erase(iterator pos)
{
    
    
    assert(pos <= _finish);
    assert(pos >= _start);

    iterator start = pos + 1;
    while(start != _finish)
    {
    
    
        *(start - 1) = *start;
        ++start;
    }
    --_finish;

}

void test_vector6()
{
    
    
    yyf::vector<int> v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    v1.push_back(5);

    for (size_t i = 0; i < v1.size(); ++i)
    {
    
    
        cout << v1[i] << " ";
    }
    cout << endl;

    // 要求删除所有的偶数
    std::vector<int>::iterator it = v1.begin();
    while (it != v1.end())
    {
    
    
        if (*it % 2 == 0)
        {
    
    
            it = v1.erase(it);//g++中std的erase会指向删除数据的后一个数据的新位置
        }
        else
        {
    
    
            ++it;
        }
    }
  
    for (size_t i = 0; i < v1.size(); ++i)
    {
    
    
        cout << v1[i] << " ";
    }
    cout << endl;
}
iterator erase(iterator pos)
{
    
    
    assert(pos <= _finish);
    assert(pos >= _start);

    iterator start = pos + 1;
    while(start != _finish)
    {
    
    
        *(start - 1) = *start;
        ++start;
    }
    --_finish;


    return pos;
}

erase以后我们认为pos失效了,不能再使用
vs下会有强制检查,erase以后迭代器不能访问。
g++没有强制检查,会导致一系列结果未定义

匿名对象生命周期延长

vector(size_t n, const T& val = T())
{
    
    	
    
}
  1. const T& val = T()就是创建了一个匿名对象,通过构造函数,然后再引用
  1. 匿名对象生命周期只在当前一行,因为这行之后就没人会用它了
  2. const引用会延长匿名对象的生命周期到引用对象域结束,因为引用就是起别名,就是这个匿名对象
    对象
  3. 匿名对象和临时对象具有常性

vector 内存布局

在这里插入图片描述

在这里插入图片描述

深拷贝与浅拷贝

//拷贝构造
vector(const vector<T>& v)
{
    
    
    reserve(v.capacity());
    //memcpy(_start, v._start, sizeof(T) * v.size());
    for (size_t i = 0; i < v.size(); ++i)
    {
    
    
        _start[i] = v._start[i];
    }
    _finish = _start + v.size();
    _end_of_storage = _start + v.capacity();
}

//赋值重载
void swap(vector<T>& v)
{
    
    
    std::swap(_start, v._start);
    std::swap(_finish, v._finish);
    std::swap(_end_of_storage, v._end_of_storage);
}

//赋值重载现代写法
vector<T>& operator=(vector<T> v)
{
    
    //先构造拷贝了一个v,然后进行引用交换,最后析构掉原来的*this.
    swap(v);
    return *this;
}

//扩容
void reserve(size_t n)
{
    
    
    if (n > capacity())
    {
    
    
        //提前记录size
        size_t sz = size();
        T* tmp = new T[n];
        //开新空间
        if (_start)
        {
    
    
            //memcpy(tmp, _start, sizeof(T) * size());
            for (size_t i = 0; i < sz; ++i)
            {
    
    
                tmp[i] = _start[i];
            }
            //拷贝旧数据
            delete[] _start;
        }
        _start = tmp;
        _finish = tmp + sz;
        _end_of_storage = tmp + n;

    }
}


void test_vector11()
{
    
    //类模板类型为自定义类型时要深拷贝
    yyf::vector<std::string> v3(3, "111111111111111111111");
    for (auto e : v3)
    {
    
    
        cout << e << " ";
    }
    cout << endl;

    vector<std::string> v4(v3);
    for (auto e : v4)
    {
    
    
        cout << e << " ";
    }
    cout << endl;

    v4.push_back("2222222222222222222");
    v4.push_back("2222222222222222222");
    v4.push_back("2222222222222222222");
    for (auto e : v4)
    {
    
    
        cout << e << " ";
    }
    cout << endl;



    class Solution {
    
    
    public:
        vector<vector<int>> generate(int numRows) {
    
    
            vector<vector<int>> nums;
            nums.resize(numRows, vector<int>());
            for (int i = 0; i < nums.size(); i++)
            {
    
    
                nums[i].resize(i + 1, 0);
                nums[i][0] = nums[i][nums[i].size() - 1] = 1;
            }
            for (size_t i = 0; i < nums.size(); ++i)
            {
    
    
                for (size_t j = 0; j < nums[i].size(); ++j)
                {
    
    
                    if (nums[i][j] == 0)
                    {
    
    
                        nums[i][j] = nums[i - 1][j] + nums[i - 1][j - 1];
                    }
                }
            }
            return nums;
        }
    };


    yyf::vector<vector<int>> ret = Solution().generate(5);
    for (size_t i = 0; i < ret.size(); ++i)
    {
    
    
        for (size_t j = 0; j < ret[i].size(); ++j)
        {
    
    
            cout << ret[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}

memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
如果拷贝的是自定义类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起野指针问题甚至程序崩溃。

总结

vector是可以变大小任意类型的数组的类。
会当凌绝顶,一览众山小。——杜甫

猜你喜欢

转载自blog.csdn.net/yanyongfu523/article/details/131839955