Sie sind auf Seite 1von 158

Gua Practica de Programacion

en C
Septiembre de 2012

Rafael Llobet Azpitarte

EDITORIAL
`
`
UNIVERSITAT POLITECNICA
DE VALENCIA

Coleccin Acadmica

Para referenciar esta publicacin utilice la siguiente cita: LLOBET-AZPITARTE,


R.(2012). Gua prctica de programacin en C. Valencia : Editorial Universitat
Politcnica .

Primera edicin 2012


Rafael Llobet- Azpitarte
todos los nombres comerciales, marcas o signos distintivos de
cualquier clase contenidos en la obra estn protegidos por la Ley.
de la presente edicin:
Editorial Universitat Politcnica de Valncia
www.editorial.upv.es
Distribucin: pedidos@editorial.upv.es
Tel. 96 387 70 12

Imprime: By print percom sl.


ISBN: 978-84-8363-945-0
Impreso bajo demanda
Ref. editorial: 298
Queda prohibida la reproduccin, distribucin, comercializacin,
transformacin, y en general, cualquier otra forma de explotacin, por
cualquier procedimiento, de todo o parte de los contenidos de esta obra
sin autorizacin expresa y por escrito de sus autores.
Impreso en Espaa

Indice general
Indice general

1 Introduccion a la computacion

1.1 Tratamiento automatico de la informacion . . . . . . . . . . . . . . . . . . . . . . . . . .

1.2 Codicacion de la informacion: el sistema binario . . . . . . . . . . . . . . . . . . . . .

1.3 Concepto de algoritmo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.4 El proceso de la programacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.5 Lenguajes de programacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.6 El lenguaje C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.7 Compiladores e interpretes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2 Elementos basicos de un programa en C

2.1 Estructura basica de un programa en C . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2.2 Mostrando mensajes con printf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10

2.3 Variables y tipos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11

2.3.1 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11

2.3.2 Tipos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12

2.3.3 Uso de variables: declaracion y asignacion . . . . . . . . . . . . . . . . . . . . . . . .

13

2.4 Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

16

2.5 Uso avanzado de printf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17

2.6 Leyendo datos con scanf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

19

2.7 Expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

21

2.7.1 Expresiones aritmeticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

21

2.7.2 Expresiones relacionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

23


Indice
general

2.7.3 Expresiones logicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

23

2.8 Otros conceptos sobre tipos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

24

2.8.1 El tipo char . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

24

2.8.2 Conversion de tipos: casting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

26

2.9 Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

2.9.1 Declaracion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

2.9.2 El operador de direccion & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

2.9.3 El operador de indireccion * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

29

2.9.4 La constante NULL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

30

2.10 Directivas del precompilador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

31

2.10.1 Incluir cheros de cabecera: #include . . . . . . . . . . . . . . . . . . . . . . . . . .

31

2.10.2 Denicion de constantes: #dene. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

32

2.11 Ejercicios resueltos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

33

2.12 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

34

3 Estructuras de control
3.1 Sentencias de seleccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

35

3.1.1 Seleccion con if-else. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

35

3.1.2 Seleccion con switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

39

3.2 Sentencias de repeticion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

42

3.2.1 La sentencia while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

42

3.2.2 La sentencia do-while. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

44

3.2.3 La sentencia for. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

47

3.2.4 Bucles anidados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

50

3.3 Algunas tecnicas u tiles: contadores, acumuladores y banderas . . . . . . . . . . . . . .

51

3.3.1 Contadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

51

3.3.2 Acumuladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

52

3.3.3 Banderas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

53

3.4 Ejercicios resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

55

3.5 Ejercicios propuestos: condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

57

3.6 Ejercicios propuestos: bucles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

57

4 Funciones

II

35

61

4.1 Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

61

4.2 Funciones de la librera de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

61


Indice
general

4.3 Creando nuestras propias funciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

63

4.3.1 Cuestiones sintacticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

65

4.3.2 Control de ujo y transferencia de la informacion. . . . . . . . . . . . . . . . . . . . .

67

4.3.3 Parametros y argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

69

4.3.4 Operaciones de Entrada/Salida en las funciones . . . . . . . . . . . . . . . . . . . . . .

70

4.3.5 Prototipos de funciones y cheros de cabecera . . . . . . . . . . . . . . . . . . . . . .

72

4.4 Variables locales y globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

73

4.5 Paso de argumentos por valor y por referencia . . . . . . . . . . . . . . . . . . . . . . .

76

4.5.1 Paso de argumentos por valor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

76

4.5.2 Paso de argumentos por referencia. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

77

4.6 Ejercicios resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

79

4.6.1 Funciones que no devuelven ningun valor . . . . . . . . . . . . . . . . . . . . . . . . .

79

4.6.2 Funciones que devuelven un valor . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

80

4.6.3 Funciones que devuelven un valor del tipo VERDADERO/FALSO . . . . . . . . . . .

81

4.6.4 Funciones que devuelven mas de un valor . . . . . . . . . . . . . . . . . . . . . . . . .

82

4.7 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

84

5 Vectores

87

5.1 Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

87

5.2 Declaracion de vectores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

88

5.3 Acceso a los elementos de un vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

89

5.4 Operaciones con vectores: automatizacion mediante bucles. . . . . . . . . . . . . . . .

90

5.5 Paso de vectores a una funcion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

92

5.6 Devolucion de vectores en una funcion. . . . . . . . . . . . . . . . . . . . . . . . . . . .

98

5.7 Vectores multidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

98

5.8 Operaciones con matrices: automatizacion mediante bucles . . . . . . . . . . . . . . .

99

5.9 Paso de matrices a una funcion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101


5.10 Relacion entre vectores y punteros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.11 Cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
5.11.1 Entrada y salida con cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . 106
5.11.2 Funciones de manipulacion de cadenas de caracteres . . . . . . . . . . . . . . . . . . 109

5.12 Ejercicios resueltos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110


5.13 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114

III


Indice
general

6 Estructuras

117

6.1 Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117


6.2 Declaracion de variables de tipos estructurados . . . . . . . . . . . . . . . . . . . . . . . 117
6.2.1 Denicion de tipos estructurados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.2.2 Declaracion de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

6.3 Operaciones con estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119


6.3.1 Inicializacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.3.2 Acceso a los miembros de una estructura . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.3.3 Asignacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.3.4 Otras operaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

6.4 Estructuras anidadas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122


6.5 Vectores de estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.6 Punteros a estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.7 Paso de estructuras como parametros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.7.1 Pasar los miembros de forma independiente . . . . . . . . . . . . . . . . . . . . . . . . 125
6.7.2 Pasar una estructura completa por valor . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.7.3 Pasar una estructura completa por referencia . . . . . . . . . . . . . . . . . . . . . . . 127
6.7.4 Pasar un vector de estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.7.5 Devolver una estructura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128

6.8 Ejercicios resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128


6.9 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

7 Gestion de cheros

133

7.1 Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133


7.2 Tipos de cheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
7.3 Operaciones con cheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
7.3.1 Abrir y cerrar cheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
7.3.2 Lectura y escritura de cheros de texto . . . . . . . . . . . . . . . . . . . . . . . . . . 137
7.3.3 Lectura y escritura de cheros binarios . . . . . . . . . . . . . . . . . . . . . . . . . . 143

7.4 Acceso aleatorio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146


7.5 Ejercicios resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
7.6 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148

Bibliografa
IV

151

Captulo 1

Introduccion a la computacion
La informatica es la ciencia que aborda el tratamiento automatico de la informacion por
medio de ordenadores. Es importante conocer el modo en que un ordenador codica,
almacena y procesa la informacion, para poder abordar con soltura la tarea de programacion. El objetivo de este captulo es dar una pequena introduccion a la computacion y al
proceso de programacion.

1.1

Tratamiento automatico de la informacion

Cualquier sistema de tratamiento de la informacion, sea o no automatico, puede descomponerse, tal y como muestra la gura 1.1, en tres etapas basicas:
Entrada: Recogida de datos.
Proceso: Tratamiento de los datos.
Salida: Presentacion de los resultados obtenidos.
Cuando el volumen de datos a tratar es muy elevado o se requiere gran velocidad en el
proceso de los mismos o simplemente la tarea a realizar es lo sucientemente aburrida

Entrada

Proceso

Salida
S

Figura 1.1: Etapas basicas en el procesamiento de la informacion

Captulo 1. Introduccion a la computacion

o tediosa para un humano, sera deseable realizar este proceso de forma automatica. En
este sentido, los ordenadores tienen la capacidad de procesar gran cantidad de datos a una
velocidad de miles de millones de operaciones por segundo.
Para que un ordenador pueda llevar a cabo cualquier tarea, debera disponer de los elementos hardware necesarios para la entrada de datos (teclado, raton, ...), para el procesamiento
de los mismos (procesador o CPU), para la salida de resultados (monitor, impresora, ...) y
para el almacenamiento temporal o permanente de instrucciones y datos (memoria, disco
duro, ...). Ademas de todos estos elementos, sera necesario introducir en el ordenador las
instrucciones necesarias para realizar las operaciones deseadas sobre los datos, esto es,
los programas (software). Los programas son los que hacen que un ordenador sea versatil
y pueda utilizarse para realizar tareas muy diversas. En este libro aprenderemos a crear
dichos programas, pero antes sera necesario tener unos conocimientos mnimos sobre el
modo en que un ordenador codica la informacion.

1.2

Codicacion de la informacion: el sistema binario

Un ordenador funciona por impulsos electricos, de modo que u nicamente puede diferenciar entre presencia y ausencia de corriente o carga electrica, esto es, solo puede diferenciar dos estados. Comunmente identicamos a estos dos estados mediante los dgitos
0 y 1. Es por ello que cualquier informacion que deba ser procesada por un ordenador,
necesariamente tiene que estar representada mediante una secuencia de unos y ceros, esto
es, debe estar codicada en el sistema binario.
En el sistema decimal que empleamos los humanos, cada dgito tiene un peso distinto
segun la posicion que ocupa. De derecha a izquierda tenemos las unidades, decenas, centenas, etc, donde el dgito que representa las unidades tiene un peso de 100 , el de las
decenas de 101 , el de las centenas de 102 , y as sucesivamente. De modo similar, en el sistema binario, empleado por los ordenadores y otros dispositivos electronicos, cada dgito,
de derecha a izquierda, tiene un peso de 20 , 21 , 22 , etc.
Por ejemplo, el numero binario 1101 equivale al decimal 13, ya que 1 23 + 1 22 + 0
21 + 1 20 = 13.
A los sistemas decimal y binario (por poner algunos ejemplos de sistemas de numeracion)
se los denomina tambien sistema en base 10 y en base 2 respectivamente. En general,
para conocer el valor decimal de cualquier numero N expresado en base B aplicaremos la
n
formula:

xi B i
N = x0 B 0 + x1 B 1 + ... + xn B n =
i=0

donde xi son los dgitos del numero expresado en la base B.


Denominamos bit a un dgito binario 0/1 y byte a una agrupacion de 8 bits. Un bit u nicamente nos permite diferenciar entre dos estados posibles. Con una secuencia de 2 bits
2

1.2 Codicacion de la informacion: el sistema binario

pueden formarse 4 combinaciones distintas (00, 01, 10 y 11), con 3 bits 8 combinaciones
(000, 001, 010, 011, 100, 101, 110 y 111). En general, con n bits pueden formarse 2n
combinaciones. Podemos codicar los elementos de un conjunto dado (numeros enteros,
letras, colores, etc.) mediante distintas secuencias de bits. Por ejemplo, con un byte (8
bits) pueden codicarse 28 = 256 elementos. Con 4 bytes (32 bits) pueden codicarse
mas de cuatro mil millones de elementos.
Siguiendo con las unidades de medida, diremos que un kilobyte (1 KB) son 1024 bytes,
un gigabyte (1 GB) son 1024 KB y un terabyte (1 TB) son 1024 GB (esto es, mas de mil
millones de bytes).
Ya hemos visto como un ordenador puede representar numeros enteros mediante el sistema binario, pero tambien debe ser capaz de representar numeros reales, caracteres, imagenes, sonido y por supuesto instrucciones, por poner solo algunos ejemplos. Es necesario
disponer de distintos sistemas de codicacion para poder representar informacion de
cualquier tipo.
La codicacion es el proceso por el cual representamos smbolos o secuencias de un
alfabeto mediante los smbolos o secuencias de otro alfabeto, es decir, establecemos una
correspondencia biunvoca entre los smbolos de dos alfabetos distintos. En los ejemplos
anteriores hemos visto como codicar numeros enteros mediante secuencias de 1s y 0s
en lo que conocemos como sistema de codicacion BCD. Pero, tal y como acabamos de
mencionar, necesitamos otros sistemas para codicar otros objetos de otros conjuntos.
Para codicar los numeros reales en el sistema binario se utiliza lo que denominamos
representacion en coma otante o formato cientco. Por ejemplo, el numero 12.5 (el
cual acabamos de escribir en su notacion habitual o coma ja) se expresara en notacion
cientca como 0.125E2. La representacion binaria de dicho numero consiste en la utilizacion de una cierta cantidad de bits para representar lo que se denomina mantisa (0.125)
y otra cierta cantidad de bits para representar el exponente. Valiendonos del sistema BCD
utilizado para numeros enteros, codicaramos por un lado el numero 125 (mantisa en
formato normalizado) y por otro lado el exponente (2 en este ejemplo).
Otro sistema de codicacion es el ASCII (American Standard Code for Information Interchange) utilizado para la representacion de caracteres alfanumericos. Para ello se dene
una tabla de correspondencia, en la que a cada caracter se le asocia un codigo binario.
Dicha tabla de correspondencia es un estandar que utilizan todos los sistemas informaticos, lo que permite el intercambio de informacion entre distintos sistemas. Imaginemos
algo tan habitual como enviar un correo electronico. El texto en e l contenido, se representara mediante en una secuencia de 1s y 0s, resultado de codicar cada uno de los
caracteres del mensaje mediante la tabla ASCII. Dicha secuencia sera sencillamente descifrable por cualquier equipo informatico, sin mas que aplicar de nuevo la tabla de conversion ASCII en sentido inverso. Resulta evidente que sin la existencia de estandares de
codicacion, no sera posible compartir informacion entre los distintos ordenadores.
3

Captulo 1. Introduccion a la computacion

1.3

Concepto de algoritmo

Un algoritmo es una secuencia o conjunto ordenado de operaciones que permiten hallar


la solucion de un problema dado en un tiempo nito.
Aunque los algoritmos datan de tiempos babilonicos y los griegos ya disenaron algoritmos, aun famosos hoy en da, como por ejemplo el de Euclides (300 A.C.) para calcular
el maximo comun divisor de dos numeros, fue el matematico persa Al-Khowarizmi (siglo IX) el primero que diseno algoritmos pensando en su eciencia, en particular, para el
calculo de races de ecuaciones. La palabra algoritmo procede de las sucesivas deformaciones sufridas hasta hoy en da del nombre Al-Khowarizmi1 .
Como ejemplo mostraremos el famoso algoritmo de Euclides para obtener el maximo
comun divisor de dos numeros enteros:
1) Sean a y b dos enteros positivos.
2) Comparar los dos numeros de forma que:
3) Si son iguales, ese es el resultado. Finalizar.
4) Si a es menor que b intercambiar los valores de a y b.
5) Restar el segundo numero del primero y almacenar en a el sustraendo y en b el
residuo. Ir al paso 2.
Si aplicamos esta secuencia de reglas a dos numeros enteros positivos, obtendremos el
maximo comun denominador de ambos. Por ejemplo, dados los valores a=5 y b=15, a
continuacion se muestra como evolucionan las variables a y b a medida que se ejecuta el
algoritmo propuesto, hasta llegar a obtener el valor del maximo comun divisor (5):
a
b
-----------5
15
15
5
5
10
10
5
5
5
Todo algoritmo debe cumplir las 4 condiciones siguientes:
Finitud: Un algoritmo tiene que acabar siempre tras un numero nito de pasos.
1 Hay muchas variantes para el nombre de Al Khowarizmi al usar el alfabeto latino, tales como Al-Khorezmi,
Al-Khwarizmi, Al-Khawarizmi o Al-Khawaritzmi.

1.4 El proceso de la programacion

Denibilidad: Toda regla debe denir perfectamente la accion a desarrollar sin que
pueda haber ambiguedad alguna de interpretacion.
General: Un algoritmo no debe solucionar un problema aislado particular, sino
toda una clase de problemas para los que los datos de entrada y los resultados
nales pertenecen respectivamente a conjuntos especcos.
Ecacia: Se debe pretender que un algoritmo sea ecaz, esto es, que su ejecucion
resuelva el problema en el mnimo numero de operaciones posible.
Se dice que un determinado problema es decidible o computable cuando existe un algoritmo que lo resuelve. Existen, sin embargo, muchas clases de problemas no decidibles.
Estos problemas, por mucho que aumente la capacidad y velocidad de los computadores,
nunca podran resolverse.
Un ejemplo tpico de problema no decidible es el problema de la teselacion. Una tesela
(baldosa) es un cuadrado de 1x1, dividido en cuatro partes iguales por sus dos diagonales.
Cada una de estas partes tiene un color. El problema es: dada cualquier supercie nita, de
cualquier tamano (con dimensiones enteras), puede recubrirse usando teselas de forma
que cada dos teselas adyacentes tengan el mismo color en los lados que se tocan? Este es
un problema indecidible: no se puede demostrar que la supercie pueda recubrirse segun
el planteamiento del problema, pero tampoco puede demostrarse lo contrario. No existe
ningun algoritmo que resuelva este problema.

1.4

El proceso de la programacion

Un programa es la implementacion o escritura de un algoritmo en un lenguaje de programacion especco. Las fases para la correcta elaboracion de un programa son las siguientes:
1. Analisis del problema: denir el problema de la manera mas precisa posible.
del algoritmo: disenar un metodo (algoritmo) que resuelva el problema
2. Diseno
planteado.
3. Codicacion: escribir (implementar) el algoritmo en un lenguaje de programacion.
4. Vericacion: comprobacion del correcto funcionamiento del programa. Si no funciona correctamente, dependiendo del error habra que volver al paso 3, al 2 o incluso al 1.
5. Mantenimiento: desarrollo de mejoras y nuevas funcionalidades.
Estas cinco fases es lo que se denomina el ciclo de vida de un proyecto informatico. En
ocasiones el termino programacion se asocia, de forma incorrecta, u nicamente al paso
3, pero para desarrollar correctamente un programa es necesario abordar todas las fases
descritas.
5

Captulo 1. Introduccion a la computacion

1.5

Lenguajes de programacion

Un lenguaje de programacion es un conjunto de smbolos y palabras especiales que, junto


con unas reglas sintacticas perfectamente denidas, permiten implementar un algoritmo,
de modo que e ste pueda ser ejecutado por un ordenador. En denitiva, un lenguaje de
programacion permite construir un programa informatico.
En general los lenguajes de programacion pueden dividirse en:
Lenguajes de bajo nivel: Son lenguajes cuya sintaxis esta mas proxima a la maquina (al ordenador) que al humano. El lenguaje de mas bajo nivel es el codigo maquina (cuyas instrucciones son secuencias de unos y ceros), seguido por el lenguaje
ensamblador. Los lenguajes de bajo nivel resultan poco legibles para el humano.
Lenguajes de alto nivel: Son lenguajes cuya sintaxis esta mas proxima al lenguaje
natural de los humanos y, por tanto, resultan mas comprensibles. Existen multitud
de lenguajes de alto nivel como C, C++, Java, Pascal, Basic, PHP, etc. (por citar
solo unos pocos ejemplos).
A continuacion se muestra, a modo de ejemplo, un programa que realiza la suma de
dos numeros enteros, escrito en tres lenguajes de programacion diferentes: C, Pascal y
ensamblador:
Suma de dos numeros enteros en C:
1

#include <stdio.h>

2
3
4

int main() {
int a, b, c;

printf("Introduce 2 n
umeros enteros");
scanf(" %d %d", &a, &b);
c = a + b;
printf("La suma es %d\n", c);
return 0;

6
7
8
9
10
11

Suma de dos numeros enteros en Pascal:


1

program suma;

2
3
4
5

var a,b,c:integer;
begin
writeln(Introduce 2 n
umeros enteros);

1.6 El lenguaje C

readln(a,b);
c:= a + b;
writeln(La suma es

6
7
8

,c);

end

Suma de dos numeros enteros en ensamblador:

20

model small
.stack
.data
var1 db ?
.code
.startup
mov ah,01h
int 21h
sub al,30h
mov var1,al
mov ah,01h
int 21h
sub al,30h
add al,var1
mov dl,al
add dl,30h
mov ah,02h
int 21h
;.exit
end

1.6

El lenguaje C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

C es un lenguaje estructurado de alto nivel de proposito general. Esto quiere decir que sirve para distintos propositos: programas cientcos, programas de gestion, comunicacion,
manejadores de dispositivos (drivers), sistemas operativos, compiladores, etc.
C dispone de las estructuras tpicas de los lenguajes de alto nivel pero, a su vez, permite
trabajar a bajo nivel, por lo que algunos autores lo clasican como lenguaje de medio
nivel.
El lenguaje C fue desarrollado en la decada de los 70 por Kenneth Thompson y Dennis
Ritchie en los laboratorios Bell y alcanzo gran popularidad durante la decada de los 80
tras la publicacion de su denicion por parte de Brian Kernighan y el propio Ritchie2 . La
2 Brian

W. Kernighan y Dennis M. Ritchie, The C Programming Languaje, Prentice-Hall, 1978.

Captulo 1. Introduccion a la computacion

creacion del lenguaje C va ligada a otro fenomeno de la informatica: la creacion del sistema operativo UNIX, desarrollado tambien en los laboratorios Bell. El nucleo (o kernel)
del sistema operativo Unix esta escrito casi en su totalidad en lenguaje C, con excepcion
de alguna parte escrita en ensamblador. Tambien el sistema operativo Linux y muchas de
sus aplicaciones estan escritas en este mismo lenguaje.
Hoy en da el lenguaje C se sigue utilizando en multitud de programas. Ademas, muchos
otros lenguajes de programacion basan su sintaxis en C, como C++, C#, Java, PHP, Perl,
etc.

1.7

Compiladores e interpretes

Los ordenadores u nicamente entienden el lenguaje maquina. Por tanto, un programa escrito en cualquier otro lenguaje debe ser traducido a lenguaje maquina para que pueda
ser ejecutado. Este proceso de traduccion se conoce como compilacion o interpretacion,
dependiendo de como se realice.
El proceso de compilacion consiste en traducir el programa completamente a lenguaje
maquina antes de ser ejecutado. La interpretacion, por contra, consiste en ir traduciendo
las instrucciones una a una o en pequenos grupos, e ir ejecutandolas a medida que se
traducen.
Como puede deducirse, un programa compilado se ejecutara a mayor velocidad que uno
interpretado, ya que en el primer caso la traduccion se hace una u nica vez, de modo que
se almacena en un archivo aparte el programa traducido (denominado codigo maquina
o ejecutable) y a continuacion se ejecuta tantas veces como se desee el codigo maquina
generado, mientras que en el segundo caso es necesario ir traduciendo las instrucciones
durante la propia ejecucion.
El proceso de compilacion se realiza mediante un programa especial denominado compilador, mientras que el programa encargado de la interpretacion se denomina interprete.
Un programa dado, dependiendo de el lenguaje en que este escrito, debera ser compilado
o interpretado. El lenguaje C requiere del proceso de compilacion.

Captulo 2

Elementos basicos de un programa


en C
2.1

Estructura basica de un programa en C

Un programa escrito en lenguaje C tiene, en su forma mas sencilla, la siguiente estructura


basica:
int

main() {
sentencia_1;
sentencia_2;
. . .
sentencia_N;

Todo programa en C esta compuesto por una serie de instrucciones o sentencias1 , separadas por punto y coma. Estas instrucciones se encuentran dentro de la funcion main.
En el Captulo 4 se estudiaran en detalle las funciones, de momento es suciente con
entender que una funcion es un modulo o bloque de nuestro programa que contiene una
serie de instrucciones y que la funcion main (funcion principal) es el punto de inicio de
todo programa en C. Para delimitar el conjunto de instrucciones que contiene una funcion
(main en nuestro caso) se utilizan los smbolos { y }.

1A

lo largo de este libro vamos a utilizar indistintamente los terminos instruccion y sentencia.

Captulo 2. Elementos basicos de un programa en C

El programa mas sencillo que se puede escribir en C es


1
2
3

int main() {
return 0;
}

aunque este programa no hara absolutamente nada ya que la u nica instruccion que contiene (return 0) indica la nalizacion del mismo.

2.2

Mostrando mensajes con printf

El programa mas sencillo, y que ademas haga algo tangible, es aquel que muestra un
mensaje por pantalla. Por ejemplo:
1

#include <stdio.h>

2
3
4
5
6

int main() {
printf("Hola");
return 0;
}

Ya se ha dicho que la funcion main es el punto de inicio de cualquier programa en C, por


tanto la primera instruccion que se ejecuta en el programa anterior se encuentra en la lnea
4 (mas adelante explicaremos la sentencia #include). La sentencia printf muestra un
texto por pantalla, as que el resultado de ejecutar este programa sera simplemente
Hola

La sintaxis de printf, en su formato mas sencillo, es


1

printf("Texto a mostrar");

En el Apartado 2.5 se estudiaran otras formas de utilizar la sentencia printf.


Ademas de la funcion main, vemos que en este programa aparece la instruccion
1

#include <stdio.h>

Sin entrar en detalle, diremos que es necesario incluir esta instruccion para poder utilizar
printf en nuestros programas. En el Apartado 2.10 se explican este tipo de instrucciones
10

2.3 Variables y tipos de datos

con mayor profundidad. De momento u nicamente es necesario saber que cualquier programa que utilice la instruccion printf debera contener al inicio del mismo la sentencia
#include <stdio.h>.
La instruccion return 0 la pondremos habitualmente como u ltima instruccion de la funcion main. En el Captulo 4 se estudiaran con detalle las funciones y se explicara el uso
de la instruccion return.

2.3

Variables y tipos de datos

Un programa esta compuesto por instrucciones y datos. Los datos, al igual que las instrucciones, se almacenan en la memoria del ordenador durante la ejecucion del programa.
Cada dato ocupa una posicion de memoria, de modo que es posible consultar o modicar
el valor de un dato mediante el acceso a la zona de memoria en la que esta almacenado.

2.3.1

Variables

Una variable puede verse como un contenedor donde almacenar un dato determinado. En
realidad una variable representa una posicion de memoria en la que se encuentra almacenado un dato. Las variables son un mecanismo que ofrecen los lenguajes de programacion
para facilitar el acceso a los datos sin necesidad de conocer las posiciones o direcciones
de memoria en que se encuentran almacenados.
Cuando se crea una variable se le asigna un nombre que la identica. Un nombre de
variable puede constar de uno o mas caracteres y debe cumplir las siguientes restricciones:
El primer caracter debe ser una letra o el caracter subrayado ( ), mientras que el
resto pueden ser letras, dgitos o el caracter subrayado ( ). Las letras pueden ser
minusculas o mayusculas del alfabeto ingles. Por lo tanto, no esta permitido el uso
vocales acentuadas, smbolos especiales, etc.
de las letras n, N,
No pueden existir dos variables con el mismo nombre dentro de una misma funcion.
El nombre de una variable no puede ser una palabra reservada. Las palabras reservadas son identicadores propios del lenguaje. Por ejemplo, int y main son
palabras reservadas del lenguaje C.
Por ejemplo, lo siguiente seran nombres validos de variables: x, y, v1, v2, temperatura,
velocidad_maxima, Vmax.

Los siguientes nombres de variables no son validos: 1v, velocidad-maxima, Vm


ax,
#tag.

11

Captulo 2. Elementos basicos de un programa en C

2.3.2

Tipos de datos

Las variables se clasican en distintos tipos, segun la informacion que almacenan. Cuando se crea una variable, es necesario especicar a que tipo de dato pertenece, de este
modo se le asigna memoria suciente para almacenar valores del tipo especicado. En
C existen cinco tipos de datos basicos: numeros enteros, numeros reales con precision
simple, numeros reales de doble precision, caracteres y punteros.
El tipo de dato de una variable determina:
(cantidad de bytes) que ocupara la variable en la memoria del ordenador.
El tamano
El rango de valores que la variable podra almacenar.
El conjunto de operaciones que se puede realizar sobre dicha variable.
En la Tabla 2.1 se muestran los tipos basicos en C, el tamano que ocupa cada uno de ellos
en memoria y el rango de valores que pueden almacenar.2
Tabla 2.1: Tipos de datos simples en C

Tipo de dato
Caracter
Entero
Real
Real (doble precision)
Puntero

Nombre del tipo en C


char
int
float
double

(depende del tipo de puntero)

Bytes
1
4
4
8
4

Rango
-127 a 128
-2147483648 a 2147483647
3.4E-38 a 3.4E38
1.7E-308 a 1.7E308
-

Como puede observarse en la Tabla 2.1, el tipo char se puede utilizar tanto para representar caracteres como numeros enteros pequenos. En la Seccion 2.8.1 se explica este tipo de
dato con mas detalle. El tipo puntero se emplea para almacenar direcciones de memoria.
En la Seccion 2.9 se explican los punteros.
Los tipos char, int, float y double tambien existen en su version unsigned (sin signo):
unsigned char, unsigned int, unsigned float y unsigned double. Las variables de estos tipos u nicamente pueden almacenar numeros positivos, pero con la ventaja
de duplicar el rango de la parte positiva. Por ejemplo, una variable de tipo unsigned char
puede almacenar numeros enteros entre 0 y 255.
Algunos tipos de datos tambien admiten los modicadores short y long para disminuir y
aumentar respectivamente el rango de valores (y consecuentemente el espacio de memoria
empleado). Por ejemplo, una variable de tipo short int ocupa 2 bytes (en lugar de los 4
que ocupa el int) y permite almacenar valores entre -32768 y 32768. Una variable de tipo
2 En realidad el tama
no y rango de valores pueden variar en funcion de la implementacion. Los valores dados
son los habituales en un ordenador con arquitectura de 32 bits.

12

2.3 Variables y tipos de datos

unsigned short int ocupa igualmente 2 bytes, pero en este caso puede almacenar

valores entre 0 y 65535.

2.3.3

Uso de variables: declaracion y asignacion

Antes de poder utilizar una variable en un programa hay que declararla. Para ello se
emplea la siguiente sintaxis:
tipo_de_dato nombre_variable;

Por ejemplo, para declarar una variable de tipo int y nombre x utilizaramos la siguiente
sentencia:
1

int x;

Si se desea declarar mas de una variable del mismo tipo, puede hacerse separandolas por
comas del siguiente modo:
1
2

int x, y, z;
float a, b;

Las variables pueden declararse:


Al inicio de cada funcion (por ejemplo de la funcion principal main). A estas variables se las denomina variables locales.
Fuera de las funciones. A estas variables se las denomina variables globales.
Salvo en casos muy excepcionales, no se recomienda el uso de variables globales. En
la Seccion 4.4 se explica con mas detalle la diferencia entre ambos tipos de variables.
De momento, u nicamente emplearemos variables locales y, por tanto, la variable x del
ejemplo anterior la declararamos al inicio de la u nica funcion que hasta el momento
hemos visto: main.
1
2

int main() {
int x;

. . .

4
5

13

Captulo 2. Elementos basicos de un programa en C

Para almacenar una valor en una variable se utiliza la operacion de asignacion. En el


lenguaje C la asignacion se realiza mediante la operacion =. Por ejemplo, el siguiente
programa declara una variable real de nombre pi y le asigna el valor 3.14;
1
2
3

int main() {
float pi;
pi = 3.14;

. . .

5
6

En la operacion de asignacion, a la izquierda del smbolo = se debe poner siempre el


nombre de la variable que recibe el valor y a la derecha el valor que deseamos almacenar
o una operacion cuyo resultado sea un valor.
Por ejemplo la siguiente operacion de asignacion es incorrecta:
1

3.14 = pi;

Obviamente el valor que recibe la variable debe de ser compatible con el tipo de dato de
la misma. Por ejemplo, no sera adecuado declarar una variable de tipo int y tratar de
almacenar en ella un valor real, tal y como muestra el siguiente ejemplo:
1
2
3

int main() {
int x;
x = 2.75;

. . .

5
6

En este caso en la operacion de asignacion se producira el truncamiento del numero real


de modo que en la variable x se almacenara el valor 2 en lugar de 2.7.
Tambien es posible almacenar en una variable el resultado de una operacion (o expresion). El siguiente programa almacena en una variable de nombre area el resultado de
multiplicar 3.14 por 4.
1
2
3

int main() {
float area;
area = 3.14 * 4;

. . .

5
6

14

2.3 Variables y tipos de datos

En las operaciones tambien pueden aparecer variables. En este caso la variable que forma parte de la operacion se sustituye por su valor. Por ejemplo, el siguiente programa
almacena en la variable perim el resultado de la operacion 2 3,14 3.
1
2
3
4
5

int main() {
float pi, radio, perim;
pi = 3.14;
radio = 3;
perim = 2 * pi * radio;

. . .

De forma generica, la operacion de asignacion tiene siempre la siguiente estructura:


1

nombre_variable = expresion;

donde expresion puede ser un valor constante (por ejemplo x=2), una variable (por
ejemplo x=y), una operacion (por ejemplo x=2*y), etc. En la Seccion 2.7 se explica con
mas detalle el concepto de expresion. El efecto de la operacion de asignacion sera almacenar en nombre_variable el resultado de expresion.
Es muy importante tener la precaucion de que cuando se utilice una variable como parte
de una expresion, dicha variable tenga un valor asignado previamente, de lo contrario el
resultado de la expresion sera impredecible. Por ejemplo el siguiente programa, aunque
es sintacticamente correcto, su resultado es impredecible:
1
2
3
4
5

int main() {
float pi, radio, perim;
pi = 3.14;
perim = 2 * pi * radio;
radio = 3;

. . .

7
8

Debe entenderse que las instrucciones de un programa se ejecutan secuencialmente, comenzando con la primera. El error del ejemplo anterior radica en que cuando se ejecuta la
instruccion perim = 2 * pi * radio el valor de la variable radio es desconocido,
ya que todava no se le ha asignado nada. En consecuencia, el resultado almacenado en
perim sera impredecible. El hecho de ejecutar posteriormente la instruccion radio=3 ya
no cambia para nada el valor almacenado en perim.
15

Captulo 2. Elementos basicos de un programa en C

El que a una variable no le asignemos nada de forma explcita no quiere decir que no
contenga ningun valor, sino que e ste es desconocido. En el momento de declarar una
variable, e sta ya contiene un valor, resultado de lo que hubiese previamente en la zona de
memoria que se le asigna, sin embargo, como se ha dicho, este valor es impredecible.
La operacion de asignacion es destructiva. Esto quiere decir que cuando a una variable
se le asigna un valor, pierde el valor que tuviera anteriormente.

2.4

Comentarios

En un programa es posible (y recomendable) introducir comentarios. Un comentario es


un texto que incluimos en nuestros programas pero que el compilador ignora por completo. El objetivo de los comentarios es hacer los programas mas legibles, ayudando a la
comprension de los mismos.
En C existen dos modos de anadir comentarios:
Para anadir un comentario de parrafo se encierra el texto a comentar entre los
smbolos /* y */. Un comentario de parrafo puede contener mas de una lnea. Por
ejemplo:
1
2
3
4
5
6

/* Este programa calcula el


area de un c
rculo
dado su radio */
int main() {
float area, radio;
. . .
}

Para anadir un comentario de lnea se utiliza //. El texto que aparece desde //
hasta el nal de la lnea es ignorado por el compilador. Por ejemplo:
1
2
3
4

int main() {
// Declaraci
on de variables
float r; // Radio del c
rculo
float a; // Area del c
rculo

a = 3.14 * r * r;
. . .

6
7
8

16

2.5 Uso avanzado de printf

2.5

Uso avanzado de printf

En la Seccion 2.2 se ha explicado como mostrar un texto simple con printf. En ocasiones, junto con el texto, necesitaremos mostrar el valor de alguna variable. Tomemos como
ejemplo el siguiente programa que calcula la suma de dos variables y muestra de forma
incorrecta el resultado:
1

#include <stdio.h>

2
3
4

int main() {
int a, b, c;

a = 2;
b = 3;
c = a + b;
printf("La suma de a y b es c");
return 0;

6
7
8
9
10
11

El error del programa anterior radica en que la instruccion printf, tal y como se ha
escrito, mostrara literalmente el texto especicado. Esto es, la ejecucion de este programa
mostrara por pantalla el mensaje:
La suma de a y b es c

Si lo que se pretenda es que el programa mostrase


La suma de 2 y 3 es 5

entonces la instruccion printf se debera haber escrito del siguiente modo:


1

printf("La suma de %d y %d es %d", a, b, c);

Podemos observar que en la instruccion printf aparece en primer lugar un texto encerrado entre dobles comillas, seguido de una serie de variables separadas por comas. El
texto encerrado entre comillas contiene una serie de codigos especiales %d. Los codigos %
(existen mas aparte de %d) indican que esta parte del texto debe ser sustituida por el valor
de alguna expresion (una variable en el caso mas sencillo). Concretamente, el codigo %d
indica que, en la posicion en la que aparece, debe mostrarse el valor de una expresion que
de como resultado un valor entero (por ejemplo una variable de tipo int). La expresion
o variable a mostrar sera alguna de las que aparezcan a continuacion del texto encerra17

Captulo 2. Elementos basicos de un programa en C

do entre comillas. La asociacion entre los codigos % y las expresiones que aparecen a
continuacion se hace por orden de aparicion. En consecuencia, en el ejemplo anterior, el
primer codigo %d se asocia a la variable a, el segundo a la variable b y el tercero a c.
Debe observarse que es necesario que la instruccion printf contenga tantas expresiones
(variables en el ejemplo anterior) como codigos %.
En la Tabla 2.2 se muestra los codigos % (especicadores) que pueden aparecer en una
instruccion printf. En la primera parte de la tabla se muestran los que se usan de forma
mas habitual y en los que, de momento, nos centraremos.
Tabla 2.2: Especicadores mas habituales.

Especicador
%d o %i
%f
%c
%u
%x o %X
%o
%e o %E
%g o %G
%s

Tipo de expresion con que se asocia


Entero
Real
Caracter
Entero sin signo
Entero (se muestra en notacion hexadecimal)
Entero (se muestra en notacion octal)
Real de doble precision (se muestra en notacion exponencial)
Real de doble precision (se muestra en notacion cientca)
Cadena de caracteres (string)

Ademas de los especicadores mostrados en la Tabla 2.2, el texto de una instruccion


printf puede contener otras secuencias de caracteres que se interpretan de un modo
especial:
\n: Se sustituye por un salto de lnea
\t: Se sustituye por un tabulador
\\: Muestra el caracter \

A continuacion se muestra un ejemplo del uso de printf:


1

#include <stdio.h>

2
3
4
5
6

int main() {
int entero = 47;
float real = 128.75;
char car = A;

7
8
9
10

18

printf("Este texto aparece en la primera l


nea.");
printf("Este tambi
en.\nAhora en la segunda.\n");
printf("\tEsto aparece tabulado.\n");

2.6 Leyendo datos con scanf

printf("Mostramos el car
acter \\.\n");
printf("Variable entera: %d\n", entero);
printf("Variable real: %f\n", real);
printf("Variable de tipo car
acter: %c\n", car);
printf("Variable entera en hexadecimal: %X\n", entero);
printf("Variable entera en octal: %o\n", entero);
printf("Variable real en notaci
on exponencial: %E\n",real);
printf("Variable real en notaci
on cient
fica: %G\n", real);

11
12
13
14
15
16
17
18
19

return 0;

20
21

El ejemplo anterior produce la siguiente salida:


Este texto aparece en la primera l
nea.Este tambi
en.
Ahora en la segunda.
Esto aparece tabulado.
Mostramos el car
acter \.
Variable entera: 47
Variable real: 128.750000
Variable de tipo car
acter: A
Variable entera en hexadecimal: 2F
Variable entera en octal: 57
Variable real en notaci
on exponencial: 1.287500E+02
Variable real en notaci
on cient
fica: 128.75

2.6

Leyendo datos con scanf

Habitualmente es necesario que sea el usuario del programa quien introduzca los datos.
La instruccion scanf permite almacenar en variables los datos que el usuario introduce a
traves del teclado. La sintaxis de esta instruccion es la siguiente:
scanf("especificadores", lista_de_variables);

donde especificadores contiene una secuencia de codigos % (tantos como datos se


quieran introducir) y lista_de_variables contiene la lista de variables, separadas por
coma, donde se almacenaran los datos. Cada una de estas variables debe ir precedida por
el smbolo & (salvo en algun caso especial que ya se comentara en la Seccion 5.11). Los
especicadores o codigos % que usaremos de momento en la instruccion scanf seran %d
(o su equivalente %i), %f y %c para leer variables de tipo entero, real o caracter respectivamente.
Por ejemplo, la siguiente instruccion almacena en la variable x un numero que el usuario
debe introducir por teclado:
19

Captulo 2. Elementos basicos de un programa en C

scanf(" %d", &x);

En este caso la variable x debera ser de tipo int.


El porque las variables deben ir precedidas por el smbolo & es algo que se vera en la
Seccion 2.9. De momento basta con saber que es necesario hacerlo de este modo.
Cuando se ejecuta una instruccion scanf el programa se detiene en espera de que el
usuario introduzca los datos necesarios (debe pulsarse la tecla enter para nalizar la introduccion). En el momento en que los datos han sido introducidos, e stos se almacenan en las
variables especicadas y el programa continua. A continuacion se muestra un programa
completo que solicita dos numeros y los suma:
1

#include <stdio.h>

2
3
4

int main() {
float a, b, c;

printf("Introduzca un n
umero: ");
scanf(" %f", &a);
printf("Introduzca otro n
umero: ");
scanf(" %f", &b);
c = a + b;
printf("La suma es %f\n", c);
return 0;

6
7
8
9
10
11
12
13

Como se puede observar en el programa anterior, es habitual que cada instruccion scanf
vaya precedida de un printf para indicar al usuario de lo que debe hacer.
Tambien es posible leer mas de un dato con una u nica instruccion scanf. Por ejemplo:
1
2

printf("Introduzca dos n
umeros: ");
scanf(" %f %f", &a, &b);

En este caso el programa se detendra en la instruccion scanf hasta que el usuario haya
introducido los dos valores.

20

2.7 Expresiones

2.7

Expresiones

Una expresion es una combinacion de constantes, variables, smbolos de operacion, parentesis y funciones. Por ejemplo, lo siguiente son expresiones validas en C:
1
2
3

a-(b+3)*c
2*3.1416*r
sin(x)/2

En la u ltima expresion del ejemplo aparece una llamada a la funcion seno (sin). Las funciones se estudiaran en el Captulo 4.
a un valor determinado, esto es, el resultado de una expreLas expresiones se evaluan
sion siempre es un valor (entero o real). Las expresiones en C pueden clasicarse en tres
categoras:
Aritmeticas
Relacionales
Logicas
En los siguientes apartados se explica cada una de ellas.

2.7.1

Expresiones aritmeticas

Las expresiones aritmeticas son aquellas que utilizan los operadores +, -, *, / y %. El


resultado de una expresion aritmetica es un valor entero o real.
El operador * representa la multiplicacion. El operador / realiza la division, si bien debe
llevarse especial cuidado con esta operacion ya que, dependiendo de los operandos, realizara la division entera o la real. Si al menos uno de los dos operandos es un valor real,
entonces se hara la division real, pero si ambos operandos son enteros se realizara la division entera. Por ejemplo, el resultado de la expresion 5 / 2 es 2 (division entera) y no
2.5 (division real) ya que tanto 5 como 2 son numeros enteros. Sin embargo, el resultado
de 5.0 / 2 es 2.5 ya que, en este caso, alguno de los operandos (el primero de ellos en
este ejemplo) es un numero real.
El operador % realiza la operacion modulo o resto de la division entera. Por ejemplo
11 % 3 = 2 ya que la division entera de 11 y 3 da cociente 3 y resto 2. En general, el
resultado de a % b sera un numero comprendido entre 0 y b-1, ya que el resto debe de ser
necesariamente menor que el divisor. La operacion modulo u nicamente puede realizarse
entre numeros enteros y puede ser u til para diversas situaciones. Por ejemplo, para obtener
la u ltima cifra de un numero n basta con hacer n % 10 (por ejemplo, 127 % 10 = 7). Si
21

Captulo 2. Elementos basicos de un programa en C

se desean las dos u ltimas cifras habra que hacer n % 100 (127 % 100 = 27). Para saber
si un numero n es par o impar habra que calcular n % 2. Si el resultado de esta operacion
es 0 quiere decir que n es par, mientras que si el resultado es 1 signicara que n es impar.
Para evaluar las expresiones aritmeticas existen unas reglas de prioridad y asociatividad:
Las operaciones entre parentesis se evaluan primero.
Los operadores *, / y % se evaluan antes (tienen mayor prioridad) que + y -.
Los operadores de igual prioridad se evaluan de izquierda a derecha.
Por ejemplo, el resultado de 3 + 2 * 4 es 11 (se realiza en primer lugar la multiplicacion) mientras que (3 + 2) * 4 da 20 (se realiza en primer lugar la suma).
Operadores de incremento y decremento
Los operadores ++ y -- sirven, respectivamente, para incrementar y decrementar en una
unidad el valor de una variable. Por ejemplo, n++ incrementa en uno el valor de la variable n, mientras que n-- lo decrementa. Como puede observarse, se trata de operadores
unarios (unicamente tienen un operando).
La operacion n++ es equivalente a n = n + 1 (se calcula n + 1 y el resultado se
almacena en la propia variable n, lo que provoca que el valor de n acabe incrementandose
en 1). De forma analoga, n-- es equivalente a n = n - 1.
Tambien es posible escribir el operador antes del operando (++n y --n). Esto no introduce
ninguna diferencia cuando la operacion se hace de forma aislada, aunque es importante
tenerlo en cuenta cuando esta operacion se combina con otras dentro de la misma expresion, ya que modica el orden en el que se realiza el incremento o decremento. Por
ejemplo x / y++ calcula primero la division x / y y a continuacion incrementa la variable y, mientras que x / ++y incrementa en primer lugar el valor de y y a continuacion
realiza la division (con el valor de y ya incrementado).
Operadores reducidos
Los operadores reducidos mas habituales son +=, -=, *= y /=. Estos operadores permiten
expresar de forma mas compacta algunas operaciones. Por ejemplo, x += 2 equivale a
la operacion x = x + 2 (esto es, incrementa el valor de la variable x en dos unidades).
x *= 5 equivale a la operacion x = x * 5.
A continuacion se muestra un ejemplo con el uso de las operaciones aritmeticas vistas:

22

2.7 Expresiones

#include <stdio.h>

2
3
4

int main() {
int x, y, z;

x = 6;
x--;
y = x / 2;
z = y++;

6
7
8
9
10

x += 3;
x *= 2;
z = x % 7;

11
12
13

//
//
//
//
//
//
//

x vale 5
y vale 2 (se realiza la divisi
on entera)
z vale 2 e y vale 3 (primero se realiza
la asignaci
on y luego el incremento)
x vale 8
x vale 16
z vale 2 (el resto de dividir 16 entre 7)

14

return 0;

15
16

2.7.2

Expresiones relacionales

Las expresiones relacionales son aquellas que comparan valores. Para ello se utilizan los
operadores <, <=, >, >=, == y != (que se corresponden, respectivamente, con las operaciones menor, menor o igual, mayor, mayor o igual, igual y distinto). El resultado de una
expresion relacional es 1 cuando el resultado de la comparacion es cierto y 0 cuando el
resultado es falso. Por ejemplo, dado x = 5, el resultado de la operacion x >= 5 es 1
(cierto) y el de x != 5 es 0 (falso).
No debe confundirse la operacion de comparacion == con la operacion de asignacion =
(explicada en la Seccion 2.3.3). Por ejemplo, la expresion x==5 comprueba si la variable
x vale 5. En caso armativo el resultado de esta expresion sera 1 y de lo contrario dara 0.
Por contra, la operacion x=5 almacena el valor 5 en la variable x.

2.7.3

Expresiones logicas

Las expresiones logicas son aquellas que utilizan los operadores logicos &&, ||, y !, los
cuales se corresponden, respectivamente, con las operaciones Y, O y NO (AND, OR
y NOT). Las expresiones logicas permiten combinar otras expresiones cuyo resultado es
verdadero/falso (1/0). A su vez, el resultado de una expresion logica es 1 (verdadero) o 0
(falso).
Por ejemplo, dadas las variables a=5 y b=2:
a != 0 && b >= a (a distinto de cero y b mayor o igual que a) se evalua a 0

(falso).
23

Captulo 2. Elementos basicos de un programa en C

a == 4 || a+b < 8 (a igual a cuatro o a+b menor que ocho) se evalua a 1 (cierto).
!(b > a) (no b mayor que a) se evalua a 1 (cierto).

Los siguientes puntos resumen el uso de los operadores logicos:


exp1 && exp2 se evalua a cierto cuando tanto exp1 como exp2 son ciertas. En

cualquier otro caso se evalua a falso.


exp1 || exp2 se evalua a cierto cuando alguna de las expresiones exp1 o exp2
son ciertas. Se evalua a falso cuando tanto exp1 como exp2 son falsas.
!exp1 se evalua a cierto cuando exp1 es falso y viceversa.

Estos tres puntos se resumen en la Tabla 2.3 denominada taba de verdad.


Tabla 2.3: Tabla de verdad de los operadores logicos.

exp1
0
0
1
1

2.8
2.8.1

exp2
0
1
0
1

exp1 && exp2


0
0
0
1

exp1 || exp2
0
1
1
1

!exp1
1
1
0
0

Otros conceptos sobre tipos de datos


El tipo char

Como se ha visto en la Seccion 2.3.2, el lenguaje C utiliza el tipo char para almacenar
caracteres. En realidad el ordenador trabaja u nicamente con numeros, por lo que cada
caracter se representa mediante un numero. Esto es, el tipo char realmente almacena
numeros, los cuales pueden ser interpretados como caracteres.
La interpretacion de que caracter hay almacenado en una variable de tipo char se realiza
mediante una tabla de conversion. La tabla mas conocida (por ser la primera que aparecio)
es el estandar ASCII (American Standard Code for Information Interchange). En la Tabla 2.4 se muestra la tabla ASCII. En esta tabla, los codigos del 0 al 31 en realidad no son
caracteres imprimibles sino que representan codigos de control (por ejemplo, el codigo
13 representa el salto de lnea o Carriage Return, mientras que el codigo 27 representa
la tecla Escape). Entre el 65 y el 90 se codican las letras mayusculas, mientras que las
minusculas ocupan los codigos del 97 al 122. Los caracteres numericos (dgitos del 0 al
9) aparecen entre los codigos 48 al 57.
24

2.8 Otros conceptos sobre tipos de datos

Tabla 2.4: Tabla ASCII


0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

NUL
SOH
STX
ETX
EOT
ENQ
ACK
BEL
BS
HT
LF
VT
FF
CR
SO
SI

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

DLE
DC1
DC2
DC3
DC4
NAK
SYN
ETB
CAN
EM
SUB
ESC
FS
GS
RS
US

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

SP
!

#
$
%
&

(
)
*
+
,
.
/

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

0
1
2
3
4
5
6
7
8
9
:
;
<
=
>
?

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

@
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

P
Q
R
S
T
U
V
W
X
Y
Z
[

\
]

96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

a
b
c
d
e
f
g
h
i
j
k
l
m
n
o

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

p
q
r
s
t
u
v
w
x
y
z
{
|
}

DEL

Debe aclararse que la tabla ASCII se denio para 7 bits, lo que permite representar 128
caracteres. Esto es insuciente para muchas lenguas que utilizan caracteres que no estan
C, etc.). Por ello se denieron otros
representados en esta tabla (vocales acentuadas, N,
estandares de 8 bits que permitan extender el numero de caracteres a 256 (por ejemplo el
ISO-8859-1).
En C los caracteres deben encerrarse entre comillas simples. Por ejemplo, para almacenar
el caracter A en una variable lo haremos del siguiente modo:
1
2

char c;
c = A;

Dado que las variables de tipo char realmente almacenan numeros, es posible realizar
operaciones aritmeticas con este tipo de variables, tal y como se muestra en el siguiente
programa:
1

#include <stdio.h>

2
3
4

int main() {
char c;

5
6

c = A;

7
8
9
10
11
12

printf(" %d\n",
printf(" %c\n",
c += 5;
printf(" %d\n",
printf(" %c\n",

c);
c);
c);
c);

//
//
//
//
//
//
//

Almacena en c el n
umero 65 (c
odigo
ascii de la letra A)
Imprime el n
umero 65
Imprime la letra A
Almacena en c el n
umero 70
Imprime el n
umero 70
Imprime una F (cuyo c
odigo ascii es 70)

25

Captulo 2. Elementos basicos de un programa en C

c = 68;
c++;
printf(" %d\n", c);
printf(" %c\n", c);
c = 2;

13
14
15
16
17
18

printf(" %d\n", c);


printf(" %c\n", c);

19
20

//
//
//
//
//
//
//
//

Almacena en c el n
umero 68
c vale 69
Imprime el n
umero 69
Imprime la letra E
Almacena en c el n
umero 50 (c
odigo
ascii del car
acter 2)
Imprime el n
umero 50
Imprime el car
acter 2

21

return 0;

22
23

2.8.2

Conversion de tipos: casting

En ocasiones se utilizan expresiones en las que no todos los datos son del mismo tipo.
Imaginemos el siguiente codigo:
1
2

float f;
f = 5;

La instruccion f = 5 asigna un valor entero a una variable de tipo real, produciendose


una conversion de entero a real. En este caso se produce una conversion sin perdida de
precision. Otras conversiones, sin embargo, pueden conllevar perdida de precision, tal y
como se muestra en el siguiente codigo:
1
2
3

float radio = 2.5;


int area;
area = 3.1416 * r * r;

En este caso el resultado de la operacion 3.1416 * r * r se convierte a entero para poder almacenarlo en la variable area, de tipo int, con la consiguiente perdida de
precision.
Si ordenamos los tipos de datos de menor a mayor capacidad (char, int, float, double),
cualquier conversion de un tipo de menor capacidad a otro de mayor puede realizarse sin
perdida de precision, mientras que una conversion en el otro sentido (de mayor a menor
capacidad) puede conllevar perdida de informacion. El compilador realiza de forma automatica estos cambios de tipo, si bien en los casos que impliquen perdida de informacion
puede generar un warning para advertir al programador del posible error.
En ocasiones es el propio programador el que desea hacer esta conversion de forma
explcita, bien para dejar claro que desea hacer la conversion y evitar los posibles warnings dados por el compilador, bien para forzar un cambio de tipo en aquellos casos en los
26

2.8 Otros conceptos sobre tipos de datos

que el compilador no la hara de forma automatica. Esta conversion explcita se conoce


por el nombre de casting. Para realizar el cambio de tipo de una expresion debe escribirse el nuevo tipo entre parentesis a la izquierda de la expresion, tal y como se muestra a
continuacion::
1
2
3

float radio = 2.5;


int area;
area = (int)(3.1416 * r * r);

Mediante esta conversion explcita el programador deja claro que es consciente del cambio de tipo que se va a producir, con la consecuente perdida de precision.
Tambien hay ocasiones en las que se desea forzar un cambio de tipo ya que el compilador
no lo hace de manera automatica. Tomemos el siguiente ejemplo:
1
2
3

int suma=5, n=2;


float media;
media = suma/n;

En este caso el valor almacenado en media sera 2.0 y no 2.5. Esto es debido (tal y como
se explico en la Seccion 2.7.1) a que la operacion suma/n realiza la division entera, ya
que ambos operandos (suma y n) son enteros. El hecho de que el resultado se almacene
posteriormente en una variable de tipo float no cambia para nada la situacion ya que en
este caso la perdida de precision se produce en la propia operacion de division y no en la
posterior asignacion. Para solucionar este problema debera forzarse el cambio de tipo de
al menos uno de los operandos para que se realice la division real, tal y como se muestra
a continuacion:
1
2
3

int suma=5, n=2;


float media;
media = (float)suma/n;

En este caso la variable suma se cambia temporalmente a tipo float, con lo que se
realiza la division real. Debe quedar claro que este cambio de tipo afecta u nicamente
a la operacion en la que aparece el casting, en el resto del programa la variable suma
seguira siendo de tipo entero.

27

Captulo 2. Elementos basicos de un programa en C

2.9

Punteros

La memoria del ordenador puede verse como una serie de celdas en las que se almacena la
informacion. Cada una de estas celdas tiene asociada una direccion de memoria. Cuando
declaramos una variable (por ejemplo int x), a e sta se le asigna una celda de memoria
con una direccion conocida. Hasta ahora no nos habamos preocupado por la direccion
de memoria en la que se ubican nuestras variables, nos bastaba con conocer su nombre
para poder acceder al dato almacenado en las mismas. Este es el momento de empezar a
preguntarnos en que direccion se encuentran las variables de nuestros programas.
Una variable de tipo puntero es una variable que almacena direcciones de memoria. Normalmente se utilizan para almacenar la direccion de memoria en la que se encuentra
alguna otra variable. Cuando una variable de tipo puntero (por ejemplo p) contiene la
direccion de memoria de alguna otra variable (por ejemplo x) decimos que el puntero p
apunta a la variable x. Como veremos mas adelante, una variable de tipo puntero debe conocer el tipo de dato de la variable a la cual apunta (o, en general, el tipo de dato
almacenado en la direccion de memoria que contiene).

2.9.1

Declaracion

Para declarar una variable de tipo puntero se emplea la siguiente sintaxis:


tipo_del_dato_apuntado * nombre_del_puntero;

Por ejemplo
1

int * p;

declara una variable puntero de nombre p que debe utilizarse para almacenar direcciones
de memoria en las que se encuentren datos de tipo int.

2.9.2

El operador de direccion &

El operador de direccion & (tambien llamado de referencia) obtiene la direccion de memoria de una variable. Por ejemplo, dada una variable x, la operacion &x obtiene la direccion de memoria donde se encuentra x, tal y como se muestra en el siguiente ejemplo:
1

#include <stdio.h>

2
3
4
5

28

int main() {
int x = 3;
printf("La variable x contiene el valor %f y se encuentra en
la direcci
on %d\n", x, &x);

2.9 Punteros

return 0;

6
7

Si quisieramos almacenar la direccion de x en otra variable, entonces esta otra variable


debera ser de tipo puntero, tal y como se muestra en el siguiente ejemplo:
1

#include <stdio.h>

2
3
4
5

8
9

int main() {
int x = 3;
int * p;
// Declaramos una variable de tipo puntero a
entero
p = &x;
// Guardamos en p la direcci
on de memoria de la
variable x
printf("La variable x contiene el valor %f y se encuentra en
la direcci
on %d\n", x, p);
return 0;
}

2.9.3

El operador de indireccion *

El operador de indireccion * (tambien llamado de deferencia) se aplica sobre punteros y


devuelve el valor de la variable apuntada por el puntero. A continuacion se muestra un
ejemplo:
1

#include <stdio.h>

2
3
4
5

6
7
8
9
10
11
12
13

int main() {
int x = 3, y;
int * p;
//
entero
p = &x;
//
y = *p;
//
//
//
*p = 2;
p = &y;
//
*p = 5 * 3; //
return 0;
}

Declaramos una variable de tipo puntero a


Guardamos en p la direcci
on de x
Almacenamos en y el valor de la variable
apuntada por p, esto es, el valor de x
Ahora x vale 2
p contiene la direcci
on de y (p apunta a y)
Ahora y vale 15

En general, si un puntero p contiene la direccion de cierta variable x (p=&x), entonces la


expresion *p equivale a x. Por ejemplo, la operacion *p=2 es equivalente a x=2.
29

Captulo 2. Elementos basicos de un programa en C

Como puede verse, el operador * tiene tres usos distintos en C:


Para declarar variables de tipo puntero. Por ejemplo, int * p;
Como operador de indireccion. Por ejemplo, *p = 2;
Como operador de multiplicacion. Por ejemplo, 5 * 3;
Como puede observarse en el ejemplo anterior, la instruccion *p = 5 * 3 utiliza el *
con dos signicados distintos.
El porque nos interesa almacenar la direccion de una variable en un puntero y luego acceder a dicha variable a traves de su puntero en lugar de utilizar su propio nombre es algo
que a estas alturas es difcil de explicar, aunque debe decirse que es de gran importancia entender el manejo de punteros para poder entender en profundidad muchos aspectos
de la programacion. De forma muy simplicada diremos que en ocasiones puede haber
alguna parte de un programa en la que lo u nico que se conozca de algunas variables sea
las direcciones de memoria que ocupan pero no sus nombres (los cuales pueden incluso ni existir). En estos casos el u nico modo de acceder a dichas variables es mediante
el manejo de punteros. De momento basta con entender la sintaxis y manejo de punteros, en captulos posteriores, en la Seccion 4.5.2, se vera una situacion en la que resultan
imprescindibles.

2.9.4

La constante NULL

Una variable de tipo puntero, ademas de direcciones de memoria, tambien puede almacenar la constante NULL. Este valor sirve para indicar que el puntero no contiene ninguna
direccion de memoria valida. Por ejemplo:
1
2

int * p;
p = NULL;

// p no contiene ninguna direcci


on de memoria

Para poder utilizar la constante NULL debe incluirse el chero stdlib.h mediante la
instruccion
1

30

#include <stdlib.h>

2.10 Directivas del precompilador

2.10

Directivas del precompilador

El precompilador es un programa que analiza y modica el chero fuente antes de la


compilacion real, en funcion de ciertas directivas de compilacion que podemos incluir en
nuestros programas. Todas las directivas de compilacion comienzan por el caracter # (por
ejemplo, #include) y, a diferencia de las sentencias de C, no llevan punto y coma al
nal. El precompilador, ademas, elimina todos los comentarios del codigo fuente, ya que
e stos no forman parte del lenguaje y por tanto no seran entendidos por el compilador. A
continuacion se explican las directivas #include y #define que utilizaremos de forma
habitual en nuestros programas:

2.10.1

Incluir cheros de cabecera: #include

La directiva #include se utiliza para incluir en nuestro chero fuente el contenido de


otros cheros. Habitualmente los cheros que se incluyen son los denominados cheros
de cabecera (header les). La sintaxis empleada es:
#include <fichero>

o
#include "fichero"

Por ejemplo, la siguiente directiva incluye el chero de cabecera stdio.h


1

#include <stdio.h>

El chero stdio.h (standard input output header) contiene el codigo necesario para que
se compilen correctamente todas las funciones relacionadas con la entrada/salida (input/output) de nuestro programa, como por ejemplo printf y scanf. En la Seccion 4.3.5
se explica con mas detalle los cheros de cabecera.
La diferencia entre utilizar los corchetes angulados (<...>) y las dobles comillas ("...")
radica en que, en el primer caso, el chero incluido sera buscado en los directorios que
el compilador tenga congurados a tal efecto, mientras que en el segundo caso el chero
incluido sera buscado en el mismo directorio en el que se encuentra el chero fuente.

31

Captulo 2. Elementos basicos de un programa en C

2.10.2

Denicion de constantes: #dene

La directiva #define se emplea para denir constantes (en realidad tambien para denir
macros, aunque nosotros no la emplearemos con esta nalidad). La sintaxis empleada es:
#define nombre_constante valor

Por ejemplo, la directiva


1

#define PI 3.1416

dene la constante PI con el valor especicado. Una vez denida una constante, e sta
puede utilizarse a lo largo de todo el programa:
1
2

#include <stdio.h>
#define PI 3.1416

3
4
5

int main() {
float radio, perim;

printf("Introduce valor del radio: ");


scanf(" %f", &radio);
perim = 2 * PI * radio;
printf("El per
metro de la circunferencia es %f\n", perim);
return 0;

7
8
9
10
11
12

En este caso el precompilador buscara el texto PI como parte de alguna expresion y lo


sustituira por 3.1416, de modo que cuando se inicie el proceso de compilacion la lnea
1

perim = 2 * PI * radio;

habra sido sustituida por


1

32

perim = 2 * 3.1416 * radio;

2.11 Ejercicios resueltos

2.11

Ejercicios resueltos

1. Escribir un programa que solicite la base y la altura de un rectangulo y muestre por


pantalla el a rea y el permetro.

SOLUCION:
1

#include <stdio.h>

2
3
4

int main() {
float base, alt, area, perim;

// Pedir datos de entrada


printf("Introduce la base: ");
scanf(" %f", &base);
printf("Introduce la altura: ");
scanf(" %f", &alt);

6
7
8
9
10
11

// Calcular resultados
area = base * alt;
perim = 2*base + 2*alt;

12
13
14
15

// Mostrar resultados
printf("Area = %f\n", area);
printf("Per
metro = %f\n", perim);

16
17
18
19

return 0;

20
21

2. Indicar que mostrara por pantalla el siguiente programa:


1

#include <stdio.h>

2
3
4
5

int main() {
int a, b=5, res;
char c=A;

6
7
8
9
10
11
12
13
14
15

a = 2;
printf(" %d\n",
a *= 2;
b--;
printf(" %d\n",
c += a;
printf(" %c\n",
res = ( c==A
printf(" %d\n",

b/a);

a==b);
c);
|| ( a > 0 &&
res);

b < 5 ) );

16

33

Captulo 2. Elementos basicos de un programa en C

return 0;

17

18

SOLUCION:
2
1
E
1

2.12

Ejercicios propuestos

1. Se pretende calcular el importe del combustible que consume un vehculo durante


un determinado trayecto. Para ello se pide escribir un programa que solicite como
datos de entrada: el consumo medio del vehculo (litros/100 km.), los kilometros
del trayecto y el precio del litro de combustible. Con esos datos, el programa debera calcular y mostrar: el total de litros consumidos y el coste total.
2. Indicar que mostrara por pantalla el siguiente programa:
1

#include <stdio.h>

2
3
4
5

int main() {
int a, b, c;
int * p1, * p2;

a = 2;
p1 = &b;
p2 = &c;
*p1 = a * 2;
*p2 = *p1 + 3;
printf("a= %d b= %d c= %d\n", a, b, c);
p2 = &a;
*p1 += *p2;
printf("a= %d b= %d c= %d\n", a, b, c);
c = *p1 + *p2;
printf("a= %d b= %d c= %d\n", a, b, c);
return 0;

7
8
9
10
11
12
13
14
15
16
17
18
19

34

Captulo 3

Estructuras de control
En los programas que hemos realizado hasta ahora, cada una de las instrucciones se ejecuta en modo secuencial, una tras otra y una u nica vez. Sin embargo, es habitual que
los programas necesiten ejecutar, en funcion de cierta condicion, un grupo de instrucciones u otro (ejecucion condicional), o que, por ejemplo, requieran ejecutar un bloque de
instrucciones mas de una vez (bucle). Estas situaciones se resuelven mediante lo que se
denomina sentencias de seleccion y sentencias de repeticion.

3.1

Sentencias de seleccion

Las sentencias de seleccion permiten ejecutar unas instrucciones u otras, en funcion de


cierta condicion. En este sentido, el lenguaje C dispone de las instrucciones if-else y
switch.

3.1.1

Seleccion con if-else

La instruccion if-else permite ejecutar un bloque de instrucciones u otro, dependiendo


de que la evaluacion de una expresion logica resulte ser verdadera o falsa. La sintaxis
general de esta instruccion es la siguiente:
if( expresion ) {
instruccion_1_1;
instruccion_1_2;
. . .
instruccion_1_N;
}
else {
instruccion_2_1;

35

Captulo 3. Estructuras de control

instruccion_2_2;
. . .
instruccion_2_N;
}

Si la expresion de la instruccion if se evalua a 1 (cierto), se ejecutaran las instrucciones


1_1 a 1_N, en caso contrario se ejecutaran las instrucciones 2_1 a 2_N.
Por ejemplo, el siguiente programa indica si cierta nota introducida por teclado corresponde a un aprobado o a un suspenso:
1

#include <stdio.h>

2
3
4

int main() {
float nota;

printf("Introduce el valor de la nota: ");


scanf(" %f", &nota);
if( nota >= 5 ) {
printf("APROBADO\n");
}
else {
printf("SUSPENDIDO\n");
}
return 0;

6
7
8
9
10
11
12
13
14
15

Cuando alguno de los bloques if o else consta de una u nica instruccion, entonces las
llaves son opcionales.
if( expresion )
instruccion_1;
else
instruccion_2;

Por lo tanto la instruccion if-else del ejemplo anterior tambien podra haberse escrito
como:
1
2
3
4

if( nota >= 5 )


printf("APROBADO\n");
else
printf("SUSPENDIDO\n");

Por otro lado, debe resaltarse que el bloque else es opcional. En el siguiente ejemplo se
utiliza una instruccion if sin el bloque else:
36

3.1 Sentencias de seleccion

#include <stdio.h>

2
3
4
5

int main() {
float precio;
char aplicar_descuento;

printf("Introduce el precio del art


culo: ");
scanf(" %f", &precio);
printf("Desea aplicar descuento? (s/n) ");
scanf(" %c", &aplicar_descuento);

7
8
9
10
11

if( aplicar_descuento == s )
precio = precio * 0.9;
// Aplico un descuento del 10 %

12
13
14

printf("Total a pagar: %.2f\n", precio);

15
16

return 0;

17
18

Las instrucciones if-else pueden anidarse, esto es, tanto dentro del bloque if como del
else pueden aparecer otras instrucciones if-else. En el siguiente ejemplo se muestra
un programa que solicita tres numeros y muestra el mayor de ellos mediante el uso de
if-else anidados:
1

#include <stdio.h>

2
3
4

int main() {
float a, b, c, max;

5
6
7

printf("Introduce tres n
umeros: ");
scanf(" %f %f %f", &a, &b, &c);

8
9
10
11
12
13
14
15
16
17
18
19
20
21

if ( a > b ) { // El m
aximo ser
a a o c
if( a > c )
max = a;
else
max = c;
}
else {
// El m
aximo ser
a b o c
if( b > c )
max = b;
else
max = c;
}
printf("El m
aximo es %f\n", max);

37

Captulo 3. Estructuras de control

22

return 0;

23
24

Otro modo de resolver el problema anterior podra ser el siguiente:


1

. . .

2
3
4
5
6
7
8
9
10
11

if ( a>b && a>c)


max = a;
else {
if( b > c )
max = b;
else
max = c;
}
printf("El m
aximo es %f\n", max);

En ocasiones es necesario anidar un numero elevado de instrucciones if-else con el n


de seleccionar una de entre varias acciones. La sintaxis en este caso no diere para nada
de lo expuesto anteriormente, sin embargo, por cuestiones de legibilidad, el codigo suele
escribirse de modo algo distinto, tal y como se muestra a continuacion:
if( expresion_1 ) {
instrucciones;
}
else if (expresion_2) {
instrucciones;
}
else if (expresion_3) {
instrucciones;
}
. . .

Debe observarse que el codigo que acabamos de escribir coincide con el que se muestra a
continuacion, aunque el primero resulta mas legible:
if( expresion_1 ) {
instrucciones;
}
else {
if (expresion_2) {
instrucciones;
}
else {

38

3.1 Sentencias de seleccion

if (expresion_3) {
instrucciones;
}
. . .
}
}

El siguiente ejemplo muestra la calicacion obtenida (suspenso, aprobado, notable o sobresaliente) en funcion de la nota numerica, mediante la concatenacion de varias instrucciones if-else:
1

#include <stdio.h>

2
3
4

int main() {
float nota;

printf("Introduce el valor de la nota: ");


scanf(" %f", &nota);

6
7
8

if( nota >= 9 )


printf("SOBRESALIENTE\n");
else if( nota >= 7 )
printf("NOTABLE\n");
else if( nota >= 5 )
printf("APROBADO\n");
else
printf("SUSPENDIDO\n");

9
10
11
12
13
14
15
16
17

return 0;

18
19

3.1.2

Seleccion con switch

Tal y como se ha visto en el apartado anterior, dados dos grupos de instrucciones, la


instruccion if-else permite seleccionar uno de ellos. Si se desea seleccionar entre mas
de dos opciones, se ha visto que es necesario utilizar una combinacion de instrucciones
if-else.
Otra alternativa es es utilizar la instruccion switch. Antes de pasar a ver la sintaxis de
esta instruccion, debe quedar claro que cualquier algoritmo que se implemente utilizando switch puede implementarse igualmente mediante una combinacion de instrucciones
if-else. La instruccion switch simplemente proporciona otra manera de escribir ciertas partes de un programa, lo que en ocasiones puede ofrecer mayor legibilidad a nuestro
codigo.
39

Captulo 3. Estructuras de control

La sintaxis de la instruccion switch es la siguiente:


switch( expresion ) {
case valor_1: instrucciones;
case valor_2: instrucciones;
. . .
case valor_N: instrucciones;
default: instrucciones;
}

La expresion de la sentencia switch debe evaluarse a un entero o a un caracter (en la


mayora de los casos esta expresion sera simplemente una variable de tipo int o char).
Si el resultado de dicha expresion coincide con el valor especicado en alguna de las sentencias case, entonces se ejecutaran todas las instrucciones que aparecen a continuacion
de dicho case, hasta que se encuentre una instruccion break. Si el valor no coincide
con ningun case, entonces se ejecutaran las instrucciones especicadas en default. El
apartado default es opcional, en caso de no existir y de que la expresion del switch no
coincida con ninguno de los valores case, entonces no se hace nada.
En el siguiente ejemplo el usuario introduce un numero entero y el programa muestra el
da de la semana correspondiente:
1

#include <stdio.h>

2
3
4

int main() {
int dia;

printf("Introduce el d
a de la semana (1-7): ");
scanf(" %d", &dia);

6
7
8

switch( dia ) {
case 1: printf("LUNES\n"); break;
case 2: printf("MARTES\n"); break;
case 3: printf("MIERCOLES\n"); break;
case 4: printf("JUEVES\n"); break;
case 5: printf("VIERNES\n"); break;
case 6: printf("SABADO\n"); break;
case 7: printf("DOMINGO\n"); break;
default: printf("D
a incorrecto\n");
}
return 0;

9
10
11
12
13
14
15
16
17
18
19
20

En este ejemplo la instruccion switch evalua el valor de la variable dia. A continuacion


busca un case cuyo valor coincida con el de esta variable y, si lo encuentra, ejecuta las
40

3.1 Sentencias de seleccion

instrucciones correspondientes. Si el valor de la variable dia no coincide con ninguno de


los case, entonces se ejecuta el bloque default.
Debe tenerse en cuenta que si se encuentra un case que coincida con el valor de la expresion (en este ejemplo dia), se ejecutan todas las instrucciones que aparezcan a continuacion, hasta que se alcance una instruccion break. Esto quiere decir que si en el ejemplo
anterior no se hubieran incluido las instrucciones break y el usuario introduce, por ejemplo, un 6, se hubiera ejecutado no solo la instruccion printf("SABADO\n") sino tama incorrecto\n"). Lo
bien las instrucciones printf("DOMINGO\n") y printf("D
habitual sera, por tanto, que cada grupo de instrucciones especicados en un case termine con la instruccion break. Sin embargo, tal y como se muestra en el ejemplo siguiente,
habra ocasiones en las que interese no incluir la instruccion break.
1

#include <stdio.h>

2
3
4

int main() {
int curso;

printf("Introduce el curso en el que te encuentras: ");


scanf(" %d", &curso);

6
7
8

printf("Asignaturas que todav


a debes cursar:\n");

9
10

switch( curso ) {
case 1: printf("PROGRAMACI
ON\n");
case 2: printf("ALGOR
ITMICA\n");
case 3: printf("PROGRAMACI
ON AVANZADA\n");
case 4: printf("PROGRAMACI
ON DE REDES\n");
case 5: printf("INGENIERIA DEL SOFTWARE\n"); break;
default: printf("Curso incorrecto\n");
}
return 0;

11
12
13
14
15
16
17
18
19
20

En el ejemplo anterior, si el usuario introduce, por ejemplo, un 2, el programa mostrara:


Asignaturas que todav
a debes cursar:
ALGOR
ITMICA
PROGRAMACI
ON AVANZADA
PROGRAMACI
ON DE REDES
INGENIERIA DEL SOFTWARE

Observese que, una vez se entra en un case, se ejecuta el resto de instrucciones hasta
alcanzar una instruccion break.
41

Captulo 3. Estructuras de control

3.2

Sentencias de repeticion

Las sentencias de repeticion permiten ejecutar un bloque de instrucciones mas de una


vez, esto es, permiten hacer bucles. En lenguaje C se pueden implementar bucles de tres
modos distintos, mediante las sentencias while, do-while y for.

3.2.1

La sentencia while

Para implementar un bucle while se utiliza la siguiente sintaxis:


while( expresion ) {
instruccion_1;
instruccion_2;
. . .
instruccion_N;
}

Mientras la expresion de la instruccion while sea cierta, se ejecutaran las instrucciones


contenidas en el bucle. Cuando se ejecuta la u ltima instruccion del bucle (instruccion_N)
se vuelve a evaluar de nuevo la expresion y, si sigue siendo cierta, se ejecutan de nuevo
todas las instrucciones. Se denomina iteracion a cada una de las repeticiones.
Al igual que ocurre con la instruccion if-else, si el bucle contiene una u nica instruccion,
las llaves se pueden omitir.
El siguiente ejemplo muestra 10 veces el texto Hola mundo y a continuacion una vez el
texto Fin del programa.
1

#include <stdio.h>

2
3
4
5
6
7
8
9
10
11

int main() {
int i = 0;
while( i < 10 ) {
printf("Hola mundo\n");
i++;
}
printf("Fin del programa\n");
return 0;
}

Como puede observarse, en este ejemplo se ha utilizado la variable i a modo de contador,


para controlar el numero de veces que queremos que se repita el bucle (numero de iteraciones). En este caso, el bucle se repite 10 veces ya que en cada iteracion la variable i se
42

3.2 Sentencias de repeticion

incrementa en uno. Cuando e sta variable valga 10, la condicion i < 10 sera falsa, con lo
que ya no se entrara de nuevo en el bucle.
Para evitar bucles innitos es imprescindible que alguna de las instrucciones del bucle
modique de algun modo la expresion de la sentencia while, de lo contrario, una vez
se entra en el bucle ya no se puede salir del mismo. El siguiente programa muestra un
ejemplo de un bucle innito:
1

#include <stdio.h>

2
3
4
5
6
7
8
9

int main() {
int i = 0;
while( i < 10 ) {
// Esto siempre va a ser cierto
printf("Hola mundo\n");
}
return 0;
}

En el siguiente ejemplo se muestra un programa que solicita dos numeros y una operacion
(suma, resta, multiplicacion o division) y realiza la operacion especicada. A continuacion pregunta si se desea hacer otra operacion. En caso armativo se repite de nuevo todo
el proceso.
1

#include <stdio.h>

2
3
4
5

int main() {
float a, b, c;
int operacion, repetir;

6
7

8
9
10
11

12
13
14
15
16
17
18
19
20
21

repetir = 1; // Para forzar la entrada en el bucle la primera


vez
while( repetir == 1 ) {
printf("Introduce dos n
umeros: ");
scanf(" %f %f", &a, &b);
printf("1.Sumar \n2.Restar \n3.Multiplicar \n4.Dividir \n")
;
scanf(" %d", &operacion);
switch( operacion ) {
case 1: c = a+b; break;
case 2: c = a-b; break;
case 3: c = a*b; break;
case 4: c = a/b; break;
default: printf("Operaci
on incorrecta\n"); c=0;
}
printf("El resultado de la operaci
on es %f\n", c);
printf("Deseas hacer otra operaci
on? (1=SI / 2=NO) ");

43

Captulo 3. Estructuras de control

scanf(" %d", &repetir);


}
return 0;

22
23
24
25

Puede ocurrir que el contenido de un bucle while no se ejecute nunca. El siguiente programa solicita numeros enteros hasta que se introduzca un cero y al nal muestra cuantos
de ellos eran positivos.
1

#include <stdio.h>

2
3
4

int main() {
int num, positivos;

positivos = 0; // De momento no se ha introducido ning


un
n
umero positivo
printf("Introduce un n
umero entero: ");
scanf(" %d", &num);
while( num != 0 ) {
if( num > 0 )
positivos++;
printf("Introduce otro n
umero: ");
scanf(" %d", &num); // Este n
umero se utilizar
a en la
siguiente iteraci
on
}
printf("Has introducido %d n
umeros positivos\n", positivos);
return 0;

7
8
9
10
11
12
13

14
15
16
17

En el ejemplo anterior, si el primer numero introducido es cero, no llega a entrarse en el


bucle while. En este caso la variable positivos se queda con su valor inicial cero. En
caso de que se entre en el bucle, al nal del mismo se pide un nuevo numero que, en caso
de que sea distinto de cero, provocara que se entre de nuevo en el bucle.

3.2.2

La sentencia do-while

La sintaxis del bucle do-while es la siguiente:


do {
instruccion_1;
instruccion_2;
. . .
instruccion_N;
} while( expresion );

44

3.2 Sentencias de repeticion

La construccion de un bucle do-while es muy similar a la de un bucle while. La u nica


diferencia es que en el bucle while primero se comprueba la expresion y, si es cierta, se
ejecutan las instrucciones del bucle, mientras que en el caso del bucle do-while primero
se ejecutan las instrucciones del bucle y al nal se evalua la expresion. Si e sta resulta ser
cierta, entonces se ejecutaran de nuevo todas las instrucciones, as repetidas veces hasta
que la expresion sea falsa. Vemos por tanto que en un bucle do-while se hara como
mnimo una iteracion.
Cualquier codigo escrito con un bucle do-while puede reescribirse mediante un bucle
while y viceversa. Por ejemplo, el siguiente bucle do-while
1
2
3
4

printf("Introduce un n
umero positivo:");
do {
scanf(" %d", &num);
} while( num <= 0 );

puede reescribirse como:


1
2
3
4
5

printf("Introduce un n
umero positivo:");
scanf(" %d", &num);
while( num <= 0 ) {
scanf(" %d", &num);
}

En el siguiente ejemplo se solicitan dos numeros enteros a y b y se dividen. Para evitar


una division por cero, mediante un bucle do-while nos aseguramos de que el valor de b
sea distinto de cero (si b vale cero, solicitamos los datos de nuevo).
1

#include <stdio.h>

2
3
4

int main() {
float a, b;

do {
printf("Introduce dos n
umeros (el segundo de ellos distinto
de cero) ");
scanf(" %f %f", &a, &b);
} while( b == 0 );
printf(" %f / %f = %f\n", a, b, a/b);
return 0;

6
7

8
9
10
11
12

45

Captulo 3. Estructuras de control

En este ejemplo, si el valor introducido en b es cero, entonces la condicion b == 0 es


cierta y, en consecuencia, se piden los datos de nuevo. En general se puede emplear un
bucle do-while con la siguiente sintaxis para validar los datos de entrada:
do {
solicitar datos
} while( datos incorrectos );

Otro uso habitual del bucle do-while es para la implementacion de un menu de opciones.
A continuacion se muestra un ejemplo que presenta un menu, de modo que el programa
se ejecuta repetidas veces hasta que se escoge la opcion Salir:
1

#include <stdio.h>

2
3
4
5

int main() {
float a, b;
int opc;

do {
printf("1.- Sumar\n");
printf("2.- Restar\n");
printf("3.- Salir\n");
printf("Elige una opci
on: ");
scanf(" %d", &opc);
printf("Introduce dos n
umeros: ");
scanf(" %f %f", &a, &b);
if( opc == 1 ) printf(" %f + %f = %f\n", a, b, a+b);
else if( opc == 2 ) printf(" %f - %f = %f\n", a, b, a-b);
else if( opc == 3 ) printf("Adi
os\n");
else printf("Opci
on incorrecta\n");
} while( opc != 3 );
return 0;

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

En este caso el programa presenta un menu con dos opciones, mas una tercera para nalizar. El bucle do-while provoca que el programa se ejecute repetidas veces hasta que se
escoja la opcion 3 (salir). Cualquier otro valor distinto de 3 provoca que el bucle se repita
de nuevo. El grupo de instrucciones if-else se podra haber resuelto tambien mediante
una sentencia switch, tal y como se ha mostrado en ejemplos anteriores.

46

3.2 Sentencias de repeticion

3.2.3

La sentencia for

La sintaxis del bucle for es la siguiente:


for(exp1; exp2; exp3) {
instruccion_1;
instruccion_2;
. . .
instruccion_N;
}

Las llaves, como siempre, son opcionales si el bucle contiene una u nica instruccion. Por
otro lado, exp1, exp2 y exp3 pueden ser cualquier expresion valida en C. De forma mas
detallada diremos que:
exp1: se ejecuta una u nica vez, antes de que comiencen a ejecutarse las instruccio-

nes del bucle. Suele utilizarse para inicializar alguna variable.


exp2: es la condicion de entrada al bucle. Mientras exp2 se evalue a cierto, se

ejecutaran las instrucciones del bucle.


exp3: se ejecuta al nal de cada iteracion (despues de instruccion_N). Suele

utilizarse para modicar las variables que intervienen en la condicion de entrada


(exp2).
Por ejemplo, el siguiente programa muestra 10 veces el texto Hola Mundo:
1

#include <stdio.h>

2
3
4
5
6
7
8
9

int main() {
int i;
for( i=1; i<=10; i++ ) {
printf("Hola Mundo\n");
}
return 0;
}

En este ejemplo, i=1 se ejecuta una u nica vez antes de que comience el bucle. La expresion i<=10 es la condicion de entrada al bucle, mientras sea cierta se ejecutaran las
instrucciones de dentro del bucle. Por u ltimo la expresion i++ se ejecuta al nalizar cada
una de las iteraciones, despues de la instruccion printf. En denitiva, la variable i se ha
utilizado a modo de contador para controlar el numero de iteraciones. Cuando el bucle se
haya repetido 10 veces, la variable i valdra 11 y por tanto nalizara.
47

Captulo 3. Estructuras de control

El bucle for del ejemplo anterior es equivalente al siguiente bucle while:


1
2
3
4
5

i = 1;
while( i <= 10 ) {
printf("Hola Mundo\n");
i++;
}

En general, cualquier bucle for del tipo


for(exp1; exp2; exp3) {
instruccion_1;
. . .
instruccion_N;
}

puede expresarse mediante un bucle while del siguiente modo:


exp1;
while(exp2) {
instruccion_1;
. . .
instruccion_N;
exp3;
}

En un bucle for puede omitirse cualquiera de las expresiones. Por ejemplo, el siguiente
programa muestra n veces el texto Hola Mundo, donde n es un valor introducido por el
usuario. Observese que en el bucle for se ha omitido exp1 ya que en este caso la variable
n ya tiene un valor inicial:
1

#include <stdio.h>

2
3
4
5
6
7
8
9
10
11

int main() {
int n;
printf("Introduce un valor entero: ");
scanf(" %d", &n);
for( ; n>0; n-- ) {
printf("Hola Mundo\n");
}
return 0;
}

Si la que se omite es exp2 entonces se considera que la condicion de entrada es siempre


cierta, con lo que se podra generar un bucle innito.
48

3.2 Sentencias de repeticion

Tambien es posible que alguna de las expresiones contenga mas de una instruccion. En
este caso, dichas instrucciones deben separarse por coma, tal y como se muestra en el
siguiente ejemplo:
1

#include <stdio.h>

2
3
4
5
6
7
8
9

int main() {
int i, j;
for( i=0, j=10; i<j; i++, j-- ) {
printf(" %d + %d = %d\n", i, j, i+j);
}
return 0;
}

En este ejemplo se inicializan las variables i y j a 0 y 10 respectivamente, y al nal


de cada iteracion se incremente el valor de i y se decrementa el de j. Por tanto, este
programa producira la siguiente salida:
1
2
3
4
5

+
+
+
+
+

10 = 11
9 = 11
8 = 11
7 = 11
6 = 11

Este mismo programa escrito con un bucle while sera:


1

#include <stdio.h>

2
3
4
5
6
7
8
9
10
11
12
13

int main() {
int i, j;
i = 0;
j = 10;
while( i<j ) {
printf(" %d + %d = %d\n", i, j, i+j);
i++;
j--;
}
return 0;
}

49

Captulo 3. Estructuras de control

3.2.4

Bucles anidados

Es posible que alguna de las instrucciones de un bucle sea, a su vez, otro bucle. Esto es lo
que se conoce como un bucle anidado. Por ejemplo, el siguiente programa:
1

#include <stdio.h>

2
3
4
5
6
7
8
9
10
11
12
13

int main() {
int i, j;
for( i=1; i<=3; i++ ) {
printf("Bucle externo, iteraci
on %d\n", i);
for( j=1; j<=2; j++ ) {
printf("\tBucle interno, iteraci
on %d- %d\n", i, j);
}
printf("-------------------------------------\n");
}
return 0;
}

generara la siguiente salida por pantalla:


Bucle externo, iteraci
on 1
Bucle interno, iteraci
on 1-1
Bucle interno, iteraci
on 1-2
------------------------------------Bucle externo, iteraci
on 2
Bucle interno, iteraci
on 2-1
Bucle interno, iteraci
on 2-2
------------------------------------Bucle externo, iteraci
on 3
Bucle interno, iteraci
on 3-1
Bucle interno, iteraci
on 3-2
-------------------------------------

Como puede observarse, el bucle externo se repite tres veces y, en cada una de estas
iteraciones, el bucle interno se repite dos veces.

50

3.3 Algunas tecnicas u tiles: contadores, acumuladores y banderas

3.3

Algunas tecnicas utiles:


contadores, acumuladores y
banderas

Existen algunos modos particulares y ampliamente utilizados de trabajar con variables,


que nos permiten implementar los conceptos de contador, acumulador y bandera. A
continuacion se describen estos conceptos:

3.3.1

Contadores

Utilizamos el termino contador para referirnos a una variable cuyo valor se va incrementando de uno en uno bajo ciertas condiciones. Este tipo de variables deben inicializarse
con algun valor (normalmente cero).
Por ejemplo, el siguiente programa solicita la introduccion de 10 numeros enteros y calcula cuantos de ellos son pares:
1

#include <stdio.h>

2
3
4
5
6
7
8
9
10
11
12
13
14

int main() {
int i, cont, num;
cont = 0;
// Inicializamos el contador
for( i=1; i<=10; i++ ) {
printf("Introduce un n
umero entero: ");
scanf(" %d", &num);
if( num %2 == 0 )
cont++;
// Incrementamos el contador
}
printf("Has introducido %d n
umeros pares\n", cont);
return 0;
}

En este ejemplo la variable cont funciona a modo de contador, de modo que se inicializa a cero y, cada vez que se detecta la entrada de un numero par, se incrementa en uno.
La variable i del bucle for tambien es un contador, en este caso empleado para controlar el numero de iteraciones que realiza el bucle. Este contador se inicializa a uno y se
incrementa tras cada iteracion del bucle.

51

Captulo 3. Estructuras de control

3.3.2

Acumuladores

Otro uso habitual de las variables consiste en anadirles cierto valor al que ya tenan de
alguna operacion anterior, mediante operaciones del tipo
variable += valor;

Por ejemplo, la instruccion


1

i += 5;

incrementa en 5 el valor de i.
Al igual que los contadores, los acumuladores deben ser inicializados. De hecho un contador es un tipo particular de acumulador, al que se le van sumando unidades.
El siguiente ejemplo calcula la media aritmetica de 10 valores. Para ello se emplea un
acumulador para obtener, en primer lugar, la suma de todos ellos.
1

#include <stdio.h>

2
3
4
5
6
7
8
9
10

11
12
13
14

int main() {
int i;
float num, suma;
suma = 0;
// Inicializamos el acumulador
for( i=1; i<=10; i++ ) {
printf("Introduce un n
umero: ");
scanf(" %f", &num);
suma += num;
// Incrementamos el acumulador en
num unidades
}
printf("Media = %f\n", suma/10);
return 0;
}

En este ejemplo se puede observar que en la variable suma se van acumulando todos los
valores introducidos en la variable num.
Tambien es posible utilizar un acumulador en modo producto con una instruccion del tipo
variable *= valor;

Por ejemplo, la instruccion


1

52

i *= 5;

3.3 Algunas tecnicas u tiles: contadores, acumuladores y banderas

multiplica por 5 el valor que hubiese en i y almacena el resultado en la propia variable i.


Los acumuladores en forma de producto se inicializan habitualmente a 1. Por ejemplo,
el factorial de un numero n (1*2*3* ... *n) se puede calcular facilmente empleando un
acumulador en forma de producto. En este caso el acumulador debera inicializarse con
valor 1 e ir multiplicandose cada vez por los valores 2,3,...,n tal y como se muestra en el
siguiente ejemplo:
1

#include <stdio.h>

2
3
4
5
6
7
8
9

int main() {
int i, n, fact;
fact = 1;
// Inicializamos el acumulador
printf("Introduce un n
umero: ");
scanf(" %d", &n);
for( i=2; i<=n; i++ )
fact *= i;
// Multiplicamos i a lo que hay en el
acumulador

10

printf("Factorial de %d = %d\n", n, fact);


return 0;

11
12
13

Debe observarse que cuando se utiliza un acumulador en forma de producto, lo habitual


sera inicializarlo a uno (nunca debe inicializarse a cero ya que en este caso no cambiara
su valor con las sucesivas multiplicaciones).

3.3.3

Banderas

Una bandera o ag es una variable que toma u nicamente dos valores (1/0, verdadero/falso)
y que se emplea para comprobar si se ha producido una determinada condicion durante
la ejecucion del programa. La variable se inicializa a 0 (falso) o a 1 (verdadero) y, si se
cumplen ciertas condiciones en un instante determinado, se cambia su valor. Finalmente
se chequea la variable para ver si su valor ha cambiado.
Por ejemplo, imaginemos que se desea hacer un programa que sume el precio de 10
productos y, ademas, queremos sacar un mensaje de advertencia si alguno de los precios
introducidos es negativo. La suma se desea calcular igualmente, pero en caso de que se
detecte algun precio negativo, se mostrara al nal del programa un mensaje a modo de
advertencia. Esto puede resolverse con el uso de una bandera tal y como se muestra a
continuacion:

53

Captulo 3. Estructuras de control

#include <stdio.h>

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

17

18
19

int main() {
int i, flag;
float pvp, suma;
suma = 0;
// Inicializamos el acumulador
flag = 0;
// Inicializamos la bandera
for( i=1; i<=10; i++ ) {
printf("Introduce precio: ");
scanf(" %f", &pvp);
if( pvp < 0 )
// Si el n
umero es negativo...
flag = 1;
// nos lo anotamos en el flag
suma += pvp;
// En cualquier caso lo sumamos
}
printf("Suma = %f\n", suma);
if( flag == 1 )
// Comprobamos si el flag ha
cambiado
printf("ATENCI
ON, se han introducido precios negativos!\n")
;
return 0;
}

Como puede observarse, al ag se le da un valor inicial (0 en este caso) y, si detectamos


algun numero negativo, lo anotamos cambiando el valor original del ag. Bastara con que
haya un solo numero negativo para que el ag no conserve su valor inicial. Al nal del
proceso se chequea el valor del ag para comprobar si haba algun numero negativo.
Continuando con el ejemplo anterior, un error habitual con el uso de banderas es el siguiente:
1
2
3
4
5
6
7
8
9

flag = 0
for( i=1; i<=10; i++ ) {
printf("Introduce precio: ");
scanf(" %f", &pvp);
if( pvp < 0 )
flag = 1;
else
flag = 0;
}

Observese que la sentencia else no es correcta. Este codigo provoca que la variable flag

se quede con valor 0 o 1, dependiendo del signo del ultimo


valor introducido. Esto es,
con el codigo anterior, si el u ltimo numero introducido es positivo, el ag quedara con
valor 0, independientemente de que los numeros anteriores fueran positivos o negativos.
54

3.4 Ejercicios resueltos

3.4

Ejercicios resueltos

1. Escribir un programa que realice la division de dos numeros, teniendo en cuenta la


posibilidad de que el divisor pueda ser cero.

SOLUCION:
1

#include <stdio.h>

2
3
4

int main() {
float a, b;

printf("Introduce
scanf(" %f", &a);
printf("Introduce
scanf(" %f", &b);
if( b == 0 )
printf("Error:
else
printf(" %f\n",
return 0;

6
7
8
9
10
11
12
13
14
15

dividendo: ");
divisor: ");

divisi
on por cero\n");
a/b);

2. Escribir un programa que solicite un numero entero y muestre la tabla de multiplicar


de dicho numero.

SOLUCION:
1

#include <stdio.h>

2
3
4

int main() {
int i, n;

printf("Introduce un n
umero: ");
scanf(" %d", &n);
for( i=1; i<=10; i++ )
printf(" %d x %d = %d\n", n, i, n*i);

6
7
8
9
10

return 0;

11
12

3. Escribir un programa que muestre las tablas de multiplicar del 1 al 10.

SOLUCION:
1

#include <stdio.h>

55

Captulo 3. Estructuras de control

2
3
4

int main() {
int i, j;

for( i=1; i<=10; i++ ) {


printf("TABLA DE MULTIPLICAR DEL %d\n", i);
for( j=1; j<=10; j++ ) {
printf(" %d x %d = %d\n", i, j, i*j);
}
}
return 0;

6
7
8
9
10
11
12
13

4. Escribir un programa que calcule la media de n valores. El numero de datos a promediar (n) debera ser introducido por el usuario. Ademas, debera comprobarse que
el valor de n introducido sea mayor que cero, de lo contrario volvera a solicitarse
tantas veces como sea necesario.

SOLUCION:
1

#include <stdio.h>

2
3
4
5

int main() {
int i, n;
float num, suma;

// Pedimos valor de n, asegur


andonos que no sea cero
do {
printf("Introduce la cantidad de valores a promediar: "
);
scanf(" %d", &n);
if( n <= 0 )
printf("Debes introducir un valor mayor que cero\n")
;
} while( n <= 0 );

7
8
9

10
11
12

13
14

// Pedimos n n
umeros y los sumamos
suma = 0;
for( i=1; i<=n; i++ ) {
printf("Introduce valor %d: ", i);
scanf(" %f", &num);
suma += num;
}
printf("Media = %f\n", suma/n);
return 0;

15
16
17
18
19
20
21
22
23
24

56

3.5 Ejercicios propuestos: condicionales

3.5

Ejercicios propuestos: condicionales

1. Dada una ecuacion de segundo grado de la forma aX 2 + bX + c = 0, implementar


un programa que pida los coecientesa, b y c, y muestre por pantalla las dos soluciones de la ecuacion (X = (b b2 4ac)/(2a)). Debera comprobarse que
b2 4ac 0 y que a = 0.
Nota: Para calcular la raz cuadrada, se puede utilizar la funcion sqrt denida en la
librera math.h .
2. Una empresa de transportes cobra 30 euros por cada bulto que transporta. Ademas,
si el peso total de todos los bultos supera los 300 kilos, cobra 0.9 euros por cada
kilo extra. Por u ltimo, si el transporte debe realizarse en sabado, cobra un plus de
60 euros. La empresa no realiza el pedido si hay que transportar mas de 30 bultos,
si el peso total supera los 1.000 kilos o si se solicita hacerlo en domingo. Se pide
realizar un programa que solicite el numero de bultos, el peso total y el da de la
semana (numero entre 1 y 7) y muestre el precio en caso de que pueda realizarse el
transporte, o un mensaje de advertencia en caso contrario.

3.6

Ejercicios propuestos: bucles

Aprendiendo a utilizar contadores


3. Mostrar 10 veces por pantalla el texto Hola mundo.
4. Mostrar N veces por pantalla el texto Hola mundo. Pedir el valor de N al inicio
del programa.
5. Repetir el ejercicio anterior, numerando cada una de las lneas. Por ejemplo, si N
vale 10, el programa debera mostrar:
1. Hola mundo
2. Hola mundo
. . .
10. Hola mundo

6. Repetir el ejercicio anterior, numerando las lneas en orden decreciente, esto es:
10. Hola mundo
9. Hola mundo
. . .
1. Hola mundo

57

Captulo 3. Estructuras de control

7. Mostrar el valor del seno(x) para valores de x comprendidos entre 0 y 2, con incrementos de 0,1. El seno se puede calcular mediante la funcion sin(x), lo que
requiere incluir la librera math.h. La salida por pantalla debera tener el siguiente
aspecto:
X
------0.000000
0.100000
0.200000
. . .
2.000000

sen(x)
-------0.000000
0.099833
0.198669
. . .
0.909297

8. Introducir 10 numeros y decir cuantos de ellos se encuentran en el rango [50100].

Aprendiendo a utilizar acumuladores


9. Solicitar 10 valores y calcular la suma de todos ellos.
10. Solicitar 10 valores y mostrar la suma total de los pares por un lado y la de los
impares por otro.
11. Calcular 1 2 10, esto es,

10


i.

i=1

12. Escribir un programa que calcule el producto de dos numeros enteros positivos sin
utilizar la operacion * (el producto debera calcularse a base de sumas).
13. Solicitar dos numeros enteros positivos a y b y calcular a/b y a %b, sin utilizar las
operaciones / y %.
Pista: Contar el numero de veces que se puede restar b de a.
14. Solicitar un numero N y calcular el factorial de N. Comprobar que el numero introducido es positivo y menor o igual que 20. En caso contrario solicitarlo de nuevo,
hasta que el valor de N sea correcto.
15. Dado el siguiente fragmento de codigo, explicar que operacion realiza. Hacer una
traza suponiendo inicialmente a = 5.
1
2
3
4
5

58

valor = 0;
while( a > 0 ) {
valor += a--;
}
printf("El resultado es %d\n", valor);

3.6 Ejercicios propuestos: bucles

Aprendiendo a utilizar ags


17. Solicitar un numero y decir si es o no primo. Para ello, dado el numero n se deben
realizar las operaciones n%2, n%3, n%4... Si alguna de estas operaciones da cero
quiere decir que la division era exacta y por tanto n no es primo. En caso contrario,
sera primo.
18. Solicitar 10 numeros y decir si estan ordenados crecientemente, esto es, si cada
nuevo valor introducido es mayor que el anterior.
Pista: Utiliza una variable para almacenar el u ltimo valor introducido y otra para
almacenar el penultimo y, si ultimo < penultimo, activa un ag.

Juegos
19. Escribir un programa que juegue a adivinar un numero del siguiente modo: El
usuario piensa un numero entre 1 y 100 y el ordenador debe averiguar dicho numero. Para ello, cada vez que el ordenador sugiere un numero, el usuario debe contestar
con el caracter g, p o c en funcion de que el numero sea demasiado grande, demasiado pequeno o correcto. Se debe implementar un algoritmo eciente que
adivine el numero en el menor numero de intentos posible.
20. Se desea implementar un juego con las siguientes reglas:
Se parte de una cantidad inicial de 17 chas. Dos jugadores, de forma alterna, van
retirando chas del monton, permitiendose en cada jugada retirar 1, 2 o 3 chas.
Pierde el jugador que retira la u ltima cha del monton.
Implementar un programa que simule el juego descrito, de modo que se juegue
contra el ordenador. Empezara retirando chas el jugador humano, de modo que el
ordenador seguira en todo momento la estrategia de retirar 4 x chas, siendo x
las chas que retiro el otro jugador en el turno anterior. Jugando de este modo, el
ordenador siempre ganara la partida. Cada vez que sea el turno del ordenador, e ste
informara de cuantas chas retira y cuantas quedan en el monton. A continuacion
se le pedira al usuario que introduzca el numero de chas que desea retirar, y se
mostrara por pantalla igualmente cuantas chas quedan en el monton. Este proceso
se repetira hasta que nalice la partida.

59

Captulo 4

Funciones
4.1

Introduccion

Los programas que hemos creado hasta ahora no son modulares, todo el codigo del programa aparece dentro de main. Esto puede ser adecuado para programas pequenos y
sencillos, pero cuando se abordan programas de mayor envergadura y complejidad es necesario modularizarlos, esto es, dividir el programa en bloques menores. En el lenguaje
C, esta division se hace mediante el uso de funciones.
Estudiaremos en primer lugar el uso de las funciones existentes en la librera de C, para
aprender mas adelante como crear nuestras propias funciones.

4.2

Funciones de la librera de C

Cualquier compilador de C viene acompanado de una serie de funciones que realizan


operaciones de uso frecuente. Algunas de ellas ya las hemos venido utilizando. Por ejemplo, existen funciones como scanf y printf que permiten la entrada/salida de datos,
o funciones matematicas como sqrt, sin, cos, etc. que permiten realizar calculos matematicos. El listado completo de funciones es realmente extenso y no es objetivo nuestro
detallarlo.
Aunque ya hemos hecho uso de las funciones en captulos anteriores, conviene observar
con mayor detalle el modo de utilizarlas. A continuacion se muestra un ejemplo sencillo:
1
2

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

61

Captulo 4. Funciones

4
5

int main() {
float x, y;

printf("Introduce un valor: ");


scanf(" %f", &x);
y = pow(x, 2);
printf(" %.2f elevado a 2 = %f\n", x, y);

7
8
9
10
11

return 0;

12
13

En este ejemplo printf, scanf y pow son funciones de la librera de C. Lo primero que
debemos saber es que las funciones de C se agrupan en categoras. Por ejemplo printf
y scanf son funciones de entrada/salida, mientras que pow es una funcion matematica.
Para poder utilizar las funciones de C es necesario incluir cierta informacion en nuestro
programa. Esta informacion se encuentra almacenada en lo que se conoce como cheros
de cabecera o header les y se incluye mediante la instruccion:
#include <nombre_del_fichero_de_cabecera>

En la seccion 4.3.5 se explicara con mayor detalle los cheros de cabecera. De momento
nos basta con saber que es necesario incluirlos. En el ejemplo anterior stdio.h hace
referencia al chero de cabecera standar input/output (entrada/salida estandar). Hemos
incluido este chero ya que hacemos uso de las funciones printf y scanf, las cuales
se agrupan bajo esta categora. De forma similar, dado que nuestro programa utiliza la
funcion matematica pow, es necesario incluir el chero de cabecera math.h.
Si nos centramos en la sintaxis, se puede observar que para llamar o invocar a una funcion debemos poner el nombre de la funcion seguido de una lista de argumentos. Estos
argumentos representan la informacion que le pasamos a la funcion y deben ir encerrados
entre parentesis y separados por coma:
nombre_funcion(argumento1, argumento2, ... , argumentoN)

En el ejemplo anterior podemos observar que en las llamadas a las funciones scanf
y pow contienen dos argumentos. Por otro lado, la funcion printf, se puede invocar
con un numero variable de argumentos1 . En el ejemplo, el primer printf tiene un solo
argumento, mientras que el segundo tiene tres.
Tambien hay funciones que no tienen argumentos, como getchar(). Aun en este caso,
los parentesis deben estar presentes.
1 En realidad scanf tambi
en tiene un numero variable de argumentos. Por ejemplo scanf( %f %f, &a, &b)
tiene tres argumentos.

62

4.3 Creando nuestras propias funciones

Cuando se invoca a una funcion, el programa realiza las operaciones necesarias para ejecutar la funcion en cuestion, teniendo en cuenta los argumentos especicados. Algunas
funciones (en realidad la mayora) ademas de requerir argumentos con cierta informacion,
tambien devuelven un valor, resultante de la operacion realizada. En el ejemplo anterior
la funcion pow requiere dos argumentos y devuelve un valor (en este caso el resultado de
elevar el segundo argumento al primero). Cuando una funcion devuelve un valor, e sta puede ser utilizada como parte de una expresion. Por ejemplo, lo siguiente seran expresiones
validas en C:
y = pow(x, 2);
if( pow(x, 2) > 1 ) { ...
printf("%.2f elevado a 2 = %f\n", x, pow(x, 2));

Por otro lado, los argumentos de una funcion pueden ser tambien expresiones mas complejas, tal y como se muestra en los siguientes ejemplos:
z = pow(3*x, 2*y);
z = pow(sqrt(x)*2, 1.0/y);

4.3

Creando nuestras propias funciones

Hasta ahora los programas que hemos creado contienen todas las instrucciones en main.
Esto es adecuado para programas muy pequenos, pero para programas de mayor complejidad este modo de trabajar presenta grandes inconvenientes: implementacion complicada,
imposibilidad de reutilizar el codigo creado y codigo difcil de entender.
 
Imaginemos que queremos realizar un programa que calcule la operacion m
n , esto es, el
numero de combinaciones de m elementos tomados de n en n. Sabiendo que
 
m
m!
=
n
n!(m n)!
y teniendo en cuenta que el lenguaje C no dispone de ninguna funcion propia para el
calculo del factorial, podramos desarrollar un programa que calcule m
n del siguiente
modo:
1
2
3

#include <stdio.h>
int main() {
int m, n, i, fact_m, fact_n, fact_m_n, res;

4
5
6

// Pedir datos
do {

63

Captulo 4. Funciones

printf("Introduce dos valores m, n, positivos, con m>=n ");


scanf(" %d %d", &m, &n);
} while (m<0 || n<0 || m<n);

7
8
9
10

// Calcular m!
fact_m = 1;
for(i=1; i<=m; i++)
fact_m = fact_m * i;

11
12
13
14
15

// Calcular n!
fact_n = 1;
for(i=1; i<=n; i++)
fact_n = fact_n * i;

16
17
18
19
20

// Calcular (m-n)!
fact_m_n = 1;
for(i=1; i<=(m-n); i++)
fact_m_n = fact_m_n * i;

21
22
23
24
25

// Calcular n
umero de combinaciones y mostrar resultado
res = fact_m / (fact_n*fact_m_n);
printf("N
umero combinaciones = %d\n", res);

26
27
28
29

return 0;

30
31

Aunque el programa anterior es correcto, la implementacion no es la mas adecuada ya


que estamos repitiendo un codigo muy similar en varios puntos del programa (el calculo
del factorial). Lo ideal sera disponer de una funcion que devolviese el factorial de un
numero dado, con lo que el programa anterior podra simplicarse tal y como se muestra
a continuacion:
1
2
3

#include <stdio.h>
int main() {
int m, n, res;

4
5
6
7
8
9

// Pedir datos
do {
printf("Introduce dos valores m, n, positivos, con m>=n ");
scanf(" %d %d", &m, &n);
} while (m<0 || n<0 || m<n);

10
11
12
13
14

64

// Calcular n
umero de combinaciones y mostrar resultado
res = fact(m) / (fact(n)*fact(m-n));
printf("N
umero combinaciones = %d\n", res);

4.3 Creando nuestras propias funciones

return 0;

15
16

Desafortunadamente el lenguaje C no dispone de la funcion fact, sin embargo es posible


crearla, tal y como veremos mas adelante. Mediante la creacion de funciones conseguimos desarrollar programas mejor estructurados. La programacion modular o estructurada
permite:
Dividir un problema complejo en subproblemas mas faciles de resolver: divide y
venceras.
Construir y probar por separado los subprogramas (funciones).
Reutilizar los modulos ya creados, evitando replicar codigo de manera innecesaria.
Facilitar el mantenimiento de los programas.

4.3.1

Cuestiones sintacticas

Para crear nuestras propias funciones empleamos la siguiente sintaxis:


tipo_dato nombre_funcion(lista_parametros) {
/* Cuerpo de la funci
on */
}

donde:
tipo_dato indica el tipo de dato del valor que devuelve la funcion (int, foat,
char, etc.). En caso de que la funcion no devuelva ningun valor, el tipo de dato
debera ser void.
nombre_funcion es el nombre que queremos darle a nuestra funcion, y por tanto
el que debera utilizarse cuando queramos invocarla.
lista_parametros es un conjunto de pares tipo_dato nombre_variable se-

parados por comas. La lista de parametros utilizada a la hora de implementar la


funcion debe coincidir en numero y tipo con la lista de argumentos empleada en la
llamada a dicha funcion.
El tipo de dato devuelto por la funcion, junto con el nombre y la lista de parametros es lo
que se conoce como cabecera de la funcion, mientras que el codigo que queda encerrado
entre la apertura y cierre de llave se denomina cuerpo de la funcion.
Por ejemplo, si quisieramos crear una funcion que calcule el factorial de cierto numero
entero, escribiramos:
65

Captulo 4. Funciones

1
2
3

int fact( int n ) {


/* Instrucciones para calcular el factorial de n */
}

Esta funcion, de nombre fact, recibe un u nico parametro de tipo int y de nombre n que
representa el valor del que queremos obtener el factorial y devuelve un valor de tipo int
(el resultado de calcular el factorial de n).
El siguiente ejemplo correspondera a una funcion que calcula la media aritmetica de tres
numeros enteros:
1
2
3

float media( int a, int b, int c ) {


/* Instrucciones para calcular la media de a, b, c */
}

En este caso la funcion tiene tres parametros enteros que representan los numeros de los
que se desea obtener la media y devuelve un numero real (float) que representa la media
calculada.
En general, un programa en C estara compuesto por varias funciones, donde necesariamente una de ellas sera la funcion main, tal y como se muestra en el siguiente ejemplo:
1

#include <stdio.h>

2
3
4
5

int funcion1( float a, float b ) {


. . .
}

6
7
8
9

void funcion2( int x ) {


. . .
}

10
11
12
13

66

int main() {
. . .
}

4.3 Creando nuestras propias funciones

4.3.2

Control de ujo y transferencia de la informacion

Cuando invocamos a una funcion, el control del ujo de ejecucion se transere a dicha funcion, esto es, se ejecutan las instrucciones contenidas en el cuerpo de la funcion.
Cuando la funcion que ha sido invocada naliza su ejecucion, se devuelve el control del
ujo de ejecucion al punto siguiente de la llamada.
La funcion invocada puede nalizar por dos motivos:
Porque se ha llegado a la u ltima instruccion de la funcion.
Porque se ha ejecutado una instruccion return. La instruccion return sirve tanto
para nalizar la funcion en curso como para devolver un valor, tal y como veremos
mas adelante.
Por otro lado, cuando invocamos a una funcion, puede existir (y normalmente existe)
una transferencia de informacion entre la funcion que realiza la llamada y la funcion
llamada. Esta transferencia de informacion se produce en dos instantes:
Al inicio de la llamada se copian los valores de los argumentos especicados en
la llamada (en caso de que existan) en los parametros denidos en la funcion. Por
ejemplo, dado el siguiente codigo:
1
2
3
4
5
6
7
8
9
10

float media( int a, int b, int c ) {


/* Instrucciones para calcular la media de a, b, c */
. . .
}
int main() {
int num1, num2, num3, result;
. . .
result = media(num1, num2, num3);
. . .
}

cuando se realiza la llamada a la funcion media en la lnea 8, los valores de los


argumentos num1, num2 y num3 se copian en los parametros a, b y c de la funcion
media (lnea 1). Es importante darse cuenta que el numero y tipo de argumentos
utilizados en la llamada debe coincidir con el numero y tipo de parametros denidos
en la cabecera de la funcion. En este caso la funcion media tiene tres parametros
de tipo entero y, en consecuencia, cuando llamemos a dicha funcion deberemos
proveer tres argumentos enteros. El valor de los argumentos se copia por orden de
aparicion sobre los parametros, de modo que en el ejemplo anterior, el valor de
num1 se copiara en la variable a, el de num2 en b y el de num3 en c.
67

Captulo 4. Funciones

Al nalizar la llamada, en caso de que exista una sentencia return, el valor especicado a continuacion de return es devuelto a la funcion que realizo la llamada.
Por ejemplo, dado el siguiente codigo:
1
2
3
4
5
6
7
8
9
10
11
12

float media( int a, int b, int c ) {


float m;
/* Instrucciones para calcular la media de a, b, c */
. . .
return m;
}
int main() {
int num1, num2, num3, result;
. . .
result = media(num1, num2, num3);
. . .
}

cuando se ejecuta la instruccion return m y, por tanto, naliza la llamada a la


funcion media, el valor que tenga la variable m es devuelto. Esto provoca que la
llamada inicial media(num1, num2, num3) se evalue
al valor de la variable m.
Podra verse como si toda la instruccion media(num1, num2, num3) se sustituyera por el valor de m.
El programa anterior, escrito en su totalidad, quedara como sigue:
1

#include <stdio.h>

2
3
4
5
6
7
8
9
10
11
12
13

14

68

float media( int a, int b, int c ) {


float m;
m = (a+b+c)/3.0;
return m;
}
int main() {
int num1, num2, num3, result;
printf("Introduce tres valores: ");
scanf(" %d %d %d", &num1, &num2, &num3);
result = media(num1, num2, num3);
printf("La media de %d %d y %d es %f\n", num1, num2, num3,
result);
}

4.3 Creando nuestras propias funciones

Si retomamos el ejemplo del calculo de


1

m 
n

, el programa completo quedara como sigue:

#include <stdio.h>

2
3
4
5
6
7
8

int fact( int num ) {


int i, f=1;
for( i=2; i<=num; i++ )
f = f * i;
return f;
}

// Calculamos el factorial de num


// Devolvemos el factorial calculado

9
10
11

int main() {
int m, n, res;

12

// Pedir datos
do {
printf("Introduce dos valores m, n, positivos, con m>=n ");
scanf(" %d %d", &m, &n);
} while (m<0 || n<0 || m<n);

13
14
15
16
17
18

// Calcular resultado y mostrarlo


res = fact(m) / (fact(n)*fact(m-n));
printf("N
umero combinaciones = %d\n", res);

19
20
21
22

return 0;

23
24

En el programa mostrado como ejemplo existen tres llamadas a la funcion fact en la


lnea 20, con argumentos distintos en cada caso: fact(m), fact(n) y fact(m-n). Ello
provoca que en las distintas llamada a fact se copie cada vez un valor diferente en el
parametro num: m en la primera llamada, n en la segunda y m-n en la tercera. En todos los
casos la funcion fact opera con el valor copiado en num. Este modo de trabajo permite
crear funciones genericas que realicen ciertas operaciones con los valores que interese en
cada momento.

4.3.3

Parametros y argumentos

Segun la nomenclatura que hemos venido utilizando, denominamos parametros a las


variables declaradas en la cabecera de las funciones. Estas variables reciben valores en
el momento en que se realiza una llamada a la funcion. Denominamos argumentos a
los valores especicados en la llamada a una funcion, los cuales son transferidos a los
parametros correspondientes. Un argumento puede ser una variable, una constante o, en
general, una expresion.
69

Captulo 4. Funciones

En ocasiones, en la literatura se utiliza tambien el termino parametro formal para referirse a los parametros y parametro actual para referirse a los argumentos. Tambien es
frecuente que se utilice el termino parametro (sin mas calicativos) para referirse, de
forma indistinta, tanto a los parametros como a los argumentos.

4.3.4

Operaciones de Entrada/Salida en las funciones

Normalmente conviene que las operaciones de entrada/salida (printf/scanf) se encuentren en la funcion principal. Por ejemplo, no sera adecuado resolver el programa
anterior del siguiente modo:
1

#include <stdio.h>

2
3
4
5
6
7

void media( int a, int b, int c ) {


float res;
res = (a+b+c)/3.0;
printf("MEDIA= %f\n", res);
}

8
9
10
11
12
13
14
15

int main() {
float num1, num2, num3;
printf("Introduce tres valores: ");
scanf(" %d %d %d", &num1, &num2, &num3);
media(num1, num2, num3);
return 0;
}

La diferencia de este programa con el anterior es que ahora el resultado lo muestra la


propia funcion media en lugar de hacerlo main. En este caso media es de tipo void ya
que no necesita devolver ningun valor (lo muestra directamente). Aunque el resultado obtenido es el mismo, esta nueva forma de implementar la funcion media resta exibilidad.
Por ejemplo, ahora no sera posible lo siguiente:
1
2

int main() {
int num1, num2, num3;

printf("Introduce tres valores: ");


scanf(" %d %d %d", &num1, &num2, &num3);
if( media(num1, num2, num3) > 5 ) // Sentencia incorrecta
printf("APROBADO\n");
else
printf("SUSPENSO\n");
return 0;

4
5
6
7
8
9
10
11

70

4.3 Creando nuestras propias funciones

Ahora la funcion media no devuelve ningun resultado (es de tipo void), por lo que no
es posible comparar si el resultado de la misma es mayor o menor que cinco. En este
ejemplo nuestro objetivo no era mostrar por pantalla la media obtenida, sino operar con
el resultado obtenido, por lo tanto nos interesaba una funcion media que devolviese un
valor numerico, tal y como hicimos en el primer ejemplo.
De forma similar, tampoco resulta interesante hacer la lectura de datos dentro de la propia
funcion media:
1
2
3
4
5
6
7

float media() {
int a, b, c, res;
printf("Introduce tres valores: ");
scanf(" %d %d %d", &a, &b, &c);
res = (a+b+c)/3.0;
return res;
}

En este caso es la propia funcion media la que se encarga de solicitar la introduccion


de datos. Observese que ahora la funcion carece de parametros, ya que no es necesario
transferir ningun valor en la llamada. Nuevamente este esquema no resulta interesante ya
que podra darse alguna situacion en la que no se desee calcular la media de tres valores
introducidos por el usuario, sino de algun otro valor resultante de ciertos calculos. Por
ejemplo, la funcion anterior no podra utilizarse en la siguiente situacion:
1
2
3

int main() {
int num1, num2, num3, num4;
float m;

printf("Introduce cuatro valores: ");


scanf(" %d %d %d %d", &num1, &num2, &num3, &num4);
m = media(num1, num2, num3*num4);
. . .

5
6
7
8
9

En denitiva, salvo en aquellos casos en los que el acometido de la funcion sea explcitamente solicitar o mostrar datos, sera conveniente que las operaciones de entrada/salida se
realicen fuera de la funcion que realiza los calculos. De este modo las funciones implementadas podran utilizarse en diferentes contextos, esto es, estaremos creando funciones
mas generales.

71

Captulo 4. Funciones

4.3.5

Prototipos de funciones y cheros de cabecera

Como se ha dicho ya anteriormente, un programa en C esta formado por un conjunto


de funciones. Es importante que cada una de estas funciones este denida antes de su
llamada. En los ejemplos anteriores puede observarse que las funciones fact o media
son llamadas desde main. Si observamos dichos ejemplos, puede comprobarse que en
todos los casos estas funciones se denen antes de la funcion main y, por tanto, antes de
que sean llamadas.
Sin embargo, existe una alternativa que permite implementar las funciones en cualquier
orden, pudiendo incluso estar la implementacion despues de la llamada. Para ello basta
con declarar la funcion antes de la llamada (habitualmente al inicio del chero). La
declaracion de una funcion consiste en escribir el prototipo, esto es, la cabecera, seguido
de un punto y coma. Recordemos que la cabecera de una funcion esta formada por el tipo
de dato devuelto, el nombre y la lista de parametros:
tipo_dato nombre_funcion(lista_parametros);

Por ejemplo, el prototipo de la funcion fact del ejemplo dado en la seccion anterior sera:
1

int fact(int num);

En consecuencia,
haciendo uso de la declaracion de funciones, en el ejemplo del calculo

ser

a
posible
implementar las funciones en el siguiente orden:
de m
n
1

#include <stdio.h>

2
3

int fact(int num);


funci
on fact

// Declaraci
on de la

4
5
6
7

8
9

int main() {
. . .
res = fact(m) / (fact(n)*fact(m-n)); // Llamadas a la funci
on
fact
. . .
}

10
11

12
13

int fact( int num ) {


funci
on fact
. . .
}

// Implementaci
on de la

Como se observa, las llamadas a fact preceden a la implementacion, lo cual es posible


por haber declarado la funcion con anterioridad.
72

4.4 Variables locales y globales

En la declaracion de la funcion se puede omitir el nombre de los parametros. En realidad


el compilador lo u nico que necesita conocer de antemano es el nombre de la funcion, el
tipo de dato que devuelve y el numero y tipo de parametros; el nombre de los mismos
es irrelevante. Con esta informacion el compilador puede comprobar si la llamada a la
funcion es correcta (coinciden el numero y tipo de argumentos con el numero y tipo de
parametros), sin necesidad de conocer la implementacion. En consecuencia, la declaracion de la funcion fact podra ser simplemente:
1

int fact(int);

Cuando utilizamos funciones de la librera de C es necesario declararlas antes de su utilizacion, ya que en este caso la implementacion de las mismas no se encuentra en nuestro
chero fuente (en realidad ni siquiera se dispone de la implementacion sino del codigo
ya compilado o codigo objeto). Por ejemplo, cualquier programa que utilice la funcion
printf debera declararla previamente. Para facilitar esta labor existen unos cheros especiales que contienen las declaraciones (cabeceras) de las funciones. Estos cheros se
denominan cheros de cabecera o header les. Por poner solo algunos ejemplos, el chero stdio.h contiene las cabeceras o declaraciones de todas las funciones relacionadas
con la entrada/salida estandar (printf, scanf, etc.) y el chero math.h contiene las
cabeceras de las funciones matematicas(pow, sqrt, sin, cos, ...). En consecuencia, para
declarar estas funciones basta con incluir el chero de cabecera correspondiente mediante
una instruccion #include.
1
2
3

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

La extension .h viene precisamente de header le, si bien estos cheros ademas de cabeceras de funciones tambien pueden contener otra informacion como declaracion de
constantes (mediante instrucciones #define) o declaracion de tipos estructurados, algo
que se vera en el Captulo 6.

4.4

Variables locales y globales

Se dene el a mbito de una variable como la porcion de programa donde esa variable es
visible y, por tanto, puede accederse a su contenido. En este sentido las variables pueden
clasicarse en variables locales (cuando el a mbito se restringe a una funcion) y variables
globales (cuando el a mbito se extiende a todo el programa).
Variables locales: Una variable es local cuando se declara dentro de una funcion, bien en
el cuerpo de la misma, bien como parametro. Por ejemplo, dado el siguiente programa:
73

Captulo 4. Funciones

1
2
3
4
5
6
7
8
9
10
11
12

int fact( int n ) {


int i, f=1;
for( i=2; i<=n; i++ )
f = f * i;
return f;
}
int main() {
int num, res;
. . .
res = fact(num);
. . .
}

las variables n, i y f son locales a la funcion fact y por tanto no es posible usarlas fuera
de esta funcion. De modo similar las variables num y res son locales a la funcion main.
En realidad, todas las variables que hemos venido usando hasta ahora en los ejemplos
mostrados son variables locales.
Es posible que dos variables que se encuentren en funciones distintas tengan el mismo
nombre. Por ejemplo, el siguiente codigo sera perfectamente valido:
1
2
3
4
5
6
7
8
9
10
11
12

int fact( int n ) {


int i, f=1;
for( i=2; i<=n; i++ )
f = f * i;
return f;
}
int main() {
int i, res;
. . .
res = fact(i);
. . .
}

En este caso tanto la funcion fact como main contienen una variable de nombre i. Esto
no supone ningun problema, de hecho es algo bastante habitual, pero debe quedar claro
que la variable i de la funcion fact es distinta de la variable i de main. Cada una ocupa
su propio espacio de memoria y no hay confusion posible entre ellas, ya que dependiendo
de la funcion donde se esten utilizando quedara claro a que variable nos referimos.
En el ejemplo mostrado la variable i de main representa el valor del cual queremos calcular el factorial, mientras que la variable i de fact se usa a modo de contador en el bucle
for. El hecho de que la variable i de fact vaya cambiando su valor en cada iteracion del
bucle no afecta para nada a la variable i de main, la cual mantiene su valor.
74

4.4 Variables locales y globales

Variables globales: Una variable es global cuando se declara fuera de cualquier funcion
(normalmente a continuacion de las instrucciones #include y #define). El a mbito de
una variable global es la totalidad del programa y, por tanto, puede ser utilizada en cualquier parte del mismo.
El uso de variables globales evita tener que pasar informacion a las funciones mediante
el uso de parametros, o devolverla mediante el uso de return, sin embargo, tal y como
veremos mas adelante, su uso no se recomienda.
En el siguiente programa las variable num y f se declaran a nivel global:
1
2
3
4
5
6
7
8
9
10
11
12
13
14

#include <stdio.h>
int num, f;
// Variables globales
void fact() {
int i;
// Variable local
f = 1;
for( i=2; i<=num; i++ )
f = f * i;
}
int main() {
printf("Introduce un n
umero entero positivo: ";
scanf(" %d", &num);
fact();
printf("FACTORIAL( %d)= %d\n", num, f);
}

En el ejemplo mostrado las variables num y f declaradas en la lnea 2 son globales y, por
tanto, conocidas tanto dentro de main como de fact. Observese que ahora la funcion
fact no necesita recibir ni devolver ningun valor. La funcion main puede dar valor a la
variable num (lnea 11) y, posteriormente, la funcion fact consultar el valor de esa misma
variable (lnea 6).
Esta forma de trabajar esta totalmente desaconsejada, ya que rompe en gran medida con
el principio de modularidad. Es interesante que las funciones tengan una alta cohesion
(la funcion realiza una u nica tarea bien denida) y un bajo acoplamiento (la funcion
tiene una baja dependencia de otras funciones). El uso de variables globales aumenta la
dependencia entre funciones, esto es, aumenta el acoplamiento. Otros motivos por los que
debe evitarse el uso de variables globales son:
Codigo mas difcil de comprender.
Errores imprevistos debidos a efectos colaterales: esto sucede cuando se altera de
forma no deseada el contenido de una variable en algun punto del programa. Si
dicha variable es global, puede resultar muy complicado localizar el error, mientras
75

Captulo 4. Funciones

que si la variable se restringe al a mbito de una funcion (variable local) el error


sera mas facil de localizar.

4.5

Paso de argumentos por valor y por referencia

En la Seccion 4.3 se ha visto el modo en que se transere la informacion de una funcion


a otra mediante el uso de argumentos. Existen dos formas de pasar los argumentos: por
valor y por referencia.

4.5.1

Paso de argumentos por valor

Tal y como se ha explicado con anterioridad, cuando se transere informacion de una


funcion a otra mediante el uso de argumentos, se copia el valor que tiene cada uno de
los argumentos de la llamada al parametro correspondiente de la funcion invocada. Por
ejemplo, dado el siguiente codigo:
1
2
3

void f(int v) {
. . .
}

4
5
6
7
8
9
10

int main() {
int num;
. . .
f(num);
. . .
}

la llamada f(num) provoca que se copie el valor contenido en num en el parametro v


denido en f.
Esto es lo que se conoce como pase de parametros por valor. Hay que tener en cuenta
que las variables num y v ocupan espacios de memoria distintos (incluso aunque ambas
tuviesen el mismo nombre) por lo que un cambio en el valor de v no afecta para nada a la
variable num. Veamos el siguiente ejemplo:
1

#include <stdio.h>

2
3
4
5
6
7
8

76

void modificar( int a ) {


printf("Valor de a antes de modificar: %d\n", a);
a = a + 2;
printf("Valor de a despu
es de modificar: %d\n", a);
}

4.5 Paso de argumentos por valor y por referencia

9
10
11
12
13
14
15

int main() {
int a = 3;
printf("Valor de a al inicio de main: %d\n", a);
modificar(a);
printf("Valor de a al final de main: %d\n", a);
return 0;
}

El resultado de ejecutar este programa es:


Valor
Valor
Valor
Valor

de
de
de
de

a
a
a
a

al inicio de main: 3
antes de modificar: 3
despu
es de modificar: 5
al final de main: 3

Como puede observarse, el valor de la variable a declarada en main no se altera aunque


modiquemos el valor de la variable a declarada en modificar.
En denitiva, mediante el pase de argumentos por valor, no es posible que una funcion
modique el valor de una variable perteneciente a otra funcion. Esta forma de trabajar
es la mas adecuada en muchas situaciones, pero en ocasiones interesa que una funcion
modique alguna de las variables de la funcion que la invoco. Esto se consigue mediante
el paso de argumentos por referencia, el cual se basa en el uso de punteros. Para poder
entender el siguiente apartado es necesario haber comprendido previamente el manejo de
punteros explicado en el Captulo 2.

4.5.2

Paso de argumentos por referencia

El paso de argumentos por referencia consiste en pasar la direccion de una variable en


lugar de su valor. Esta forma de pasar los argumentos permite que una funcion pueda modicar las variables pertenecientes a otra, tal y como se muestra en el siguiente ejemplo:
1
2
3

void modificar( int * p ) {


*p = *p + 2;
}

4
5
6
7
8
9
10
11

int main() {
int a = 3;
printf("Valor de a al inicio de main: %d\n", a);
modificar(&a);
printf("Valor de a al final de main: %d\n", a);
return 0;
}

77

Captulo 4. Funciones

En este ejemplo, la llamada modificar(&a) pasa la variable a por referencia, esto es,
pasamos la direccion de a en lugar de su valor. Esta llamada produce que en el parametro p
de la funcion modificar se copie la direccion de a. A partir de este momento, la funcion
modificar podra acceder a la variable a ya que conoce su direccion de memoria. En
concreto, la operacion *p = *p + 2 incrementa en 2 el valor de a.
Imaginemos una situacion en la que una funcion necesita devolver dos valores. Ya se ha
visto que mediante la sentencia return u nicamente es posible devolver un valor2 , por lo
que deberamos hacer uso del pase de parametros por referencia para transferir los valores
deseados. En el siguiente ejemplo se muestra el uso de una funcion que, dado el radio de
una circunferencia, devuelve (mediante argumentos pasados por referencia), tanto el a rea
como el permetro.
1
2
3

#include <stdio.h>
#define PI 3.141592
void area_perim(float radio, float * p_area, float * p_perim);

4
5
6

int main() {
float r, area, perim;

printf("Introduce radio: ");


scanf(" %f", &r);
area_perim(r, &area, &perim);
printf("AREA= %f PERIMETRO= %f\n", area, perim);
return 0;

8
9
10
11
12
13

14
15
16
17
18

void area_perim(float radio, float * p_area, float * p_perim) {


*p_area = PI * radio * radio;
*p_perim = 2 * PI * radio;
}

En este ejemplo, la llamada area_perim(r, &area, &perim) de la lnea 10 provoca


que el valor de la variable r se copie en el parametro radio de la funcion area_perim y
que las direcciones de memoria de las variables area y perim se copien en los parametros p_area y p_perim respectivamente. En consecuencia, cuando dentro de la funcion
area_perim empleamos *p_area o *p_perim (lneas 16-17), estaremos accediendo en
realidad a la zona de memoria asignada a las variables area y perim respectivamente.

2 En realidad es posible devolver un grupo de variables mediante el uso de estructuras, tal y como se ver
a en
el Captulo 6, pero si nos restringimos a los tipos simples u nicamente podremos devolver un valor.

78

4.6 Ejercicios resueltos

4.6
4.6.1

Ejercicios resueltos
valor
Funciones que no devuelven ningun
Escribir un programa que dibuje por pantalla la siguiente gura:
*
**
***
*****
******
*******
********
*********
**********

Para ello implementar una funcion que reciba un numero entero n y muestre por
pantalla una lnea con n asteriscos.

SOLUCION:
1

#include <stdio.h>

2
3

void dibuja_asteriscos(int);

4
5
6
7
8
9
10
11

int main() {
int i;
for(i=1; i<=10; i++) {
dibuja_asteriscos(i);
}
return 0;
}

12
13
14
15
16
17
18
19

void dibuja_asteriscos( int n ) {


int i;
for(i=1; i<=n; i++) {
printf("*");
}
printf("\n");
}

En la lnea 3 se encuentra la declaracion de la funcion. Esta lnea indica que en


algun punto de nuestro programa debe existir una funcion de nombre dibuja_asteriscos
que recibe como parametro un entero (int) y no devuelve ningun valor (void).
79

Captulo 4. Funciones

Entre las lneas 13 y 19 se encuentra la implementacion de la funcion. La funcion


recibe un entero que almacena en la variable n y, mediante un bucle, muestra por
pantalla n asteriscos seguidos de un salto de lnea (\n).
En la lnea 8 se encuentra la llamada a la funcion. Puesto que la funcion espera
recibir un valor entero, en la llamada debemos proveer dicho valor (en este caso el
valor de la variable i). En el momento de la llamada, el valor de i se copia en la
variable n.
No debe confundirse la variable i de la funcion main con la variable de igual
nombre de la funcion dibuja_asteriscos. Aunque tengan el mismo nombre, se
trata de variables completamente distintas.
Puesto que la funcion es de tipo void (no devuelve ningun valor) la llamada no
puede formar parte de ninguna expresion. Por ejemplo no tendra sentido una instruccion como:
1

x = dibuja_asteriscos(i);

4.6.2

Funciones que devuelven un valor


Implementar un programa que calcule el a rea de un anillo de radios r1, r2 (area =
r22 r12 ). El programa debera solicitar, tantas veces como sea necesario, los
valores de r1 y r2 para asegurarse de que ambos son mayores de cero y de que
r2 > r1. A continuacion, mediante el uso de una funcion, debera calcular el a rea.
Finalmente mostrara el resultado. Tanto la peticion de datos como la salida de resultados debera realizarse desde el programa principal.

SOLUCION:

1
2

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

3
4

float area_anillo(float, float);

5
6
7
8
9
10
11

int main() {
float r1, r2, area;
do {
printf("Introduce los radios del anillo: ");
scanf(" %f %f", &r1, &r2);
} while( r1<=0 || r2<=0 || r2<=r1 );

12

area = area_anillo(r1, r2);


printf("AREA= %f\n", area);
return 0;

13
14
15
16

80

4.6 Ejercicios resueltos

17
18
19
20
21
22

float area_anillo( float rad1, float rad2 ) {


float res;
res = PI*rad2*rad2 - PI*rad1*rad1;
return res;
}

En la lnea 2 se incluye la librera math.h para poder utilizar la constante PI.


En la lnea 4 se declara la funcion area_anillo. Se especica que la funcion
recibe dos numeros reales (float) como parametros y devuelve otro real.
Entre las lneas 8 y 11 se realiza la peticion de datos. El bucle do-while provoca
que los datos se vuelvan a solicitar en caso de que sean incorrectos.
En la lnea 13 se realiza la llamada a la funcion. En el momento de la llamada los
valores de r1 y r2 se copian en las variables rad1 y rad2.
Entre las lnea 18 y 21 se implementa la funcion. El valor calculado se devuelve
mediante la sentencia return.
Puesto que la funcion es de tipo float (devuelve un valor real) la llamada puede
formar parte de cualquier expresion que requiera un numero real. Por ejemplo, las
siguientes instrucciones seran validas:
area = area_anillo(r1, r2);
area_cuadrado = pow(area_anillo(r1,r2), 2);
printf("El
area es %f\n", area_anillo(r1,r2));
if( area_anillo(r1,r2) < 1.5 )

4.6.3

Funciones que devuelven un valor del tipo VERDADERO/FALSO


Realizar un programa que muestre todos los numeros primos comprendidos entre
1 y 100. Para ello, implementar una funcion que devuelva si un numero dado es
primo.

SOLUCION:

#include <stdio.h>

2
3

int primo(int);

4
5
6

int main() {
int i;

7
8
9
10

for(i=1; i<=100; i++) {


if( primo(i)==1 )
printf(" %d\n", i);

81

Captulo 4. Funciones

}
return 0;

11
12
13

14
15
16

int primo( int n ) {


int i, es_primo;

17

es_primo = 1;
for(i=2; i<n; i++) {
if( n %i == 0 )
es_primo = 0;
}
return es_primo;

18
19
20
21
22
23
24

En la lnea 3 se declara la funcion. Las funciones que devuelven un valor del tipo
VERDADERO/FALSO se declaran de tipo int, de modo que si la funcion da como
resultado VERDADERO se devuelve 1 y si da FALSO se devuelve 0. Esta funcion
ademas de devolver un entero, recibe como parametro otro entero (el numero que
deseamos evaluar si es o no primo).
En la lnea 9 se realiza la llamada a la funcion. El valor de i se copia en la variable n. Cuando naliza la funcion, esta llamada se sustituye por el valor devuelto
en la instruccion de la lnea 23. Si se devuelve 1, la instruccion de la lnea 9 realizara la comparacion if(1==1), mientras que si devuelve 0 realizara la comparacion if(0==1).
Entre las lneas 15 y 24 se implementa la funcion. La variable es_primo se usa
a modo de ag o bandera. Se inicializa con 1 y, si durante la ejecucion del bucle
for se encuentra algun valor i que sea un divisor exacto de n (n%i == 0) entonces se cambia el ag a 0 ya que, con toda seguridad, el numero n no sera primo.
Finalmente en la lnea 23 se devuelve el valor del ag.
Una posible mejora a este algoritmo consistira en iterar u nicamente hasta n/2en el
bucle for de la lnea 19, ya que no vamos a encontrar divisores mas grandes que
este valor. Por otro lado, tambien podra forzarse la salida del bucle en cuanto se
encuentre un divisor, en cuyo caso queda claro que el numero no es primo y por
tanto no sirve de nada seguir iterando en el bucle.

4.6.4

Funciones que devuelven mas de un valor


Escribir un programa que solicite un tiempo dado en segundos y calcule, mediante
una funcion, la cantidad de horas, minutos y segundos que corresponden al tiempo
introducido.

82

4.6 Ejercicios resueltos

SOLUCION:
1

#include <stdio.h>

2
3

void convertir(int , int *, int *, int *);

4
5
6

int main() {
int seg_total, hor, min, seg;

printf("Introduce tiempo en segundos: ");


scanf(" %d", &seg_total);
convertir(seg_total, &hor, &min, &seg);
printf(" %ds = %dh %dm %ds\n", seg_total, hor, min, seg);

8
9
10
11
12

return 0;

13
14

15
16
17
18
19
20

void
*h
*m
*s
}

convertir(int st, int * h, int * m, int * s ) {


= st/3600;
= (st %3600)/60;
= st %60;

Una funcion no puede devolver mas de un valor a traves de una sentencia return
(a no ser que lo haga mediante una variable de tipo struct, las cuales todava no
se han estudiado). Un modo habitual de devolver mas de un valor es mediante el
pase de parametros por referencia.
En la lnea 10 se encuentra la llamada a la funcion convertir. Esta funcion tiene
cuatro parametros: los segundos totales (pasado por valor) y las direcciones de memoria de las variables hor, min y seg (pase por referencia) declaradas en main.
Es necesario pasar estas tres u ltimas variables por referencia, ya que su valor debe
ser modicado dentro de la funcion. En el momento de la llamada, en el parametro
st (lnea 16) se copia el valor de seg_total, y en los parametros h, m y s se
copian las direcciones de memoria de las variables hor, min y seg respectivamente. Esto es, en el momento de la llamada se produce la siguiente transferencia
de informacion: st=seg_total, h=&hor, m=&min y s=&seg.
Entre las lneas 16 y 20 se encuentra la implementacion de la funcion. Esta funcion
asigna valores a las variables hor y min y seg declaradas en main a traves de los
punteros h, m y s.
En la lnea 3 se encuentra la declaracion de la funcion. Puede observarse que los
tres u ltimos parametros son de tipo puntero, ya que corresponden, no al valor de
una variable, sino a su direccion de memoria. La funcion es de tipo void ya que no
devuelve nada a traves de la sentencia return.
83

Captulo 4. Funciones

4.7

Ejercicios propuestos

1. Implementar una funcion que, dado un numero entero positivo, muestre por pantalla
todos sus divisores. Escribir un programa que solicite un numero y, mediante una
llamada a la funcion anterior, muestre sus divisores.
2. Implementar una funcion que, dado un valor de temperatura expresado en grados
centgrados, devuelva el equivalente expresado en grados farenheit (F=C*9/5+32).
Escribir un programa que, mediante llamadas a la funcion anterior, muestre la
equivalencia entre grados centgrados y farenheit para todos los valores de grados
centgrados comprendidos entre 0 y 30 con incrementos de dos en dos.
3. Implementar una funcion que calcule el modulo de un numero complejo (dado
mediante su parte real e imaginaria). Escribir un programa que solicite un numero
complejo y muestre su modulo.
4. Implementar una funcion que reciba tres valores (los dos primeros reales y el tercero
entero) y devuelva:
La suma de los dos primeros si el tercero es igual a 1.
La resta de los dos primeros si el tercero es igual a 2.
El producto de los dos primeros si el tercero es igual a 3.
La division de los dos primeros si el tercero es igual a 4.
Cero en cualquier otro caso
Escribir un programa que pida dos numeros y una operacion (1-4) y muestre el
resultado en funcion de la operacion solicitada.
5. Implementar una funcion que, dados tres numeros reales, devuelva el mayor de
ellos. Escribir un programa que haga uso de esta funcion para mostrar el mayor de
tres valores introducidos por teclado.
6. Implementar una funcion que, dados tres numeros reales, devuelva si estan ordenados crecientemente. Escribir un programa que solicite tres numeros y muestre si
estan o no ordenados.
7. Implementar una funcion que devuelva si dos numeros dados son amigos. Dos
numeros a y b son amigos si a es la suma de los divisores de b y b es la suma
de los divisores de a, sin considerar la division por si mismo. Por ejemplo 220 y
284 son numeros amigos, ya que:
Los divisores de 220 (sin contar e l mismo) son 1, 2, 4, 5, 10, 11, 20, 22, 44,
55 y 110, que suman 284.
Los divisores de 284 son 1, 2, 4, 71 y 142, que suman 220.
84

4.7 Ejercicios propuestos

Otros pares de numeros amigos son: (1184, 1210), (6232, 6368), (17296, 18416) y
(9363584, 9437056).
Escribir un programa que solicite dos numeros enteros positivos y muestre si son o
no amigos.
8. Implementar una funcion que, dados dos numeros reales que representen el a ngulo (a) y modulo (m) de un vector, devuelva las coordenadas cartesianas de dicho
vector (x = m cos(a), y = m sin(a)). Escribir un programa que solicite un vector
expresado en coordenadas polares y lo muestre en coordenadas cartesianas.
9. Implementar una funcion que, dados dos numeros reales, los intercambie en el caso
de que el primero sea mayor que el segundo. Escribir un programa que solicite dos
valores y, tras invocar a la funcion anterior, los muestre ordenados.

85

Captulo 5

Vectores
5.1

Introduccion

Es posible distinguir entre tipos de datos basicos y tipos compuestos. Las variables de
tipos basicos (char, int, float, double y tipo puntero) pueden almacenar un solo
dato en un instante determinado. Por contra, las variables de tipos compuestos permiten
almacenar un conjunto de valores.
Un vector en C es una variable compuesta que permite almacenar una serie de valores,
todos ellos del mismo tipo. Gracamente, podemos representar un vector tal y como se
muestra en la Figura 5.1.
Denominamos:
Elemento: cada una de las variables individuales que componen el vector.
Talla o rango: numero de elementos que contiene el vector.

13

21

ndices

Elementos
del vector
E
Nombre del vector

Figura 5.1: Representacion graca de un vector

87

Captulo 5. Vectores

Indice:
la posicion que ocupa cada elemento dentro del vector. Al primer elemento
se le asocia el ndice 0 y al u ltimo el ndice talla-1.
La Figura 5.1 representa un vector de 8 elementos. El primer elemento, en la posicion 0,
almacena el valor 1, mientras que el u ltimo elemento, en la posicion 7, almacena el valor
21.

5.2

Declaracion de vectores

Para declarar un vector se emplea la siguiente sintaxis:


tipo_dato nombre_vector[talla];

donde tipo_dato especica el tipo de dato de cada uno de los elementos que forman el
vector y talla el numero de elementos. Por ejemplo, la sentencia
1

int v[10];

declara un vector de nombre v que contiene 10 elementos de tipo int.


Al igual que ocurre con otros tipos de variables, es posible inicializar los valores de un
vector en el momento de la declaracion. Por ejemplo, la siguiente sentencia crea un vector
de enteros de nombre dias_mes e inicializa cada uno de los elementos con los das que
tiene cada uno de los meses del ano:
1

int dias_mes[12] = {31,28,31,30,31,30,31,31,30,31,30,31};

Vemos que para inicializar un vector deben escribirse los valores de cada uno de sus
elementos encerrados entre llaves y separados por coma. La sentencia anterior crea el
vector dias_mes y almacena el valor 31 en la posicion 0, el 28 en la posicion 1, etc.
Cuando se inicializa el vector en el momento de la declaracion, es posible omitir el tamano. En este caso se crea un vector del tamano necesario para almacenar todos los
valores especicados. Por ejemplo, la siguiente sentencia crea un vector de tamano 4 e
inicializa cada uno de sus elementos con los valores especicados entre llaves.
1

88

float temperaturas[] = {17.5, 18.6, 20.1, 22.5};

5.3 Acceso a los elementos de un vector

5.3

Acceso a los elementos de un vector

Para acceder a los distintos elementos de un vector se emplea la siguiente sintaxis:


nombre_vector[indice]

donde indice hace referencia al elemento al que deseamos acceder. Debe recordarse que
el primer elemento tiene ndice 0 y el u ltimo ndice talla-1.
Por ejemplo, dado el vector
1

int v[10];

las siguientes sentencias almacenan un 5 en la primera posicion y un 20 en la u ltima:


1
2

v[0] = 5;
v[9] = 20;

En general, dado un vector


tipo_dato nombre_vector[talla];

la expresion nombre_vector[indice] se comporta, a todos los efectos, como una variable de tipo tipo_dato, por lo que puede ser empleada bajo las mismas condiciones
que se empleara cualquier otra variable de ese tipo. Por ejemplo, dado el siguiente programa:
1

#include <stdio.h>

2
3
4
5
6
7
8
9
10

int main() {
int a, b, c;
printf("Introduce dos n
umeros enteros: ");
scanf(" %d %d", &a, &b);
c = a + b;
printf("La suma de %d y %d es %d\n", a, b, c);
return 0;
}

se podra reescribir usando vectores del siguiente modo:

89

Captulo 5. Vectores

#include <stdio.h>

2
3
4
5
6
7
8
9
10

int main() {
int v[3];
printf("Introduce dos n
umeros enteros: ");
scanf(" %d %d", &v[0], &v[1]);
v[2] = v[0] + v[1];
printf("La suma de %d y %d es %d\n", v[0], v[1], v[2]);
return 0;
}

Observese que v[0], v[1] y v[2] se usan exactamente igual a como se usara cualquier
otra variable de tipo int (en este caso, igual a como se usan las variables a, b y c del
primer ejemplo).

5.4

Operaciones con vectores: automatizacion mediante bucles

Cualquier operacion que se desee realizar con un vector, debera hacerse elemento a elemento. El lenguaje C no permite operar con el vector en su totalidad. Por ejemplo, si
queremos sumar dos vectores v1, v2 y almacenar el resultado en v3, debemos sumar
uno a uno cada uno de los elementos de los vectores. La sentencia v3 = v1 + v2 es
incorrecta (mas adelante veremos como hacer e sta operacion).
Imaginemos que disponemos de un vector de 10 elementos enteros y deseamos dar valores
a cada uno de ellos. El siguiente programa permite hacer esta operacion, aunque de un
modo poco recomendable.
1

#include <stdio.h>

2
3
4

int main() {
int v[10];

5
6
7
8
9
10
11

printf("Introduce el valor del primer elemento: ");


scanf(" %d", &v[0]);
printf("Introduce el valor del segundo elemento: ");
scanf(" %d", &v[1]);
printf("Introduce el valor del tercer elemento: ");
scanf(" %d", &v[2]);

12
13

. . .

14
15
16

90

printf("Introduce el valor del d


ecimo elemento: ");
scanf(" %d", &v[9]);

5.4 Operaciones con vectores: automatizacion mediante bucles

17

. . .
return 0;

18
19
20

Este modo de trabajar, ademas de poco operativo, es impracticable cuando el tamano del
vector es grande. Sin embargo, resulta sencillo acceder secuencialmente a los elementos
de un vector mediante el uso de bucles. Para ello debe usarse una variable entera en el
lugar del ndice, de modo que e sta vaya variando en cada iteracion del bucle, tal y como
se muestra en el siguiente ejemplo:
1

#include <stdio.h>

2
3
4

int main() {
int i, v[10];

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


printf("Introduce el valor del elemento %d: ", i);
scanf(" %d", &v[i]);
}
. . .
return 0;

6
7
8
9
10
11
12

Observese que en el bucle del programa anterior, la variable i comienza con valor 0 y
naliza con valor 9, por lo que en cada iteracion la sentencia scanf va almacenando
valores en los distintos elementos del vector (v[0], v[1], v[2], ...). Debe recordarse
que en un vector v de tamano 10, sus elementos van desde v[0] hasta v[9].
Habitualmente el tamano de un vector se dene con una constante simbolica mediante la
directriz #define. Ello permite modicar facilmente el tamano del vector sin necesidad
de tener que cambiar la condicion en todos los bucles de acceso al vector. A continuacion
se muestra un programa de ejemplo que permite sumar dos vectores, dejando el resultado
en un tercero:
1
2

#include <stdio.h>
#define N 10

3
4
5

int main() {
int i, v1[N], v2[N], v3[N];

// Vectores de tama
no 10

6
7
8
9

// Introducimos los datos del primer vector


printf("Valores del primer vector:\n");
for( i=0; i<N; i++) {

91

Captulo 5. Vectores

printf("Introduce el valor del elemento %d: ", i);


scanf(" %d", &v1[i]);

10
11

12
13

// Introducimos los datos del segundo vector


printf("Valores del segundo vector:\n");
for( i=0; i<N; i++) {
printf("Introduce el valor del elemento %d: ", i);
scanf(" %d", &v2[i]);
}

14
15
16
17
18
19
20

// Realizar la suma v3 = v1 + v2 elemento a elemento


for( i=0; i<N; i++)
v3[i] = v1[i] + v2[i];

21
22
23
24

// Mostrar el resultado de la suma


printf("Vector suma:\n");
for( i=0; i<N; i++)
printf(" %d ", v3[i]);

25
26
27
28
29

return 0;

30
31

Observese que los bucles for de las lneas 9, 16, 22 y 27 iteran con el ndice i desde 0 a
N-1, siendo N el tamano de los vectores. De este modo, si se quisiera trabajar con vectores
de otro tamano, bastara con cambiar el valor de N denido en la lnea 2. Observese tambien que para sumar dos vectores, debemos hacerlo elemento a elemento (lneas 22-23).

5.5

Paso de vectores a una funcion

Al igual que ocurre con otro tipo de variables, los vectores tambien pueden ser pasados
a funciones, aunque, tal y como se explicara mas adelante, el mecanismo de pase de
parametros en este caso es sensiblemente distinto.
A continuacion se muestra la sintaxis a utilizar cuando se pasan vectores como parametro
a una funcion. Para ello distinguimos entre la llamada a la funcion, la implementacion y
la declaracion.

92

5.5 Paso de vectores a una funcion

Llamada a la funcion:
nombre_funcion(nombre_vector)

Por ejemplo
1

inicializar(v);

Implementacion de la funcion:
tipo nombre_funcion( tipo nombre_vector[talla] ) {
// Cuerpo de la funci
on
}

donde talla se puede omitir, en cuyo caso la funcion admite vectores de cualquier tamano.
Por ejemplo
1
2
3
4
5

void inicializar( int v[] ) {


int i;
for( i=0; i<N; i++ ) // Suponiendo un vector de tama
no N
v[i] = 0;
}

Declaracion de la funcion:
tipo nombre_funcion( tipo nombre_vector[talla] );

donde tanto nombre_vector como talla se pueden omitir.


Por ejemplo
1

void inicializar( int [] );

Visto en conjunto dentro de un programa, las sentencias anteriores quedaran como sigue:

93

Captulo 5. Vectores

1
2

#include <stdio.h>
#define N 10

3
4
5

// Declaraci
on de la funci
on
void inicializar( int [] );

6
7
8
9
10
11
12
13

// Programa principal
int main() {
int vect[N];
inicializar(vec); // Llamada a la funci
on
. . .
return 0;
}

14
15
16
17
18
19
20

// Implementaci
on de la funci
on
void inicializar( int v[] ) {
int i;
for( i=0; i<N; i++ ) // Suponiendo un vector de tama
no N
v[i] = 0;
}

A continuacion se muestra un sencillo ejemplo de un programa que solicita los valores de


un vector y seguidamente los muestra por pantalla.
1
2

#include <stdio.h>
#define N 10

3
4
5
6

// Declaraci
on de funciones
void pedir_valores( int [] );
void mostrar_valores( int [] );

7
8
9
10

// Programa principal
int main() {
int vec[N];

11

pedir_valores(vec);
// Llamada a funci
on
mostrar_valores(vec);

12
13
14

return 0;

15
16

17
18
19
20
21

94

// Implementaci
on de las funciones
void pedir_valores( int v[] ) {
int i;
for( i=0; i<N; i++) {

5.5 Paso de vectores a una funcion

printf("Introduce el valor del elemento %d: ", i);


scanf(" %d", &v[i]);

22
23

24
25

26
27
28
29
30
31

void mostrar_valores( int v[] ) {


int i;
for( i=0; i<N; i++)
printf(" %d ", v[i]);
}

En el ejemplo anterior, la llamada pedir_valores de la lnea 12 pasa el vector vec a la


funcion correspondiente, implementada en la lnea 19, la cual rellena el vector a traves de
la funcion scanf. Es importante entender que tras la ejecucion de esta funcion, el vector
vec declarado en main mantiene los valores dados dentro de la funcion. Este comportamiento diere de lo visto en el pase de parametros por valor explicado en la Seccion 4.5.1
del Captulo 4, en el que se explico que las modicaciones hechas dentro de una funcion
a los parametros pasados por valor, no afectan a la variable original enviada. Lo que ocurre en este caso es que los vectores se pasan siempre por referencia, sin tener que usar
explcitamente la sintaxis de punteros. En consecuencia, siempre que se pase un vector
como parametro a una funcion, cualquier alteracion que se haga sobre el vector dentro
de la funcion quedara reejada en el vector original enviado. En el ejemplo anterior, en
realidad el vector vec declarado en main y el vector v utilizado dentro de las funciones
hacen referencia al mismo espacio de memoria y, por tanto, cuando se modique v, realmente se estara modicando vec. Este comportamiento se explica con mayor detalle en
la Seccion 5.10.
Obviamente, una misma funcion puede ser invocada con distintos vectores en distintos
puntos del programa, lo que permite la reutilizacion de la funcion. En el siguiente ejemplo
se muestra un programa que suma dos vectores mediante el uso de funciones. Observese
como se invoca mas de una vez a una misma funcion, con distintos vectores en cada caso.
1
2

#include <stdio.h>
#define N 10

3
4
5
6
7

// Declaraci
on de funciones
void pedir_valores( int [] );
void mostrar_valores( int [] );
void sumar( int [], int [], int [] );

8
9
10
11

// Programa principal
int main() {
int v1[N], v2[N], v3[N];

12
13

pedir_valores(v1);

95

Captulo 5. Vectores

pedir_valores(v2);
sumar(v1, v2, v3);
printf("Vector 1:\n");
mostrar_valores(v1);
printf("Vector 2:\n");
mostrar_valores(v2);
printf("Suma:\n");
mostrar_valores(v3);

14
15
16
17
18
19
20
21
22

return 0;

23
24

25
26
27
28
29
30
31
32
33

// Implementaci
on de las funciones
void pedir_valores( int v[] ) {
int i;
for( i=0; i<N; i++) {
printf("Introduce el valor del elemento %d: ", i);
scanf(" %d", &v[i]);
}
}

34
35
36
37
38
39
40

void mostrar_valores( int v[] ) {


int i;
for( i=0; i<N; i++)
printf(" %d ", v[i]);
printf("\n");
}

41
42
43
44
45
46

void sumar( int x[], int y[], int z[] ) {


int i;
for( i=0; i<N; i++)
z[i] = x[i] + y[i];
}

Observar en el ejemplo anterior que las funciones pedir_valores y mostrar_valores


se invocan con un vector distinto en cada ocasion. En la implementacion de las funciones, el vector v recibido como parametro hace referencia al argumento pasado en cada
momento (v1, v2 o v3). Tambien debe resaltarse el modo en que se ha implementado
la funcion sumar. Debe observarse que esta funcion no devuelve el vector resultante (la
funcion es de tipo void) sino que dicho vector se pasa como un argumento mas. Esto es
posible ya que, tal y como se ha dicho anteriormente, los vectores se pasan por referencia
y, por tanto, es posible modicar su contenido dentro de la funcion.
Por u ltimo, cabe senalar que en ocasiones resulta interesante disenar funciones que sean
capaces de operar con vectores de distintos tamanos (las funciones de los ejemplos anteriores, por el modo en que estan implementados los bucles for, estan pensadas u nica96

5.5 Paso de vectores a una funcion

mente para vectores de tamano N). Si se desea operar con vectores de distintos tamanos,
es necesario pasar como parametro tanto el vector como su tamano, tal y como se muestra
en el siguiente ejemplo, que calcula la media de distintos vectores:
1
2
3

#include <stdio.h>
#define N 10
#define M 15

4
5
6
7

// Declaraci
on de funciones
void pedir_valores( float [], int );
float media( float [], int );

8
9
10
11

// Programa principal
int main() {
float v1[N], v2[M], m1, m2;

12

pedir_valores(v1, N);
pedir_valores(v2, M);
m1 = media(v1, N);
m2 = media(v2, M);
printf("Media de v1: %f\n", m1);
printf("Media de v2: %f\n", m2);

13
14
15
16
17
18
19

return 0;

20
21

22
23
24
25
26
27
28
29
30

// Implementaci
on de las funciones
void pedir_valores( float v[], int dim ) {
int i;
for( i=0; i<dim; i++) {
printf("Introduce el valor del elemento %d: ", i);
scanf(" %d", &v[i]);
}
}

31
32
33
34
35
36
37
38
39
40
41

float media( float v[], int dim ) {


int i;
float suma = 0;
for( i=0; i<dim; i++)
suma += v[i];
if( dim == 0 )
return 0;
else
return suma/dim;
}

97

Captulo 5. Vectores

Observese que, en este caso, los bucles for implementados en las funciones (lneas 26
y 35) tienen como lmite la variable dim recibida como parametro, la cual podra tener
valores distintos dependiendo de la llamada (lneas 13-16).
Ademas de los ejemplos mostrados a lo largo de esta seccion, existen operaciones tpicas
con vectores que, debido a su uso habitual en programas de ndole muy diversa, conviene
conocer en detalle, como por ejemplo la obtencion del maximo, del mnimo, busqueda de
un elemento, etc. En el apartado de ejercicios resueltos (Seccion 5.12) se puede encontrar
la solucion de e stos y otros algoritmos.

5.6

Devolucion de vectores en una funcion

Las funciones en C no pueden devolver vectores, aunque s pueden devolver un puntero


a un vector. En este caso esta operacion u nicamente sera conveniente cuando el vector
haya sido reservado de forma dinamica. En este libro no se aborda la reserva dinamica de
memoria y, por tanto, en ningun caso emplearemos funciones que devuelvan un puntero
a un vector.
Si el resultado de una funcion es un vector, e ste lo declararemos en main y lo pasaremos
como un argumento mas, tal y como hemos hecho en las funciones pedir_datos y
sumar de los ejemplos anteriores.

5.7

Vectores multidimensionales

El lenguaje C tambien permite trabajar con vectores de mas de una dimension (matrices).
En este captulo abordaremos u nicamente las matrices de dos dimensiones. Una matriz
bidimensional es un conjunto de variables de un mismo tipo organizado en las y columnas.
Para declarar una matiz bidimensional se emplea la siguiente sintaxis:
tipo_dato nombre_matriz[talla_filas][talla_columnas];

donde tipo_dato especica el tipo de dato de cada uno de los elementos de la matriz
y talla_fila y talla_columnas indican el numero de las y columnas de la matriz.
Por ejemplo, la sentencia
1

int m[3][4];

declara una matriz m de enteros con 3 las y 4 columnas.

98

5.8 Operaciones con matrices: automatizacion mediante bucles


0

0,0

0,1

0,2

0,3

1,0

1,1

1,2

1,3

2,0

2,1

2,2

2,3

Figura 5.2: Indices en una matriz de 3 x 4.

Al igual que ocurre con los vectores, los ndices comienzan en 0 y terminan en talla - 1.
En la Figura 5.2 se muestra los ndices (la, columna) de cada uno de los elementos de la
matriz anterior.
Para acceder a los elementos de una matriz se emplea la siguiente sintaxis:
nombre_matriz[indice_fila][indice_columna];

Por ejemplo, dada la matriz


1

int m[3][4];

las siguientes sentencias almacenan un 5 en el primer elemento de la matriz (primera la,


primera columna) y un 20 en el u ltimo elemento (ultima la, u ltima columna):
1
2

m[0][0] = 5;
m[2][3] = 20;

5.8

Operaciones con matrices: automatizacion mediante


bucles

Al igual que ocurre con los vectores, las operaciones con matrices deben realizarse elemento a elemento. Para ello es conveniente (y con matrices mnimamente grandes, absolutamente necesario) automatizar el acceso a los elementos de la matriz mediante el uso
de bucles.
Para recorrer una matriz bidimensional emplearemos dos variables enteras que vayan tomando los valores de sus ndices (la y columna). Esto se consigue mediante el uso de
dos bucles anidados (ver la Seccion 3.2.4 para entender el funcionamiento de los bucles
anidados).
99

Captulo 5. Vectores

En el siguiente ejemplo se declara una matriz de 3 las y 4 columnas y, a continuacion,


se solicitan los valores de cada uno de sus elementos:
1
2
3

#include <stdio.h>
#define FIL 3
#define COL 4

4
5
6

int main() {
int i, j, m[FIL][COL];

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


for( j=0; j<COL; j++ ) {
printf("Introduce valor del elemento ( %d, %d): ", i, j);
scanf(" %d", &m[i][j]);
}
}
. . .
return 0;

8
9
10
11
12
13
14
15
16

Es importante entender como van variando los ndices i, j tras cada iteracion de los
bucles. En la primera iteracion del bucle mas externo (lnea 8) se inicializa la variable i a
0 y, seguidamente, se entra en el bucle. A continuacion se ejecutan todas las iteraciones del
bucle interno (lneas 9-12) en las que j vara de 0 a COL-1, esto es, j toma sucesivamente
los valores 0, 1, 2 y 3 con i valiendo 0. Tras la nalizacion de todas las iteraciones
del bucle interno, se vuelve a la siguiente iteracion del bucle externo, con la variable i
incrementada en 1. A continuacion se ejecutan de nuevo todas las iteraciones del bucle
interno, donde j toma nuevamente los valores 0, 1, 2 y 3, pero esta vez con i valiendo 1.
De este modo se procede hasta haber recorrido todos los elementos de la matriz.
Para mostrar por pantalla una matriz m de enteros de tamano FIL x COL utilizaramos el
siguiente codigo:
1
2
3
4
5
6

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


for( j=0; j<COL; j++ ) {
printf(" %d\t" m[i][j]);
}
printf("\n");
}

Observese que la sentencia printf de la lnea 5 provoca que, tras mostrarse todas las
columnas de una la dada (esto es, tras haberse completado el bucle interno), se inserte
un salto de lnea, con lo que la siguiente la se mostrara en una lnea distinta.
100

5.9 Paso de matrices a una funcion

5.9

Paso de matrices a una funcion

El paso de matrices a una funcion es similar, en cuanto a sintaxis y funcionamiento, al


paso de vectores. A continuacion se muestra la sintaxis empleada:
Llamada a la funcion:
nombre_funcion(nombre_matriz)

Implementacion de la funcion:
tipo nombre_funcion( tipo nombre_matriz[talla_filas][talla_columnas] ) {
// Cuerpo de la funci
on
}

donde talla_filas se puede omitir, en cuyo caso la funcion admite matrices con cualquier numero de las, pero talla_columnas debe especicarse obligatoriamente.
Declaracion de la funcion:
tipo nombre_funcion( tipo nombre_matriz[talla_filas][talla_columnas]);

donde tanto nombre_matriz como talla_filas se pueden omitir, si bien talla_columnas


debe especicarse obligatoriamente.
Como se observa, la principal diferencia respecto de los vectores radica en que en este
caso hay que utilizar una doble pareja de corchetes ( [ ][ ] ) en lugar de una simple ( [ ] ).
Hay que recordar que, como en el caso de los vectores, con las matrices tambien se emplea
el metodo de paso de argumentos por referencia.
En el siguiente ejemplo se solicitan los valores de dos matrices de tamano 3 x 5, se
suman, y se muestra por pantalla la matriz resultante:
1
2
3

#include <stdio.h>
#define N 3
#define M 5

4
5
6
7
8

// Declaraci
on de funciones
void pedir_valores( int [][M] );
void mostrar_valores( int [][M] );
void sumar( int [][M], int [][M], int [][M] );

9
10
11

// Programa principal
int main() {

101

Captulo 5. Vectores

int m1[N][M], m2[N][M], m3[N][M];

12
13

pedir_valores(m1);
pedir_valores(m2);
sumar(m1, m2, m3);
printf("Matriz 1:\n");
mostrar_valores(m1);
printf("Matriz 2:\n");
mostrar_valores(m2);
printf("Suma:\n");
mostrar_valores(m3);

14
15
16
17
18
19
20
21
22
23

return 0;

24
25

26
27
28
29
30
31
32
33
34
35

// Implementaci
on de las funciones
void pedir_valores( int m[][M] ) {
int i, j;
for( i=0; i<N; i++) {
for( j=0; j<M; j++) {
printf("Introduce valor del elemento ( %d, %d): ", i, j);
scanf(" %d", &m[i][j]);
}
}

36
37
38
39
40
41
42
43
44
45

void mostrar_valores( int m[][M] ) {


int i, j;
for( i=0; i<N; i++) {
for( j=0; j<M; j++) {
printf(" %d\t" m[i][j]);
}
printf("\n");
}
}

46
47
48
49
50
51
52

102

void sumar( int x[][M], int y[][M], int z[][M] ) {


int i, j;
for( i=0; i<N; i++)
for( j=0; j<M; j++)
z[i][j] = x[i][j] + y[i][j];
}

5.10 Relacion entre vectores y punteros

5.10

Relacion entre vectores y punteros

Ya se explico en la Seccion 2.9 que todas las variables tienen asociada una direccion de
memoria, que indica la posicion que ocupa dicha variable en la memoria del ordenador.
En el caso de los vectores habra que precisar que, dado que e stos son, en realidad, un
conjunto de variables (a los que denominamos elementos del vector), cada una de estos
elementos tiene asociada una direccion.
Hay que entender que los elementos de un vector se almacenan de forma consecutiva
en memoria, por lo tanto, basta con conocer la direccion del primer elemento para saber
tambien la del resto. Esto es, dado el siguiente fragmento de codigo
1
2
3

int v[10];
int * p;
p = &v[0]

en el que se observa que el puntero p almacena la direccion de memoria del primer elemento del vector, entonces sabremos que el segundo elemento esta en la direccion p+1,
el tercero en p+2 y as sucesivamente. Vemos por tanto que, a partir del puntero p, es posible acceder a todos los elementos del vector. Para ello debemos recordar que mediante
el operador de indireccion * se puede acceder a la variable apuntada por un puntero. En
consecuencia, el fragmento de codigo
1
2
3
4

int v[10];
v[0] = 0;
v[1] = 1;
v[2] = 2;

es equivalente a1
1
2
3
4
5
6

int v[10];
int * p;
p = &v[0];
*p = 0;
*(p+1) = 1;
*(p+2) = 2;
1 Conviene aclarar que cada uno de los elementos de un vector puede ocupar m
as de un byte, dependiendo del
tipo. Por ejemplo, los enteros ocupan 4 bytes. Esto quiere decir que cada elemento del vector ocupa en realidad
4 posiciones (direcciones) de memoria. Debe entenderse que cuando se emplea aritmetica de punteros, dado un
puntero p, la operacion p+n en realidad hace p+(n*tamano-del-dato). Es por ello que en la declaracion de un
puntero, resulta necesario indicar el tipo de dato al cual apunta.

103

Captulo 5. Vectores

Por otro lado hay que saber que el nombre de un vector, utilizado sin corchetes, representa
en realidad la direccion de memoria de su primer elemento. Esto es, dado
1

int v[10];

v es, en realidad, &v[0].

En consecuencia, el fragmento de codigo anterior tambien podra haberse escrito del siguiente modo:
1
2
3
4

int v[10];
*v = 0;
*(v+1) = 1;
*(v+2) = 2;

En resumen, dada la declaracion:


1

int v[10];

y entendiendo que v es, en realidad, la direccion del primer elemento del vector, entonces
las expresiones v[i] y *(v+i) son equivalentes. Esto es, podemos acceder a los elementos de un vector mediante la sintaxis de corchetes que hemos venido utilizando hasta
ahora, o mediante la sintaxis de punteros que acabamos de presentar.
Por ejemplo, dado el vector int v[10], el siguiente bucle inicializa todos los elementos
del vector a cero:
1
2

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


// Equivale a v[i] = 0;
*(v+i) = 0;

Por u ltimo hay que entender que, cuando en los ejemplos de la Seccion 5.5 empleabamos
la sentencia
1

inicializar(v);

en realidad lo que se esta ejecutando es


1

104

inicializar(&v[0]);

5.11 Cadenas de caracteres

lo que explica que los vectores siempre se pasan por referencia, aunque para ello no
utilicemos explcitamente la sintaxis clasica de punteros.
La implementacion de la funcion inicializar con la sintaxis empleada hasta el momento
1
2
3
4
5

void inicializar( int v[] ) {


int i;
for( i=0; i<N; i++ ) // Suponiendo un vector de tama
no N
v[i] = 0;
}

puede reescribirse como


1
2
3
4
5

void inicializar( int * v ) {


int i;
for( i=0; i<N; i++ ) // Suponiendo un vector de tama
no N
(v+i)
=
0;
*
}

o incluso alternando ambos tipos de sintaxis:


1
2
3
4
5

void inicializar( int * v ) {


int i;
for( i=0; i<N; i++ ) // Suponiendo un vector de tama
no N
v[i] = 0;
}

5.11

Cadenas de caracteres

Cuando se necesita almacenar informacion en forma de texto en una variable (nombres,


apellidos, direcciones o, en general, cualquier cadena de texto) empleamos una cadena
de caracteres. Una cadena de caracteres (tambien llamada string) es un vector de tipo
char que almacena una determinada secuencia de caracteres (letras, dgitos, signos de
puntuacion...) seguida del caracter especial \0. Este caracter especial se emplea para
indicar el n de la cadena, de modo que lo que sigue a \0 en el vector es, por lo general,
ignorado, por lo que el contenido de estos elementos es irrelevante. La Figura 5.3 muestra
la cadena HOLA almacenada en un vector de tipo char.
Cuando trabajamos con vectores de caracteres, es muy habitual que desconozcamos a
priori el tamano de la cadena que se va a almacenar, por lo que sera necesario declarar
estos vectores sucientemente grandes como para dar cabida a la cadena mas larga que
105

Captulo 5. Vectores
0

\0

Figura 5.3: Ejemplo de una cadena en C.

queramos permitir, mas un elemento extra para el caracter \0. Por ejemplo, si estuviesemos pensando en una aplicacion que almacene mensajes de twitter, deberamos utilizar
vectores de tamano 141, ya que un tweet tiene una longitud maxima de 140 caracteres.
El motivo de usar el caracter especial \0 es el de permitir almacenar cadenas de menor
longitud al tamano del vector. Si no tuviesemos este caracter especial, sera imposible
determinar que caracteres del vector forman parte de la cadena y cuales no. Como hemos
dicho anteriormente, \0 indica n de cadena. Debe entenderse que una variable de tipo
char siempre contiene un valor (un caracter) aunque no haya sido inicializada (al igual
que una variable int siempre va a contener un entero). No es posible almacenar algo
que indique no_valor, de ah la necesidad de indicar de algun modo donde termina la
cadena. En el ejemplo de la gura anterior, los elementos del 5 al 9, en caso de no haber
sido inicializados, contendran cualquier caracter arbitrario.
Las cadenas de caracteres, por sus peculiaridades, tienen un tratamiento especial en C. La
principal diferencia con otro tipo de vectores es que, en este caso, no es necesario operar
elemento a elemento, sino que la librera de C dispone de funciones que permiten realizar
ciertas operaciones con la cadena completa. En los siguientes apartados de explican las
principales operaciones con cadenas de caracteres.

5.11.1

Entrada y salida con cadenas de caracteres

En este apartado estudiaremos las funciones printf y puts empleadas para mostrar
cadenas de caracteres por al salida estandar (esto es, por pantalla) y las funciones scanf y
gets empleadas para almacenar cadenas introducidas por la entrada estandar (el teclado)
en un vector de caracteres.
Funcion printf:
Dado el siguiente vector de caracteres
1

char cad[50];

en el que supondremos almacenada una cadena con su correspondiente \0 al nal de la


misma, la funcion printf permite mostrar su contenido por pantalla del siguiente modo:
1

106

printf(" %s", cad);

5.11 Cadenas de caracteres

Como vemos, el especicador %s indica que la variable asociada es una cadena de


caracteres (string). En este caso, la funcion printf mostrara por pantalla los caracteres
almacenados en cad, desde la posicion 0 del vector hasta la aparicion de \0 (sin incluir
este u ltimo).
El resultado del codigo anterior sera identico al siguiente:
1
2
3
4
5

i = 0;
while( cad[i] != \0 ) {
printf(" %c", cad[i]);
i++;
}

En este caso, en lugar de mostrar toda la cadena de golpe con el especicador %s, lo
vamos haciendo caracter a caracter con el especicador %c. Observese que, en el primer
caso, a la funcion printf se le pasa como argumento el vector completo (cad), mientras
que en este segundo ejemplo se le pasa, en cada iteracion, un u nico elemento del vector
(cad[i]), que corresponde a una variable de tipo char. Obviamente, el primer metodo
resulta mucho mas comodo.
Por supuesto, al igual que ocurre con otras variables, la funcion printf puede mostrar,
junto con la cadena, otra informacion. Por ejemplo:
1

printf("La cadena cad contiene el texto %s\n", cad);

Funcion puts:
La funcion puts (acronimo de put string) se emplea tambien para mostrar cadenas de
caracteres por pantalla. Su sintaxis es:
puts(nombre_cadena);

La principal diferencia con printf radica en que puts no admite otros argumentos extra
y en que inserta automaticamente un salto de lnea (\n). La instruccion
1

puts(cad);

es equivalente a
1

printf(" %s\n", cad);

107

Captulo 5. Vectores

Funcion scanf:
Para almacenar en una cadena de caracteres un texto introducido por teclado se puede
utilizar la sentencia scanf con el especicador %s, tal y como muestra el siguiente
fragmento de codigo:
1
2
3

char cad[50];
printf("Introduce un texto: ");
scanf(" %s", cad);

El codigo anterior provoca que el texto introducido por teclado se almacene en el vector
cad. Ademas, el caracter especial \0 es introducido automaticamente al nal del texto.
Debe prestarse especial atencion a que, en este caso, en la sentencia scanf no se utiliza
el operador & precediendo al nombre de la variable, tal y como venamos haciendo hasta
ahora. La explicacion de ello esta relacionada con lo explicado en la Seccion 5.10, en la
que se vio que el nombre de un vector (cad en este caso) es, en realidad, la direccion de
memoria de su primer elemento (&cad[0]). Es decir, sin necesidad de poner el operador
& delante del nombre de la cadena, e sta ya se esta pasando por referencia.
La funcion scanf lee caracteres hasta que se encuentre un salto de lnea (provocado mediante la pulsacion de la tecla enter), un espacio en blanco o un tabulador. Esto implica
que scanf no permite la lectura de cadenas que contengan espacios en blanco. Por ejemplo, si tras la ejecucion de la sentencia scanf("%s", cad) el usuario introduce por
teclado el texto "Esto es una cadena" seguido de la pulsacion de la tecla enter, en
la cadena cad u nicamente se almacenara el texto "Esto". Para la lectura de cadenas que
contengan espacios en blanco debera emplearse la funcion gets.
Funcion gets:
Esta funcion lee caracteres de la entrada estandar (el teclado) hasta que encuentra un salto
de lnea y los almacena en una cadena. El salto de lnea (\n) no se almacena en la cadena.
A continuacion se muestra un ejemplo de uso:
1
2
3

108

char cad[50];
printf("Introduce un texto: ");
gets(cad);

5.11 Cadenas de caracteres

5.11.2

Funciones de manipulacion de cadenas de caracteres

La librera de C dispone de diversas funciones para el manejo de cadenas de caracteres.


Todas ellas estan denidas en el chero de cabecera string.h, por lo que para utilizarlas
en un programa debera incluirse dicho chero mediante la siguiente instruccion:
1

#include <string.h>

A continuacion se muestran algunas de las funciones mas usadas. En este caso usaremos
la notacion de punteros (ver Seccion 5.10), ya que es de este modo como se encuentra habitualmente en la bibliografa. Debe entenderse que char * str es equivalente a
char str[].
Funcion
int strlen(char *str)

char * strcpy(char *c1, char *c2)

char * strcat(char *c1, char *c2)

int strcmp(char *c1, char *c2)

Descripcion
Devuelve la longitud de la cadena str,
sin incluir el caracter de terminacion
\0.
Copia la cadena almacenada en c2 a la
cadena c1, incluyendo el caracter de terminacion \0. Devuelve un puntero a la
cadena destino (la misma que se pasa como parametro, por lo que este valor devuelto es redundante).
Concatena la cadena almacenada en c2
a continuacion la cadena almacenada en
c1. El caracter de terminacion \0 de
la cadena c1 se sobreescribe con el primer caracter de la cadena c2 y se anade
un nuevo \0 en c1, al nal de la concatenacion de las dos cadenas. Devuelve
un puntero a la cadena destino (la misma
que se pasa como parametro, por lo que
este valor devuelto es redundante).
Compara las cadenas c1 y c2. Devuelve cero si ambas cadenas son iguales, un
valor positivo si c1 es, en orden alfabetico, posterior a c2, o un valor negativo si
c1 es, en orden alfabetico, anterior a c2.

109

Captulo 5. Vectores

A continuacion se muestra un ejemplo de uso:


1
2
3

#include <stdio.h>
#include <string.h>
#define N 50

4
5
6
7

int main() {
char cad1[N], cad2[N], cad3[N];
int x;

strcpy(cad1, "Hola"); // Copia en cad 1 el texto Hola


printf(" %s tiene %d caracteres\n", cad1, strlen(cad1));
strcpy(cad2, cad1);
// Copia en cad2 el contenido de cad1
strcat(cad2, "Adios"); // Concatena "Adios" a cad2
printf(" %s\n", cad2);
strcpy(cad3, "Mundo");
x = strcmp(cad1, cad3);
if( x == 0 )
printf(" %s y %s son iguales\n", cad1, cad3);
else if( x > 0 )
printf(" %s es alfab
eticamente posterior a %s\n", cad1, cad3
);
else
printf(" %s es alfab
eticamente anterior a %s\n", cad1, cad3)
;

9
10
11
12
13
14
15
16
17
18
19

20
21

22

return 0;

23
24

El resultado de la ejecucion del programa anterior es el siguiente:


Hola tiene 4 caracteres
HolaAdios
Hola es alfab
eticamente anterior a Mundo

5.12

Ejercicios resueltos

1. Implementar un programa que solicite 50 valores reales, los almacene en un vector,


y obtenga el valor maximo.

110

5.12 Ejercicios resueltos

SOLUCION:
1
2

#include <stdio.h>
#define N 50

3
4
5

void pedir_valores( float v[] );


float maximo( float v[] );

6
7
8

int main() {
float v[N], m;

pedir_valores(v);
m = maximo(v);
printf("M
aximo: %f\n", m);

10
11
12
13

return 0;

14
15

16
17
18
19
20
21
22
23

void pedir_valores( float v[] ) {


int i;
for( i=0; i<N; i++ ) {
printf("Introduce elemento %d: ", i);
scanf(" %f", &v[i]);
}
}

24
25
26
27
28
29
30
31
32
33
34

float maximo( float v[] ) {


int i;
float max;
max = v[0];
for( i=1; i<N; i++ ) {
if( v[i] > max )
max = v[i];
}
return max;
}

Como puede observarse, para obtener el maximo de un vector inicializamos una


variable (max en el ejemplo anterior) con el primer elemento del vector (lnea 28). A
continuacion se itera sobre el resto de elementos del vector y, si se encuentra algun
valor que supere lo que hasta el momento tenemos en max, se actualiza el valor de
esta variable (lneas 30-31). La busqueda del mnimo sera similar, cambiando la
comparacion > por <.
2. Implementar un programa que almacene 50 valores enteros en un vector, a continuacion solicite un nuevo valor, y diga si dicho valor se encuentra en el vector.
111

Captulo 5. Vectores

SOLUCION:
1
2

#include <stdio.h>
#define N 50

3
4
5

void pedir_valores( int v[] );


int pertenece( int v[], int x );

6
7
8

int main() {
int v[N], num;

pedir_valores(v);
printf("Introduce el valor a buscar: ");
scanf(" %d", &num);
if( pertenece(v, num) )
printf(" %d se encuentra en el vector\n", num);
else
printf(" %d no se encuentra en el vector\n", num);

10
11
12
13
14
15
16
17

return 0;

18
19

20
21
22
23
24
25
26
27

void pedir_valores( float v[] ) {


int i;
for( i=0; i<N; i++ ) {
printf("Introduce elemento %d: ", i);
scanf(" %f", &v[i]);
}
}

28
29
30
31
32
33
34

int pertenece( int v[], int x ) {


int i = 0;
while( i < N && v[i] != x )
i++;
return (i<N); // Devuelve 1 si i<N y 0 en caso contrario
}

Observese que en la funcion pertenece del ejercicio anterior el bucle de la lnea


31 naliza, bien cuando se encuentra el valor buscado, en cuyo caso el valor de la
variable i sera necesariamente menor que N, bien cuando se ha recorrido el vector
completo sin que se haya encontrado el valor buscado, en cuyo caso la variable i
valdra N.
3. Implementar una funcion que, dada una matriz de enteros de tamano NxM, obtenga
su traspuesta.
112

5.12 Ejercicios resueltos

SOLUCION:
1
2
3
4
5
6

void traspuesta( int m1[N][M], m2[M][N] ) {


int i, j;
for( i=0; i<N; i++ )
for( j=0; j<M; j++ )
m2[j][i] = m1[i][j];
}

4. Implementar una funcion que realice la misma operacion que la funcion strlen
de la librera de C (calculo de la longitud de una cadena).

SOLUCION:
1
2
3
4
5
6

int mi_strlen( char cad[] ) {


int i = 0;
while( cad[i] != \0 )
i++;
return i;
}

5. Implementar una funcion que, dada una cadena de caracteres que contiene palabras
separadas por un espacio en blanco, muestre la u ltima palabra de la cadena.

SOLUCION:
1
2

void ultima_palabra( char cad[] ) {


int i = 0;

// Nos situamos al final de la cadena


while( cad[i] != \0 )
i++;

4
5
6
7

// Retrocedemos hasta encontrar un espacio en blanco,


// o hasta el inicio en caso de que no haya espacios
while( i > 0 && cad[i] != )
i--;

8
9
10
11
12

// Mostramos los caracteres hasta el final


if( cad[i] == )
i++;
while( cad[i] != \0 ) {
printf(" %c", cad[i]);
i++;
}

13
14
15
16
17
18
19
20

113

Captulo 5. Vectores

5.13

Ejercicios propuestos

1. Escribir un programa que solicite la introduccion de 20 numeros y muestre:


Los valores que superan la media
El porcentaje de valores que supera la media
2. Implementar una funcion que, dados un vector y un valor, devuelva la posicion que
ocupa dicho valor en el vector (o -1 si el valor no se encuentra).
3. Implementar una funcion que, dado un vector de reales, muestre por pantalla todos
los maximos locales. Un elemento de un vector es un maximo local cuando su valor
es mayor que el anterior y el posterior. Consideraremos que el primer y u ltimo
elemento del vector no son, en ningun caso, maximos locales.
4. Escribir un programa que solicite una cadena de caracteres y nos diga si es palndroma (se lee igual del derecho que del reves).
5. Dados dos vectores V 1 y V 2 de tamanos N y M respectivamente, cuyos elementos
se encuentran ordenados de menor a mayor, implementar una funcion que almacene
en un tercer vector V 3 de tamano N + M los elementos de V 1 y V 2, de modo que
los elementos de V 3 queden tambien ordenados de menor a mayor.
6. Escribir un programa que permita jugar al Master Mind. El mecanismo de este
juego es el siguiente: el ordenador, de forma aleatoria, selecciona 4 numeros distintos, cada uno de ellos comprendido entre 1 y 8. El jugador tiene que averiguar
los numeros que ha seleccionado el ordenador y en el orden correcto. Para ello el
jugador ira haciendo intentos, indicando 4 numeros. Tras cada intento del jugador,
el ordenador le respondera la cantidad de numeros rojos (numero acertado y en
la posicion correcta) y la cantidad de numeros verdes (numero acertado, pero
no en la posicion correcta). Por ejemplo, suponiendo que el ordenador ha escogido
aleatoriamente el numero 4263, lo siguiente podra ser el desarrollo de una partida:
1234
1325
4632
4263

1
0
1
4

Rojo, 2 Verdes
Rojos, 2 Verdes
Rojo, 3 Verdes
Rojos

Nota.- Para generar un numero aleatorio entre 1 y 8, utilizar la instruccion rand()%8+1.


7. Implementar una funcion que, dada una matriz cuadrada, calcule su traspuesta
(cambiar las por columnas). La funcion recibira una u nica matriz, y el resultado se almacenara sobre la matriz original.
8. Se dispone de una matriz S de M las por N columnas, en la que cada la almacena
los valores en voltios de cierta senal electrica a lo largo del tiempo. La matriz S
114

5.13 Ejercicios propuestos

almacena, por tanto, los valores de M senales electricas muestreadas cada una de
ellas en N instantes de tiempo, representando el elemento S[i][j] el valor en voltios
de la senal i en el instante j. Por otro lado se dispone de un vector V de N elementos
que almacena igualmente los valores en voltios de una senal electrica muestreada
en N instantes de tiempo. Se desea comparar la senal almacenada en el vector V
con cada una de las M senales almacenadas en la matriz S y obtener aquella senal
de S que mas se parece a la almacenada en V . Para calcular como de parecidas
son dos senales X e Y se utiliza la formula:
similitud =

1
N


(Xi Yi )2

i=0

Cuanto menor sea este valor, mas parecidas seran las senales X e Y . Se pide implementar las funciones necesarias que nos permitan obtener, a partir de la matriz
S y el vector V , el ndice de la la en la matriz S donde se encuentra la senal mas
parecida a V .

115

Captulo 6

Estructuras
6.1

Introduccion

Cuando ciertas variables estan fuertemente relacionadas entre s, resulta interesante agruparlas bajo una misma entidad. Una estructura es un agrupamiento de variables en el que
e stas pueden ser de distinto tipo (a diferencia de los vectores, en los que todos sus elementos son del mismo tipo). A los elementos de una estructura se los denomina miembros.

6.2

Declaracion de variables de tipos estructurados

Hasta ahora se han visto los tipos de datos simples int, float, char, double y el tipo
puntero. El lenguaje C permite crear nuevos tipos de datos para denir agrupaciones de
variables de distinta naturaleza. Este es el tipo struct. A diferencia de los tipos basicos,
que ya estan denidos en el lenguaje y pueden ser utilizados para declarar variables, el
tipo struct debe crearse a medida, con los miembros que nos interese en cada caso. Una
vez creado el tipo, sera posible declarar variables del mismo, de manera similar a como
se declaran variables de cualquier otro tipo.
En resumen, para declarar variables de un tipo estructurado se requieren dos pasos:
Denir un tipo de dato estructurado (se denen los miembros que va a tener
nuestro agrupamiento).
Declarar variables de dicho tipo.

117

Captulo 6. Estructuras

6.2.1

Denicion de tipos estructurados

Para denir un tipo estructurado se debe dar un nombre al nuevo tipo e indicar el conjunto
de elementos (miembros) que contendra.
struct nombre_tipo_datos {
tipo_dato miembro1;
tipo_dato miembro2;
. . .
};

Por ejemplo:
1
2
3
4
5
6

struct persona {
char nombre[50];
float altura;
float peso;
int
anyo_nacimiento;
};

En este ejemplo se dene un nuevo tipo de datos de nombre struct persona que establece un agrupamiento de cuatro miembros (nombre, altura, peso y anyo_nacimiento).
Debe quedar claro que el nombre del nuevo tipo es la palabra compuesta struct persona
y no simplemente persona.
Por otro lado es importante entender que de momento no hemos declarado ninguna variable. Simplemente hemos denido un nuevo tipo de datos. En este ejemplo struct persona
es equivalente a int o float en el sentido de que es un tipo de dato. La diferencia
principal es que struct persona es un tipo compuesto (esto es, una variable de tipo
struct persona podra almacenar un conjunto de valores) mientras que int, por poner
un ejemplo, es untipo basico (una variable de tipo int solo podra almacenar un valor).
Una vez denido el tipo, sera posible declarar variables del mismo.

6.2.2

Declaracion de variables

Una vez denido el tipo de datos, las variables de un tipo estructurado se declaran como
cualquier otra variable:
tipo nombre_variable;

Por ejemplo, la sentencia


1

118

struct persona p;

6.3 Operaciones con estructuras

crea una variable p de tipo struct persona. Dicha variable p es una variable compuesta, ya que internamente almacena varios datos. Concretamente la variable p contiene
cuatro miembros: nombre, altura, peso y anyo_nacimiento. De hecho, cuando se
declara la variable p, se reserva memoria para almacenar 50 caracteres (nombre), 2 oats
(peso y altura) y 1 entero (ano de nacimiento).

6.3
6.3.1

Operaciones con estructuras


Inicializacion

Al igual que ocurre con otros tipos de variables, las variables de tipo estructurado se
pueden inicializar en el momento de la declaracion. Para ello se encierran entre llaves y se
separan por comas los valores con los que se desea inicializar cada uno de los miembros.
1

struct persona p = {"W. Amadeus Mozart", 1.52, 47, 1756};

6.3.2

Acceso a los miembros de una estructura

Para acceder a los miembros de una estructura se utiliza la siguiente sintaxis:


nombre_variable.miembro

El par nombre_variable.miembro se utiliza de modo similar a como se usara cualquier otra variable. Por ejemplo, dada la variable p de tipo struct persona lo siguiente
seran instrucciones validas:
1
2
3
4

p.peso=60;
p.altura=1.75;
printf(" %s", p.nombre);
scanf(" %d", &p.anyo_nacimiento);

A continuacion se muestra un ejemplo completo:


1

#include <stdio.h>

2
3
4
5
6
7

// Definimos el tipo de dato struct persona


struct persona {
char nombre[50];
float altura;
float peso;

119

Captulo 6. Estructuras

int

8
9

anyo_nacimiento;

};

10
11
12

13

int main() {
struct persona p;
persona
float imc;

// Declaramos una variable de tipo struct

14

printf("Introduce tu nombre: ");


scanf(" %s", p.nombre);
printf("Introduce tu altura (en metros): ");
scanf(" %f", &p.altura);
printf("Introduce tu peso (en kg.): ");
scanf(" %f", &p.peso);
printf("Introduce tu a
no de nacimiento: ");
scanf(" %d", &p.anyo_nacimiento);

15
16
17
18
19
20
21
22
23

imc = p.peso/(p.altura*p.altura);
printf("Hola %s ", p.nombre);
printf("Tu Indice de Masa Corporal es %f\n", imc);
return 0;

24
25
26
27
28

Observese que los tipos estructurados suelen denirse a nivel global, fuera de la funcion
main. De este modo sera posible declarar variables del tipo estructurado no solo en main
sino en cualquier otra funcion de nuestro programa.

6.3.3

Asignacion

A diferencia de lo que ocurre con los vectores, entre los que no se permite la asignacion,
entre variables de tipo estructurado s que es posible realizar esta operacion.
Por ejemplo, la operacion
1
2
3

struct persona p1, p2;


. . .
p1 = p2;

es valida y sera equivalente a:


1
2
3
4

120

struct persona p1, p2;


. . .
strcpy(p1.nombre, p2.nombre);
p1.altura = p2.altura;

6.3 Operaciones con estructuras

5
6

p1.peso = p2.peso;
p1.anyo_nacimiento = p2.anyo_nacimiento;

Es curioso observar que aunque no es posible copiar todos los elementos de un vector
en otro mediante la operacion de asignacion (debe hacerse elemento a elemento mediante un bucle o, en el caso de las cadenas, mediante la funcion strcpy), si dicho vector se
encuentra dentro de una estructura, entonces la asignacion s es posible. En el ejemplo anterior, cuando se hace la asignacion p1 = p2 se esta copiando (entre otras cosas) el vector
p2.nombre en p1.nombre. Sin embargo, hacer directamente p1.nombre = p2.nombre
no sera valido ya que, como se ha mencionado, la asignacion entre vectores no esta permitida.

6.3.4

Otras operaciones

En general, salvo la asignacion, el resto de operaciones con estructuras (lectura, escritura,


comparacion, etc.) hay que hacerlas miembro a miembro.
Por ejemplo, el siguiente codigo sera incorrecto si lo que pretendemos es comparar el
contenido de dos estructuras:
1
2
3
4

struct persona p1, p2;


. . .
if( p1 == p2 ) {
printf("Ambas personas tienen el mismo nombre, altura, peso y
a
no nac.");

La forma correcta de hacer esta comparacion sera miembro a miembro:


1
2
3
4
5
6

struct persona p1, p2;


. . .
if( strcmp(p1.nombre, p2.nombre)==0 &&
p1.altura==p2.altura && p1.peso==p2.peso &&
p1.anyo_nacimiento==p2.anyo_nacimiento)
printf("Ambas personas tienen el mismo nombre, altura, peso y
a
no nac.");

121

Captulo 6. Estructuras

6.4

Estructuras anidadas

Los miembros de una estructura pueden ser de cualquier tipo de datos, incluido otras
estructuras. Por ejemplo:
1
2
3
4
5
6
7
8
9
10
11

struct fecha {
int dia;
int mes;
int anyo;
};
struct persona {
char
float
float
struct fecha
};

nombre[50];
altura;
peso;
fecha_nacimiento;

En este ejemplo el tipo struct persona contiene un miembro (fecha_nacimiento)


que es, a su vez, de otro tipo estructurado. Para acceder a un miembro de una estructura
anidada se utiliza la sintaxis:
nombre_variable.miembro.submiembro;

Por ejemplo, dada la denicion de struct persona anterior, se podra hacer:


1

struct persona p;

2
3
4
5
6

strcpy(p.nombre, "Albert Einstein");


p.fecha_nacimiento.dia=14;
p.fecha_nacimiento.mes=3;
p.fecha_nacimiento.anyo=1879;

6.5

Vectores de estructuras

Es muy habitual utilizar las variables de tipo estructurado como elementos de vectores.
Un vector de variables estructuradas se declara de forma similar a como se declara un
vector de cualquier otro tipo de datos:
tipo nombre_vector[dimension];

Por ejemplo, la instruccion


122

6.5 Vectores de estructuras

struct persona v[100];

declara 100 variables de tipo struct persona. En este caso, cada elemento del vector
v contiene una variable de tipo struct persona y, por tanto, debera tratarse como tal.
Por ejemplo, para acceder al nombre de la persona almacenada en la primera posicion del
vector emplearamos v[0].nombre y para acceder al peso de la u ltima v[99].peso.
En el siguiente ejemplo se dene una estructura de tipo alumno con dos miembros (nombre y nota), a continuacion se declara un vector de N alumnos, se introducen los datos de
cada uno, se calcula la nota media y nalmente se muestra un listado de los aprobados:
1
2

#include <stdio.h>
#define N 100

3
4
5
6
7

struct alumno {
char nombre[50];
float nota;
};

8
9
10
11
12
13
14
15
16
17
18
19

int main() {
struct alumno v[N]; // Declaramos un vector de 100 alumnos
int i;
float suma;
// Introducir datos
for(i=0; i<N; i++) {
printf("Nombre: ");
scanf(" %s", v[i].nombre);
printf("Nota: ");
scanf(" %f", &v[i].nota);
}

20

// Calcular nota media


suma=0;
for(i=0; i<N; i++)
suma += v[i].nota;
printf("Nota media: %f\n", suma/N);

21
22
23
24
25
26

// Mostrar aprobados
printf("Listado de aprobados\n");
for(i=0; i<N; i++) {
if( v[i].nota >= 5 )
printf(" %s\n", v[i].nombre);
}
return 0;

27
28
29
30
31
32
33
34

123

Captulo 6. Estructuras

6.6

Punteros a estructuras

Los punteros a variables de tipo estructurado se utilizan de forma similar a como se emplean los punteros a otro tipo de variables.
Dada la variable
1

struct alumno un_alumno;

es posible almacenar su direccion de memoria en otra variable de tipo struct alumno *


(puntero a struct alumno), tal y como se muestra a continuacion:
1
2

struct alumno un_alumno;


struct alumno * puntero_alumno;

3
4

puntero_alumno = &un_alumno;

// Guardamos en puntero_alumno la
// direcci
on de la variable
un_alumno

A partir de este momento es posible acceder a la variable un_alumno a traves de la propia


variable, o a traves de su puntero.
1

un_alumno.nota = 10;

es equivalente a
1

(*puntero_alumno).nota = 10;

ya que en este contexto *puntero_alumno equivale a un_alumno.


El lenguaje C permite una sintaxis alternativa al * para el caso particular de punteros a estructuras. Dado un puntero a una variable estructurada, es posible acceder a los miembros
de esa variable segun la sintaxis habitual
(*puntero).miembro

o con la sintaxis alternativa


puntero->miembro

124

6.7 Paso de estructuras como parametros

6.7

Paso de estructuras como parametros

Las estructuras, como cualquier otro tipo de variables, pueden pasarse a una funcion como parametro. A continuacion se muestran distintos ejemplos de paso de estructuras como parametros. Tal y como podra observarse, no existen diferencias respecto al paso de
parametros de otro tipo de variables.

6.7.1

Pasar los miembros de forma independiente

Una posibilidad consiste en pasar los distintos miembros de la estructura de forma independiente. Esto es equivalente a pasar variables de tipos basicos.
1

#include <stdio.h>

2
3
4
5
6

struct alumno {
char nombre[50];
float nota;
};

7
8

void mostrar_alumno( char nombre[], float nota );

9
10
11

int main() {
struct alumno un_alumno;

12

strcpy(un_alumno.nombre, "Dennies Ritchie");


un_alumno.nota = 10;

13
14
15

// Pasamos nombre y nota de forma independiente


mostrar_alumno(un_alumno.nombre, un_alumno.nota);
return 0;

16
17
18
19

20
21
22
23

void mostrar_alumno( char nombre[], float nota ) {


printf(" %s %f\n", nombre, nota);
}

6.7.2

Pasar una estructura completa por valor

Tambien es posible pasar toda la estructura completa, como una sola variable. En este caso, y siguiendo con el ejemplo anterior, la funcion mostrar_alumno recibira una
variable de tipo struct alumno.
Nota.- Para simplicar el codigo, en sucesivos ejemplos supondremos ya denido el tipo
struct alumno , y todas las funciones declaradas.
125

Captulo 6. Estructuras

1
2

int main() {
struct alumno un_alumno;

strcpy(un_alumno.nombre, "Dennies Ritchie");


un_alumno.nota = 10;

4
5
6

// Pasamos toda la estructura de golpe


mostrar_alumno(un_alumno);
return 0;

7
8
9
10

11
12
13
14

void mostrar_alumno( struct alumno a ) {


printf(" %s %f\n", a.nombre, a.nota);
}

Es importante entender que las estructuras se pasan por valor, tal y como ocurre con las
variables de tipos basicos (o primitivos). Esto quiere decir que cualquier modicacion
que hagamos sobre los miembros de la estructura no tendra efecto fuera de la funcion
donde se realiza (la funcion que recibe la estructura opera con una copia de la misma). A
continuacion se muestra un ejemplo:
1
2
3
4
5
6

int main() {
struct alumno un_alumno;
. . .
subir_nota(un_alumno);
. . .
}

7
8
9
10

void subir_nota( struct alumno a ) {


a.nota = a.nota + 0.5;
}

En este ejemplo la funcion subir_nota no tiene ningun efecto sobre la variable un_alumno.
Esta variable seguira teniendo el mismo valor que tena antes de la llamada a la funcion
subir_nota. Obviamente, aunque a la variable a de la funcion subir_nota le hubieramos dado el nombre un_alumno la situacion sera exactamente la misma.

126

6.7 Paso de estructuras como parametros

6.7.3

Pasar una estructura completa por referencia

Si queremos que una funcion modique el valor de algun miembro de una variable estructurada, es necesario pasar dicha variable por referencia, esto es, pasar la direccion de
memoria de la variable en lugar del valor de la misma.
1
2
3
4
5
6

int main() {
struct alumno un_alumno;
. . .
subir_nota( &un_alumno );
. . .
}

7
8
9

void subir_nota( struct alumno * puntero_alumno ) {


(*puntero_alumno).nota = (*puntero_alumno).nota + 0.5;

10

// O con la sintaxis equivalente


// puntero_alumno->nota = puntero_alumno->nota + 0.5;

11
12
13

En este ejemplo, a traves de puntero_alumno somos capaces de modicar la variable a


la que apunta, esto es, un_alumno.

6.7.4

Pasar un vector de estructuras

El paso de un vector como parametro a una funcion se realiza siempre del mismo modo, independientemente de que el vector contenga variables simples o estructuradas. En
general hay que recordar que los vectores se pasan implcitamente por referencia. Esto quiere decir que cualquier modicacion que hagamos sobre los elementos del vector
(sean estructuras o no) se mantendra cuando termine la funcion donde se realiza.
1
2
3
4
5
6
7
8

int main() {
struct alumno v[N]; // Declaramos un vector de N alumnos
. . .
subir_notas( v );
// Subir la nota a todos los alumnos
. . .
}
void subir_notas( struct alumno v[] ) {
int i;

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


v[i].nota += 0.5;
// Este cambio perdurar
a cuando
// termine la funci
on

10
11
12
13

127

Captulo 6. Estructuras

6.7.5

Devolver una estructura

Las funciones tambien pueden devolver estructuras. Esta circunstancia, como veremos,
esta relacionada con el hecho de que la asignacion entre estructuras esta permitida.
1
2
3
4
5
6

int main() {
struct alumno un_alumno;
. . .
un_alumno = crear_alumno();
. . .
}

7
8
9

struct alumno crear_alumno() {


struct alumno a;

10

printf("Nombre: ");
scanf(" %s", a.nombre);
printf("Nota: ");
scanf(" %f", &a.nota);

11
12
13
14
15

return a;

16
17

La funcion crear_alumno declara una variable a de tipo struct alumno. Esta variable
es devuelta al nal de la funcion. Por tanto a la variable un_alumno declarada en la
funcion main se le asigna el valor de a. Esto es posible ya que, tal y como se ha comentado
en el apartado 6.3.3, el lenguaje C permite la operacion de asignacion entre estructuras.

6.8

Ejercicios resueltos

1. Dada la siguiente denicion de un tipo estructurado:


struct complejo {
float real;
float imag;
};

implementar un programa que solicite la parte real e imaginaria de un numero complejo, y calcule su modulo mediante el uso de una funcion.

SOLUCION:
1
2
3

128

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

6.8 Ejercicios resueltos

4
5
6
7

struct complejo {
float real;
float imag;
};

8
9

float modulo( struct complejo c );

10
11
12
13

int main() {
float m;
struct complejo c;

// Declaraci
on de variable

14

printf("Introduzca la parte real e imaginaria de un n


umero
complejo:");
scanf(" %f %f", &c.real, &c.imag);
m = modulo(c);
printf("M
odulo = %f\n", m);
return 0;

15

16
17
18
19
20

21
22
23
24

float modulo( struct complejo c ) {


return sqrt(c.real*c.real + c.imag*c.imag);
}

2. Implementar un programa que solicite el peso, altura y sexo de 10 personas y lo almacene en un vector de estructuras. A continuacion calcular, mediante una funcion,
el peso medio de las personas con una altura comprendida entre 1.70 y 1.80.

SOLUCION:
1
2

#include <stdio.h>
#define N 5

3
4
5
6
7
8

struct persona {
float peso;
float altura;
int sexo; // 1=Mujer
};

2=Hombre

9
10
11

void pedir_valores( struct persona v[] );


float media_peso( struct persona v[], float alt_min, float
alt_max );

12
13
14
15

int main() {
struct persona v[N];
float m;

16
17

pedir_valores(v);

129

Captulo 6. Estructuras

m = media_peso(v, 1.7, 1.8);


printf("Peso medio de personas con altura entre 1.7 y 1.8:
%f\n", m);

18
19

20

return 0;

21
22

23
24
25
26
27
28
29
30
31
32
33
34

void pedir_valores( struct persona v[] ) {


int i;
for( i=0; i<N; i++ ) {
printf("Peso: ");
scanf(" %f", &v[i].peso);
printf("Altura: ");
scanf(" %f", &v[i].altura);
printf("Sexo (1=Mujer 2=Hombre): ");
scanf(" %d", &v[i].sexo);
}
}

35
36

37
38
39
40
41
42
43
44
45
46
47
48
49

float media_peso( struct persona v[], float alt_min, float


alt_max ) {
int i, cont = 0;
float suma = 0;
for( i=0; i<N; i++ ) {
if( v[i].altura >= alt_min && v[i].altura <= alt_max ) {
suma += v[i].peso;
cont++;
}
}
if( cont == 0 )
return 0;
else
return suma/cont;
}

3. Se supone que disponemos de cierta imagen en color almacenada en una matriz de


tamano FIL x COL, de modo que cada elemento de la matriz dene un pxel de la
imagen mediante 3 valores r, g, b que representan, respectivamente, la cantidad de
rojo, verde y azul del pxel en cuestion. Se pide:
Declarar una matriz para que pueda almacenar una imagen tal y como se ha
denido.
Implementar una funcion que reciba una imagen y aumente el nivel de azul
de todos sus pxeles en 10 unidades.
Indicar como se realizara la llamada a la funcion.
130

6.9 Ejercicios propuestos

SOLUCION:
// Definimos el tipo RGB
struct RGB {
int r;
int g;
int b;
};

1
2
3
4
5
6
7

int main() {
// Declaramos una matriz de tipo RGB
struct RGB imagen[FIL][COL];
. . .
// Llamada a la funci
on
modificar_azul(imagen, 10);
. . .
}

8
9
10
11
12
13
14
15
16

// Implementaci
on de la funci
on
void modificar_azul( struct RGB img[FIL][COL], int x ) {
int i, j;
for(i=0; i<FIL; i++)
for(j=0; j<COL; j++)
img[i][j].b += x;
}

17
18
19
20
21
22
23

6.9

Ejercicios propuestos

1. Denir una estructura que pueda almacenar los datos de un alumno: dni, nombre,
grupo y nota (la nota de una u nica asignatura). A continuacion, declarar:
Una variable del tipo recien creado.
100 variables del tipo recien creado.
2. Modicar la estructura del ejercicio anterior para poder almacenar, ademas, la direccion del alumno (calle, numero, CP y poblacion). Utilizar una estructura de datos
anidada.
3. Escribir un programa que lea los datos de 100 personas (nombre, dni, fecha nacimiento y sexo) y muestre el numero de hombres y mujeres nacidos a partir de
1980.
4. Se dispone de una biblioteca de hasta 500 libros con codigos numericos correlativos desde el 1. Se quiere informatizar el proceso de mantenimiento. Escribir la
estructura de datos necesaria y las funciones para:
131

Captulo 6. Estructuras

Dar de alta un libro.


Dar de baja un libro. Se debe marcar el hueco que quedara en el vector para
usarlo en la proxima insercion.
Realizar prestamos.
Recibir devoluciones de libros prestados.
Listar por pantalla, para cada libro, el codigo, ttulo, autor y ubicacion.
5. En una aplicacion de expedicion de billetes de tren se dispone de la siguiente informacion por estacion:
Nombre de la estacion.
Distancia en kilometros desde esta estacion a la siguiente.
Precio desde esta estacion a la siguiente.
Se pide:
Denir la estructura de datos necesaria para representar la informacion anterior.
Segun esa representacion, y suponiendo almacenada en esa estructura la informacion ordenada segun el recorrido de la primera estacion a la estacion terminal, escribir una funcion que, recibiendo como parametros dos estaciones, escriba en pantalla
las estaciones por las que va a pasar el tren y devuelva la cantidad de kilometros y
precio necesario para hacer el recorrido.

132

Captulo 7

Gestion de cheros
7.1

Introduccion

La memoria principal de los ordenadores es volatil. Esto quiere decir que en ausencia de
corriente electrica la memoria pierde toda la informacion que tena almacenada. Por otro
lado, no tendra sentido introducir en un programa informatico los datos de matrcula de
20.000 alumnos y luego no poder almacenarlos de forma permanente para su posterior
recuperacion (no podemos conar en que el ordenador vaya a estar encendido indenidamente). Es necesario un mecanismo que nos permita almacenar los datos de nuestros
programas para futuros usos.
Los ordenadores pueden almacenar informacion de forma permanente1 en lo que se denomina sistemas de almacenamiento secundarios: discos, CDs, DVDs, memorias USB,
etc.
Un chero (o archivo) es una secuencia de bytes que representan cierta informacion, almacenada permanentemente en algun soporte digital. La informacion se almacena en los
cheros en forma de una secuencia de bytes terminada con el caracter especial EOF (End
Of File). El modo en que debe interpretarse la secuencia de bytes contenida en el chero dependera del sistema de codicacion empleado. Un mismo chero podra contener
caracteres, numeros, imagenes, etc. codicados cada uno en un sistema diferente. Es responsabilidad del programador conocer que criterios de codicacion tienen las secuencias
de bytes contenidas en el chero, para poder interpretar correctamente la informacion que
contiene.
1 El t
ermino permanente debera ser interpretado con cautela, ya que ningun sistema de almacenamiento
esta exento del riesgo de perdida de informacion. En este sentido, las copias de seguridad en mas de un medio
debera ser una practica habitual.

133

Captulo 7. Gestion de cheros

7.2

Tipos de cheros

Cuando se trabaja con cheros se distinguen basicamente dos formas en las que puede
almacenarse la informacion (lo que da lugar a dos tipos de cheros):
Como caracteres. En este caso se entiende que la secuencia de bytes almacenada
en el chero contiene una secuencia de caracteres representados en algun sistema
de codicacion de caracteres (ASCII, Unicode, etc.). Logicamente, este tipo de
cheros serviran para almacenar texto (mas concretamente texto plano, sin ningun
tipo de formato, ya que estos sistemas de codicacion no tienen capacidad para
representar conceptos como negrita, cursiva, tamano de la letra, etc.). Los cheros
que contienen u nicamente caracteres se denominan cheros de texto.
Como informacion binaria. En este caso se entiende que la secuencia de bytes representa informacion codicada en cualquier sistema (no necesariamente un sistema
de codicacion de caracteres). Por tanto, cualquier chero que no contenga texto
plano diremos que es un chero binario.
En ambos casos el chero contiene una secuencia de bytes terminada con el caracter
especial EOF. La u nica diferencia radica en la forma en la que el programa que trata el
chero debe interpretar la informacion en e l contenida.

7.3

Operaciones con cheros

Para trabajar con cheros hay que realizar siempre las siguientes acciones:
1. Abrir el chero mediante la instruccion fopen.
2. Realizar operaciones de lectura o escritura sobre el chero.
3. Cerrar el chero mediante la instruccion fclose.
Las operaciones de apertura y cierre de chero se realizan de modo similar, independientemente de que el chero sea de texto o binario, mientras que las operaciones de
lectura/escritura seran distintas en funcion del tipo de chero. En los siguientes apartados
se explican con detalle estas operaciones.

134

7.3 Operaciones con cheros

7.3.1

Abrir y cerrar cheros

Todos los cheros tienen un nombre que los identica (o, con mayor precision, un nombre
y un directorio). El lenguaje C, sin embargo, para operar con cheros no utiliza su nombre
sino lo que se conoce como descriptor de chero. Estos descriptores se almacenan en
variables de un nuevo tipo de datos hasta ahora no visto denominado FILE *.
Antes de realizar cualquier operacion con un chero es necesario abrirlo mediante la
instruccion fopen. Esta instruccion asocia un nombre de chero a un descriptor. Para
ello, recibe el nombre del chero como parametro y devuelve su descriptor. A partir de
este momento, y para el resto de operaciones, el nombre ya no volvera a usarse y, en su
lugar, se identicara el chero mediante su descriptor. La instruccion fopen se usa del
siguiente modo:
descriptor = fopen("nombre_del_fichero", modo);

donde:
descriptor debera ser una variable de tipo FILE *.
nombre_del_fichero es, obviamente, el nombre del chero. Si este nombre no

contiene la ruta de ningun directorio, entonces se entendera que el chero se encuentra en el directorio actual de trabajo.
modo es una cadena de caracteres que indica el modo en que se desea abrir el chero

(lectura, escritura o anadir) y el tipo de chero (binario o texto), tal y como se indica
en la siguiente tabla:
Texto
rt
wt

Binario
rb
wb

at

ab

rt+

rb+

wt+

wb+

at+

ab+

Abre el chero para lectura (read). El chero debe existir.


Abre el chero para escritura (write). Si el chero no existe,
se crea uno nuevo, en caso contrario se borra el contenido del
chero existente.

Abre el chero en modo anadir


(append). Similar al modo
de escritura, pero no borra el contenido de los cheros existentes, sino que anade los nuevos datos al nal.
Abre el chero tanto para lectura como para escritura. El
chero debe existir.
Abre el chero tanto para lectura como para escritura. Si
el chero no existe, se crea uno nuevo, en caso contrario se
borra el contenido del chero existente.

Abre el chero tanto para lectura como para anadir.


Si el
chero no existe, se crea uno nuevo.
135

Captulo 7. Gestion de cheros

Respecto a la tabla anterior debe puntualizarse que, en modo texto, puede omitirse la letra
t, esto es, tambien son validos los modos r, w, a, etc. En este caso se entiende
que se desea abrir en modo texto. Por otro lado, en los ejemplos presentados a lo largo de
este captulo abordaremos u nicamente los modos de solo lectura y solo escritura, esto es,
r, w, rb y wb.
Si, por ejemplo, pretendemos abrir un chero de texto de nombre datos.txt en modo
lectura, deberamos hacer:
1
2

FILE * f;
// Mi descriptor de fichero
f = fopen("datos.txt", "r"); // Abre el fichero en modo lectura

Si un chero no puede abrirse, la instruccion fopen devuelve el valor NULL. Es importante


comprobar si el chero se ha abierto correctamente antes de tratar de hacer cualquier
operacion con e l, tal y como se muestra en el siguiente ejemplo:
1

#include <stdio.h>

2
3
4
5
6
7
8
9

int main() {
FILE * f;
f = fopen("datos.txt", "r");
if( f == NULL ) {
printf("Error abriendo el fichero datos.txt \n");
return 0;
}

10

// Operaciones de lectura del fichero


. . .

11
12
13

En ocasiones resulta interesante que sea el propio usuario quien indique el nombre del
chero, en lugar de establecerlo directamente en el codigo del programa.
1
2
3

int main() {
FILE * f;
char nombre_fichero[512];

printf("Introduce nombre del fichero: ");


scanf(" %s", nombre_fichero);
f = fopen(nombre_fichero, "r");
. . .

5
6
7
8
9

136

7.3 Operaciones con cheros

Cuando un chero termina de usarse debe cerrarse siempre mediante la instruccion fclose.
fclose(descriptor_de_fichero);

7.3.2

Lectura y escritura de cheros de texto

La lectura y escritura de texto en un chero es muy similar a la lectura/escritura sobre la


consola. De modo muy parecido a como escribimos texto en la pantalla del ordenador,
podemos hacerlo sobre un chero. Igualmente, podemos leer la informacion contenida en
un chero de modo muy similar a como leemos los datos introducidos por teclado.
Cuando se realizan operaciones de lectura/escritura sobre cheros han de tenerse presente
dos conceptos:
Generalmente, el chero lo abriremos, bien en modo lectura, bien en modo escritura. Aunque existen modos de abrir un chero para lectura y escritura a la vez, esta
forma de operar no es la habitual. En este libro nos centraremos u nicamente en los
modos de solo lectura o solo escritura.
Tanto la lectura como la escritura se realizan de modo secuencial. Esto quiere decir
que operaciones sucesivas de escritura sobre un mismo chero iran anadiendo el
nuevo texto a continuacion del que ya haba, tal y como ocurre cuando escribimos
sobre la consola. Del mismo modo, en cada operacion de lectura se ira leyendo de
forma consecutiva la informacion contenida en el chero.
Para la lectura y escritura de cheros de texto utilizaremos las instrucciones fscanf y
fprintf respectivamente. Su uso es identico al de printf y scanf, excepto que, en
este caso, debe especicarse como primer argumento el descriptor del chero sobre el
que se desea leer/escribir. A continuacion se explica el uso de estas instrucciones.
Operaciones de escritura
La sintaxis de la instruccion fprintf es la siguiente:
int fprintf(descriptor, formato [, lista_de_variables]);

donde descriptor hace referencia al descriptor del chero obtenido mediante la funcion
fopen. En el parametro formato podran utilizarse los codigos %d, %f, %c y %s, de modo
similar a como se emplean en la funcion printf.
El siguiente programa de ejemplo escribe el texto Hola Mundo en un chero de nombre
hola.txt.

137

Captulo 7. Gestion de cheros

1
2
3

#include <stdio.h>
int main() {
FILE * f;

f = fopen("hola.txt", "w"); // Abrir el fichero en modo


escritura
fprintf(f, "Hola Mundo");
// Escribir en el fichero
fclose(f);
// Cerrar el fichero
return 0;

6
7
8
9

En caso de que el chero hola.txt existiese antes de la ejecucion del programa, su


contenido original habra sido borrado. Si hubiesemos querido mantener los datos del
chero original y haber anadido el nuevo texto a continuacion, simplemente tendramos
que haber abierto el chero en modo append (anadir).
1

f = fopen("hola.txt", "a");

Por supuesto, se puede hacer mas de un fprintf sobre el mismo chero. Cada instruccion fprintf ira anadiendo el nuevo texto a continuacion del que ya tenamos, tal y
como se muestra en el siguiente fragmento de codigo:
1
2
3
4
5
6

f = fopen("quijote.txt", "w");
fprintf(f, "En un lugar de la Mancha ");
fprintf(f, "de cuyo nombre no quiero acordarme,\n");
fprintf(f, "no ha mucho tiempo que viv
a un hidalgo ");
fprintf(f, "de los de lanza en astillero\n");
fclose(f);

Observese que el caracter \n se puede utilizar exactamente igual a como se hace en la


sentencia printf.
Por otro lado, que el chero sea de texto no quiere decir que no podamos escribir numeros.
Simplemente, e stos se escribiran como caracteres, utilizando la codicacion ASCII, tal y
como se muestra en el siguiente ejemplo:
1
2
3
4

#include <stdio.h>
int main() {
FILE * f;
int i, j;

5
6
7

138

f = fopen("tablas_de_multiplicar.txt", "w");
for( i = 1; i <= 10; i++ ) {

7.3 Operaciones con cheros

fprintf(f, "TABLA DEL %d\n", i);


for( j = 1; j <= 10; j++ )
fprintf(f, " %d x %d = %d\n", i, j, i*j);

8
9
10

}
fclose(f);
return 0;

11
12
13
14

Operaciones de lectura
La sintaxis de la instruccion scanf es la siguiente:
int fscanf(descriptor, formato [, lista_de_punteros_a_variables]);

La lectura, como se ha comentado anteriormente, se realiza de forma secuencial, comenzando desde el inicio del chero. Esto quiere decir que cada operacion de lectura con
fscanf ira leyendo partes o trozos consecutivos del chero. Cuanta informacion se lee
en cada sentencia fscanf depende de el/los codigo/s % que se indiquen en el parametro
formato.
Por ejemplo
1

fscanf(f, " %s", cadena);

leera una palabra (se entiende por palabra una secuencia de caracteres delimitada por un
espacio en blanco, un tabulador, un salto de lnea o el caracter especial EOF). La proxima
instruccion fscanf leera la informacion que aparezca a continuacion de dicha palabra.
Por ejemplo, dado un chero de texto que almacena cien numeros enteros, el siguiente
programa lee todos los valores del chero y calcula su media:
1
2
3
4

#include <stdio.h>
int main() {
FILE * f;
int i, num, suma = 0;

5
6
7
8
9
10
11
12

f = fopen("numeros.txt", "r");
if( f == NULL ) {
printf("Error abriendo fichero\n");
return 0;
}
for( i = 1; i <= 100; i++ ) {
fscanf( f, " %d", &num );

139

Captulo 7. Gestion de cheros

suma += num;
}
printf("Media = %f\n", suma/100.0);
fclose(f);
return 0;

13
14
15
16
17
18

En el ejemplo anterior hemos ledo un chero de tamano conocido, sin embargo, Es muy
habitual que no sepamos de antemano la cantidad de datos almacenados en el chero. En
este caso habra que ir leyendo hasta que se encuentre el caracter EOF. Para ello hay que
saber que la instruccion fscanf devuelve el numero de elementos ledos o EOF en caso
de que se haya alcanzado el nal del chero, con lo que sera facil averiguar cuando hemos
terminado de leer toda la informacion.
El siguiente ejemplo lee, caracter a caracter, el contenido completo de un chero y lo
muestra por pantalla.
1
2
3
4

#include <stdio.h>
int main() {
FILE * f;
char caracter;

// Abrir el fichero
f = fopen("quijote.txt", "r");
if( f == NULL ) {
printf("Error abriendo el fichero quijote.txt\n");
return 0;
}

6
7
8
9
10
11
12

// Leer el fichero car


acter a car
acter y mostrarlo por
pantalla
while( fscanf(f," %c", &caracter) != EOF )
printf(" %c", caracter);

13

14
15
16

// Cerrar el fichero
fclose(f);
return 0;

17
18
19
20

En el ejemplo anterior se emplea un esquema muy habitual para la lectura de cheros de


texto:
1
2
3

140

while( fscanf( . . . ) != EOF ) {


// Procesar los datos le
dos
}

7.3 Operaciones con cheros

Con este esquema, en la propia condicion del bucle while se realiza una lectura y se comprueba si dicha lectura ha tenido e xito, en cuyo caso fscanf devuelve un valor distinto
de EOF. En este caso, se procesan los datos recien ledos y se vuelve al inicio del bucle
para realizar la siguiente lectura. En el momento en que fscanf devuelva EOF, sera senal
de que hemos alcanzado el nal del chero.
En los ejemplos mostrados hasta ahora el nombre del chero viene jado en el programa,
pero en ocasiones puede resultar necesario que sea el usuario quien introduzca dicho
nombre. El siguiente ejemplo copia el contenido del chero que elija el usuario sobre otro
chero.
1
2
3
4

#include <stdio.h>
int main() {
FILE * f_in, * f_out;
char nom_fich_in[512], nom_fich_out[512], caracter;

5
6

7
8
9
10
11
12
13

// Pedir nombre del fichero de entrada y abrirlo en modo


lectura
printf("Introduce nombre de fichero a copiar: ");
scanf(" %s", nom_fich_in);
f_in = fopen(nom_fich_in, "r");
if( f_in == NULL ) {
printf("Error abriendo el fichero %s\n", nom_fich_in);
return 0;
}

14
15

16
17
18
19
20
21
22

// Pedir nombre del fichero de salida y abrirlo en modo


escritura
printf("Introduce nombre de fichero destino: ");
scanf(" %s", nom_fich_out);
f_out = fopen(nom_fich_out, "w");
if( f_out == NULL ) {
printf("Error abriendo el fichero %s\n", nom_fich_out);
return 0;
}

23
24

25
26

// Leer el fichero de entrada y volcar el contenido en el de


salida
while( fscanf(f_in," %c", &caracter) != EOF )
fprintf(f_out, " %c", caracter);

27
28
29
30

// Cerrar los ficheros


fclose(f_in);
fclose(f_out);

31

141

Captulo 7. Gestion de cheros

return 0;

32
33

Debe observarse que en este ejemplo, los nombres de los cheros son introducidos por el
usuario y almacenados en cadenas de caracteres (lneas 8 y 17). A continuacion, en las
instrucciones fopen (lneas 9 y 18) utilizamos como argumento estas variables en lugar
de una cadena constante.
Tal y como se ha visto ya en algun ejemplo, es perfectamente posible que un chero de
texto contenga numeros. Simplemente, estaran almacenados mediante sus correspondientes caracteres. En este caso es posible, a la hora de leerlos mediante fscanf, guardarlos
en nuestro programa en variables de tipos numericos (int, float o double). El propio
fscanf hara la conversion al tipo especicado. En el siguiente ejemplo se parte de un
chero que contiene, por cada lnea, el nombre de un alumno, el grupo en el que esta matriculado y su nota. El programa lee toda esta informacion y la almacena en un vector de
estructuras.
1
2

#include <stdio.h>
#define MAX_ALUMNOS 500

3
4
5
6
7
8

struct alumno {
char nombre[50];
char grupo;
float nota;
};

9
10

int cargar_datos( struct alumno v[] );

11
12
13
14
15
16
17

int main() {
struct alumno v[MAX_ALUMNOS];
int num_alumnos;
num_alumnos = cargar_datos(v);
. . .
}

18
19
20
21
22

int cargar_datos( struct alumno v[] ) {


FILE * f;
char nom_fich[512];
int i;

23
24
25
26
27
28
29

142

// Abrir fichero
printf("Introduce nombre de fichero de alumnos: ");
scanf(" %s", nom_fich);
f = fopen(nom_fich, "r");
if( f == NULL )
return 0; // No se han podido leer los datos

7.3 Operaciones con cheros

30

// Leer datos del fichero


i = 0;
while( i<MAX_ALUMNOS && fscanf(f, " %s", v[i].nombre)!= EOF ) {
// Si no hemos alcanzado EOF tras leer el nombre,
supondremos
// que el fichero contiene tambi
en un grupo y una nota
fscanf(f, " %c", &v[i].grupo);
fscanf(f, " %f", &v[i].nota);
i++;
}

31
32
33
34

35
36
37
38
39
40

// Cerrar fichero
fclose(f);

41
42
43

return i;

44
45

// Devolvemos el n
umero de alumnos le
dos

7.3.3

Lectura y escritura de cheros binarios

Llamamos chero binario a cualquier chero que no contenga u nica y exclusivamente


texto plano. Por tanto, un chero binario puede contener datos con distintos tipos de
codicacion.
La lectura/escritura de cheros binarios se realiza mediante las instrucciones fread y
fwrite respectivamente.

Operaciones de escritura
La funcion fwrite se usa del siguiente modo:
fwrite(direccion_memoria, tama
no_variable, numero_variables, descriptor);

donde:
direccion_memoria es la direccion de la variable que queremos escribir.
tama
no_variable es el numero de bytes que ocupa la variable a escribir. Se puede conocer el tamano de una variable con la instruccion sizeof(variable) (o
tambien sizeof(tipo)).
numero_variables es la cantidad de variables que vamos a escribir. En caso de
que direccion_memoria sea la direccion de un vector, este valor podra ser mayor
de uno.

143

Captulo 7. Gestion de cheros

descriptor es la variable de tipo FILE * que identica al chero.

Por ejemplo, si quisieramos guardar en un chero binario el valor de cierta variable entera
n, haramos:
1
2
3
4

FILE * f;
int n;
. . .
fwrite(&n, sizeof(int), 1, f);

Para guardar una variable de un tipo estructurado (por ejemplo una variable alum de tipo
struct alumno) ejecutaramos:
1
2
3
4

FILE * f;
struct alumno alum;
. . .
fwrite(&alum, sizeof(struct alumno), 1, f);

Si en lugar de tener una u nica variable tenemos un vector, podemos guardar el vector
entero con una u nica instruccion del siguiente modo:
1
2
3
4

FILE * f;
struct alumno v[N];
. . .
fwrite(v, sizeof(struct alumno), N, f);

Debe recordarse que el nombre de un vector es, en realidad, un puntero al primer elemento
del vector. Por este motivo la variable v no debe llevar, en este caso, el & delante.
Basicamente, la instruccion fwrite del ejemplo anterior podra interpretarse como: .a
partir de la direccion de memoria v (esto es, &v[0]), selecciona X bytes y guardalos en el
chero f, donde X=N*sizeof(struct alumno).
Operaciones de lectura
La funcion fread se usa del siguiente modo:
fread(puntero_variable, tama
no_variable, numero_variables, descriptor);

donde puntero_variable, tama


no_variable, numero_variables y descriptor
tienen exactamente el mismo signicado que tenan en la instruccion fwrite.
144

7.3 Operaciones con cheros

Basicamente, la diferencia entre fwrite y fread es el sentido en que se transere la


informacion (de memoria a chero en el caso de fwrite y de chero a memoria en el
caso de fread). La direccion de memoria a/desde la que se transere la informacion viene
dada por puntero_variable, mientras que la cantidad de bytes a transferir viene dada
no_variable x numero_variables.
por tama
La funcion fread devuelve el numero de elementos ledos. Si el valor devuelto por fread
es cero, querra decir que no ha sido posible llevar a cabo la lectura (normalmente porque
se ha alcanzado el nal de chero). Por ejemplo, si disponemos de un chero de nombre
alumnos.bin que contiene 500 variables de tipo struct alumno, podramos leerlo con
el siguiente fragmento de codigo:
1
2
3
4
5

FILE * f;
struct alumno v[N]; // Suponiendo N = 500
f=fopen("alumnos.bin", "rb");
fread(v, sizeof(struct alumno), N, f);
fclose(f);

Podra ocurrir que no se sepa de antemano el numero de alumnos almacenados en el


chero. En este caso habra que ir leyendo los alumnos del chero uno a uno, hasta que
se alcanzase el nal del chero, tal y como se indica a continuacion.
1
2
3

FILE * f;
struct alumno v[MAX_ALUMNOS];
int i = 0;

4
5
6

7
8
9

f=fopen("alumnos.bin", "rb");
while( i < MAX_ALUMNOS && fread(&v[i], sizeof(struct alumno), 1,
f) > 0 )
i++;
fclose(f);
printf("Hemos le
do %d alumnos\n", i);

Como puede observarse en el ejemplo anterior, en cada iteracion del bucle while se leen
los datos de un alumno. En el momento en que la instruccion fread devuelva cero se
saldra del bucle, ya que esto indica que se han ledo todos los datos del chero. En caso
de que el chero contenga mas de MAX_ALUMNOS elementos, el bucle nalizara cuando se
alcance este numero de alumnos, evitando de este modo sobrepasar el rango del vector v.

145

Captulo 7. Gestion de cheros

7.4

Acceso aleatorio

Hasta ahora se han utilizado los cheros en modo secuencial, esto es, las distintas operaciones de escritura/lectura iban guardando/consumiendo los datos en el chero de forma
secuencial. Para ello el descriptor del chero recuerda la posicion donde se realizo la
u ltima operacion de lectura/escritura, de modo que la proxima operacion se realiza siempre en la siguiente posicion.
En algunas ocasiones puede resultar interesante romper esta secuencialidad y leer o escribir en una posicion determinada del chero. En este caso hablamos de acceso aleatorio.
La funcion fseek permite avanzar o retroceder en el chero para realizar la operacion
de lectura o escritura en la posicion deseada (en el caso de escritura sobreescribiendo lo
que haba previamente en dicha posicion, no insertando). El uso de la funcion fseek es
el siguiente:
fseek(descriptor, desplazamiento, modo);

donde descriptor es el descriptor del chero, desplazamiento es un entero que indica la cantidad de bytes a desplazarse (puede ser positivo o negativo) y modo es otro entero
que indica a partir de donde se realiza el desplazamiento. Para este u ltimo parametro
pueden utilizarse las siguientes constantes denidas en stdio.h:
SEEK_SET: Desde el principio del chero.
SEEK_CUR: Desde la posicion actual.
SEEK_END: Desde el nal del chero.

El acceso aleatorio no suele utilizarse en cheros de texto, ya que habitualmente no se


sabra cuantos bytes ocupan nuestros datos y, por tanto, no sabremos en que posicion del
chero se encuentran. En un chero binario, un numero entero siempre ocupara 4 bytes
(sizeof(int) bytes), independientemente de lo grande o pequeno que sea el numero.
Sin embargo, en un chero de texto, la cantidad de bytes necesaria para representar un
numero puede variar (el numero 123 ocupa 3 bytes, 123.00 ocupa 6 bytes y 3.141592 8
bytes). En consecuencia, la instruccion fseek no suele emplearse en cheros de texto.

7.5

Ejercicios resueltos

1. Dado un chero de texto de nombre polares.txtcon una cantidad de lneas desconocida, en el que cada lnea contiene las coordenadas polares (modulo y a ngulo)
de un punto, escribir un programa que lea la informacion contenida en dicho chero y almacene en un segundo chero de nombre cartesianas.txttodos los puntos
expresados en coordenadas cartesianas.
146

7.5 Ejercicios resueltos

SOLUCION:
1
2

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

3
4
5
6

int main() {
FILE * f_in, *f_out;
float mod, ang, x, y;

// Abrir fichero de entrada en modo lectura


f_in = fopen("polares.txt", "r");
if( f_in == NULL ) return 0;

8
9
10
11

// Abrir fichero de salida en modo escritura


f_out = fopen("cartesianas.txt", "w");
if( f_out == NULL ) return 0;

12
13
14
15

// Procesar datos del fichero


while( fscanf(f_in, " %f %f", &mod, &ang) != EOF) {
x = mod * cos(ang);
y = mod * sin(ang);
fprintf( f_out, " %f %f\n", x, y );
}

16
17
18
19
20
21
22

// Cerrar ficheros
fclose( f_in );
fclose( f_out );

23
24
25
26

return 0;

27
28

2. Un chero binario contiene los datos censales de cierto numero de personas (como maximo 5.000). Concretamente almacena, para cada persona, el nombre (50
caracteres), el municipio de residencia (50 caracteres) y el da, mes y ano de nacimiento (enteros). Implementar una funcion que lea la informacion de dicho chero,
la almacene en un vector de estructuras y devuelva el numero de personas ledas.
Supondremos que esta denido el siguiente tipo estructurado:
struct persona {
char nombre[50];
char municipio[50];
int dia, mes, anyo;
};

Escribir tambien la funcion main en la que se declare el vector, se solicite el nombre


del chero y se invoque a la funcion implementada.

SOLUCION:
147

Captulo 7. Gestion de cheros

int cargar_datos_censales( char nom_fich[], struct persona v


[] ) {
FILE * f;
int i = 0;

2
3
4

f = fopen(nom_fich, "rb");
if( f == NULL )
return 0;
while( fread( &v[i], sizeof(struct persona), 1, f ) > 0 )
i++;
fclose(f);
return i;

5
6
7
8
9
10
11

12
13

int main() {
struct persona v[5000];
char nom_fich[50];
int num_personas;

14
15
16
17
18

// Solicitar nombre del fichero


printf("Introduce nombre del fichero: ");
scanf(" %s", nom_fich);

19
20
21
22

// Llamada a la funci
on
num_personas = cargar_datos_censales(nom_fich, v);

23
24
25

// Resto del programa


. . .

26
27
28

return 0;

29

30

7.6

Ejercicios propuestos

1. Escribir un programa que pida al usuario el nombre de un chero de texto y calcule


el numero de caracteres y de lneas que contiene.
2. Dada la siguiente denicion de tipo:
struct alumno {
char nombre[50];
int grupo;
float nota;
};

148

7.6 Ejercicios propuestos

Implementar una funcion que reciba un vector de tipo struct alumno y almacene
en el chero aprobados.txt el nombre y la nota de los alumnos aprobados, y en
suspensos.txt el nombre y la nota de los suspendidos.
3. Se dispone de un chero binario de nombre alumnos que contiene, para cada
alumno, su nombre (cadena de 50 caracteres), su grupo y su nota. Implementar un
programa que, a partir de dicho chero, almacene en otro chero binario de nombre
aprobados el nombre, grupo y nota de los aprobados.

149

Bibliografa
[1] B. S. Gottfried, Programacion en C. Mc. Graw-Hill, 1997.
[2] H. Schildt, C. Manual de referencia. Mc. Graw-Hill, 2000.
[3] M. W. y Stephen Prata, Programacion en C. Anaya Multimedia, 1990.
[4] Programacion en c - wikiversidad. http://es.wikiversity.org/wiki/
Programaci%C3%B3n_en_C.
[5] Programacion en c - wikilibros.
Programaci%C3%B3n_en_C.

http://es.wikibooks.org/wiki/

[6] C++ con clase - programacion c++. http://c.conclase.net.

151

Das könnte Ihnen auch gefallen