1. 基本操作



#include<iostream>
#include<vector>
using namespace std;

int main()
{
vector<int>v1;
vector<int>v2(4);
vector<int>v3(1,4);

v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);

// 这里清除区间,左闭右开
v1.erase(v1.begin()+2, v1.end()-1);

v2 = v1;
if(!v2.empty())
{
for(const auto i : v2)
{
cout << i << " ";
}
cout << endl;
cout << "v1 size: " << v2.size() << endl;
v2.~vector();
}
return 0;
}


1. push_back和pop_back操作
都只是对尾部进行操作,
push_back:从尾部插入数据时,当数组还有备用空间时就直接插入尾部就行,如果没有就重新寻找更大的空间并将数据赋值过去
void push_back(const T& x)
{
// 如果还没有到填满整个数组, 就在数据尾部插入
if (finish != end_of_storage)
{
construct(finish, x);
++finish;
}
// 数组被填充满, 调用insert_aux必须重新寻找新的更大的连续空间, 再进行插入
else
insert_aux(end(), x);
}

pop_back: 从尾部删除,使用空间的尾自减并调用析构函数,但是并没有释放内存

void pop_back()
{
--finish;
destroy(finish);
}

finish始终都指向最后一个元素的后一个位置的地址.


2. 容器大小的调整
reverse: 修改容器的大小 空间大小
void reverse(size_type n)
{
// 修改容器的大小要大于之前的容器大小

}

resize: size() 使用大小, 变小释放,变大用尾部填充

new_size大于cap,小于2*cap,则用cap = 2*new_size
大于2*cap,则cap = new_size

void resize(size_type new_size, const T& x)
{
// 元素大小大于了要修改的大小, 则释放掉超过的元素
if (new_size < size())
erase(begin() + new_size, end());
// 元素不够, 就从end开始到要求的大小为止都初始化x
else
insert(end(), new_size - size(), x);
}


2. reverse 和 resize



// vector::reserve
#include <iostream>
#include <vector>

int main ()
{
std::vector<int>::size_type sz;

std::vector<int> foo;
sz = foo.capacity();
std::cout << "making foo grow:\n";
for (int i=0; i<1000; ++i) {
foo.push_back(i);
//std::cout << "i: " << i << '\n';
if (sz!=foo.capacity()) {
sz = foo.capacity();
std::cout << "capacity changed: " << sz << '\n';
}
}

std::cout << "foo size: " << foo.size() << '\n';
std::cout << "cap: " << foo.capacity() <<'\n';

sz = foo.capacity();
for(int i = 0;i < 1000;i++)
{
//std::cout << foo.pop_back() << '\n';
std::cout << "i: " << i << " cap: " << foo.capacity() << " size: " << foo.size()<< '\n';
foo.pop_back();
if (sz!=foo.capacity()) {
sz = foo.capacity();
std::cout << "capacity changed: " << sz << '\n';
}
}

return 0;
}


3. 测试



#include <iostream>
#include <vector>
using namespace std;

int main ()
{
std::vector<int>::size_type sz;
std::vector<int> foo;
vector<int>v1;
for(int i = 0;i < 12;i++)
{
v1.push_back(i);
cout << v1.capacity() << " " << v1.size() << endl;
}
cout << "max_size: " << v1.max_size() << endl;
}


可见,VS中按1.5倍扩容,GCC以2倍扩容。

一种不调用析构函数将vector清空的方法:



vector<int>().swap(v1);


4. 扩容因子

实际上,C++标准并没有push_back要用哪个增长因子,这是由标准库的实现者决定的。

如何选取扩容因子呢?

从空间角度:扩容因子越大,预留的空间就越大,浪费的空间也越多

从时间角度:扩展到相同长度下,K越小,扩容的次数越多,时间开销越大

假设扩容因子为k,扩容后的最终长度为n,这意味着需要扩容 $log_kn$ 次,

这元素复制和开辟内存的时间正比于: $t = 1 + k + k^2 + ... + k^{log_kn -1} = \frac{n-1}{k-1}$,可见k越小越好。

如何达到时间和空间的平衡呢?

我们来看一下K = 2时的情况。

每次扩容后capacity的情况如下:1,2,4,8,16,32 ……..

当我们释放了4的空间,我们寻找8的新空间,再次扩容,释放8,寻找16。。

仔细分析,第5次扩容时,需要寻找16的新空间,第4次释放了8,第3次释放了4,第2次释放了2,第1次释放了1,所以 1 + 2 + 4 + 8 = 15 < 16,也就意味着,之前释放的空间,永远无法被下一次的扩容利用,这对内存与cache是非常不友好的。

我们再来看一下K = 1.5的情况。

每次扩容之后capacity的情况为:1,2,3,4,6,9,13,19,28 ……

再按刚才的思路分析一遍,1 + 2 >= 3; 2 + 3 + 4 >= 6; 6 + 9 >= 13 …….

所以,当K为1.5时,显然对内存和cache要友好很多,至少从容量上来说,是存在重复利用的可能性的。

因此,我们可以得出结论,当K = 2时,时间上要比 K = 1.5 占优,而空间上比 1.5 稍有劣势。

理论最优扩容因子是多少呢?

继续刚才的分析,我们希望的是,上几次的空间,存在被下一次扩容时利用的可能性。

也就是 X(n-2) + X(n-1) >= X(n),显然我们也希望时间上也要更好,即X(n-2) + X(n-1) = X(n)

即:1,2,3,5,8,13,21,34,55 。。。。

是不是很熟悉。。。是的,这就是我们的斐波那契数列。。。

那么当N趋于无限大时,取极限,最佳的扩容因子也就是那个最美的数,黄金分割率,1.618。

  

个性签名:时间会解决一切