C++ Accesso agli elementi


Esempio

Esistono due modi principali per accedere agli elementi in un std::vector

Accesso basato su indice:

Questo può essere fatto sia con l'operatore pedice [] , sia con la funzione membro at() .

Entrambi restituiscono un riferimento all'elemento nella rispettiva posizione in std::vector (a meno che non sia un vector<bool> ), in modo che possa essere letto e modificato (se il vettore non è const ).

[] e at() differiscono per il fatto che [] non è garantito il controllo dei limiti, mentre at() fa. Accedere agli elementi in cui index < 0 o index >= size è un comportamento non definito per [] , mentre at() genera un'eccezione std::out_of_range .

Nota: gli esempi seguenti utilizzano l'inizializzazione in stile C ++ 11 per chiarezza, ma gli operatori possono essere utilizzati con tutte le versioni (a meno che non sia contrassegnato C ++ 11).

C ++ 11
std::vector<int> v{ 1, 2, 3 };
// using []
int a = v[1];    // a is 2
v[1] = 4;        // v now contains { 1, 4, 3 }

// using at()
int b = v.at(2); // b is 3
v.at(2) = 5;     // v now contains { 1, 4, 5 }
int c = v.at(3); // throws std::out_of_range exception

Poiché il metodo at() esegue il controllo dei limiti e può generare eccezioni, è più lento di [] . Questo rende [] codice preferito in cui la semantica dell'operazione garantisce che l'indice sia limitato. In ogni caso, gli accessi agli elementi dei vettori vengono eseguiti in tempo costante. Ciò significa che l'accesso al primo elemento del vettore ha lo stesso costo (nel tempo) dell'accesso al secondo elemento, al terzo elemento e così via.

Ad esempio, considera questo ciclo

for (std::size_t i = 0; i < v.size(); ++i) {
    v[i] = 1;
}

Qui sappiamo che la variabile indice i è sempre in perenne, quindi sarebbe uno spreco di cicli della CPU per verificare che i limiti per ogni chiamata operator[] .

Le funzioni membro front() e back() consentono un facile accesso di riferimento al primo e all'ultimo elemento del vettore, rispettivamente. Queste posizioni sono usate frequentemente e gli accessor speciali possono essere più leggibili delle loro alternative usando [] :

std::vector<int> v{ 4, 5, 6 }; // In pre-C++11 this is more verbose

int a = v.front();   // a is 4, v.front() is equivalent to v[0]
v.front() = 3;       // v now contains {3, 5, 6}
int b = v.back();    // b is 6, v.back() is equivalent to v[v.size() - 1]
v.back() = 7;        // v now contains {3, 5, 7}

Nota : è un comportamento indefinito invocare front() o back() su un vettore vuoto. È necessario verificare che il contenitore non sia vuoto usando la funzione membro empty() (che controlla se il contenitore è vuoto) prima di chiamare front() o back() . Segue un semplice esempio dell'uso di "empty ()" per verificare un vettore vuoto:

int main ()
{
  std::vector<int> v;
  int sum (0);

  for (int i=1;i<=10;i++) v.push_back(i);//create and initialize the vector

  while (!v.empty())//loop through until the vector tests to be empty
  {
     sum += v.back();//keep a running total
     v.pop_back();//pop out the element which removes it from the vector
  }

  std::cout << "total: " << sum << '\n';//output the total to the user

  return 0;
}

L'esempio sopra crea un vettore con una sequenza di numeri da 1 a 10. Quindi apre gli elementi del vettore fino a quando il vettore non è vuoto (usando 'empty ()') per impedire un comportamento indefinito. Quindi la somma dei numeri nel vettore viene calcolata e visualizzata all'utente.

C ++ 11

Il metodo data() restituisce un puntatore alla memoria grezza utilizzata da std::vector per memorizzare internamente i suoi elementi. Questo è più spesso usato quando si passa il dato vettoriale al codice legacy che si aspetta un array in stile C.

std::vector<int> v{ 1, 2, 3, 4 }; // v contains {1, 2, 3, 4}
int* p = v.data(); // p points to 1
*p = 4;            // v now contains {4, 2, 3, 4}
++p;               // p points to 2
*p = 3;            // v now contains {4, 3, 3, 4}
p[1] = 2;          // v now contains {4, 3, 2, 4}
*(p + 2) = 1;      // v now contains {4, 3, 2, 1}
C ++ 11

Prima di C ++ 11, il metodo data() può essere simulato chiamando front() e prendendo l'indirizzo del valore restituito:

std::vector<int> v(4);
int* ptr = &(v.front()); // or &v[0]

Questo funziona perché i vettori sono sempre garantiti per memorizzare i loro elementi in posizioni di memoria contigue, assumendo che il contenuto del vettore non sovrascriva l' operator& unario operator& . Se lo fa, dovrai implementare nuovamente std::addressof in pre-C ++ 11. Presume anche che il vettore non sia vuoto.

iteratori:

Gli iteratori sono spiegati in modo più dettagliato nell'esempio "Iterating over std::vector " e l'articolo Iterators . In breve, agiscono in modo simile ai puntatori agli elementi del vettore:

C ++ 11
std::vector<int> v{ 4, 5, 6 };

auto it = v.begin();
int i = *it;        // i is 4
++it; 
i = *it;            // i is 5
*it = 6;            // v contains { 4, 6, 6 }
auto e = v.end();   // e points to the element after the end of v. It can be 
                    // used to check whether an iterator reached the end of the vector:
++it; 
it == v.end();      // false, it points to the element at position 2 (with value 6)
++it;
it == v.end();      // true

È coerente con lo standard che gli iteratori di std::vector<T> siano in realtà T* s, ma la maggior parte delle librerie standard non lo fa. Non farlo migliora entrambi i messaggi di errore, cattura codice non portabile e può essere usato per strumentare gli iteratori con controlli di debug in build non a rilascio. Quindi, in build di rilascio, la classe che avvolge il puntatore sottostante viene ottimizzata.


È possibile mantenere un riferimento o un puntatore a un elemento di un vettore per l'accesso indiretto. Questi riferimenti o puntatori agli elementi nel vector rimangono stabili e l'accesso rimane definito a meno che non si aggiungano / rimuovano elementi ao prima dell'elemento nel vector o si provochi la modifica della capacità del vector . Questa è la stessa regola per invalidare gli iteratori.

C ++ 11
std::vector<int> v{ 1, 2, 3 };
int* p = v.data() + 1;     // p points to 2
v.insert(v.begin(), 0);    // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1;          // p points to 1
v.reserve(10);             // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1;          // p points to 1
v.erase(v.begin());        // p is now invalid, accessing *p is a undefined behavior.