Iterar a través de un vector de C ++ usando un bucle 'for'

140

Soy nuevo en el lenguaje C ++. He comenzado a usar vectores, y he notado que en todo el código veo iterar a través de un vector a través de índices, el primer parámetro del forbucle siempre es algo basado en el vector. En Java, podría hacer algo como esto con una ArrayList:

for(int i=0; i < vector.size(); i++){
   vector[i].doSomething();
}

¿Hay alguna razón por la que no veo esto en C ++? ¿Es una mala práctica?

Flynn
fuente
1
El bucle for no es una función, por lo que no tiene parámetros (o argumentos, que es lo que pasa). ¿Te refieres a algo así std::vector<int>::size_type i = 0;o tal vez std::vector<int>::iterator it = vector.begin();?
Chris
Exactamente, todos los ejemplos que veo están escritos así.
Flynn
44
En Java, preferiría un ciclo for-each o usar iteradores. Más o menos lo mismo que C ++, aunque sintaxis ligeramente diferente.
Jesse Good
10
La mayoría de las respuestas aquí suponen incorrectamente que la Q es: ¿Cuál es la mejor / más corta forma de repetir std::vector? , la pregunta real que se pregunta aquí es: ¿Hay alguna razón por la que no veo esto en C ++? ¿Es una mala práctica? aka ¿Por qué siempre veo código en C ++ que usa iteradores mientras itera std::vector?
Alok Save

Respuestas:

93

¿Hay alguna razón por la que no veo esto en C ++? ¿Es una mala práctica?

No. No es una mala práctica, pero el siguiente enfoque hace que su código tenga cierta flexibilidad .

Por lo general, antes de C ++ 11, el código para iterar sobre elementos de contenedor usa iteradores, algo así como:

std::vector<int>::iterator it = vector.begin();

Esto se debe a que hace que el código sea más flexible.

Todos los contenedores de biblioteca estándar admiten y proporcionan iteradores. Si en un momento posterior del desarrollo necesita cambiar a otro contenedor, entonces este código no necesita ser cambiado.

Nota: Escribir código que funcione con todos los contenedores de biblioteca estándar posibles no es tan fácil como parece.

Alok Save
fuente
25
¿Podría alguien explicarme por qué en este caso / fragmento de código en particular aconseja a los iteradores sobre la indexación? ¿De qué "flexibilidad" estás hablando? Personalmente, no me gustan los iteradores, hinchan el código, simplemente más caracteres para escribir con el mismo efecto. Especialmente si no puedes usar auto.
Violet Giraffe
8
@VioletGiraffe: mientras se usan iteradores, es difícil equivocarse con ciertos casos, como los rangos vacíos, y el código es más detallado.
Alok Save
9
¿Por qué solo muestra cómo declarar el iterador pero no cómo usarlo para hacer el ciclo ...?
underscore_d
116

La razón por la que no ve tal práctica es bastante subjetiva y no puede tener una respuesta definitiva, porque he visto muchos de los códigos que utilizan su manera mencionada en lugar de iteratorcódigo de estilo.

Los siguientes pueden ser motivos de personas que no consideran la vector.size()forma de bucle:

  1. Ser paranoico acerca de las llamadas size()cada vez en la condición de bucle. Sin embargo, no es un problema o se puede solucionar de manera trivial
  2. Prefiriendo std::for_each()sobre el forciclo en sí
  3. Más tarde cambiar el contenedor desde std::vectora otro (por ejemplo map, list) también exigir el cambio del mecanismo de bucle, porque no todos los soporte de contenedor size()estilo de bucle

C ++ 11 proporciona una buena instalación para moverse a través de los contenedores. Eso se llama "rango basado para bucle" (o "mejorado para bucle" en Java).

Con un código pequeño, puede atravesar el completo (obligatorio) std::vector:

vector<int> vi;
...
for(int i : vi) 
  cout << "i = " << i << endl;
iammilind
fuente
12
Solo para notar una pequeña desventaja del rango basado en el bucle : no puede usarlo con #pragma omp parallel for.
liborm
2
Me gusta la versión compacta porque hay menos código para leer. Una vez que realiza el ajuste mental, es mucho más fácil de entender y los errores se destacan más. También lo hace mucho más obvio cuando ocurre una iteración no estándar porque hay una porción de código mucho más grande.
Code Abominator
87

La forma más limpia de iterar a través de un vector es a través de iteradores:

for (auto it = begin (vector); it != end (vector); ++it) {
    it->doSomething ();
}

o (equivalente a lo anterior)

for (auto & element : vector) {
    element.doSomething ();
}

Antes de C ++ 0x, debe reemplazar auto por el tipo de iterador y usar funciones miembro en lugar de comenzar y finalizar funciones globales.

Esto probablemente es lo que has visto. En comparación con el enfoque que menciona, la ventaja es que no depende en gran medida del tipo de vector. Si cambia vectora una clase diferente de "tipo de colección", su código probablemente seguirá funcionando. Sin embargo, también puede hacer algo similar en Java. No hay mucha diferencia conceptual; C ++, sin embargo, utiliza plantillas para implementar esto (en comparación con los genéricos en Java); por lo tanto el enfoque de trabajo para todos los tipos para los que beginy endfunciones están definidas, incluso para tipos no clase, tales como matrices estáticas. Vea aquí: ¿Cómo funciona el rango basado en trabajo para matrices simples?

JohnB
fuente
55
auto, inicio / fin gratis también son C ++ 11. Y también, debe usar ++, en lugar de ++ en muchos casos.
ForEveR
Sí tienes razón. Implementar beginy end, sin embargo, es una línea.
JohnB
@JohnB es más que un trazador de líneas, porque también funciona para matrices de tamaño fijo. autopor otro lado sería bastante complicado.
juanchopanza
Si lo necesita solo para el vector, es una línea.
JohnB
Aún así, el primer ejemplo es engañoso, ya que no puede funcionar en C ++ 03, mientras que su redacción sugiere que sí.
juanchopanza
35

La forma correcta de hacerlo es:

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    it->doSomething();
 }

Donde T es el tipo de la clase dentro del vector. Por ejemplo, si la clase era CActivity, simplemente escriba CActivity en lugar de T.

Este tipo de método funcionará en cada STL (no solo en vectores, que es un poco mejor).

Si aún desea usar índices, la forma es:

for(std::vector<T>::size_type i = 0; i != v.size(); i++) {
    v[i].doSomething();
}
DiGMi
fuente
no es std::vector<T>::size_typesiempre size_t? Ese es el tipo que siempre uso para ello.
Violet Giraffe
1
@VioletGiraffe Estoy bastante seguro de que tienes razón (realmente no lo he verificado), pero es una mejor práctica usar std :: vector <T> :: size_type.
DiGMi
8

Hay un par de fuertes razones para usar iteradores, algunos de los cuales se mencionan aquí:

Cambiar contenedores más tarde no invalida su código.

es decir, si pasa de un std :: vector a un std :: list, o std :: set, no puede usar índices numéricos para obtener su valor contenido. Usar un iterador sigue siendo válido.

Captura de tiempo de ejecución de iteración no válida

Si modifica su contenedor en el medio de su ciclo, la próxima vez que use su iterador arrojará una excepción de iterador no válida.

Eddie Parker
fuente
1
¿podría señalar algún artículo / publicación que explique los puntos anteriores con un código de ejemplo? ¡sería genial! o si pudieras agregar uno :)
Anu
5

Me sorprendió que nadie mencionara que iterar a través de una matriz con un índice entero facilita la escritura de código defectuoso al suscribir una matriz con el índice incorrecto. Por ejemplo, si tiene bucles anidados utilizando iy jcomo índices, podría subíndice incorrectamente una matriz en jlugar dei e introducir una falla en el programa.

Por el contrario, las otras formas enumeradas aquí, a saber, el forbucle basado en rango y los iteradores, son mucho menos propensos a errores. La semántica del lenguaje y el mecanismo de verificación de tipo del compilador le impedirá acceder accidentalmente a una matriz usando el índice incorrecto.

Diomidis Spinellis
fuente
4

Con STL, los programadores utilizan iteratorspara atravesar contenedores, ya que iterador es un concepto abstracto, implementado en todos los contenedores estándar. Por ejemplo, std::listno tiene operator []en absoluto.

Siempre
fuente
3

El uso del operador automático realmente facilita su uso, ya que uno no tiene que preocuparse por el tipo de datos y el tamaño del vector o cualquier otra estructura de datos

Vector iterativo usando auto y para loop

vector<int> vec = {1,2,3,4,5}

for(auto itr : vec)
    cout << itr << " ";

Salida:

1 2 3 4 5

También puede usar este método para iterar conjuntos y listas. Usando automático detecta automáticamente el tipo de datos utilizado en la plantilla y le permite utilizar la misma. Así que, incluso si tuviéramos una vectorde stringo charla misma sintaxis funcionará bien

Hrishikesh
fuente
1

La forma correcta de iterar el ciclo e imprimir sus valores es la siguiente:

#include<vector>

//declare the vector of type int
vector<int> v;

//insert the 5 element in the vector
for ( unsigned int i = 0; i < 5; i++){
    v.push_back(i);
}

//print those element
for (auto it = 0; it < v.end(); i++){
    std::cout << *it << std::endl;
}
Nikhil Rai
fuente
1

Aquí hay una forma más simple de iterar e imprimir valores en el vector.

for(int x: A) // for integer x in vector A
    cout<< x <<" "; 
Akram Mohammed
fuente
0
 //different declaration type
    vector<int>v;  
    vector<int>v2(5,30); //size is 5 and fill up with 30
    vector<int>v3={10,20,30};
    
    //From C++11 and onwards
    for(auto itr:v2)
        cout<<"\n"<<itr;
     
     //(pre c++11)   
    for(auto itr=v3.begin(); itr !=v3.end(); itr++)
        cout<<"\n"<<*itr;
bashar
fuente