Matrices - básico

Veamos un primer ejemplo:


#include <iostream>

using namespace std;

const int MaxDimensiones = 2;

int main()
{
int v[][MaxDimensiones] = { 1, 2, 3, 4 };
for(int i = 0; i < MaxDimensiones; ++i) {
for(int j = 0; j < MaxDimensiones; ++j) {
cout << v[ i ][ j ] << endl;
}
}
   
    return 0;
}


Un posible programa que maneje una matriz es éste, basado en el publicado ya la semana pasada.

Las matrices son espacios contíguos en memoria, de manera que podemos acceder a sus elementos utilizando el nombre del vector, los corchetes, y la posición.


v[ 0 ][ 0 ] = 11;
cout << v[ 0 ][ 0 ] << endl;


En el programa anterior, como con los vectores, se utiliza una constante para indicar el número de elementos en cada fila, es decir, el número de columnas,

Normalmente, es necesario indicar el tamaño máximo de las dimensiones de la matriz, aunque, como en este caso, podemos dejar el tamaño de la primera dimensión libre, y que sea el compilador el que cuente las filas (conociendo las columnas) y la cree del tamaño adecuado.

Es interesante tener en cuenta que las matrices se almacenan de manera contígua en memoria, en el caso de C, una fila detrás de la otra (tengamos en cuenta que la memoria es plana), y en FORTRAN (para verlo como ejemplo de contraposición), en cambio, una columna detrás de la otra.

Es interesante que os fijéis en que, el vector v del programa anterior, puede ser interpretado como una matriz 2x2 o como un vector. Esto es una constante en C/C++ ... nos deja libertad para todo, y eso a veces puede ser peligroso.

[SÓLO FRIKIS]
Así, cuando recorremos una matriz, utilizamos el bucle exterior como número de fila o de columna, y el bucle interior anidado, como de columna. Podéis hacer pruebas y veréis que, aunque el resultado obtenido es el mismo, es más rápido hacerlo de una forma que de otra ... ¿alguien se anima a publicar tiempos? [NOTA: Tb. intervienen las cachés del procesador]
[/SOLO FRIKIS]

Pasar matrices a funciones, punteros a matrices

¿Cómo se pasa un vector o una matriz a una función?
Veamos un ejemplo:


#include <iostream>

using namespace std;

const int MaxDimensiones = 2;

void muestra(int v[][MaxDimensiones])
{
    for(int i = 0; i < MaxDimensiones; ++i) {
        for(int j = 0; j < MaxDimensiones; ++j) {
            cout << v[ i ][ j ] << endl;
        }
    }
}

int main()
{
    int v[][MaxDimensiones] = { 1, 2, 3, 4 };
    muestra( v );

    return 0;
}


Como siempre, lo que sucede es que nosotros coordinamos, gracias a la constante MaxDimensiones, el acceso a la matriz durante todo el programa.

Como ya se ha comentado, las matrices y los vectores son espacios contíguos en memoria, de manera que podemos acceder a sus elementos utilizando el nombre del vector, los corchetes, y la posición. Pero ahora ya sabemos que el nombre del vector es un puntero al primer elemento, de manera que podemos hacerlo de otra manera ...

En un vector, su nombre, utilizado "sin nada", es decir, "a secas", es un puntero al primer elemento. En un matriz, es un puntero a un puntero, de manera que para C++ una matriz bidimensional es un vector de vectores.


cout << endl << **v << endl;
cout << v[ 0 ][ 0 ] << endl << endl;

cout << endl << *( (*v) + 1) << endl;
cout << v[ 0 ][ 1 ] << endl << endl;


*( (*v) + 1 ) = 55;
cout << *( (*v) + 1) << endl;
cout << v[ 0 ][ 1 ] << endl << endl;


*( (*v + MaxDimensiones) + 1 ) = 57;
cout << *( (*v + MaxDimensiones) + 1) << endl;
cout << v[ 1 ][ 1 ] << endl << endl;


En cuanto a posibles errores, es posible salirnos de la matriz sin que el lenguaje nos avise, así que, como siempre, es preocupación del programador que las cosas vayan bien, es decir, comprobar los límites, un error que en otros lenguajes 

Pasar vectores a funciones, punteros a vectores

¿Cómo se pasa un vector o una matriz a una función?


#include <iostream>

using namespace std;

const int MaxElementos = 6;

void muestra(int v[])
{
    for(int i = 0; i < MaxElementos; ++i) {
        cout << v[ i ] << endl;
    }
}

int main()
{
    int v[] = { 45, 23, 22, 12, 11, 10 };
    muestra( v );

    return 0;
}


Lo que sucede es que el nombre del vector, utilizado "sin nada", es decir, "a secas", es un puntero al primer elemento, así que la función muestra se podría reescribir de la siguiente manera, sin tener por qué cambiar nada más del programa:


void muestra(int * v)
{
    for(int i = 0; i < MaxElementos; ++i) {
        cout << v[ i ] << endl;
    }
}


Y también podríamos, sin tener que cambiar nada, llamar a muestra de la siguiente manera:


int main()
{
    int v[] = { 45, 23, 22, 12, 11, 10 };
    muestra( &v[ 0 ] );

    return 0;
}


Como ya se ha comentado, los vectores son espacios contíguos en memoria, de manera que podemos acceder a sus elementos utilizando el nombre del vector, los corchetes, y la posición. Pero ahora ya sabemos que el nombre del vector es un puntero al primer elemento, de manera que podemos hacerlo de otra manera ...


*v = 11;
cout << *v << endl;

Para otra posición, símplemente sumamos al puntero:


*(v + 2) = 11;
cout << *(v + 2) << endl;


Que es equivalente a:


v[ 2 ] = 11;
cout << v[ 2 ] << endl;


¿Qué sucede si nos "salimos" de los límites de un vector?. Veamos:


#include <iostream>

using namespace std;

const int MaxElementos = 6;

int main()
{
    int v[] = { 45, 23, 22, 12, 11, 10 };
    for(int i = 0; i < ( MaxElementos * 3 ); ++i) {
        v[ i ] = i *7;
    }

    return 0;
}


Este programa es completamente legal, compila y se ejecuta, sin embargo el resultado de su ejecución es incierto (formalmente, se conoce como comportamiento indefinido). El programa escribe más allá de los límites del vector, sobreescribiendo probablemente otras variables, y, seguro, el marco del stack (la pila) en el que se está ejecutando la función main. Aunque en este caso, es bastante claro que ocurrirá algún error de protección de memoria.

Finalmente, los arrays se pueden pasar como argumentos a una función simulando una llamada por referencia, eso quiere decir que cualquier modificación se verá reflejada en la función llamadora; para evitar la modificación se sus valores, en C++ podemos utilizar el calificador const, convirtiendo a sus elementos como constantes.

void muestra(const int v[])
{
    for(int i = 0; i < MaxElementos; ++i) {
        cout << v[ i ] << endl;
    }
}

int main()
{
    int v[] = { 45, 23, 22, 12, 11, 10 };
    muestra( v );

    return 0;
}

Así, si en el bucle de la función muestra() añadimos:

	v[ i ] *= 2;

Al compilar nos devolverá el siguiente error:

prumatriz.cpp: In function ‘void muestra(const int*)’:
prumatriz.cpp:10: error: assignment of read-only location