Entre os muitos contêineres da STL, o vetor é um dos contêineres mais comumente usados.A estrutura de dados subjacente é muito simples, apenas um espaço de memória linear contínuo.
Ao analisar o código-fonte do contêiner de vetor, não é difícil descobrir que ele é representado por 3 iteradores (que podem ser entendidos como ponteiros):
//_Alloc 表示内存分配器,此参数几乎不需要我们关心
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
...
protected:
pointer _Myfirst;
pointer _Mylast;
pointer _Myend;
};
Entre eles, _Myprimeiro aponta para a posição inicial do byte do objeto contêiner vetorial; _Mylast aponta para o byte final do último elemento atual; _myend aponta para o byte final do espaço de memória ocupado por todo o contêiner vetorial.
A Figura 1 demonstra para onde os três iteradores acima apontam.
Conforme mostrado na Figura 1, por meio desses 3 iteradores, um contêiner vetorial com 2 elementos e capacidade de 5 pode ser representado.
Com base nisso, combinar os três iteradores em pares também pode expressar diferentes significados, por exemplo:
- _Myfirst e _Mylast podem ser usados para representar o espaço de memória atualmente usado no contêiner de vetor;
- _Mylast e _Myend podem ser usados para representar o espaço de memória livre do contêiner de vetor;
- _Myfirst e _Myend podem ser usados para representar a capacidade do contêiner de vetor.
Para um contêiner de vetor vazio, uma vez que não há alocação de espaço para nenhum elemento, _Myfirst, _Mylast e _Myend são todos nulos.
Usando de forma flexível esses três iteradores, o contêiner de vetor pode facilmente implementar quase todas as funções, como a primeira e a última identificação, tamanho, contêiner e julgamento de contêiner vazio, como:
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
public:
iterator begin() {
return _Myfirst;}
iterator end() {
return _Mylast;}
size_type size() const {
return size_type(end() - begin());}
size_type capacity() const {
return size_type(_Myend - begin());}
bool empty() const {
return begin() == end();}
reference operator[] (size_type n) {
return *(begin() + n);}
reference front() {
return *begin();}
reference back() {
return *(end()-1);}
...
};
A essência da expansão vetorial
Além disso, é preciso destacar que quando o tamanho e a capacidade do vetor são iguais (tamanho == capacidade), ou seja, quando ele está cheio, se você adicionar elementos a ele, o vetor precisa ser expandido. O processo de expansão do recipiente vetorial requer as seguintes 3 etapas:
- Abandone completamente o espaço de memória existente e solicite um espaço de memória maior;
- Mova os dados do antigo espaço de memória para o novo espaço de memória na ordem original;
- Finalmente, o antigo espaço de memória é liberado.
Isso também explica porque os ponteiros, referências e iteradores relacionados ao contêiner de vetor podem se tornar inválidos após a expansão.
Pode-se ver que a expansão do vetor consome muito tempo . Para reduzir o custo de realocação de espaço de memória, o vetor irá solicitar mais espaço de memória do que o usuário precisa cada vez que ele se expandir (esta é a origem da capacidade do vetor, ou seja, capacidade> = tamanho) para uso posterior.
Quando o contêiner de vetor é expandido, diferentes compiladores solicitam mais espaço de memória de forma diferente. Tome o VS como exemplo, ele irá expandir 50% da capacidade do contêiner existente.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a;
cout << "a.size(): " << a.size() << " a.capacity(): " << a.capacity() << endl;
for (int i = 0; i < 10; i++)
{
a.push_back(i);
cout << "a.size(): " << a.size() << " a.capacity(): " << a.capacity() << endl;
}
return 0;
}