Buscar..


Introducción

Las matrices son tipos de datos derivados, que representan una colección ordenada de valores ("elementos") de otro tipo. La mayoría de las matrices en C tienen un número fijo de elementos de cualquier tipo, y su representación almacena los elementos de forma contigua en la memoria sin espacios ni rellenos. C permite matrices multidimensionales cuyos elementos son otras matrices y también matrices de punteros.

C admite matrices asignadas dinámicamente cuyo tamaño se determina en el tiempo de ejecución. C99 y posteriores admiten matrices de longitud variable o VLA.

Sintaxis

  • escriba nombre [longitud]; / * Definir matriz de 'tipo' con nombre 'nombre' y longitud 'longitud'. * /
  • int arr [10] = {0}; / * Definir una matriz e inicializar TODOS los elementos a 0. * /
  • int arr [10] = {42}; / * Definir una matriz e inicializar los primeros elementos a 42 y el resto a 0. * /
  • int arr [] = {4, 2, 3, 1}; / * Definir e inicializar una matriz de longitud 4. * /
  • arr [n] = valor; / * Establecer valor en el índice n. * /
  • valor = arr [n]; / * Obtener valor en el índice n. * /

Observaciones

¿Por qué necesitamos matrices?

Las matrices proporcionan una forma de organizar los objetos en un agregado con su propio significado. Por ejemplo, cadenas de C son matrices de caracteres ( char s), y una cadena como "Hola, mundo!" tiene un significado como un agregado que no es inherente a los caracteres individualmente. De manera similar, las matrices se usan comúnmente para representar vectores matemáticos y matrices, así como listas de muchos tipos. Además, sin una forma de agrupar los elementos, uno tendría que abordar cada uno individualmente, como a través de variables separadas. No solo es difícil de manejar, sino que no se adapta fácilmente a colecciones de diferentes longitudes.

Las matrices se convierten implícitamente a punteros en la mayoría de los contextos .

Excepto cuando aparece como el operando de la sizeof operador, el _Alignof operador (C2011), o el unario & operador (dirección-de), o como un literal de cadena utilizado para inicializar un (otro) de matriz, una matriz se convierte implícitamente en ( "decae a") un puntero a su primer elemento. Esta conversión implícita está estrechamente relacionada con la definición del operador de subíndice de matriz ( [] ): la expresión arr[idx] se define como equivalente a *(arr + idx) . Además, dado que la aritmética de punteros es conmutativa, *(arr + idx) también es equivalente a *(idx + arr) , que a su vez es equivalente a idx[arr] . Todas esas expresiones son válidas y se evalúan con el mismo valor, siempre que idx o arr sean un puntero (o una matriz, que decaiga en un puntero), la otra es un número entero, y el entero es un índice válido en la matriz a la que apunta el puntero.

Como caso especial, observe que &(arr[0]) es equivalente a &*(arr + 0) , lo que simplifica a arr . Todas esas expresiones son intercambiables siempre que la última se desintegre a un puntero. Esto simplemente expresa nuevamente que una matriz se desintegra en un puntero a su primer elemento.

Por el contrario, si la dirección del operador se aplica a una matriz de tipo T[N] ( es decir, &arr ), el resultado tiene el tipo T (*)[N] y apunta a toda la matriz. Esto es distinto de un puntero al primer elemento de la matriz al menos con respecto a la aritmética de punteros, que se define en términos del tamaño del tipo apuntado.

Los parámetros de función no son matrices .

void foo(int a[], int n);
void foo(int *a, int n);

Aunque la primera declaración de foo usa una sintaxis similar a a matriz para el parámetro a , dicha sintaxis se usa para declarar que un parámetro de función declara ese parámetro como un puntero al tipo de elemento de la matriz. Por lo tanto, la segunda firma para foo() es semánticamente idéntica a la primera. Esto corresponde a la caída de los valores de matriz a los punteros donde aparecen como argumentos a una llamada de función, de modo que si una variable y un parámetro de función se declaran con el mismo tipo de matriz, el valor de esa variable es adecuado para su uso en una llamada de función como el Argumento asociado al parámetro.

Declarar e inicializar una matriz

La sintaxis general para declarar una matriz unidimensional es

type arrName[size];

donde type podría ser cualquier tipo incorporado o tipos definidos por el usuario, como las estructuras, arrName es un identificador definido por el usuario, y size es una constante entera.

La declaración de una matriz (una matriz de 10 variables int en este caso) se realiza de la siguiente manera:

int array[10];

ahora tiene valores indeterminados. Para asegurarse de que mantiene valores cero al declarar, puede hacer esto:

int array[10] = {0};

Las matrices también pueden tener inicializadores, en este ejemplo se declara una matriz de 10 int , donde las primeras 3 int contendrán los valores 1 , 2 , 3 , todos los demás valores serán cero:

int array[10] = {1, 2, 3};

En el método de inicialización anterior, el primer valor de la lista se asignará al primer miembro de la matriz, el segundo valor se asignará al segundo miembro de la matriz y así sucesivamente. Si el tamaño de la lista es más pequeño que el tamaño de la matriz, entonces, como en el ejemplo anterior, los miembros restantes de la matriz se inicializarán a ceros. Con la inicialización de lista designada (ISO C99), es posible la inicialización explícita de los miembros de la matriz. Por ejemplo,

int array[5] = {[2] = 5, [1] = 2, [4] = 9}; /* array is {0, 2, 5, 0, 9} */

En la mayoría de los casos, el compilador puede deducir la longitud de la matriz para usted, esto se puede lograr dejando los corchetes vacíos:

int array[] = {1, 2, 3}; /* an array of 3 int's */
int array[] = {[3] = 8, [0] = 9}; /* size is 4 */

No se permite declarar una matriz de longitud cero.

C99 C11

Las matrices de longitud variable (VLA para abreviar) se agregaron en C99 y se hicieron opcionales en C11. Son iguales a las matrices normales, con una diferencia importante: la longitud no debe conocerse en el momento de la compilación. Los VLA tienen duración de almacenamiento automático. Solo los punteros a VLA pueden tener una duración de almacenamiento estático.

size_t m = calc_length(); /* calculate array length at runtime */
int vla[m];               /* create array with calculated length */

Importante:

Los VLA son potencialmente peligrosos. Si la matriz vla en el ejemplo anterior requiere más espacio en la pila que el disponible, la pila se desbordará. Por lo tanto, el uso de VLA se suele desaconsejar en las guías de estilo y en los libros y ejercicios.

Borrar el contenido de la matriz (puesta a cero)

A veces es necesario establecer una matriz en cero, después de que se haya realizado la inicialización.

#include <stdlib.h> /* for EXIT_SUCCESS */

#define ARRLEN (10)

int main(void)
{
  int array[ARRLEN]; /* Allocated but not initialised, as not defined static or global. */

  size_t i;
  for(i = 0; i < ARRLEN; ++i)
  {
    array[i] = 0;
  }

  return EXIT_SUCCESS;
}

Un atajo común al bucle anterior es usar memset() de <string.h> . Al pasar la array como se muestra a continuación, se descompone en un puntero a su primer elemento.

memset(array, 0, ARRLEN * sizeof (int)); /* Use size explicitly provided type (int here). */

o

memset(array, 0, ARRLEN * sizeof *array); /* Use size of type the pointer is pointing to. */

Como en este ejemplo, la array es una matriz y no solo un puntero al primer elemento de una matriz (ver longitud de la matriz sobre por qué esto es importante) una tercera opción para eliminar la matriz es 0:

 memset(array, 0, sizeof array); /* Use size of the array itself. */

Longitud de la matriz

Las matrices tienen longitudes fijas que se conocen dentro del alcance de sus declaraciones. Sin embargo, es posible y, a veces, conveniente calcular las longitudes de matriz. En particular, esto puede hacer que el código sea más flexible cuando la longitud de la matriz se determina automáticamente a partir de un inicializador:

int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

/* size of `array` in bytes */
size_t size = sizeof(array);

/* number of elements in `array` */
size_t length = sizeof(array) / sizeof(array[0]); 

Sin embargo, en la mayoría de los contextos donde aparece una matriz en una expresión, se convierte automáticamente en ("decae a") un puntero a su primer elemento. El caso en el que una matriz es el operando del operador sizeof es una de las pocas excepciones. El puntero resultante no es en sí mismo una matriz y no transporta ninguna información sobre la longitud de la matriz de la que se derivó. Por lo tanto, si esa longitud se necesita junto con el puntero, como cuando el puntero se pasa a una función, debe transmitirse por separado.

Por ejemplo, supongamos que queremos escribir una función para devolver el último elemento de una matriz de int . Continuando con lo anterior, podríamos llamarlo así:

/* array will decay to a pointer, so the length must be passed separately */
int last = get_last(array, length);

La función se podría implementar así:

int get_last(int input[], size_t length) {
    return input[length - 1];
}

Tenga en cuenta en particular que aunque la declaración de input de parámetros se parece a la de una matriz, de hecho declara la input como un puntero (a int ). Es exactamente equivalente a declarar input como input int *input . Lo mismo sería cierto incluso si se diera una dimensión. Esto es posible porque las matrices nunca pueden ser argumentos reales de las funciones (se descomponen en los punteros cuando aparecen en expresiones de llamada de función), y se pueden ver como mnemotécnicas.

Es un error muy común intentar determinar el tamaño de la matriz desde un puntero, que no puede funcionar. NO HAGAS ESTO:

int BAD_get_last(int input[]) {
    /* INCORRECTLY COMPUTES THE LENGTH OF THE ARRAY INTO WHICH input POINTS: */
    size_t length = sizeof(input) / sizeof(input[0]));

    return input[length - 1];  /* Oops -- not the droid we are looking for */
}

De hecho, ese error en particular es tan común que algunos compiladores lo reconocen y lo advierten. clang , por ejemplo, emitirá la siguiente advertencia:

warning: sizeof on array function parameter will return size of 'int *' instead of 'int []' [-Wsizeof-array-argument]
        int length = sizeof(input) / sizeof(input[0]);
                           ^
note: declared here
int BAD_get_last(int input[])
                     ^

Configuración de valores en matrices

El acceso a los valores de la matriz se realiza generalmente entre corchetes:

int val;
int array[10];

/* Setting the value of the fifth element to 5: */
array[4] = 5;

/* The above is equal to: */
*(array + 4) = 5;

/* Reading the value of the fifth element: */
val = array[4];

Como efecto secundario de los operandos al operador + que es intercambiable (-> ley conmutativa), lo siguiente es equivalente:

*(array + 4) = 5;
*(4 + array) = 5;

así también las siguientes afirmaciones son equivalentes:

array[4] = 5;
4[array] = 5; /* Weird but valid C ... */

y esos dos también:

val = array[4];
val = 4[array]; /* Weird but valid C ... */

C no realiza ninguna verificación de límites, el acceso al contenido fuera de la matriz declarada no está definido (se accede a la memoria más allá del fragmento asignado ):

int val;
int array[10];

array[4] = 5;    /* ok */
val = array[4];  /* ok */
array[19] = 20;  /* undefined behavior */
val = array[15]; /* undefined behavior */

Definir matriz y elemento de matriz de acceso.

#include <stdio.h>
 
#define ARRLEN (10)

int main (void) 
{

   int n[ ARRLEN ]; /* n is an array of 10 integers */
   size_t i, j; /* Use size_t to address memory, that is to index arrays, as its guaranteed to 
                   be wide enough to address all of the possible available memory. 
                   Using signed integers to do so should be considered a special use case, 
                   and should be restricted to the uncommon case of being in the need of 
                   negative indexes. */
 
   /* Initialize elements of array n. */         
   for ( i = 0; i < ARRLEN ; i++ ) 
   {
      n[ i ] = i + 100; /* Set element at location i to i + 100. */
   }
   
   /* Output each array element's value. */
   for (j = 0; j < ARRLEN ; j++ ) 
   {
      printf("Element[%zu] = %d\n", j, n[j] );
   }
 
   return 0;
}

Asignar y cero inicializar una matriz con el tamaño definido por el usuario

#include <stdio.h>
#include <stdlib.h>


int main (void)
{
  int * pdata;
  size_t n;

  printf ("Enter the size of the array: ");
  fflush(stdout); /* Make sure the prompt gets printed to buffered stdout. */

  if (1 != scanf("%zu", &n)) /* If zu is not supported (Windows?) use lu. */
  {
    fprintf("scanf() did not read a in proper value.\n");
    exit(EXIT_FAILURE);
  }

  pdata = calloc(n, sizeof *pdata);
  if (NULL == pdata) 
  {
    perror("calloc() failed"); /* Print error. */
    exit(EXIT_FAILURE);
  }

  free(pdata); /* Clean up. */

  return EXIT_SUCCESS;
}

Este programa intenta escanear en un valor entero sin signo de la entrada estándar, asignar un bloque de memoria para una matriz de n elementos de tipo int llamando a la función calloc() . La memoria se inicializa a todos los ceros por este último.

En caso de éxito la memoria es free() por la llamada a free() .

Iterando a través de una matriz de manera eficiente y orden de fila mayor

Las matrices en C se pueden ver como una porción contigua de memoria. Más precisamente, la última dimensión de la matriz es la parte contigua. Llamamos a esto el orden de la fila principal . Al comprender esto y el hecho de que un error de caché carga una línea de caché completa en el caché cuando se accede a datos no almacenados en caché para evitar fallas de caché subsiguientes, podemos ver por qué el acceso a una matriz de dimensión 10000x10000 con array[0][0] podría potencialmente cargar array[0][1] en caché, pero acceder a la array[1][0] justo después generaría un segundo error de caché, ya que está a sizeof(type)*10000 bytes de la array[0][0] , y por lo tanto no en la misma línea de caché. Es por eso que iterar así es ineficiente:

#define ARRLEN 10000
int array[ARRLEN][ARRLEN];

size_t i, j;
for (i = 0; i < ARRLEN; ++i)
{
    for(j = 0; j < ARRLEN; ++j)
    {
        array[j][i] = 0;
    }
}

E iterar así es más eficiente:

#define ARRLEN 10000
int array[ARRLEN][ARRLEN];

size_t i, j;
for (i = 0; i < ARRLEN; ++i)
{
    for(j = 0; j < ARRLEN; ++j)
    {
        array[i][j] = 0;
    }
}

En la misma línea, esta es la razón por la que cuando se trata de una matriz con una dimensión y múltiples índices (digamos 2 dimensiones aquí por simplicidad con los índices i y j), es importante recorrer la matriz de esta manera:

#define DIM_X 10
#define DIM_Y 20

int array[DIM_X*DIM_Y];

size_t i, j;
for (i = 0; i < DIM_X; ++i)
{
    for(j = 0; j < DIM_Y; ++j)
    {
        array[i*DIM_Y+j] = 0;
    }
}

O con 3 dimensiones e índices i, j y k:

#define DIM_X 10
#define DIM_Y 20
#define DIM_Z 30

int array[DIM_X*DIM_Y*DIM_Z];

size_t i, j, k;
for (i = 0; i < DIM_X; ++i)
{
    for(j = 0; j < DIM_Y; ++j)
    {
        for (k = 0; k < DIM_Z; ++k)
        {
            array[i*DIM_Y*DIM_Z+j*DIM_Z+k] = 0;
        }
    }
}

O de una manera más genérica, cuando tenemos una matriz con N1 x N2 x ... x Nd elementos, d dimensiones e índices anotados como n1, n2, ..., nd el desplazamiento se calcula de esta manera

Fórmula

Imagen / fórmula tomada de: https://en.wikipedia.org/wiki/Row-major_order

Matrices multidimensionales

El lenguaje de programación en C permite matrices multidimensionales . Aquí está la forma general de una declaración de matriz multidimensional:

type name[size1][size2]...[sizeN];

Por ejemplo, la siguiente declaración crea una matriz de enteros tridimensional (5 x 10 x 4):

int arr[5][10][4];

Arrays bidimensionales

La forma más simple de matriz multidimensional es la matriz bidimensional. Una matriz bidimensional es, en esencia, una lista de matrices unidimensionales. Para declarar una matriz entera bidimensional de dimensiones mxn, podemos escribir de la siguiente manera:

type arrayName[m][n];

Donde type puede ser cualquier tipo de datos de C válido ( int , float , etc.) y arrayName puede ser cualquier identificador de C válido. Una matriz bidimensional se puede visualizar como una tabla con m filas n columnas. Nota : el orden importa en C. La matriz int a[4][3] no es lo mismo que la matriz int a[3][4] . El número de filas es lo primero, ya que C es un idioma principal de la fila .

Una matriz bidimensional a , que contiene tres filas y cuatro columnas se puede mostrar de la siguiente manera:

Disposición visual de la matriz 2D como tabla.

Por lo tanto, cada elemento de la matriz a se identifica por un nombre de elemento de la forma a[i][j] , donde a es el nombre de la matriz, i representa qué fila y j representa qué columna. Recuerde que las filas y columnas están indexadas en cero. Esto es muy similar a la notación matemática para las matrices 2D de subíndices.

Inicializando matrices bidimensionales

Los arreglos multidimensionales pueden inicializarse especificando valores entre corchetes para cada fila. Lo siguiente define una matriz con 3 filas donde cada fila tiene 4 columnas.

int a[3][4] = {  
   {0, 1, 2, 3} ,   /*  initializers for row indexed by 0 */
   {4, 5, 6, 7} ,   /*  initializers for row indexed by 1 */
   {8, 9, 10, 11}   /*  initializers for row indexed by 2 */
};

Las llaves anidadas, que indican la fila deseada, son opcionales. La siguiente inicialización es equivalente al ejemplo anterior:

int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

Si bien el método para crear matrices con llaves anidadas es opcional, se recomienda encarecidamente ya que es más legible y más claro.

Acceso a elementos de matriz bidimensional

Se accede a un elemento de una matriz bidimensional utilizando los subíndices, es decir, el índice de fila y el índice de columna de la matriz. Por ejemplo

int val = a[2][3];

La declaración anterior tomará el cuarto elemento de la tercera fila de la matriz. Revisemos el siguiente programa en el que hemos utilizado un bucle anidado para manejar una matriz bidimensional:

#include <stdio.h>
 
int main () {

   /* an array with 5 rows and 2 columns*/
   int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};
   int i, j;
 
   /* output each array element's value */
   for ( i = 0; i < 5; i++ ) {

      for ( j = 0; j < 2; j++ ) {
         printf("a[%d][%d] = %d\n", i,j, a[i][j] );
      }
   }
   
   return 0;
}

Cuando el código anterior se compila y ejecuta, produce el siguiente resultado:

a[0][0]: 0
a[0][1]: 0
a[1][0]: 1
a[1][1]: 2
a[2][0]: 2
a[2][1]: 4
a[3][0]: 3
a[3][1]: 6
a[4][0]: 4
a[4][1]: 8

Array tridimensional:

Una matriz 3D es esencialmente una matriz de matrices de matrices: es una matriz o colección de matrices 2D, y una matriz 2D es una matriz de matrices 1D.

Diseño visual de la matriz 2D como una colección de tablas.

Mapa de memoria de matriz 3D:

Array 3D distribuido de forma contigua en la memoria

Inicializando una matriz 3D:

double cprogram[3][2][4]={ 
{{-0.1, 0.22, 0.3, 4.3}, {2.3, 4.7, -0.9, 2}},
 {{0.9, 3.6, 4.5, 4}, {1.2, 2.4, 0.22, -1}},
 {{8.2, 3.12, 34.2, 0.1}, {2.1, 3.2, 4.3, -2.0}} 
};

Podemos tener matrices con cualquier número de dimensiones, aunque es probable que la mayoría de las matrices que se crean sean de una o dos dimensiones.

Iterando a través de una matriz utilizando punteros

#include <stdio.h>
#define SIZE (10)
int main()
{
    size_t i = 0;
    int *p = NULL;
    int a[SIZE];
    
    /* Setting up the values to be i*i */
    for(i = 0; i < SIZE; ++i) 
    {
        a[i] = i * i;
    }
    
    /* Reading the values using pointers */
    for(p = a; p < a + SIZE; ++p) 
    {
        printf("%d\n", *p);
    }

    return 0;
}

Aquí, en la inicialización de p en la primera condición de bucle for , la matriz a decae a un puntero a su primer elemento, como lo haría en casi todos los lugares donde se usa dicha variable de matriz.

Luego, ++p realiza aritmética de punteros en el puntero p y recorre uno por uno los elementos de la matriz, y se refiere a ellos desreferiéndolos con *p .

Pasar matrices multidimensionales a una función.

Las matrices multidimensionales siguen las mismas reglas que las matrices unidimensionales al pasarlas a una función. Sin embargo, la combinación de decaimiento a puntero, la precedencia del operador y las dos formas diferentes de declarar una matriz multidimensional (matriz de matrices frente a matriz de punteros) puede hacer que la declaración de tales funciones no sea intuitiva. El siguiente ejemplo muestra las formas correctas de pasar matrices multidimensionales.

#include <assert.h>
#include <stdlib.h>

/* When passing a multidimensional array (i.e. an array of arrays) to a
   function, it decays into a pointer to the first element as usual.  But only
   the top level decays, so what is passed is a pointer to an array of some fixed
   size (4 in this case). */
void f(int x[][4]) {
    assert(sizeof(*x) == sizeof(int) * 4);
}

/* This prototype is equivalent to f(int x[][4]).
   The parentheses around *x are required because [index] has a higher
   precedence than *expr, thus int *x[4] would normally be equivalent to int
   *(x[4]), i.e. an array of 4 pointers to int.  But if it's declared as a
   function parameter, it decays into a pointer and becomes int **x, 
   which is not compatable with x[2][4]. */
void g(int (*x)[4]) {
    assert(sizeof(*x) == sizeof(int) * 4);
}

/* An array of pointers may be passed to this, since it'll decay into a pointer
   to pointer, but an array of arrays may not. */
void h(int **x) {
    assert(sizeof(*x) == sizeof(int*));
}

int main(void) {
    int foo[2][4];
    f(foo);
    g(foo);

    /* Here we're dynamically creating an array of pointers.  Note that the 
       size of each dimension is not part of the datatype, and so the type 
       system just treats it as a pointer to pointer, not a pointer to array
       or array of arrays. */
    int **bar = malloc(sizeof(*bar) * 2);
    assert(bar);
    for (size_t i = 0; i < 2; i++) {
        bar[i] = malloc(sizeof(*bar[i]) * 4);
        assert(bar[i]);
    }

    h(bar);
    
    for (size_t i = 0; i < 2; i++) {
        free(bar[i]);
    }
    free(bar);
}

Ver también

Pasando en Arrays a Funciones



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow