Tome las siguientes dos líneas de código:
for (int i = 0; i < some_vector.size(); i++)
{
//do stuff
}
Y esto:
for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
some_iterator++)
{
//do stuff
}
Me dicen que se prefiere la segunda forma. ¿Por qué es esto exactamente?
some_iterator++
a++some_iterator
. El post-incremento crea un iterador temporal innecesario.end()
en la cláusula de declaración.vector::end
probablemente tenga que preocuparse por problemas peores que si se ha sacado de bucles o no. Personalmente, prefiero la claridad; sin embargo, si fuera una llamadafind
en la condición de terminación, me preocuparía.it != vec.end()
yit != end
, es entre(vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)
y(vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it)
. No necesito contar los personajes. Por supuesto, prefiera uno sobre el otro, pero el desacuerdo de otras personas con su preferencia no es "descuido", es una preferencia por un código más simple con menos variables y, por lo tanto, menos en qué pensar al leerlo.Respuestas:
La primera forma es eficiente solo si vector.size () es una operación rápida. Esto es cierto para los vectores, pero no para las listas, por ejemplo. Además, ¿qué planeas hacer dentro del cuerpo del bucle? Si planea acceder a los elementos como en
entonces estás asumiendo que el contenedor tiene
operator[](std::size_t)
definido. Nuevamente, esto es cierto para el vector pero no para otros contenedores.El uso de iteradores te acerca a independencia del contenedor . No está haciendo suposiciones sobre la capacidad de acceso aleatorio u
size()
operación rápida , solo que el contenedor tiene capacidades de iterador.Puede mejorar aún más su código utilizando algoritmos estándar. Dependiendo de lo que esté tratando de lograr, puede optar por usar
std::for_each()
,std::transform()
y así sucesivamente. Al usar un algoritmo estándar en lugar de un ciclo explícito, evitas reinventar la rueda. Es probable que su código sea más eficiente (dado que se elige el algoritmo correcto), correcto y reutilizable.fuente
size_t
miembro variable de seguimientosize()
.size()
se requerirá que la función miembro tenga una complejidad de tiempo constante para todos los contenedores que la soportan, incluidostd::list
.Es parte del moderno proceso de adoctrinamiento de C ++. Los iteradores son la única forma de iterar la mayoría de los contenedores, por lo que lo usa incluso con vectores solo para tener la mentalidad adecuada. En serio, esa es la única razón por la que lo hago: no creo que alguna vez haya reemplazado un vector con un tipo diferente de contenedor.
Wow, esto todavía se está rechazando después de tres semanas. Supongo que no vale la pena ser un poco irónico.
Creo que el índice de matriz es más legible. Coincide con la sintaxis utilizada en otros lenguajes y la sintaxis utilizada para las matrices de C antiguas. También es menos detallado. La eficiencia debería ser un lavado si su compilador es bueno, y casi no hay casos en los que sea importante de todos modos.
Aun así, todavía me encuentro usando iteradores con frecuencia con vectores. Creo que el iterador es un concepto importante, así que lo promociono siempre que puedo.
fuente
porque no está vinculando su código a la implementación particular de la lista some_vector. si usa índices de matriz, tiene que ser alguna forma de matriz; si usa iteradores, puede usar ese código en cualquier implementación de lista.
fuente
Imagine que some_vector se implementa con una lista vinculada. Luego, solicitar un elemento en el lugar número i requiere que se realicen operaciones para recorrer la lista de nodos. Ahora, si usa un iterador, en términos generales, hará su mejor esfuerzo para ser lo más eficiente posible (en el caso de una lista vinculada, mantendrá un puntero al nodo actual y avanzará en cada iteración, requiriendo solo un operación única).
Entonces proporciona dos cosas:
fuente
Voy a ser el defensor de los demonios aquí, y no recomendaré iteradores. La razón principal es que todo el código fuente en el que he trabajado, desde el desarrollo de aplicaciones de escritorio hasta el desarrollo de juegos, no he necesitado ni necesito usar iteradores. Todo el tiempo no han sido necesarios y, en segundo lugar, las suposiciones ocultas y el desorden de código y las pesadillas de depuración que obtienes con los iteradores los convierten en un excelente ejemplo para no usarlo en ninguna aplicación que requiera velocidad.
Incluso desde un punto de vista de mantenimiento son un desastre. No es por ellos sino por todos los alias que ocurren detrás de la escena. ¿Cómo sé que no ha implementado su propio vector virtual o lista de matriz que hace algo completamente diferente a los estándares? ¿Sé qué tipo es actualmente ahora durante el tiempo de ejecución? ¿Sobrecargó un operador? No tuve tiempo de verificar todo su código fuente. Demonios, ¿sé qué versión de STL estás usando?
El siguiente problema que tienes con los iteradores es la abstracción permeable, aunque hay numerosos sitios web que discuten esto en detalle con ellos.
Lo siento, aún no he visto ningún punto en los iteradores. Si abstraen la lista o el vector lejos de usted, cuando en realidad ya debería saber con qué vector o lista está tratando, si no lo hace, simplemente se preparará para algunas sesiones de depuración en el futuro.
fuente
Es posible que desee utilizar un iterador si va a agregar / eliminar elementos al vector mientras está iterando sobre él.
Si usara índices, tendría que mezclar elementos arriba / abajo en la matriz para manejar las inserciones y eliminaciones.
fuente
std::list
comparación con astd::vector
, si está recomendando usar una lista vinculada en lugar de astd::vector
. Consulte la página 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf En mi experiencia, he encontrado questd::vector
es más rápido que astd::list
incluso si lo busco todo y elimino elementos en posiciones arbitrarias.for (node = list->head; node != NULL; node = node->next)
más corta que las dos primeras líneas de código juntas (declaración y encabezado de bucle). Entonces, repito, no hay mucha diferencia fundamental en la brevedad entre usar iteradores y no usarlos, aún debe satisfacer las tres partes de unafor
declaración, incluso si usawhile
: declarar, iterar, verificar la terminación.Separación de intereses
Es muy agradable separar el código de iteración de la preocupación 'central' del bucle. Es casi una decisión de diseño.
De hecho, iterar por índice lo vincula a la implementación del contenedor. Al pedirle al contenedor un iterador de inicio y fin, habilita el código de bucle para usarlo con otros tipos de contenedor.
Además, en el
std::for_each
camino, le Dices a la colección qué hacer, en lugar de PREGUNTARle algo sobre sus elementos internosEl estándar 0x introducirá cierres, lo que hará que este enfoque sea mucho más fácil de usar: eche un vistazo al poder expresivo de, por ejemplo, el de Ruby
[1..6].each { |i| print i; }
...Actuación
Pero tal vez un problema mucho más supervisado es que, usar el
for_each
enfoque brinda la oportunidad de tener la iteración en paralelo: ¡los bloques de subprocesos de inteligencia pueden distribuir el bloque de código sobre la cantidad de procesadores en el sistema!Nota: después de descubrir la
algorithms
biblioteca, y especialmenteforeach
, pasé dos o tres meses escribiendo estructuras de operador 'auxiliares' ridículamente pequeñas que volverán locos a sus compañeros desarrolladores. Después de este tiempo, volví a un enfoque pragmático: los cuerpos de bucle pequeños no merecenforeach
más :)Una referencia de lectura obligatoria en los iteradores es el libro "STL extendido" .
El GoF tiene un pequeño párrafo al final del patrón Iterator, que habla sobre este tipo de iteración; se llama un "iterador interno". Echa un vistazo aquí también.
fuente
Porque está más orientado a objetos. si está iterando con un índice, está asumiendo:
a) que esos objetos están ordenados
b) que esos objetos pueden obtenerse mediante un índice
c) que el incremento del índice alcanzará cada elemento
d) que ese índice comienza en cero
Con un iterador, estás diciendo "dame todo para que pueda trabajar con él" sin saber cuál es la implementación subyacente. (En Java, hay colecciones a las que no se puede acceder a través de un índice)
Además, con un iterador, no hay necesidad de preocuparse por salir de los límites de la matriz.
fuente
Otra cosa buena de los iteradores es que te permiten expresar (y hacer cumplir) tu preferencia constante. Este ejemplo asegura que no alterará el vector en medio de su ciclo:
fuente
const_iterator
. Si modifico el vector en el bucle, lo hago por una razón, y durante el 99.9% de las veces que alterar no es un accidente, y por lo demás, es solo un error como cualquier tipo de error en el código, el autor Necesita arreglarlo. Porque en Java, y en muchos otros lenguajes, no hay ningún objeto constante, pero los usuarios de esos idiomas nunca tienen un problema sin soporte constante en esos idiomas.const_iterator
, ¿cuál podría ser el motivo?Aparte de todas las otras excelentes respuestas ...
int
puede que no sea lo suficientemente grande para su vector. En cambio, si desea usar la indexación, use elsize_type
para su contenedor:fuente
int i
amyvector.size()
.Probablemente debería señalar que también puedes llamar
std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);
fuente
Los iteradores STL están principalmente allí para que los algoritmos STL como sort puedan ser independientes del contenedor.
Si solo desea recorrer todas las entradas de un vector, use el estilo de bucle de índice.
Es menos tipeado y más fácil de analizar para la mayoría de los humanos. Sería bueno si C ++ tuviera un bucle foreach simple sin exagerar con la magia de la plantilla.
fuente
No creo que haga mucha diferencia para un vector. Prefiero usar un índice yo mismo, ya que considero que es más legible y puedes hacer acceso aleatorio como saltar 6 elementos hacia adelante o hacia atrás si es necesario.
También me gusta hacer una referencia al elemento dentro del bucle de esta manera para que no haya muchos corchetes alrededor del lugar:
El uso de un iterador puede ser bueno si cree que podría necesitar reemplazar el vector con una lista en algún momento en el futuro y también se ve más elegante para los fanáticos de STL, pero no puedo pensar en ninguna otra razón.
fuente
I prefer to use an index myself as I consider it to be more readable
solo en algunas situaciones; en otros, los índices se vuelven muy complicados rápidamente.and you can do random access
que no es una característica única de los índices: consulte en.cppreference.com/w/cpp/concept/RandomAccessIteratorLa segunda forma representa lo que estás haciendo con mayor precisión. En su ejemplo, realmente no le importa el valor de i: todo lo que quiere es el siguiente elemento en el iterador.
fuente
Después de haber aprendido un poco más sobre el tema de esta respuesta, me doy cuenta de que fue una simplificación excesiva. La diferencia entre este bucle:
Y este bucle:
Es bastante mínimo. De hecho, la sintaxis de hacer bucles de esta manera parece estar creciendo en mí:
Los iteradores desbloquean algunas características declarativas bastante poderosas, y cuando se combinan con la biblioteca de algoritmos STL, puedes hacer algunas cosas geniales que están fuera del alcance de la administración de índice de matriz.
fuente
for (Iter it = {0}; it != end; ++it) {...}
, simplemente omitió la declaración, por lo que la brevedad no es muy diferente de su segundo ejemplo. Aún así, +1.La indexación requiere una
mul
operación adicional . Por ejemplo, paravector<int> v
, el compilador se conviertev[i]
en&v + sizeof(int) * i
.fuente
sizeof
y simplemente agregarlo una vez por iteración, en lugar de hacer todo el cálculo de compensación nuevamente cada vez.Durante la iteración, no necesita saber la cantidad de elementos a procesar. Solo necesita el elemento y los iteradores hacen esas cosas muy bien.
fuente
Nadie mencionó aún que una ventaja de los índices es que no se invalidan cuando se agrega a un contenedor contiguo como
std::vector
, por lo que puede agregar elementos al contenedor durante la iteración.Esto también es posible con iteradores, pero debe llamar
reserve()
y, por lo tanto, debe saber cuántos elementos agregará.fuente
Varios buenos puntos ya. Tengo algunos comentarios adicionales:
Suponiendo que estamos hablando de la biblioteca estándar de C ++, "vector" implica un contenedor de acceso aleatorio que tiene las garantías de C-array (acceso aleatorio, diseño de memoria contigua, etc.). Si hubiera dicho 'some_container', muchas de las respuestas anteriores habrían sido más precisas (independencia del contenedor, etc.).
Para eliminar cualquier dependencia de la optimización del compilador, puede mover some_vector.size () fuera del bucle en el código indexado, de esta manera:
Siempre pre-incremente los iteradores y trate los post-incrementos como casos excepcionales.
Tan asumible e indexable
std::vector<>
como contenedor, no hay una buena razón para preferir uno sobre otro, pasando secuencialmente por el contenedor. Si tiene que referirse a índices de elementos más antiguos o más nuevos con frecuencia, entonces la versión indexada es más apropiada.En general, se prefiere usar los iteradores porque los algoritmos los usan y el comportamiento puede controlarse (y documentarse implícitamente) cambiando el tipo de iterador. Las ubicaciones de matriz se pueden usar en lugar de iteradores, pero la diferencia sintáctica se mantendrá.
fuente
No uso iteradores por la misma razón por la que no me gustan las declaraciones foreach. Cuando se tienen múltiples bucles internos, es bastante difícil hacer un seguimiento de las variables globales / miembros sin tener que recordar también todos los valores locales y los nombres de iterador. Lo que encuentro útil es usar dos conjuntos de índices para diferentes ocasiones:
Ni siquiera quiero abreviar cosas como "animation_matrices [i]" a algún "anim_matrix" -named-iterator al azar, por ejemplo, porque entonces no puedes ver claramente de qué matriz se originó este valor.
fuente
it
,jt
,kt
, etc, o incluso sólo seguir utilizandoi
,j
,k
, etc. Y si lo que necesita saber exactamente lo que un iterador representa, a continuación, a mí algo asífor (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()
sería más descriptivo que tener que indexar continuamente comoanimation_matrices[animIndex][boneIndex]
.for
ciclo basado en rango , lo que hace que la forma basada en iterador de hacer esto sea aún más concisa.Realmente, eso es todo lo que hay que hacer. No es como si fuera a ganar más brevedad de cualquier manera en promedio, y si la brevedad realmente es su objetivo, siempre puede recurrir a las macros.
fuente
Si tiene acceso a las funciones de C ++ 11 , también puede usar un bucle basado en rango
for
para iterar sobre su vector (o cualquier otro contenedor) de la siguiente manera:El beneficio de este bucle es que puede acceder a elementos del vector directamente a través de la
item
variable, sin correr el riesgo de estropear un índice o cometer un error al desreferenciar un iterador. Además, el marcador de posiciónauto
evita que tenga que repetir el tipo de elementos del contenedor, lo que lo acerca aún más a una solución independiente del contenedor.Notas:
operator[]
existe para su contenedor (y es lo suficientemente rápido para usted), entonces mejor vaya por su primera manera.for
bucle basado en rango no se puede usar para agregar / eliminar elementos en / desde un contenedor. Si quieres hacer eso, entonces mejor quédate con el solución dada por Brian Matthews.const
de la siguiente manera:for (auto const &item : some_vector) { ... }
.fuente
Incluso mejor que "decirle a la CPU qué hacer" (imperativo) es "decirle a las bibliotecas lo que quiere" (funcional).
Entonces, en lugar de usar bucles, debe aprender los algoritmos presentes en stl.
fuente
Para la independencia del contenedor
fuente
Siempre uso el índice de matriz porque muchas aplicaciones mías requieren algo así como "mostrar imagen en miniatura". Entonces escribí algo como esto:
fuente
Ambas implementaciones son correctas, pero preferiría el bucle 'for'. Como hemos decidido usar un Vector y no cualquier otro contenedor, usar índices sería la mejor opción. El uso de iteradores con vectores perdería el beneficio de tener los objetos en bloques de memoria continua que ayudan a facilitar su acceso.
fuente
Sentí que ninguna de las respuestas aquí explica por qué me gustan los iteradores como concepto general sobre la indexación en contenedores. Tenga en cuenta que la mayor parte de mi experiencia usando iteradores en realidad no proviene de C ++ sino de lenguajes de programación de nivel superior como Python.
La interfaz de iterador impone menos requisitos a los consumidores de su función, lo que permite a los consumidores hacer más con ella.
Si todo lo que necesita es poder iterar hacia adelante, el desarrollador no se limita a usar contenedores indexables, pueden usar cualquier clase de implementación
operator++(T&)
,operator*(T)
yoperator!=(const &T, const &T)
.Su algoritmo funciona para el caso que lo necesite, iterando sobre un vector, pero también puede ser útil para aplicaciones que no necesariamente anticipa:
Intentar implementar un operador de corchetes que haga algo similar a este iterador sería ingenioso, mientras que la implementación del iterador es relativamente simple. El operador de corchetes también tiene implicaciones sobre las capacidades de su clase, que puede indexar a cualquier punto arbitrario, lo que puede ser difícil o ineficiente de implementar.
Los iteradores también se prestan a la decoración . Las personas pueden escribir iteradores que toman un iterador en su constructor y amplían su funcionalidad:
Si bien estos juguetes pueden parecer mundanos, no es difícil imaginar usar iteradores y decoradores de iteradores para hacer cosas poderosas con una interfaz simple: decorar un iterador de resultados de base de datos de solo avance con un iterador que construye un objeto modelo a partir de un solo resultado, por ejemplo . Estos patrones permiten la iteración de conjuntos infinitos con uso eficiente de la memoria y, con un filtro como el que escribí anteriormente, una evaluación de resultados potencialmente perezosa.
Parte de la potencia de las plantillas de C ++ es su interfaz de iterador, cuando se aplica a conjuntos de C de longitud fija, se descompone en aritmética de puntero simple y eficiente , lo que la convierte en una abstracción de costo cero.
fuente