You are on page 1of 80

Universidade da Coruña

Escuela Universitaria Politécnica


Departamento de Computación

Manual práctico
programación en C

Fundamentos de Informática
Ingeniería Técnica Industrialalidad

Curso 2008/2009
Índice
1.
 Introducción. .............................................................................................................. 3

2.
 Entrada / salida básica................................................................................................ 4

3.
 Sentencias condicionales. ........................................................................................ 12

4.
 Bucles....................................................................................................................... 20

5.
 Funciones ................................................................................................................. 26

6.
 Punteros (paso por referencia) ................................................................................. 30

Paso por referencia...................................................................................................... 31

7.
 Estructuras ............................................................................................................... 37

8.
 Arrays....................................................................................................................... 40

Ordenación.................................................................................................................. 43

9.
 Cadenas de caracteres .............................................................................................. 48

10.
 Matrices ................................................................................................................... 52

11.
 Ficheros.................................................................................................................... 56

12.
 Reserva dinámica de memoria ................................................................................. 63

Apéndice A. Normas de estilo de programación ............................................................ 67

Identificadores significativos ...................................................................................... 67

Constantes simbólicas................................................................................................. 68

Comentarios ................................................................................................................ 69

Estructura del programa.............................................................................................. 70

Indentación o sangrado ............................................................................................... 73

13.
 Apéndice B .............................................................................................................. 78

Antonio Becerra Permuy
1. Introducción.

El objetivo de este documento es proporcionar una orientación práctica al alumno de


primer curso de programación en ingeniería. En ningún caso estos apuntes pretenden ser
el único material necesario para aprender a programar en C, y siempre deberán ir
apoyados los apuntes o transparencias que se proporcionan en clase de teoría o bien de
uno de los libros que incluidos en la bibliografía.
El alumno encontrará en este documento aquellos aspectos de la programación en C
que, a lo largo de los años, hemos encontrado que se deben recalcar más a menudo al
alumno, bien por su dificultad o bien por su importancia. De esta forma, los programas
que se encuentran resueltos en este manual incluyen una descripción desde el punto de
vista conceptual, pero no inciden en cómo elaborar los algoritmos.
Todos los ejemplos aquí mostrados están realizados con el compilador que se utiliza
este año en las clases de prácticas: el Codeblocks. Este programa está disponible en su
página web para cualquier plataforma: http://www.codeblocks.org/
2. Entrada / salida básica.
La inmensa mayoría de los programas necesitan conocer para su ejecución uno o más
datos (que denominaremos datos de entrada) y proporcionan, como resultado, uno o
más datos (datos de salida). En caso contrario, estaríamos ante un programa que siempre
realiza la misma función y que no tiene ningún tipo de relación con el exterior, lo cual
no es muy común.
• Los datos de entrada pueden ser introducidos en el programa por medio de un
fichero o por algún dispositivo de entrada como un teclado.
• Los datos de salida pueden almacenarse también en un fichero o pueden
mostrarse por algún dispositivo de salida, como una pantalla o una impresora.
A continuación veremos varios ejemplos de programas en C en el que los datos de
entrada se van a adquirir por teclado y los datos de salida se van a mostrar por pantalla.
Para poder desarrollar estos programas, utilizaremos el entorno de desarrollo integrado
(IDE, en inglés) Codeblocks que incluye, además de un compilador de lenguaje C, un
depurador y un editor de texto. Una vez que se ejecuta el Codeblocks nos encontramos
con una pantalla similar a la de la Figura 1 (el entorno gráfico dependerá del sistema
operativo):

Figura 1: Aspecto del Codeblocks

Para crear una nueva aplicación es necesario pinchar sobre la opción de la ventana
cental Create a new project o bien acceder al menú File, ahí seleccionar New y después
Project. Toda aplicación es para Codeblocks un proyecto. Si bien los ejemplos que
veremos en este curso son muy sencillos y todo el código fuente estará únicamente en
un fichero, una aplicación real suele tener su código fuente dividido en varios ficheros,
lo que facilita su desarrollo y posterior mantenimiento, ya sea por una persona o por un
equipo de personas. Un proyecto es, por tanto, un conjunto de ficheros a partir de los
cuales se generará una aplicación.
Tras seleccionar la opción para crear un nuevo proyecto nos aparece el cuadro de
diálogo de la Figura 2.

Figura 2: Selección del tipo de proyecto

Codeblocks tiene plantillas para distintos tipos de proyectos. Nosotros escogeremos


siempre Console Application, que se corresponde con el perfil de nuestra aplicación, sin
ningún tipo de interfaz gráfica. Al pinchar sobre el botón Go nos aparece un asistente,
en la primera ventana marcamos Next y en la siguiente escogemos la opción C y
volvemos a marcar Next. Es muy importante que en este paso no seleccionemos la
opción C++ porque es otro lenguaje diferente de C y no es el objeto de esta
asignatura. En la siguiente ventana debemos dar un nombre al proyecto (Project Title)
y escoger un directorio donde guardar sus ficheros (Folder to create project in). Es
conveniente que el directorio sea distinto para cada proyecto, ya que de lo contrario
ficheros de un proyecto sobrescribirán ficheros de otro proyecto (por ejemplo, el fichero
main.c o las carpetas obj y bin). Además, el proyecto debería tener un nombre
significativo del programa que almacena de cara a una identificación posterior simple y
únicamente debería contener letras y números separados, si es necesario, con
“guión bajo”. Otros caracteres como espacios, puntos, guiones, acentos, etc y la
letra ñ nunca se deben poner en el nombre del proyecto. Al escoger el nombre del
proyecto y su ubicación, las dos casillas Project Filename y Resulting filename se
rellenan automáticamente.
Una vez finalizados estos pasos, pinchamos en Next y nos aparece la última ventana del
asistente, donde nos pregunta el compilador y el tipo de configuración (Debug o
Release) que deseamos crear. Por defecto, dejaremos estas opciones tal cual aparecen y
simplemente seleccionamos Finish. Codeblocks nos crea un esqueleto para nuestra
aplicación que puede verse en la Figura 3, donde en la parte izquierda de la ventana
aparece el explorador del proyecto, donde tendremos una carpeta source que contiene el
archivo main.c inicial que nos crea Codeblocks.
Las dos primeras líneas son directivas del compilador que incluyen sendos ficheros en
el de vuestra aplicación. Estos ficheros, stdio.h y stdlib.h, contienen definiciones de
constantes y funciones útiles en cualquier aplicación de consola, por ejemplo para leer
de teclado y escribir por pantalla.
A continuación tenemos la función main. Todo programa en C se divide en
funciones. Una función es un conjunto de sentencias que realiza una determinada tarea
y que, opcionalmente, puede tener una serie de parámetros de entrada (argumentos) y,
también opcionalmente, puede devolver un resultado. Todo programa en C debe tener al
menos la función main, que es la función por la cual se empieza a ejecutar un programa.
A la hora de definir una función, la primera línea indica el tipo de dato del valor que
devuelve, si es que devuelve alguno, el nombre de la función y los argumentos. En
principio, main no tiene porque devolver ningún dato, pero por convenio se suele hacer
que devuelva el número 0 si el programa finaliza correctamente y otro número distinto
de 0, dependiente de la aplicación, en caso contrario. Esto es particularmente útil
cuando se invoca un programa (“hijo”) desde otro (“padre”) ya que, en ese caso, el
sistema operativo permite al programa “padre” consultar el valor devuelto por el
programa “hijo” con lo que el primero puede comprobar si el segundo produjo algún
error. Como se puede observar, el código fuente que crea por defecto el Codeblocks
hace uso de ese convenio y devuelve un 0 cuando finaliza correctamente (sentencia
“return 0;”).

Figura 3: Console Application generada por defecto

La instrucción printf que aparece en la línea 6 del programa muestra por pantalla el
mensaje “Hola mundo” en inglés. Este tipo de instrucción forma parte del contenido de
este tema y se verá en detalle a continuación. Decir, por último, que para compilar y
ejecutar este programa de prueba bastaría con ir al menú Build y seleccionar Build and
Run (o bien seleccionar la tecla F9). Los detalles sobre el funcionamiento del resto de
menús y opciones del Codeblocks serán explicadas en clase y además se pueden
consultar en la documentación on-line: http://www.codeblocks.org/user-manual.

A continuación vamos a hacer un primer programa en C que, tras pedir al usuario el


valor del radio, nos dé el área del círculo con dicho radio:
#include <stdio.h>
#include <stdlib.h>

int main()
{
float radio;

printf("Introduce el radio del circulo: ");


scanf("%f", &radio);
printf("El area del circulo es %f\n", 3.14159 * radio * radio);

return 0;
}

El programa sólo usa una variable: radio. Esta variable está definida dentro de la
función main, por tanto es una variable local. Las variables locales sólo son accesibles
dentro de la función en la que son definidas. Si estuviese definida fuera de la función,
después de los #include, por ejemplo, sería una variable global. Las variables globales
son accesibles desde cualquier función de un programa. Como regla general, siempre
que sea posible se deben utilizar variables locales, ya que el mantenimiento de un
programa que utiliza variables globales es más complicado por el mero hecho de que
una variable global puede ser modificada en cualquier parte del programa y encontrar
un fallo relacionado con esa variable es más difícil. En la normas de estilo que aparecen
en el Apéndice A se muestran ejemplos concretos que apoyan esta afirmación.

Uno de los errores más frecuentes al empezar a programar en C consiste en un paso de


parámetros a las funciones printf y scanf incorrecto. printf escribe en la pantalla,
mientras que scanf lee de teclado y almacena lo leído en variables. Lo que printf escribe
por pantalla puede ser texto predeterminado o algún dato proveniente de una constante,
una variable, una llamada a una función o una expresión aritmética. Sin embargo, scanf
no necesita el valor de una variable, si no la dirección de memoria donde se guarda el
contenido de esa variable, ya que va a sobrescribir dicho contenido con lo que se lea por
teclado. Es por eso que las variables en scanf van precedidas por un signo & que se
puede interpretar como “dirección de memoria de”, mientras que eso no sucede con
printf.

Otro aspecto a mencionar en este primer programa es que la sintaxis de C fue elaborada
por anglosajones, con lo que:

- El separador de decimales es el punto, no la coma.


- No se pueden utilizar caracteres acentuados y ni la letra “ñ” en los nombres de
las variables o las funciones.

Finalmente, resaltar la importancia de una correcta indentación en el código fuente. En


C, el elemento sintáctico que determina el final de una sentencia es el “;”. Un programa
en C podría escribirse, si así se desease, en una sola línea. Obviamente, esto no es
deseable. Y no lo es porque dificulta el entendimiento del funcionamiento del programa
y, por tanto, su mantenimiento. Un programa no sólo debe funcionar correctamente y
eficientemente, si no que debe estar programado de tal forma que realizar
modificaciones en él en el futuro sea lo más fácil posible. Uno de los elementos que
ayudan a esto es la correcta indentación del código fuente:

• Al empezar a escribir un programa, todas las sentencias se escriben de tal forma que
disten la misma distancia del margen izquierdo.
• Sin embargo, cuando se empieza a escribir el código de una función, de una
sentencia condicional, o de una sentencia iterativa, el código se separa x caracteres
adicionales del margen izquierdo, donde x es un número constante para todo el
programa.
• Ninguna línea debe de pasar de 80 caracteres para facilitar su impresión. Si una
sentencia excede de esa longitud, se continúa en la línea siguiente dejando
nuevamente x caracteres adicionales en el margen izquierdo.

A lo largo de los ejemplos presentes en este manual, se podrá observar como se aplican
estas reglas de indentación. En el Apéndice A de este manual, mostramos unas
normas básicas de estilo de programación en C que serán aplicables a cualquier
programa realizado a partir de ahora.

A continuación, modificamos el ejercicio anterior de forma que el programa no sólo nos


dé el área del círculo para un radio determinado, sino también la longitud de la
circunferencia para ese mismo radio:
#include <stdio.h>
#include <stdlib.h>

int main()
{
float radio, pi=3.14159;

printf("Introduce el radio del circulo: ");


scanf("%f", &radio);
printf("El area del circulo es %.3f\n", pi * radio * radio);
printf("La longitud de la circunferencia es %.3f\n", 2 * pi * radio);
system("PAUSE");
return 0;
}

A diferencia del ejercicio anterior, aquí hemos utilizado una variable pi para almacenar
en ella el valor del número π en vez de usar una constante. En general, siempre es mejor
definir las constantes como tales o almacenarlas en variables que no introducirlas
directamente en las sentencias que las usen, ya que lo primero facilita el mantenimiento
del código (si queremos cambiar la constante sólo es necesario cambiarla en un sitio) y
es menos propenso a cometer errores (cuántas más veces escribamos un número, más
probabilidades hay de que nos equivoquemos haciéndolo).

La expresión 2 * pi * radio produce como resultado un número en punto flotante.


Cuando en una expresión aparecen valores en punto flotante y valores enteros, los
enteros son convertidos a punto flotante antes de realizar la operación. Por tanto, 2 es
convertido a 2.0 antes de multiplicarse por pi. Este tipo de conversiones automáticas
deben de tenerse en cuenta. Por ejemplo, ambas expresiones producen resultados
distintos:

3*5/2
3 * 5 / 2.0

La primera da como resultado 7, ya que tanto la multiplicación como la división son con
números enteros. Sin embargo, la segunda da como resultado 7.5, ya que al ser 2.0 un
número en punto flotante el resultado de la multiplicación (15) es convertido a otro
número en punto flotante y la división es una división de números en punto flotante.
En los printf de este ejercicio podemos ver también cómo limitar el número de
decimales de un número en punto flotante a la hora de imprimirlo por pantalla: %.3f
provoca que sólo se impriman 3 decimales.

Como se ha comentado con anterioridad, las constantes es mejor definirlas como tales o
introducirlas en variables antes que explicitarlas cada vez que se requieren. Lo primero,
en C, se realiza mediante la directiva #include:
#include <stdio.h>
#include <stdlib.h>

#define PI 3.14159

int main()
{
float radio;

printf("Introduce el radio del circulo:\t\t");


scanf("%f", &radio);
printf("El area del circulo es\t\t\t%.3f\n", PI * radio * radio);
printf("La longitud de la circunferencia es\t%.3f\n", 2 * PI * radio);

return 0;
}

En programación, una constante se puede pensar como una variable que no es tal, es
decir, a la que no se le puede cambiar el valor inicial una vez se le ha asignado este. Sin
embargo, en C es un poco distinto. Lo que realmente hace #define es sustituir
literalmente en el código fuente, desde el momento en el que aparece el #define, todas
las instancias de lo definido por su valor antes de realizar la compilación. Es decir, en el
ejemplo anterior las apariciones de PI se cambiarían por 3.14159 antes de compilar.
Esto tiene las siguientes implicaciones:

• No se puede cambiar el valor de una constante. Esto es muy fácil de ver. Si tenemos
un
#define NUMERO 2

y, posteriormente, hacemos

NUMERO = 3;

eso sería lo mismo que poner

2 = 3;

lo cual, obviamente, no tiene sentido.


• Un #define no tiene porque restringirse a un valor numérico, si no que puede ser
cualquier cosa y puede tener incluso argumentos como si fuese una función. Ej:
#include <stdio.h>
#include <stdlib.h>

#define CUADRADO(x) (x*x)

int main()
{
int x;

printf("Dime un numero: ");


scanf("%d", &x);
printf("El cuadrado de %d es %d\n", x, CUADRADO(x));
system("PAUSE");
return 0;
}

Hay que tener cuidado de que no se produzca ningún error sintáctico o semántico en
el código fuente tras sustituir el valor. Por ejemplo, esto sería erróneo (aunque
fácilmente corregible poniendo paréntesis alrededor de x+y en el #define):
#include <stdio.h>
#include <stdlib.h>

#define SUMA(x,y) x+y

int main()
{
int x,y;

printf("Dime un numero: ");


scanf("%d", &x);
printf("Dime otro numero: ");
scanf("%d", &y);
printf("La mitad de la suma entera es %d\n", SUMA(x,y)/2);
system("PAUSE");
return 0;
}

A la hora de definir constantes, éstas se suelen poner en mayúsculas, reservando las


minúsculas para las variables, para que, en cualquier lado del código fuente, se pueda
saber de forma inmediata cuando algo es una constante / definición o una variable /
función.

En los printf de la última versión mostrada del programa que calcula el área de un
círculo, aparece el carácter especial “\t”, que representa el carácter tabulador y, por
tanto, a la hora de imprimirlo por pantalla se substituye por espacios en blanco.

Para finalizar con el apartado de entrada / salida básica, veremos una versión más del
programa que calcula el área de un círculo:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
float radio, pi_radio;

printf("Introduce el radio del circulo:\t\t");


scanf("%f", &radio);
pi_radio = M_PI * radio;
printf("El area del circulo es\t\t\t%.3f\n", pi_radio * radio);
printf("La longitud de la circunferencia es\t%.3f\n", 2 * pi_radio);
return 0;
}

La utilización de número π es muy habitual, por lo que en realidad no es necesario


definirlo en nuestros programas. Podemos hacer un #include del fichero math.h que
incluye definiciones de números habituales en Matemáticas y de las funciones
implementadas en la librería matemática estándar de C (estas funciones nos permiten,
por ejemplo, calcular raíces cuadradas, logaritmos, potencias, etc.). Estas son las
constantes definidas en math.h:
#define M_E 2.7182818284590452354
#define M_LOG2E 1.4426950408889634074
#define M_LOG10E 0.43429448190325182765
#define M_LN2 0.69314718055994530942
#define M_LN10 2.30258509299404568402
#define M_PI 3.14159265358979323846
#define M_PI_2 1.57079632679489661923
#define M_PI_4 0.78539816339744830962
#define M_1_PI 0.31830988618379067154
#define M_2_PI 0.63661977236758134308
#define M_2_SQRTPI 1.12837916709551257390
#define M_SQRT2 1.41421356237309504880
#define M_SQRT1_2 0.70710678118654752440

Como se puede ver, el número π está aquí definido bajo la constante M_PI y con mucha
mayor precisión que la que nosotros empleamos en los ejemplos anteriores.

Anteriormente hemos comentado que un programa no sólo debe de funcionar, si no que


debe de hacerlo de forma eficiente. Por tanto, ¿para qué repetir dos veces la misma
operación? En esta última versión del programa, hemos creado una variable temporal
pi_radio en la que guardamos el valor resultante de multiplicar pi por radio. De esta
forma, en los dos printf usamos el valor de esta variable en vez de repetir la operación.
Notar que la asignación de un valor a una variable es siempre de la forma variable =
expresión, es decir, la variable se pone a la izquierda del signo igual y el valor a
almacenar en la variable, o la expresión que producirá un valor a almacenar en la
variable, se pone a la derecha. En la siguiente captura de pantalla podemos ver el
resultado de ejecutar el programa.

Figura 4: Aplicación que calcula la circunferencia y el área del círculo a partir del radio.
3. Sentencias condicionales.
Para ilustrar la utilización de las sentencias condicionales utilizaremos como ejemplo un
programa que calculará las raíces reales de una ecuación de segundo grado.

Una posible primera implementación sería esta:


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

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


{
double a, b, c, raiz, dos_a;

printf("Introduce el valor de \"a\":\t");


scanf("%lf",&a);
printf("Introduce el valor de \"b\":\t");
scanf("%lf",&b);
printf("Introduce el valor de \"c\":\t");
scanf("%lf",&c);
raiz = sqrt(b * b - 4 * a * c);
dos_a = 2 * a;
printf("Una solucion es:\t\t%lf\n", (-b + raiz) / dos_a);
printf("La otra solucion es:\t\t%lf\n", (-b - raiz) / dos_a);
system("PAUSE");
return 0;
}

En este programa vemos varias cosas nuevas:

• Las variables de tipo double. Este tipo de dato se usa para almacenar números en
punto flotante que necesitan de una precisión o rango de representación mayor que
el que proporciona el tipo float. Aunque el número de bytes usado para representar
un float o un double depende de la arquitectura de la máquina y del sistema
operativo en ella instalado, en las máquinas usadas en clase de prácticas se usan 4
bytes para los float y 8 bytes para los double.
• Como las comillas se usan para empezar y finalizar las cadenas de caracteres,
cuando se quiere introducir ese carácter en la propia cadena de caracteres es
necesario anteponer el carácter ‘\’.
• Para imprimir o leer un double se usa el indicador de formato %lf.
• Para calcular la raíz cuadrada de un número usamos la función sqrt, definida en
math.h.

Además, es importante volver a recalcar la importancia de dos cosas:

• Los programas deben ser eficientes y evitar repetir cálculos de forma innecesaria:
usamos las variables raiz y dos_a para almacenar resultados temporales.
• Los operadores * y / tienen más prioridad que + y -, por eso es necesario agrupar (-
b+raiz) y (-b-raiz) entre paréntesis, ya que en caso contrario sólo raiz estaría siendo
dividida por dos_a.

Ahora bien, este programa es incorrecto porque no contempla determinados casos


particulares de a, b y c para los cuales no es posible realizar alguna de las operaciones
aritméticas y el programa abortará con un error. Estos casos son los siguientes:
• (b * b – 4 * a * c) es negativo => no se puede hacer la raíz cuadrada de un número
negativo ya que la función sqrt sólo trabaja con números reales. Si esta situación se
produce lo que debemos hacer es avisar al usuario de que no existen raíces reales
para la ecuación planteada.
• a es 0 => no se puede hacer una división por 0. En este caso, nos encontramos con
que la ecuación sólo tiene una solución (-c/b).

Antes de proseguir, es necesario recalcar una cosa: ¡no es suficiente con probar un
programa una vez para asegurar su correcto funcionamiento! Es muy habitual la frase
“pues a mi me funciona” cuando se muestra un fallo en un programa a un alumno.
Aunque la verificación formal no entra dentro del programa de esta asignatura, es
importante tener claro que probar el correcto funcionamiento de un programa no es
trivial. Algunos consejos prácticos son:

• Comprobar que no se pueden dar divisiones por cero.


• Comprobar que no se pueden dar raíces cuadradas de números negativos.
• Comprobar que no se pueden dar situaciones de desbordamiento. Por ejemplo, si
multiplicamos dos números muy grandes es posible que excedamos el rango de
representación del tipo de datos y obtengamos un resultado incorrecto.

Vemos, por tanto, que en este programa es necesario ejecutar unas instrucciones u otras
en función del valor de unas variables. Para ello utilizaremos la instrucción condicional
if (…) … else…, explicada en clase, y que permite precisamente eso, ejecutar un bloque
de instrucciones cuando se cumple una condición y ejecutar otro bloque de
instrucciones distinto en caso contrario. El programa quedaría así:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

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


{
double a, b, c, raiz, dos_a;

printf("Introduce el valor de \"a\":\t");


scanf("%lf",&a);
printf("Introduce el valor de \"b\":\t");
scanf("%lf",&b);
printf("Introduce el valor de \"c\":\t"); Comparación
scanf("%lf",&c);
if (a == 0)
if (b != 0)
printf("La solucion es:\t\t\t%lf\n", -c / b);
else
printf("Esto no es una ecuacion.\n"); Asignación
else
{
raiz = b * b - 4 * a * c;
if (raiz >= 0)
{
raiz = sqrt(raiz);
dos_a = 2 * a;
printf("Una solucion es:\t\t%lf\n", (-b + raiz) / dos_a);
printf("La otra solucion es:\t\t%lf\n", (-b - raiz) / dos_a);
}
else
printf("La ecuacion no tiene raices reales.\n");
}
system("PAUSE");
return 0;
}

Figura 5: Aplicación que calcula las raíces reales de una ecuación de segundo grado

Sobre este programa, recalcaremos varios aspectos que suelen ser fuente de errores al
empezar a programar en C:

• Los operadores para realizar una comparación de igualdad y para realizar una
asignación son distintos. Para lo primero se usan dos símbolos “=” adyacentes
mientras que para lo segundo un único símbolo “=”. Si se usa el operador incorrecto
(asignación en vez de comparación o comparación en vez de asignación), no se
producirá ningún error de compilación, si no que el programa no se comportará
como cabría esperar. Esto es así porque ambos tipos de expresión tienen un
significado completo como sentencia. Una comparación es una expresión que
devuelve “1” si se verifica y “0” en caso contrario. Ponerla en una sentencia como
en el siguiente ejemplo, que siempre imprimirá el valor de x, es sintácticamente
correcto aunque no tenga sentido ya que no se utiliza el resultado para nada:
#include <stdio.h>
#include <stdlib.h>

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


{
int x, y, z;

printf("Introduce un numero: ");


scanf("%d",&x);
printf("Introduce otro numero: ");
scanf("%d",&y);
z = x;
if (y > z)
z == y;
printf("El mayor es %d\n", z);
system("PAUSE");
return 0;
}
• Por otra parte, una asignación toma el valor lógico de cierto si el valor almacenado
en la variable es distinto de “0” y toma el valor lógico de falso cuando es “0”. El
siguiente programa funciona de forma totalmente incorrecta, diciendo que los
números son distintos cuando “y” vale 0 y que son iguales en cualquier otro caso:
#include <stdio.h>
#include <stdlib.h>

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


{
int x, y;

printf("Introduce un numero: ");


scanf("%d",&x);
printf("Introduce otro numero: ");
scanf("%d",&y);
if (x = y)
printf("Los numeros son iguales.\n");
else
printf("Los numeros son distintos.\n");
system("PAUSE");
return 0;
}

• No se pone el carácter “;” después de la condición del if. El carácter “;”, en C, sirve
para finalizar sentencias. No tiene sentido ponerlo después de la condición porque,
necesariamente, hay que indicar al menos una instrucción a ejecutar en el caso de
que la condición se cumpla. Nuevamente, si se pone el “;” no siempre se produce un
error (sólo se producirá si hay un else asociado al if), si no que el programa no se
comporta como el alumno espera ya que poner un “;” tiene el significado asociado
de “sentencia vacía”. Supongamos estos dos programas, el primero dará error de
compilación, mientras que el segundo siempre imprimirá el valor de y:
#include <stdio.h>
#include <stdlib.h>

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


{
int x, y;

printf("Introduce un numero: ");


scanf("%d",&x);
printf("Introduce otro numero: ");
scanf("%d",&y);
if (x > y);
printf(“El mayor es %d\n”, x);
else
printf(“El mayor es %d\n”, y);
system("PAUSE");
return 0;
}

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

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


{
int x, y, z;

printf("Introduce un numero: ");


scanf("%d",&x);
printf("Introduce otro numero: ");
scanf("%d",&y);
z = x;
if (y > z);
z = y;
printf(“El mayor es %d\n”, z);
system("PAUSE");
return 0;
}

Se pueden anidar expresiones lógicas sencillas para construir otras más complejas
mediante el uso de los operadores lógicos “&&”, “||” y “!”. El siguiente programa, que
pregunta un mes y nos dice cuántos días tiene, es un ejemplo de uso de estos operadores
lógicos para construir expresiones lógicas complejas:

Figura 6: Aplicación que muestra el número de días de un mes

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

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


{
int mes;

printf("Escribe el mes (1-12): ");


scanf("%d", &mes);
if ((mes >= 1) && (mes <= 12))
if (mes == 2)
printf("El mes tiene 28 dias.\n");
else
if ((mes == 4) || (mes == 6) || (mes == 9) || (mes == 11))
printf("El mes tiene 30 dias.\n");
else
printf("El mes tiene 31 dias.\n");
else
printf("Ese mes no existe!\n");

system("PAUSE");
return 0;
}
Nuevamente, dos importantes aspectos a tener en cuenta:

• Salvo que se tenga muy clara la prioridad de los distintos operadores del lenguaje C,
es recomendable usar los paréntesis para agrupar subexpresiones. En el ejemplo
anterior, gran parte de los paréntesis no son necesarios (los que rodean las
comparaciones), pero incluso aunque se sea consciente de este hecho, ponerlos
ayuda a dar mayor claridad al código fuente.
• Al igual que pasaba con los operadores de comparación de igualdad y asignación,
los operadores “&&” y “||” pueden confundirse fácilmente con los operadores bit a
bit “&” y “|” respectivamente y no se producirá ningún error de compilación si no
que el programa puede no funcionar correctamente. El siguiente programa que
calcula el and bit a bit de dos números funciona incorrectamente (cuando el
resultado no es el número 0 imprime siempre 1) por culpa de utilizar “&&” en vez
de “&”:
#include <stdio.h>
#include <stdlib.h>

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


{
int x, y;

printf("Introduce un numero: ");


scanf("%d",&x);
printf("Introduce otro numero: ");
scanf("%d",&y);
printf("El \"and\" de los dos números vale %d\n", x && y);
system("PAUSE");
return 0;
}

“if (..) … else …” es la sentencia condicional más usada y más genérica en lenguaje C,
pero existe otra “switch (…) case…” que es interesante en determinadas circunstancias.
Cuando tenemos que mirar el valor de una variable de tipo entero y realizar distintas
acciones en función de dicho valor, usar “switch (…) case…” reduce un poco el tamaño
del código fuente y nos evita usar numerosos “if (..) … else …” encadenados. El
siguiente ejemplo, que calcula el área de una figura geométrica u otra en función de lo
seleccionado por el usuario, ilustra este comentario:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

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


{
char opcion;
float lado1, lado2, radio;

printf("Selecciona una figura para calcular el area (a, b o c):\n");


printf("\ta) Cuadrado.\n");
printf("\tb) Rectangulo.\n");
printf("\tc) Circulo.\n");
scanf("%c", &opcion);
switch (opcion)
{
case 'a':
printf("Introduce el lado del cuadrado: ");
scanf("%f", &lado1);
printf("El area vale %.3f\n", lado1 * lado1);
break;
case 'b':
printf("Introduce un lado del rectangulo: ");
scanf("%f", &lado1);
printf("Introduce el otro lado del rectangulo: ");
scanf("%f", &lado2);
printf("El area vale %.3f\n", lado1 * lado2);
break;
case 'c':
printf("Introduce el radio del circulo: ");
scanf("%f", &radio);
printf("El area vale %.3f\n", M_PI * radio * radio);
break;
default:
printf("Opcion no valida!\n");
}
system("PAUSE");
return 0;
}

Figura 7: Aplicación que calcula el área de un cuadrado, rectángulo o círculo

Observaciones:

• Para almacenar un carácter se usa una variable de tipo char, la cual se lee de teclado
/ imprime por pantalla usando el identificador de formato %c.
• Aunque char se usa para guardar caracteres, es en realidad un tipo de dato entero,
como lo pueda ser int, ya que una letra se representa por un número (código ASCII).
Por eso opcion puede ser un char. Sin embargo, opcion nunca podría ser un número
en punto flotante ni tampoco una cadena de caracteres (como se verá en el capítulo
correspondiente).
• Notar que para indicar que a, b y c son caracteres se rodean por comillas simples. Si
no se usan comillas tendremos un error porque el compilador pensará que son
variables, mientras que si usamos comillas dobles el error se deberá a que el
compilador pensará que son cadenas de caracteres.
• break se usa para salir del switch. Si no se pusiese, la siguiente sentencia que se
ejecutaría sería la escrita en la línea de abajo, aun perteneciendo a un case distinto.
Es, en este sentido, un caso muy particular de C que no se repite para ningún otro
tipo de sentencia. El uso de break en cualquier otra situación, está totalmente
desaconsejado y va en contra de los principios de la programación estructurada, al
romper la secuencia del código y dificultar su comprensión.
4. Bucles
En ocasiones es necesario realizar una misma operación sobre un conjunto de datos, o
repetir esa operación hasta que se cumpla una condición determinada. Un ejemplo de lo
primero, que veremos en el tema en el que se traten los arrays, sería sumar dos vectores.
Un ejemplo de lo segundo es el programa que veremos a continuación: cálculo del
factorial de un número. Para calcular el factorial de un número natural se multiplica éste
por ese mismo número menos 1, el resultado se vuelve a multiplicar por el número
menos dos y así sucesivamente hasta llegar a multiplicar por 1. Si desconocemos el
número del cual hay que calcular el factorial, desconocemos también cuántas
multiplicaciones habrá que hacer, con lo que es necesario utilizar una sentencia que nos
permita realizar la multiplicación mientras no lleguemos al 1. Este tipo de sentencias se
conocen como iterativas y en C disponemos de tres: while, do…while y for. A
continuación mostramos una posible implementación de un programa que resuelve este
problema. Por supuesto, existen muchas otras formas de implementarlo, algunas más
eficientes, pero esta nos permitirá observar claramente las diferencias entre los tres tipos
de sentencias iterativas de C.

Figura 8: Aplicación que calcula el factorial de un número

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

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


{
long int numero, factorial;

printf("Introduce un numero: ");


scanf("%ld", &numero);
if (numero < 0)
printf("No existe el factorial de un numero negativo!\n");
else
{
factorial = 1;
while (numero > 1)
{
factorial = factorial * numero;
numero = numero – 1;
}
printf("El factorial vale %ld\n", factorial);
}
system("PAUSE");
return 0;
}

Consideraciones:
• Las variables son esta vez de tipo long int. El estándar dice que el rango de números
representable por un long int es igual o mayor que el de un int. En las máquinas del
laboratorio, ambos tipos pueden representar un entero entre -2147483648 y
2147483647, pero otras arquitecturas o con otro compilador los rangos podrían ser
distintos. Puede parecer un rango suficiente, pero el programa, en realidad, sólo
funciona con números menores o iguales a 12. Para números mayores, no
obtendremos ningún error, si no un resultado incorrecto ya que el código generado
por el compilador no detecta situaciones de desbordamiento. Las variables deberían
ser, en realidad, double para tener un mayor rango de representación aunque los
números a almacenar sean enteros. Y aun así, para números no muy grandes, no
tendremos un resultado preciso.
• Al igual que con el if, y por las mismas razones, no se debe poner punto y coma
después de la condición del while.
• Cuando se realiza una operación aritmética con dos operadores donde uno de ellos
es a su vez la propia variable en la cual se va a almacenar el resultado, la asignación
y la expresión aritmética se pueden abreviar como muestra la siguiente tabla:

Sentencia Forma(s) abreviada(s)


x = x + y x += y
x = x – y x -= y
x = x * y x *= y
x = x / y x /= y
x = x + 1 x++ ++x
x = x – 1 x-- --x

En la tabla también se muestra que, en el caso particular de sumar uno o restar uno,
existen otras dos formas simplificadas de expresar lo mismo. Es muy importante
recalcar que hay una substancial diferencia semántica entre x++/x-- y ++x/--x. En el
primer caso, primero se utiliza el valor actual de la variable en la sentencia en la que
aparezca y luego se incrementa / decrementa ese valor. En el segundo, caso, primero
se incrementa / decrementa el valor y luego se utiliza el resultado en la sentencia. Se
entenderá mejor con el siguiente ejemplo:
#include <stdio.h>
#include <stdlib.h>

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


{
int x=5; Imprime 5
printf("%d\n", x);
x++;
printf("%d\n", x); Imprime 6
++x;
printf("%d\n", x);
printf("%d\n", x++); Imprime 7
printf("%d\n", ++x);
system("PAUSE");
return 0; Imprime 7
}

Imprime 9
Como consideración final al respecto de estas formas abreviadas de expresar algunas
operaciones aritméticas en C, decir que sólo se deben de usar si se está
absolutamente seguro de cómo funcionan, ya que se pueden crear sentencias poco
claras, especialmente utilizando ++ y -- en la misma sentencia conjuntamente con
otros operadores. Ej:

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

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


{
int x=5; Imprime 8
printf("%d\n", (x++) + (--x));
printf("%d\n", x);
system("PAUSE"); Imprime 5
return 0;
}

En este último ejemplo, el primer printf podría escribirse como printf("%d\n",


x+++--x); pero eso sería todavía mucho menos claro: aunque no sean necesarios,
deben utilizarse paréntesis siempre que se considere adecuado con el objetivo de
facilitar la compresión del código fuente.
Teniendo en cuenta todo esto, el ejemplo anterior del factorial puede escribirse de la
siguiente forma:

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

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


{
double numero, factorial;

printf("Introduce un numero: ");


scanf("%lf", &numero);
if (numero < 0)
printf("No existe el factorial de un numero negativo!\n");
else
{
factorial = 1;
while (numero > 1)
factorial *= numero--;
printf("El factorial vale %.0lf\n", factorial);
}
system("PAUSE");
return 0;
}

Notar como se ha utilizado %.0lf para imprimir un double sin decimales.


A continuación veremos cómo realizar el mismo programa utilizando las otras dos
sentencias iterativas de C. En este caso no se ha utilizado ningún tipo de abreviatura en
las operaciones como en el caso anterior:
#include <stdio.h>
#include <stdlib.h>

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


{
double numero, factorial;

printf("Introduce un numero: ");


scanf("%lf", &numero);
if (numero < 0)
printf("No existe el factorial de un numero negativo!\n");
else
{
factorial = 1;
if (numero > 1)
do
{
factorial = factorial * numero;
numero = numero - 1;
}
while (numero > 1);
printf("El factorial vale %.0lf\n", factorial);
}
system("PAUSE");
return 0;
}
Inicialización
#include <stdio.h>
#include <stdlib.h>

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


{ Condición
double numero, factorial;

printf("Introduce un numero: ");


scanf("%lf", &numero); Actualización
if (numero < 0)
printf("No existe el factorial de un numero negativo!\n");
else
{
for (factorial = 1; numero > 1; numero=numero-1)
factorial = factorial * numero;
printf("El factorial vale %.0lf\n", factorial);
}
system("PAUSE");
return 0;
}

Consideraciones al respecto de la versión con do…while():

• Ahora sí hay un punto y coma después de la condición, ya que en este caso la


condición es la última parte de una sentencia do…while(), a diferencia del while() en
el cual al menos una sentencia tiene que seguir a la condición.
• Las sentencias dentro de un do…while() se ejecutan al menos una vez. Esto es una
diferencia con el while(), en el cual puede darse el caso de que nunca se ejecuten.
Esto es así porque en el while() la condición se evalúa antes de ejecutar el contenido
del bucle, mientras que en el do…while() se evalúa después. Por esto, y para que el
programa funcione correctamente en el caso de que el número introducido por el
usuario sea el 0, hemos tenido que añadir un if adicional y poner el do…while()
dentro de ese if. Por lo general, sólo se usa do…while() cuando sabemos a ciencia
cierta que el contenido del bucle debe ejecutarse siempre al menos una vez.

Consideraciones al respecto de la versión con for():

• for() permite escribir de forma más compacta los bucles while(). De hecho, se puede
establecer la siguiente equiparación entre for() y while():

A
while(B)
{ for (A; B; D)
C C
D
}

• Las partes de inicialización y actualización pueden ser mucho más complejas e


incluir varias sentencias separadas por comas, pero hay que tener cuidado de no
abusar de esa característica, ya que puede producir código fuente muy confuso.

Cualquier bucle puede implementarse con cualquiera de las tres sentencias iterativas,
pero while() tiene la sintaxis más clara y fácil de entender, con lo que, si no se está
seguro de la sintaxis de las otras sentencias, recomiendo optar por el uso de while().

Aunque no se ha comentado explícitamente, un programa puede contener cualquier tipo


de sentencia dentro de una sentencia condicional o iterativa. Es decir, una sentencia
condicional puede contener en su interior otras sentencias condicionales o sentencias
iterativas. Igualmente, una sentencia iterativa puede contener sentencias condicionales o
más sentencias iterativas. El siguiente ejemplo corresponde a un programa en el que se
pide al usuario realizar un programa que lea un número positivo, n, por teclado y
muestre por pantalla todos los números perfectos entre 1 y n:

NOTA: Un número es perfecto cuando es igual a la suma de sus divisores excepto él


mismo.
Ejemplo: ¿Es el número 6 perfecto?
Los divisores de 6 son: 3, 2 y 1.
3+2+1=6 por tanto 6 es un número perfecto:
#include <stdio.h>
#include <stdlib.h>

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


{
int numero, contador, suma, candidato;

printf("Introduce un numero: ");


scanf("%d", &numero);
if (numero < 1)
printf("El numero ha de ser un entero positivo\n");
else
{
printf("Los numeros perfectos entre 1 y %d son: ", numero);
for (candidato = 1; candidato <= numero; candidato++)
{
suma = 0;
for (contador = 1; contador <= candidato; contador++)
if ((candidato % contador) == 0)
suma += contador;

if (suma == candidato)
printf("%d ", candidato);
}
printf("\n");
}
system("PAUSE");
return 0;
}

Como se puede ver en el código anterior, se ha utilizado una sentencia iterativa for que
recorre los números entre 1 y el número máximo que ha introducido el usuario, dentro
de la cual hay otra sentencia iterativa for (a este procedimiento se le denomina anidar
bucles) que recorre los números entre 1 y el número máximo que marca, en este caso, el
bucle más exterior. Es importante hacer notar que la variable suma debe ser puesta a
cero en cada iteración del bucle más exterior (esto es un error común a la hora de
trabajar con variables que acumulan resultados parciales).

A continuación mostramos una versión más optimizada del programa anterior, donde se
ha reducido el número de iteraciones que deben hacer ambos bucles for teniendo en
cuenta que el caso en el que las variables valen 1 puede ser incluido directamente en la
variable suma y, por otro lado, que en el bucle interior no es necesario recorrer los
números hasta el valor de la variable candidato ya que a partir de candidato/2 ya no
pueden ser múltiplos:
#include <stdio.h>
#include <stdlib.h>

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


{
int numero, contador, suma, candidato, mitad_candidato;

printf("Introduce un numero: ");


scanf("%d", &numero);
if (numero < 1)
printf("El numero ha de ser un entero positivo\n");
else
{
printf("Los numeros perfectos entre 1 y %d son: ", numero);
for (candidato = 2; candidato <= numero; candidato++)
{
mitad_candidato = candidato / 2;
for (contador = 2, suma = 1; contador <= mitad_candidato; contador++)
if ((candidato % contador) == 0)
suma += contador;
if (suma == candidato)
printf("%d ", candidato);
}
printf("\n");
}
system("PAUSE");
return 0;
}

Es altamente recomendable que, tras realizar una versión inicial que funcione de un
programa, se dedique un tiempo a tratar de optimizar el código reduciendo el número de
operaciones que se llevan a cabo (en sentencias iterativas normalmente). Este proceso
depende del algoritmo que se deba implementar en cada caso.
5. Funciones
A medida que los problemas a resolver mediante un programa en C se hacen más
complicados, el código se convierte cada vez en más difícil de estructurar y, sobre todo,
de comprender dado el gran número de líneas que lo componen. Una solución consiste
en descomponer los problemas en otros más sencillos que se puedan analizar y
programar de forma independiente. A cada uno de estos problemas más sencillos se les
denomina módulo, y a este tipo de programación, programación modular. En lenguaje
C, los módulos se denominan funciones y es posible establecer la similitud con las
funciones matemáticas que disponen de variables sobre las que operan para obtener un
resultado.

Para dividir un programa en funciones, hay que distinguir las partes que tienen alguna
independencia. Después se intenta dividir estas partes en otras más pequeñas y así
sucesivamente, hasta llegar a fragmentos lo suficientemente simples. Esta forma de
proceder se conoce como diseño Top-Down.

Un programa diseñado mediante la técnica de programación modular está formado por:


• El programa principal, que describe la solución completa del problema y consta
fundamentalmente de llamadas a funciones. El programa principal debe constar de
pocas líneas y en él deben estar claros los diferentes pasos del proceso que se
seguirá para obtener el resultado.
• Las funciones, que tienen una estructura similar a la de un programa (declaración
de variables, programación estructurada y resultado). El objetivo de cada función
es resolver de modo independiente una parte del problema y sólo serán ejecutadas
cuando sean llamadas por el programa principal o por otras funciones. Las
funciones, para preservar sus fundamentos, deben ser pequeñas, claras y sencillas.

A cada función de un programa en C se le debe asignar un nombre significativo con la


tarea que lleva a cabo. Por ejemplo, si necesitamos una función que calcule la suma de
dos números, un nombre adecuado sería calculaSuma. Una buena metodología a la hora
de asignar nombres a las funciones consiste en que empiece por minúscula y, en caso de
constar de varias palabras, éstas se escriban pegadas unas a las otras y con su primera
letra mayúscula.

Cuando en un punto de un programa se llama a una función, se ejecutarán todas las


instrucciones de la misma, y una vez finalizada, se vuelve al punto del programa desde
donde se llamó dicha función.

Las funciones pueden (y suelen) recibir parámetros que se declaran a la derecha del
nombre de la función y entre paréntesis. Además, suelen devolver un resultado que
deberá ser tratado en el punto del programa desde el que se llamó a la función.

La forma más clara de entender la utilidad de la programación modular y el envío y


recepción de parámetros es mediante un ejemplo. A continuación mostramos un
programa en C que calcula todas las posibles combinaciones (sin repetición) de N
elementos tomados de k en k según la fórmula:
( )= k!( NN−! k )!
N
k

Por ejemplo, las combinaciones de 4 elementos tomados de 3 en 3 se calcularían:

( )= 3!(44−! 3)! = 4
4
3

NOTA: tanto N como k deben ser mayores que 0

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

long int calcularFactorial(int numero)


{
long int factorial;
int i;

if (numero<2)
factorial=1;
else
{
factorial=numero;

for (i = factorial - 1; i > 1; i--)


factorial *= i;
}
return factorial;
}

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


{
int n,k;
long int combinaciones;

printf("Introduzca el valor de N: ");


scanf("%d", &n);
if (n<=0)
printf(“N no puede ser negativo ni nulo \n”);
else //n > 0
{
printf("Introduzca el valor de k: ");
scanf("%d", &k);
if (k<=0)
printf("k no puede ser negativo ni nulo\n”);
else //(k>0)
{
if (n-k<0)
printf("N tiene que ser mayor o igual que k \n");
else
{
combinaciones=calcularFactorial(n)/
(calcularFactorial(k)*calcularFactorial(n-k));
printf("\nEl numero de combinaciones de %i elementos "
"tomados de %i en %i es: %li\n",n,k,k,combinaciones);
}
}//fin else k>0
}//fin else n>0

system("PAUSE");
return 0;

}//fin del main

La fórmula utilizada para resolver el ejercicio implica realizar 3 veces el cálculo del
factorial. Sin programación modular, el código necesario en este caso repetiría 3 veces
las mismas instrucciones y esto es, obviamente, muy ineficiente. Sobre la base de la
programación modular, hemos creado una función (denominada calcularFactorial) que
incluye las líneas de código que calculan dicho factorial una única vez en la totalidad
del programa.

Consideraciones sobre el programa anterior:

• La estructura de la función calcularFactorial es idéntica a la del main(), con una


cabecera que incluye el nombre (significativo de lo que hace la función) y el
código entre llaves.
• En el main(), tras la comprobación de errores en los valores de N y k, se ejecuta la
instrucción: combinaciones=calcularFactorial(n)/
(calcularFactorial(k)*calcularFactorial(n-k));
donde se llama a la función calcularFactorial tres veces (una en el numerador y
dos en el denominador). En cada una de estas llamadas, entre paréntesis aparece un
parámetro (n, k y n-k). Cada uno de estos parámetros se pasa a la función, que
deberá realizar sus operaciones a partir de estos valores. En el caso de sea necesario
pasar más de un parámetro a una función, deberán estar separados por comas y
dentro de los paréntesis.
La función calcularFactorial devuelve, tras su ejecución, el factorial del número
que se le pasa como parámetro. Es necesario, por tanto, almacenar en una variable
o crear una expresión que utilice el dato devuelto (en este ejemplo, primeramente
se resuelve la expresión matemática y el resultado se almacena en la variable
combinaciones).
• La declaración de la función tiene la siguiente sintaxis:
long int calcularFactorial(int numero)
Entre paréntesis aparece la única variable que la función recibe como parámetro, en
este caso almacenada en int numero. Como vemos, los parámetros que recibe una
función se declaran como una variable más, indicando el tipo de variable y su
nombre. Dicho nombre no tiene por qué coincidir con el nombre utilizado por el
parámetro en el punto en que se llama a la función. El tipo sí debe coincidir con el
tipo de dato que se pasa. Obsérvese que en las llamadas a la función
calcularFactorial del programa anterior, las dos primeras veces se le pasa una
variable de tipo int (n y k ) y la ultima se le pasa una expresión, cuyo resultado
también es de tipo int (n-k). Si una función recibe varios parámetros, éstos
deberán estar separados por comas.
Por otro lado, previo al nombre de la función aparece de nuevo long int, que
indica el tipo de dato que devuelve (en este caso) la función. Los tipos de datos que
puede devolver una función son todos los existente en C y en el que caso de que no
devuelva nada, se utiliza el tipo especial void. Para devolver un parámetro se utiliza
la sentencia return. Por este motivo, en el programa anterior se ha declarado la
variable factorial de tipo long int dentro de la función y es la variable que se
devuelve en el return.
• Vemos que la función calcularFactorial se sitúa antes del main(). Esto no tiene por
qué ser siempre así, lo importante es que en el punto del main() desde donde se
llame, dicha función debe ser conocida. Es decir, si está declarada antes del main(),
será conocida en cualquier punto del mismo, pero si se declara debajo del main(),
es necesario situar el prototipo de la función (en este caso long int
calcularFactorial(int numero) seguido de punto y coma) antes del main().
6. Punteros (paso por referencia)
Un puntero no es más que un tipo de variable que almacena direcciones de memoria. La
dirección de memoria almacenada normalmente indica la posición en la que está situada
otra variable. Así, decimos que la primera variable apunta a la segunda. Realmente, una
variable de tipo puntero puede apuntar a todo tipo de objetos que residan en memoria,
por ejemplo, constantes, funciones, otros punteros, etc.

Los punteros se declaran como cualquier otro tipo de variable, eso sí, con un
identificador especial *. Por ejemplo, un puntero de nombre punt y que apunta a una
variable de tipo double, se declara
double *punt;

Esta declaración implica la reserva de una zona de memoria donde se guardará la


dirección de memoria de una variable de tipo double, es decir, punt apuntará al primer
byte de una zona de 8 bytes.

Para trabajar con punteros tenemos dos operadores básicos:

• El operador & que devuelve la dirección de memoria en la que se encuentra


almacenada la variable sobre la que se aplica. Por ejemplo, para que punt apunte a
una variable a, de tipo double, tendríamos que incluir la sentencia:

punt = &a;

de tal forma que punt guardaría la dirección de memoria en que se encuentra a,


pero no el valor numérico de a.

• El otro operador básico es *, que es complementario a &, ya que devuelve el valor


contenido en la dirección que almacena la variable sobre la que se aplica *. Es
decir, después de la asignación anterior, podríamos acceder al valor numérico de a
de dos formas:
printf(“%lf”,a);

que es la forma habitual que ya conocemos

printf(“%lf”,*punt);

que devolvería el mismo resultado utilizando el contenido de la variable a la que


apunta punt.

El siguiente programa es un buen ejemplo del manejo básico de punteros:


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

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


{
double a;
double *punt;
a=15.6;
punt=&a;

printf("\nValor de punt=%p\n",punt);
printf("\nDireccion de memoria de la variable a=%p\n",&a);
printf("\nValor de a=%lf\n",a);
printf("\nValor al que apunta punt=%lf\n",*punt);

a=20.1;
printf("\nValor de a=%lf\n",a);
*punt=36.5;
printf("\nValor de a=%lf\n\n",a);

system("PAUSE");
return 0;
}

Cuya ejecución proporciona la siguiente salida por pantalla:

Vemos cómo la dirección de memoria que almacena punt es la de a, de modo que los
cambios que se realizan sobre *punt afectan al contenido de a.

Paso por referencia

La principal razón para la utilización de los punteros en C, viene de la necesidad de que


una función pueda modificar varios parámetros. El ejemplo que vimos en el apartado
anterior en el que se calculaba las posibles combinaciones (sin repetición) de N
elementos tomados de k en k y que requería de tres llamadas a la función que calcula el
factorial, es un ejemplo típico de paso de parámetros por valor. En aquel caso, la
función recibía un único parámetro (int numero) del que se realizaba una copia local en
la función, es decir, la variable n del main() no se veía modificada. Además, en dicha
función, únicamente se devolvía un parámetro.
En cambio, muchas veces es necesario que una función devuelva varios parámetros y,
en este caso, es necesario el uso de punteros. La idea básica consiste en enviar como
parámetro desde el main() la dirección de memoria de las variables que deben ser
modificadas, y la función las almacena en punteros. A esta forma de pasar parámetros se
le denomina paso por referencia e implica que no se realizan copias de los parámetros
locales a la función.

Por ejemplo, en el siguiente programa, dados tres números se pide crear una función
que calcule el menor, el mayor y la media de dichos números:

#include "stdio.h"
#include "stdlib.h“

/* Funcion que dados tres numeros, devuelve el menor, mayor y su media */


void calcularMedia(float *num1, float *num2, float *num3)
{
float min,max;

if (*num1<*num2)
{
//Se comprueba cual es menor
if (*num1<*num3)
min=*num1;
else
min=*num3;
//Se comprueba cual es mayor
if (*num3<*num2)
max=*num2;
else
max=*num3;
}
else //num2 < num1
{
//Se comprueba cual es menor
if (*num2<*num3)
min=*num2;
else
min=*num3;
//Se comprueba cual es mayor
if (*num3<*num1)
max=*num1;
else
max=*num3;
}

*num3=(*num1+*num2+*num3)/3;
*num1=min;
*num2=max;
}

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


{
float num1,num2,num3;

printf("Introduzca el primer numero");


scanf("%f",&num1);
printf("Introduzca el segundo numero ");
scanf("%f",&num2);
printf("Introduzca el tercer numero ");
scanf("%f",&num3);

calcularMedia(&num1,&num2,&num3);

printf(“El menor es: %6.3f\nEl mayor es: %6.3f\n”


“Y la media es: %6.3f\n”, num1,num2,num3);

system("PAUSE");
return 0;
}

Queda claro en este ejemplo la necesidad del uso de punteros, ya que así la función
calcularMedia trabaja sobre las variables num1, num2 y num3 del main() y no sobre
copias. De esta forma, las modificaciones que se hacen sobre *num1, *num2 y *num3
afectan a las variables originales (como se puede ver en el último printf del main() que
muestra el contenido de num1, num2 y num3 modificadas en la función). Es importante
resaltar el uso ya explicado del operador & a la hora de mandar las direcciones de las
variables num1, num2 y num3 a la función, que son recogidas, obviamente, por tres
punteros *num1, *num2 y *num3.

El siguiente código es otro ejemplo de paso de variables por referencia y consiste en un


programa que debe sumar dos fracciones (positivas y distintas de cero) introducidas por
el usuario. Cada vez que se introduce una fracción se debe simplificar (si no lo está) y el
resultado de la suma también debe estar simplificado. Para ello, se debe calcular el
máximo común divisor mediante la siguiente función (algoritmo de Euclides):

int mcd(int a, int b)


{
while(a!=b)
{
if (a<b)
b=b-a;
else
a=a-b;
}
return a;
}
A continuación mostramos el resultado de la ejecución del programa y su código:

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

/*Funcion que calcula el maximo comun divisor de dos numeros


usando el algoritmo de Euclides*/

int mcd(int a, int b)


{
while(a!=b)
{
if (a<b)
b=b-a;
else
a=a-b;
}
return a;
}

/* Devuelve 1 en el caso de que no se pueda simplificar la fraccion */


int simplificarFraccion(int *numerador, int *denominador)
{
int maximo;

maximo=mcd(*numerador,*denominador);
*numerador=*numerador/maximo;
*denominador=*denominador/maximo;

return maximo; //el mcd es uno si no hay divisor comun


}

/*Suma dos fracciones y sobreescribe las variables suma*/


void sumarFracciones(int n1, int d1, int n2, int d2, int *num_suma, int
*den_suma)
{
//numerador
*num_suma=n1*d2+n2*d1;
//denominador
*den_suma=d1*d2;
}

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


{
int num1,num2,num_suma,den1,den2,den_suma,entero;

do {
printf("Introduzca el numerador de la primera fraccion: ");
scanf("%i",&num1);
if (num1 <= 0)
printf("El numerador debe ser positivo y mayor que cero\n");
} while (num1 <= 0);

do {
printf("Introduzca el denominador de la primera fraccion: ");
scanf("%i",&den1);
if (den1 <= 0)
printf("El denominador debe ser positivo y mayor que cero\n");
} while (den1 <= 0);

entero = simplificarFraccion(&num1,&den1);

if (entero != 1) //si se pudo simplificar


printf("La primera fraccion simplificada es %i/%i\n",num1,den1);

do {
printf("Introduzca el numerador de la segunda fraccion: ");
scanf("%i",&num2);
if (num2 <= 0)
printf("El numerador debe ser positivo y mayor que cero\n");
} while (num2 <= 0);

do {
printf("Introduzca el denominador de la segunda fraccion: ");
scanf("%i",&den2);
if (den2 <= 0)
printf("El denominador debe ser positivo y mayor que cero\n");
} while (den2 <= 0);

entero = simplificarFraccion(&num2,&den2);

if (entero != 1) //si se pudo simplificar


printf("La segunda fraccion simplificada es: %i/%i\n",num2,den2);

sumarFracciones(num1,den1,num2,den2,&num_suma,&den_suma);

//Simplificacion de la matriz resultante


simplificarFraccion(&num_suma,&den_suma);

if (den_suma == 1) //si no es una fraccion


printf("El resultado de sumar %i/%i + %i/%i es: %i\n"
,num1,den1,num2,den2,num_suma);
else
printf("El resultado de sumar %i/%i + %i/%i es %i/%i\n"
,num1,den1,num2,den2,num_suma,den_suma);

system("PAUSE");
return 0;
}

En este caso, la función simplificarFraccion requiere dos parámetros de entrada que


deben ser sobrescritos y dicha función debe ser aplicada 3 veces en el programa. Por
otro lado, la función sumarFracciones mezcla paso por referencia y paso por valor, ya
que requiere el paso de 4 parámetros por valor (num1, num2, den1 y den2) de los que
realiza copia local y no debe modificar, y además requiere las variables num_suma y
den_suma por referencia, ya que deben ser sobrescritas con el resultado de la suma.
7. Estructuras
Una estructura es un tipo de dato definido por el usuario y que está compuesto por datos
de tipos diferentes agrupados bajo un mismo nombre. Las estructuras ayudan a
organizar datos complicados, particularmente en programas grandes, ya que permiten
tratar como una unidad a un conjunto de variables relacionadas, en lugar de tratarlas
como entidades independientes.

Una estructura se define en C a través de la siguiente sintaxis:


struct Nombre
{
tipo1 Campo1;
tipo2 Campo2;
...
tipoN CampoN;
};

La instrucción:

struct Nombre Var1;

Declara una variable del tipo "struct Nombre", esto es, el compilador reserva la cantidad
de memoria sufuciente para mantener la estructura íntegra (es decir espacio para
almacenar Campo1, Campo2, ..., CampoN). Cuando se hace referencia a la variable
Var, se esta haciendo referencia a la estructura íntegra.

Se puede inicializar una estructura externa o estática añadiendo a su definición la lista


de inicializadotes, por ejemplo:
struct Fecha
{
int Dia;
char *Mes;
int Anio;
};

struct Fecha Hoy = {8,"Mayo",2008},


VarFecha;
...
VarFecha = Hoy;

La asignación VarFecha = Hoy copia la estructura integra Hoy en VarFecha. Cuando


dentro de los campos de una estructura aparecen punteros y uno realiza este tipo de
asignación, se esta copiando también los valores de los punteros, de tal manera que se
puede estar haciendo referencia a un dato desde dos puntos diferentes lo que puede
causar efectos no deseados y un potencial peligro para la aplicación.

Un campo de una estructura se utiliza como una variable más. Para referenciar un
campo de una estructura se emplea el operador .
Hoy.Dia = 24;
Hoy.Mes = "Agosto";
Hoy.Anio = 1991;
Ejemplo para verificar año bisiesto:
Bisiesto = Fecha.Anio%4 == 0 && Fecha.Anio%100 != 0 || Fecha.Anio%400 == 0;

El uso de estructuras permite que una función modifique y devuelva más de un tipo de
dato, ya que lo que se devuelve es la estructura. De cara a ilustrar esta posibilidad, a
continuación mostramos un programa que calcula el área y la longitud de un círculo a
partir del radio del mismo que es pedido al usuario por teclado usando, inicialmente,
paso de argumentos por referencia:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void circulo(float radio, float *p_area, float *p_circunferencia)


{
*p_area = M_PI * radio * radio;
*p_circunferencia = 2.0 * M_PI * radio;
}

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


{
float radio, area, circunferencia;

printf(”Introduce el radio: ");


scanf("%f", &radio);
circulo(radio, &area, &circunferencia);
printf("Area: %.2f Circunferencia: %.2f\n", area, circunferencia);

system("PAUSE");
return 0;
}

El mismo programa usando una estructura sería:


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

struct s_circulo
{
float area;
float circunferencia;
};

struct s_circulo f_circulo(float radio)


{
struct s_circulo circulo;

circulo.area = M_PI * radio * radio;


circulo.circunferencia = 2.0 * M_PI * radio;
return circulo;
}

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


{
float radio;
struct s_circulo circulo;

printf(”Introduce el radio: ");


scanf("%f", &radio);

circulo = f_circulo(radio);
printf("Area: %.2f Circunferencia: %.2f\n", circulo.area,
circulo.circunferencia);
system("PAUSE");
return 0;

En el programa anterior hemos utilizado una función que devuelve un dato de tipo
estructura struct s_circulo.
8. Arrays
En ocasiones es interesante almacenar en memoria varios datos del mismo tipo sobre los
que se realizará algún tipo de operación común. Por ejemplo, los elementos de un
vector: si nuestro programa necesita almacenar en memoria un vector, casi seguro que
las operaciones que haga con él afectarán a todos sus elementos (ya sea leerlos,
imprimirlos por pantalla, compararlos, etc.). Es para esta necesidad de almacenar en
memoria un conjunto de datos del mismo tipo para lo que se usan los arrays. Un array
es, por tanto, una variable que contiene múltiples datos del mismo tipo y que podremos
tratar, bien de forma conjunta, bien elemento a elemento, según las necesidades del
programa.

A continuación veremos un ejemplo de programa que emplea un array. Este programa


debe leer N números por teclado (donde N le será preguntado al usuario), almacenarlos
en un array, sumar los números positivos almacenados en las posiciones pares del array
y mostrar el resultado por pantalla con dos decimales:

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

#define SIZE 256

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


{
int n, i;
float array[SIZE], suma;

printf("Cuantos numeros vas a introducir?: ");


scanf("%d", &n);
if ((n < 1) || (n > SIZE))
printf("Tienes que indicar un numero positivo y menor que %d.\n", SIZE);
else
{
printf("A continuacion introduce los numeros separados por espacios en\n"
"blanco o por retornos de carro:\n");
suma = 0;
for (i = 0; i < n; i++)
{
scanf("%f", &array[i]);
if (((i % 2) == 0) && (array[i] > 0))
suma += array[i];
}
printf("El resultado de la suma de los numeros positivos\n"
"almacenados en posicion par es: %.2f\n", suma);
}
system("PAUSE");
return 0;
}

Consideraciones:
• En este ejercicio tenemos ejemplo de todo lo visto hasta ahora: E/S, constantes,
sentencias condicionales, condiciones compuestas, sentencias iterativas y arrays.
Recuerda que:
• Las constantes, aunque no es obligatorio, se suelen declarar en mayúsculas.
• Cuando se leen datos con scanf se debe poner un & delante de cada variable en
la que se vaya a guardar un dato, porque scanf necesita saber la dirección de
memoria de esas variables.
• En una expresión lógica, || significa “o” y && significa “y”.
• El operador % significa “módulo” y se usa, por tanto, para saber cuál es el resto
de una división entre dos números enteros.
• No confundas el operador de asignación “=” con el operador de comparación de
igualdad “==”.
• Se pueden concatenar cadenas de caracteres simplemente cerrando una con
comillas y abriendo la siguiente con otras comillas, aunque estén en líneas
distintas. Lo que no se puede hacer es crear una cadena de caracteres en una
línea y continuarla en la siguiente.
• Un array, como cualquier otra variable, es necesario declararlo. En este caso, a la
derecha del nombre, se pone entre corchetes el número de elementos que tiene el
array. Una vez se ha declarado, C no permite cambiar el número de elementos del
array.
• El usuario debe especificar con cuántos números desea trabajar, pero dado que no
hemos visto asignación dinámica de memoria y tenemos que declarar el array antes
de usarlo, debemos crear un array sobredimensionado y controlar que el usuario no
especifica un número de elementos mayor que el tamaño del array.
• Recuerda que para acceder a un elemento concreto del array se pone el nombre de
éste y, entre corchetes, el índice del elemento al que queremos acceder, que será un
número entero entre 0 y N-1 donde N es el número de elementos del array. El índice
puede ser una constante, una variable o incluso una expresión, siempre y cuando el
resultado final de evaluarla sea un número entero. En el programa que acabamos de
ver se usa una variable, i, que va tomando todos los valores desde 0 hasta n-1
porque nos interesa recorrer todos los elementos del array.
• Salvo el caso particular de los arrays de caracteres (strings) que veremos más
adelante, no existen elementos en el lenguaje C ni tampoco funciones que nos
permitan tratar los arrays con una sola instrucción. Es decir, no podemos leer,
escribir, sumar, etc. arrays con una instrucción, si no que tenemos que hacerlo
elemento a elemento.
En cuanto al uso de arrays y funciones, como vimos en el tema anterior, el paso por
referencia y, consecuentemente, el uso de punteros es necesario en C cuando una
función debe modificar varias variables. Por este motivo, en el caso de pasar un array a
una función, siempre se realiza por referencia, por lo que no es necesario devolver dicho
array desde la función al estar modificando directamente el array original (no es
necesario el return).

A continuación mostramos el código del ejemplo anterior realizado ahora usando una
función par_positivo que calcula la suma:
#include <stdio.h>
#include <stdlib.h>

#define SIZE 256

float par_positivo (float array[SIZE], int numeros)


{
float sum = 0;
int i;

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


{
if (((i % 2) == 0) && (array[i] > 0))
sum += array[i];
}

return sum;
}

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


{
int n, i;
float array[SIZE], suma;

printf("Cuantos numeros vas a introducir?: ");


scanf("%d", &n);
if ((n < 1) || (n > SIZE))
printf("Tienes que indicar un numero positivo y menor que %d.\n", SIZE);
else
{
printf("A continuacion introduce los numeros separados por espacios en\n"
"blanco o por retornos de carro:\n");

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


scanf("%f", &array[i]);

suma = par_positivo(array, n);

printf("El resultado de la suma de los numeros positivos\n"


"almacenados en posicion par es: %.2f\n", suma);
}

system("PAUSE");
return 0;
}

Como vemos, el paso del array a la función es simple y podría parecer que se está
haciendo paso por valor ya que no hay distinción respecto a cómo se envía la variable n,
pero como hemos dicho anteriormente, el paso de arrays es siempre por referencia. El
nombre de un array es realmente un puntero a la dirección de memoria donde empieza
el array. Este es el motivo por el cual en este ejemplo no es necesario un return de
matriz, al ser un paso por referencia se está trabajando siempre sobre la matriz del
main(). De hecho, en el siguiente programa mostramos la forma más común de pasar
arrays a funciones utilizando un puntero como argumento:
#include <stdio.h>
#include <stdlib.h>

#define SIZE 256

float par_positivo (float *array, int numeros)


{
float sum = 0;
int i;

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


{
if (((i % 2) == 0) && (array[i] > 0))
sum += array[i];
}

return sum;
}

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


{
int n, i;
float array[SIZE], suma;

printf("Cuantos numeros vas a introducir?: ");


scanf("%d", &n);
if ((n < 1) || (n > SIZE))
printf("Tienes que indicar un numero positivo y menor que %d.\n", SIZE);
else
{
printf("A continuacion introduce los numeros separados por espacios en\n"
"blanco o por retornos de carro:\n");

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


scanf("%f", &array[i]);

suma = par_positivo(array, n);

printf("El resultado de la suma de los numeros positivos\n"


"almacenados en posicion par es: %.2f\n", suma);
}

system("PAUSE");
return 0;
}

Hemos resaltado la única diferencia respecto a la versión anterior, que consiste en que
ahora el array se recibe como puntero. La función par_positivo no copia localmente el
contenido del array, sino que trabaja con un puntero que apunta al array creado desde el
main(). La declaración de los arrays unidimensionales como punteros dentro de las
funciones es la ideal.

Ordenación

En cualquier lenguaje de programación es muy común encontrarse con la necesidad de


ordenar un conjunto de datos en función de un criterio de orden o alfabético. En
concreto, es importante conocer el funcionamiento básico de los algoritmos de
ordenación ascendente o descendente de datos numéricos almacenados en un array.
Existen distintas formas de ordenar dichos datos, que se diferencian mucho en cuanto a
su rapidez y eficiencia derivadas del número de operaciones que realizan.
De cara a ilustrar el funcionamiento básico de un algoritmo de ordenación, a
continuación mostramos el denominado método de la burbuja para ordenar, en este
caso, de menor a mayor un array de números enteros. Este método es uno de los más
sencillos y, a la vez, uno de los más ineficientes:

#include <stdio.h>
#include <stdlib.h>
#define MAX 100

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


{
int numelementos, i, j, vector[MAX];

printf("\nNumero de elementos del vector:");


scanf("%i", &numelementos);

if (numelementos <= 0 || numelementos>MAX)


printf("\nNumero de elementos incorrectos. Maximo = %i", MAX);
else
{
for (i=0; i<numelementos; i++)
{
printf("\nIntroduzca el elemento %i: ", i);
scanf("%i", &vector[i]);
}

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


{
for (j=i+1; j<numelementos; j++)
if (vector[j] < vector[i])
{
aux = vector[j];
vector[j] = vector[i];
vector[i] = aux;
}
}
printf("\nVector ordenado = ");
for (i=0; i<numelementos; i++)
printf("%i ", vector[i]);
}
system("PAUSE");
return 0;
}

Como vemos, en este algoritmo se utiliza un índice i para señalar el elemento que se
toma como referencia en la comparación (bucle exterior) y otro índice j cuyo valor
inicial es i+1 para señalar el elemento que se compara cada vez (bucle interior). De esta
forma, una vez que un elemento señalado por j es menor que el de referencia señalado
por i, simplemente se intercambian. Por este motivo, el método de la burbuja se engloba
dentro de los algoritmos de intercambio, ya que su filosofía de funcionamiento se basa
en la realización de sucesivos intercambios de valores. De hecho, el mayor defecto de
este algoritmo es el elevado número de intercambios innecesarios que se llevan a cabo.
Por ejemplo, si el array que proporciona el usuario estuviese ordenado de mayor a
menor (caso opuesto a lo que queremos obtener), el programa anterior realizaría
continuos intercambios ya que cualquier número es siempre menor que el que se toma
como referencia.

Un método de ordenación más eficiente, que se obtiene como una variación simple del
anterior, es el denominado método de selección directa donde, cada vez que se
encuentra un número menor que el de referencia señalado por el índice i, se almacena
dicho número y su posición en un par de variables y, tras finalizar el bucle en j, se lleva
a cabo un único intercambio. El siguiente código muestra este método aplicado a la
ordenación de un array, de nuevo de menor a mayor:

#include <stdio.h>
#include <stdlib.h>
#define MAX 100

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


{
int numelementos, aux, i, j, k, vector[MAX];

printf("\nNumero de elementos del vector:");


scanf("%i", &numelementos);

if (numelementos <= 0 || numelementos>MAX)


printf("\nNumero de elementos incorrectos. Maximo = %i", MAX);
else
{
for (i=0; i<numelementos; i++)
{
printf("\nIntroduzca el elemento %i: ", i);
scanf("%i", &vector[i]);
}

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


{
aux = vector[i];
k = i;
for (j=i+1; j<numelementos; j++)
if (vector[j] < aux)
{
aux = vector[j];
k = j;
}

if (k != i)
{
vector[k] = vector[i];
vector[i] = aux;
}
}
printf("\nVector ordenado = ");
for (i=0; i<numelementos; i++)
printf("%i ", vector[i]);
}

system("PAUSE");
return 0;
}

Por tanto, respecto al método de la burbuja se han añadido únicamente dos variables:

 aux: que almacena temporalmente el menor valor en cada iteración del bucle i
 k: que almacena temporalmente la posición del menor valor en cada iteración del
bucle i

Con esta simple modificación, el método de selección directa se vuelve mucho más
eficiente que el de la burbuja y, como dijimos antes, en el caso extremo de que el array
inicial esté ordenado de mayor a menor, realizaría únicamente tantos intercambios como
elementos tenga el array. Un ejercicio interesante es comprobar este incremento de
eficiencia guardando e imprimiendo una variable contador que aumente su valor en una
unidad cada vez que se lleva a cambio un intercambio en un método y en otro.

Para finalizar este apartado dedicado a los algoritmos de ordenación simples, decir
simplemente que para ordenar de mayor a menor en los ejemplos anteriores, únicamente
sería necesario cambiar el signo de comparación del primer if de < a >. De hecho, a
continuación mostramos un programa que implementa el algoritmo de selección directa
mediante el uso de una función que permite al usuario escoger entre una ordenación
ascendente o descendente:
#include <stdio.h>
#include <stdlib.h>
#define MAX 100

void ordena (int tipo, int *vector, int elem)


{
int aux, i, j, k;

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


{
aux = vector[i];
k = i;
for (j=i+1; j<elem; j++)
{
if (tipo == 0)
{
if (vector[j] < aux)
{
aux = vector[j];
k = j;
}
}
else
{
if (vector[j] > aux)
{
aux = vector[j];
k = j;
}
}
}
if (k != i)
{
vector[k] = vector[i];
vector[i] = aux;
}
}
}

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


{
int numelementos, i, tipo, vector[MAX];

printf("\nNumero de elementos del vector:");


scanf("%i", &numelementos);

if (numelementos <= 0 || numelementos>MAX)


printf("\nNumero de elementos incorrectos. Maximo = %i", MAX);
else
{
for (i=0; i<numelementos; i++)
{
printf("\nIntroduzca el elemento %i: ", i);
scanf("%i", &vector[i]);
}

do {
printf("\nIntroduzaca el tipo de ordenacion:\n");
printf("0- Ascendente\n");
printf("1- Descendente\n");
printf("Tipo: ");
scanf("%i", &tipo);
} while ((tipo != 0) && (tipo != 1));

ordena(tipo, vector, numelementos);

printf("\nVector ordenado = ");


for (i=0; i<numelementos; i++)
printf("%i ", vector[i]);
}

system("PAUSE");
return 0;
}

En este ejemplo se ve claramente el efecto del paso por referencia del array vector, ya
que dentro de la función ordenacion se modifica pero no es necesario un return para
actualizar su valor en el main, ya que la función realmente está modificando el array
declarado en el main mediante el uso del puntero.
9. Cadenas de caracteres
Las cadenas de caracteres son simplemente arrays que almacenan variables de tipo char.
La única característica propia de este tipo de array es que se utiliza el carácter 0 (NULL
o carácter de código ASCII 0) para indicar el final de la cadena (esto no es necesario en
arrays numéricos).

El uso de variables de tipo carácter cobra una gran importancia práctica a la hora de
trabajar con cadenas de caracteres, ya que desde el punto de vista del usuario, esto le
permite utilizar palabras y frases de forma cómoda.

Existen en C una serie de funciones específicas para operar con estas cadenas, por
ejemplo para:

– Leer o escribir una cadena entera (array completo) (gets y puts)


– Averiguar la longitud de una cadena (strlen)
– Buscar un carácter o grupo dentro de una cadena (strchr)
– Unir dos cadenas o extraer parte de una cadena (strcat, strtok)

Es importante recalcar que estas funciones sólo sirven para cadenas de caracteres, no
para arrays numéricos.

A continuación mostramos un ejemplo de manejo de cadenas de caracteres, que consiste


en averiguar si una frase es o no palíndromo (es decir, si se lee igual de izquierda a
derecha y de derecha a izquierda):

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

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


{
char frase[40];
int i, j, longitud, es_palindromo=1;

printf("\nIntroduzca una frase: ");


gets(frase);

/* convierte a minúsculas la frase */


strlwr(frase);

/* elimina los espacios en blanco */


for(i=0; frase[i]!=0;i++)
{
if (frase[i] == ' ')
for(j=i;frase[j]!=0;j++)
frase[j] = frase[j+1];
}

longitud = strlen(frase);

for (i=0; i<=longitud/2 && es_palindromo == 1; i=i+1)


if (frase[i] != frase[longitud-1-i])
es_palindromo = 0;

if (es_palindromo == 1)
printf("\nLa frase es un palindromo\n");
else
printf("\nLa frase no es un palindromo\n");

system("PAUSE");
return 0;
}

La idea básica para la resolución de este ejemplo es que se debe comparar el primer
carácter del array con el último, el segundo con el penúltimo, etc. En el caso de que una
de estas comparaciones detecte que dos caracteres simétricos no son iguales, la frase ya
no será palíndromo. Previamente a esta comparación, debemos pasar todos los
caracteres a minúsculas (la comparación simple de una letra mayúscula con una
minúscula devolvería que son distintas y la frase sería palíndromo igualmente) y, a
continuación, eliminar los espacios en blanco que contenga la frase para que la
comparación tenga sentido.

Además, debemos tener en cuenta las siguientes consideraciones relacionadas con la


utilización de cadenas de caracteres:

• En el ejemplo anterior, la declaración de una cadena de caracteres se realiza


declarando un array de 40 caracteres. Esto implica, de acuerdo con lo que
comentamos antes, que la frase más grande que podríamos utilizar en este caso es de
39 caracteres, ya que el último carácter es el nulo.
• Las cadenas de caracteres se pueden leer completas usando las funciones gets() y
scanf() con %s. Es decir, no es necesario leer los caracteres uno a uno en un bucle
como se hacía en el caso de arrays numéricos.
• La función gets() que hemos utilizado en estos dos ejemplos lee todos los caracteres
hasta retorno de carro que lo sustituye por un carácter nulo. Pero si se leen más
datos de los que caben en la cadena, desborda la cadena que traerá como
consecuencia fallos impredecibles en la ejecución del programa (acceso a dirección
de memoria inválida, sobrescribir variables del propio programa), por lo cual su
utilización está desaconsejada y de ahora en adelante no volverá a aparecer.
• Un opción que evita el uso de gets() es usar scanf() con indicador de longitud (%),
aunque sólo funciona con palabras, no con frases ya que el scanf() para de leer al
encontrar un espacio en blanco, tabulador, o retorno de carro.
• Para solucionar este problema existe la posibilidad de utilizar scanf con indicador de
longitud (%) y con un indicador de caracteres permitidos de lectura (entre corchetes)
que le puede indicar que pare de leer únicamente al detectar un retorno de carro. Por
ejemplo: scanf("%16[^\n]s", cadena); Esta forma de lectura será la empleada a partir
de ahora en lugar del gets.
• Las funciones strlwr() y strlen() son ejemplos de funciones específicas de las
cadenas de caracteres que no pueden ser utilizadas con arrays numéricos.
• La función strlen() devuelve una variable de tipo int indicando el número de
elementos que tiene la cadena hasta el carácter nulo. Debemos almacenar dicha
variable en otra conocida de nuestro programa, en este caso, longitud.
• Por último, el programa anterior utiliza una variable que únicamente toma dos
valores (cero o uno) para detectar si la cadena es palíndromo. Dicha variable
(es_palindromo) se pone a cero en caso de que la condición dentro del bucle se
cumpla, lo que indicaría que no es palíndromo. Como consecuencia, el bucle for se
termina (ya que es_palindromo también controla la condición de realización del
bucle) y no se realizan más comprobaciones innecesarias.

La eliminación de los espacios en blanco en el ejemplo anterior se ha llevado a cabo


sobre el mismo array, desplazando los elementos de derecha a izquierda cada vez que se
detecta un espacio. Otra posible solución de este ejercicio es la que se muestra a
continuación, donde se utiliza un segundo array (nueva_frase) al que se copian las
palabras de la frase original:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TAM 40

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


{
char frase[TAM], nueva_frase[TAM];
int i, j, longitud, es_palindromo=1;

printf("\nIntroduzca una frase: ");

scanf("%39[^\n]s", frase);

/* convierte a minúsculas la frase */


strlwr(frase);

longitud = strlen(frase);

for(i=0,j=0;i<=longitud;i++)
if (frase[i] != ' ')
{
nueva_frase[j] = frase[i];
j++;
}

longitud = strlen(nueva_frase);

for (i=0; i<=longitud/2 && es_palindromo == 1; i=i+1)


if (nueva_frase[i] != nueva_frase[longitud-1-i])
es_palindromo = 0;

if (es_palindromo == 1)
printf("\nLa frase es un palindromo\n");
else
printf("\nLa frase no es un palindromo\n");

system("PAUSE");
return 0;
}

El uso de arrays de caracteres y funciones es equivalente al resto de arrays explicado en


el tema anterior.
10. Matrices
En las secciones anteriores hemos trabajado con arrays de una única dimensión
(vectores), sin embargo en muchas aplicaciones y problemas reales es necesario el uso
de matrices. Estas matrices podrán ser de dos dimensiones o en general n-dimensionales
para cualquier número n entero positivo.

A continuación veremos el ejemplo de un programa que lee dos matrices por teclado y
calcula la suma de ambas matrices.

#include <stdio.h>
#include <stdlib.h>
#define MAXFIL 20
#define MAXCOL 20

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


{
int i, j, numfil, numcol;
int matriz1[MAXFIL][MAXCOL], matriz2[MAXFIL][MAXCOL], suma[MAXFIL][MAXCOL];

printf("\nIntroduzca el numero de filas de las matrices: ");


scanf("%i", &numfil);
printf("\nIntroduzca el numero de columnas de las matrices: ");
scanf("%i", &numcol);

if (numfil <= 0 || numfil > MAXFIL)


printf("\nError. Numero de filas incorrectas (MAX = %i)", MAXFIL);
else
if (numcol <= 0 || numcol > MAXCOL)
printf("\nError. Numero de columnas incorrectas (MAX = %i)", MAXCOL);
else
{
printf("\nMatriz 1:\n");
for (i=0; i<numfil; i=i+1)
for (j=0; j<numcol; j=j+1)
{
printf("Introduzca el elemento [%i][%i]: ", i, j);
scanf("%i", &matriz1[i][j]);
}
printf("\nMatriz 2:\n");
for (i=0; i<numfil; i++)
for (j=0; j<numcol; j++)
{
printf("Introduzca el elemento [%i][%i]: ", i, j);
scanf("%i", &matriz2[i][j]);
}

printf("\nSuma de las matrices:\n");


for (i=0; i<numfil; i=i+1)
{
for (j=0; j<numcol; j=j+1)
{
suma[i][j] = matriz1[i][j] + matriz2[i][j];
printf("%i\t", suma[i][j]);
}
printf("\n");
}
}
system("PAUSE");
return 0;
}

En este programa hay que tener en cuenta las siguientes consideraciones:

• Una matriz, como cualquier otra variable, es necesario declararla. En este caso, a la
derecha del nombre de la variable, se ponen tantos corchetes como dimensiones
tenga la matriz. En el ejemplo anterior, al ser matrices bidimensionales, se usaron
dos pares de corchetes para indicar en el primero de ellos el número de filas y en el
segundo el número de columnas de la matriz. Al igual que sucedía con los arrays,
una vez se ha declarada la matriz no se permite cambiar el número de elementos de
la misma.
• El programador debe especificar el tamaño (números de elementos) de cada
dimensión. En el ejemplo anterior se especificaron el número de filas y de columnas
de la matriz. Puesto que dicho tamaño no puede ser sobrepasado, será necesario
controlar que el usuario no especifica un número de elementos mayor que el tamaño
de la matriz.
• Recuerda que para acceder a un elemento concreto de la matriz se pone el nombre
de éste y, entre corchetes, el índice del elemento al que queremos acceder, que será
un número entero entre 0 y N-1 donde N es el número de elementos de esa
dimensión de la matriz. El índice puede ser una constante, una variable o incluso
una expresión, siempre y cuando el resultado final de evaluarla sea un número
entero. En el programa que acabamos de ver se usan una variable, i, que va tomando
todos los valores desde 0 hasta numfil porque nos interesa recorrer todos las filas de
la matriz y otra variable numcol para ir recorriendo todas las columnas de la matriz.
Hay que tener en cuenta que para cada fila de la matriz se deberán recorrer todas sus
columnas por ello en el programa anterior el bucle de la variable j (las columnas) es
un bucle anidado dentro del bucle de la variable i (filas de la matriz).
• Al igual que en el caso de los arrays, no existen elementos en el lenguaje C ni
tampoco funciones que nos permitan tratar las matrices con una sola instrucción. Es
decir, no podemos leer, escribir, sumar, etc. matrices con una instrucción, si no que
tenemos que hacerlo elemento a elemento.

En cuanto al uso de matrices y funciones, hay que tener en cuenta que cuando se recibe
una array bidimensional (matriz) en una función, se deben especificar todas las
dimensiones menos la primera, que es opcional. El siguiente ejemplo muestra el uso de
matrices en funciones, donde el objetivo consiste en realizar un programa que ponga a 0
(mediante una función) los elementos de la diagonal principal y secundaria de una
matriz cuadrada:

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

#define MAX 10

void modificarDiagonalesMatriz(int matriz[][MAX],int dim)


{
int i;

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


{
matriz[i][i]=0;
matriz[i][dim-i-1]=0;
}
}

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


{

int i,j,dimension,matriz[MAX][MAX];

printf("Introduzca la dimension de la matriz cuadrada: ");


scanf("%d",&dimension);

if (dimension>MAX || dimension<=0)
printf("Error. El numero de elementos es incorrecto (Maximo= %i)\n",MAX);
else
{
for (i=0; i<dimension; i++)
for (j=0; j<dimension; j++)
{
printf("Introduzca el elemento %i, %i: ", i,j);
scanf("%i",&matriz[i][j]);
}

modificarDiagonalesMatriz(matriz,dimension);
for (i=0; i<dimension; i++)
{
for (j=0; j<dimension; j++)
printf("%i\t ", matriz[i][j]);

printf("\n");
}
}//fin else

system("PAUSE");
return 0;
}

La función modificarDiagonalesMatriz recibe como parámetros la matriz y la variable


dimension y no devuelve nada (void). Por este motivo, en el main() únicamente se llama
a la función y se le pasan los parámetros correspondientes. Al terminar la ejecución de
la función, la matriz ha quedado modificada también en el main(). De cara a clarificar el
hecho de que no se realiza una copia de la matriz local a la función, en la declaración de
la misma hemos mantenido el nombre que tenía en el main(). En cambio, la variable
dimension se almacena localmente en la función en una variable con otro nombre, dim
(las modificaciones que se hiciesen sobre dim no afectarían a la variable dimension). Por
otro lado, la declaración de la matriz en la cabecera de la función no incluye la primera
dimensión, pero sí la segunda tal y como habíamos comentado anteriormente.
11. Ficheros
Hasta este momento, los datos que hemos utilizado en nuestros programas, se
almacenaban en la memoria principal y dejaban de estar accesibles cuando el programa
finalizaba. Los ficheros permiten almacenar información de manera permanente en la
memoria secundaria (disco duro, cd, dvd, …) de tal forma que puedan ser accedidos con
posterioridad por el propio programa o por otros. Asimismo, un programa puede leer
datos por teclado (como hemos hecho hasta el momento) pero también los puede leer de
un fichero. De hecho, esta es una opción muy común cuando deben ser leídas grandes
cantidades de datos, ya que evita que el usuario tenga que teclear demasiado.

Los ficheros de texto pueden ser de dos tipos: de texto o binarios. Los ficheros de texto
almacenan información “legible”, es decir, basada en el código ASCII. Cualquier
fichero que no sea de texto, es binario.

Para explicar el funcionamiento práctico de la lectura y escritura de ficheros en C,


utilizaremos un programa que realiza lo siguiente programa:

1- Pedir al usuario el nombre de un archivo


2- Pedir al usuario el número de datos N que desea guardar en el archivo (máximo 100)
3- Crear N datos aleatorios entre 0 y 1
4- Almacenar los datos en el archivo anterior

NOTA: si el programa se ejecuta desde el diskette, es necesario que el nombre del


fichero incluya la ruta completa donde se encuentra el archivo de texto, por ejemplo:
a:\programas\ficheros\palabras.txt
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 100

int main()
{
int i,j,k;
int cantidad;
char nombre[35];
FILE *fichero;

printf("\n\nIntroduce el nombre del fichero destino sin extension: ");


scanf("%30[^\n]s",nombre);

strcat(nombre,".txt");

/* lo abro como lectura para ver si ya existe */


fichero = fopen(nombre,"w");
if (fichero == NULL)
printf(“\n\nERROR..... No se puede crear el fichero %s en”
“ la ruta especificada\n\n”,nombre);
else
{
do {
printf("\nIntroduzca Numero de datos a guardar (maximo %d): ",MAX);
scanf("%d",&cantidad);
} while (cantidad>=MAX || cantidad<=0);
/* almaceno en el fichero los numeros aleatorios */

srand(time(NULL));

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


fprintf(fichero,"\n%f", (float)rand()/RAND_MAX);

fclose(fichero);
}

system("PAUSE");
return 0;
}

La comprobación de que este ejercicio funciona correctamente se realiza abriendo el


archivo de texto creado y viendo que guarda el número de datos entre 0 y 1 que el
usuario ha especificado y que están en desorden (es decir, que son realmente aleatorios).

La estructura del programa anterior es muy simple y se basa en la creación de números


aleatorios que se almacenan en un fichero. En cuanto a la utilización de ficheros, vemos
como mediante la instrucción
FILE *fichero;

declaramos un puntero que será utilizado para acceder a los ficheros (realmente es un
puntero a la estructura FILE, declarada en stdio.h).

Antes de usar un fichero es necesario realizar una operación de apertura del mismo y,
posteriormente, si se desea almacenar datos en él hay que realizar una operación de
escritura. Cuando ya no se quiera utilizar el fichero se realiza una operación de cierre.
En este sentido, en el programa anterior se lleva a cabo la apertura de un fichero para
escribir datos de él. El nombre del fichero se solicita al usuario sin extensión, se le
concatena la extensión .txt mediante la función strcat y se almacena en la cadena de
caracteres nombre, mediante la instrucción:
fichero = fopen(nombre, "w");

La función fopen requiere como primer parámetro una cadena de caracteres que
contenga el nombre del fichero que se quiere tratar (y en su caso la ruta de acceso).
Como segundo parámetro es necesario especificar el modo de apertura de dicho fichero,
que es una cadena de caracteres que indica el tipo del fichero (texto o binario) y el uso
que se va ha hacer de él (lectura, escritura, añadir datos al final, etc). En este caso, el
fichero nombre almacenará una serie de números aleatorios, y se abre como escritura. A
partir de esta instrucción, en nuestro programa accederemos al fichero mediante la
variable fichero.

Si existe algún tipo de error al realizar la operación de apertura del fichero (por ejemplo,
porque el disco está lleno, porque el usuario no tiene permisos de escritura en el disco,
porque la ruta especificada no existe, etc), fopen devuelve el valor NULL. Esto nos
permite controlar un error la apertura del fichero mediante una instrucción condicional,
como la utilizada en este caso:
if (fichero == NULL)
Lo siguiente que realiza el programa anterior es pedir al usuario el número de datos que
quiere guardar en el fichero y después genera dicho número de datos mediante el uso de
la función rand(), que devuelve un número entero aleatorio entre 0 y RAND_MAX (el
mayor número entero que puede generar la función rand). Para crear números decimales
entre 0 y 1 basta con normalizar y cambiar el tipo de dato a float tal y como se hace en
el código anterior:
(float)rand()/RAND_MAX

La función srand(time(NULL)) que se utiliza en este programa inicializa la semilla para


la generación de números aleatorios con rand. Si no se utiliza una semilla distinta cada
vez (esto se consigue mediante la función time), siempre obtendríamos los mismos
aleatorios en distintas ejecuciones del programa.

Los números aleatorios que se generan con la función rand deben ser guardados en el
fichero. Para ello es necesario realizar una operación de escritura. En este caso
utilizaremos dentro de un bucle la función fprintf, análoga al printf que hemos usado
hasta ahora pero especificando como primer argumento el puntero al fichero donde
queremos escribir:
fprintf(fichero,"%f\n", (float)rand()/RAND_MAX);

Por último, tras terminar de usar cada fichero, debemos cerrarlo utilizando la función
fclose.

Como continuación del ejercicio anterior, y de cara a mostrar la lectura de datos de un


archivo, a continuación mostramos el código del siguiente programa en C:

1- Pedir al usuario el nombre de un archivo donde se encuentran, como máximo,


100 números en punto flotante entre 0 y 1.
2- Leer los números del archivo y ordenarlos de menor a mayor
3- Guardar los datos ordenados en otro archivo cuyo nombre se pedirá de nuevo al
usuario
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 100

int main()
{
int i,j,k;
int c; /* variable que utilizaremos para borrar el buffer del teclado */
int num_datos=0;
float vector[MAX], aux;
char nombre[35];
FILE *fichero;

printf("\n\nIntroduce el nombre del fichero origen sin extension: ");


scanf("%30[^\n]s",nombre);

strcat(nombre,".txt");

/* Abro el archivo anterior como lectura */


fichero = fopen(nombre,"r");
if (fichero == NULL)
printf("\n\n ERROR..... No se puede leer el fichero %s \n\n",nombre);
else
{
/* leo los datos aleatorios del archivo */
while (! feof(fichero))
{

fscanf(fichero,"%f",&vector[num_datos]);
num_datos++;
}

fclose(fichero);

/* Ordeno de menor a mayor */


for(i=0; i<num_datos; i++)
{
aux = vector[i];
k = i;

for (j=i+1; j<num_datos; j++)


if (vector[j] < aux)
{
aux = vector[j];
k = j;
}

if (k != i)
{
vector[k] = vector[i];
vector[i] = aux;
}
} /* Fin del proceso de ordenacion */

/* esta instrucción borra el buffer del teclado */


while((c = getchar()) != '\n' && c != EOF);

/* Guardo los datos en un archivo */


printf(“\nIntroduce el nombre del fichero destino para”
“ los datos ordenados (sin extension): “);
scanf("%30[^\n]s",nombre);

strcat(nombre,".txt");

if ((fichero = fopen(nombre,"w")) == NULL)


printf(“\n\nERROR..... No se puede crear el fichero %s en”
“ la ruta especificada\n\n”,nombre);
else
{
for(i=0; i<num_datos; i++)
fprintf(fichero,"%f\n",vector[i]);

fclose(fichero);
}
}

system("PAUSE");
return 0;
}

El resultado de este programa será correcto si el archivo creado contiene los mismos
datos que el archivo original, pero ordenados de menor a mayor.

En este caso, para obtener los datos hay que efectuar una operación de lectura de
fichero. Una de las posibles instrucciones de lectura es fscanf, que es la utilizada en el
programa anterior. Su estructura es similar al scanf que ya hemos utilizado en los
capítulos anterior, con la diferencia de que hay que especificar como primer argumento
el fichero en el que leemos:
fscanf(fichero,"%f",&vector[num_datos]);

En ciertos casos no conoceremos el número de datos que almacena el fichero, de modo


que es útil la utilización de la función feof() que se muestra en el ejemplo anterior. Esta
función nos devuelve un valor distinto de 0 si se alcanza el final de fichero. Por esta
razón, las instrucciones:
while (! feof(fichero))
{

fscanf(fichero,"%f",&vector[num_datos]);
num_datos++;
}

leen los datos (y los cuentan mediante la variable num_datos) hasta que se alcanza el
final del archivo sin necesidad de que el usuario introduzca cuántos hay.

Una vez que se tienen los datos en el array vector simplemente ejecutamos el algoritmo
de ordenación visto en el tema 5. Antes de añadir estos datos ordenados a un fichero de
destino debemos vaciar previamente el buffer del teclado mediante la expresión:
while((c = getchar()) != '\n' && c != EOF);

El buffer de teclado es una memoria temporal donde se introducen los caracteres


tecleados antes de pulsar la tecla RETURN (Intro o Enter), la cual es otro carácter (el
carácter '\n'). Usualmente, cuando se usan funciones de lectura como scanf() o getchar(),
por ejemplo, la ejecución del programa se detiene hasta que se introducen estos
caracteres en el buffer de teclado, o sea, hasta que se pulsa la tecla RETURN. Sin
embargo, si en el buffer ya existen valores entonces la función de lectura lee esos
valores sin detenerse.

Cuando estamos leyendo números la función intenta leer números, saltándose algunos
caracteres de control (si se los encuentra), como el carácter '\n'. Sin embargo, cuando
estamos leyendo un carácter (o varios) la función de lectura leerá el siguiente carácter
que haya en el buffer de teclado, sea cual sea. Si no hay ninguno, entonces, la función se
detiene a la espera de poder leer. Pero si en el buffer existe algún carácter éste será
leído, aunque sea un carácter de control. Para evitar la lectura de caracteres de control y
también para evitar la lectura de otros caracteres anteriores que puede haber en el buffer
del teclado, se suele utilizar la instrucción flush(stdin) que vacía dicho buffer. Pero n
osotros lo desaconsejamos totalmente porque fflush solo está definido en el estándar
para flujos de salida y stdin es un flujo de entrada, y por ello no podemos estar seguros
de cómo se comportará en un caso general. Aunque en algunos compiladores funciona,
no lo hará en todos, por lo cual nosotros recomendamos la utilización de la expresión
anterior como equivalente para el fflush(stdin).

Continuando con el programa anterior, solo nos resta pedir al usuario un nombre para el
fichero destino, abrir el fichero ahora en modo escritura y guardar en él los datos del
array vector.
A continuación mostramos otro ejemplo del uso de archivos en C que lee datos de un
fichero “numeros.txt” que contiene los valores de una matriz de tamaño 10 x 10. Así
cada línea del fichero contiene 3 números, el primero de ellos indica el número de fila,
el segundo el número de columna y el último el valor correspondiente. El objetivo es
realizar un programa en C que lea este fichero “numeros.txt” y muestre por pantalla y
guarde en otro fichero el contenido del fichero original, pero de forma matricial.

El contenido del fichero de texto se muestra en el Apéndice B, al final de este manual.

La ejecución del programa proporciona la siguiente salida por pantalla:

#include <stdio.h>
#include <stdlib.h>
#define MAX 10

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


{
int matriz[MAX][MAX];
int i,j,valor;
char nombreFichero[40];
FILE *fichero1,*fichero2;

printf("\nIntroduzca el nombre del fichero origen: ");


scanf(“%s39[^\n]s,nombreFichero);

fichero1 = fopen(nombreFichero, "rb");

if (fichero1 != NULL)
{
printf("\nIntroduzca el nombre del fichero destino: ");
scanf(“%s39[^\n]s,nombreFichero);

fichero2 = fopen(nombreFichero, "wb");

if (fichero2!=NULL)
{
while (! feof(fichero1))
{
fscanf(fichero1, "%i", &i);
fscanf(fichero1, "%i", &j);
fscanf(fichero1, "%i", &valor);
matriz[i][j]=valor;
}

for (i=0;i<MAX;i++)
{
for (j=0;j<MAX;j++)
{
printf("%4i",matriz[i][j]);
fprintf(fichero2,"%4i",matriz[i][j]);
}
printf("\n");
fprintf(fichero2,"\n");
}
fclose(fichero2);

}
else
printf("\nError al crear el fichero\n");

fclose(fichero1);
}
else
printf("\nError al abrir el fichero\n");

system("PAUSE");
return 0;
}

Como vemos, la utilización de las funciones de acceso a ficheros es muy similar al


ejercicio anterior.
12. Reserva dinámica de memoria
En los programas que hemos visto hasta ahora, la reserva o asignación de memoria para
los vectores y matrices se realiza cuando se declaran dichas variables, asignando
normalmente un tamaño por exceso y dejando el resto sin usar. Por ejemplo, si
realizamos un programa en C que calcula el producto de dos matrices, éstas se pueden
declarar para un tamaño máximo de 100x100 (float matriz[100][100]), de manera que
en dicho programa se podrá calcular cualquier producto de un tamaño igual o inferior a
100x100, pero en el caso de que el producto sea por ejemplo de tamaño 3x3, la memoria
reservada para esa matriz corresponderá al tamaño máximo de 100x100, que resulta en
una utilización ineficiente de la memoria. Por este motivo, es muy útil el poder reservar
más o menos memoria en tiempo de ejecución, según el tamaño del caso concreto que
se vaya a resolver. Hablamos, en este caso, de reserva dinámica de memoria.

La función que permite crear o asignar un espacio de memoria interna durante la


ejecución de un programa es malloc(), que requiere como parámetro el número de bytes
de memoria que se van a reservar y devuelve un puntero al comienzo de la zona de
memoria reservada, o NULL si no es posible reservar la cantidad de memoria pedida.
La memoria reservada mediante malloc() puede (y debe) ser liberada una vez utilizada
mediante la función free() que requiere como parámetro el puntero asignado en
malloc().

Como ejemplo de aplicación simple de la reserva dinámica de memoria, el siguiente


programa crea un array de elementos float dinámicamente en función del tamaño que
introduce el usuario. A continuación se introducen los elementos uno a uno y se calcula
la media:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
float *numeros, media=0;
int i,n;

printf("Introduzca el numero de datos: ");


scanf("%d", &n);

numeros =(float *)malloc(n*sizeof(float));

if (numeros == NULL)
printf("Memoria insuficiente\n");
else
{
for (i=0;i<n;i++)
{
printf("Introduce el elemento %d: ",i);
scanf("%f",&numeros[i]);
media += numeros[i];
}

media = media/n;
printf("\nLa media es: %f\n\n",media);

free(numeros);
}

system("PAUSE");
return 0;
}

Lo primero que hay que notar del código anterior es que el array está definido como un
puntero a un float en lugar de utilizar la notación para declarar los arrays de manera
estática. Una vez que el usuario introduce el número de elementos que tendrá el array
(variable n), mediante la función malloc() se realiza la reserva de un número de bytes
que permita almacenar n elementos de tipo float. Para ello, utilizamos la función sizeof()
que devuelve el número de bytes del tipo de dato o de la variable que se le pasa como
argumento. Por ejemplo, en este caso sizeof(float) devolvería un 4, que al multiplicarlo
por n nos dará el número exacto de bytes necesario para almacenar en memoria los
datos que introducirá el usuario.

La función malloc() devuelve un puntero de tipo void (es decir, un puntero genérico)
por lo que es necesario hacer una conversión de tipo de puntero (en este caso float *) de
acuerdo con el tipo de dato que se desea almacenar. A partir de este momento, la
variable numero guardará la dirección de comienzo del array en memoria y a partir de
este momento la utilización del array es la común.

En el resto del ejemplo, si la memoria no se pudo asignar correctamente, se finaliza la


ejecución del programa. Por último, debemos liberar siempre la memoria cuando ya no
sea necesaria su utilización, en este caso con la sentencia free(numeros).

Un aspecto que debe ser tratado cuando se trabaja con funciones, es la posibilidad de
que se realice reserva dinámica de memoria dentro de una función. El siguiente código
corresponde a un programa que crea un array de elementos dinámicamente en función
del tamaño que introduce el usuario y, a continuación, pide los elementos del array uno
a uno y calcula la media. En este caso se han utilizado funciones para realizar las
operaciones básicas de reserva de memoria y de cálculo de la media:
#include <stdio.h>
#include <stdlib.h>

float *reserva_local(float *, int);


float calcula_media(float *, int);

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


{
float *numeros, media;
int i,n;

printf("Introduzca el numero de datos: ");


scanf("%d", &n);

numeros = reserva_local(numeros,n);

if (numeros == NULL)
printf("Memoria insuficiente\n");
else
{
for (i=0;i<n;i++)
{
printf("Introduce el elemento %d: ",i);
scanf("%f",&numeros[i]);
}

media = calcula_media(numeros,n);
printf("\nLa media es: %f\n\n",media);

free(numeros);
}

system("PAUSE");
return 0;
}

float *reserva_local(float *array, int tam)


{
array = (float *) calloc(tam*sizeof(float));

return(array);
}

float calcula_media(float *array, int num)


{
int i;
float media=0;

for(i=0;i<num;i++)
media += array[i];

return (media/num);
}

Como podemos observar en el código, la función reserva_local() recibe como


parámetro el puntero definido en el main() y el número de elementos que va a introducir
el usuario. Tras realizar la llamada a malloc(), la función devuelve un puntero que
apunta a la primera dirección de memoria asignada a array. Por este motivo, la función
es de tipo float *. Vemos también que en la función calcula_media() el array de
números se recibe como primer parámetro en forma de puntero y no con el formato de
matriz estática que hemos usado hasta el momento. Esto es posible porque cuando
desde el main() se llama a la función calcula_media() lo que se pasa como primer
argumento es el puntero numeros, es decir, la dirección de memoria donde comienza el
array, y por tanto, una variable tipo float * que es donde se almacena.
Para finalizar, debemos notar que en este ejemplo se ha usado la función de reserva
dinámica calloc(), que es idéntica a malloc() pero que inicializa a cero las posiciones
reservadas.
Apéndice A. Normas de estilo de programación
Estas normas de estilo de programación, están basada en las que pueden ser consultadas
en la página web: http://www.ieev.uma.es/fundinfo/matdoc/normasC.html
correspondientes a la asignatura de Fundamentos de Informática de la Universidad de
Málaga. Han sido ligeramente modificadas y adaptadas para el contexto de la actual
asignatura y conforman una serie de “buenos hábitos” de programación, es decir, una
serie de indicaciones sobre el formato de los programas que el alumno debería seguir
siempre. Estos consejos de estilo no son propios del lenguaje C, sino que pueden ser
aplicados a cualquier otro lenguaje.

Independientemente de los algoritmos usados, hay muchas formas y estilos de


programar. La legibilidad de un programa es demasiado importante como para prestarle
la atención que merece por parte del programador. Es frecuente que uno deba modificar
un programa escrito por otro programador o bien que deba actualizar un programa
escrito por uno mismo en el pasado. Es entonces, cuando surgen los problemas de
legibilidad de un programa. Para poder modificarlo, primero hay que comprender su
funcionamiento, y para facilitar esta tarea el programa debe estar escrito siguiendo unas
normas básicas. La tarea de mantenimiento del software (corregir y ampliar los
programas) es una de las tareas más laboriosas del ciclo de vida del software. Por eso, al
programar debemos intentar que nuestros programas sean lo más expresivos posibles,
para ahorrarnos tiempo, dinero y quebraderos de cabeza a la hora de modificarlos.

Además, el C es un lenguaje que se presta a hacer programas complejos y difíciles de


comprender. En C se pueden encapsular órdenes y operadores, de tal forma que, aunque
consigamos mayor eficiencia su comprensión sea todo un reto.

Unas normas de estilo en programación, son tan importantes que todas las empresas
dedicadas a programación imponen a sus empleados una mínima uniformidad, para
facilitar el intercambio de programas y la modificación por cualquier empleado, sea o
no el programador inicial. Por supuesto, cada programa debe ir acompañado de una
documentación adicional, que aclare detalladamente cada módulo del programa,
objetivos, algoritmos usados, ficheros...

No existen un conjunto de reglas fijas para programar con legibilidad, ya que cada
programador tiene su modo y sus manías y le gusta escribir de una forma determinada.
Lo que sí existen son un conjunto de reglas generales, que aplicándolas, en mayor o
menor medida, se consiguen programas bastante legibles. Aquí intentaremos resumir
estas reglas:

Identificadores significativos

Un identificador es un nombre asociado a un objeto de programa, que puede ser una


variable, función, constante, tipo de datos... El nombre de cada identificador debe
identificar lo más claramente posible al objeto que identifica (valga la redundancia).
Normalmente los identificadores deben empezar por una letra, no pueden contener
espacios (ni símbolos raros) y suelen tener una longitud máxima que puede variar, pero
que no debería superar los 10-20 caracteres para evitar lecturas muy pesadas.
Se recomienda encarecidamente la utilización de letras minúsculas para definir los
nombres de las variables y las funciones y de mayúsculas para las constantes y los
define.

Un identificador debe indicar lo más breve y claramente posible el objeto al que


referencia. Por ejemplo, si una variable contiene la nota de un alumno de informática, la
variable se puede llamar nota_informatica. Observe que no ponemos los acentos, los
cuales pueden dar problemas de compatibilidad en algunos sistemas. El carácter '_' es
muy usado para separar palabras en los identificadores.

Es muy normal usar variables como i, j o k para nombres de índices de bucles (for,
while...), lo cual es aceptable siempre que la variable sirva sólo para el bucle y no tenga
un significado especial. En determinados casos, dentro de una función o programa
pequeño, se pueden usar este tipo de variables, si no crean problemas de comprensión,
pero esto no es muy recomendable.

Para los identificadores de función se suelen usar las formas de los verbos en infinitivo,
seguido de algún sustantivo, para indicar claramente lo que hace. Por ejemplo, una
función podría llamarse escribir_opciones, y sería más comprensible que si le
hubiéramos llamado escribir o escrOpc. Si la función devuelve un valor, su nombre
debe hacer referencia a este valor, para que sea más expresivo usar la función en
algunas expresiones, como:

precio_total = precio_total + IVA(precio_total,16) +


gastos_transporte(destino);

Constantes simbólicas

En un programa es muy normal usar constantes (numéricas, cadenas...). Si estas


constantes las usamos directamente en el programa, el programa funcionará, pero es
más recomendable usar constantes simbólicas, de forma que las definimos al principio
del programa y luego las usamos cuando haga falta. Así, conseguimos principalmente
dos ventajas:

1. Los programas se hacen más legibles: Es más legible usar la constante simbólica
PI como el valor de π que usar 3.14 en su lugar:

volumen_esfera = 4/3. * PI * pow(radio,3);

2. Los programas serán más fáciles de modificar: Si en un momento dado


necesitamos usar PI con más decimales (3.141592) sólo tenemos que cambiar la
definición, y no tenemos que cambiar todas las ocurrencias de 3.14 por 3.141592
que sería más costoso y podemos olvidarnos alguna.

En C, las constantes simbólicas se suelen poner usando una orden al preprocesador de


C, quedando definidas desde el lugar en que se definen hasta el final del fichero (o hasta
que expresamente se indique). Su formato general es:

#define CONSTANTE valor


que se encarga de cambiar todas las ocurrencias de CONSTANTE por el valor indicado en
la segunda palabra (valor). Este cambio lo realiza el preprocesador de C, antes de
empezar la compilación. Por ejemplo:

#define PI 3.141592

Como hemos dicho, las constantes se suelen poner completamente en mayúsculas y las
variables no, de forma que leyendo el programa podamos saber rápidamente qué es cada
cosa. En general, se deben usar constantes simbólicas en constantes que aparezcan más
de una vez en el programa referidas a un mismo ente que pueda variar ocasionalmente.
Obsérvese, que aunque el valor de π es constante, podemos variar su precisión, por lo
que es recomendable usar una constante simbólica en este caso, sobre todo si se va a
usar en más de una ocasión en nuestro programa. Puede no resultar muy útil dedicar una
constante para el número de meses del año, por ejemplo, ya que ese valor es
absolutamente inalterable.

Comentarios

El uso de comentarios en un programa escrito en un lenguaje de alto nivel es una de las


ventajas más importantes con respecto a los lenguajes máquina, además de otras más
obvias. Los comentarios sirven para aumentar la claridad de un programa, ayudan para
la documentación y bien utilizados nos pueden ahorrar mucho tiempo.

No se debe abusar de comentarista, ya que esto puede causar una larga y tediosa lectura
del programa, pero en caso de duda es mejor poner comentarios de más. Por ejemplo, es
absurdo poner:

Nota = 10; /* Asignamos 10 a la variable Nota */

Los comentarios deben ser breves y evitando divagaciones. Se deben poner comentarios
cuando se crean necesarios, y sobre todo:

• Al principio del programa o de cada fichero del programa que permita seguir
un poco la historia de cada programa, indicando: Nombre del programa,
objetivo, parámetros (si los tiene), condiciones de ejecución, módulos que lo
componen, autor o autores, fecha de finalización, últimas modificaciones
realizadas y sus fechas... y cualquier otra eventualidad que el programador
quiera dejar constancia.
• En cada sentencia o bloque (bucle, if, switch...) que revista cierta
complejidad, de forma que el comentario indique qué se realiza o cómo
funciona.
• Al principio de cada función cuyo nombre no explique suficientemente su
cometido. Se debe poner no sólo lo que hace sino la utilidad de cada parámetro,
el valor que devuelve (si lo hubiera) y, si fuera oportuno, los requisitos
necesarios para que dicha función opere correctamente.
• En la declaración de variables y constantes cuyo identificador no sea
suficiente para comprender su utilidad.
• En los cierres de bloques con '}', para indicar a qué sentencias de control de
flujo pertenecen, principalmente cuando existe mucho anidamiento de sentencias
y/o los bloques contienen muchas líneas de código.
No olvidemos que los comentarios son textos literarios, por lo que debemos cuidar el
estilo, acentos y signos de puntuación.

Estructura del programa


Un programa debe ser claro, estar bien organizado y que sea fácil de leer y entender.
Casi todos los lenguajes de programación son de formato libre, de manera que los
espacios no importan, y podemos organizar el código del programa como más nos
interese.

Para aumentar la claridad no se deben escribir líneas muy largas que se salgan de la
pantalla y funciones con muchas líneas de código (especialmente la función principal).
Una función demasiado grande demuestra, en general, una programación descuidada y
un análisis del problema poco estudiado. Se deberá, en tal caso, dividir el bloque en
varias llamadas a otras funciones más simples, para que su lectura sea más agradable.
En general se debe modularizar siempre que se pueda, de forma que el programa
principal llame a las funciones más generales, y estas vayan llamando a otras, hasta
llegar a las funciones primitivas más simples. Esto sigue el principio de divide y
vencerás, mediante el cual es más fácil solucionar un problema dividiéndolo en
subproblemas (funciones) más simples.

A veces, es conveniente usar paréntesis en las expresiones, aunque no sean necesarios,


para aumentar la claridad.

Cada bloque de especial importancia o significación, y cada función debe separarse de


la siguiente con una línea en blanco. A veces, entre cada función se añaden una linea de
asteriscos o guiones, como comentario, para destacar que empieza la implementación de
otra función (aparte de los comentarios explicativos de dicha función).

El uso de la sentencia GOTO está totalmente desaconsejado. Como dicen Kerninghan y


Ritchie en su libro de C, su utilización sólo estaría justificada en casos muy especiales,
como salir de una estructura profundamente anidada, aunque para ello recomendamos
usar los comandos break, continue, return o la función exit(). De todas formas,
esos recursos, para salir de bucles anidados deben usarse lo menos posible y sólo en
casos donde quede bien clara su finalidad. Si no queda clara su finalidad, será mejor que
nos planteemos de nuevo el problema y estudiemos otra posible solución que seguro
que la hay.

Normalmente, un programa en C se suele estructurar de la siguiente forma:

1. Primero los comentarios de presentación, como ya hemos indicado.


2. Después, la inclusión de bibliotecas del sistema, los ficheros .h con el
#include y entre ángulos (<...>) el nombre del fichero. Quizás la más típica
sea:

#include <stdio.h>

3. Bibliotecas propias de la aplicación. Normalmente, en grandes aplicaciones, se


suelen realizar varias librerías con funciones, separadas por su semántica. Los
nombres de fichero se ponen entre comillas (para que no las busque en el
directorio de las bibliotecas o librerías estándar) y se puede incluir un
comentario aclarativo:

#include "raton.h" /*Rutinas para control del ratón*/


#include "cola.h" /*Primitivas para el manejo de una cola*/

4. Variables globales, usadas en el módulo y declaradas en otro módulo distinto,


con la palabra reservada extern.
5. Constantes simbólicas y definiciones de macros, con #define.
6. Definición de tipos, con typedef.
7. Declaración de funciones del módulo: Se escribirá sólo el prototipo de la
función, no su implementación. De esta forma, el programa (y el programador)
sabrá el número y el tipo de cada parámetro y cada función.
8. Declaración de variables globales del módulo: Se trata de las variables
globales declaradas aquí, y que si se usan en otro módulo deberán llevar, en el
otro módulo, la palabra extern.
9. Implementación de funciones: Aquí se programarán las acciones de cada
función, incluida la función principal. Normalmente, si el módulo incluye la
función principal, la función main(), ésta se pone la primera, aunque a veces se
pone al final y nunca en medio. El resto de funciones, se suelen ordenar por
orden de aparición en la función principal y poner juntas las funciones que son
llamadas desde otras. Es una buena medida, que aparezcan en el mismo orden
que sus prototipos, ya que así puede ser más fácil localizarlas.

Naturalmente, este orden no es estricto y pueden cambiarse algunos puntos por otros,
pero debemos ser coherentes, y usar el mismo orden en todos los módulos. Por ejemplo,
es frecuente saltarse la declaración de los prototipos de las funciones poniendo en su
lugar la implementación y, por supuesto, dejando para el final la función main().

Otro punto muy importante es el referente a variables globales. En general es mejor no


usar nunca variables globales, salvo que sean variables que se usen en gran parte de
las funciones (y módulos) y esté bien definida y controlada su utilidad. El uso de estas
variables puede dar lugar a los llamados efectos laterales, que provienen de la
modificación indebida de una de estas variables en algún módulo desconocido. Lo
mejor es no usar nunca variables globales y pasar su valor por parámetros a las
funciones que estrictamente lo necesiten, viendo así las funciones como cajas negras, a
las que se le pasan unos determinados datos y nos devuelve otros, perfectamente
conocidos y expresados en sus parámetros. Por la misma razón que no debemos usar
variables globales, no se deben usar pasos de parámetros por referencia (o por variable),
cuando no sea necesario.

A continuación mostramos un programa que calcula el factorial de un número de un


número y que utiliza variables locales:

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

double f_factorial(double numero)


{
double factorial = 1;

while (numero > 1)


factorial *= numero--;
return factorial;
}

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


{
double numero = 0, factorial = 1;

while (numero >= 0)


{
printf("Introduce un numero positivo (en caso contrario el programa
finaliza): ");
scanf("%lf", &numero);
if (numero == 0)
printf("El factorial vale 1\n");
else
{
if (numero > 1)
factorial = f_factorial(numero);
printf("El factorial vale %.0lf\n", factorial);
}
}
system("PAUSE");
return 0;
}

El mismo programa, simplemente realizado con variables globales, no funciona (se deja
como ejercicio al alumno averiguar por qué). Aunque la programación se simplifica al
evitar el envío y devolución de parámetros a la función, al final el esfuerzo deberá ser
realizado en la etapa de depuración tratando de encontrar dónde está el fallo:
#include <stdio.h>
#include <stdlib.h>

double numero = 0, factorial = 1;

void f_factorial()
{
while (numero > 1)
factorial *= numero--;
}

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


{
while (numero >= 0)
{
printf("Introduce un numero positivo (en caso contrario el programa
finaliza): ");
scanf("%lf", &numero);
if (numero == 0)
printf("El factorial vale 1\n");
else
{
if (numero > 1)
f_factorial();
printf("El factorial vale %.0lf\n", factorial);
}
}
system("PAUSE");
return 0;
}

De hecho, el principal problema derivado del uso de variables globales reside en


encontrar dónde se produce la modificación de una variable que provoca que el
programa no funcione correctamente. Este problema es más grave cuanto más complejo
sea el programa y mayor número de funciones utilice. Como ejemplo, mostramos a
continuación un programa que simplemente pide números al usuario y va calculando y
mostrando por pantalla la media acumulada. Existe un fallo en este código que el
alumno deberá encontrar y que viene derivado del uso de una variable global en varios
puntos y funciones del programa:
#include <stdio.h>
#include <stdlib.h>
#define MAX 20

int datos[MAX],contador,num_datos;
float media;

void media_acumulada()
{
media = 0;
for (contador=0; contador < num_datos; contador++)
media += datos[contador];

printf("La media acumulada es: %f\n",media/num_datos);


}

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


{

printf("\nCalculo del valor medio\n");

printf("Introduce el numero de datos a tratar (MAX %d): ",MAX);


scanf("%d",&num_datos);

for(contador=0;contador<num_datos;contador++)
datos[contador] = 0;

contador=0;
do {
printf("Introduce un numero entero: ");
scanf("%d",&datos[contador]);
contador++;
media_acumulada();
} while (contador < num_datos);

system("PAUSE");
return 0;
}

Indentación o sangrado

La indentación o sangrado consiste en marginar hacia la derecha todas las sentencias de


una misma función o bloque, de forma que se vea rápidamente cuales pertenecen al
bloque y cuales no. Algunos estudios indican que el indentado debe hacerse con 2, 3 ó 4
espacios. Usar más espacios no aumenta la claridad y puede originar que las líneas se
salgan de la pantalla, complicando su lectura.

La indentación es muy importante para que el lector/programador no pierda la estructura


del programa debido a los posibles anidamientos.

Normalmente, la llave de comienzo de una estructura de control ({) se pone al final de


la línea y la que lo cierra (}) justo debajo de donde comienza -como veremos más
adelante- pero algunos programadores prefieren poner la llave { en la misma columna
que la llave }, quedando una encima de otra. Eso suele hacerse así, en la
implementación de funciones, donde la llave de apertura de la función se suele poner en
la primera columna.

Veamos, a continuación, las indentaciones típicas en las estructuras de control:

Sentencia if-else
Primera opción: Segunda opción:

if (condición) { if (condición)
sentencia1; {
sentencia2; sentencia1;
... sentencia2;
} ...
else { }
sentencia1; else
sentencia2; {
... sentencia1;
} sentencia2;
...

Sentencia swith
Primera opción: Segunda opción

switch (expresión) { switch (expresión)


case expresión1:sentencia1; {
sentencia2; case expresión1:sentencia1;
... sentencia2;
break; ...
case expresión2:sentencia1; break;
sentencia2; case expresión2:sentencia1;
... sentencia2;
break; ...
. break;
. .
. .
case default2: sentencia1; .
sentencia2; case default2: sentencia1;
... sentencia2;
} ...
}

Sentencia for
Primera opción: Segunda opción:

for (exp1;exp2;exp3) { for (exp1;exp2;exp3)


sentencia1; {
sentencia2; sentencia1;
... sentencia2;
} ...
}
Sentencia while
Primera opción: Segunda opción:

while (condición) { while (condición)


sentencia1; {
sentencia2; sentencia1;
... sentencia2;
} ...
}
Sentencia do-while
Primera opción: Segunda opción:

do { do
sentencia1; {
sentencia2; sentencia1;
... sentencia2;
} while (condición); ...
} while (condición);

Aunque estos formatos no son en absoluto fijos, lo que es muy importante es que quede
bien claro las sentencias que pertenecen a cada bloque, o lo que es lo mismo, donde
empieza y termina cada bloque. En bloques con muchas líneas de código y/o con
muchos anidamientos, se recomienda añadir un comentario al final de cada llave de
cierre del bloque, indicando a qué sentencia cierra. Por ejemplo:

for (exp1;exp2;exp3) {
sentencia1;
sentencia2;
...

while (condición_1) {
sentencia1;
sentencia2;
...
if (condición) {
sentencia1;
sentencia2;
...

while (condición_2) {
sentencia1;
sentencia2;
...
} /*while (condición_2)*/

} /*if*/
else {
sentencia1;
sentencia2;
...

if (condición) {
sentencia1;
sentencia2;
...
}
else {
sentencia1;
sentencia2;
...
}

} /*else*/
} /*while (condición_1)*/
} /*for*/

Por otro lado, las indentaciones para las funciones deben llevar siempre las llaves de
apertura y cierre siempre se ponen a la en la primera columna, como se muestra en la
función factorial() y en el main() del siguiente ejemplo:

double factorial(double numero)


{
double factorial = 1;

while (numero > 1)


factorial *= numero--;
return factorial;
}

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


{
double numero = 0, factorial = 1;

while (numero >= 0)


{
printf("Introduce un numero positivo: ");
scanf("%lf", &numero);

if (numero == 0)
printf("El factorial vale 1\n");
else
{
if (numero > 1)
factorial = f_factorial(numero);
printf("El factorial vale %.0lf\n", factorial);
}
}
system("PAUSE");
return 0;
}

Un código mal indentado es difícil de leer y, por tanto, de arreglar en caso de errores. El
siguiente programa muestra un ejemplo claro de programa que tiene un fallo y que,
debido a la mala indentación del mismo, es complicado averiguar donde se encuentra.
Recomendamos al alumno que trate de encontrar dicho fallo arreglando previamente la
indentación:
#include <stdio.h>
#include <stdlib.h>

#define SIZE 25

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


{
int f1, f2, c1, c2, i, j, k;
int matriz1[SIZE][SIZE], matriz2[SIZE][SIZE], matrizr[SIZE][SIZE];

printf("Introd el num de filas de la primera matriz (max.%d): ", SIZE);


scanf("%d", &f1);
printf("Introd el num de columnas de la primera matriz (max.%d): ", SIZE);
scanf("%d", &c1);
printf("Introd el num de filas de la segunda matriz (max.%d): ", SIZE);
scanf("%d", &f2);
printf("Introd el num de columnas de la segunda matriz (max.%d): ", SIZE);
scanf("%d", &c2);

if ((f1 < 1) || (f1 > SIZE) || (c1 < 1) || (c1 > SIZE) || (f2 < 1) || (f2 >
SIZE) || (c2 < 1) || (c2 > SIZE) || (c1 != f2))
{
printf("Error!");
}
else
{
printf("Introduce los elementos de la primera matriz:\n");
for (i = 0; i < f1; i++)
for (j = 0; j < c1; j++)
scanf("%d", &(matriz1[i][j]));
printf("Introduce los elementos de la segunda matriz:\n");
for (i = 0; i < f2; i++)
for (j = 0; j < c2; j++) scanf("%d", &(matriz2[i][j]));
for (i = 0; i < f1; i++)
for (j = 0; j < c2; j++)
matrizr[i][j] = 0;
for (k = 0; k < c1; k++)
matrizr[i][j] += matriz1[i][k] * matriz2[k][j];
printf("La matriz resultante es:\n");
for (i = 0; i < f1; i++) {
for (j = 0; j < c2; j++)
printf("%d ", matrizr[i][j]);
printf("\n");
}
printf("\n");
}
system("PAUSE");
return 0;
}
13. Apéndice B
Contenido del fichero de texto necesario para la solución del segundo ejemplo de la
parte de ficheros:

0 6 7
7 3 74
2 4 25
5 5 56
0 5 6
0 7 8
4 6 47
8 3 84
4 0 41
5 6 57
9 9 100
5 0 51
8 7 88
1 6 17
3 6 37
4 5 46
9 6 97
0 9 10
2 3 24
5 9 60
7 6 77
5 7 58
2 5 26
7 5 76
2 0 21
3 9 40
6 7 68
4 7 48
9 3 94
5 3 54
6 8 69
4 9 50
3 8 39
2 1 22
1 4 15
4 1 42
6 1 62
4 3 44
0 0 1
0 3 4
1 2 13
0 2 3
6 4 65
9 1 92
3 1 32
0 1 2
1 3 14
9 2 93
7 9 80
6 9 70
6 0 61
8 9 90
1 9 20
6 3 64
3 7 38
1 7 18
8 6 87
8 1 82
0 4 5
3 4 35
2 9 30
9 7 98
9 5 96
8 5 86
5 8 59
3 2 33
8 4 85
9 8 99
6 2 63
6 5 66
7 8 79
9 0 91
5 2 53
6 6 67
2 2 23
7 4 75
4 2 43
4 8 49
1 8 19
7 2 73
1 1 12
8 8 89
0 8 9
8 0 81
7 7 78
2 7 28
1 0 11
1 5 16
3 0 31
3 5 36
9 4 95
7 0 71
7 1 72
5 4 55
8 2 83
2 6 27
5 1 52
4 4 45
2 8 29
3 3 34

You might also like