C++ Elementos de acceso


Ejemplo

Hay dos formas principales de acceder a los elementos en un std::vector

Acceso basado en índices:

Esto se puede hacer con el operador de subíndice [] o la función miembro at() .

Ambos devuelven una referencia al elemento en la posición respectiva en std::vector (a menos que sea un vector<bool> ), para que pueda leerse y modificarse (si el vector no es const ).

[] y at() difieren en que [] no se garantiza que realice ninguna comprobación de límites, mientras que at() hace. El acceso a los elementos donde index < 0 o index >= size es un comportamiento indefinido para [] , mientras que at() lanza una excepción std::out_of_range .

Nota: Los ejemplos a continuación utilizan la inicialización del estilo C ++ 11 para mayor claridad, pero los operadores se pueden usar con todas las versiones (a menos que estén marcados con 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

Debido a que el método at() realiza la verificación de límites y puede lanzar excepciones, es más lento que [] . Esto hace que [] código preferido donde la semántica de la operación garantice que el índice está dentro de los límites. En cualquier caso, los accesos a elementos de vectores se realizan en tiempo constante. Eso significa que acceder al primer elemento del vector tiene el mismo costo (en el tiempo) de acceder al segundo elemento, al tercer elemento y así sucesivamente.

Por ejemplo, considere este bucle

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

Aquí sabemos que la variable de índice i siempre está dentro de los límites, por lo que sería una pérdida de ciclos de CPU comprobar que i está dentro de los límites para cada llamada al operator[] .

Las funciones de miembro front() y back() permiten un acceso de referencia fácil al primer y último elemento del vector, respectivamente. Estas posiciones se utilizan con frecuencia y los accesores especiales pueden ser más legibles que sus alternativas utilizando [] :

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 : es un comportamiento indefinido invocar front() o back() en un vector vacío. Debe verificar que el contenedor no esté vacío utilizando la función miembro empty() (que verifica si el contenedor está vacío) antes de llamar a front() o back() . A continuación se muestra un ejemplo simple del uso de 'empty ()' para probar un vector vacío:

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;
}

El ejemplo anterior crea un vector con una secuencia de números del 1 al 10. Luego saca los elementos del vector hasta que el vector está vacío (usando 'vacío ()') para evitar un comportamiento indefinido. Luego, la suma de los números en el vector se calcula y se muestra al usuario.

C ++ 11

El método data() devuelve un puntero a la memoria sin formato utilizada por std::vector para almacenar internamente sus elementos. Esto se usa con más frecuencia cuando se pasan los datos vectoriales a un código heredado que espera una matriz de estilo 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

Antes de C ++ 11, el método data() se puede simular llamando a front() y tomando la dirección del valor devuelto:

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

Esto funciona porque los vectores siempre tienen la garantía de almacenar sus elementos en ubicaciones de memoria contiguas, asumiendo que el contenido del vector no anula al operator& unario operator& . Si lo hace, tendrás que volver a implementar std::addressof en pre-C ++ 11. También supone que el vector no está vacío.

Iteradores

Los iteradores se explican más detalladamente en el ejemplo "Iterando sobre std::vector " y el artículo Iteradores . En resumen, actúan de manera similar a los punteros a los elementos del vector:

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

Es consistente con el estándar que los iteradores de std::vector<T> realidad son T* s, pero la mayoría de las bibliotecas estándar no lo hacen. No hacer esto mejora los mensajes de error, atrapa el código no portátil y se puede usar para instrumentar los iteradores con las comprobaciones de depuración en las compilaciones no liberadas. Luego, en las compilaciones de lanzamiento, la clase que rodea el puntero subyacente se optimiza.


Puede persistir una referencia o un puntero a un elemento de un vector para el acceso indirecto. Estas referencias o punteros a elementos en el vector permanecen estables y el acceso permanece definido a menos que agregue / elimine elementos en o antes del elemento en el vector , o haga que cambie la capacidad del vector . Esta es la misma que la regla para invalidar iteradores.

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.