Un puntero es una variable que en un lugar de contener un valor
contiene la dirección de una variable. Se pueden crear punteros
a todos los tipos de variables y la
sintaxis de su declaración es casí como la de las
variables normales, salvo que se añade el símbolo *.
Veamos una variable int normal y un puntero llamado p:

int numero;
int *p;

numero = 31337;
p = 666;

Si representamos gráficamente estas variables tipo int
tendríamos algo así:
Puntero

Es decir, así como la variable número es un entero que
contiene un valor concreto (31337), la variable p lo que contiene es un
dirección
de memoria donde reside un valor entero: 666. ¿Podemos hacer
esto?
p = numero;

¡No! pese a que las dos manejan tipos int, son variables
incompatibles: numero es un número entero y p un puntero. Para
asignar el valor de
numero a p debemos hacer lo siguiente:

p = №

Con el símbolo lo que hacemos es asignar la dirección de
memoria de la variable numero, y eso sí es compatible. Pero...
¿qué es lo que ocurre realmente?
¿Se hace una copia de ese valor? No! Con la asignación p
= № estamos haciendo lo siguiente:

puntero

¿Y qué ocurre ahora? Las dos variables hacer referencia a
la misma posición de la memoria, por tanto si hacemos:

*p = 1457;

Ocurrirá que:
puntero

El valor de la variable numero también cambia!
Las posibles operaciones con punteros se pueden resumir en este
pequeño código:

int
numero; /* numero es un entero */

int cifra = 43; /*
cifra es un entero */


numero = 536;

int *p; /* p
no es más que un apuntador a un entero*/


p =
&cifra; /* ahora p apunta al
entero cifra, por tanto p apunta al valor 43 */

numero = *p; /* ahora
numero vale lo que apunta p (a cifra), por tanto numero es 43 */

*p =
3; /*
ahora cifra vale 3 */
++*p;
/* ahora cifra vale 4 */
numero = *p + 5; /* ahora numero vale lo que apunta p
más 5, 9 */
(*p)++;
/* ahora cifra vale 5 */


¿Todavía no te
cuadra? Edita, compila y ejecuta este código, y lo
comprobarás:

#include <stdio.h>

main ()
{
int numero, x;
int *p;

numero = 31337;
p = &x;
*p = 666;

printf("Paso 1: ");
printf("p apunta a: %d, y su valor es: %d ", p, *p);
printf("En que posicion de memoria esta x: %d ", &x);
printf("Valor de numero: %d , y de p: %d ", numero, p);

/* Le asignamos el valor... */
p = &numero;

printf("Paso 2: ");
printf("p apunta a: %d, y su valor es: %d ", p, *p);
printf("En que posicion de memoria esta numero: %d ", &x);
printf("Valor de numero: %d , y de p: %d ", numero, *p);

/* Le cambiamos el valor a p ... */
*p = 1457;

printf("Paso 3: ");
printf("p apunta a: %d, y su valor es: %d ", p, *p);
printf("Valor de numero: %d , y de p: %d ", numero, *p);

}


Al ejecutar ocurre lo siguiente:

C:>puntero
Paso 1:
p apunta a: 2293616, y su valor es: 666
En que posicion de memoria esta x: 2293616
Valor de numero: 31337 , y de p: 2293616

Paso 2:
p apunta a: 2293620, y su valor es: 31337
En que posicion de memoria esta numero: 2293616
Valor de numero: 31337 , y de p: 31337
Paso 3:
p apunta a: 2293620, y su valor es: 1457
Valor de numero: 1457 , y de p: 1457
C:>


Este mecanismo para acceder a un valor o a otra variable tan
característico del lenguaje C nos será de utilidad en
muchas ocasiones aunque
también arrastran mala fama de complicados. No hay que
complicarse la vida, los punteros son para lo que son, para referenciar
a otras variables.
Un error frecuente puede ser este:

int *p = 564;

No podemos asignar un valor a p directamente, porque al principio p no
será nada. Pero entonces, ¿para que queremos punteros
teniedo variables normales?
Esta claro que una de sus utilidades está en el paso de
parámetros a funciones. El ejemplo clásico es el de la
función swap (=intercmbio), una función que intercambia
los valores de dos variables entre si. Su implementación
sería así:

/**
* swap.c
* Implementa dos versiónes de la función swap, una para intercambiar enteros
* y la otra para intercambiar arrays
* Compilacion: gcc -o swap swap.c
* curso de C - instituto cuatrovientos
*/

#include <stdio.h>


void swap(int *px, int *py);


main ()
{
int valor1 = 666;
int valor2 = -34;

printf("Antes: valor1:%d y valor2: %d ", valor1, valor2);
/* Atención, los parametros deben ser las direcciones */
swap(&valor1, &valor2);
printf("Despues: valor1:%d y valor2: %d ", valor1, valor2);

}

/* Implementación de SWAP */
void swap (int *px, int *py)
{
int tmp;

tmp = *px;
*px = *py;
*py = tmp;
}


Hay que fijarse en varios detalles:

  • Esta función tiene que pasar los parámetros por
    referencia: en lugar de pasar valores debe pasar las direcciones de
    memoria. Por tanto se usan punteros.
  • Los parámetros que hemos usado son valor1 y valor2, son
    enteros. Como hay que pasarlos por referencia usamos el símbolo
    &, con lo que la llamada queda así: swap(&valor1,
    &valor2)
  • ¿Qué ocurriría si implementáramos la
    función swap sin punteros? Al terminar la función, los
    valores x,y no se habrían intercambiado

Compila el programa y prueba la función swap.

Arrays

Los arrays o arreglos estan muy vinculados a los punteros. Un array es
una colección de variables de un mismo tipo.
En lenguaje C podemos declarar un array de forma muy fácil, por
ejemplo, un array de 10 número enteros:

int numeros[10];

También podemos inicializar el array al declararlo:

int numeros[10] =
{4,3,6,0,-4,6,3,11,1,4};


Gráficamente el array sería algo así, un cojunto
de bloques consecutivos de variables del mismo tipo:
array

Para acceder a cada uno de los elementos usamos la variable del array
con un indice concreto, pero cuidado! el indice de los arrays siempre
empieza en 0!!! por tanto el array numeros tendrá indices del 0
al 9, y por ejemplo numeros[2] apunta al valor 6.

Podemos definir un array de cualquier cosa, pero siempre hay que
establecerle un tamaño concreto, ya sea de forma explicita o con
una inicilización.

float valores[3] = {0.0, 6.0,
4.5};

char vocales[5] = {'a', 'e',
'i', 'o', 'u'};

int cifras[] = {3,4,5}; /*
Aunque no se especifique tamaño, el compilador lo deduce */

int v[2]; /* Lo
inicializamos más tarde */


v[0] = 34;
v[1] = 666;


Podemos acceder a cualquier elemento del array usando el índice
correspondiente, por ejemplo:

int valor;
valor = numeros[0];
printf("Este es el tercer
elemento del array de numeros: %d ", numeros[2]);


Podemos recorrer el array utilizando la estructura for:

#include <stdio.h>

main ()
{
int numeros[10] = {4,3,6,0,-4,6,3,11,1,4};
int i;

for (i = 0; i < 10; i++)
{
printf("Valor del elemento %d: %d ", i, numeros[i]);
}
}


Si definimos un puntero tipo int, podremos acceder a un array de tipo
int. sin ningún problema. Supongamos este escenario:

int numeros[10] =
{4,3,6,0,-4,6,3,11,1,4};

int *p;
p = &numeros[0];

Se traduciría en lo siguiente:
array
Por tanto el valor de pint sería 4. Pero mucha atención
ahora. ¿Qué ocurre si incrementamos el valor de p?

p = p + 1;
o su equivalente:
*(p++);

En ese caso lo que estamos incrementando en la posición de
memoria a la que apunta pint, no el valor al que apunta.
Por tanto lo que pasaría es esto:
array

¿Curioso no? esa forma de acceder a arrays se usa muy a menudo
al tratar con arrays de caracteres, o dicho de otra forma
al trabajar con cadenas, o dicho de otra manera al trabajar con
strings, o dicho de otra manera al trabajar con palabras.


Cadenas


Una cadena o string, no es sino un array de caracteres, con la
particularidad de que se puede declarar así:
char saludo[4] = "Hola";
char despedida[] = "Adios,
hasta más ver";

char adios[] = "No nos veremos
mas ";


Pero hay que tener en cuenta una cosa muy importante. Al inicializar
una cadena, al final de la misma se añade un caracter especial
que
marca el final del la cadena: ''
Gracias a ese caracter se facilita el trabajo con cadenas, empezando
por el cálculo de su longitud.

Atención, hay que tener mucho cuidado con esto:
char frase[] = "Vamonos ya";
char *otra = "Vamos que nos vamos";

Por que NO son lo mismo, frase[] es un array y otra no es más
que un puntero. Mientras que el contenido de frase se puede modificar, los cambios en la cadena a la que apunta otra pueden
provocar resultados inesperados.


De todas formas, frase[] y *otra son compatibles a la hora de pasarse
como parámetros de funciones; y además, los arrays de
caracteres o cadenas al pasarse como parámetro se pasan por referencia. Por
tanto si se modifica una cadena dentro de una función los cambios permanecerán al volver.

Esta sería una posible implementación de la
función strlen, disponible ya en la librería standar.
Dada una cadena, devuelve su longitud:


#include <stdio.h>

main ()
{
char ejemplo1[] = "Vamos a ver";
char *ejemplo2 = "Vamos a ver";

printf("Longitudes: %d y %d ", strlen1(ejemplo1), strlen1(ejemplo2));
printf("Longitudes: %d y %d ", strlen2(ejemplo1), strlen2(ejemplo2));
printf("Longitudes: %d y %d ", strlen2(""), strlen2("sdd"));
}

int strlen(char *cadena)
{
int i = 0;

/* Mientras no sea el final */
while ( cadena [i] != '')
{
i++;
}

return i;
}


Podriamos hacer un bucle más elegante, sin ninguna
operación dentro. Cuidado! no hay que olvidar el ";", y es muy
importante incrementar
usando i++, es decir, primero tomamos leemos el valor, y luego se
incrementa.

int strlen2(char *cadena)
{
int i = 0;

/* Mientras no sea el final */
while ( cadena [i++] != '')
;

return (i-1);
}


Otra forma interesante de conocer las distintas formas de recorrer una
cadena son estas implementaciones de la controvertida función
strcpy. Esta función
copia una cadena en otra caracter a caracter y esta podría ser
una de sus implementaciones:

/* Copia el contenido de b en a */
int strcpy (char *a, char *b)
{
int i = 0;

/* Hacemos la asignación y a la vez comprobamos si ha llegado al final */
while ( (a[i] = b[i]) != '')
i++;
}


Otra implementación de strcpy podría ser accediendo a los
arrays como si fueran punteros, así los podemos incrementar de
forma directa!!!

/* Copia el contenido de b en a */
int strcpy (char *a, char *b)
{

/* Hacemos la asignación y a la vez comprobamos si ha llegado al final */
while ( (*a = *b) != '')
{
a++;
b++;
}
}


Y otra manera más abreviada aún...


/* Copia el contenido de b en a */
int strcpy (char *a, char *b)
{

/* Hacemos la asignación e incrementamos */
while ( (*a++ = *b++) != '' )
;
}


Incluso podríamos quitar la operación != ''. Prueba y
verás. De todas formas JAMAS utilices esta función en un
programa, si te das cuenta
estas funciones no se toman la molestia de comprobar las longitudes de
las cadenas que copias, y esto puede dar lugar a que revienten tu
código
y puedan ejecutar comandos arbitrarios allá donde funcione el
programa.

Arrays multidimensionales

tipo nombre[filas][columnas];

Los arrays se pueden complicar tanto como haga falta:
int matriz[3][5] = { {3,5,2,4,2} , {0,3,6,2,3} , {2,4,6,3,2} };

Pero ten cuidado, si vas a pasar un array de dos dimensiones como
parámetro de una función, al declarar la función
es obligatorio especificar el tamáno concreto de columnas.


Argumentos de linea de comandos

Cuando ejecutas un programa puedes pasarle argumento o
parámetros desde la linea de comandos, o desde msdos, o desde el
bash de linux.
Para recoger esos valores puedes usar unos parámetros que se
indican en la función principal, la función main:

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

  • argc = es un variable que contiene el número de
    parámetros que se han pasado
  • argv[] = es un array de cadenas que contiene los argumentos en
    si, donde argv[0] es el nombre del programa


Con esta función puedes comprobar el funcionamiento de este
mecanismo:

#include <stdio.h>

main (int argc, char *argv[])
{
int i;

printf("Me has pasado %d argumentos ", argc);
printf("Y el primero es %s ", argv[0]);

/* Vamos a exigir que se pasen al menos 3 argumentos */
if (argc < 4)
{
printf("Error!, debes pasarme 3 argumentos!! ");
return;
}

for (i = 0; i < argc; i++)
printf("Argumento: %d = %s ", i, argv[i]);

}


Un ejemplo de ejecución podría ser:

C:>args primero 666 tercero
Me has pasado 4 argumentos
El nombre del programa es args
Argumento: 0 = args
Argumento: 1 = primero
Argumento: 2 = 666
Argumento: 3 = tercero

C:>



Opcionalmente puedes meter otro argumento, *envp[], para variables de
entorno.