前言

在 C++ 标准模板库(STL)中,vector 是最常用也最灵活的容器之一。它作为动态数组,既保留了数组随机访问的高效性,又具备动态扩容的灵活性,在实际开发中有着广泛的应用。

本文将从 vector 的构造方法入手,逐步深入到迭代器使用、空间管理、元素操作等核心知识点,不仅会讲解各种 API 的用法细节,还会通过模拟实现代码帮助读者理解其底层工作原理。同时,针对 vector 使用中常见的迭代器失效、边界访问等问题进行详细说明,并结合典型算法题目展示 vector 在实际场景中的应用,旨在为读者提供一份系统、全面的 vector 学习指南,无论是 C++ 初学者还是需要巩固基础的开发者,都能从中获得实用的知识与技巧。

构造vector的几种方法

在这里插入图片描述

具体的使用方法查询官网

像这种网站的容器里有看不懂的类型,一般就是typedef出来的

在这里插入图片描述

示例:(举的中间那两个的)

vector<int> v1(10, 1); vector<int> v3(v1.begin(), v1.end());

eg:vector<vector<int>>的用法
    vector<vector<int>> vv;
    vv.size();   vv[i].size();
resize时,vv要resize,vv[i]也要resize

vector的迭代器

begin--获取第一个字符的迭代器
end--获取最后一个字符的下一个位置的迭代器

rbegin--获取最后一个字符的迭代器
rend-获取第一个字符的上一个位置的迭代器(这里的迭代器其实是反向迭代器)
反向迭代器用范围for的话是倒着遍历的

注意:vector的迭代器不一定是原生指针
注意:反向迭代器++和迭代器++的移动方向是相反的

vector的空间操作

size–获取数据个数

capacity–获取的容量大小

empty–判断是否为空

resize–改变vector的size和capacity

在这里插入图片描述

reserve–改变vector的capacity

注意:[]去访问的时候是根据size来的
比如 a是vector<int>类型的   a.capacity() == 10;a.size() == 1
a[9]就是越界的,会报错

vector获取位置

operator[]--一般用这个
front --返回容器中第一个元素的引用
back --返回容器最后一个元素的引用
data(C++11允许)--获取vector内部存储元素的连续内存的首地址

vector的增删查改

push_back 尾插

pop_back 尾删

–上面这两感觉没啥用

insert

在这里插入图片描述

注意:传的是迭代器   插入的话,是在pos前面插入

erase 删除position位置的数据(这个地方没数据的话,会报错)

返回值:返回一个迭代器,指向被删除元素的下一个元素

在这里插入图片描述

swap 交换两个vector的数据空间

operator[]

emplace – 以后再了解,涉及右值引用了

注意:vector里面没有find,但是里面有find可以用

里面find的用法:

查找的是第一个出现val的位置

在这里插入图片描述

注意:这种区间都是左闭右开(迭代器和指针)

vector的模拟实现

源代码的看法:

1.通过单词意思去猜测成员的作用

2.或者通过构造函数和插入接口函数(比如:insert)来看成员作用

3.先看大框架

模拟实现

namespace renshen
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

	vector(size_t n, const T& val = T())//注意理解这里的T()
        :_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
	{
		resize(n, val);
	}

	template<class InputIterator>//模板里面可以套模板
	vector(InputIterator first, InputIterator last)
        :_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
	{
		while (first != last)
		{
			push_back(*first);
			++first;
		}
	}

	vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}

	vector(const vector<T>& v)//传统写法
       :_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
	{
		_start = new T[v.capacity()];//size也行
		//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();
		_endofstorage = _start + v.capacity();//前面是size的话,这也要改成size
	}

	/*vector(const vector<T>& v)//现代写法
		:_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
	{
		reserve(v.capacity());
		for (auto e : v)
		{
			push_back(e);
		}
	}*/

	void swap(vector<T>& v)
	{
		std::swap(_start, v._start);
		std::swap(_finish, v._finish);
		std::swap(_endofstorage, v._endofstorage);
	}

	vector<T>& operator=(vector<T> v)
	{
		swap(v);
		return *this;
	}

		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _endofstorage = nullptr;
			}
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];

				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * sz);
       for (size_t i = 0; i < sz; i++)
         {
				tmp[i] = _start[i];
          }
					delete[] _start;//把那一块都删了
				}
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}

void resize(size_t n, const T& val = T())
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		reserve(n);

		while (_finish != _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

void push_back(const T& x)
{
	insert(end(), x);
}

void pop_back()
{
	erase(--end());
}

		size_t capacity() const
		{
			return _endofstorage - _start;
		}

		size_t size() const
		{
			return _finish - _start;//不用打括号
		}

		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}

		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}

		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);
			if (_finish == _endofstorage)
			{
				size_t len = pos - _start;
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
			return pos;
		}

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

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

	--_finish;

	return pos;
}

	private:
		iterator _start;
		iterator _finish;//最后一个元素的下一个位置
		iterator _endofstorage;//内存的末尾的下一个位置
	};

	void print(const vector<int>& v)
	{
		for (auto e : v)
//前面有const_iterator类型的begin和end,这里const的vector才能用范围for
		{
			cout << e << " ";
		}
		cout << endl;
	}

问题:以下两个分别调用的哪一个

vector v(10, 1);–编译器会去调用迭代器区间那个,因为另外一个是size_t,没有那个合适–10的话编译器默认是int类型
vector v1(10, “1111”);–调用的是size_t那个

引申:迭代器失效:1.常常出现在insert之后(因为扩容过后,迭代器就成’‘野指针’'了)

                     2.erase也有这种情况--后面用形参迭代器VS会强制报错

总结:vector的erase和insert形参迭代器不再去访问了

代码要讲究通用性,自己写的代码要VS下能用,gcc也要能用–不用编译器的特性就行了

reverse和vecor的构造函数里面最好不用那个memcpy–这个是浅拷贝,但是遇到比如:string这种的时,就不行了

eg:int j = int();这样也是对了的,这样j就是int的默认值了-0

作业部分

只出现一次的数字 力扣

只出现一次的数字   力扣
方法:用异或的方法
注意:异或是符合分配律的
代码参考:
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int e =0 ;
        for(int i = 0;i<nums.size();i++)
        {
          e = e^nums[i];
        }
        return e;
    }
};
T是一个数据类型,在vs系列编译器中,debug模式下
关于std::vector::at 和 std::vector::operator[] 描述正确的是(C)

A.at总是做边界检查, operator[] 不做边界检查.
B.at 不做边界检查, operator[] 做边界检查.
C.at和operator[] 都是会做边界检查的
D.以上都不对
下面程序的输出结果正确的是(C)

int main()

{
	int ar[] = {1,2,3,4,5,6,7,8,9,10};
	int n = sizeof(ar) / sizeof(int);
	vector<int> v(ar, ar+n);
	cout<<v.size()<<":"<<v.capacity()<<endl;
	v.reserve(100);
	v.resize(20);
	cout<<v.size()<<":"<<v.capacity()<<endl;
	v.reserve(50);
	v.resize(5);
	cout<<v.size()<<":"<<v.capacity()<<endl;
}

A.10:10 20:100 5:50
B.10:20 20:100 5:100
C.10:10 20:100 5:100
D.10 10 20:20 20:50
//总结:容量不会变小,但是size会变小

在这里插入图片描述

这种情况的话,其实算是值返回,会深拷贝,所以没事

电话号码的字母组合 力扣

电话号码的字母组合 力扣
注意:vector<string> a;
    没有值的时候,直接返回a,不要返回""   
还有,dfs传a过去要传a的引用
eg:a.pushback(" ")的话,不是在已经有的string后面追加
(注意:vector没有+=,而是用的pushback)
而是比如本为a[0],现在加在a[1]
string为空也不能跟nullptr比较
代码展示:
class Solution {
public:
    string qq[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
     void dfs(vector<string>&f,int level,string p,string digits)
     {
        if(level == digits.size())
        {
            f.push_back(p);
            return;
        }
        for(int i = 0;i<qq[digits[level]-'0'].size();i++)
        {
            dfs(f,level+1,p+qq[digits[level]-'0'][i],digits);
        }
     }
    vector<string> letterCombinations(string digits) {
        vector<string> a;
        if(digits == "") return a;
       dfs(a,0,"",digits);
          return a;
    }
};

牛客网 数组中出现次数超过一半的数字

牛客网  数组中出现次数超过一半的数字
做法:其实就是排序之后,数组中间的那个数(如果数组长度是偶数的话,中间那俩个数其实都是那个数)
下标就是numbers.size()/2
代码展示:
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param numbers int整型vector
     * @return int整型
     */
    int MoreThanHalfNum_Solution(vector<int>& numbers) {
     sort(numbers.begin(),numbers.end());
     return numbers[numbers.size()/2];
    }
};

力扣 只出现一次的数ii

力扣 只出现一次的数ii --这道题非常薄弱--自己对二进制不熟!!!
做法:对于出现3次的数字,二进制各位出现的次数都是3的倍数,因此对统计的为1的二进制位各位%3
     结果就是只出现一次的数字
注意:线性时间复杂度的意思是时间复杂度要是O(N)
引申:sort的时间复杂度是O(NlogN)
代码展示:
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (int i = 0; i < 32; ++i) {
            int total = 0;
            for (int e: nums) {
                total += ((e >> i) & 1);
            }
            if (total % 3) {
                ret |= (1 << i);
            }
        }
        return ret;
    }
};

力扣 只出现一次的数iii

力扣 只出现一次的数iii--这个题也是薄弱项
做法:先全部异或到sum上,找出sum二进制是1的最低位(第i位)   假设num1 = num2 = 0;
把第i位是1的数全异或到num1上,把第i位是0是全异或到num2上即可

这个题要注意异或和要用long long int存,不然会溢出
不能写成 int num1 = num2 = 0; num1^=e;(^=之间不能有空格)
代码展示:
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        long long int sum = 0;
        for(auto e: nums)
        {
           sum = sum^e;
        }
        long long int lowbit = sum&(-sum);
        int num1 = 0,num2 = 0;
        for(auto e:nums)
        {
            if((lowbit&e) == 0) num1^= e;
            else  num2^=e;
        }
        return {num1,num2};
    }
};
Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐