Series Article Directory
Article Directory
foreword
vector is a class for variable-sized arrays
Introduction and use of vector
Introduction to vector
Documentation introduction of vector
- A vector is a sequence container representing a variable-sized array .
- Just like arrays, vectors also use contiguous storage space to store elements. That means that the elements of the vector can be accessed using subscripts, which is as efficient as an array. But unlike an array, its size can be changed dynamically, and its size will be automatically handled by the container.
- Essentially, vector uses a dynamically allocated array to store its elements. When new elements are inserted, the array needs to be resized in order to increase storage space. **The method is to allocate a new array and then move all elements to this array. **In terms of time, this is a relatively expensive task, because the vector does not re-allocate the size every time a new element is added to the container.
- vector allocation space strategy: vector will allocate some additional space to accommodate possible growth, because the storage space is larger than the actual storage space required. Different libraries employ different strategies for weighing space usage and reallocation. But in any case, the reallocation should be of logarithmically growing interval size, so that inserting an element at the end is done in constant time complexity.
- Therefore, vector takes up more storage space, in order to gain the ability to manage storage space, and grow dynamically in an efficient way.
- Compared with other dynamic sequence containers (deque, list and forward_list), vector is more efficient when accessing elements, and adding and removing elements at the end is relatively efficient . For other deletion and insertion operations that are not at the end, it is less efficient. Better than list and forward_list unified iterators and references
- The difference between vector and string : char is used in vector, although they all store char in an array at the bottom layer, they are still different. The space pointed to by the s object has '\0' at the end. Both string and vector have their own special requirements (interface), so the functions are different.
The use of vector
1. The definition of vector
(constructor) constructor declaration | Interface Description |
---|---|
vector() (emphasis) | No parameter construction |
vector(size_type n, const value_type& val = value_type()) | Construct and initialize n vals |
vector (const vector& x); (emphasis) | copy construction |
vector (InputIterator first, InputIterator last); | Initialize construction using iterators |
Vector construction code demonstration
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. The use of vector iterator
Except for string and vector, the remaining containers use iterators
The use of iterators | Interface Description |
---|---|
begin +end (emphasis) | Get the iterator/const_iterator of the first data position, get the iterator/const_iterator of the next position of the last data |
rbegin + rend | Get the reverse_iterator of the last data position, get the reverse_iterator of the previous position of the first data |
Vectot's iterator uses code demonstration
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 space growth problem
capacity space | Interface Description |
---|---|
size | Get the number of data |
capacity | Get the size of the capacity |
empty | Determine whether it is empty |
resize (emphasis) | Change the size of the vector |
reserve (emphasis) | Change the capacity of the vector |
- When the capacity code is run under vs and g++ respectively, it will be found that the capacity under vs increases by 1.5 times, and g++ increases by 2 times. This question is often investigated. Don’t think that the capacity of the vector is doubled. The specific increase is defined according to the specific needs. vs is the PJ version STL, and g++ is the SGI version STL.
- Reserve is only responsible for opening up space. If you know how much space is needed, reserve can alleviate the cost defect of vector capacity expansion.
- Resize will also be initialized while opening the space, which will affect the size.
- Try not to shrink the capacity. C++ does not support in-place shrinking. Generally, a new space will be re-opened, the data will be copied, and the original space will be released at last.
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. Add, delete, check and modify vector
Vector add, delete, check and modify | Interface Description |
---|---|
push_back (emphasis) | tail plug |
pop_back (emphasis) | tail delete |
find | find. (Note that this is an algorithm module implementation, not a member interface of vector) |
insert | Insert val before position |
erase | Delete the data at the position |
swap | Exchange the data space of two vectors |
operator[] (emphasis) | access like an array |
Vector insertion and deletion operation code demonstration
5. Other interfaces of vector
Can be used for all containers
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;
}
Iterator ranges are left-closed and right-open
6. The use of vector in OJ
2. Phone number letter combination OJ
vector depth analysis
built-in type constructor
Built-in types also have constructors, which are used to implement interval construction
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;
}
}
Iterator invalidation problem
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;
}
But it is better not to do this, so we think that pos is invalid after insert and can no longer be used
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;
}
After erasing, we think that pos is invalid and can no longer be used.
There will be a mandatory check under vs, and the iterator cannot be accessed after erasing.
g++ does not enforce checks, which will lead to a series of undefined results
Anonymous object lifetime extension
vector(size_t n, const T& val = T())
{
}
- const T& val = T() is to create an anonymous object, through the constructor, and then reference
- The anonymous object life cycle is only in the current line, because no one will use it after this line
- A const reference will extend the life cycle of the anonymous object to the end of the reference object field, because the reference is an alias, which is the anonymous object
object - Anonymous objects and temporary objects are const
vector memory layout
deep copy vs shallow copy
//拷贝构造
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 is a copy of memory in binary format. It copies the contents of one memory space to another memory space intact.
If the copied element is a custom type, memcpy is efficient and error-free.
If the object involves resource management, you must not use memcpy to copy between objects, because memcpy is a shallow copy, otherwise it may cause wild pointer problems or even program crashes.
Summarize
vector is a class that can resize arrays of any type.
It will be the top of the mountain, and you can see all the small mountains. ——Du Fu