Ingeniero Técnico en Informática de Gestión
Introducción al lenguaje C. Matrices y Punteros
7
Matrices y punteros
¿Qué es una matriz?
Una matriz es una estructura de datos interna que almacena un conjunto de datos del mismo tipo bajo un nombre de variable común. La posición de un elemento dentro de la matriz viene identificada por uno o varios índices, de tal modo que a cada elemento se accede mediante el nombre de la matriz y sus índices.
La dimensión de una matriz es el número de índices necesario para identificar un elemento.
¿Qué son los punteros?
Un puntero es una variable que contiene una dirección de memoria. Por ejemplo, la dirección de otra variable.
Direcciones de memoria
1386 | 1387 | 1388 | 1389 | 1390 | 1391 | 1392 | 1393 | 1394 | 1395 | ||
1393 | dato |
El puntero almacenado en la posición
de memoria 1388 apunta al dato almacenado
en la posición de memoria 1393
Las variables puntero se declaran de la siguiente forma:
tipo *nombre;
siendo nombre el identificador de la variable puntero, y tipo el tipo de variable a la que apunta. Por ejemplo,
char *m;
int *n;
float *p;
En estas declaraciones, las variables m, n y p son punteros que apuntan, respectivamente, a datos de tipo char, int y float. Es importante darse cuenta de que ni m es una variable de tipo char, ni n de tipo int, ni p de tipo float. Los tipos definen el tipo de dato al que apunta el puntero
m | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | ||||||
189 |
m apunta a un dato char
n | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | ||||||
189 |
n apunta a un dato int
p | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | ||||||
189 |
p apunta a un dato float
Los operadores de punteros son los que estudiamos en el Capítulo 2, y se definieron en la página 22, es decir,
-
& dirección de
-
* en la dirección de
El operador * sólo se puede aplicar a punteros. Después de
float m;
float *p;
...
...
m = *p;
la variable m almacena el contenido de la dirección apuntada por p. Del mismo modo después de
*p = m;
el valor de m se almacena en la dirección apuntada por p. La siguiente asignación
int valor = 100, q;
...
...
q = valor;
puede conseguirse también mediante
int valor = 100, q, *m;
...
...
m = &valor;
q = *m;
es decir, se almacena en la variable q el valor 100.
El código de formato para visualizar variables puntero mediante funciones tipo printf() es %p. Así,
int *m;
...
...
printf ("%p", m);
muestra en pantalla la dirección almacenada por m. La visualización se hace en hexadecimal. Otro código de formato relacionado con punteros es %n. Este código da lugar a que el número de caracteres que se han escrito en el momento en que se encuentra %n, se asocien a una variable cuya dirección se especifica en la lista de argumentos. El siguiente programa muestra en pantalla la frase
Se han escrito 11 caracteres
#include <stdio.h>
#include <conio.h>
void main (void)
{
int p;
clrscr ();
printf ("Hola mundo %n", &p);
printf ("\nSe han escrito %d caracteres", p);
}
Las operaciones permitidas con punteros son:
-
Asignación
-
Incremento / Decremento
-
Suma / Resta
-
Comparación
Vamos a describir cada una de ellas.
Asignación
Dadas las declaraciones
float x;
float *p, *q;
la forma de asignar a p y q la dirección de x es:
p = &x;
q = &x;
Ahora p y q almacenan la misma dirección de memoria: la de la variable x. El mismo efecto se consigue con la asignación directa entre punteros:
p = &x;
q = p;
No es correcta una sentencia como
p = x;
puesto que x es una variable de tipo float (almacena un dato float), mientras que p almacena la dirección de un dato de tipo float.
Por medio de punteros pueden hacerse asignaciones de contenidos. Por ejemplo:
float a, b;
float *p, *q;
...
...
a = 1.5;
p = &b;
q = &a;
*p = *q;
En esta secuencia, p y q almacenan valores diferentes (la dirección de b y la dirección de a, respectivamente). La última sentencia asigna contenidos, es decir, almacena en el lugar apuntado por p (la variable b) lo que hay en el lugar apuntado por q (la variable a). Es, por tanto, equivalente a
b = a;
Incremento / Decremento
Para comprender estas operaciones, debemos tener en cuenta que hacen referencia a elementos de memoria y no a direcciones. Esto quiere decir que los operadores ++ y -- actúan de modo diferente según el tipo apuntado por el puntero. Si p es un puntero a caracteres (char *p) la operación p++ incrementa el valor de p en 1. Si embargo, si p es un puntero a enteros (int *p), la misma operación p++ incrementa el valor de p en 2 para que apunte al siguiente elemento, pues el tipo int ocupa dos bytes. Del mismo modo, para el tipo float la operación p++ incrementa el valor de p en 4.
Lo dicho para el operador ++ se cumple exactamente igual, pero decrementando, para el operador --.
Suma / Resta
Ocurre exactamente lo mismo que con las operaciones de incremento y decremento. Si p es un puntero, la operación
p = p + 5;
hace que p apunte 5 elementos más allá del actual. Si p estaba definido como un puntero a caracteres, se incrementará su valor en 5, pero si estaba definido como un puntero a enteros, se incrementará en 10.
Comparación
Pueden compararse punteros del mismo modo que cualquier otra variable, teniendo siempre presente que se comparan direcciones y no contenidos.
int *p, *q;
...
...
if (p == q) puts ("p y q apuntan a la misma posición de memoria");
...
...
if (*p == *q) puts ("Las posiciones apuntadas por p y q almacenan el mismo valor");
En el segundo caso puede aparecer el mensaje aunque p y q apunten a direcciones diferentes.
Matrices unidimensionales
Son aquellas que sólo precisan de un índice para acceder a cada elemento. También se les llama vectores o listas.
Todos los elementos de un vector se almacenan en posiciones de memoria contiguas, almacenándose el primer elemento en la dirección más baja.
Un vector se declara de la siguiente forma:
tipo nombre[num_elem];
donde tipo es el tipo de dato de todos los elementos del vector, nombre es cualquier identificador válido C, y num_elem es el número de elementos del vector. Por ejemplo,
char frase[20];
int numero[16];
float valor[12];
declaran, por este orden, un vector de 20 caracteres, otro de 16 elementos enteros y otro de 12 elementos en coma flotante. El número de bytes ocupado por una matriz se calcula multiplicando el número de elementos por el tamaño de cada uno de ellos. Así, los 3 vectores anteriores ocupan, respectivamente, 20, 32 y 48 bytes.
En las declaraciones de matrices, el nombre de la matriz sin índices es un puntero al primer elemento. Así, en las declaraciones anteriores frase es un puntero a char y almacena la dirección de frase[0]. Lo mismo ocurre para numero y valor.
En un vector de N elementos, el primero se referencia con el índice 0 y el último con el índice N-1. Así, en el vector
int numero[16];
el primer elemento es numero[0] y el último numero[15].
Es importante tener en cuenta que C no hace comprobación de límites en el proceso de matrices. El control debe hacerlo el programador. Así, es posible escribir
numero[30] = 250;
manejando el elemento 30 del vector numero que se declaró para almacenar sólo 16 elementos. Las consecuencias para el programa suelen ser desastrosas.
En el siguiente programa se carga un vector de 10 caracteres desde el teclado, y se muestra después la dirección y contenido de cada elemento.
#include <stdio.h>
#include <conio.h>
void main (void)
{
register int i;
char vector[10];
for (i = 0; i <= 9; i++) vector[i] = getche ();
for (i = 0; i <= 9; i++) printf ("\nLugar: %d - Dirección: %p - Valor: %c", i, vector + i, vector[i]);
}
Cadenas de caracteres
Un caso particular de vector es la cadena de caracteres. Una cadena de caracteres se declara mediante
char nombre[num_car];
y permite almacenar num_car-1 caracteres y el carácter nulo '\0' de terminación. Por lo tanto, una declaración como
char frase[21];
es apta para almacenar 20 caracteres y el nulo.
C permite la inicialización de cadenas de caracteres en la declaración, mediante sentencias del tipo
char cadena[ ] = "Esto es una cadena de caracteres";
en la que no es necesario añadir el nulo final ni indicar el tamaño, pues lo hace automáticamente el compilador.
Las operaciones con cadenas de caracteres como copiar, comparar, concatenar, medir, etc., se hacen mediante funciones de la biblioteca estándar. Su utilización requiere incluir el archivo de cabecera string.h. Veamos alguna de ellas.
char *strcat (char *cad1, const char *cad2);
Concatena cad2 a cad1 devolviendo la dirección de cad1. Elimina el nulo de terminación de cad1 inicial.
Ejemplo:
char cad1[80], cad2[80];
...
...
printf ("\nTeclee una frase: ");
gets (cad1);
printf ("\nTeclee otra frase: ");
gets (cad2);
strcat (cad1, cad2);
puts (cad1);
En este ejemplo, si se teclea Primera frase para cad1 y Segunda frase para cad2, se muestra en pantalla
Primera fraseSegunda frase
Tendría el mismo efecto
puts (strcat (cad1, cad2));
Hay que asegurarse de que el tamaño de cad1 es suficiente para almacenar el resultado.
char *strchr (const char *cad, int ch);
Devuelve la dirección de la primera aparición del carácter ch en la cadena cad. Si no se encuentra devuelve un puntero nulo.
Ejemplo:
char *p, caracter, cadena[80];
int lugar;
...
...
printf ("\nTeclee un carácter: ");
caracter = getche ();
printf ("\nTeclee una frase: ");
gets (cadena);
p = strchr (cadena, caracter);
if (!p) printf ("\nNo está el carácter %c en la frase", caracter);
else {
lugar = p - cadena;
printf ("\nEl carácter %c ocupa el lugar %d de la frase", caracter, lugar);
}
int strcmp (const char *cad1, const char *cad2);
Para la comparación de cadenas de caracteres no se permite la utilización de los operadores >, <, >=, !=, etc., sino que se debe utilizar la función strcmp. Esta función compara lexicográficamente cad1 y cad2 y devuelve un entero que se debe interpretar como sigue:
< 0 | cad1 < cad2 |
0 | cad1 igual a cad2 |
> 0 | cad1 > cad2 |
Ejemplo:
char cad1[40], cad2[40];
...
...
printf ("\nTeclee una frase: ");
gets (cad1);
printf ("\nTeclee una frase: ");
gets (cad2);
n = strcmp (cad1, cad2);
if (!n) puts ("Son iguales");
else if (n < 0) puts ("La primera es menor que la segunda");
else puts ("La primera es mayor que la segunda");
Es necesario insistir en que la comparación no se hace en cuanto al tamaño de la cadena sino en cuanto al orden de los caracteres en el código ASCII. Esto quiere decir que si cad1 es ABCDE y cad2 es xyz, la función strcmp devuelve un valor negativo, pues se considera que cad1 < cad2 ya que el carácter A tiene un código ASCII menor que el carácter x.
char *strcpy (char *cad1, const char *cad2)
Copia la cadena cad2 en cad1, sobreescribiéndola. Devuelve la dirección de cad1. Hay que asegurarse de que el tamaño de cad1 es suficiente para albergar a cad2.
Ejemplo:
char cadena[40];
...
...
strcpy (cadena, "Buenos días");
int strlen (const char *cad);
Devuelve el número de caracteres que almacena cad (sin contar el nulo final).
Ejemplo:
char cad[30];
...
...
printf ("\nTeclee una frase: ");
gets (cad);
printf ("\nLa frase <<<%s>>> tiene %d caracteres", cad, strlen (cad));
char *strlwr (char *cad);
Convierte cad a minúsculas. La función no tiene efecto sobre los caracteres que no sean letras mayúsculas. Tampoco tiene efecto sobre el conjunto extendido de caracteres ASCII (código mayor que 127), por lo que no convierte las letras Ñ, Ç o vocales acentuadas. Devuelve la dirección de cad.
Ejemplo:
char cad[40];
...
...
printf ("\nTeclee una frase en mayúsculas: ");
gets (cad);
printf ("\nLa cadena en minúsculas es %s", strlwr (cad));
char *strrev (char *cad);
Invierte la cadena cad y devuelve su dirección.
Ejemplo:
char cad[80];
...
...
printf ("\nTeclee una frase: ");
gets (cad);
printf ("\nFrase invertida: %s", strrev (cad));
char *strset (char *cad, int ch);
Reemplaza cada uno de los caracteres de cad por el carácter ch. Devuelve la dirección de cad.
Ejemplo:
char cad[80];
...
...
strcpy (cad, "12345");
strset (cad, 'X');
puts ("Se visualiza una cadena con 5 X");
puts (cad);
char *strupr (char *cad);
Convierte cad a mayúsculas. La función no tiene efecto sobre los caracteres que no sean letras minúsculas ni sobre el conjunto extendido de caracteres ASCII (letras ñ, ç o vocales acentuadas). Devuelve la dirección de cad.
Ejemplo:
char cad[40];
...
...
printf ("\nTeclee una frase en minúsculas: ");
gets (cad);
printf ("\nLa frase en mayúsculas es %s", strupr (cad));
En el Capítulo 13 se muestran más funciones de cadenas de caracteres.
También existe un amplio conjunto de funciones que manejan caracteres individuales. La mayoría de estas funciones informan del tipo de carácter incluido en su argumento devolviendo un valor 1 ó 0. Estas funciones tienen su prototipo definido en ctype.h y generalmente no consideran el conjunto ASCII extendido. Alguna de estas funciones se explican a continuación:
int isalnum (int ch) Devuelve 1 si ch es alfanumérico (letra del alfabeto o dígito) y 0 en caso contrario.
int isalpha (int ch) Devuelve 1 si ch es una letra del alfabeto y 0 en caso contrario.
int isdigit (int ch) Devuelve 1 si ch es un dígito del 0 al 9, y 0 en caso contrario.
int islower (int ch) Devuelve 1 si ch es un letra minúscula y 0 en caso contrario.
int isupper (int ch) Devuelve 1 si ch es una letra mayúscula y 0 en caso contrario.
int tolower (int ch) Devuelve el carácter ch en minúscula. Si ch no es una letra mayúscula la función devuelve ch sin modificación.
int toupper (int ch) Devuelve el carácter ch en mayúscula. Si ch no es una letra minúscula la función devuelve ch sin modificación.
En el Capítulo 13 se muestran más funciones de caracteres.
El programa siguiente hace uso de alguna de las funciones anteriores para examinar una cadena de caracteres y convertir las minúsculas a mayúsculas y viceversa. Además cuenta cuántos caracteres son dígitos numéricos.
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <ctype.h>
void main (void)
{
char cadena[100];
int contador = 0;
register int i;
clrscr ();
printf ("\nTeclee una cadena de caracteres: ");
gets (cadena);
for (i = 0; i <= strlen (cadena); i++) {
if (isupper (cadena[i])) cadena[i] = tolower (cadena[i]);
else if (islower (cadena[i])) cadena[i] = toupper (cadena[i]);
else if (isdigit (cadena[i])) contador++;
}
printf ("\nLa cadena tiene %d dígitos numéricos\n", contador);
puts (cadena);
}
Punteros y matrices
Puesto que una matriz se identifica mediante la dirección del primer elemento, la relación entre punteros y matrices es estrecha. Sea la declaración
int matriz[100];
Podemos identificar un elemento, digamos el 25, de cualquiera de las dos formas siguientes:
matriz[24];
*(matriz + 24);
puesto que matriz (sin índices) es la dirección del primer elemento.
El uso de punteros para manejar matrices es, en general, más eficiente y rápido que el uso de índices. La decisión de qué método utilizar (punteros o índices) depende del tipo de acceso. Si se va a acceder a los elementos de la matriz de forma aleatoria, es mejor utilizar índices. Sin embargo, si el acceso va a ser secuencial, es más adecuado usar punteros. Este segundo caso es el típico en las cadenas de caracteres. Por ejemplo, las sentencias siguientes muestran en pantalla, carácter a carácter una cadena de caracteres.
char *p, cadena[30];
register int i;
...
...
for (i = 0; cadena[i]; i++) putch (cadena[i]);
...
...
p = cadena;
for (; *p; p++) putch (*p);
...
...
p = cadena;
while (*p) putch (*p++);
Las dos últimas son las más adecuadas. Otro ejemplo lo constituyen las 3 funciones que se describen a continuación. Estas funciones comparan dos cadenas de caracteres de modo similar a como lo hace strcmp().
int compara1 (char *cad1, char *cad2)
{
register int i;
for (i = 0; cad1[i]; i++) if (cad1[i] - cad2[i]) return (cad1[i] - cad2[i]);
return 0;
}
int compara2 (char *cad1, char *cad2)
{
char *p1, *p2;
p1 = cad1;
p2 = cad2;
while (*p1) {
if (*p1 - *p2) return (*p1 - *p2);
else {
p1++;
p2++;
}
}
return 0;
}
int compara3 (char *cad1, char *cad2)
{
char *p1, *p2;
p1 = cad1;
p2 = cad2;
for (; *p1; p1++, p2++) if (*p1 - *p2) return (*p1 - *p2);
return 0;
}
De las 3 funciones anteriores son más eficientes las dos últimas.
Matrices bidimensionales
Una matriz bidimensional es aquella que necesita dos índices para identificar un elemento. Puede decirse que una matriz bidimensional es una estructura de datos organizados en filas y columnas. Se declaran de la siguiente forma:
tipo nombre[nº filas][nº columnas];
Por ejemplo, para declarar una matriz de números enteros organizada en 8 filas y 6 columnas se escribe
int total[8][6];
El primer elemento de la matriz es total[0][0] y se almacena en una dirección de memoria identificada por total. El último es total[7][5].
Un caso particular de matrices bidimensionales lo constituyen las matrices de cadenas de caracteres. Por ejemplo, la sentencia
char cadenas[10][25];
declara una matriz de 10 cadenas de 24 caracteres más el nulo. Para acceder a una cadena en particular basta especificar el índice izquierdo (número de cadena). Así,
gets (cadena[6]);
lee del teclado una cadena de caracteres y la almacena en la séptima cadena (la primera es cadena[0]) de la matriz . Esta sentencia es equivalente a
gets (&cadena[6][0]);
Para acceder a un carácter concreto de una cadena hay que especificar ambos índices. Por ejemplo, la sentencia
cadena[3][9] = 'X';
almacena en el 10º carácter de la 4ª cadena el carácter X.
//Cambia en un grupo de cadenas las vocales por #
#include <stdio.h>
#include <conio.h>
#include <string.h>
void main (void)
{
char cadena[10][70];
register int i, j;
clrscr ();
for (i = 0; i <= 9; i++) {
printf ("\nCadena nº %d: ", i);
gets (cadena[i]);
}
for (i = 0; i <= 9; i++) {
for (j = 0; cadena[i][j]; j++) {
if (strchr ("aeiouAEIOU", cadena[i][j])) cadena[i][j] = '#';
}
puts (cadena[i]);
}
}
En el siguiente programa se carga del teclado una matriz de 10 cadenas de caracteres y se busca en ellas la primera aparición de la cadena HOLA, informando del número de cadena en que se encuentra y de la posición que ocupa en ella. Para ello se utiliza la función strstr explicada en el Capítulo 13 (página 212).
#include <stdio.h>
#include <conio.h>
#include <string.h>
void main (void)
{
char cadena[10][50];
register int i;
char *p;
clrscr ();
for (i = 0; i <= 9; i++) {
printf ("\nCadena nº %d: ", i);
gets (cadena[i]);
strupr (cadena[i]);
}
for (i = 0; i <= 9; i++) {
p = strstr (cadena[i], "HOLA");
if (p) printf ("\nCadena nº %d, posición %d", i, p - cadena[i]);
}
}
Podemos acceder a los elementos de una matriz bidimensional mediante punteros. Se accede al elemento matriz[i][j] mediante la fórmula:
*(matriz + i * nº_de_filas + j)
Matrices de más de 2 dimensiones
En C pueden manejarse matrices de más de 2 dimensiones. Para declarar estas matrices se hace
tipo nombre[tamaño 1][tamaño 2] ... [tamaño N];
La siguiente sentencia declara una matriz tridimensional de elementos de tipo float:
float matriz3D[5][3][8];
El problema de este tipo de matrices es la cantidad de memoria que pueden llegar a ocupar, ya que esta aumenta exponencialmente con el número de dimensiones. Es por ello que para estas estructuras suele utilizarse asignación dinámica de memoria, de modo que permite asignar o recortar memoria según se va necesitando. Estudiaremos esto en el Capítulo 9.
Las siguientes sentencias muestran como se puede cargar desde el teclado la matriz matriz3D.
for (i = 0; i <= 4; i++) {
for (j = 0; j <= 5; j++) {
for (k = 0; k <= 7; k++) {
printf ("\nElemento %d-%d-%d: ", i, j, k);
scanf ("%f", &matriz3D[i][j][k]);
}
}
}
Cómo inicializar matrices
Cualquier matriz puede ser inicializada en el momento de la declaración. Para ello se encierran entre llaves, separados por comas, los datos de la inicialización. Veamos algunos ejemplos:
float vector[5] = {1.23, 16.9, -1.2, 2.06, 31.15};
int tabla[2][3] = {5, 62, 34, 21, 43, 90};
Para más claridad, en las matrices de dos dimensiones suele hacerse:
int tabla[2][3] = { 5, 62, 34,
21, 43, 90};
En los dos últimos casos, la matriz tabla queda inicializada de la siguiente manera:
tabla[0][0] = 5 tabla[0][1] = 62 tabla[0][2] = 34
tabla[1][0] = 21 tabla[1][1] = 43 tabla[1][2] = 90
El mismo efecto se produce usando anidamiento de llaves:
int tabla[2[3] = { { 5, 62, 34 },
{ 21, 43, 90 } };
Las matrices de cadenas de caracteres pueden inicializarse de la forma
char frases[3][30] = { "Primera cadena", "Segunda cadena", "Tercera" };
En todas las matrices multidimensionales puede omitirse el primer índice cuando se inicializan en la declaración. Por ello, las declaraciones siguientes:
int tabla[3][4] = { 6, 12, 25, 4, 5, 13, 7, 2, 2, 4, 9, 6};
int tabla[ ][4] = {6, 12, 25, 4, 5, 13, 7, 2, 2, 4, 9, 6};
son idénticas. En el segundo caso, el compilador se encarga de calcular el tamaño correspondiente.
Matrices como argumentos de funciones
Cuando se pasa una matriz como argumento de una función, el compilador genera la llamada con la dirección del primer elemento. De esta forma se evita pasar la matriz completa, lo que consume mucha memoria y hace el proceso más lento. Sea, por ejemplo, la matriz unidimensional
int vector[30];
que se quiere pasar como argumento a una función mayor() que devuelve como valor de retorno el elemento más grande de la matriz. La llamada se hace de la forma
a = mayor (vector);
y la función mayor() puede declararse de cualquiera de las formas siguientes:
int mayor (int x[30]) { ... ... } | int mayor (int x[ ]) { ... ... } |
En cualquiera de los 2 casos el resultado es idéntico, pues ambas declaraciones le indican al compilador que se va a recibir en el argumento la dirección de un entero (la del primer elemento de la matriz). En el primer caso, el valor 30 no se tiene en cuenta.
El siguiente programa carga desde el teclado una matriz de enteros y muestra el mayor de ellos, calculándolo mediante una función.
#include <stdio.h>
void main (void)
{
register int i;
int vector[30];
for (i = 0; i <= 29; i++) {
printf ("\nElemento %d: ", i);
scanf ("%d", &vector[i]);
}
printf ("\nEl mayor es %d", mayor (vector, 30));
}
int mayor (int tabla[ ], int num_element)
{
register int i;
int max;
max = tabla[0];
for (i = 0; i < num_element; i++) if (max < tabla[i]) max = tabla[i];
return max;
}
Nótese que es necesario pasar el tamaño de la matriz como argumento, pues de lo contrario la función mayor() no tendría ninguna información sobre el número de elementos de la matriz.
Para las matrices bidimensionales, aunque se pasa sólo la dirección del primer elemento, es necesario especificar el número de columnas (segundo índice) para que el compilador sepa la longitud de cada fila. Por ejemplo, una función que reciba como argumento una matriz declarada como
int matriz2D[10][20];
se declara de la forma siguiente:
funcion (int x[ ][20])
{
...
}
y la llamada se hace con una sentencia como
funcion (matriz2D);
En general, para matrices multidimensionales, sólo se puede omitir el primer índice en la declaración. Por ejemplo, una matriz declarada mediante
int matriz3D[5][10][20];
se pasa como argumento de una función mediante una llamada de la forma
funcion (matriz3D);
y la declaración de la función tiene el siguiente aspecto:
funcion (int x[ ][10][20])
{
...
}
Argumentos de la función main()
Cuando, por ejemplo, escribimos desde el indicador del DOS una orden como
C:\>XCOPY *.DAT B: /S
estamos ejecutando un programa llamado XCOPY.EXE con 3 parámetros: *.DAT, B: y /S. Esos parámetros, de alguna forma, son leídos por el programa XCOPY.
Para hacer esto en cualquier programa C, es necesario modificar la forma en que se llama a la función main(), incluyendo en ella argumentos que permitan la obtención de los parámetros introducidos en la línea de órdenes. La forma de hacer esto es la siguiente:
main (int argc, char *argv[ ])
Veamos el significado de las variables argc y argv.
argc: Entero que indica el número de parámetros tecleados (incluye el nombre del programa).
argv[ ]: Matriz de cadenas de caracteres. Cada uno de los elementos argv[i] es una cadena que almacena un argumento.
La variable argc vale 1 como mínimo, puesto que se cuenta el nombre del programa. Los parámetros se identifican mediante argv de la siguiente manera:
-
argv[0] cadena que almacena el nombre del programa.
-
argv[1] cadena que almacena el primer parámetro.
-
argv[2] cadena que almacena el segundo parámetro.
-
...
-
...
-
argv[argc] vale cero (En realidad es un puntero nulo).
Para que los argumentos sean tratados como diferentes tienen que ir separados por uno o varios espacios blancos. Así, en el ejemplo anterior la variable argc vale 4 (nombre del programa y 3 parámetros). Si escribimos, por ejemplo
C:\>PROG PAR1,PAR2
la variable argc valdría 2, puesto que la cadena PAR1,PAR2 queda identificada como un sólo parámetro (almacenado en la cadena argv[1]) ya que la coma no actúa como separador. Para que PAR1 y PAR2 sean tratados como dos parámetros diferentes, debe ejecutarse PROG mediante
C:\>PROG PAR1 PAR2
El siguiente programa lista los parámetros, si los hay, de la línea de órdenes.
#include <stdio.h>
void main (int argc, char *argv[ ])
{
register int i;
printf ("\nNombre del programa: %s", argv[0]);
if (argc == 1) printf ("\nNo se han introducido parámetros");
else {
printf ("\nParámetros en la línea de órdenes: ");
for (i = 1; i < argc; i++) printf ("\n%d: %s", i, argv[i]);
}
}
La función main() soporta una variable más, llamada env:
env[ ]: Matriz de cadenas de caracteres. Cada uno de los elementos de la matriz es una cadena que almacena el nombre y contenido de una variable del entorno.
#include <stdio.h>
void main (int argc, char *argv[ ], char *env[ ])
{
register int i;
printf ("\nNombre del programa: %s", argv[0]);
if (argc == 1) printf ("\nNo se han introducido parámetros");
else {
printf ("\nParámetros en la línea de órdenes: ");
for (i = 1; i < argc; i++) printf ("\n%d: %s", i, argv[i]);
}
printf ("\nVariables de entorno: \n");
for (i = 0; env[i]; i++) puts (env[i]);
}
Si este programa esta compilado y linkado como PROG.EXE y se ejecuta como
C:\>PROG PAR1 PAR2 | C:\> PROG |
proporciona | la salida |
Nombre del programa: PROG | Nombre del programa: PROG |
Parámetros en la línea de órdenes: | No se han introducido parámetros |
1: PAR1 | Variables de entorno: |
2: PAR2 | COMSPEC=C:\DOS\COMMAND.COM |
Variables de entorno: | PROMPT=$P$G |
COMSPEC=C:\DOS\COMMAND.COM | PATH=C:\;C:\DOS;C:\WINDOWS |
PROMPT=$P$G | |
PATH=C:\;C:\DOS;C:\WINDOWS |
Matrices de punteros
Pueden definirse matrices de punteros, es decir, matrices cuyos elementos son direcciones. Por ejemplo,
int *pint[20];
char *pchar[40];
declaran una matriz pint de 20 punteros a enteros y otra matriz pchar de 40 punteros a caracteres.
Para asignar las direcciones se hace igual que con cualquier otro puntero. Mediante las siguientes sentencias
int *pint[20], a;
...
...
a = 20;
pint[3] = &a;
printf ("%d", *pint[3]);
se muestra en pantalla el número 20.
Un ejemplo típico de matriz de punteros es la matriz de cadenas de caracteres. Las declaraciones siguientes son equivalentes
char dias[ ][10] = { "Domingo", "Lunes", "Martes", "Miércoles",
"Jueves", "Viernes", "Sábado" };
char *dias[ ] = { "Domingo", "Lunes", "Martes", "Miércoles",
"Jueves", "Viernes", "Sábado" };
Punteros a punteros
En la mayoría de los casos un puntero apunta a un dato.
p | x | |
Dirección de x | Valor |
y *p proporciona el valor de x. Pero también se pueden definir punteros a punteros.
p1 | p2 | x | ||
Dirección de p2 | Dirección de x | Valor |
En este caso *p2 proporciona el valor de x. Pero p1 proporciona la dirección de p2. Para acceder a x mediante p1 hay que hacer **p1.
#include <stdio.h>
void main (void)
{
int x, *p, **q;
x = 10;
p = &x;
q = &p;
printf ("%d", **q);
}
Este programa muestra en pantalla el número 10 que almacena x.
Punteros a funciones
Puesto que una función ocupa una posición en la memoria, pueden definirse punteros a funciones, y hacer llamadas a la función por medio de la dirección. Esto se aplica generalmente para trabajar con matrices de funciones. Veámoslo con un ejemplo. El siguiente programa realiza las operaciones básicas suma, resta y producto, por medio de llamadas a funciones que forman parte de una matriz.
#include <stdio.h>
#include <conio.h>
int menu (void);
int suma (int, int);
int resta (int, int);
int producto (int, int);
int (*calculo[3]) (int, int) = { suma, resta, producto };
void main (void)
{
int n, x, y, resultado;
while (( n = menu()) != 3) {
printf ("\nTeclea dos números: ");
scanf ("%d %d", &x, &y);
resultado = (*calculo[n]) (x, y);
printf (" = %d", resultado);
getch ();
}
}
int menu (void)
{
int opcion;
clrscr ();
puts ("0. Suma");
puts ("1. Resta");
puts ("2. Producto");
puts ("3. Salir");
do {
opcion = getch ();
} while (opcion < '0' || opcion > '3');
return opcion - 48;
}
int suma (int a, int b)
{
printf ("\n%d + %d", a, b);
return a + b;
}
int resta (int a, int b)
{
printf ("\n%d - %d", a, b);
return a - b;
}
int producto (int a, int b)
{
printf ("\n%d * %d", a, b);
return a * b;
}
En general, un puntero a una función se declara de acuerdo a
tipo (*pfunc) ();
siendo pfunc el nombre del puntero, y tipo el tipo del valor devuelto por la función. Los paréntesis son necesarios, pues
tipo *pfunc ();
declara una función que devuelve un puntero. Para llamar a la función debe hacerse
(*pfunc) ();
Ejercicios
1. Carga mediante el teclado una matriz entera de 4 filas y 3 columnas. Calcula y muestra en pantalla la suma de cada fila y de cada columna por medio de dos funciones de prototipos
int suma_fila (int fila);
int suma_columna (int columna);
2. Construye un programa que cree y muestre en pantalla un cuadrado mágico de orden 3. Un cuadrado mágico es aquél en el que todas las filas y columnas suman lo mismo. Para ello, se coloca el valor 1 en el medio de la 1ª fila. Los siguientes valores (2, 3, 4, ...) se sitúan en la fila de arriba, columna de la derecha, salvo que esté ocupada, en cuyo caso se coloca inmediatamente debajo. Se supone que las filas y columnas de los extremos son adyacentes.
1 | 1 | 6 | 8 | 1 | 6 | |||||
3 | 3 | 5 | 3 | 5 | 7 | |||||
2 | 4 | 2 | 4 | 9 | 2 |
3. Construye un programa que cree y muestre en pantalla un cuadrado mágico de orden N impar (3 < N £ 19). El valor de N se leerá de la línea de órdenes (declarando adecuadamente la función main). Envía a pantalla un mensaje de error en cada uno de los siguientes casos:
-
El valor de N está fuera de rango.
-
No se ha dado valor a N en la línea de órdenes.
-
Hay demasiados parámetros en la línea de órdenes.
En cualquiera de los casos finalizarás el programa después de enviado el mensaje. (NOTA: Utiliza la función atoi() de la biblioteca estándar).
4. Construye un programa que cargue del teclado dos matrices enteras A y B de orden 3x3, y que visualice la matriz producto P = A · B.
La carga de matrices debes realizarla mediante una función a la que llamarás dos veces: una para cargar A y otra para cargar B. También calcularás los elementos pij mediante una función.
5. Construye dos funciones similares a strlwr() y strupr() que actúen también sobre los caracteres ñ, ç, letras acentuadas, etc. Se llamarán strminus() y strmayus().
6. Desarrolla las siguientes funciones:
-
Una función llamada burbuja() que ordene ascendentemente un vector de 100 elementos enteros por el método de la burbuja.
-
Una función llamada seleccion() que ordene ascendentemente un vector de 100 elementos enteros por el método de selección.
-
Una función llamada insercion() que ordene ascendentemente un vector de 100 elementos enteros por el método de inserción.
-
Una función llamada shell() que ordene ascendentemente un vector de 100 elementos enteros por el método shell.
-
Una función llamada quick() que ordene ascendentemente un vector de 100 elementos enteros por el método de ordenación rápida o Quick Sort.
Construye un programa que cargue un vector ordenado descendentemente
a[0] = 99 a[1] = 98 a[2] = 97 ... a[99] = 0
y que determine, mediante llamadas a las funciones anteriores, qué método de ordenación es más rápido. Para medir el tiempo utiliza la función de la biblioteca estándar biostime() cuyo prototipo está en bios.h.
7. Construye dos funciones de prototipos
char *Copia (char *cad1, const char *cad2);
char *Concatena (char *cad1, const char *cad2);
que realicen las mismas operaciones, respectivamente, que las funciones de biblioteca estándar strcpy() y strcat(). Haz dos versiones de cada función: una con índices y otra con punteros.
8. Construye una función de prototipo
char *Insertar (char *cad, int car, int pos);
que inserte el carácter car en la posición pos de la cadena cad. La función debe devolver la dirección de cad. Haz dos versiones de la función: una con índices y otra con punteros.
9. Construye una función de prototipo
char *Elimina (char *cad, int pos);
que elimine de cad el carácter situado en la posición pos. La función debe devolver la dirección de cad. Haz dos versiones de la función: una con índices y otra con punteros.
10. Construye una función de prototipo
char *Substr (char *orig, int desde, int n, char *dest);
que almacene en la cadena dest la subcadena de cad que comienza en la posición desde y tiene n caracteres. La función debe devolver la dirección de dest. Haz dos versiones de la función: una con índices y otra con punteros.
11. Crea un vector de 1000 elementos enteros ordenados descendentemente:
x[0] | x[1] | x[2] | ... | x[998] | x[999] |
999 | 998 | 997 | ... | 1 | 0 |
y ordénalo ascendentemente por el método de la burbuja. Haz dos versiones del programa: una con índices y otra con punteros. Mide la eficiencia de cada método utilizando la función clock() de la biblioteca estándar.
12. Utilizando punteros carga un vector de 50 elementos enteros (Genera los valores aleatoriamente mediante las funciones de la biblioteca estándar random() y randomize()). Posteriormente muestra cuál es el valor mayor, su posición en el vector y la posición de memoria que ocupa.
13. Haz un programa que cree un vector preparado para almacenar 50 datos enteros que funcione con estructura de pila. El programa ejecutará las siguientes acciones:
Mostrar el contenido de la pila.
Introducir un dato en la pila.
Sacar un dato de la pila.
Fin del programa.
El programa presentará este menú y realizará las acciones deseadas mediante funciones agrupadas en una matriz.
Descargar
Enviado por: | Juan |
Idioma: | castellano |
País: | España |