Vectores estilo C¶
Los datos de un vector estilo C (matriz unidimensional) se caracterizan por:
Ocupan posiciones consecutivas en memoria
Son del mismo tipo
Su número es inalterable: se decide en tiempo de compilación
La definición de un vector estilo C sigue la siguiente sintaxis:
tipo_dato nombre_vector[numero_elementos];
Por ejemplo,
int c[10];
double mi_vector[53];
Pueden declararse múltiples vectores del mismo tipo en la misma línea:
int b[100], c[27];
double x[4], y[23];
Inicialización de vectores¶
Se permiten diversas formas. Por ejemplo:
int n[5] = {1, 2, 3, 4, 5};
o mejor:
int n[5]{1, 2, 3, 4, 5};
Si no hay suficientes valores de inicialización,
los valores más a la derecha se inicializan
automáticamente a 0
.
int n[5]{0}; // Todos los valores a 0
Si se añaden demasiados valores, el compilador generará un error de síntaxis.
int n[5]{1,2,3,4,5,6}; // ¡ERROR!
Si el tamaño se omite, el compilador genera el tamaño de forma automática.
int n[]{1,2,3,4,5};
Uso de vectores estilo C¶
Para usar un elemento de un vector, debemos especificar:
el nombre o identificador del vector,
nombre_vector
.entre corchetes, el índice
i
de su posición respecto al primer elemento
nombre_vector[i]
Los corchetes, []
, son un operador en sí mismo, el operador de indexación.
Es importante tener en cuenta que el primer elemento de un vector tiene
índice 0
. Por tanto, si un vector v
tiene n
elementos,
para acceder al último de ellos deberá usarse v[n-1]
.
Los elementos de un vector se usan de forma idéntica a las variables normales:
v[0] = 3;
y = coef[2]*x*x+ coef[1]*x + coef[0];
cout << v[0] << endl;
Pueden realizarse operaciones dentro de los corchetes siempre que la expresión sea entera:
c[5-2] = 4;
c[3] = c[i*j]; // i y j variables enteras
En tiempo de ejecución no es posible chequear si se sobrepasan los límites del vector. Por tanto, si los sobrepasamos, se tendrá un error semántico, normalmente de consecuencias catastróficas.
int v[10];
...
v[10] = 11; // ¡ERROR EN TIEMPO DE EJECUCIÓN!
Ventaja del uso de vectores¶
Supongamos que hay que almacenar un conjunto de 10 datos enteros para su procesamiento posterior. Hemos de leerlos desde teclado.
Un fragmento de una versión sin vectores sería la siguiente:
int a0, a1, a2, a3, a4, a5, a6, a7, a8, a9;
cout << "Introduzca dato 1 \n";
cin >> a0;
...
cout << "Introduzca dato 10 \n";
cin >> a9;
Nótese que, a pesar de ser una tarea repetitiva, no podemos usar bucles. Si el número de datos creciese, el programa se tornaría ilegible e inmanejable.
Por el contrario, un fragmento de una versión con vectores sería la siguiente:
int a[10];
for (int i = 0; i < 10; ++i)
{
cout << "Introduzca el dato " << i+1 << ": ";
cin >> a[i];
}
Ejemplo 1
El siguiente programa almacena en un vector los cuadrados de los enteros en el rango \([0,9]\).
#include <iostream>
using namespace std;
int main()
{
const int num_elementos = 10;
int cuadrado[num_elementos];
for (int i = 0; i < num_elementos; ++i)
cuadrado[i] = i*i;
for (int i = 0; i < num_elementos; ++i)
cout << cuadrado[i] << endl;
}
Edita, compila y ejecuta el código
Nótese el uso del especificador const
para definir e inicializar
la variable num_elementos
:
Se mejora la legibilidad, al evitar utilizar a lo largo del código la constante literal
10
.Si en una versión posterior del programa es necesario alterar el número de elementos, sólo hace falta cambiar esa línea.
El especificador
const
garantiza que no podamos, por error, alterar ese valor a lo largo del código.
Ejemplo 2
Se solicita la edad de los alumnos de una clase y se calcula la media.
#include <iostream>
using namespace std;
int pide_entero(int inf, int sup, string mensaje);
int main() // Calcula la edad media de una clase de alumnos
{
const int num_max_alumnos = 100;
int edad[num_max_alumnos];
int num_alumnos = pide_entero(1, num_max_alumnos, "numero de alumnos");
for(int i = 0; i < num_alumnos; ++i)
{
cout << "\nIntroduce la edad del alumno " << i+1 << ": ";
cin >> edad[i];
}
double media = 0.;
for(int i = 0; i < num_alumnos; ++i)
media += edad[i];
media /= num_alumnos;
cout << "La edad media es " << media << endl;
}
int pide_entero(int inf, int sup, string mensaje)
{
int valor;
do
{
cout << "Introduzca el " << mensaje << ": ";
cin >> valor;
if (valor > sup)
cout << "\n\nERROR: el " << mensaje << " no puede sobrepasar "
<< sup << "\n\n";
else if (valor < inf)
cout << "\n\nERROR: el " << mensaje << " debe ser al menos "
<< inf << "\n\n";
}while (valor > sup || valor < inf);
return valor;
}
Edita, compila y ejecuta el código
La función pide_entero()
es reutilizable para todos aquellos problemas que
necesiten introducir un valor perteneciente a un intervalo.
Vectores estilo C y punteros¶
Los conceptos de vector y puntero en el lenguaje C están íntimamente ligados.
El nombre del vector es un puntero a la dirección de memoria en la que se encuentra el primer elemento del vector.
Así, dado un vector c
es equivalente usar c
o &c[0]
Si sobrepasamos los límites del vector pueden ocurrir errores como los mostrados en la figura. En este caso, se ha sobrescrito el valor de una variable. El programa tendrá un comportamiento imprevisible.
En cualquier caso, el nombre del vector no representa a una variable que pueda modificarse:
int c[]{1, 2, 3};
c = c + 1; //¡ERROR AL COMPILAR¡
Vectores como argumentos de una función¶
El prototipo de declaración una función que recibe como argumento un vector es el siguiente:
tipo_funcion nombre_funcion(..., tipo_vector [], ...);
La definición tendrá el siguiente aspecto:
tipo_funcion nombre_funcion(..., tipo_vector nombre_vector[], ...)
{
...
}
Por último, para la llamada a la función basta con enviar el nombre del vector:
{
...
resultado = nombre_funcion(..., nombre_vector, ...)
...
}
Nótese que pasar el nombre del vector equivale a pasar la dirección del primer elemento del vector. Por ello, la función sabe dónde está almacenado el vector y puede modificar cualquiera de sus valores.
Esta forma de operar es relevante y pone de manifiesto la utilidad detrás de las bambalinas de los punteros. No es necesario hacer una copia de todos los elementos del vector, con el consiguiente ahorro en tiempo de ejecución y memoria, sobre todo para vectores de gran tamaño. Por otro lado, la función puede alterar el contenido del vector, como si de un paso por referencia se tratase.
Sin embargo, dado que un vector al estilo C no posee información de su tamaño, el número de elementos suele ser un argumento obligatorio más de la función.
Analizaremos a continuación el siguiente ejemplo, que duplica el valor de los elementos de un vector:
void mult_2(int v[], int n)
{
for (int i = 0; i < n; ++i)
v[i] = 2*v[i];
}
int main()
{
int a[]{1,2,5,2};
mult_2(a, 4);
}
Traza de ejemplo del uso de vectores y funciones¶
Estado tras definición del vector en main()
void mult_2(int v[], int n)
{
for (int i = 0; i < n; ++i)
v[i] = 2*v[i];
}
int main()
{
int a[]{1,2,5,2};
mult_2(a, 4);
}
Dirección |
Valor |
Variable |
Ámbito |
---|---|---|---|
0x61FDDC |
|||
0x61FDF0 |
|||
0x61FDF8 |
|||
0x61FE10 |
1 |
|
|
0x61FE14 |
2 |
|
|
0x61FE18 |
5 |
|
|
0x61FE1C |
2 |
|
|
Ejecutando función mult_2()
. Estado tras paso por valor.
void mult_2(int v[], int n)
{
for (int i = 0; i < n; ++i)
v[i] = 2*v[i];
}
int main()
{
int a[]{1,2,5,2};
mult_2(a, 4);
}
Dirección |
Valor |
Variable |
Ámbito |
---|---|---|---|
0x61FDDC |
¿? |
|
|
0x61FDF0 |
0x61FE10 |
|
|
0x61FDF8 |
4 |
|
|
0x61FE10 |
1 |
|
|
0x61FE14 |
2 |
|
|
0x61FE18 |
5 |
|
|
0x61FE1C |
2 |
|
|
Primera iteración del bucle en mult_2()
void mult_2(int v[], int n)
{
for (int i = 0; i < n; ++i)
v[i] = 2*v[i];
}
int main()
{
int a[]{1,2,5,2};
mult_2(a, 4);
}
Dirección |
Valor |
Variable |
Ámbito |
---|---|---|---|
0x61FDDC |
0 |
|
|
0x61FDF0 |
0x61FE10 |
|
|
0x61FDF8 |
4 |
|
|
0x61FE10 |
2 |
|
|
0x61FE14 |
2 |
|
|
0x61FE18 |
5 |
|
|
0x61FE1C |
2 |
|
|
Salida de mult_2()
void mult_2(int v[], int n)
{
for (int i = 0; i < n; ++i)
v[i] = 2*v[i];
}
int main()
{
int a[]{1,2,5,2};
mult_2(a, 4);
}
Dirección |
Valor |
Variable |
Ámbito |
---|---|---|---|
0x61FDDC |
|||
0x61FDF0 |
|||
0x61FDF8 |
|||
0x61FE10 |
2 |
|
|
0x61FE14 |
4 |
|
|
0x61FE18 |
10 |
|
|
0x61FE1C |
4 |
|
|
Ejemplos¶
Mínimo de un vector de enteros¶
#include <iostream>
using namespace std;
int minimo_vector(int [], int);
int main()
{
int v[]{4,1,5,3};
cout << minimo_vector(v, 4) << endl;
}
int minimo_vector(int v[], int num)
{
int minimo = v[0];
for (int i = 1; i < num; ++i)
if (v[i] < minimo)
minimo = v[i];
return minimo;
}
Mínimo y máximo de un vector de enteros¶
Como no podemos devolver dos valores, una opción es utilizar referencias.
#include <iostream>
using namespace std;
void minimo_maximo_vector(int [], int, int&, int&);
int main()
{
int v[]{4,1,5,3};
int minimo, maximo;
minimo_maximo_vector(v, 4, minimo, maximo);
cout << "El mínimo y máximo del vector son "
<< minimo << " y " << maximo << endl;
}
void minimo_maximo_vector(int v[], int num, int& minimo, int& maximo)
{
minimo = maximo = v[0];
for (int i = 1; i < num; ++i)
if (v[i] < minimo)
minimo = v[i];
else if (v[i] > maximo)
maximo = v[i];
}
Invertir el orden de los elementos de un vector¶
#include <iostream>
using namespace std;
void invertir_orden_vector(int v[], int num)
{
for (int i = 0; i < num/2; ++i) // Se recorre el vector hasta la mitad
{
int aux = v[i];
v[i] = v[num-1-i];
v[num-1-i] = aux;
}
}
int pide_entero(int inf, int sup, string mensaje)
{
int valor;
do
{
cout << "Introduzca el " << mensaje << ": ";
cin >> valor;
if (valor > sup)
cout << "\n\nERROR: el " << mensaje << " no puede sobrepasar " << sup << "\n\n";
else if (valor < inf)
cout << "\n\nERROR: el " << mensaje << " debe ser al menos " << inf << "\n\n";
}while (valor > sup || valor < inf);
return valor;
}
void carga_vector(int v[], int tam)
{
for(int i = 0; i < tam; ++i)
{
cout << "v[" << i << "]= ";
cin >> v[i];
}
}
void muestra_vector(int v[], int tam)
{
for (int i = 0; i < tam; ++i)
cout << v[i] << " ";
cout << endl;
}
int main()
{
const int tam_max = 20;
int v[tam_max];
int tam = pide_entero(1, tam_max, "numero de elementos del vector");
carga_vector(v, tam);
invertir_orden_vector(v, tam);
muestra_vector(v, tam);
}