Vector

Introducción

Un vector nos sirve para almacenar en forma de array cualquier tipo de objeto debido a su posibilidad de declarar a los vectores mediante templates.

Anteriormente para hacer un array de un determinado tipo de objetos hubiésemos necesitado declarar un array de ese tipo de objetos más todas las funciones de gestión asociadas a dicho vector.

Con el objeto ‘vector’ de las STL todo esto se nos simplifica y nos ofrece mayor robustez y velocidad en nuestro código.

Consideraciones

Hay que tener en cuenta ciertas consideraciones para poder trabajar con los vectores de las STL.

Constructores

Como todos sabéis, los objetos necesitan constructores, pero los vectores de las STL, hacen uso en determinados casos de los constructores copias de los objetos.

Normalmente los constructores copia no son tan comúnmente implementados como los constructores por defecto. Yo concretamente os recomiendo que para todos aquellos objetos que vayáis a introducir en un vector, defináis el constructor copia de ese objeto

CObjeto::CObjeto( const CObjeto &inObj);

operador =

Todo objeto que vaya a ser introducido en un vector debe tener implementado o sobrecargado el operador ‘=’.

const CObjeto CObjeto::operator= (const CObjeto &inObj);

operador <

Si queremos llamar a las funciones de ordenación de los objetos de dentro del vector, estos tendrán que tener implementado el operador ‘<’ para que el vector los sepa ordenar.

Este operador NO es necesario mientras no se quieran ordenar los elementos, por lo tanto es opcional.

Funciones con ‘const’

Relacionado con el apartado del operador ‘=’, como el parámetro que recibe es un objeto marcado como const, todas las funciones de ese objeto que se utilicen en el operador ‘=’ deben ser de tipo const para eliminar posibles errores de código.

Un ejemplo podría ser el siguiente

Declaración:

unsigned int GetNumero() const;

Implementación:

unsigned int CObjeto::GetNumero() const
{
    return (m_iNum);
}

En el código anterior podemos observar como la función está marcada como const y es apta para ser utilizada en el cuerpo del operador ‘=’. De no marcar a la función como const, el compilador dará un error cuando este compilando el código del operador ‘=’.

Declaración

Para declarar en nuestro código un vector y poder utilizarlo será necesario incluir en nuestro código el include <vector>. Una vez declarado este include, ya podemos declarar nuestro vector indicando que tipo de objetos contendrá.

#include <vector>
 
void main()
{
    std::vector<int> vVec1;
    std::vector<float> vVec2;
    std::vector<CObject> vVec3;
}

Los objetos vector pertenecen al namespace std, esto implica que cada uno de los objetos vector deberá ser precedido en su declaración por el prefijo std:: para indicar que es de ese namespace.

Si queremos simplificar este aspecto, solo tenemos que añadir una línea después de los includes de nuestro código como podemos ver a continuación.

#include < vector >
 
using namespace std;
 
void main()
{
    vector<int> vVec1;
    vector<float> vVec2;
    vector<CObject> vVec3;
}

Esa línea nos indica que nuestro código ya conoce el namespace std y que por ello ya no es necesario preceder a los objetos de dicho espacio con su nombre.

Añadir elementos

Una vez disponemos de de un objeto vector declarado ya podemos empezar a añadir elementos a este.

Para poder añadir elementos el objeto vector nos proporciona dos funciones muy sencillas de utilizar, estas son push_back(…) y el assign(…)

push_back(…)

La función push_back (…) nos permite insertar nuestro elemento al final del vector, es decir, siempre que usemos esta función, nuestro elemento se colocará en la última casilla del vector.

A continuación podemos observar un ejemplo:

#include <string>
#include <sstream>
#include <algorithm>
#include <vector>
 
using namespace std;
 
void main()
{
    vector<int> vVec1;
 
    vVec1.push_back(1);
    vVec1.push_back(4);
    vVec1.push_back(2);
    vVec1.push_back(7);
}

En el código anterior podemos observar como introducimos 4 elementos y estos se insertan siempre al final del vector. Como resultado obtenemos el vector { 1, 4, 2, 7 }

assign(…)

La función assign(…) nos ayuda a asignar elementos al vector de una forma muy parecida a una inicialización.

Esta función tiene dos declaraciones:

void assign(int iCount, CObject inObj);

Usando esta anterior declaración le decimos al vector que reemplace lo que había en el vector por tantas copias de inObj como indique el numero iCount.

void assign(iterator iTer1, iterator iTer2);

Usando esta segunda declaración, le indicamos al vector que queremos que reemplace lo que había en el vector por los elementos que hay entre los iteradores de inicio (iTer1) y final (iTer2) de otro vector.

A continuación podemos observar un ejemplo de las dos declaraciones

#include <vector>
 
using namespace std;
 
void main()
{
    vector<int> vVec1;
    vector<int> vVec2;
 
    vVec1.assign(5,199); 
 
    vVec2.assign(vVec1.begin(),vVec1.end());
}

En el código anterior, el primer assign(…) nos introduce en el vector vVec1 los valores { 199, 199, 199, 199, 199 }

Con la segunda asignación, el vector vVec2 asigna todos los valores del primer vector en el segundo vector.

Consultar elementos

operador […]

El operador [] nos permite acceder directamente mediante un índice a la casilla deseada. Evidentemente este elemento al que queremos acceder debe haber sido insertado con anterioridad, pues si intentamos acceder a una casilla fuera del rango del vector, esta función provocará un error en tiempo de ejecución.

Veamos un ejemplo:

#include <vector>
 
using namespace std;
 
void main()
{
    vector<int> vVec1;
 
    vVec1.push_back(1);
    vVec1.push_back(4);
    vVec1.push_back(2);
    vVec1.push_back(7);
 
    vVec1[1] = 2;
    vVec1[2] = 3;
    vVec1[3] = 4;
 
    vVec1[4] = 5; //Provoca un error, esta fuera de rango
}

En el código anterior podemos observar como la última línea provoca un error en tiempo de ejecución irrecuperable debido a que se está accediendo fuera del rango del vector.

at(…)

Una función de igual comportamiento es la función at(…). Esta función funciona igual que la anterior a diferencia de que antes de insertar el elemento, hace la comprobación del rango del vector, si el índice esta fuera del rango, lanzara una excepción que podremos recoger en nuestro código y actuar en consecuencia.

Veamos un ejemplo de cómo utilizar estas funciones:

#include <vector>
 
using namespace std;
 
void main()
{
    vector<int> vVec1;
 
    vVec1.push_back(1);
    vVec1.push_back(4);
    vVec1.push_back(2);
    vVec1.push_back(7);
 
    vVec1.at(1) = 2;
    vVec1.at(2) = 3;
    vVec1.at(3) = 4;
    vVec1.at(4) = 5; //Lanza una excepción de error
}

En el código anterior podemos observar como accedemos a los elementos del vector y le asignamos otros valores distintos. También podemos ver como la última línea provoca un error, esta línea se puede tratar mediante una excepción e intentar recuperar.

El vector resultante si no hay errores seria { 1, 2, 3, 4 }

Tamaño

Una vez hemos insertado o consultado los elementos de un vector, otra de las operaciones típicas que podemos realizar es la de obtener el tamaño del vector.

Cuando hablamos del tamaño de un vector, lógicamente pensamos de que ese tamaño es un número y que solo puede ser representado por un número.

En el caso de los vectores que tratamos aquí existen dos tipos de tamaños. El primero es el número de elementos que ese vector almacena. El segundo es la cantidad de elementos más la cantidad de elementos no inicializados que hay en memoria esperando a ser inicializados.
Esto se debe en parte a una optimización del objeto vector y de sus funciones para añadir elementos al propio vector.

Cuando un elemento se inserta en un vector que contiene N elementos, se pide memoria para un vector de N+1 elementos. Luego se procede a copiar el antiguo vector en el nuevo añadiendo el nuevo elemento. Para finalizar liberamos la memoria del antiguo vector.

Para muchos elementos esta metodología puede ser un cuello de botella muy grande. Es por eso que existe este concepto de dos tamaños.

size()

Esta función nos devuelve la cantidad de elementos que tenemos en un vector. Veamos un ejemplo:

#include <vector>
 
using namespace std;
 
void main()
{
    vector<int> vVec1;
    int a;
 
    vVec1.push_back(1);
    vVec1.push_back(4);
    vVec1.push_back(2);
    vVec1.push_back(7);
 
    a = vVec1.size();
}

En el código anterior podemos observar como en la variable ‘a’ recibimos el tamaño del vector, en este caso concreto ‘a’ valdrá 4.

NOTA: El tipo de variable que devuelve size() es ‘size_t’. Si miráis donde está definido podréis observar que es un long, sabiendo esto y teniendo en cuenta el posible tamaño de vectores que utilicéis, podéis hacer un cast a otro tipo de variable pensando claro está en posibles pérdidas de precisión.

resize(…)

Esta función nos genera un vector con tantos elementos como nosotros le indiquemos y aparte de esto, estos elementos vendrán inicializados mediante su constructor por defecto.

Veamos un ejemplo lógico:

43 13 3 7

Como podemos ver, en el vector tenemos 4 elementos, si hacemos un riseze(10) entonces tendremos lo siguiente:

43 13 3 7 0 0 0 0 0 0

Obtenemos un vector de tamaño 10 en el cual, los 6 elementos restantes han sido insertados y inicializados a su constructor por defecto ( en el caso de los números estos se inicializan a 0 ).

Si el parámetro del resize(…) es menor que la cantidad actual de elementos que hay en el vector, este no tendrá efecto.

Esta función nos ayuda a reducir la cantidad de peticiones de memoria y posteriores copiados de elementos de un vector a otro.

Lo positivo de esta función es que una vez ejecutada, podemos acceder y modificar cualquiera de los 10 elementos que tenemos en el vector.

Lo negativo es que la función size() nos devolverá un valor de 10, y puede ser que esto no fuese lo que estábamos buscando.

#include <vector>
 
using namespace std;
 
void main()
{
    vector<int> vVec1;
    int a;
 
    vVec1.push_back(43);
    vVec1.push_back(13);
    vVec1.push_back(3);
    vVec1.push_back(7);
 
    vVec1.resize(10);
 
    vVec1[6] = 100;
}

El codigo anterior nos da como resultado el vector { 43, 13, 3, 7, 0, 0, 100, 0, 0, 0 }

reserve(…)

La función Reserve(…) nos reserva memoria en el vector para una cantidad de elementos a nuestra elección. Funciona de la misma forma que Resize(…) pero teniendo en cuenta de que solo pedimos la memoria, en ningún momento se añade ni inicializa ningún elemento mas del que teníamos anteriormente.

Veamos un ejemplo:

43 13 3 7

A partir del vector ejemplo anterior nosotros hacemos un reserve(10) y sucede lo siguiente:

43 13 3 7

Ya tenemos la memoria, ahora cuando añadamos un nuevo elemento, este no pedirá nueva memoria pues el lugar para colocarse ya lo tiene. Ahora en cambio la función size() ejecutada después de este reserve(…) nos devuelve el valor de 4.

#include <vector>
 
using namespace std;
 
void main()
{
    vector<int> vVec1;
    int a;
 
    vVec1.push_back(43);
    vVec1.push_back(13);
    vVec1.push_back(3);
    vVec1.push_back(7);
 
    vVec1.reserve(10);
 
    vVec1[6] = 100; // Error de acceso
}

El codigo anterior nos da como resultado un error de acceso

NOTA: Si intentamos acceder a un elemento no inicializado generado por el reserve(…) sin haber añadido el elemento, este dará un error porque el vector piensa que se están superando los limites superiores para acceso de los objetos.

capacity()

La función capacity() es la que nos devuelve la cantidad de elementos que el vector tiene reservados estén o no inicializados.

Si recuperamos el ejemplo anterior:

43 13 3 7

La función capacity() nos devuelve un valor de 10. La función size() nos devuelve un valor de 4.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License