Está en la página 1de 49

JAIRO ENRIQUE MARTINEZ BANDA

Principios de un puntero

1. ¿Qué es un puntero?

a. Memoria RAM

Todos los objetos informáticos que hemos explicado, ya sean variables,


funciones, tablas, tuplas, etc., corresponden a un registro localizado en
alguna parte de la memoria principal, denominada también memoria RAM. En
pocas palabras, cada objetivo tiene una «dirección» en la memoria principal.
Esta dirección es un número entero y corresponde a una ubicación de la
memoria.

La memoria de 32 bits se construye sobre la base de un entrelazado de filas


de 4 bytes consecutivos. A partir del byte, que es la unidad de memoria
direccionable más pequeña (el byte son 8 bits y corresponde en C al tipo
char), la memoria se forma con estas «palabras» de cuatro bytes, 32 bits,
cuya dirección siempre es un múltiplo de cuatro, generalmente en
hexadecimal. A continuación, se muestra una representación de este
principio partiendo, por ejemplo, de la dirección «100» con una progresión de
4 en 4 mostrada en este caso en decimal para simplificar:

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA -1-
JAIRO ENRIQUE MARTINEZ BANDA

En un programa, cuando se declara una variable cualquiera, se le asigna


automáticamente una dirección de memoria en la compilación. Sean por
ejemplo tres variables en un programa:

char C=255;
int A=256, B=129;

Supongamos que la dirección de C es 100, la dirección de A podría ser 104 y


la dirección de B 116, lo que resultaría en memoria:

La variable B tiene por valor 129 y se encuentra en la dirección 116.

La variable A tiene por valor 256 y se encuentra en la dirección 104.

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA -2-
JAIRO ENRIQUE MARTINEZ BANDA

La variable C tiene por valor 255 y se encuentra en la dirección 100.

Cada nuevo objeto comienza siempre en una nueva de palabra. Solo los
objetos de tipo char (1 byte) o a veces short (2 bytes) pueden tener
direcciones de bytes intermedios. Esta es la razón por la que la dirección de
la variable entera A está en la nueva palabra, la siguiente a la dirección de la
variable de tipo char C. Los tres bytes de las direcciones 101, 102 y 103 no se
utilizan.

b. Una variable puntero

Un puntero es una variable que toma por valor direcciones de memoria. De


este modo, una variable puntero permite acceder al contenido de la dirección
de memoria que contiene. La variable puntero se codifica en cuatro bytes y,
como cualquier otra variable, tiene su propia dirección de memoria.

c. Cuatro operadores

Para utilizar direcciones de memoria, hay dos operaciones básicas:

ˇ
Obtener una dirección de memoria.
ˇ
Acceder a una dirección de memoria.

El operador de referencia: &, «dirección de», permite obtener una dirección de


memoria (lo que se utiliza con la función scanf()).

El operador de indirección: * , «asterisco», permite acceder a una dirección de


memoria. Hay otros dos operadores para acceder a direcciones de memoria
que son realmente abreviaciones de

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA -3-
JAIRO ENRIQUE MARTINEZ BANDA

escritura: el operador -> «flecha», que permite acceder a los campos de una
tupla a partir de su dirección de memoria, y el operador [ ] «corchete», que
permite acceder a los elementos de una tabla a partir de la dirección del
primer elemento.

d. Tres utilizaciones básicas de los punteros

Los punteros forman parte de una de las herramientas más potentes del
lenguaje C. Hay tres casos de uso de punteros:

Asignación dinámica de tablas

Con un puntero se puede reservar un espacio de memoria contiguo de


cualquier tamaño durante el funcionamiento del programa y usar este bloque
como una tabla. De este modo, el primer uso de los punteros consiste en
poder obtener dinámicamente tablas de cualquier tamaño y de cualquier tipo
(para más detalles, consultar la sección Asignación dinámica de tablas).

Punteros como parámetros de función

Con un puntero se puede acceder a una variable mediante su dirección de


memoria. Por este motivo, con un puntero como parámetro de una función,
se puede trasladar a una función la dirección de memoria de una variable y
en la función acceder a esta variable y modificar su valor a través de su
dirección de memoria. Es decir, que con un puntero se puede transformar un
parámetro de entrada en un parámetro de entrada/salida (para más detalles,
consultar la sección Punteros como parámetros de función).

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA -4-
JAIRO ENRIQUE MARTINEZ BANDA

Estructuras de datos compuestas (listas encadenadas, árboles, grafos)

Con los punteros se pueden elaborar estructuras de datos complejas que no


existen como tales en el lenguaje: las listas encadenadas, los árboles y los
grafos. Estas grandes figuras de la programación y la algorítmica se pueden
construir dinámicamente en C mediante el uso de punteros. (Hay un capítulo
dedicado a las listas.)

2. Declarar un puntero en un programa

Para declarar un puntero, es decir, para tener un puntero en un programa, hay


que escribir:

ˇ
El tipo del objeto al que apuntará.
ˇ
El operador * (a la izquierda del nombre del puntero).
ˇ
Un nombre (identificador) para el puntero.

Por ejemplo:

char *c; // declara un puntero a char


int *i, *j; // declara dos punteros a int
float *f1,*f2; // declara dos punteros a float

c es un puntero de tipo char*: puede tener direcciones de char.

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA -5-
JAIRO ENRIQUE MARTINEZ BANDA

i y j son dos punteros de tipo int*: pueden contener direcciones de int.

f1 y f2 son dos punteros de tipo float*: pueden contener direcciones de float.

Con tuplas es idéntico:

typedef struct player{ // definicin del tipo player


int x,y;
int dx,dy;
}player;

player *p; // declara un puntero a player

p es un puntero de tipo player* y p puede contener direcciones de tuplas


player.

3. Funcionamiento de los cuatro operadores

a. Operador de dirección: &

El operador & colocado a la izquierda de un objeto cualquiera en un programa


devuelve su dirección. Por ejemplo:

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA -6-
JAIRO ENRIQUE MARTINEZ BANDA

int k;
&k // esta expresin vale la direccin en memoria de la
// variable k

Cualquier dirección es un valor de puntero: si k es un objeto de tipo T,


entonces &k es de tipo puntero a T.

Observación:

Cabe decir que k debe ser lo que se llama un lvalue. Es decir, una expresión
que tiene una dirección de memoria accesible, en general una expresión a la
que se le pueda asignar un valor. En efecto, si i es un int, &i es la dirección de i
que apunta i. Pero una expresión como &(i+1) con los paréntesis no es
correcta sintácticamente, ya que (i+1) no se corresponde con una variable en
memoria y, por tanto, no es un lvalue. No es posible escribir algo como:
(i+1)=77.

El siguiente programa permite mostrar las direcciones de memoria de varias


variables utilizando el operador & (y el formato %p de printf() reservado a
valores de puntero, es decir, direcciones de memoria):

#include <stdio.h>

int main()
{

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA -7-
JAIRO ENRIQUE MARTINEZ BANDA

char c;
int i,j;
struct {
int x,y;
float dx,dy;
}s1;
printf("la direccin de c es: %p\n", &c);
printf("la direccin de i es: %p\n", &i);
printf("la direccin de j es: %p\n", &j);
printf("la direccin de s1 es: %p\n", &s1);
return 0;
}

Las expresiones, &c, &i, &j y &s1 corresponden respectivamente a las


direcciones de los objetos c, i, j y s1. Este programa permite visualizarlas.

b. Operador asterisco: *

El operador * puesto a la izquierda de un puntero que contiene la dirección de


memoria de un objeto devuelve el objeto que se encuentra en esa dirección.
Este operador permite acceder al objeto mediante su dirección y modificarlo.
Por ejemplo:

int i;
int* ptr = &i;

Si ptr vale la dirección del entero i, entonces *ptr e i corresponden a

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA -8-
JAIRO ENRIQUE MARTINEZ BANDA

la misma ubicación en memoria: *ptr e i son lo mismo.

El siguiente programa permite visualizar la adecuación existente, por el hecho


de usar el operador *, entre un objeto cualquiera y un puntero que contiene su
dirección:

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

int main()
{
int i = 23; // 1
int *ptr;

ptr = &i; // 2
printf("%d, ",*ptr);

*ptr = 55; // 2
printf("%d, ",i);

i = 777; // 3
printf("%d.",*ptr);
return 0;
}

El programa imprime por pantalla: 23, 55, 777.

(1) Declaraciones del entero i que vale 23 y del puntero a entero ptr.

(2) La dirección de i, es decir, la expresión &i, se asigna al puntero

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA -9-
JAIRO ENRIQUE MARTINEZ BANDA

ptr.

A partir de este momento, ptr contiene la dirección de la variable i. *ptr e i


corresponden a la misma ubicación de memoria. i y *ptr son idénticos, el
mismo objeto. De este modo, mostrar *ptr es mostrar i, el valor 23.

(3) El valor 55 se asigna a *ptr, es decir, a i e i ahora vale 55.

(4) El valor 777 se asigna a la variable i y el valor de la expresión *ptr también


es 777.

c. Operador flecha: ->

El operador flecha -> afecta únicamente a las tuplas. La flecha sirve


simplemente para facilitar la escritura, es la contracción de los operadores
asterisco y punto utilizados juntos.

Sea la definición de un tipo de tupla llamado «enemigo»:

typedef struct{
int vida;
float x,y,dx,dy;
int color;
} enemigo;

Dos variables de tipo enemigo:

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 10 -
JAIRO ENRIQUE MARTINEZ BANDA

enemigo S1,S2;

Una variable puntero de tipo enemigo*:

enemigo*p;

con

p=&S1;

Si se usa el operador asterisco para acceder a los campos de la estructura S1


a partir del puntero p, se obtiene:

(*p).vida=1;
(*p).x=rand()%80;
(*p).y=rand()%25;
etc.

Como se ha visto anteriormente, el puntero p contiene la dirección de S1 y la


expresión *p es equivalente a S1. *p puede usarse como S1 con el operador
punto para acceder a los campos de la tupla. Pero atención: hay que añadir
unos paréntesis porque el operador punto es prioritario en relación con el
operador asterisco. En efecto,

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 11 -
JAIRO ENRIQUE MARTINEZ BANDA

la expresión *p.vida equivalente a *(p.vida) significaría, por un lado, que p es


una tupla y no un puntero y, por otro, que el campo vida es un puntero, lo cual
no es el caso.

Para evitar errores y facilitar el uso de punteros a tupla la expresión p-> es


una notación abreviada de (*p). Para acceder a los campos de la tupla
enemigo, se escribe simplemente:

p->vida=1;
p->x=rand()%80;
p->y=rand()%25;
etc.

d. Operador corchete: [ ]

Una tabla es la dirección de memoria del primer entero:

int tab[50]; // tab vale &tab[0]

Debido a que una tabla es una dirección de memoria, se puede asignar a un


puntero:

int *p;
p=tab;

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 12 -
JAIRO ENRIQUE MARTINEZ BANDA

lo que significa que tab y p son equivalentes y se puede usar p en vez de tab:

int i;
for (i=0; i<50; i++)
p[i] = rand()%256;

y podríamos escribir también:

for (i=0; i<50; i++)


*(p+i) = rand()%256;

La expresión (p+i) vale la dirección de memoria de p, comienzo de tabla, más


un desplazamiento de i*sizeof(int) bytes en memoria; es la dirección de los
elementos siguientes. Para i=0 es la dirección de la tabla, el elemento 0; para
i=1 es la dirección del elemento siguiente que está cuatro bytes más
adelante; para i=2 es la dirección del elemento que está ocho bytes más
adelante, etc. De este modo, *(p+i) permite acceder a cada elemento i de la
tabla.

Sin embargo, esta segunda forma de escritura se utiliza poco. Generalmente,


se usan los operadores corchetes; p[i] es una contracción de *(p+i).

Como vemos, se pueden usar operadores

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 13 -
JAIRO ENRIQUE MARTINEZ BANDA

aritméticos con los punteros. Pero tenga mucho cuidado: está


expresamente prohibido acceder a posiciones de memoria no
reservadas debido al peligro de cuelgues o de comportamientos
inesperados del programa.

e. Prioridad de los cuatro operadores

La flecha y el punto para acceder a los campos de una tupla, el operador


corchete para la indexación de la tabla y los paréntesis para la llamada a la
función son los de prioridad más alta. La dirección, que devuelve la referencia
en memoria de un objeto, y el asterisco, que permite acceder a un objeto
mediante su dirección, son justamente del nivel inferior (ver Anexo - Prioridad
y asociatividad de operadores).

4. Asignación dinámica de memoria

a. La función malloc()

Hemos visto casos en los que un puntero recibe como valor la dirección de
una variable previamente declarada. Sin embargo, un puntero también
permite obtener dinámicamente, durante el funcionamiento del programa,
una dirección de memoria asignada para una variable de cualquier tipo. Para
ello, el lenguaje C nos proporciona una función importante:

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 14 -
JAIRO ENRIQUE MARTINEZ BANDA

void* malloc(size_t tam);

Esta función asigna un bloque de memoria de tam bytes (el tamaño se pasa
por parámetro) y devuelve la dirección de memoria de la zona asignada (o
NULL si no hay suficiente memoria). El void* es un puntero genérico, es decir,
que puede funcionar con cualquier tipo de objeto. El funcionamiento del
sistema garantiza que la dirección asignada está correctamente reservada
para el objeto solicitado por el programa y que en ningún caso se usará para
otros menesteres. El tamaño de una zona de memoria para un objeto
cualquiera de tipo T se obtiene simplemente con el operador sizeof (T). De
este modo, si ptr es un puntero de tipo T*, la llamada a la función es:

ptr = malloc (sizeof( T ));

Y se tendrá en ptr la dirección de una zona de memoria de tamaño suficiente


reservada para un objeto de tipo T. Por ejemplo:

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

int main()
{

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 15 -
JAIRO ENRIQUE MARTINEZ BANDA

int*iptr;
float* tab[10]; // tabla de punteros a float
int i;

iptr=malloc(sizeof(int));
*iptr=45;
printf("*iptr, en la direccin %p vale: %d\n"
, iptr, *iptr);

srand(time(NULL));
for(i=0; i<10; i++){
tab[i]=malloc(sizeof(float));
*tab[i]= (rand() / float
( )RAND_MAX)*5;
printf("*tab[%d] en la direccin %p vale: %.2f\n"
,
i,tab[i], *tab[i]);
}
return 0;
}

En este programa hay un puntero a entero y una tabla de punteros a float. Al


empezar, se asigna una dirección de memoria para la variable puntero a
entero iptr y el valor 45 se asigna en esta dirección de memoria. Se muestran
la dirección obtenida por iptr y el valor asignado a esta dirección. A
continuación, se asigna una dirección de memoria a cada uno de los
punteros de la tabla de punteros. A cada una de las ubicaciones de memoria
correspondientes se le asigna un valor aleatorio en coma flotante entre 0 y 5.
La dirección de cada puntero, así como el valor aleatorio obtenido de cada
puntero, se muestran por pantalla.

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 16 -
JAIRO ENRIQUE MARTINEZ BANDA

b. Liberar la memoria asignada: la función free()

La memoria asignada queda reservada, es decir, inutilizable para otro fin en el


programa. Por este motivo es importante liberar la memoria cuando el objeto
asignado ya no se usa en el programa, para dejar esta memoria disponible de
nuevo. Esta operación, inversa a la asignación, se realiza con una llamada a
la función:

void free(void*p);

Por ejemplo, para liberar la memoria asignada al puntero anterior, se debe


utilizar la siguiente llamada:

free(ptr);

En C, durante el funcionamiento de un programa, no hay un garbage collector


que haga el trabajo de forma automática, como en Java o en C#, por ejemplo.
El riesgo que se corre es agotar la memoria RAM disponible y que el
programa se quede sin memoria para poder funcionar en un momento dado.
Esto es posible en particular con pequeños objetos en robótica o similares.
Por ejemplo, en el siguiente programa:

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

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 17 -
JAIRO ENRIQUE MARTINEZ BANDA

int main()
{
double*p[500000];
int i;
int fin=0;
do{
printf("\n\n");

for (i=0; i<500000; i++)


p[i]=malloc(sizeof(double)) ; // 1

for (i=0; i<500000; i++) // 2


free(p[i]) ;

printf("¿Jugar de nuevo? (s/n)\n"


);
fin=getch();

}while(fin!=n);
return 0;
}

En cada iteración de bucle se asignan 500000 direcciones de memoria (1)


para un total de 4 millones de bytes (500000*8 bytes). Si la memoria no se
libera, cada iteración ocupa aún más memoria, y así hasta que el programa se
bloquea. En cambio, si la memoria asignada se libera (2), el programa puede
funcionar indefinidamente.

Digamos que es una cuestión del día a día del programador de C: cuando el
programa finaliza, debe poner especial atención en que

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 18 -
JAIRO ENRIQUE MARTINEZ BANDA

ese programa deje la memoria en el estado en que la encontró inicialmente.


En la salida del programa, por norma, hay que desasignar toda la memoria
previamente asignada.

c. El puntero genérico void*

La función malloc(), explicada anteriormente, devuelve un puntero de tipo


void*.

El puntero void* es un puntero de tipo indefinido, su uso principal consiste en


utilizarlo como un puntero llamado «genérico», es decir, un puntero
independiente de cualquier tipo en particular. Es solo una dirección de
memoria para un espacio contiguo de memoria de cualquier tamaño. Con tal
puntero se puede manipular una dirección de memoria de un objeto cuyo tipo
se ignora.

Es el caso concreto de la función malloc(), que se usa para cualquier tipo de


objeto: int*, char*, float*, double*, así como para direcciones de memoria de
estructuras de datos creadas por el programador. La función debe poder
asignar memoria para cualquier tipo de objeto. Es por ello por lo que la
función malloc() devuelve un puntero genérico, es decir, la dirección de una
zona de memoria contigua del tamaño en bytes pasados por parámetro a la
función.

No se puede usar void* con el operador * sin cast en un programa

Todas las operaciones con el operador * (asterisco) aplicado a un puntero


genérico void* necesitan un cast para poderse realizar; por ejemplo:

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 19 -
JAIRO ENRIQUE MARTINEZ BANDA

int toto=55;
void* ptr = &toto;
printf("toto=%d\n",*((int*)ptr)); // esta instruccin funciona con el c
printf( "toto=%d\n", *ptr); // si no hay cast, se produce un e
// la compilacin.

d. El valor NULL

NULL se define en <stddef.h>. Es un valor especial de puntero que, de hecho,


vale 0. C garantiza que 0 no será nunca una dirección usable para un objeto
cualquiera. Y 0, que no es por lo tanto una dirección de memoria, es el único
caso en el que un entero puede asignarse a un puntero. Se trata de hecho de
una conversión del tipo de la forma ( (void*) 0).

Por ello, es frecuente encontrar comparaciones de un puntero con el valor


NULL. Por ejemplo, si la función malloc() no puede asignar suficiente
memoria, devuelve NULL y se puede garantizar que el programa seguirá
funcionando incluso si la memoria es insuficiente, con una comprobación del
valor de retorno:

ptr=(int*)malloc(sizeof(int));
if (ptr!=NULL){
printf("malloc ha tenido Øxito, el puntero se puede usar\n"
);
*ptr=rand()%300;

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 20 -
JAIRO ENRIQUE MARTINEZ BANDA

}
else
printf("memoria insuficiente,"
"el puntero no se puede usar\n"
);

Por otro lado, con mucha frecuencia es necesario inicializar los punteros a
NULL en su declaración en un programa para evitar acceder por descuido a
zonas de memoria no reservadas (los valores residuales albergados en un
puntero sin inicializar), lo que cuelga el programa.

5. Atención a la validez de una dirección de


memoria

a. Validez de una dirección de memoria

La asignación dinámica requiere mucha atención por parte del programador.


Es él quien se encarga de la asignación y de la liberación de la memoria.

En efecto, cuando una tabla estática o cualquier tipo de variable se declara en


un programa, el espacio de memoria requerido se reserva automáticamente
por el compilador en el momento de la compilación. Por este motivo, se
puede escribir en este espacio y utilizar la tabla o la variable. En cambio, esto
no sucede con un puntero.

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 21 -
JAIRO ENRIQUE MARTINEZ BANDA

Cuando un puntero se declara en el programa, no contiene la dirección de


memoria de un bloque reservado. Lo que realmente contiene es cualquier
valor, una dirección aleatoria, lo que había en memoria en ese instante y, si se
intenta escribir en una dirección de memoria no reservada, el programa se
cuelga o se aborta la ejecución repentinamente con un mensaje de error. Por
ejemplo:

#include <stdio.h>

int main()
{
char*ptr;
char tab[80];
printf("entre una frase:\n"
);
fgets(tab,80,stdin);
printf("tab: %s\n",tab);

printf("entre otra frase:\n"


);
fgets(ptr,80,stdin); // ¡¡ERROR!! el programa sale
printf("ptr: %s\n",ptr);
return 0;
}

El puntero ptr no contiene una dirección válida, no se le ha hecho una


asignación, la dirección que contiene no es accesible en escritura. Intentar
acceder a él provoca un error de ejecución. La dificultad de este tipo de
problemas es que el código compila a la perfección. Este tipo de errores no
son detectados por el

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 22 -
JAIRO ENRIQUE MARTINEZ BANDA

compilador.

En cambio, si por ejemplo asignamos a ptr la dirección de la tabla tab,


funciona:

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

int main()
{
char*ptr;
char tab[80];
printf("entre una frase:\n"
);
fgets(tab,80,stdin);
printf("tab : %s\n",tab);

ptr=tab;
printf("entre otra frase:\n"
);
fgets(ptr,80,stdin); // ok
printf("ptr: %s",ptr); //
printf("tab: %s\n",tab); // ptr y tab son idØnticos
return 0;
}

ptr toma la dirección de tab, que está debidamente reservada por la máquina
para la tabla tab. Entonces ptr y tab designan el mismo espacio de memoria;
escribir a partir de ptr es escribir a partir de tab. Es la misma dirección de
memoria, la misma ubicación en la

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 23 -
JAIRO ENRIQUE MARTINEZ BANDA

memoria. Por lo tanto, atención, porque en este caso ptr y tab son dos
accesos para el mismo bloque de memoria, y no dos bloques distintos.

Otra solución consiste en asignar dinámicamente un segundo bloque de


memoria y copiarle el contenido del bloque tab (ver sección Asignación
dinámica de tablas):

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

int main()
{
char*ptr;
char tab[80];
int i;
printf("entre una frase:\n"
);
fgets(tab,80,stdin);

// asignacin de un nuevo bloque


ptr=(char*)malloc(sizeof(char)*strlen(tab)+1);

// copia de tab
strcpy(ptr,tab);

// modificacin de la copia
for(i=0; i<strlen(ptr); i++)

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 24 -
JAIRO ENRIQUE MARTINEZ BANDA

ptr[i]++;

// ptr y tab son distintos


printf("ptr: %s\n",ptr);
printf("tab: %s\n",tab);
return 0;
}

Esta vez, ptr y tab son dos bloques distintos y cada uno de ellos corresponde
a una dirección de memoria válida.

b. ¿Por qué hay que realizar casting con el retorno de las funciones
de asignación?

El retorno de la función malloc(), por ejemplo, puede ser asignado a cualquier


tipo de puntero sin ningún control. Por ejemplo:

char c;
double *d;

d=&c;

provoca un aviso «warning» en la compilación: asignación de puntero


incompatible. &c es de tipo char* y d es de tipo double*. Es mejor tener
cuidado, porque el puntero d adquiere la dirección de un char, es decir, el
espacio de memoria asignado de un solo byte. Si escribimos fuera del
espacio reservado, el programa se cuelga:

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 25 -
JAIRO ENRIQUE MARTINEZ BANDA

*d=512; // provoca el cuelgue del programa.

En cambio, si escribimos:

double *d;
d=malloc(sizeof(char)); // atencin, error invisible

La situación es idéntica. El espacio de memoria reservado es de un solo byte


cuando un double realmente espera 8 bytes. Esto queda inadvertido, ya que
no se muestra ningún mensaje de error ni de aviso por parte del compilador;
pasa discretamente y puede convertirse en un bug dificilísimo de encontrar
que puede provocar el cuelgue del programa de vez en cuando.

Así, para beneficiarse de un mensaje de aviso sobre la compatibilidad del


retorno void* de función malloc() con el puntero destino, es mejor realizar
siempre el casting del void* al tipo deseado. Este cast se realiza colocando
entre paréntesis el tipo de puntero deseado a la izquierda de la función. De
este modo, si en nuestro ejemplo escribimos:

double *d;
// retorno de malloc transformÆndolo a char*:
d=(char*)malloc(sizeof(char)); // entonces el error queda

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 26 -
JAIRO ENRIQUE MARTINEZ BANDA

// visible en la compilacin

Se produce un mensaje de advertencia «warning» por el hecho de la


incompatibilidad entre el puntero d double* y la asignación para un char*. Por
otro lado, esta práctica obliga a estar atento a la llamada de malloc()
realizada: el tamaño solicitado debe estar acorde con el tipo de dato
devuelto.

6. Caso de las tablas de punteros

a. Una estructura de datos muy útil

La tabla de punteros no permite almacenar objetos, sino direcciones de


memoria de objetos. Por ejemplo, sea la tupla trol:

typedef struct trol{


int x,y;
int color;
}t_trol;

Podemos definir una tabla estática de punteros, que es una tabla normal que
contiene punteros, en este caso de tipo t_trol*.

#define NUMMAX 100

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 27 -
JAIRO ENRIQUE MARTINEZ BANDA

t_trol* tab[NUMMAX]; // una tabla de punteros

Para utilizar una tabla de punteros, hay que velar por que cada puntero
contenga una dirección de memoria válida:

for (i=0; i<NUMMAX; i++){

tab[i]=(t_trol*)malloc(sizeof(t_trol));
// asignacin de memoria

// Cada elemento es un puntero de la tupla:


tab[i]->x=rand()%800;
tab[i]->y=rand()%600;
tab[i]->color=rand()%256;
}

Por otro lado, es una tabla estática y se comporta como tal en parámetros de
función:

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

void inicializacion
(t_trol* t[])
{
int i;

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 28 -
JAIRO ENRIQUE MARTINEZ BANDA

for (i=0; i<NUMMAX; i++){

t[i]=(t_trol*)
malloc(sizeof(t_trol));
t[i]->x=rand()%800;
t[i]->y=rand()%600;
t[i]->color=rand()%256;
}
}
void mostrar(t_trol*t[])
{
int i;
for (i =0; i<NUMMAX; i++){
printf("%4d %4d %4d\n"
,t[i]->x,t[i]->y,t[i]->color);
}
}
int main()
{
t_trol* ALL[NUMMAX];

inicializacion(ALL);
mostrar(ALL);
return 0;
}

Utilizar direcciones de memoria de objetos, más que los objetos


directamente, es bueno sobre todo para los parámetros de las funciones
asociadas a los tratamientos de estos objetos. El hecho de poder pasar la
dirección de memoria por parámetro a la función transforma la entrada en
salida: se puede escribir en la dirección

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 29 -
JAIRO ENRIQUE MARTINEZ BANDA

pasada (ver sección Punteros como parámetro de funciones - Paso por


referencia).

b. Una tabla de cadenas de caracteres

Las cadenas de caracteres son tablas de caracteres y terminan con el


carácter ’\0’. A menudo se usan los punteros char* para representarlas y, por
lo tanto, contienen la dirección del comienzo de la tabla.

Lo interesante es entonces poder formar conjuntos de cadenas de caracteres


más fácilmente. Una lista de palabras se puede convertir en una tabla de
punteros de caracteres. Por ejemplo:

char* lista_pal[]={
"titi",
"toto practica bicicleta"
,
"tutu",
"tata goes to the sea"
,
"fin" };

Con una matriz de char:

char mat_pal[5][30]={"titi",
"toto practica bicicleta"
,
"tutu",
"tata goes to the sea"
,
"fin" };

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 30 -
JAIRO ENRIQUE MARTINEZ BANDA

No es lo mismo en memoria. La matriz de char es menos flexible. En el caso


de la tabla de punteros a carácter, el tamaño de memoria no es exactamente
el del número de caracteres. En el de la matriz de caracteres, que es una tabla
de tablas de caracteres, se tiene un bloque de memoria de 5*100 bytes
reservado sea cual sea el tamaño de las palabras, y las palabras no pueden
exceder los 100 caracteres.

Cuando el tamaño de la tabla de char* no se hace explícito, por ejemplo en el


caso de una lista muy larga de palabras o frases, el problema es saber el final
de la tabla. Para lista_pal y mat_pal, la palabra "fin" indica que se trata de la
última palabra de la tabla y ambas pueden recorrerse de la siguiente forma:

int i;
for (i=0; strcmp("fin", lista_pal[i]) !=0 ; i++)
printf("%s\n",lista_pal[i]);

La función strcmp() compara dos cadenas de caracteres y devuelve un valor


negativo, cero o positivo según si la primera es menor, igual o superior
respectivamente en orden lexicográfico.

En nuestro ejemplo, el bucle continuará hasta llegar al índice de la cadena


"fin" (el índice i es igual a 4). La función strcmp() devolverá entonces un 0, la
prueba se evalúa como falsa y el bucle se detiene.

Otro método consiste en utilizar la dirección 0 de memoria, el valor NULL. La


siguiente forma permite obtener un centinela a NULL en

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 31 -
JAIRO ENRIQUE MARTINEZ BANDA

una tabla de char*:

((char*)0)

El valor 0, de tipo int, con casting a char* se acepta en una tabla de char*
como una cadena de caracteres. La lista de palabras se puede escribir de la
siguiente forma:

char* lista_pal[ ] ={
"titi",
"toto practica bicicleta"
,
"tutu",
"tata goes to the sea"
,
((char*)0) };

y la comprobación del bucle pasa a ser:

int i;
for (i=0; lista_pal[i] !=NULL ; i++)
printf("%s\n",lista_pal[ i ]);

c. Utilizar los argumentos de línea de comandos

Se pueden pasar argumentos a los parámetros del main en el momento de la


ejecución de la aplicación mediante una ventana de consola (intérprete de
comandos). Los parámetros del main(),

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 32 -
JAIRO ENRIQUE MARTINEZ BANDA

cuando puede haber argumentos, son:

int main(int argc, char* argv[])


{
(...)
return 0 ;
}

Toda la información se transmite mediante la tabla de cadena de caracteres


argv y el entero argc proporciona el número de cadenas transmitidas.
Siempre hay al menos una cadena en el índice 0, es el nombre del programa,
y argc siempre vale como mínimo 1.

Ahora ya podemos construir aplicaciones que reciben parámetros en su


inicio. Por ejemplo:

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

int main(int argc, char*argv[])


{
int i;
printf("nombre del programa: %s\n"
,argv[0]);
if (argc>1)
for (i=1;i<argc; i++ )
printf("param %d: %s\n"
,i,argv[i]);

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 33 -
JAIRO ENRIQUE MARTINEZ BANDA

// continuacin del programa que tendrÆ o no


// en cuenta los argumentos recibidos.
return 0;
}

Este programa muestra su nombre y, además, si se le han pasado


argumentos, los muestra.

En Windows, se puede acceder al intérprete de comandos (una ventana de


consola) yendo al menú Inicio, en la carpeta Accesorios. Para iniciar el
ejecutable a partir del intérprete, hay que acceder al ejecutable tecleando su
ruta exacta desde la raíz del disco. El comando cd permite desplazarse
dentro de la estructura de directorios: cd .. (dos puntos) se usa para subir un
nivel y cd carpeta1 para descender a la carpeta1. Lo mejor, para comenzar, es
copiar su ejecutable a la raíz de C, a continuación subir hasta la raíz (cd ..
hasta la raíz) y llamar a su programa (en este caso, el programa se llama
test.exe):

C:\> test.exe

Si se pulsa [Enter], y el programa está en el sitio adecuado, el programa se


iniciará. Como no hay argumentos, termina mostrando únicamente su
nombre.

Los argumentos se colocan después de la llamada al programa, por ejemplo:

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 34 -
JAIRO ENRIQUE MARTINEZ BANDA

C:\> test.exe tata usa la bicicleta

muestra:

nombre del programa: test.exe


param1: tata
param2: usa
param3: la
param4: bicicleta

Se realiza un desacople con los espacios. Para evitarlo, basta con agrupar los
conjuntos de palabras con comillas dobles:

C:\> test.exe"tata usa" "la bicicleta"

muestra:

nombre del programa: test.exe


param1: tata usa
param2: la bicicleta

Por supuesto, estas cadenas pueden contener números y convertirse a


continuación en el programa (utilizando las funciones

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 35 -
JAIRO ENRIQUE MARTINEZ BANDA

estándar adecuadas atoi(), atof(), etc.).

El argumento puede ser un nombre de archivo que contenga mucha


información para el funcionamiento del programa, etc.

7. Experimento: conocimientos básicos de


punteros

/***************************************************************

¿QuØ es un puntero?
Una variable que adquiere œnicamente direcciones de memoria como
valor.

¿Para quØ sirven los punteros?


Tres usos:
1) como parÆmetro de funcin: permite el paso de la direccin de
memoria de una variable y transformar la entrada en salida
(posibilidad de escribir en una direccin de memoria)

2) asignacin dinÆmica de objetos o de tablas de objeto

3) crear estructuras de datos dinÆmicas no nativas en C:


listas encadenadas, Ærboles, grafos

¿Cmo se usan?
Hay cuatro operadores asociados y tres funciones de asignacin

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 36 -
JAIRO ENRIQUE MARTINEZ BANDA

de memoria (para la asignacin dinÆmica)

Los cuatro operadores son:


1) "direccin de": & permite obtener la direccin de memoria de
una variable

2) "asterisco": * permite declarar un puntero y acceder a una


direccin de memoria

EJEMPLO:
int a;
int *ptr; // declaracin de un puntero a entero
ptr = &a; // ptr toma por valor la direccin de a
*/

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

int main()
{
int a;
int*ptr=&a;

a= 100;
*ptr=200;
printf("a : %d / *ptr : %d\n"
,a,*ptr);

return 0;
}

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 37 -
JAIRO ENRIQUE MARTINEZ BANDA

/***************************************************************

3) "flecha": -> permite acceder a un campo de una tupla mediante


un puntero

EJEMPLO:
struct test{
int x,y;
} t;

struct test *ptr;


ptr=&t;
ptr->x=450;
ptr->y=90;

equivale a: (*ptr).x=450 y (*ptr).y=90


la flecha es justo una contraccin, un elemento para facilitar la escritu
*/
/*
#include <stdio.h>
#include <stdlib.h>

int main()
{
struct test{
int x,y;
} t;
struct test *ptr;

ptr=&t;

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 38 -
JAIRO ENRIQUE MARTINEZ BANDA

ptr->x=450;
ptr->y=90;
printf("t.x=%d, t.y=%d\n", t.x, t.y);

return 0;
}
*/
/***************************************************************
4) "corchetes": [] es el operador de tabla, desde una
direccin de partida permite acceder a las direcciones de
distintos elementos de la tabla

EJEMPLO:

int tab[50];
int*ptr;
ptr=tab; // ptr toma la direccin de la tabla por valor
// que es la direccin del primer elemento de esta

for (i=0; i<50; i++)


ptr[i] = rand()%256;

// equivalente a:
for (i=0; i<50; i++)
*(ptr+i) = rand()%256;

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

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 39 -
JAIRO ENRIQUE MARTINEZ BANDA

int main()
{
int tab[10];
int*ptr;
int i;

ptr=tab;
for (i=0; i<10; i++){
ptr[i] = rand()%256;
printf("ptr[%d] = %d\n",i,ptr[i]);
}

// equivalente a:
for (i=0; i<10; i++){
*(ptr+i) = ptr[i]+1;
printf("*(ptr+%d) = %d\n",i,*(ptr+i));
}
return 0;
}
*/

8. Puesta en práctica: conocimientos básicos de


punteros

a. Declarar punteros y operar con ellos

Ejercicio 1

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 40 -
JAIRO ENRIQUE MARTINEZ BANDA

En un programa, declarar un entero, un double y un float. Asignarles valores


aleatorios entre 600 y 700 con un valor decimal. Mostrar los valores.
Modificar los valores de cada variable mediante su dirección de memoria
obtenida en un puntero y mostrar el nuevo resultado. Al finalizar, el programa
pregunta al usuario si hay que volver a empezar o salir.

Ejercicio 2

En un programa, declarar dos variables, asignar a cada una un valor e


intercambiar los valores sin tocar directamente las variables, sino pasando
sus direcciones de memoria obtenidas en punteros. Mostrarlas antes y
después de la modificación. El programa finaliza si el usuario lo solicita.

Ejercicio 3

Sea una tupla persona que incluye datos tales como: nombre, apellidos,
dirección, edad, fecha de nacimiento, nacionalidad, trabajo y hobby. En un
programa:

ˇ
Definir el tipo.
ˇ
Inicializar una tupla persona únicamente accediendo a ella a través
de su dirección de memoria.
ˇ
Mostrar el resultado.
ˇ
Volver a empezar o salir.

Modificar el programa para tener una tabla de num tuplas persona. Escribir
una función de inicialización e inicializar la tabla (si puede ser con valores
aleatorios, mejor). El usuario puede modificar el

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 41 -
JAIRO ENRIQUE MARTINEZ BANDA

elemento que desee siempre pasando la dirección mediante un puntero.

Ejercicio 4

En un programa, sea una tabla de enteros inicializados a 0:

El usuario entra un número de modificaciones.

Las modificaciones se ejecutan a continuación en elementos seleccionados


al azar, únicamente si el valor del elemento es inferior a 10. Cada
modificación utiliza la dirección de memoria del elemento seleccionado
mediante un puntero a entero. Salir o volver a empezar.

b. Pruebas con tablas/punteros

Ejercicio 5

En un programa, inicializar una tabla estática de 5 enteros con valores


comprendidos entre 0 y 255 (en la declaración). A continuación, obtener la
dirección de la tabla con un puntero char* y mostrar la tabla con el puntero de
char. ¿Qué sucede? ¿Cuántos valores cree que pueden visualizarse? ¿Qué
bucle permite recorrer todo el espacio de memoria de la tabla?

Ejercicio 6

¿Cuál es el resultado de este programa?

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 42 -
JAIRO ENRIQUE MARTINEZ BANDA

#include <stdio.h>

int main()
{
int t[3];
int i, j;
int* ptr;

for (i=0; j=0; i<3; i++) // 1


t[i]=j++ +i;

for (i=0; i<3; i++) // 2


printf("%d ", t[i]);
putchar(\n);

for (i=0; i<3; i++) // 3


printf("%d ", *(t+i));
putchar(\n);

for (ptr=t; ptr<t+;


3 ptr++) // 4
printf("%d ", *ptr);
putchar(\n);

for (ptr=t+2; ptr>=t; ptr--) // 5


printf("%d ", *ptr);
putchar(\n);

return 0;
}

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 43 -
JAIRO ENRIQUE MARTINEZ BANDA

Ejercicio 7

En un programa se declara una tabla de 15 enteros pero sin utilizar un


puntero. Escribir de dos formas distintas, una con el operador corchete y otra
con el operador asterisco *:

ˇ
La inicialización de la tabla con 12 valores aleatorios y 3 entrados por
el usuario.
ˇ
La visualización de la tabla.
ˇ
La búsqueda del más grande.
ˇ
La búsqueda del más pequeño.
ˇ
La visualización de los resultados.

Ejercicio 8

En un programa se declara una tabla de 10 enteros.

ˇ
Escribir una función de inicialización de la tabla que la recorra con un
puntero.
ˇ
Escribir una función de visualización de la tabla que la recorra con un
puntero.
ˇ
Salir o volver a empezar.

Ejercicio 9

En un programa se declara una matriz de 20 * 15 enteros.

ˇ
Escribir una función de inicialización de la matriz en la que la matriz
se recorra con un puntero. Los valores serán aleatorios, excepto por
una entrada del usuario cada 100

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 44 -
JAIRO ENRIQUE MARTINEZ BANDA

valores.
ˇ
Escribir una función de visualización paralelamente recorrida por un
puntero.
ˇ
Salir o volver a empezar.

Ejercicio 10

En un programa:

ˇ
Introducir una cadena de caracteres.
ˇ
Escribir una función de visualización de la cadena recorriéndola en
sentido inverso con un puntero.
ˇ
Salir o volver a empezar.

c. Conocimientos básicos de asignación dinámica

Ejercicio 11

En un programa:

ˇ
Asignar memoria para un char, un entero y un float.
ˇ
Inicializar con valores introducidos por el usuario.
ˇ
Visualizar.
ˇ
Salir si el usuario lo solicita (si no, volver a empezar, ¡cuidado con la
memoria!).

Ejercicio 12

En un programa:

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 45 -
JAIRO ENRIQUE MARTINEZ BANDA

ˇ
Escribir una función de asignación de memoria con control de errores
para un entero.
ˇ
Inicializar una tabla de 10 punteros a entero.
ˇ
Asignar valores decrecientes a los enteros.
ˇ
Mostrar los valores.
ˇ
Salir si el usuario lo solicita (si no, volver a empezar, ¡cuidado con la
memoria!).

Ejercicio 13

En un programa, una entidad se define con un nombre, un carácter, un código


genético almacenado en una tabla de 4 enteros (4 secuencias), una posición,
un desplazamiento, un color y posiblemente otras características:

ˇ
Definir el tipo para la entidad.
ˇ
Declarar dos punteros para dos entidades e inicializar con valores
cada una de sus características.
ˇ
Comparar las características de ambas entidades y mostrar el
resultado.
ˇ
Salir si el usuario lo solicita (si no, volver a empezar, ¡cuidado con la
memoria!).

Ejercicio 14

En un programa, retomar el ejemplo del tipo definido para una entidad del
ejercicio 13 y:

ˇ
Declarar dos punteros a entidad.

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 46 -
JAIRO ENRIQUE MARTINEZ BANDA

ˇ
Escribir una función de inicialización e inicializar cada entidad.
ˇ
Escribir una función de visualización y mostrar cada entidad.
ˇ
Escribir una función que visualice el nombre de la entidad con la
característica más fuerte.

Ejercicio 15

En un programa, retomar el ejemplo del tipo definido para una entidad del
ejercicio 13 y:

ˇ
Declarar una tabla de punteros para NUM_MAX entidades.
ˇ
Escribir una función de inicialización e inicializar la tabla.
ˇ
Escribir una función de visualización y visualizar la tabla.
ˇ
Escribir una función que muestre el nombre de la entidad con la
característica más fuerte.

d. Ojo con los errores

Ejercicio 16

¿Qué hace el programa siguiente? ¿Hay errores? En caso afirmativo, ¿cuáles?


¿Cómo se pueden corregir?

int main()
{
char*s1;

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 47 -
JAIRO ENRIQUE MARTINEZ BANDA

char s2[80];
int i;

fgets(s2,100,stdin);
strcpy(s1, s2);

s1=s2;
fgets(s2,100,stdin);
if (strcmp(s1,s2)==0)
printf("ambas frases son idØnticas\n"
);
else
printf("son distintas\n"
);

s1="hola";
printf("s1: %s\n",s1);
strcpy(s2,"hace buen da, cojo mi bastn y mi sombrero con alegra"
printf("s2: %s\n",s2);

while(s2[i]){
s1[i]=s2[i];
i++;
}
printf("s1: %s\n",s1);
printf("s2: %s\n",s2);
return 0;
}

e. Tablas de cadenas

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 48 -
JAIRO ENRIQUE MARTINEZ BANDA

Ejercicio 17

Crear un juego de 52 cartas a partir de una lista de palabras. Al inicio, mostrar


el juego ordenado por colores. A continuación, escribir una función de mezcla
de cartas, otra de distribución entre dos jugadores y mostrar las cartas de
ambos.

Ejercicio 18

Sea una lista de palabras:

char*lista[ ={"pan","fruta","patata","manzana","pera",
"queso","crepes", "miel", "sidra","tortilla","fin"};

Escribir un programa que muestre la lista tal cual. A continuación, debe


ordenar la lista alfabéticamente. La ordenación alfabética se realiza en una
función que recibe la lista por parámetro.

© Éditions ENI - Todos los derechos reservados - Copia personal de JAIRO ENRIQUE MARTINEZ BANDA - 49 -

También podría gustarte