C++初阶:STL之vector类模板
目录
一.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> v
和string 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 |
详解:
迭代器主要有两种形式:一种是可读可写的迭代器,一种是只读的常量迭代器:
- 正向迭代器:容器类名::iterator 迭代器名;
- 常量正向迭代器:容器类名::const_iterator 迭代器名;
- 反向迭代器:容器类名::reverse_iterator 迭代器名;
- 常量反向迭代器:容器类名::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进行修改,并且也会进行一定的扩容。
改进:
小结:
- capacity的代码在vs和g++下分别运行,可以发现:vs下capacity是按1.5倍增长的,g++下capacity是按2倍增长的。这个问题经常会考察,不要固化的认为vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL;
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题;
- 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* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
情况分类:
-
如果容器扩容,并在其他地方重新开辟了一块空间,那么原来容器底层的空间上所保存的迭代器全部失效;
-
当容器调用
insert()
方法后,当前位置到容器末尾元素的所有迭代器全部失效; -
当容器调用
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调用析构函数时会对这个野指针进行二次释放,进而会导致程序崩溃。
小结:
- memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中;
- 如果拷贝的是自定义类型的元素,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大小。分三种情况进行讨论:
n < _finish;
_finish <= n <= _end_of_storage
;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;
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!