Los iteradores son una forma alternativa de pasar por todos los elementos de un vector. Hasta ahora, si tenemos un vector como
vector<double> R;
para sumar todos sus elementos haríamos
double suma = 0.0; for (int i = 0; i < R.size(); i++) { suma += v[i]; }
Esto lo podemos hacer porque los índices nos permiten acceder a las casillas de un vector de forma instantánea. O sea, si tengo un índice (un entero), puedo acceder rápidamente a la casilla con ese índice. El bucle lo que hace es pasar por todos los índices.
Pero existe una forma alternativa, que no utiliza índices: utilizar iteradores. La necesidad de esto será mucho más evidente más adelante, con otras clases de la STL como list o map.
Pese a que muchas veces se denominan iteradores incluso las variables como la i del ejemplo anterior, los iteradores en la STL son objetos especiales. Para hacer todo el ejemplo, vamos a utilizar un vector de Booleanos llamado B, con la declaración siguiente:
vector<bool> B;
Entonces, un iterador es: un objeto que almacena la posición de un elemento particular dentro de un vector. Los iteradores pueden hacer 4 cosas básicas:
Estas cuatro operaciones, si uno lo piensa un poco, son las únicas necesarias para poder pasar por todos los elementos de un vector y hacer algo con ellos.
Para declarar un iterador se utiliza un tipo especial llamado, apropiadamente, iterator. Veamos un ejemplo, la siguiente instrucción declara un iterador para vectores de Booleanos:
vector<bool>::iterator it;
No hay que dejarse engañar, esto es una simple declaración de una variable (un objeto) como las de siempre. El tipo es lo que puede sorprender. Lleva un prefijo vector<bool>:: que indica "pertenencia a vector<bool>", y luego la palabra iterator (en inglés, iterador). O sea, el tipo del objeto it es vector<bool>::iterator que significa "iterador a vectores de booleanos". Es un poco largo, eso sí.
Para situar un iterador al principio de un vector hay que asignarle el valor apropiado, y éste se obtiene del vector. Es decir, se le pide al vector, usando el método begin (begin es principio en inglés), que devuelva la posición de la primera casilla . Se haría así:
it = B.begin();
Esto situa it al principio del vector B. Es interesante ver que el mismo iterador puede servir para muchos vectores y por tanto el método begin se debe llamar para el vector sobre el cual nos queremos situar.
Luego, se puede hacer un bucle donde se hace avanzar el iterador a cada paso hasta que se detecte el final. Para al avance, se utiliza el operador ++. O sea it++ situa el iterador en el elemento siguiente. Y para detectar el final, se utiliza un "centinela". O sea, sabremos que it ha pasado por todos los elementos cuando su valor sea el end del vector.
El método end de vectores devuelve la posición del "final" (que curiosamente es la posición de un elemento ficticio que está después del último). En definitiva, si escribimos la expresión (observa la comparación en vez de asignación):
it == B.end()
ésta será cierta si hemos llegado al final.
Si lo ponemos todo junto, tenemos la siguiente iteración, que pasa por todos los elementos del vector:
vector<bool>::iterator it; for (it = B.begin(); it != B.end(); it++) { // hacer algo con el elemento 'it' }
Este for empieza en el principio de B, a cada iteración mueve el iterador al siguiente elemento y lo compara con el final para saber si hay que acabar. Es bastante parecido al típico for con índices, pero cambian algunas cosas.
Hasta aquí el bucle, pero aunque pasamos por todos los elementos, no hacemos nada con ellos aún.
Nos falta, finalmente, el acceso al elemento. Es decir, ahora tenemos un bucle que pasa por todos los elementos pero no hacemos nada con ellos. Para conseguirlo, debemos usar it, que indica la posición de una casilla, para obtener el elemento en esa posición. Eso se consigue con el operador * (la desdirección y no la multiplicación), que proviene del tema de punteros. La idea es que it representa la posición de una casilla y *it su contenido.
Con esto podemos acabar el bucle y, por ejemplo, mostrar todos los elementos de B por la pantalla:
vector<bool>::iterator it; for (it = B.begin(); it != B.end(); it++) { cout << *it << endl; }
Si queremos negar todos los elementos de B (poner true donde había false y viceversa), podemos hacer esto:
vector<bool>::iterator it; for (it = B.begin(); it != B.end(); it++) { *it = ! *it; }
Es decir, *it se utiliza como una variable de tipo bool que representa la variable (la casilla) en la que está situado it.
Haz la acción muestra, que muestra un vector de enteros por pantalla utilizando iteradores.
Haz una acción multiplica_por_2 que recibe un vector de reales multiplica cada elemento por 2. Recorre el vector utilizando un iterador.
Haz un programa para probar la acción y utiliza la acción muestra para mostrar el vector antes y después de multiplicar por 2 sus casillas.
Haz una función producto_escalar que reciba dos vectores de reales y devuelva un real que es la suma de las multiplicaciones casilla por casilla de los dos vectores. Es decir, si el primer vector es [1, 2, 3] y el segundo es [4, 5, 6], el producto escalar es 32 (1·4 + 2·5 + 3·6).
Implementa primero la función sin iteradores y luego haces otra versión con iteradores.
Haz una función juntar que recibe un vector de strings y un caracter c que contiene una lista de palabras y devuelve un solo string que consiste en una frase que junta todas las palabras poniendo entre cada par de palabras el caracter c. Por ejemplo, si el vector es:
["Pistoleros", "de", "agua", "dulce"]
y el caracter c es un espacio, la función juntar deberá retornar:
"Pistoleros de agua dulce"
Haz una función partir (la contraria que juntar) que recibe una frase (un string) y devuelve un vector con las palabras por separado. Por ejemplo, si la función recibe la frase
"The Wild Bunch"
debe devolver el vector
["The", "Wild", "Bunch"]
En preparación