C++初阶:STL之vector类模板

2023-12-14 16:47:02

目录

一.vector的介绍及使用

1.1.vector的介绍

1.2.vector的使用

1.2.1.vector的定义

1.2.2.vector iterator的使用

1.2.3.vector的空间增长问题

1.2.4.vector的增删查改

1.3.vector在OJ中的使用

题一:只出现一次的数字

题二:杨辉三角

题三:电话号码的字母组合

1.4.vector迭代器失效问题

在VS环境下

在Linux环境下

string迭代器失效问题

二.vector深度剖析及模拟实现

2.1.原码剖析

2.2.模拟实现

2.2.1.迭代器相关

2.2.2.容量相关

2.2.3.元素访问相关

2.2.4.修改操作相关

2.2.5.成员函数相关

2.3.vector类的完整实现

vector.h

test.c


一.vector的介绍及使用

1.1.vector的介绍

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

注意:

1.在向向量容器中插入元素时,如果插入操作引起了向量容量的扩展,那么执行插入之前所获得的一切迭代器,指针,引用都会失效,因为空间被重新分配了,元素的内存地址发生了变化;如果插入操作没有引起向量容量的扩展,那么只有处于插入位置之后的迭代器,指针,引用失效,因为插入位置后面的元素被移动了,对插入之前的元素不会有影响。

2.在删除向量容器中的元素时,被删除元素后面的元素会顺序向前移动,将被删除元素留下的空位补上,而后面闲置的空间并不会被释放。删除操作的效率和插入类似,被删除元素越靠前,删除操作所需的时间就越多,删除操作不会引起向量容器容量的改变,因此被删除之前的迭代器,指针,引用都能够继续使用,而删除元素之后的迭代器,指针,引用都会失效。

1.2.vector的使用

vector是STL中一种自定义数据类型,包含在头文件<vector>中,其定义如下所示:

template<class T,class Allocator=allocator<T>>
class vector;

vector是将元素置于一个动态数组中加以管理的容器,是最简单的序列式容器。它支持随机存取元素。在程序开发过程中,使用vector作为动态数组是非常方便的,尤其是在末端添加或删除元素时,速度非常快。

1.2.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)?? 使用迭代器进行初始化构造

详解:

说明:

迭代器是STL中最基本的一部分内容,它将容器与算法联系起来,起着粘合剂的作用,几乎STL提供的所有算法都是通过迭代器存取元素来实现的。它有些类似于指针,当参数化类型是C++内部类型时,迭代器就是指针。

注意:

vector<char> vstring s是不同的,虽然二者存放的都是字符char。对于前者来说,它是一个存放字符的数组,末尾是不存在\0的,需要我们自己手动添加;对于后者来说,它是一个字符串,而字符串的末尾默认存在\0,不需要我们自己手动添加。

案例:

void test_vector()
{
	//方式一:无参构造
	//vector对象可以初始化状态为空,不指定大小也不指定初始值
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

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

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

	//范围for
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	//方式二:拷贝构造
	//用一个vector对象初始化另一个或相互赋值时,两个vector对象的元素类型必须相同
	//其实它调用的是vector的拷贝构造函数
	vector<int> copy(v1);
	for (auto e : copy)
	{
		cout << e << " ";
	}
	cout << endl;

	vector<int> v = v1;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	//方式三:构造并初始化n个val
	//用vector对象的大小和统一初始值来定义容器
	vector<int> v2(10, 1);
	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	//方式四:使用迭代器进行初始化构造
	//迭代器有些类似于指针,当参数化类型是C++内部类型时,迭代器就是指针
	vector<int> v3(v2.begin(), v2.end());
	for (auto e : v3)
	{
		cout << e << " ";
	}
	cout << endl;

	//既然有了vector,为什么还需要string呢?
	//虽然它和string底层都是存储字符char,但还是有所不同的。
	//相比T是char的vector,它自身默认是不存在\0的,需要我们自己手动添加,但是对于s来说它是一个字符串,而字符串本身就是以\0结尾的。
	//所以说T是char的vector不能去替代string。
	string s1("hello world");
	vector<char> v4(s1.begin(), s1.end());//可以设置部分区间v3(++s1.begin(), --s1.end())
	for (auto e : v4)
	{
		cout << e << " ";
	}
	cout << endl;
}

运行结果:

1.2.2.vector iterator的使用

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

详解:

迭代器主要有两种形式:一种是可读可写的迭代器,一种是只读的常量迭代器:

  1. 正向迭代器:容器类名::iterator 迭代器名;
  2. 常量正向迭代器:容器类名::const_iterator 迭代器名;
  3. 反向迭代器:容器类名::reverse_iterator 迭代器名;
  4. 常量反向迭代器:容器类名::const_reverse_iterator 迭代器名;

STL中的容器提供了begin()与end()函数返回一个随机访问迭代器来访问元素,除此之外,各容器还提供了rbegin()与rend()这样一对函数,用于返回一个逆向迭代器。rbegin()返回的是逆向遍历的第一个元素,即倒数第一个元素,rend()返回的是逆向遍历的末尾元素的后一个位置,即第一个元素的上一个位置,其实这个位置已经不在容器中了。

案例:

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

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

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

	cout << v.max_size() << endl;
}

运行结果:

1.2.3.vector的空间增长问题

?????????????????? 容量空间????????????? ? ?? 接口说明
????????????????????? size????????? ? ?? 获取数据个数
???????????????? ?? capacity?????????????? 获取容量大小
???????????????????? empty????????????? 判断是否为空
????????????? ? resize(重点)??????????? 改变vector的size
?????????? ? ? reserve(重点)????????? 改变vector的capacity

详解:

向量容器的容量与容器大小并不一定相同,vector提供了两个函数capacity()和size(),分别获取容器容量和容器大小。

reserve:改变vector的capacity

案例一:

#include<time.h>

void test_vector()
{
	//vs下capacity是按1.5倍增长的,g++下是按2倍增长的
	size_t sz;
	vector<int> v;
	const size_t n = 10000;

	size_t begin = clock();

	sz = v.capacity();
	
	cout << "making v grow:\n";
	for (int i = 0; i < n; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}

	size_t end = clock();
	cout << "消耗时间:" << end - begin << endl;
}

运行结果:

如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够,这样就可以避免边插入边扩容导致效率低下的问题了。

案例二:

#include<time.h>

void test_vector()
{
	//vs下capacity是按1.5倍增长的,g++下是按2倍增长的
	size_t sz;
	vector<int> v;
	const size_t n = 10000;

	size_t begin = clock();

	sz = v.capacity();
	//如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
	//这样就可以避免边插入边扩容导致效率低下的问题了
	v.reserve(n);//提前将容量设置好,可以避免一边插入一边扩容
	cout << "making v grow:\n";
	for (int i = 0; i < n; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}

	size_t end = clock();
	cout << "消耗时间:" << end - begin << endl;
}

运行结果:

resize:改变vector的size

案例一:

void test_vector()
{
	//vector<int> v1;
	//v1.resize(10, 0);
	//vector<int> v2(10, 0);

	//reisze(size_t n, const T& data = T())
    //将有效元素个数设置为n个,如果增多时,增多的元素使用data进行填充
    //注意:resize在增多元素个数时可能会扩容

	vector<int> v;

	for (int i = 1; i < 10; i++)
	{
		v.push_back(i);
	}

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

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

运行结果:

案例二:

void test_vector()
{
	vector<int> v;
	v.reserve(10);
	cout << "capacity:" << v.capacity() << endl;
	cout << "size:" << v.size() << endl;

	for (size_t i = 0; i < 10; i++)
	{
		v[i] = i;
	}

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

运行结果:

分析:

可以发现程序运行出错,因为对于reserve而言它只是改变capacity,而不会改变size,所以此时的size依旧为0,容器中没有存取任何元素。当我们通过关系运算符operator[]去访问容器中元素时,程序就会发生异常,显示下标越界。此时,我们可以使用push_back()对程序进行修改,它在插入元素的同时,也会对size进行修改,并且也会进行一定的扩容。

改进:

小结:

  1. capacity的代码在vs和g++下分别运行,可以发现:vs下capacity是按1.5倍增长的,g++下capacity是按2倍增长的。这个问题经常会考察,不要固化的认为vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL;
  2. reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题;
  3. resize在开空间的同时还会进行初始化,影响size。

1.2.4.vector的增删查改

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

详解:

push_back和pop_back:从尾部插入和删除元素

push_back()函数是向vector容器末尾添加元素。先创建一个空的vector对象,再调用push_back()函数向其中添加元素。而pop_back()用于弹出vector容器末尾的一个元素。

案例:

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

	auto it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	v.pop_back();
	v.pop_back();

	it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

运行结果:

insert和erase:插入和删除元素

vector提供了一对向容器中随机插入和删除元素的函数insert()与erase()函数,其中insert()函数用于向容器中插入元素,erase()函数用于移除容器中的元素。注意:insert()函数与erase()函数中的位置只能由begin()或end()返回的迭代器来指示,而不能用纯粹的数字作为参数。

案例:

void test_vector()
{
	//find:查找(注意这个是算法模块实现,不是vector的成员接口)
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

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

	//1.先使用find查找2所在位置
    //注意:vector没有提供find方法,如果要查找只能使用STL提供的全局find
	vector<int>::iterator pos = find(v.begin(), v.end(), 2);
	if (pos != v.end())
	{
		//2.在pos位置之前插入30
		v.insert(pos, 20);
	}

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

	//v.erase(pos);//err,不能直接用,因为迭代器失效了,若想要删除原来pos位置上的元素,要对迭代器重新赋值
	pos = find(v.begin(), v.end(), 2);
	if (pos != v.end())
	{
		v.erase(pos);
	}

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

	v.erase(v.begin());//可以直接进行头删
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

运行结果:

swap和operator[ ]

swap()函数主要用于交换两个vector对象的数据空间,而operator[ ]主要用于对容器中元素进行访问与修改。

案例:

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

	//通过operator[]读写第0个位置
	v[0] = 10;
	cout << v[0] << endl;

	//使用for+[]方式遍历
	for (size_t i = 0; i < v.size(); ++i)
	{
		cout << v[i] << " ";
	}
	cout << endl;

	vector<int> swapv;
	swapv.swap(v);

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

	//使用迭代器遍历
	cout << "swapv data:" << endl;
	auto it = swapv.begin();
	while (it != swapv.end())
	{
		cout << *it << " ";
		++it;
	}

	//使用范围for遍历
	for (auto x : v)
	{
		cout << x << " ";
	}
	cout << endl;
}

运行结果:

1.3.vector在OJ中的使用

题一:只出现一次的数字

题目描述:

给你一个非空整数数组nums,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

示例 1 :

输入:nums = [2,2,1]
输出:1

示例 2 :

输入:nums = [4,1,2,1,2]
输出:4

示例 3 :

输入:nums = [1]
输出:1

分析: 使用异或操作符^:相同为0,相异为1。

实现:

class Solution
{
public:
	int singleNumber(vector<int>& nums)
	{
		int ret = 0;
		for (auto e : nums)
			ret ^= e;

		return ret;
	}
};

题二:杨辉三角

题目描述:

给定一个非负整数numRows生成「杨辉三角」的前numRows行。在「杨辉三角」中,每个数是它左上方和右上方的数的和。

示例 1:

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

示例?2:

输入: numRows = 1
输出: [[1]]

分析: 找出杨辉三角的规律,发现每一行头尾都是1,中间第[j]个数等于上一行[j-1]+[j]。

实现:

class Solution 
{
public:
	vector<vector<int>> generate(int numRows)//vector<vector<int>> vv;==>vv[i]对应vector<int>==>vv[i][j]对应int
	{
		vector<vector<int>> vv;
		vv.resize(numRows, vector<int>());//采用匿名对象对每一行进行初始化

		//初始化
		for (size_t i = 0; i < vv.size(); ++i)
		{
			vv[i].resize(i + 1, 0);
			vv[i][0] = vv[i][vv[i].size() - 1] = 1;//将每行的第一个和最后一个元素置为1
		}
		
		//遍历
		for (size_t i = 0; i < vv.size(); ++i)
		{
			for (size_t j = 0; j < vv[i].size(); ++j)
			{
				if (vv[i][j] == 0)
				{
					vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
				}
			}
		}
		return vv;
	}
};

扩展:

vector<bit::vector<int>> vv(n):构造一个vv动态二维数组,vv中总共有n个元素,每个元素都是vector类型的,每行没有包含任何元素,如果n为5时如下所示:

vv中元素填充完成之后,如下图所示:

使用标准库中vector构建动态二维数组时与上图实际是一致的。

题三:电话号码的字母组合

题目描述:

给定一个仅包含数字2-9的字符串,返回所有它能表示的字母组合。答案可以按任意顺序返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

实现:

class Solution
{
	string _numToStr[10] = { "", "","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz" };
public:
	void Combinations(const string& digits, size_t di, string combineStr, vector<string>& strV)
	{
		if (di == digits.size())
		{
			strV.push_back(combineStr);
			return;
		}

		int num = digits[di] - '0';
		string str = _numToStr[num];
		for (auto ch : str)
		{
			Combinations(digits, di + 1, combineStr + ch, strV);
		}
	}

	vector<string> letterCmobinations(string digits)
	{
		vector<string> strV;
		if (digits.size() == 0)
		{
			return strV;
		}

		Combinations(digits, 0, "", strV);
		return strV;
	}
};

1.4.vector迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

情况分类:

  1. 如果容器扩容,并在其他地方重新开辟了一块空间,那么原来容器底层的空间上所保存的迭代器全部失效;

  2. 当容器调用insert()方法后,当前位置到容器末尾元素的所有迭代器全部失效;

  3. 当容器调用erase()方法后,当前位置到容器末尾元素的所有迭代器全部失效。

在VS环境下

案例一:

void test1()
{
	vector<int> v{ 1,2,3,4,5 };
	auto it = v.begin();

	//将有效元素个数增加到10个,多出的位置使用8填充,操作期间底层会扩容
    v.resize(10, 8);

	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

void test2()
{
	vector<int> v{ 1,2,3,4,5 };
	auto it = v.begin();

	//reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
    v.reserve(10);

	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

void test3()
{
	vector<int> v{ 1,2,3,4,5 };
	auto it = v.begin();

	//插入元素期间,可能会引起扩容,而导致原空间被释放
    v.insert(v.begin(), 0);
    v.push_back(6);

	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

void test4()
{
	vector<int> v{ 1,2,3,4,5 };
	auto it = v.begin();

	//给vector重新赋值,可能会引起底层容量改变
	v.assign(10, 8);

	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	test1();

	return 0;
}

运行结果:

分析:

会引起底层空间改变的操作,都有可能导致迭代器失效,比如:resize、reserve、insert、assign、push_back等。

  • 出错原因:以上操作都有可能导致vector扩容,也就是说vector底层原有的旧空间被释放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的旧空间,从而引起代码运行时崩溃。
  • 解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。

案例二:

int main()
{
	int a[] = { 1, 2, 3, 4 };
	vector<int> v(a, a + sizeof(a) / sizeof(int));

	//使用find查找3所在位置的iterator
	vector<int>::iterator pos = find(v.begin(), v.end(), 3);

	//删除pos位置的数据,导致pos迭代器失效
	v.erase(pos);

	cout << *pos << endl; //此处会导致非法访问

	return 0;
}

运行结果:

分析:

erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。

案例三:

int main()
{
	vector<int> v{ 1, 2, 3, 4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			v.erase(it);
			//it = v.erase(it);
		}
		else
		{
			++it;
		}
	}

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

	return 0;
}

运行结果:

分析:

该代码的功能是删除vector中所有的偶数,在使用erase进行删除时,出现迭代器失效问题。

  • 出错原因:因为vetor使用连续分配的内存,erase删除一个元素导致后面所有的元素都会向前移动一个位置,这些元素的地址发生了变化,所以当前位置到容器末尾元素的所有迭代器全部失效。
  • 解决方式:利用erase方法可以返回下一个有效的iterator,即:it = v.erase(it)。
在Linux环境下

注意:在Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

案例一:

运行结果:

分析:

扩容之后,迭代器已经失效,程序虽然可以运行,但是运行结果已经不对了。经过reserve之后,it迭代器肯定会失效,在vs下程序直接崩溃,但是在Linux下不会,虽然程序可以运行,但是输出的结果是不对的。

案例二:

运行结果:

分析:

erase删除任意位置代码后,Linux下迭代器并没有失效。因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的。

案例三:

运行结果:

分析:

erase删除的迭代器如果是最后一个元素,删除之后it已经超过end。此时迭代器是无效的,++it导致程序崩溃。

从上述三个例子中可以看到:SGI STL中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it不在begin和end范围内,肯定会崩溃的。

string迭代器失效问题

与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效。

#include <string>

void TestString()
{
	string s("hello");
	auto it = s.begin();
	//放开之后代码会崩溃,因为resize到20会string会进行扩容
	//扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了
	//后序打印时,再访问it指向的空间程序就会崩溃
	//s.resize(20, '!');
	while (it != s.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;

	it = s.begin();
	while (it != s.end())
	{
		it = s.erase(it);
		//按照下面方式写,运行时程序会崩溃,因为erase(it)之后
		//it位置的迭代器就失效了
		//s.erase(it);
		++it;
	}
}

小结:

迭代器失效解决办法:在使用前,对迭代器重新赋值即可。

在向向量容器中插入元素时,如果插入操作引起了向量容量的扩展,那么执行插入之前所获得的一切迭代器,指针,引用都会失效,因为空间被重新分配了,元素的内存地址发生了变化;如果插入操作没有引起向量容量的扩展,那么只有处于插入位置之后的迭代器,指针,引用失效,因为插入位置后面的元素被移动了,对插入之前的元素不会有影响。

在删除向量容器中的元素时,被删除元素后面的元素会顺序向前移动,将被删除元素留下的空位补上,而后面闲置的空间并不会被释放。删除操作的效率和插入类似,被删除元素越靠前,删除操作所需的时间就越多,删除操作不会引起向量容器容量的改变,因此被删除之前的迭代器,指针,引用都能够继续使用,而删除元素之后的迭代器,指针,引用都会失效。

二.vector深度剖析及模拟实现

2.1.原码剖析

迭代器:

成员变量:

图解:

2.2.模拟实现

前提:为了避免和vector类发生冲突,我们采用命名空间的方式来隔离冲突域。如下所示:

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

		//主要接口函数
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

2.2.1.迭代器相关

常用的迭代器分为正向迭代器与常量正向迭代器。正向迭代器可以改变对象中指定元素的值,而常量正向迭代器不可以改变对象中指定元素的值。

//非const迭代器
iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

//const迭代器
const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

2.2.2.容量相关

size:用于获取容器大小

size_t size() const
{
	return _finish - _start;
}

capacity:用于获取容器容量

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

empty:用于判断某个容器是否为空

bool empty()
{
	return _start == _finish;
}

reserve:用于改变vector的capacity

在进行reserve之前,我们要判断所传入的值 n 是否大于capacity(),若n>capacity(),则先调用new来开辟一块新的空间,然后调用memcpy函数来将旧空间的内容拷贝到新空间,接着再调用delete将旧空间进行释放,最后再更新_start,_finish以及_end_of_storage。

void reserve(size_t n)
{
	if (n > capacity())
	{
        //开辟新空间
		T* tmp = new T[n];

		if (_start)
		{
            //拷贝数据
			memcpy(tmp, _start, sizeof(T) * size());
            //删除旧空间
			delete[] _start;
		}

		_start = tmp;
		_finish = _start + size();
		_end_of_storage = _start + n;
	}
}

调试分析:

可以发现_start_end_of_storage的值在程序执行过程中均已发生改变,而_finish的值却没有发生任何变化。因为当我们在开辟新空间tmp时,_start的值已经发生了改变(指向tmp),而此时的_finish依旧是初始值nullptr。当我们在计算_finish=_start+size()时,结合size()=_finish - _start,此时的_finish=_start+_finish-_start=_finish=nullptr。所以最终_finish的值为0x00000000。

那么该如何进行修改才能使_finish的值进行更新呢?

法一:先更新_finish,再更新_start。(不推荐)

_finish = tmp + size();
_start = tmp;
_end_of_storage = _start + n;

调试分析:

通过调试可以发现,此时的_finish的确发生了变化。但是该方法一般推荐,因为不利于代码的后期维护。

法二:先保存size()的值sz,然后再按顺序更新_start和_finish。(推荐)

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();

		//开辟新空间
		T* tmp = new T[n];
		if (_start)
		{
			//拷贝旧数据
			memcpy(tmp, _start, sizeof(T) * size());

			//释放旧空间
			delete[] _start;
		}

		_start = tmp;
		//_finish = _start + size();//err,因为size()=_finish-_start,所以_start+_finish-_start=_finish=nullptr。又因为_start已经发生变化,变成新空间的_start了,而_finish还是旧空间的_finish,,所以_finish-_start就是一个负值,再加_start就是0
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

调试分析:

通过调试可以发现,finish的确发生了变化,此时_finish为空的问题得到了有效解决。

之前vector中的内置类型均为int,当我们将其置为string类型时,案例如下:

void test_vector()
{
	vector<string> v;

	v.push_back("111111111111111111111");
	v.push_back("222222222222222222222");
	v.push_back("333333333333333333333");
	v.push_back("444444444444444444444");

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

	v.push_back("555555555555555555555");
}

调试分析:

通过调试可以发现,当我们在调用析构函数对旧空间进行释放时,程序运行出错。结合前面所学内容,应该是出现了深浅拷贝问题。

当第五次push_back时,需要调用reserve函数进行增容,开好空间后,memcpy将旧空间的内容拷贝至新空间。由上述调试可知,memcpy完成的是浅拷贝,同时它把_start里_str所指向的空间的内容也进行了拷贝。待拷贝完成之后,将delete[] _start,而数组里存放的是string对象,它会先去调用string对象的析构函数将堆上的空间进行释放,然后再把_start所指向的空间进行释放,最后再更新_start,_finish和_end_of_storage。当程序的生命周期结束之时,vector会调用析构函数对空间进行释放,但是tmp中string对象所指向的堆空间已经被释放,此时的_str由于空间被释放已经变成一个野指针,所以vector调用析构函数时会对这个野指针进行二次释放,进而会导致程序崩溃。

小结:

  1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中;
  2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。?

结论:

如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是
浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。?

为了避免该问题发生,我们可以调用赋值运算符重载进行逐一拷贝,以此来完成深拷贝。reserve函数的完整实现如下所示:

void reserve(size_t n)
{
	if (n > capacity())
	{
		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 = _start + size();//err,因为size()=_finish-_start,所以_start+_finish-_start=_finish=nullptr。又因为_start已经发生变化,变成新空间的_start了,而_finish还是旧空间的_finish,,所以_finish-_start就是一个负值,再加_start就是0
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

resize:改变vector的size

resize主要用于改变vector的size大小。分三种情况进行讨论:

  1. n < _finish;
  2. _finish <= n <= _end_of_storage
  3. n >_end_of_storage
//T()采用匿名对象的方式生成一个默认缺省值
//val不能置为0,因为T不一定为int类型
void resize(size_t n, T val = T())
{
	if (n < size())
	{
		//删除数据
		_finish = _start + n;
	}
	else
	{
		if (n > capacity())
		{
			reserve(n);
		}

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

2.2.3.元素访问相关

operator[ ]:主要通过下标的方式对元素进行访问。分为const版和非const版。

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

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

2.2.4.修改操作相关

push_back:尾插

void push_back(const T& x)
{
	if (_finish == _end_of_storage)
	{
		//扩容
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}

	*_finish = x;
	++_finish;
}

pop_back:尾删

void pop_back()
{
	assert(!empty());

	--_finish;
}

insert:在pos之前插入val

在插入之前,首先要对插入位置的合法性进行判断,然后判断空间是否已满,若空间已满则进行扩容,在扩完容之后要及时更新pos,否则会发生迭代器失效问题,最后再从后向前移动元素并进行插入。

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

	//检查容量
	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;//扩容后要更新pos,解决pos失效问题
	}

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

	return pos;
}

erase:删除pos位置的数据

在删除之前,首先要对删除位置的合法性进行判断,然后再从前向后移动元素并进行删除。

//返回值是一个迭代器,并且是被删除元素的下一个位置的迭代器
iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);

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

	return pos;
}

swap:交换两个vector的数据空间

通过调用std库中的模板函数swap,来实现两个vector中的数据空间交换。

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

2.2.5.成员函数相关

构造

无参构造:

vector()
{

}

有参构造:

版本一:区间构造

//若使用iterator做迭代器,会导致初始化的迭代器区间[first,last)只能是vector的迭代器
//重新声明迭代器,迭代器区间[first,last)可以是任意容器的迭代器
//[first,end)
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

版本二:

vector(size_t n, const T& val = T())//const T& val = T():调用T的默认构造生成一个匿名对象,同时使用const引用可以延长匿名对象的生命周期到引用对象域结束
{
	reserve(n);
	for (size_t i = 0; i < n; ++i)
	{
		push_back(val);
	}
}

版本三:

vector(int n, const T& val = T())//const T& val = T():调用T的默认构造生成一个匿名对象,同时使用const引用可以延长匿名对象的生命周期到引用对象域结束
{
	reserve(n);
	for (int i = 0; i < n; ++i)
	{
		push_back(val);
	}
}

说明:

理论上将,提供了:vector(size_t n, const T& value = T())之后,vector(int n, const T& value = T())就不需要再提供了,但是对于:vector<int> v(10, 5);编译器在编译时,认为T已经被实例化为int,而10和5编译器会默认其为int类型,就不会调用:vector(size_t n, const T& value = T())这个构造方法,最终选择的是:vector(InputIterator first, InputIterator last)。因为编译器认为区间构造两个参数类型一致,因此编译器就会将InputIterator实例化为int,但是10和5根本不是一个区间,编译时就报错了,故需要增加该构造方法。

为了去调用:vector(size_t n, const T& value = T()),我们可以在传递第一个参数时加上一个u即可,那么编译器在编译时就会将其识别成无符号整数。

拷贝构造

在写拷贝构造时,要避免浅拷贝问题的发生。如果对象中涉及到资源管理,千万不要使用函数memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

版本一:

vector(const vector<T>& v)
{
	reserve(v.capacity());
	for (auto e : v)
	{
		push_back(e);
	}
}

版本二:

vector(const vector<T>& v)
{
	//reserve(v.capacity());
	_start = new T[v.capacity()];

	//如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃
	//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();
}

版本三:

//vector(const vector& v)//可以不加模板参数
vector(const vector<T>& v)
{
	//借助区间构造迭代器去完成:vector(InputIterator first, InputIterator last)
	vector<T> tmp(v.begin(), v.end());
	swap(tmp);
}

赋值运算符重载

//vector& operator=(vector v)//可以不加模板参数
vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}

析构

~vector()
{
	delete[] _start;
	_start = _finish = _end_of_storage;
}

2.3.vector类的完整实现

vector.h

#pragma once
#include<assert.h>

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

		//构造
		vector()
		{

		}

		//vector<int> v(10,5);
		vector(size_t n, const T& val = T())//const T& val = T():调用T的默认构造生成一个匿名对象,同时使用const引用可以延长匿名对象的生命周期到引用对象域结束
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		/*理论上将,提供了vector(size_t n, const T& value = T())之后
		vector(int n, const T& value = T())就不需要提供了,但是对于:
		vector<int> v(10, 5);
		编译器在编译时,认为T已经被实例化为int,而10和5编译器会默认其为int类型
		就不会走vector(size_t n, const T& value = T())这个构造方法,
		最终选择的是:vector(InputIterator first, InputIterator last)
		因为编译器觉得区间构造两个参数类型一致,因此编译器就会将InputIterator实例化为int
		但是10和5根本不是一个区间,编译时就报错了
		故需要增加该构造方法*/
		vector(int n, const T& val = T())//const T& val = T():调用T的默认构造生成一个匿名对象,同时使用const引用可以延长匿名对象的生命周期到引用对象域结束
		{
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		//若使用iterator做迭代器,会导致初始化的迭代器区间[first,last)只能是vector的迭代器
        //重新声明迭代器,迭代器区间[first,last)可以是任意容器的迭代器
		//[first,end)
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		//交换
		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& operator=(vector v)//可以不加模板参数
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

		//拷贝构造
		/*vector(const vector<T>& v)
		{
			reserve(v.capacity());
			for (auto e : v)
			{
				push_back(e);
			}
		}*/

		//拷贝构造(传统写法)
		//vector(const vector<T>& v)
		//{
		//	//reserve(v.capacity());
		//	_start = new T[v.capacity()];

		//	//如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃
		//	//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();
		//}

		//拷贝构造(现代写法)
		//vector(const vector& v)//可以不加模板参数
		vector(const vector<T>& v)
		{
			//借助区间构造迭代器去完成:vector(InputIterator first, InputIterator last)
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

		//析构
		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage;
		}

		//非const迭代器
		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		//const迭代器
		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		//重置size
		//T()采用匿名对象的方式生成一个默认缺省值
		//val不能置为0,因为T不一定为int类型
		void resize(size_t n, T val = T())
		{
			if (n < size())
			{
				//删除数据
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}

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

		//err
		//void reserve(size_t n)
		//{
		//	if (n > capacity())
		//	{
		//		T* tmp = new T[n];
		//		if (_start)
		//		{
		//			memcpy(tmp, _start, sizeof(T) * size());
		//			delete[] _start;
		//		}

		//		_start = tmp;
		//		_finish = _start + size();
		//		_end_of_storage = _start + n;
		//	}
		//}

		//不推荐,不利于代码维护
		/*void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				}

				_finish = tmp + size();
				_start = tmp;
				_end_of_storage = _start + n;
			}
		}*/

		//扩容
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				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 = _start + size();//err,因为size()=_finish-_start,所以_start+_finish-_start=_finish=nullptr。又因为_start已经发生变化,变成新空间的_start了,而_finish还是旧空间的_finish,,所以_finish-_start就是一个负值,再加_start就是0
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}

		//尾插
		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				//扩容
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}

			*_finish = x;
			++_finish;
		}

		//尾删
		void pop_back()
		{
			assert(!empty());

			--_finish;
		}

		//插入
		//迭代器失效:扩容引起,野指针问题
		iterator insert(iterator pos, const T& val)
		{
			assert(pos >= _start);
			assert(pos <= _finish);

			//检查容量
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;//扩容后要更新pos,解决pos失效问题
			}

			//挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			++_finish;

			return pos;
		}

		//删除
		//返回值是一个迭代器,并且是被删除元素的下一个位置的迭代器
		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

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

			return pos;
		}

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

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

		//大小
		size_t size() const
		{
			return _finish - _start;
		}

		//容量
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}

		//判空
		bool empty()
		{
			return _start == _finish;
		}

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

	//const对象的访问
	void func(const vector<int>& v)
	{
		//for循环
		for (size_t i = 0; i < v.size(); ++i)
		{
			cout << v[i] << " ";
		}
		cout << endl;

		//迭代器
		vector<int>::const_iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

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

		//const对象的访问
		func(v1);

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

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

		v1.pop_back();
		v1.pop_back();
		v1.pop_back();

		//范围for
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	//template<class T>
	//void f()
	//{
	//	T x = T();
	//	cout << x << endl;
	//}
	//
	//void test_vector2()
	//{
	//	//内置类型有没有构造函数
	//	int i = int();//调用默认构造
	//	int j = int(1);
	//	//int* pi = int* ();//err

	//	f<int>();
	//	f<int*>();
	//	f<double>();
	//}

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

		v1.resize(10);
		cout << v1.size() << endl;
		cout << v1.capacity() << endl;
		//for循环
		for (size_t i = 0; i < v1.size(); ++i)
		{
			cout << v1[i] << " ";
		}
		cout << endl;

		v1.resize(3);
		cout << v1.size() << endl;
		cout << v1.capacity() << endl;
		//for循环
		for (size_t i = 0; i < v1.size(); ++i)
		{
			cout << v1[i] << " ";
		}
		cout << endl;
	}

	//迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了
	//封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的
	//空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)

	//g++没有强制检查,具体问题具体分析,结果未定义

	void test_vector3()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);//迭代器失效,要更新pos

		v1.insert(v1.begin(), 0);
		func(v1);

		auto pos = find(v1.begin(), v1.end(), 3);
		if (pos != v1.end())
		{
			v1.insert(pos, 30);
			//pos = v1.insert(pos, 30);
		}
		func(v1);

		//insert以后,我们认为pos失效,不能再使用,用pos接收可以解决这个问题
		//(*pos)++;

		func(v1);
	}

	void test_vector4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		auto pos = find(v1.begin(), v1.end(), 3);
		if (pos != v1.end())
		{
			v1.erase(pos);
		}

		//erase以后,pos是否失效,答案是失效了,最好不要访问,因为行为结果未定义,跟具体编译器实现有关
		(*pos)++;

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	void test_vector5()
	{
		bite::vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		//要求删除所有的偶数
		bite::vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			if (*it % 2 == 0)
			{
				//v1.erase(it);//err
				it = v1.erase(it);//返回的是删除数据的下一个位置的迭代器
			}
			else
			{
				++it;
			}
		}
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	void test_vector6()
	{
		vector<int> v1(10, 5);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v2(v1.begin() + 1, v1.end() - 1);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;

		std::string s1("hello");
		vector<int> v3(s1.begin(), s1.end());
		for (auto e : v3)
		{
			cout << e << " ";
		}
		cout << endl;

		int a[] = { 10,1,5,20,30,40,50 };
		vector<int> v4(a, a + 3);
		for (auto e : v4)
		{
			cout << e << " ";
		}
		cout << endl;

		v1.insert(v1.begin(), 10);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		sort(v1.begin(), v1.end());
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		//sort(a, a + sizeof(a) / sizeof(a[0]));//升序

		//greater<int> g;
		//sort(a, a + sizeof(a) / sizeof(a[0]), g);//降序

		sort(a, a + sizeof(a) / sizeof(a[0]), greater<int>());

		for (auto e : a)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	class Solution
	{
	public:
		vector<vector<int>> generate(int numRows)//vector<vector<int>> vv;==>vv[i]对应vector<int>==>vv[i][j]对应int
		{
			vector<vector<int>> vv;
			vv.resize(numRows, vector<int>());//采用匿名对象对每一行进行初始化

			//初始化
			for (size_t i = 0; i < vv.size(); ++i)
			{
				vv[i].resize(i + 1, 0);
				vv[i][0] = vv[i][vv[i].size() - 1] = 1;//将每行的第一个和最后一个元素置为1
			}

			//遍历
			for (size_t i = 0; i < vv.size(); ++i)
			{
				for (size_t j = 0; j < vv[i].size(); ++j)
				{
					if (vv[i][j] == 0)
					{
						vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
					}
				}
			}
			return vv;
		}
	};

	void test_vector7()
	{
		vector<int> v1(10, 5);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v2(v1);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;

		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("2222222222222222222222");
		v4.push_back("2222222222222222222222");
		v4.push_back("2222222222222222222222");
		for (auto e : v4)
		{
			cout << e << " ";
		}
		cout << endl;

		//二维数组
		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;
	}

	void test_vector()
	{
		vector<string> v;

		v.push_back("111111111111111111111");
		v.push_back("222222222222222222222");
		v.push_back("333333333333333333333");
		v.push_back("444444444444444444444");

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

		v.push_back("555555555555555555555");
	}
}

test.c

#include<iostream>
#include<algorithm>
#include<functional>
#include<string>
#include<vector>
using namespace std;

#include"vector.h"

int main()
{
	//vector<int>::iterator it;
	//cout << typeid(it).name() << endl;

	bite::test_vector();

	return 0;
}

文章来源:https://blog.csdn.net/weixin_36194037/article/details/134695627
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。