La utilización de punteros como base de matrices sigue el mismo principio que sobre vectores, pero hay que tener en cuenta que el desplazamiento del índice de filas debe ir multiplicado por número de elementos que hay en cada columna (sin embargo, ahora tampoco es necesario multiplicar el desplazamiento total por el tamaño de los elementos).

Ejemplo: Implementaremos el algoritmo que pone a cero todos los elementos de una matriz de 9 x 6 enteros largos (4 bytes por elemento).

#define N_Fil 9 
#define N_Col 6 

long ml[N_Fil][N_Col]; 
int i, j; 

for(i = 0; i < N_Fil; i++) { 
   for(j = 0; j < N_Col; j++) {
      ml[i][j] = 0;
   }
}

Podemos sustituir el acceso a los elementos ml[i][j] mediante el uso del puntero pl, y el cálculo correspondiente al desplazamiento del elemento en función de los índices.

Ejemplo: Acceso a matrices mediante punteros y desplazamientos.

#define N_Fil 9 
#define N_Col 6 

void main(void) { 
   long ml[N_Fil][N_Col]; 
   int i, j; 
   long *pl = ml; 
   for(i = 0; i < N_Fil; i++) {
      for(j = 0; j < N_Col; j++) {
         *(pl + i*N_Col + j) = 0; 
      }
   }
} 

También se puede escoger una representación de tipo matriz para los punteros, es decir:

*(pl + y*N_Col + j) = 0; <=> pl[i][j] = 0;

La pregunta que se plantea es por qué utilizar punteros si obtenemos el mismo resultado que con las tablas. La respuesta es que se permite un mecanismo muy ligado a las posibilidades de Lenguajes Máquina. Es decir, los accesos con punteros corresponden a modos de direccionamiento, y por tanto, se puede hacer una programación más óptima si se piensa en estos términos. Por ejemplo, para inicializar una matriz toda a ceros, cuyos elementos sabemos que están almacenados consecutivamente, podríamos implementar la inicialización como un solo bucle, de la siguiente forma:

for(i=0; i < N_Fil*N_Col; i++) *(pl + i) = 0; 

Yendo un poco más allá, podemos implementar el acceso con modo de direccionamiento registro indirecto autoincrementado, en vez de registro base más desplazamiento:

for(i=0; i < N_Fil*N_Col; i++, pl++) *pl = 0; 

Vectores

El nombre de una tabla en lenguaje C es un puntero constante en el tipo de dato de los elementos que almacena la mesa y que apunta a la posición de memoria donde se encuentra la primera casilla de la tabla.

int t[100]; 

t es un puntero en el inicio de una zona de memoria que ocupa el espacio de 100 elementos de tipo int. Si p es un puntero que apunta a un tipo T que ocupa n bytes y p apunta a una variable v del tipo T, resulta que p+1 apunta a una supuesta variable de tipo T, que estaría ubicada en memoria después de v; p+2 apunta a una supuesta variable de tipo T que estaría situada en memoria a continuación de la anterior, y así sucesivamente.

El paso de parámetros por referencia se realiza cuando el subprograma debe modificar alguna variable del programa que le llama. Para ello, es necesario que el subprograma conozca la dirección de memoria de la variable. Para indicar que los parámetros de un subprograma son por referencia, se indicará explícitamente, en su prototipo:

PSEUDOCODIGO C
var p1:tipo, var p2:tipo ......, var pN: tipo tipo p1, tipo p2, ........., tipo pN

En C el paso de parámetros por referencia se realiza enviando al subprograma las direcciones de memoria de los parámetros mediante el operador & y por tanto los parámetros que figuran en la cabecera deben ser punteros para poder recogido las direcciones de memoria enviadas.

PSEUDOCODIGO C
NombreSubprograma (par1, par2, ...., parN) NombreSubprograma (&par1, &par2, ...., &parN)

La memoria de un computador está constituida por celdas contiguas de forma que, en cada una de ellas, puede almacenarse un byte. Cada una de estas celdas o posiciones de memoria tiene una dirección que se expresa en hexadecimal, mediante el direccionamiento segmentado (una dirección viene dada por dos partes: segmento y desplazamiento dentro de este segmento, por ejemplo 0F4C:E0A4). Las direcciones comienzan por la 0000:0000 y se extienden en función de la memoria disponible. Asimismo cada posición de memoria tiene un contenido, es decir, el dato almacenada en ella.

La ocupación de memoria al almacenar datos es función del tipo de que se trate. Como ya hemos visto un carácter ocupa un byte y un entero ocupa 2 o 4 bytes. En algunos programas puede ser necesario conocer directamente las direcciones de los datos almacenados en la memoria, o saber qué contenido tiene una posición de memoria concreta. Asimismo debe existir alguno método que posibilite el acceso a la misma información desde diferentes partes o módulos del programa.

En C esto se logra mediante los punteros. Un puntero (pointer) es una variable que puede almacenar una dirección de memoria, generalmente la dirección de memoria de otra variable. Por los motivos explicados antes, en declarar un puntero, debe indicarse el tipo de datos al que apunta. Los punteros pueden apuntar a cualquier tipo de datos de C, tanto primitivos como definidos, así como en el tipo void. Para aclarar el concepto de puntero, pasamos a considerar el siguiente fragmento de programa:

int * pPunter; (En pseudocódigo : pPunter: ↑ entero;)

Se declara el puntero pPunter. En la declaración, su identificador debe ir precedido del símbolo asterisco *. Este puntero apunta a variables de tipo int, es decir, podrá almacenar direcciones de memoria de variables de este tipo. Es importante entender que el puntero se llama pPunter, sin el asterisco.

Declaración y definición

El concepto función correspondiente a un subprograma que devuelve un resultado es, a C, equivalente a una función que devuelve un dato. El concepto de acción correspondiente a un subprograma que no devuelve ningún resultado es en C, equivalente a una función que devuelve void.

El tipo void se utiliza entre otras situaciones para:

  • Indicar que una función no acepta argumentos:
int funcion (void)
  • Declarar funciones que no devuelven ningún valor:
void funcion (int, char) 

 Declaración de funciones

La declaración de una función, también llamada prototipo de la función, indica cuántos argumentos tiene la función y de qué tipo son, y el tipo del valor devuelto.

<tipo_retornado> <nombre_función> ([<lista_argumentos>]);

El prototipo de una función es una plantilla que utiliza el compilador para comprobar que cualquier llamada a la función es correcta, es decir, que los parámetros actuales son adecuados a los argumentos y el valor devuelto se trata correctamente.

Ejemplos:

void leer_SN_1 (int, int, char *);
char leer_SN_2 (int, int);

La declaración de una función da características de la función, pero no define el proceso que hace. La definición de una función contiene la declaración de las variables locales y del código correspondiente.

La declaración es explícita cuando el compilador detecta el prototipo de la función o su definición antes de detectar ninguna llamada. La declaración es implícita cuando se detecta alguna llamada antes de encontrarse la declaración o definición. En este último caso, el compilador de C construye un prototipo por defecto, el cual consiste en una función que siempre devuelve un resultado int y que tiene por lista de argumentos la construida a partir de los tipos de parámetros actuales especificados en la llamada de la función. Esto obliga a que el tipo de resultado en la definición de la función sea int.