INTRODUCCION
El lenguaje C es un lenguaje de programación de propósito general, es uno de los más rápidos y potentes que existen. El lenguaje C ha demostrado ser un lenguaje extremadamente eficaz, hasta como para crear sistemas operativos, como Linux que fue creado en este lenguaje.
Este lenguaje tiene ciertas caracteristicas, unas de ellas son:
Es un lenguaje Compilado
A diferencia de otros lenguajes, que son lenguajes interpretados, los cuales necesitan del código fuente para funcionar (por ejemplo Basic), C es un lenguaje compilado esto quiere decir que convierte el código fuente en un fichero objeto, éste es enlazado con las librerías necesarias dando lugar a un fichero ejecutable.
Es un lenguaje de Nivel medio
Esto quiere decir que combina elementos de lenguaje de alto nivel con la funcionalidad del lenguaje ensamblador, o sea que trabaja a un nivel cercano al computador, sin embargo, nos ofrece posibilidades de construir estructuras de datos equivalentes a los que manejan los lenguajes de alto nivel.
Es un lenguaje Estructurado
Esto quiere decir que permite crear procedimientos en bloques dentro de otros procedimientos.
Es un lenguaje Portable
Este lenguaje permite utilizar el mismo código en diferentes equipos y sistemas informáticos, o sea que es independiente de la arquitectura de cualquier máquina.
Es un lenguaje relativamente pequeño
Este es un lenguaje económico en cuanto a expresiones se refiere, se puede describir en poco espacio y es fácil y rápido de aprender.
Tiene abundancia de Operadores y Tipos de Datos
Este lenguaje practicamente posee un operador para cada una de las posibles operaciones en código de máquina.
Pero al igual que otros lenguajes, el C tiene sus desventajas:
-Carece de instrucciones entrada/salida.
-Carece de instrucciones de manejo de cadena de caracteres.
-La precedencia de los operadores dificultan la comprensión de algunas expresiones.
Todo programa en C, se compone de una o más funciones. La función que se ejecuta de primero es una función llamada main(), esta función es la más importante de todo programa y es la que nunca debe faltar. A esta función no se le puede cambiar el nombre.
A su vez toda función del programa debe devolver un valor al programa, este valor se indica con la palabra reservada "return". Ciertas veces no queremos que la función devuelva ningún valor para estos casos simplemente le indicamos "return 0" o escribimos la palabra reservada "void" antes del nombre de la función.
Comentarios
Un buen programa debe contener buenos comentarios. Asi si lo retomamos en el futuro no tendremos que descifrar lo que hicimos en el pasado. Es una buena costumbre del programador comentarear sus programas.
Un buen comentario es aquel que ni ofende el conocimiento del programador, ni tampoco deja implicito lo que se esta haciendo.
Para poner un comentario basta con encerrar el texto entre los signos /* [comentario] */.
Ejemplo.
main()
{
/*este es un comentario*/
}
TIPOS DE DATOS
Cuando usamos un programa es muy importante manejar datos. En C podemos almacenar los datos en variables. El contenido de las variables se puede ver o cambiar en cualquier momento. Estas variables pueden ser de distintos tipos dependiendo del tipo de dato que queramos guardar en ellas. No es lo mismo guardar un nombre que un número. Hay que recordar también que la memoria del ordenador es limitada, así que cuando guardamos un dato, debemos usar sólo la memoria necesaria
Identificadores
Son nombres dados a constantes, variables y funciones. El identificador esta formado por una secuencia de una o más letras, dígitos y el caracter de subrayado. El primer caracter de un identificador siempre debe ser una letra o el caracter de subrayado, en estos identificadores (nombres de variables) se debe tener gran cuidado, ya que el C hace diferencia entre mayúsculas y minúsculas.
Por ejemplo, Max, max y MAX son tres identificadores diferentes.
Constantes
Las constantes son un valor que una vez fijado por el compilador, no cambia durante la ejecución del programa. El C indica al programa que se trata de una constante usando la directiva #define.
Por ejemplo.
#define pi 3.1416
Esta linea define a la variable pi como una constante con el valor 3.1416, este valor de las constantes puede ser un numero o bien una cadena de caracteres.
Tipos de Datos
Tipo Int
En una variable de este tipo se almacenan números enteros (sin decimales). El rango de valores que admite es -32767 a 32767. Cuando definimos una variable lo que estamos haciendo es decirle al compilador que nos reserve una zona de la memoria para almacenar datos de tipo int. Hay que decirle al compilador que queremos crear una variable y hay que indicarle de qué tipo. Si esta declaración se hace antes del main(), esto indica que la variable será una variable global, o sea que se usará en todo el main() y en el resto de funciones. Mientras que si se declara dentro del main(), esto indica que la variable será una variable local, o sea, sólo para la función main().
Por ejemplo.
int valor;
main()...
Esta expresión declara una variable global de nombre "valor" y de tipo entero.
main()
{
float valor;
}
Esta expresión declara una variable local de nombre "valor" y de tipo float (coma flotante)
Tipo Char
Las variables de tipo char sirven para almacenar caracteres. Los caracteres se almacenan en realidad como números del 0 al 255. Los 128 primeros (0 a 127) son el ASCII estándar. El resto es el ASCII extendido y depende del idioma y del ordenador.
Para declarar una variable char solo escribimos: "char nombre;" y listo, ya existe una variable "nombre" que almacenará datos de tipo caracter.
Tipo Float
En este tipo de variable podemos almacenar números decimales, no sólo enteros como en los anteriores. El rango de posibles valores es del 3.4 E-38 al 3.4 E+38.
Para declarar un float, de forma similar, escribimos: "float prom;" y con esto hemos declarado una variable de nombre "prom" que tendrá un dato de tipo float o coma flotante.
Tipo double
En las variables tipo double se almacenan números reales, estos tiene el rango de 1.79769 E-308 a 1.79769 E+308.
Al igual que las anteriores para declararla escribimos: "double num;" y tenemos una variable de nombre "num" que aceptará datos tipo double.
Modificador Unsigned
Este modificador (que significa sin signo) modifica el rango de valores que puede contener una variable. Sólo admite valores positivos.
Este modificador se usa así: "unsigned int total;" esto declara un variable de nombre "total", de tipo entero positivo.
INTRODUCIR E IMPRIMIR DATOS
Es usual que en un programa el usuario introduzca datos por el teclado. Para ello contamos con varias posibilidades: Usar las funciones de la biblioteca estándar es la más usual de éstas, aunque no es la mejor es la más fácil de aprender y es la que nosotros utilizaremos.
scanf
Al utilizar la palabra scanf, nosotros podemos hacer que el usuario introduzca datos por el teclado.
El uso de scanf es de la siguiente manera: "scanf("%i", &num);" esta última expresión indica al programa que se debe solicitar un número entero (%i, es una especificación de formato y esto lo puedes ver en la sección de operadores y manipuladores), luego se indica el nombre de la variable en la que se va a guardar el dato, éste (el nombre) precedido por el signo &, que indica al programa la dirección de ésta variable.
printf
El uso de printf, es similar al de scanf, lo que scanf hace para leer una variable, printf lo hace para imprimirla. De esta manera podemos imprimir en pantalla, ya sea un mensaje o el valor contenido en una variable. La sintaxis de printf es así: "printf("%d", num);" esta sentencia lo que hace es imprimir el valor de la variable num en el lugar donde aparece el "%d".
El printf nos sirve también para mandar a imprimir en pantalla un mensaje así: "printf("Bienvenidos al mundo de C")" esto impirmirá en pantalla:
Bienvenidos al mundo de C
Nota: Para hacer uso de estas funciones, es necesario incluir la directiva o archivo de cabecera stdio.h, esto se hace de la siguiente manera: "#include ", así queda incluida éste archivo y se podrá proceder con el programa.
Ahora haremos uso de lo que hemos aprendido para hacer un programa.
# include stdio.h
main()
{
int x;
printf("Bienvenidos al mundo de C\n");
printf("Introduzca un valor entero ");
scanf("%i", &x);
printf("\nEl valor que introdujo fue %i",x);
return 0;
}
Ahora explicaremos lo que hace:
Primero se declara una variable de tipo entero y de nombre x. Después se manda a imprimir en pantalla el mensaje "Bienvenidos al mundo de C", el "\n" que sigue es para dar un salto de línea en el programa, si no lo pusieramos el siguiente mensaje aparecería pegado al anterior.
Luego se imprime "Introduzca un valor entero", para que el usuario pueda introducir un valor por medio del teclado y se manda a guardar este valor a la variable antes declarada.
Despues se imprime un mensaje y el valor de la variable.
Como pueden ver, no es tan difícil.
La expresión "return 0" es una instrucción. Como verán, toda función debe retornar un valor, como no queremos que nuestra función main devuelva nada, entonces le decimos que retorne 0.
La salida del programa anterior sería:
Bienvenidos al mundo de C
Introduzca un valor entero 25 (por ejemplo 25)
El valor que introdujo fue 25
getch y getche
Si lo que queremos es que el usuario introduzca un caracter por el teclado usamos las funciones getch y getche. Estas esperan a que el usuario introduzca un carácter por el teclado. La diferencia entre getche y getch es que la primera saca por pantalla la tecla que hemos pulsado y la segunda no.
Por ejemplo.
# include stdio.h
int main()
{
char letra;
printf( "Introduzca una letra: " );
letra = getche();
printf( "\nLa letra que introdujo es: %c", letra );
return 0;
}
La salida del programa anterior sería:
Introduzca una letra:N
La letra que introdujo es: N
Ahora hagamos el mismo programa con getch():
# include stdio.h
int main()
{
char letra;
printf( "Introduzca una letra: " );
letra = getch();
printf( "\nLa letra que introdujo es: %c", letra );
return 0;
}
La salida del programa anterior sería:
Introduzca una letra:
La letra que introdujo es: N
Se debe tener en cuenta, aunque no se haga en estos ejemplos, que el archivo de cabecera stdio.h debe estar entre los signos <>, de lo contrario se produciría un error en el programa.
OPERADORES (MANIPULADORES)
Los operadores son símbolos que indican como son manipulados los datos. Estos operadores se pueden clasificar en: aritméticos, lógicos, relacionales, de asignación, de manejo de bits y otros.
Operadores de Asignación
Estos operadores son los que nos sirven para darle (asignarle) un valor a una variable. Este valor lo podemos teclear manualmente o bien puede ser el valor de otra variable.
Por ejemplo.
x=5; /*Esto mete un valor directamente*/
x=a; /*Esto asigna el valor de otra variable*/
Operadores Aritméticos
Los operadores aritméticos son aquellos que sirven para realizar operaciones tales como suma, resta, división y multiplicación. Con ellos podemos calcular el resto o módulo de una división entera, o bien podemos incrementar o disminuir el valor de las variables.
Por ejemplo.
# include stdio.h
main()
{
int x=7; int y; int z=2; /*Como verán es posible declarar e iniciar la variable al mismo tiempo*/
printf("El valor de x es: %i\n", x);
y=x+z;
/*Asigna el valor de una suma*/
x++; /*Incrementa el valor de x en uno*/
printf("Los valores de x, y, z son %i, %i, %i", x,y,z);
return 0;
}
La salida a este programa sería:
El valor de x es: 7
Los valores de x, y, z son 8, 9, 2
Operadores Lógicos
Estos operadores son los que nos permiten hacer comparaciones entre dos o más variables. Estos operadores son los que nos permitirán decidir en una sentencia condicional que veremos más adelante.
Por ejemplo.
x<7&&t==4; /*Esta línea devuelve 1, si x es menor que 7 "y" t es igual a 4*/
Operadores Binarios
Estos permiten ver el contenido de las variables como valores binarios. Utiles en el acceso a bits individuales en memoria, tales como la memoria de pantalla para mostrar gráficos.
Las funciones de estos operadores se describen en la tabla, y se ocupan similarmente en los programas.
Operador Sizeof Existe un operador muy utilizado que es el sizeof. Este operador nos devuelve el tamaño en bytes de una variable. De esta manera no tenemos que preocuparnos en recordar o calcular cuanto ocupa. Además el tamaño de una variable cambia de un compilador a otro, es la mejor forma de asegurarse. Se usa poniendo el nombre de la variable después de sizeof y separado de un espacio.
Por ejemplo.
int main()
{
float variable;
printf( "Tamaño de la variable: %f\n", sizeof variable );
}
Se debe tener gran cuidado al agrupar estos operadores, pues unas acciones se llevan a cabo antes que otras, esto depende del nivel de precedencia que tenga el operador. Para esto se presenta una tabla, en la que se muestra en orden descendente la prioridad de los mismos.
Operador Operación
+ Suma dos numeros, pueden ser enteros o reales
- Resta dos numeros, pueden ser enteros o reales
* Multiplica dos numeros, pueden ser enteros o reales
/ División, el resultado depende de los tipos de datos
% Módulo o resto de una división entera, los operandos tienen que ser enteros
++ Incrementa en uno la variable
-- Decrementa en uno la variable
OPERADORES LOGICOS
Operador Operación
&& AND, en español Y. Da como resultado el valor lógico 1, si ambos operadores son distintos de cero
|| OR, en español O. El resultado es cero, si ambos operandos son cero
! NOT, en español NO. El resultado es cero si el operando tiene un valor distinto de zero
OPERADORES DE RELACION
Operador Operación
< Primer operando menor que el segundo
> Primer operando mayour que el segundo
<= Primer operando menor o igual que el segundo
>= Primer operando mayor o igual que el segundo
== Primer operando igual que el segundo
!= Primer operando distinto del segundo
OPERADORES BINARIOS
Operador Operación
& AND, compara los bits dos a dos, si ambos son 1 el resultado es 1
! OR, compara los bits dos a dos, si alguno de ellos es 1, se obtien 1
^ XOR, compara los bits dos a dos, si son distintos el resultado es 1
~ Complemento, convierte los ceros a uno y los unos a cero
>> Desplazamiento a la derecha, esto equivale a dividir por 2
<< Desplazamiento a la izquierda, esto equivale a multiplicar por 2
OPERADORES DE ASIGNACION
Operador Operación
= Asignación simple
*= Multiplicación más asignación
/= División más asignación
%= Módulo más asignación
+= Suma más asignación
-= Resta más asignación
<<= Desplazamiento a la izquierda más asignación
>>= Desplazamiento a la derecha más asignación
&= Operación AND sobre bits más asignación
!= Operación OR sobre bits más asignación
^= Operación XOR sobre bits más asignación
NIVELES DE PRECEDENCIA
Operador
( ) [ ]
~ ! & ++ --
* / %
+ -
<< >>
< <= > >=
== !=
^
!
&&
||
= *= /= %= += -= &= ^= != <<= >>=
SENTENCIAS Y BUCLES
Hasta ahora los programas que hemos visto eran lineales. O sea que las instrucciones se ejecutaban en orden y una sola vez. Pero resulta que muchas veces no es esto lo que queremos que ocurra. Lo que nos suele interesar es que dependiendo de los valores de los datos se ejecuten unas instrucciones y no otras. O también puede que queramos repetir unas instrucciones un número determinado de veces. Para esto están las sentencia de control de flujo.
Bucles
Los bucles nos ofrecen la solución cuando queremos repetir una tarea un número determinado de veces. Supongamos que queremos escribir 100 veces la palabra Bienvenido.
Con lo que sabemos, tendríamos que escribir 100 veces la expresión "printf("Bienvenido\n");", sin embargo un ciclo for nos facilitaría en grande esta tarea.
Estos bucles pueden ser:
For (Para), While (Mientras), Do while(Hacer mientras).
El Bucle For:
Este bucle nos permite hacer un número finito de tareas, cuantas veces necesitemos. La sintaxis del for es la siguiente:
for([valor inical];[condición];[incremento])
{
...sentencias
...sentencias
}
La parte del valor inicial, es el valor con el que empieza la variable contadora, o la variable que nos va a decir cuando salir.
La condición, es la que nos determinará el número de veces que se llevarán a cabo las instrucciones que siguen. Estas sentencias se realizarán mientras la condición estipulada se cumpla.
El incremento, es el numero en que vamos a incrementar la variable contadora.
El Bucle While:
La sintaxis del bucle while es la siguiente:
while ( condición )
{
bloque de instrucciones a ejecutar
}
La condición de este bucle, al igual que el for, es la que indica cuantas veces se ejecutarán las instrucciones. Estas (las instrucciones) se ejecutarán mientras la condición estipulada se cumpla.
El Bucle Do While
La sintaxis de este bucle es:
do
{
instrucciones a ejecutar
} while ( condición );
En este bucle,las instrucciones se llevan a cabo al menos una vez, puesto que la condición se evalúa al final del mismo; a diferencia del while o el for, que si la condición inicial no se cumple, entonces las instrucciones no se cumplen ni una sola vez.
La condición es similar a las antes descritas.
Sentencias Condicionales
Hasta aquí hemos visto cómo podemos repetir un conjunto de instrucciones las veces que deseemos. Pero ahora vamos a ver cómo podemos controlar totalmente el flujo de un programa. Dependiendo de los valores de alguna variable se tomarán unas acciones u otras.
Sentencia if
La palabra if significa si (condicional), su sintaxis es como sigue:
if ( condición )
{
instrucciones a ejecutar
}
Cuando se cumple la condición entre paréntesis se ejecuta el bloque inmediatamente siguiente al if (instrucciones a ejecutar).
Por ejemplo.
# include stdio.h
main()
{
int x=2;
if(x==2)
printf("X es 2");
return 0;
}
El programa anterior analiza la condición de que si x es igual a dos, entonces se imprime el mensaje "X es 2".
Sentencia if-else
En el if, si la condición no se cumplía, entonces las instrucciones no se ejecutaban y el programa finalizaba o seguía con las siguientes instrucciones. En el if-else, (si-sino) si no se cumple la condición entonces se realizarán otras instrucciones.
Su sintaxis es la siguiente:
if ( condición )
{
bloque que se ejecuta si se cumple la condición
}
else
{
bloque que se ejecuta si no se cumple la condición
}
Por ejemplo.
int main()
{
int a;
printf( "Introduce un número " );
scanf( "%i", &a );
if ( a==8 )
{
printf ( "El número que introdujo es ocho.\n" );
}
else
{
printf ( "El número que introdujo no es ocho"\n" );
}
}
En este ejemplo se manda a solicitar un número, si el número introducido es ocho, entonces manda un mensaje positiv, mientras que si el numero no es ocho manda un mensaje negativo.
Se pueden agrupar varios if, o sea que si se cumple uno, luego se pregunta otra condición y así sucesivamente. A esto se le llama if anidados. A la vez es posible analizar más de una condición siempre y cuando estas vayan ligadas con su respectivo operador.
Por ejemplo.
int main()
{
int a;
printf( "Introduce un número " );
scanf( "%i", &a );
if ( a<10 )
{
printf ( "El número introducido era menor de 10.\n" );
}
else if ( a>10 && a<100 )
{
printf ( "El número está entre 10 y 100"\n" );
}
else if ( a>100 )
{
printf( "El número es mayor que 100\n" );
}
En este ejemplo lo que se hace es pedir un numero, se analiza una condición y si se cumple se mand un mensaje, sino, se analiza otra condición y se manda a imprimir un mensaje para cada caso.
Sentencia switch
La sentencia switch, analiza una condición y dependiendo del valor escoge entre varias opciones para realizar distintas instrucciones y si la opción no está establecida, simplemente ejecuta una opción por defecto.
La sintaxis para el switch es:
switch ( variable )
{
case opción 1:
código a ejecutar si la variable tiene el
valor de la opción 1
break;
case opción 2:
código a ejecutar si la variable tiene el
valor de la opción 2
break;
default:
código que se ejecuta si la variable tiene
un valor distinto a los anteriores
break;
}
Ahora miremos, el switch analiza el valor de "variable" luego si variable tiene el valor "opcion 1" entonces se ejecutan las instrucciones que siguen, y así sucesivamente. Es importante poner al final de cada grupo de instrucciones la palabra "break", la que indica al programa que ahí terminan las sentencias. Se pueden poner cuantas opciones se desee y al final es recomendable (aunque no exigido) que se ponga un grupo de instrucciones que se ejecuten si la variable toma un valor que no se ha definido. Esta última se pone despues de la palabra "default".
Sentencia goto
La sentencia goto (ir a) nos permite hacer un salto a la parte del programa que deseemos. En el programa podemos poner etiquetas, estas etiquetas no se ejecutan. Es como poner un nombre a una parte del programa. Estas etiquetas son las que nos sirven para indicar a la sentencia goto dónde tiene que saltar.
Por ejemplo.
int main()
{
printf( "Línea 1\n" );
goto linea3; /* Le decimos al goto que busque la etiqueta linea3 */
printf( "Línea 2\n" );
linea3: /* Esta es la etiqueta */
printf( "Línea 3\n" );
}
Como resultado tenemos:
Línea 1
Línea 3
O sea que el printf de la linea 2 no se ejecuta, puesto que lo saltamos con el goto.
ARREGLOS O MATRICES
Un array (arreglo) es un conjunto de variables del mismo tipo, las cuales se diferencian entre sí por su posición (ubicación). Para ilustrarlo, supongamos una tabla dividida en celdas y cada celda es una variable en particular, por supuesto que todas las celdas son del mismo tipo y pertenecen a la misma tabla.
Los arreglos se declaran así:
tipo_dato nombre_arreglo [tamaño];
El tipo de dato, puede ser uno de los definidos anteriormente.
El nombre del arreglo es cualquier identificador válido y el tamaño es el tamaño que deseamos que tenga el arreglo, este siempre debe ir entre corchetes. Los datos del arreglo empiezan a introducirse por el dato
- .
int temp[24]; /* Con esto tenemos declaradas 24 variables de tipo entero*/
Para poder introducir los datos en un arreglo debemos recorrerlo, o sea ir a cada celda del arreglo y ahí escribir el dato. Para esto se usa frecuentement un ciclo for de la siguiente manera:
for( hora=0; hora<24; hora++ )
{
printf( "Temperatura de las %i: ", hora );
scanf( "%i", &temp[hora] );
media += temp[hora];
}
Los arreglos al igual que las otras variables se pueden inicializar, para hacerlo se prosigue de la siguiente manera:
int temperaturas[24] = { 15, 18, 20, 23, 22, 24, 22, 25, 26, 25, 24, 22, 21, 20, 18, 17, 16, 17, 15, 14, 14, 14, 13, 12 };
De esta manera hemos inicializado el arreglo con los valores correspondientes. Asi temperatura[0] tiene el valor 15, temperatura[1] tiene el valor 18 y así sucesivamente.
Punteros a Arreglos
Aquí tenemos otro de los importantes usos de los punteros, los punteros a arrays. Estos están íntimamente relacionados.
Para que un puntero apunte a un array se puede hacer de dos formas, una es apuntando al primer elemento del array:
Por ejemplo.
int *puntero;
int temperaturas[24];
puntero = &temperaturas[0];
Otra forma equivalente, pero mucho más usada es:
puntero = temperaturas;
Ahora vamos a ver cómo acceder al resto de los elementos. Para ello empezamos por cómo funciona un array: Un array se guarda en posiciones seguidas en memoria, de tal forma que el segundo elemento va inmediatamente después del primero en la memoria.
Ejemplo.
#include
int main()
{
int i;
int temp[24];
for( i=0; i<24; i++ )
{
printf( "La dirección del elemento %i es %p.\n", i, &temp );
}
}
Nota: Recuerda que %p sirve para imprimir en pantalla la dirección de una variable en hexadecimal.
El resultado es:
La dirección del elemento 0 es 4c430.
La dirección del elemento 1 es 4c434.
La dirección del elemento 2 es 4c438.
La dirección del elemento 3 es 4c43c.
...
La dirección del elemento 21 es 4c484.
La dirección del elemento 22 es 4c488.
La dirección del elemento 23 es 4c48c
Paso de un Arreglo a una función
En C no podemos pasar un array entero a una función. Lo que tenemos que hacer es pasar un puntero al array. Con este puntero podemos recorrer el array, así:
int sumar( int *m )
{
int suma, i;
suma = 0;
for( i=0; i<10; i++ )
{
suma += m;
}
return suma;
}
int main()
{
int contador;
int matriz[10] = { 10, 11, 13, 10, 14, 9, 10, 18, 10, 10 };
for( contador=0; contador<10; contador++ )
printf( " %3i\n", matriz[contador] );
printf( "+ -----\n" );
printf( " %3i", sumar( matriz ) );
}
Este programa tiene alguna cosilla adicional como que muestra toda la matriz en una columna. Además se usa para imprimir los números %3i. El 3 indica que se tienen que alinear los números a la derecha, así queda más elegante. Como he indicado no se pasa el array, sino un puntero a ese array.
STRINGS
(CADENAS DE CARACTERES)
Vamos a ver por fin cómo manejar texto con C, hasta ahora sólo sabíamos cómo mostrarlo por pantalla.
Para empezar diré que en C no existe un tipo string como en otros lenguajes. No existe un tipo de datos para almacenar texto, se utilizan arrays de chars. Funcionan igual que los demás arrays con la diferencia que ahora jugamos con letras en vez de con números. Se les llama cadenas, strings o tiras de caracteres. A partir de ahora les llamaremos cadenas. Para declarar una cadena se hace como un array:
char texto[20];
Al igual que en los arrays no podemos meter más de 20 elementos en la cadena.
Por ejemplo.
int main()
{
char nombre[20];
printf( "Introduzca su nombre (20 letras máximo): " );
scanf( "%s", nombre );
printf( "\nEl nombre que ha escrito es: %s\n", nombre );
}
Vemos cosas curiosas como por ejemplo que en el scanf no se usa el símbolo &. No hace falta porque es un array, y ya sabemos que escribir el nombre del array es equivalente a poner &nombre[0].
También puede llamar la atención la forma de imprimir el array. Con sólo usar %s ya se imprime todo el array. Ya veremos esto más adelante.
Inicialización de una Cadena
int main()
{
char nombre[] = "Gorka";
printf( "Texto: %s\n", nombre );
printf( "Tamaño de la cadena: %i bytes\n", sizeof nombre );
}
Como vemos la inicialización es parecida a la de un arreglo, solo que aqui se escribe toda la frase para inicializarla. Cuando inicializamos una cadena el C automáticamente introduce como ultimo elemento el "\0" que indica fin de la cadena. Esto sirve para que a la hora de imprimir el C, sepa cuando debe parar de imprimir.
Es importante no olvidar que la longitud de una cadena es la longitud del texto más el símbolo de fin de cadena. Por eso cuando definamos una cadena tenemos que reservarle un espacio adicional.
Funciones de Manejo de Cadenas
Existen varias funciones para el manejo de cadenas de caracteres o strings. Para poder utilizar estas funciones es necesario incluir la función de biblioteca string.h.
Estas funciones son: strlen, strcpy, strcat, sprintf, strcmp.
strlen
Su sintaxis es:
strlen(char *s)
Esta función nos devuelve el número de caracteres que tiene la cadena (sin contar el '\0').
Si no utilizaramos esta función, para poder saber cuantos elementos tiene una cadena tendríamos que recorrerla con un puntero.
strcpy
Su sintaxis es:
char *strcpy(char *cadena1, const char *cadena2);
Esta función copia el contenido de cadena2 en cadena1. cadena2 puede ser una variable o una cadena directa (por ejemplo "hola"). Debemos tener cuidado de que la cadena destino (cadena1) tenga espacio suficiente para albergar a la cadena origen (cadena2).
strcat
Su sintaxis es.
char *strcat(char *cadena1, const char *cadena2);
Esta función copia la cadena2 al final de la cadena1.
Como siempre tenemos que asegurarnos que la variable en la que metemos las demás cadenas tenga el tamaño suficiente.
sprintf
Su sintaxis es:
int sprintf(char *destino, const char *format, ...);
Funciona de manera similar a printf, pero en vez de mostrar el texto en la pantalla lo guarda en una variable (destino). El valor que devuelve (int) es el número de caracteres guardados en la variable destino.
strcmp
Su sintaxis es:
int strcmp(const char *cadena1, const char *cadena2);
Compara cadena1 y cadena2. Si son iguales devuelve 0. Un número negativo si cadena1 va antes que cadena2 y un número positivo si es al revés.
Ejemplo.
#include stdio.h
#include string.h
int main()
{
char nombre1[]="Gorka";
char nombre2[]="Pedro";
char nombre_completo[50];
char nombre[]="Gorka";
char apellido[]="Urrutia";
sprintf( nombre_completo, "%s %s", nombre, apellido );
printf( "El nombre completo es: %s.\n", nombre_completo );
printf( "%i", strcmp(nombre1,nombre2));
}
En este ejemplo vemos claramente el uso de las funciones antes mencionadas, para la salida es mejor probarlo.
Entrada de Cadenas por el Teclado
scanf
Hemos visto en capítulos anteriores el uso de scanf para números, ahora es el momento de ver su uso con cadenas. Scanf almacena en memoria (en un buffer) lo que vamos escribiendo. Cuando pulsamos ENTER lo analiza, comprueba si el formato es correcto y por último lo mete en la variable que le indicamos. Scanf toma una palabra como cadena. Usa los espacios para separar variables. Asi si introducimos una palabra, damos espacio y escribimos otra, scanf solo almacenará la primea palabra.
Es importante siempre asegurarse de que no vamos a almacenar en cadena más letras de las que caben. Para ello debemos limitar el número de letras que le va a introducir scanf.
gets
Esta función nos permite introducir frases enteras, incluyendo espacios.
Su sintaxis es:
char *gets(char *buffer);
Almacena lo que vamos tecleando en la variable buffer hasta que pulsamos ENTER. Si se ha almacenado algún caracter en buffer le añade un '\0' al final y devuelve un puntero a su dirección. Si no se ha almacenado ninguno devuelve un puntero NULL.
Por ejemplo.
int main()
{
char cadena[30];
char *p;
printf( "Escribe una palabra: " );
fflush( stdout );
p = gets( cadena );
if (p) printf( "He guardado: \"%s\" \n", cadena );
else printf( "No he guardado nada!\n" );
}
Qué son los buffer?
#include stdio.h
int main()
{
char ch;
char nombre[20], apellido[20], telefono[10];
printf( "Escribe tu nombre: " );
scanf( "%[A-Z]s", nombre );
printf( "Lo que recogemos del scanf es: %s\n", nombre );
printf( "Lo que había quedado en el buffer: " );
while( (ch=getchar())!='\n' )
printf( "%c", ch );
}
En este ejemplo el [A-Z] lo que hace es que solamente lee las letras mayúsculas. Y la salida sería:
Escribe tu nombre: GORka
Lo que recogemos del scanf es: GOR
Lo que había quedado en el buffer: ka
Arreglos de Cadenas
Un array de cadenas puede servirnos para agrupar una serie de mensajes. Por ejemplo todos los mensajes de error de un programa. Luego para acceder a cada mensaje basta con usar su número.
Por ejemplo.
#include stdio.h
#include string.h
int error( int errnum )
{
char *errores[] = {
"No se ha producido ningún error",
"No hay suficiente memoria",
"No hay espacio en disco",
"Me he cansado de trabajar"
};
printf( "Error número %i: %s.\n", errnum, errores[errnum] );
exit( -1 );
}
int main()
{
error( 2 );
}
La salida a este programa sería:
Error número 2: No hay espacio en disco.
Un array de cadenas es en realidad un array de punteros a cadenas. El primer elemento de la cadena ("No se ha producido ningún error") tiene un espacio reservado en memoria y errores[0] apunta a ese espacio.
FUNCIONES
Las funciones son de una gran utilidad en los programas. Nos ayudan a que sean más legibles y más cortos. Con ellos estructuramos mejor los programas.
Una función sirve para realizar tareas concretas y simplificar el programa. Nos sirve para evitar tener que escribir el mismo código varias veces. Ya hemos visto en el curso algunas funciones como printf, gotoxy, scanf y clrscr. Estas funciones están definidas en una biblioteca (la biblioteca estándar de C). No tenemos que preocuparnos de ella porque el compilador se encarga de ella automáticamente. Sin embargo nosotros también podemos definir nuestras propias funciones. Pocas veces se ve un programa un poco complejo que no use funciones. Una de ellas, que usamos siempre, es la función main.
Una función tiene la siguiente estructura:
tipo_de_variable nombre_de_la_función( argumentos )
{
definición de variables;
cuerpo de la función;
return 0;
}
El nombre de la función debe ser un nombre de identificador válido. Este se utiliza para llamar la función dentro del programa.
El tipo_de_variable: Cuando una función se ejecuta y termina debe devolver un valor. Este valor puede ser cualquiera de los tipos de variables que hemos visto en el capítulo de Tipos de datos (int, char, float, double), o un tipo de dato definido por el usuario. El valor que devuelve la función suele ser el resultado de las operaciones que se realizan en la función, o si han tenido exito o no.
Aqui también podemos usar el tipo void, el cual nos permite que podamos devolver cualquier tipo de variable o ninguna.
Definición de variables: Dentro de la función podemos definir variables que sólo tendrán validez dentro de la propia función. Si declaramos una variable en una función no podemos usarla en otra. Cuerpo de la función: Aquí es donde va el código de la función.
Return: Antes hemos indicado que la función devuelve un valor. La sentencia return se usa para esto. El dato que se pone despues de return es el dato que se devuelve. Puede ser una constante o una variable. Debe ser del mismo tipo que tipo_de_variable.
Argumentos: Estos son variables que se pasan como datos a una función. Deben ir separados por una coma. Cada variable debe ir con su tipo de variable. Las funciones deben definirse antes de ser llamadas.
Ejemplo.
#include stdio.h
#include conio.h
void prepara_pantalla() /* No se debe poner punto y coma aquí */
{
clrscr();
printf( "La pantalla está limpia\n" );
return; /* No hace falta devolver ningún valor, mucha gente ni siquiera pone este return */
}
int main()
{
prepara_pantalla();/* Llamamos a la función */
}
Este ejemplo aunque no devuelve ningún valor, nos muestra como se define y llama una función.
Ejemplo.
#include stdio
#include conio.h
int compara( int a, int b ) /* Metemos los parámetros a y b a la función */
{
int mayor; /* Esta función define su propia variable, esta variable sólo se puede usar aquí */
if ( a>b )
mayor = a;
else mayor = b;
return mayor;
}
int main()
{
int num1, num2;
int resultado;
printf( "Introduzca dos números: " );
scanf( "%i %i", num1, num2 );
resultado = compara( num1, num2 );/* Recogemos el valor que devuelve la función en resultado */
printf( "El mayor de los dos es %i\n", resultado );
}
En este ejemplo vemos la definición y llamada de la función, además que podemos ver que devuelve un valor. Este valor es la comparación de dos numeros.
Las variables las podemos definir en cualquier parte del programa, aunque es recomendable que se definan antes de que se llamen. Hay quienes, primero las declaran al inicio del programa y al final del mismo las definen, pero esto se puede hacer de la manera que nosotros querramos.
Duración de una Variable
Cuando definimos una variable dentro de una función, esa variable sólo es válida dentro de la función. Si definimos una variable dentro de main sólo podremos usarla dentro de main. Si por el contrario la definimos como variable global, antes de las funciones, se puede usar en todas las funciones.
Podemos crear una variable global y en una función una variable local con el mismo nombre. Dentro de la función cuando llamamos a esa variable llamamos a la local, no a la global. Esto no da errores pero puede crear confusión al programar y al analizar el código.
Argumentos de un Programa
Ya sabemos cómo pasar argumentos a una función. La función main también acepta argumentos. Sin embargo sólo se le pueden pasar dos argumentos. Veamos cuáles son y cómo se declaran:
int main( int argc, char *argv[] )
El primer argumento es argc (argument count). Es de tipo int e indica el número de argumentos que se le han pasado al programa.
El segundo es argv (argument values). Es un array de strings (o puntero a puntero a char). En el se almacenan los parámetros. Normalmente (como siempre depende del compilador) el primer elemento (argv[0]) es el nombre del programa con su ruta. El segundo (argv[1]) es el primer parámetro, el tercero (argv[2]) el segundo parámetro y así hasta el final.
A los argumentos de main se les suele llamar siempre así, no es necesario pero es costumbre.
Ejemplo.
#include stdio.h
int main(int argc,char *argv[])
{
int i;
for( i=0 ; i printf( "Argumento %i: %s\n", i, argv );
}
Si llamamos al programa argumentos.c y lo compilamos (argumentos.exe) podríamos teclear "c:\programas> argumentos hola amigos" y tendríamos como salida:
Argumento 0: c:\programas\argumentos.exe
Argumento 1: hola
Argumento 2: amigos
ESTRUCTURAS
Supongamos que queremos hacer una agenda con los números de teléfono de nuestros amigos. Necesitaríamos un array de Cadenas para almacenar sus nombres, otro para sus apellidos y otro para sus números de teléfono. Esto puede hacer que el programa quede desordenado y difícil de seguir. Y aquí es donde vienen en nuestro auxilio las estructuras.
Para definir una estructura usamos el siguiente formato:
struct nombre_de_la_estructura {
campos de estructura;
};
Por ejemplo.
struct cliente {
char nombre[30];
char apellido[40];
char telefono[10];
char edad;
};
Con esto hemos creado una estructura llamada cliente la que almacena 4 datos de tipo cadena de caracteres que son: el nombre y el apellido del cliente, telefono y edad del mismo.
Ahora que tenemos la estructura definida, es necesario declarar una variable del tipo de la estructura, esto se hace así:
struct cliente cliente1;
Ahora tenemos una variable del tipo cliente. Para acceder a cada uno de sus miembros usamo un punto ".". Así para acceder al nombre simplemente escribimos "cliente1.nombre".
Ejemplo.
# include stdio.h
struct amigo { /* Definimos la estructura estructura_amigo */
char nombre[30];
char apellido[40];
char telefono[10];
char edad;
};
struct amigo amigo;
int main()
{
printf( "Escribe el nombre del amigo: " );
fflush( stdout );
scanf( "%s", &amigo.nombre );
printf( "Escribe el apellido del amigo: " );
fflush( stdout );
scanf( "%s", &amigo.apellido );
printf( "Escribe el número de teléfono del amigo: " );
fflush( stdout );
scanf( "%s", &amigo.telefono );
printf( "El amigo %s %s tiene el número: %s.\n", amigo.nombre, amigo.apellido, amigo.telefono );
}
En este ejemplo se puede usar un gets, en vez del scanf, debido a que alguien puede introducir un nombre separado por espacios, el cual no sería aceptado por scanf.
Arreglos de Estructuras
Supongamos ahora que queremos guardar la información de varios clientes. Con una variable de estructura sólo podemos guardar los datos de uno. Para manejar los datos de más gente (al conjunto de todos los datos de cada persona se les llama REGISTRO) necesitamos declarar arrays de estructuras.
Esto se hace de la siguiente manera:
struct cliente cliente1[NUMERO_ELEMENTOS];
Ahora para acceder a un elemento determinado simplemente hacemos cliente1[0].nombre.
Ejemplo.
#include stdio.h
#define ELEMENTOS 3
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
struct estructura_amigo amigo[ELEMENTOS];
int main()
{
int num_amigo;
for( num_amigo=0; num_amigo {
printf( "\nDatos del amigo número %i:\n", num_amigo+1 );
printf( "Nombre: " ); fflush( stdout );
gets(amigo[num_amigo].nombre);
printf( "Apellido: " ); fflush( stdout );
gets(amigo[num_amigo].apellido);
printf( "Teléfono: " ); fflush( stdout );
gets(amigo[num_amigo].telefono);
printf( "Edad: " ); fflush( stdout );
scanf( "%i", &amigo[num_amigo].edad );
while(getchar()!='\n');
}
/* Ahora imprimimos sus datos */
for( num_amigo=0; num_amigo {
printf( "El amigo %s ", amigo[num_amigo].nombre );
printf( "%s tiene ", amigo[num_amigo].apellido );
printf( "%i años ", amigo[num_amigo].edad );
printf( "y su teléfono es el %s.\n" , amigo[num_amigo].telefono );
}
}
En este ejemplo debemos introducir los datos de 3 amigos y podemos ver la manera que se imprimen los datos.
Inicialización de una estructura
A las estructuras se les pueden dar valores iniciales de manera análoga a como hacíamos con los arrays. Primero tenemos que definir la estructura y luego cuando declaramos una variable como estructura le damos el valor inicial que queramos.
Ejemplo.
struct cliente cliente1 = {
"Pedro",
"Lopez",
"260-0483",
30
};
En lo único que se debe tener cuidado es que se debe seguir el orden de la declaración de los miembros de la estructura. Así que si de declara primero el apellido, se deberá poner primero el apellido.
De forma análoga se puede inicializar un arreglo de estructuras. Siempre teniendo en cuenta el orden de las mismas.
Ejemplo.
struct cliente cliente1[] =
{
"Juan", "Lopez", "220-9632", 30,
"Pedro", "Gonzalez", "520-1258", 42,
"Ana", "Martinez", "713-5694", 20
};
En este ejemplo cada línea es un registro. Como sucedía en los arrays si damos valores iniciales al array de estructuras no hace falta indicar cuántos elementos va a tener. En este caso la matriz tiene 3 elementos, que son los que le hemos pasado.
Punteros a Estructuras
Cómo no, también se pueden usar punteros con estructuras. Vamos a ver como funciona esto de los punteros con estructuras. Primero de todo hay que definir la estructura de igual forma que hacíamos antes. La diferencia está en que al declara la variable de tipo estructura debemos ponerle el operador '*' para indicarle que es un puntero.
Es importante recordar que un puntero no debe apuntar a un lugar cualquiera, debemos darle una dirección válida donde apuntar. No podemos por ejemplo crear un puntero a estructura y meter los datos directamente mediante ese puntero, no sabemos dónde apunta el puntero y los datos se almacenarían en un lugar cualquiera.
Ejemplo.
#include stdio.h
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
struct estructura_amigo amigo = {
"Juanjo",
"Lopez",
"592-0483",
30
};
struct estructura_amigo *p_amigo;
int main()
{
p_amigo = &amigo;
printf( "%s tiene ", p_amigo->apellido );
printf( "%i años ", p_amigo->edad );
printf( "y su teléfono es el %s.\n" , p_amigo->telefono );
}
Ahora que definimos el puntero y le indicamos que apunte a amigo, podemos acceder los datos de la estructura mediante el puntero. La diferencia es que ahora no se accesa con ".", sino que ahora se utiliza "->".
Accesando a estos datos podemos leer e imprimirlos.
Punteros a Arreglos de Estructuras
Por supuesto también podemos usar punteros con arrays de estructuras. La forma de trabajar es la misma, lo único que tenemos que hacer es asegurarnos que el puntero inicialmente apunte al primer elemento, luego saltar al siguiente hasta llegar al último.
Ejemplo.
# include stdio.h
# define ELEMENTOS 3
struct amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
struct amigo amigo[] =
{
"Juan", "Lopez", "305-4342", 30,
"Marcos", "Gomez", "635-4823", 42,
"Ana", "Martinez", "713-5694", 20
};
struct amigo *p_amigo;
int main()
{
int num_amigo;
p_amigo = amigo; /* apuntamos al primer elemento del array */
/* Ahora imprimimos sus datos */
for( num_amigo=0; num_amigo {
printf( "El amigo %s ", p_amigo->nombre );
printf( "%s tiene ", p_amigo->apellido );
printf( "%i años ", p_amigo->edad );
printf( "y su teléfono es el %s.\n" , p_amigo->telefono );
/* y ahora saltamos al siguiente elemento */
p_amigo++;
}
}
Ahora que sabemos acceder a los datos de un arreglo de estructuras por medio de punteros, entonces podemos introducir datos y también mandarlos a imprimir.
Paso de estructuras a Funciones
Las estructuras se pueden pasar directamente a una función igual que hacíamos con las variables. Por supuesto en la definición de la función debemos indicar el tipo de argumento que usamos, así:
int nombre_función ( struct nombre_de_la_estructura nombre_de_la variable_estructura )
Tambien podemos pasar solo los miembros de la estructura a la función.
Estructuras Anidadas
Es posible crear estructuras que tengan como miembros otras estructuras. Esto tiene diversas utilidades, por ejemplo tener la estructura de datos más ordenada.
Por ejemplo, si queremos una estructura de clientes de un banco con sus datos y tambien la fecha del último pago, se podría hacer de la siguiente manera:
struct ultimopago{
int dia;
int mes;
int año;
};
Y ahora la estructura total, incluyendo la de la fecha del último pago.
struct cliente{
char nombre[25];
char apellido[25];
float saldo;
struct ultimopago pago;
}cliente1;
De esta manera tenemos una variable "cliente1" del tipo cliente con una estructura anidada.
PUNTEROS
Los punteros son una de las más potentes características de C, pero a la vez uno de sus mayores peligros. Los punteros nos permites acceder directamente a cualquier parte de la memoria. Esto da a los programas C una gran potencia. Sin embargo son una fuente ilimitada de errores. Un error usando un puntero puede bloquear el sistema y a veces puede ser difícil detectarlo. Otros lenguajes no nos dejan usar punteros para evitar estos problemas, pero a la vez nos quitan parte del control que tenemos en C.
Memoria
Cuando hablamos de memoria nos estamos refiriendo a la memoria RAM del ordenador. Son unas pastillas que se conectan a la placa base y nada tienen que ver con el disco duro. El disco duro guarda los datos permanentemente (hasta que se rompe) y la información se almacena como archivos. Nosotros podemos decirle al ordenador cuándo grabar, borrar, abrir un documento, etc. La memoria RAM en cambio, se borra al apagar el ordenador. La memoria Ram la usan los programas sin que el usuario de éstos se de cuenta.
Direcciones de Variables
Al declarar una variable estamos diciendo al ordenador que nos reserve una parte de la memoria para almacenarla. Cada vez que ejecutemos el programa la variable se almacenará en un sitio diferente, eso no lo podemos controlar, depende de la memoria disponible y otros factores misteriosos. Puede que se almacene en el mismo sitio, pero es mejor no fiarse. Dependiendo del tipo de variable que declaremos el ordenador nos reservará más o menos memoria. Como vimos en el capítulo de tipos de datos cada tipo de variable ocupa más o menos bytes.
Cuando finaliza el programa todo el espacio reservado queda libre. Existe una forma de saber qué direcciones nos ha reservado el ordenador. Se trata de usar el operador & (operador de dirección).
Para mostrar la dirección de la variable usamos %p en lugar de %i, sirve para escribir direcciones de punteros y variables. El valor se muestra en hexadecimal.
No hay que confundir el valor de la variable con la dirección donde está almacenada la variable. La variable 'a' está almacenada en un lugar determinado de la memoria, ese lugar no cambia mientras se ejecuta el programa. El valor de la variable puede cambiar a lo largo del programa, lo cambiamos nosotros. Ese valor está almacenado en la dirección de la variable. El nombre de la variable es equivalente a poner un nombre a una zona de la memoria. Cuando en el programa escribimos 'a', en realidad estamos diciendo, "el valor que está almacenado en la dirección de memoria a la que llamamos 'a'.
QUE ES UN PUNTERO?
Un puntero es una variable un tanto especial. Con un puntero podemos almacenar direcciones de memoria. En un puntero podemos tener guardada la dirección de una variable.
Muchas veces los punteros se usan para guardar las direcciones de variables. Vimos en el capítulo Tipos de Datos que cada tipo de variable ocupaba un espacio distinto. Por eso cuando declaramos un puntero debemos especificar el tipo de datos cuya dirección almacenará.
Para declarar un puntero se utiliza un asterisco, para indicar que se trata de un puntero.
Por ejemplo.
char *punt;
Esto declara una variable puntero que almacenara la direccion de una variable de tipo char. Cuando un puntero contiene la direccion de una variable, se dice que ese puntero apunta a esa variable.
La sintaxis general para declarar un puntero es como sigue:
tipo_de_dato *nombre_del_puntero;
Utilidad de un Puntero
Los punteros tienen muchas utilidades, por ejemplo nos permiten pasar argumentos (o parámetros) a una función y modificarlos. También permiten el manejo de cadenas y de arrays. Otro uso importante es que nos permiten acceder directamente a la pantalla, al teclado y a todos los componenetes del ordenador.
Nos deben dejar también la posibilidad de acceder a esas posiciones de memoria. Para acceder a ellas se usa el operador *, que no hay que confundir con el de la multiplicación.
Ejemplo.
# include stdio.h
int main()
{
int numero;
int *punt;
numero = 43;
punt = №
printf( "Dirección de numero = %p, valor de numero = %i\n", &numero, *punt );
}
Se puede observar en el ejemplo que para acceder al valor de numero usamos *punt en vez de numero. Esto es porque punt apunta a numero y *punt nos permite acceder al valor al que apunta punt.
Usando un puntero podemos apuntar a una variable y con *puntero vemos o cambiamos el contenido de esa variable. Un puntero no sólo sirve para apunta a una variable, también sirve para apuntar una dirección de memoria determinada.
Punteros como argumentos de funciones
Hemos visto en el capítulo de funciones cómo pasar parámetros y cómo obtener resultados de las funciones (con los valores devueltos con return). Pero tiene un inconveniente, sólo podemos tener un valor devuelto. Ahora vamos a ver cómo los punteros nos permites modificar varias variables en una función.
Ejemplo.
# include stdio.h
int suma( int *a, int b )
{
int c;
c = *a + b;
*a = 0;
return c;
}
int main()
{
int var1, var2;
var1 = 5; var2 = 8;
printf( "La suma es: %i y a vale: %i\n", suma(&var1, var2), var1 );
}
En este ejemplo podemos ver como con un puntero podemos cambiar el valor de una variable, a traves de ellos podemos acceder y cambiar los datos. Tambien los podemos utilizar como argumentos de funciones, tal es el caso del ejemplo anterior.
ASIGNACION DINAMICA DE MEMORIA
Sabemos que cada vez que queremos usar una varible, debemos reservarle un lugar en memoria al comenzar el programa, o sea que debemos indicar de antemano cuanta memoria vamos a utilizar.
Pero hay ocasiones en que esto no es bueno, hay veces en las que no sabemos cuánta memoria vamos a necesitar. Por ejemplo si hacemos un editor de texto no podemos saber de antemano cuál va a ser la longitud del texto. Por eso a veces es necesario poder reservar memoria según se va necesitando. Además de esta forma nuestros programas aprovecharán mejor la memoria del ordenador en el que se ejecuten, usando sólo lo necesario.
malloc y free
Existen varias funciones para la asignación dinámica de la memoria, pero las dos funciones básicas son malloc y free.
La función malloc sirve para reservar una parte de la memoria, y devuelve la dirección del comienzo de esa parte. Esta dirección podemos almacenarla en un puntero y así podemos acceder a la memoria reservada.
La sintaxis de malloc, es como sigue:
puntero = (tipo_de_variable *) malloc( número de bytes a reservar );
puntero: es una variable tipo puntero que almacena la dirección del bloque de memoria reservado. Puede ser un puntero a char, int, float,...
(tipo_de_variable *): es lo que se llama un molde. La función malloc nos reserva una cierta cantidad de bytes y devuelve un puntero del tipo void (que es uno genérico). Con el molde le indicamos al compilador que lo convierta en un puntero del mismo tipo que la variable puntero. Esto no es necesario en C, ya que lo hace automáticamente, aunque es aconsejable acostumbrarse a usarlo.
Una vez reservada la memoria y guardada su dirección en un puntero podemos usar ese puntero como hemos visto hasta ahora. Si no había suficiente memoria libre malloc devolverá el valor NULL. El puntero por tanto apuntará a NULL. Es muy importante comprobar siempre si se ha podido reservar memoria o no comprobando el valor de puntero. Esto se puede hacer de la siguiente manera:
if(puntero)
Si hay memoria suficiente entonces se cumple la condicion, de lo contrario es falso.
Cuando ya no necesitemos más el espacio reservado debemos liberarlo, es decir, indicar al ordenador que puede destinarlo a otros fines. Si no liberamos el espacio que ya no necesitamos corremos el peligro de agotar la memoria del ordenador. Para ello usamos la función free.
La sintaxis de free es:
free( puntero );
Ejemplo.
# include stdio.h
int main()
{
int bytes;
char *texto;
printf("Cuantos bytes quieres reservar: ");
scanf("%i",&bytes);
texto = (char *) malloc(bytes);
/* Comprobamos si ha tenido éxito la operación */
if (texto)
{
printf("Memoria reservada: %i bytes = %i kbytes = %i Mbytes\n",
bytes, bytes/1024, bytes/(1024*1024));
printf("El bloque comienza en la dirección: %p\n", texto);
/* Ahora liberamos la memoria */
free( texto );
}
else
printf("No se ha podido reservar memoria\n");
}
En este ejemplo vemos claramente la asignacion y liberacion de la memoria, despues de preguntar cuanta memoria se desea reservar, se comprueba si se reservo y luego se procede a las instrucciones de lo contrario inmediatamente manda el mensaje "No se ha podido reservar memoria".
ARCHIVOS
Lectura de un archivo
Todas las funciones de entrada/salida estándar usan el puntero *FILE para conseguir información sobre el archivo abierto. Este puntero no apunta al archivo sino a una estructura que contiene información sobre él.
Esta estructura incluye entre otras cosas información sobre el nombre del archivo, la dirección de la zona de memoria donde se almacena el fichero, tamaño del buffer. Para poder utilizar este puntero se debe incluir el archivo de cabecera stdio.h.
Para abrir el archivo utilizamos la funcion fopen. La sintaxis de esta funcion es asi:
FILE *fopen(const char *nombre_fichero, const char *modo);
El nombre del archivo se puede indicar directamente o usando una variable.
El archivo se puede abrir de distintas formas, estas se muestran en la tabla.
Parametro Modo
r Abre un archivo existente para lectura
w Crea un archivo nuevo y lo abre para escritura
Si el archivo existe, borra su contenido
a Abre un archivo (si no existe lo crea) para escritura
El puntero se situa al final del archivo, de forma
que se puedan añadir datos sin borrar los existentes
Se pueden añadir ciertos modificadores a estos parametros. Estos son:
Modificador Accion
b Abre el archivo en modo binario
t Abre el archivo en modo texto
+ Abre el archivo para lectura y escritura
Estos modificadores se pueden combinar con los parametros dados anteriormente para abrir el archivo en la forma deseada.
Ejemplo.
wb+ Crea el archivo (o lo borra si existe) en modo binario para lectura y escritura.
rt Abre un archivo existente en modo texto para lectura.
Una cosa muy importante después de abrir un fichero es comprobar si realmente está abierto. El sistema no es infalible y pueden producirse fallos: el fichero puede no existir, estar dañado o no tener permisos de lectura.
Si intentamos realizar operaciones sobre un puntero tipo FILE cuando no se ha conseguido abrir el fichero puede haber problemas. Por eso es importante comprobar si se ha abierto con éxito. Si el fichero no se ha abierto el puntero fichero (puntero a FILE) tendrá el valor NULL, si se ha abierto con éxito tendrá un valor distinto de NULL.
Por ejemplo.
if (fichero==NULL)
{
printf( "No se puede abrir el fichero.\n" );
exit( 1 );
}
Este es un ejemplo para probar si el archivo se abrio con exito. Si no se ha abierto con exito lo mas conveniente es salir del programa, esto se puede hacer con una funcion exit.
Leer un archivo
Para leer un archivo podemos utilizar la función getc, que lee los caracteres uno a uno. Se puede usar también la función fgetc. Además de estas dos existen otras funciones como fgets, fread que leen más de un carácter.
La sintaxis de la funcion getc y fgetc es asi:
int getc(FILE *archivo);
Ejemplo.
letra = getc( fichero );
En este ejemplo se toma un caracter de archivo, lo almacena en letra y el puntero se coloca en el siguiente caracter.
Comprobar el fin de un Archivo
Cuando entramos en el bucle while, la lectura se realiza hasta que se encuentre el final del fichero. Para detectar el final del fichero se pueden usar dos formas:
con la función feof()
comprobando si el valor de letra es EOF.
Esta función comprueba si se ha llegado al final de fichero en cuyo caso devuelve un valor distinto de 0. Si no se ha llegado al final de fichero devuelve un cero.
Ejemplo.
# include stdio.h
int main()
{
FILE *fichero;
char letra;
fichero = fopen("origen.txt","r");
if (fichero==NULL)
{
printf( "No se puede abrir el fichero.\n" );
exit( 1 );
}
printf( "Contenido del fichero:\n" );
letra=getc(fichero);
while (feof(fichero)==0)
{
printf( "%c",letra );
letra=getc(fichero);
}
if (fclose(fichero)!=0)
printf( "Problemas al cerrar el fichero\n" );
}
En este ejemplo se aplica todo lo que hemos dicho anteriormente, se abre el archivo para lectura.
Cerrar un Archivo
Una vez realizadas todas las operaciones deseadas sobre el archivo hay que cerrarlo. Es importante no olvidar este paso pues el archivo podría corromperse. Al cerrarlo se vacían los buffers y se guarda el archivo en disco. Un archivo se cierra mediante la función fclose(fichero). Si todo va bien fclose devuelve un cero, si hay problemas devuelve otro valor.
Escritura de un Archivo
En esta etapa se utilizan las mismas funciones que para l