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]

alternate text

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.

alternate text

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);
}
Estado tras definición del vector en main()

Dirección

Valor

Variable

Ámbito

0x61FDDC

0x61FDF0

0x61FDF8

0x61FE10

1

a[0]

main()

0x61FE14

2

a[1]

main()

0x61FE18

5

a[2]

main()

0x61FE1C

2

a[3]

main()

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);
}
Estado tras paso por valor del vector y su tamaño

Dirección

Valor

Variable

Ámbito

0x61FDDC

¿?

i

mult_2()

0x61FDF0

0x61FE10

v

mult_2()

0x61FDF8

4

n

mult_2()

0x61FE10

1

a[0], v[0]

main()

0x61FE14

2

a[1], v[1]

main()

0x61FE18

5

a[2], v[2]

main()

0x61FE1C

2

a[3], v[3]

main()

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);
}
Estado tras primera iteración del bucle en mult_2()

Dirección

Valor

Variable

Ámbito

0x61FDDC

0

i

mult_2()

0x61FDF0

0x61FE10

v

mult_2()

0x61FDF8

4

n

mult_2()

0x61FE10

2

a[0], v[0]

main()

0x61FE14

2

a[1], v[1]

main()

0x61FE18

5

a[2], v[2]

main()

0x61FE1C

2

a[3], v[3]

main()

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);
}
Estado tras salida de la función mult_2()

Dirección

Valor

Variable

Ámbito

0x61FDDC

0x61FDF0

0x61FDF8

0x61FE10

2

a[0]

main()

0x61FE14

4

a[1]

main()

0x61FE18

10

a[2]

main()

0x61FE1C

4

a[3]

main()

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;
}

Edita, compila y ejecuta el código

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];
}

Edita, compila y ejecuta el código

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);
}

Edita, compila y ejecuta el código