vector容器:可以存储任意类型的元素,较之于string只能存储类型char。底层都是动态的顺序表
stl的容器都有一些同名的方法,实现功能都是类似的比如:一般都有size(),push_back(),empty()。。。。
连续的内存,故而支持随机访问,操作起来和在C语言中的数组差不多,当然vector< int >的对象有很多成员方法,使用起来更便捷。
容器是模版实现的,所以在vector创建对象要给出模版参数 vector< int >
构造方法:
//简写版本,去掉了最后一个参数默认的分配器,以及函数头部的关键字
vector<T>();
vector<T> (size_type n, const T& val = T());
vector<T>(const vector& x);
简单使用
vector<int> arr;//空int容器
arr.resize(10);
vector<int> v1(10);
vector<int> v2(2, 10);//两个元素,并且元素的值=10
//调试后可以看到vector会把所有元素初始化为0,如果不指定值
for (int i = 0; i < v1.size(); i++);
for (auto x : v2);//范围for
for (auto& x : v2);//加上引用,可以对vector中的元素进行修改
auto it = v1.begin();//使用迭代器
while (it != v1.end())
it++;
it + 1;//迭代器可以像指针一样+整数,就像指针移动一样
//int pos = it;//但不能当做下标
二维数组:
int n, m;
cin >> n >> m;
vector<vector<int>> vi(n, vector<int>(m));//vector创建二维数组arr[n][m]
//这样就实现了C/C++语言无法创建动态二维数组
vector< T > ( size_t n, const T& val = T() );
参考这个构造函数去理解为什么这样做也是合理的
此时 T:vector< int >
n:n
val:vector< int >(m)存储的值是一维数组,
const vector< int >& val = vector< int >(m);
这是一个匿名对象赋给了val
v1.push_back(10);//数组添加元素
v1[1] = 99;//其实操作就跟数组差不多
//数组的操作很多都会涉及到排序
sort(v1.begin(), v2.end());
sort(v1.begin(), v2.end(), greater<int>());
//第三个参数是可以使函数对象,函数指针
template <class T> struct greater : binary_function <T,T,bool> {
bool operator() (const T& x, const T& y) const {return x>y;}
};
仿函数重载函数调用运算符()
在functional头文件中还有一些类似的模版:
less,less_equal,
greater_equal,
equal_to,not_equal_to
因为是模版所以使用时要给出模版参数
*/
bool myfunction (int i,int j) { return (i<j); }
struct myclass {
bool operator() (int i,int j) { return (i<j);}
} myobject;
用vector存储自定义类型airplane
#include
using namespace std;
#include
class airplane
{
int need;
int have;
int come;
};
int main()
{
int n;
cin >> n;
auto arr = new airplane[n];
vector<airplane> arr2(5);
}
vector也是1.5被扩容(不同编译器实现不同)
#include
#include
using namespace std;
int main()
{
vector<int> v;
int cap = v.capacity();
cout << cap<<" ";
for (int i = 0; i < 100; i++)
{
v.push_back(i);
if (cap != v.capacity())
{
cap = v.capacity();
cout << cap << " ";
}
}
//容量变化:0 1 2 3 4 6 9 13 19 28 42 63 94 141
return 0;
}
迭代器:
类似于指针 || 对指针的封装
对于vector来说,迭代器就是 typedef T* iterator
获取迭代器 auto it = v.begin();
迭代器失效:迭代器越界了,指向的地址原本数据转移了
其实这是从指针的角度理解,实际迭代器既然是封装,那么就不会想指针那么干,指针比较底层直接
预防:在所有可能会导致迭代器失效的操作之后,需要使用迭代器时给迭代器重新赋值。
vector不同于string,其中的insert,erase操作的参数只能传递迭代器
为什么要这样设计呢?
我理解的是,因为string类确定要存储的就是char,而vector容器类是根据模版参数来实例化容器类的,无法确定其类型,所以要使用对应的迭代器,迭代器是带有类型的指针,也只能这样处理。
模拟实现vector:
#pragma once
//模拟实现vector
#include
namespace gyx
{
template<class T>
class vector
{
public:
typedef T* iterator;
private:
iterator start, finish, endofstorage;
//这些迭代器作为底层操作的指针
public:
vector()
:start(nullptr)
, finish(nullptr)
, endofstorage(nullptr)
{}
vector(int n, const T& val = T())
{
start = new T[n];
for (size_t i = 0; i < n; ++i)
start[i] = val;
finish = start + n;
endofstorage = finish;
}
template<class Iterator>
vector(Iterator first, Iterator last)
{
auto it = first;
size_t n = 0;
while (it != last)
{
++it;
n++;
}
start = new T[n];
while (first != last)
{
*finish = *first;
++first;
++finish;
}
}
vector(const vector<T>& v)//拷贝构造
{
start = new T[v.size()];
for (size_t i = 0; i < v.size(); ++i)
start[i] = v[i];
finish = start + n;
endofstorage = finish;
}
vector<T>& operator=(vector<T> v)
{
this->swap(v);
return *v;
}
~vector()
{
if (start)
{
delete[] start;
start = finish = endofstorage = nullptr;
}
}
iterator begin(){ return start; }
iterator end(){ return finish; }
size_t size()const{ return finish - start; }
size_t capacity()const{ return endofstorage - start; }
bool empty()const{ return start == finish; }
void reserve(size_t newcapacity)//扩容或者缩,一般都是扩
{
size_t oldcap = capacity();
if (newcapacity > oldcap)
{
T* temp = new T[newcapacity];
if (start)
{
for (size_t i = 0; i < size(); ++i)
temp[i] = start[i];
delete[] start;
}
size_t sz = size();
start = temp;
finish = start + sz;
endofstorage = start + newcapacity;
}
}
void resize(size_t newsize, const T& val = T())
{
size_t oldsize = size();
if (newsize < oldsize)
finish = start + newsize;
else
{
if (newsize>capacity())
reserve(newsize);
for (size_t i = oldsize; i < newsize; ++i)
start[i] = val;
finish = start + newsize;
}
}
T& front(){ return *start; }
const T& front()const{ return *start; }
T& back(){ return *(finish - 1); }
const T& back()const{ return *(finish - 1); }
T& operator[](size_t index)
{
assert(index < size());
return start[index];
}
const T& operator[](size_t index)const
{
assert(index < size());
return start[index];
}
T& at(size_t index)
{
// assert(index < size());
if (index >= size())
{
throw out_of_range("vector at method: index out_of_range");
}
return start[index];
}
const T& at(size_t index)const
{
if (index >= size())
{
throw out_of_range("vector at method: index out_of_range");
}
return start[index];
}
iterator erase(iterator first, iterator last)
{
auto copy_first = first;
auto copy_last = last;
size_t last - first;
while (copy_last != end())
{
*copy_first = *copy_last;
++copy_first;
++copy_last;
}
finish -= n;
return first;
}
void clear()
{
erase(begin(), end());
}
void swap(vector<T>& v)
{
std::swap(start, v.start);
std::swap(finish, v, finish);
std::swap(endofstorage, v.endofstorage);
}
void push_back(const T& val)
{
if (finish == endofstorage)
{
reserve(capacity() * 2);
}
*finish = val;
++finish;
}
void pop_back()
{
if (empty())
return;
--finish;
}
iterator insert(iterator pos, const T& val)
{
if (pos<begin() || pos>end())//位置不合法
return end();
if (finish == endofstorage)
{
size_t len = pos - start;
reserve(capacity() * 2+3);
pos = start + len;//扩容后呀更新pos,否则会有迭代器失效的可能
}
auto it = finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
it--;
}
*pos = val;//这句有问题吗?扩容之后pos位置的迭代器就失效了,这样赋值是有问题的
finish++;
return pos;
}
};
}
实现原理使用是三个迭代器(指针),来管理这段连续内存,注意在扩容后原先传入的迭代器失效问题。例如:在insert中,之前传入 的pos位置,在reserve扩容后很有可能申请了另一片连续的空间,那么之前传入的pos位置就失效了,要更新,所以在代码中的设计是用len保存步长(距离start的长度,来动态定位pos),如果没有reserve前后的那两行代码,测试下面代码就会报错
#include"MYvector.h"
using namespace gyx;
int main()
{
vector<int> arr;
for (int i = 0; i < 100; i++)
{
arr.insert(arr.begin(), i);
std::cout << i << " " << arr.begin();
}
return 0;
}
对于传入的自定义类型,要有无参构造,或者全缺省构造,因为在new申请空间时T* temp = new T[newcapacity];
,
只能调用无参构造,对于每一个存储的对象,在单独去用赋值运算符传递一个新的单独构造的对象。
拷贝元素:memcpy是浅拷贝
如果vector中放置的元素中涉及到资源管理时候,采用memecpy的浅拷贝是会出问题
memcpy(temp, start, sizeof(T) * size());
将容器当中的数据一个个拷贝过来时不能使用memcpy函数,当vector存储的数据是内置类型或无需进行深拷贝的自定义类型时,使用memcpy函数是没什么问题的,但当vector存储的数据是需要进行深拷贝的自定义类型时,使用memcpy函数的弊端就体现出来了。例如,当vector存储的数据是string类的时候。