Escolar Documentos
Profissional Documentos
Cultura Documentos
Fundamentos de Programacin I
Apuntes de la Asignatura
ndice general
I Teora
1. Introduccin a la Programacin
Arquitectura Bsica de los computadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Introduccin a los Sistemas Operativos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Introduccin a los Lenguajes de Programacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1-1
1-3 1-6 1-9
2. Programacin Estructurada
Resolucin de Problemas y Algoritmos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Introduccin al Lenguaje C
2-1
2-1
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-10
3. Codicacin de la Informacin
3.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1. 3.2. Los Sistemas Posicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3-1
3-1 3-1 3-2 3-3 3-3 3-5 3-6 3-6 3-7 3-7
Codicacin de Enteros sin Signo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.1. 3.2.2. 3.2.3. La aritmtica binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Otras Bases de Numeracin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . El Desbordamiento en los Enteros sin Signo . . . . . . . . . . . . . . . . . . . . . . . .
3.3.
Codicacin de los Enteros con Signo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1. 3.3.2. Codicacin en Complemento a Dos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4-1
4-1 4-2 4-2 4-2 4-3 4-3 4-4 4-4 4-5 4-5
Tipos Enteros con Signo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipos con Decimales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipo Lgico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipo Carcter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ii
20132014
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Declaracin de Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utilizacin del Valor de una Variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . Asignacin de Valores a Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tiempo de Vida de las Variables Visibilidad de las variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-10
5. Expresiones
5.1. 5.2. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Operadores Aritmticos 5.2.1. 5.3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5-1
5-1 5-2 5-3 5-3 5-4 5-4 5-5 5-6 5-7 5-8 5-9
++
--
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-10
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-11
sizeof
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-12
6. Estructuras de Control
6.1. 6.2. 6.3. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Estructuras Secuenciales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Estructuras Selectivas 6.3.1. 6.3.2. 6.3.3. 6.3.4. 6.4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6-1
6-1 6-2 6-2 6-3 6-4 6-5 6-6 6-9 6-9
if else
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
switch .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
while for
do while
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-11
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-12
6.5.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-15
Lectura del teclado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-15 Solucin de una ecuacin de primer grado . . . . . . . . . . . . . . . . . . . . . . . . . 6-15
7. Funciones
7-1
20132014
iii
7.1.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7-1 7-2 7-2 7-3 7-3 7-3 7-4 7-4 7-5 7-5 7-7 7-8 7-9 7-9
main
7.2.
Denicin de una Funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.1. 7.2.2. 7.2.3. 7.2.4. El identicador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Los parmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . El Cuerpo de la Funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . El Tipo de la Funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.3. 7.4.
Correspondencia de parmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.5. 7.6.
Recursividad
7.7.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
include define
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-10
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-12
Directivas condicionales
7.8.
Resultado nal
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-13
8. Punteros
8.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.1.1. 8.1.2. 8.2. Declaracin de Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . El valor
8-1
8-1 8-2 8-3 8-3 8-4 8-5 8-5 8-6 8-6 8-7 8-7
NULL
en los Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.3.
Aritmtica de punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.3.1. 8.3.2. 8.3.3. Operaciones entre punteros y enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . Resta de dos Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Comparaciones de Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.4. 8.5.
Punteros a Punteros
9. Tablas
9.1. 9.2. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Declaracin de las Tablas 9.2.1. 9.3. 9.4. 9.5. 9.6. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9-1
9-1 9-2 9-2 9-3 9-4 9-4 9-5 9-7 9-8
Acceso a los Elementos de una Tabla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Recorrer los Elementos de una Tabla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utilizacin de Constantes Simblicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punteros y Tablas 9.6.1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Diferencias y Usos
9.7.
iv
20132014
9.8.
9.9.
9.10.1. Tablas Multidimensionales como Parmetros 9.10.2. Tablas de Punteros como Parmetros 9.10.3. Argumentos en la Lnea de comandos
. . . . . . . . . . . . . . . . . . . . . . . . . . . 9-13 . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-13
10.Tipos Agregados
10.1. Estructuras
10-1
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-1
10.1.1. Declaracin de Estructuras y Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-1 10.1.2. Inicializacin de Estructuras 10.1.3. Punteros a Estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-3
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-4
10.1.4. Acceso a los campos de una estructura . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-4 10.1.5. Estructuras como Campos de Estructuras . . . . . . . . . . . . . . . . . . . . . . . . . 10-5 10.1.6. El Operador de Asignacin en las Estructuras . . . . . . . . . . . . . . . . . . . . . . . 10-6 10.1.7. Tablas de Estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-7
10.1.8. Estructuras como Argumentos de Funciones . . . . . . . . . . . . . . . . . . . . . . . . 10-8 10.1.9. Estructuras como Resultado de una Funcin 10.2. Uniones . . . . . . . . . . . . . . . . . . . . . . . 10-8
10.3. Campos de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-9 10.4. El tipo Enumerado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-11 10.4.1. Declaracin de Enumerados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-11 10.4.2. Declaracin de Variables de Tipo Enumerado . . . . . . . . . . . . . . . . . . . . . . . 10-12 10.5. La Denicin de Nuevos Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-12
11.Funciones de Biblioteca
11-1
11.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-1 11.2. Reserva dinmica de memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-2 11.2.1. Funcin de reserva 11.2.2. Funcin de reserva
11.3.2. Funciones de entrada y salida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-6 11.3.3. Salida con formato: funcin
fprintf
. . . . . . . . . . . . . . . . . . . . . . . . . . . 11-8
20132014
fscanf
. . . . . . . . . . . . . . . . . . . . . . . . . . . 11-9 . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-10
11.3.5. Funciones de lectura/escritura binaria 11.3.6. Funciones especiales 11.3.7. Flujos estndar:
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-10 y
stdin, stdout
stderr
. . . . . . . . . . . . . . . . . . . . . . . . 11-11 . . . . . . . . . . . . . . . . . . . 11-11
11.3.8. Errores frecuentes en el uso de las funciones de E/S 11.4. Funciones de Cadenas de Caracteres
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-15
11.4.1. Funciones de copia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-15 11.4.2. Funciones de encadenamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-15 11.4.3. Funciones de comparacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-15 11.4.4. Funciones de bsqueda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-15 11.4.5. Otras funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-16
II Anexos
1. El Entorno del Centro de Clculo
1.1. 1.2. 1.3.
11-17
1-1
1-1 1-1 1-2 1-2 1-3 1-4 1-4
1.4.
Desconexin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2-1
2-1 2-2 2-2 2-3 2-4 2-4 2-4 2-7 2-8 2-9
Formato general de las rdenes 2.3.1. 2.3.2. 2.3.3. 2.3.4. 2.3.5. 2.3.6. Ayuda en lnea
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Operaciones sobre el sistema de cheros . . . . . . . . . . . . . . . . . . . . . . . . . . Operaciones sobre directorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Comandos para visualizacin de cheros . . . . . . . . . . . . . . . . . . . . . . . . . . Comandos informativos Otros comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-10
2.4.
Funciones especiales del intrprete de comandos . . . . . . . . . . . . . . . . . . . . . . . . . . 2-11 2.4.1. 2.4.2. 2.4.3. Caracteres ordinarios y caracteres de control . . . . . . . . . . . . . . . . . . . . . . . 2-11
2.5.
Procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-13 2.5.1. 2.5.2. Intercomunicacin entre procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-13 Control de trabajos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-13
vi
20132014
2.5.3. 2.5.4.
rdenes de informacin sobre procesos y trabajos . . . . . . . . . . . . . . . . . . . . . 2-14 rdenes de cambio de estado de un trabajo . . . . . . . . . . . . . . . . . . . . . . . . 2-15
3. El compilador gcc
3.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1. 3.2. Etapas en la generacin de un ejecutable . . . . . . . . . . . . . . . . . . . . . . . . . .
3-1
3-1 3-2 3-4 3-4 3-5
gcc
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Uso de la opciones
-W
-Wall
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Uso de la opcin -c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4-1
4-1 4-1 4-2 4-3 4-5 4-5 4-5
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.5.
Macros en makeles
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5-1
5-1 5-2 5-3 5-4 5-4 5-4 5-4 5-4 5-4 5-5 5-5 5-5 5-6 5-6 5-6 5-6 5-7 5-7 5-8 5-9
gdb
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
rdenes de ayuda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.1. 5.3.2. 5.3.3. 5.3.4. Orden Orden Orden Orden
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.4. 5.5.
Ejecucin de programas bajo gdb: orden Puntos de parada (breakpoints) 5.5.1. 5.5.2. 5.5.3. 5.5.4. 5.5.5. 5.5.6. 5.5.7.
run
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Establecimiento de puntos de parada . . . . . . . . . . . . . . . . . . . . . . . . . . . . Informacin sobre los puntos de parada Deshabilitar un punto de parada . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Habilitar un punto de parada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Borrado de un punto de parada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ejecucin automtica en la parada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Continuacin de la ejecucin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6-1
6-1
20132014
vii
El chero fuente
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6-2 6-3 6-4 6-5 6-6 6-6 6-7 6-7 6-7 6-8 6-8 6-9 6-9 6-9
Sangrado y separaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Identicadores y Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Programacin Modular y Estructurada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sentencias Compuestas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.7.1. 6.7.2. Sentencia Sentencia
switch if-else
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.8. 6.9.
Otros
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.10. La sentencia
exit
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
++, --
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-10
III Ejercicios
3. Codicacin de la Informacin 4. Tipos, Constantes y Variables 5. Expresiones 6. Estructuras de Control 7. Funciones 8. Punteros 9. Tipos Agregados
6-13
3-1 4-1 5-1 6-1 7-1 8-1 9-1
viii
20132014
Parte I
Teora
Tema 1
Introduccin a la Programacin
ndice
Arquitectura Bsica de los computadores . . . . . . . . . . . . . . . . . . . . . . . . . 1-3 Introduccin a los Sistemas Operativos . . . . . . . . . . . . . . . . . . . . . . . . . . 1-6 Introduccin a los Lenguajes de Programacin . . . . . . . . . . . . . . . . . . . . . . 1-9
1-1
1-2
20132014
Introduccin
G Informtica: ciencia que estudia todo lo referente al procesamiento de la informacin de forma automtica. G Ordenador: mquina destinada a procesar informacin. G Programa: secuencia de instrucciones que describe cmo ejecutar cierta tarea.
20132014
1-3
Memoria Principal
G G G G G Tambin llamada central Almacena datos e instrucciones Convierte al ordenador en una mquina de propsito general De acceso directo y rpido Operaciones bsicas: lectura y escritura
11
1-4
20132014
La memoria principal
G La memoria de un ordenador tiene organizacin matricial: cada fila una palabra de memoria Bit
Direccin
Palabra
Descodificador
13
Direccionamiento
G Procedimiento para seleccionar una palabra de memoria mediante su direccin:
d Direccin
Registro de Direcciones
Registro de Datos
14
20132014
1-5
Direccionamiento
G Registro de direcciones:
I tamao suficiente para direccionar todas las posiciones (direcciones o filas) de la memoria.
15
16
1-6
20132014
Introduccin
G Sistema Operativo: programa (o conjunto de programas) que:
I controla todos los recursos de la computadora I proporciona la base sobre la cual pueden escribirse todos los programas de aplicacin.
G Presenta la mquina al usuario como una interfaz o mquina virtual. G Gestiona los recursos hardware.
25
20132014
1-7
Sistema Operativo
G Facilita las tareas de programacin
I I I I proporciona lenguaje de control o comandos detecta errores facilita operaciones de E/S gestiona operaciones con ficheros
26
28
1-8
20132014
Gestin de la Memoria
G Un programa debe estar en memoria para ser ejecutado.
I El S.O (o parte de l) tambin debe estar. I Puede haber varios programas en memoria.
31
20132014
1-9
Lenguajes de Programacin
G G G G G G G Introduccin. Historia Clasificacin. Ensamblador Lenguajes de Alto Nivel Traductores Editores, enlazadores y Cargadores. Entornos
36
1-10
20132014
Introduccin
G Programa
I conjunto ordenado de instrucciones (susceptible de ser entendido por un ordenador)
G Lenguaje de programacin:
I conjunto de smbolos I normas para la correcta combinacin de estos
37
Evolucin Histrica
G G G G G G G Lenguaje Mquina Lenguaje Ensamblador 50s: Fortran, Algol, Lisp 60s: Cobol, Basic 70s: Pascal, C 80s: C++ 90s: Java
38
20132014
1-11
Clasificaciones
G Segn proximidad al lenguaje mquina:
I de bajo nivel: ensamblador, mquina I de alto nivel: Pascal, C
G Segn su propsito:
I de propsito general: Pascal, C I de propsito especfico: Cobol, RPG
G Segn su orientacin:
I al procedimiento, imperativos: C, Pascal I al problema, declarativos: Prolog I a objetos: C++, Java
39
Lenguaje Mquina
G Es el directamente inteligible por la mquina. G Sus instrucciones:
I son cadenas binarias. I dependen del hardware de la computadora I difieren de una a otra.
40
1-12
20132014
Lenguaje Mquina
G Ventajas:
I Rapidez. I No necesita traduccin.
G Desventajas:
I Dificultad. I Lentitud en la codificacin. I Slo vlidos en una mquina.
41
42
20132014
1-13
43
G Caractersticas
I I I I palabras reservadas reglas sintcticas uso de variables y constantes operadores aritmticos y lgisos I control del flujo del programa: bifurcaciones, bucles..
44
1-14
20132014
Ventajas e Inconvenientes
G Ventajas:
I Menor tiempo de formacin de programadores. I Basados en reglas sintcticas similares a los lenguajes humanos. I Mayor facilidad para modificar y poner a punto los programas. I Reduccin en el coste de los programas. I Portables.
G Inconvenientes:
I Necesita diferentes traducciones. I No se aprovechan los recursos internos de la mquina. I Aumento de la ocupacin de memoria. I El tiempo de ejecucin de los programas es mucho mayor.
45
Traductor
G Es un programa que traduce un programa en un lenguaje de programacin a su correspondiente en lenguaje mquina.
I Adems puede detectar errores y optimizar.
46
20132014
1-15
G Compilador: traduce los programas fuente escritos en lenguaje de alto nivel a lenguaje mquina.
I C, C++, pascal, FORTRAN, COBOL.
Programa fuente
Programa fuente
Intrprete
Compilador
Programa Objeto
47
Fich. objeto
48
1-16
20132014
Entorno de Programacin
Listado escrito Editor Programa fuente Compilador Programa objeto Enlazador Programa ejecutable Cargador Programa ejecutable en memoria
49
Tema 2
Programacin Estructurada
ndice
Resolucin de Problemas y Algoritmos . . . . . . . . . . . . . . . . . . . . . . . . . . 2-1 Introduccin al Lenguaje C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-10
2-1
2-2
20132014
Introduccin
G El objetivo fundamental es ensear a resolver problemas mediante un ordenador. G Un programador es primero una persona que resuelve problemas. G La resolucin de problemas deber ser rigurosa y sistemtica. G La metodologa necesaria para resolver problemas mediante programas se llama metodologa de programacin. G El eje central de esta metodologa es el algoritmo.
52
20132014
2-3
Algoritmo
G Un algoritmo es un mtodo para resolver un determinado problema. G El algoritmo es previo al programa. G Un programa es un algoritmo expresado en un lenguaje de programacin. G Caractersticas fundamentales que debe cumplir un algoritmo:
I Ser preciso: indicar el orden de realizacin paso a paso. I Ser determinista: siempre se comporte igual. I Ser finito: debe terminar en algn momento.
53
Diseo de un algoritmo
G Debe ser descendente (top-down):
I dividir el problema en subproblemas.
G Debe presentar refinamiento sucesivo, con descripcin detallada. G La especificacin debe ser independiente del lenguaje y puede ser:
I mediante diagramas de flujo I mediante pseudocdigo
54
2-4
20132014
Ejemplo de algoritmo
G Especificaciones:
I Calcular el valor medio de una serie de nmeros positivos que se leen del teclado. I Un valor cero o negativo indicar el final de la serie.
G Algoritmo:
1. Inicializar contador de nmeros C y suma S a cero 2. Leer un nmero N 3. Si el nmero N es positivo
a. Sumar el nmero N a la suma S b. Incrementar en 1 el contador de nmeros C c. Ir al paso 2
55
G G G G
Diseo del programa Codificacin del programa Prueba del programa Mantenimiento
56
20132014
2-5
Anlisis
G Descripcin clara y completa del problema, haciendo nfasis en los siguientes aspectos:
I entradas, salidas, ejecucin.
57
Diseo
G Es la elaboracin de pasos concretos para resolver el problema. G Objetivos:
I Minimizar el coste, tamao y tiempo de ejecucin. I Obtener la mxima facilidad de uso, fiabilidad, flexibilidad y sencillez de mantenimiento.
58
2-6
20132014
Otras fases
G Codificacin: Consiste en formular el problema en algn lenguaje de programacin. G Prueba:
I Puesta en funcionamiento. I Deteccin de errores de codificacin. I Puede consumir ms del 50% del tiempo total de desarrollo.
G Mantenimiento: Etapa destinada al desarrollo de mejoras, ampliaciones y refinamiento de las aplicaciones desarrolladas.
59
Programacin Modular
G El programa se dividir en mdulos independientes, cada uno de los cuales ejecutar una determinada tarea. G Existir el llamado mdulo principal que ser el encargado de transferir el control a los dems mdulos. G Al ser independientes se pueden codificar y probar simultneamente. G Ventajas:
I I I I claridad transportabilidad modificabilidad reduccin del coste de desarrollo
60
20132014
2-7
Programacin Estructurada
G Usa solamente tres estructuras de control: bloques consecutivos con un nica entrada y una nica salida. Secuencial Selectiva Repetitiva
61
Programacin Estructurada
G La programacin estructurada se debe usar conjuntamente con la programacin modular. G El empleo de tcnicas de programacin estructurada disminuye el tiempo de:
I verificacin I depuracin I mantenimiento
de las aplicaciones.
62
2-8
20132014
Ejemplo de la media
C=0
S=0
Leer N
NO
N>0
S
S=S+N
C=C+1
M=S/C
63
S=0
Leer N
NO
N>0
S
S=S+N
C=C+1
Leer N
M=S/C
64
20132014
2-9
G Algoritmo:
1. Leer el ao 2. Suponemos que el ao no es bisiesto 3. Si el nmero es mltiplo de 4
1. Si el nmero no es mltiplo de 100
1. 1. S es bisiesto Si el nmero es mltiplo de 400 1. S es bisiesto
2. Si s es mltiplo de 100:
65
4
S
100
NO
400
S
NO
bis = SI
bis = SI
Escribir bis
66
2-10
20132014
Introduccin al Lenguaje C
ndice
G G G G G Introduccin y evolucin Caractersticas del lenguaje C. Palabras reservadas, identificadores y delimitadores. Estructura de un Programa en C. Ejemplo de un programa en C.
68
20132014
2-11
Introduccin Histrica
G Lenguaje de programacin de alto nivel
I Desarrollado en los aos setenta por Dennis Ritchie en Bell Telephone Laboratories, Inc.
G Primer gran programa escrito en C: UNIX. G Tiene algunas caractersticas de bajo nivel:
I acceso directo a memoria I permite mejorar la eficiencia.
69
Evolucin del C
G Primeras implementaciones comerciales:
I diferan en parte de la definicin original. I Estandarizado por ANSI (comit ANSI X3J11):
Primera versin en 1989. Versin actual de 1999
70
2-12
20132014
Caractersticas de C (I)
G Lenguaje estructurado de propsito general:
I Caractersticas de lenguajes de alto nivel :
sentencias de control tipos de datos. palabras reservadas, etc..
71
Caractersticas de C (II)
G Modular:
I Divisin de un programa en mdulos I Se pueden compilar de forma independiente.
G Conciso:
I Repertorio de instrucciones pequeo. I Gran nmero de operadores I Numerosas funciones de librera
G Compilado, no interpretado.
72
20132014
2-13
Identificadores
G Definicin:
I Nombres que permiten hacer referencia a los objetos (constantes, variables, funciones) que se usan en un programa.
G Reglas de formacin:
I Formado por caracteres alfanumricos y _. I El primer carcter debe ser una letra
el subguin (_) est permitido pero no se debe usar.
73
Palabras Reservadas
G Significado especial para el compilador. G No se pueden usar como identificadores auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while
74
2-14
20132014
Delimitadores
G Son smbolos que permiten al compilador separar y reconocer las diferentes unidades sintcticas del lenguaje. G Los principales delimitadores son:
I I I I I ; es necesario para finalizar sentencias o declaraciones. , separa dos elementos consecutivos de una lista. () enmarca una lista de parmetros. [] enmarca la dimensin o el subndice de una tabla. {} enmarca un bloque de instrucciones o una lista de valores iniciales.
75
Estructura de un programa en C
<rdenes de preprocesado> <Declaracin de prototipos de funciones> int main () { <declaracin de variables;> /* comentarios */ <instrucciones ejecutables;> return 0; } <definicin de otras funciones>
76
20132014
2-15
Comentarios
/* ... */ // ... G Importantsimos en la realizacin de cualquier programa G Dos formas de escribirlos:
I Cuando ocupan ms de una lnea:
/*esto es un comentario de ms de una lnea */
77
rdenes de Preprocesado
G Instrucciones especiales dentro de un programa, tambin llamadas directivas.
I Se ejecutan antes del proceso de compilacin.
78
2-16
20132014
Declaracin de Importaciones
#include <nombreFichero.h> #include nombreFichero.h G Incluye el contenido del archivo indicado:
I Antes de la compilacin.
G Utilidad:
I Declarar funciones, constantes, que pueden usarse en el programa:
De archivos de biblioteca: #include <stdio.h> #include <string.h> #include <math.h> // General de entrada/salida // Manejo de cadenas de caracteres // Matemticas
79
Definicin de Constantes
#define identificador cadena G Sustituye el identificador por la cadena de caracteres:
I En todos los lugares que aparezca en el programa. I Antes de la compilacin. I Ejemplos:
#define MAXIMO #define PI #define ERROR 999 3.1416 Se ha producido un error
80
20132014
2-17
Sentencias
G Cada sentencia:
I debe terminar en ; I puede ocupar ms de una lnea.
81
Funciones
G Adems de las funciones de biblioteca, el programador puede definir sus propias funciones que realicen determinadas tareas. G El uso de funciones definidas por el programador permite dividir un programa grande en varios pequeos. G Un programa en C se puede modular mediante el uso inteligente de las funciones. G El uso de funciones evita la necesidad de repetir las mismas instrucciones de forma redundante. G Una funcin se invoca al nombrarla, seguida de una lista de argumentos entre parntesis.
82
2-18
20132014
Funcin main
G Indica el punto de comienzo de ejecucin del programa. G Hace las funciones de hilo conductor o director de orquesta G Todo programa C debe contener una y slo una funcin main.
83
Ejemplo de Programa en C
#include <stdio.h> #define FACTOR 2
int doble(int valor); int main() { int indice = 2; printf(El doble de %d es %d\n, indice, doble(indice)); return 0; } int doble(int valor) { return (valor * FACTOR); }
84
20132014
2-19
Compilacin y Prueba
G Editamos el fichero y lo guardamos con el nombre ejemplo.c
I La extensin debe ser siempre:
.c para los ficheros fuente .h para los ficheros de cabecera
G Compilamos el fichero:
$> gcc -c ejemplo.c I Se genera el fichero objeto ejemplo.o
G Ejecutamos el programa:
$> ./ejemplo El doble de 2 es 4
85
2-20
20132014
Tema 3
Codicacin de la Informacin
ndice
3.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-1
3.1.1. Los Sistemas Posicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-1 3.2.1. La aritmtica binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-3 3.2.2. Otras Bases de Numeracin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-3 3.2.3. El Desbordamiento en los Enteros sin Signo . . . . . . . . . . . . . . . . . . . . . . 3-5 3.3.1. Codicacin en Complemento a Dos . . . . . . . . . . . . . . . . . . . . . . . . . . 3-6 3.3.2. El Desbordamiento en los Enteros con Signo . . . . . . . . . . . . . . . . . . . . . 3-7
3.3. Codicacin de los Enteros con Signo . . . . . . . . . . . . . . . . . . . . . . . . 3-6 3.4. Codicacin de Nmeros con Decimales . . . . . . . . . . . . . . . . . . . . . . . 3-7
3.1. Introduccin
Utilizamos los ordenadores para procesar informacin de forma rpida y eciente. Para ello es necesario que le proporcionemos los datos que queremos que procese, y que ste nos entregue los resultados obtenidos. Pero los ordenadores almacenan la informacin de forma diferente a la nuestra: mientras que nosotros manejamos conceptos como nmeros enteros, nmeros reales, alfabeto, colores, . . . los ordenadores slo entienden secuencias de unos y ceros. Debemos encontrar pues un mecanismo que permita trasladar la informacin tal como nosostros la manejamos
informacin.
codicacin de la
dato.
tipos de datos.
En este captulo veremos cmo podemos codicar los nmeros naturales (enteros positivos) y los nmeros enteros.
3-1
3-2
20132014
Ejemplo: El sistema de numeracin decimal que utilizamos habitualmente es un ejemplo de sistema posicional: un
3 en la posicin de las unidades (la de ms a la derecha) vale tres, mientras que el mismo 3 en la posicin de las centenas (la tercera empezando por la derecha) vale trescientos.
La
base de numeracin, b, determina cuntos smbolos son necesarios para la representacin de los nmeros.
3 en la posicin de las unidades (la de ms a la derecha) vale tres, mientras que el mismo 3 en la posicin de las centenas (la tercera empezando por la derecha) vale trescientos.
Ejemplo: El sistema de numeracin decimal que utilizamos habitualmente es un ejemplo de sistema posicional: un
Ejemplo: En el caso de la base decimal son necesarios diez smbolos: los dgitos numricos del 0 al 9.
Conocida la base
en la posicin
vale
d bp ,
siendo
p=0
para el
dgito de ms a la derecha,
p=1
Ejemplo:
dgitos vale
bn .
Ejemplo: Si disponemos de cuatro cifras en sistema decimal, podremos escribir 10000 nmeros (104 ), desde el 0
hasta el 9999.
representacin de cantidades, y se utilizan el 0 y el 1. En lo sucesivo, para indicar que un nmero est en binario, terminaremos dicho nmero con el subndice 2.
Ejemplo: El nmero 10 est escrito en decimal, mientras que el nmero 102 est escrito en binario.
Como corresponde a un sistema posicional en base dos, el total de nmeros distintos que pueden representarse con
cifras es
2n ,
en el intervalo
[0, 2n 1].
Para obtener la codicacin binaria de un nmero decimal tenemos que dividir sucesivamente por la base de numeracin (base 2) y quedarnos con el ltimo cociente y los restos de cada una de las divisiones.
Ejemplo: Para encontrar la representacin en binario de 25, tendramos que realizar las siguientes operaciones:
25 05 1 2 12 0 2 6 0
2 3 1
2 1
Ejemplo:
101012 = 1 24 + 0 23 + 1 22 + 0 21 + 1 20 = 16 + 4 + 1 = 21
20132014
3-3
+
0 1
0 0 1
1 1 10
0 1
0 0 0
1 0 1
Ejemplo: El nmero 732 est escrito en decimal, mientras que el nmero 7328 est escrito en octal.
Por tratarse de un sistema posicional, pasar de decimal a octal consistir en dividir sucesivamente por 8 (que es la base) y quedarnos con el ltimo cociente y los sucesivos restos; y pasar de octal a decimal, en aplicar la expresin correspondiente a los sistemas posicionales.
8 2
por lo que la representacin octal de 1512 es 27508 . Para encontrar la representacin decimal de 27508 , haremos:
27508 = 2 83 + 7 82 + 5 81 = 1024 + 448 + 40 = 1512
Una vez que tenemos la representacin octal de un nmero, pasarlo a binario es inmediato, puesto que cada cifra octal se corresponde con exactamente tres cifras binarias. Es decir, podremos realizar el cambio de base sin realizar ninguna operacin, ni de multiplicacin ni de divisin. La siguiente tabla recoge las conversiones a realizar entre octal y binario:
Octal 0 1 2 3
Octal 4 5 6 7
3-4
20132014
Ejemplo: Si queremos obtener la representacin binaria de 1512, una vez que sabemos que su representacin octal
es 27508 , es muy simple: basta con sustituir cada smbolo octal por la secuencia correspondiente de tres smbolos binarios, segn la tabla anterior: 2 0 1 0 7 1 1 1 5 1 0 1 0 0 0 0
Si hubisemos realizado la conversin de decimal a binario directamente, habramos necesitado 10 divisiones, en lugar de las tres que necesitamos pasando previamente por octal.
Si lo que queremos es obtener la representacin decimal de un nmero binario, tambin podemos utilizar el octal como paso intermedio, reduciendo a un tercio el nmero de operaciones necesarias. Para ello habr que agrupar de tres en tres los bits, comenzando por la derecha, y aadiendo ceros a la izquierda si fuera necesario. A continuacin, sustituiremos cada grupo de tres bits por el dgito octal correspondiente. Para nalizar, slo tendremos que pasar a decimal el nmero octal obtenido.
Ejemplo: Si queremos obtener la representacin decimal de 101111010002 , realizaremos los siguientes pasos:
separamos en grupos de tres, comenzando por la derecha, y aadiendo un cero a la izquierda: 0 1 0 2 obtenemos el valor decimal correspondiente:
27508 = 2 83 + 7 82 + 5 81 = 1024 + 448 + 40 = 1512
1 1 1 7
1 0 1 5
0 0 0 0
Ejemplo: El nmero 9723 est escrito en decimal, mientras que el nmero 972316 est escrito en hexadecimal.
Al igual que sucede para el octal, la ventaja del sistema de numeracin hexadecimal estriba en que permite obtener de forma directa la representacin binaria de un nmero. La nica diferencia con el sistema octal visto anteriormente (aparte de los smbolos utilizados) es que cada smbolo hexadecimal se corresponde con cuatro smbolos binarios, segn la siguiente tabla: Hexadecimal 0 1 2 3 4 5 6 7 Decimal 0 1 2 3 4 5 6 7 Binario 0000 0001 0010 0011 0100 0101 0110 0111 Hexadecimal 8 9 A B C D E F Decimal 8 9 10 11 12 13 14 15 Binario 1000 1001 1010 1011 1100 1101 1110 1111
Podemos utilizar la base hexadecimal como paso intermedio entre las bases decimal y binaria: en el sentido de decimal a binario, dividiremos sucesivamente por 16, y obtendremos la representacin hexadecimal del nmero. A continuacin sustituiremos cada dgito hexadecimal por la secuencia de cuatro bits correspondiente.
20132014
3-5
en el sentido de binario a decimal, separaremos los dgitos binarios en grupos de cuatro, comenzando por la derecha, y aadiendo ceros a la izquierda si fuera necesario. Sustituiremos cada grupo de cuatro bits por el smbolo hexadecimal correspondiente, y por ltimo aplicaremos la expresin general del sistema posicional.
por lo que la representacin hexadecimal de 1512 es 5E816 . Si ahora sustituimos cada dgito hexadecimal por su secuencia de cuatros bits, tenemos: 5 0 1 0 1 E 1 1 1 0 8 1 0 0 0
Si lo que queremos es la representacin decimal de 11101010102 , primero agrupamos en grupos de cuatro, aadiendo ceros a la izquierda, y sustituimos cada grupo por el dgito hexadecimal correspondiente: 0 0 1 1 3 Por ltimo, la expresin del sistema posicional:
3AA16 = 3 162 + 10 16 + 10 = 768 + 160 + 10 = 938
1 0 1 0 A
1 0 1 0 A
Hay otra ventaja inherente a los sistemas de numeracin octal y hexadecimal: es posible conocer el valor de un bit determinado sin necesidad de hacer la conversin a binario. Simplemente debemos saber qu digito hexadecimal incorpora el bit que interesa, y descodicar dicho dgito.
Ejemplo: Si queremos saber el valor del sptimo bit (empezando por la derecha, que sera el menos signicativo)
de 7FE542E16 , slo tenemos que saber que, puesto que cada dgito hexadecimal equivale a cuatro bits, el bit que interesa estara integrado en el segundo (empezando por la derecha) dgito hexadecimal, 2. Slo tendramos que obtener la correspondencia en binario de dicho dgito, 0010, y puesto que el sptimo bit sera el tercero empezando por la derecha, el valor deseado es 0.
sucede si a ese nmero le aadimos 1?. Utilizando la aritmtica binaria, el resultado ser: 1 1 1 1 1 1 1 1 1 + 1 0 0 0 0 0 0 0 0 pero como el dato slo usa 8 bits para su almacenamiento, el bit ms signicativo (el de ms a la izquierda) se pierde y nos quedamos con los ltimos ocho dgitos del nmero binario, es decir, le conoce como desbordamiento.
000000002 .
Por lo tanto,
el resultado que hemos obtenido de sumarle 1 a 255 ha sido 0, lo cual, evidentemente, es un error. A esto se
3-6
20132014
Para evitarlo, es indispensable que conozcamos qu rango de valores puede tomar un determinado dato, y seleccionar siempre un tipo de dato que tenga capacidad suciente.
Ejemplo: Si queremos representar el mes del ao (del 1 al 12), evidentemente tendremos suciente con cuatro bits
(24 = 16 > 12). Sin embargo, si queremos representar el nmero de pginas de un libro necesitaremos al menos 11 bits (211 = 2048).
[2
n1
,2
n1
1].
signo del nmero, de forma que si este bit vale 1 el nmero es negativo, y en caso contrario es positivo (si consideramos que el cero es un valor positivo).
20132014
3-7
Ejemplo: Para obtener la representacin decimal de 110011102 , sabiendo que el octeto representa un nmero entero
con signo, haremos: 1. Observamos el bit ms signicativo: 110011102 . Como es un 1, el nmero es negativo. 2. Invertimos todos los bits: 0 0 1 1 0 0 0 1 3. Le sumamos una unidad: 0 0 1 1 0 0 0 1 1 + 0 0 1 1 0 0 1 0 4. Obtenemos el nmero decimal cuya representacin binaria es 001100102 :
001100102 = 25 + 24 + 21 = 32 + 16 + 2 = 50
5. Le colocamos el signo adecuado: como el nmero era negativo, el resultado ser 50.
011111112 .
128.
128.
exponente.
Ejemplo: En notacin cientca, 175,8376 lo pondramos como 1, 758376 102 , y 0,00000012 como 1, 2 107 . En
el primer caso, la mantisa sera 1, 758376 y el exponente 2, mientras que en el segundo caso la mantisa sera 1, 2 y el exponente 7.
Las diferencias bsicas con esta representacin son las siguientes: el signo se codica mediante un bit: 0 para nmeros positivos y 1 para negativos. la mantisa se codica en binario en valor absoluto, y de tal forma que el primer dgito distinto de cero (que necesariamente ser un 1) est inmediatemente a la izquierda de la coma decimal.
3-8
20132014
10101111, 1101012 , y despus desplazamos la coma las posiciones necesarias para que slo quede un dgito a la izquierda de la coma: 1, 01011111101012 27 . Se codicara por una parte el signo (un 0 por ser un nmero positivo), por otro lado la mantisa, 1, 01011111101012 , y por otro lado el exponente, 7.
Al contrario de lo que sucede para los nmeros enteros, la codicacin de los nmeros con decimales no es exacta en la mayora de las ocasiones, por lo que es importante no perder de vista esta circunstancia a la hora de utilizarlos.
Ejemplo: Si se suman los valores 0,333333, 0,333333 y 0,333334 con una calculadora que slo tenga cuatro cifras
signicativas, el resultado ser:
0, 3333 + 0, 3333 + 0, 3333 = 0, 9999
Tema 4
4-1
4-2
20132014
a ) los enteros sin signo: para representar nmeros enteros que no pueden ser negativos. b ) los enteros con signo: para representar nmeros enteros que pueden ser tanto positivos como
negativos.
unsigned short
unsigned
o
unsigned long
o
Todos los enteros sin signo se codican en binario. Los tipos anteriores slo se diferencian en la cantidad de memoria utilizada para su almacenamiento (y por lo tanto en el rango de nmeros que pueden representar). Su tamao exacto no est denido en el estndar, y puede variar de una mquina a otra (y en la misma mquina, de un compilador a otro). Lo que s se garantiza es que el tamao utilizado por cada uno de los tipos es al menos igual que el tipo que le precede (es decir, un
unsigned int
unsigned short,
menor tamao).
Ejemplo: En la mquina en la que se han redactado estos apuntes (un Pentium IV), y para la versin 3.4.1 del
compilador gcc, los tamaos utilizados por cada uno de los tipos anteriores es: 1 2 4 4 8 octeto para el tipo unsigned char. octetos para el tipo unsigned short int. octetos para el tipo unsigned int. octetos para el tipo unsigned long int. octetos para el tipo unsigned long long int.
unsigned
char,
en el que hay que sustituir dicha palabra reservada por la palabra reservada
signed:
20132014
4-3
short
o simplemente
Todos los enteros con signo se codican en complemento a dos. La diferencia entre los diferentes tipos radica, como en el caso de los enteros sin signo, en la cantidad de memoria utilizada para su almacenamiento (y por lo tanto en el rango de nmeros que pueden representar). Su tamao exacto no est denido en el estndar, y puede variar de una mquina a otra (y en la misma mquina, de un compilador a otro). Lo que s se garantiza es que el tamao utilizado por cada uno de los tipos es al menos igual que el tipo que le precede (es decir, un
int
short,
Ejemplo: En la mquina en la que se han redactado estos apuntes (un Pentium IV), y para la versin 3.4.1 del
compilador gcc, los tamaos utilizados por cada uno de los tipos anteriores es: 1 octeto para el tipo signed char. 2 octetos para el tipo short int. 4 octetos para el tipo int. 4 octetos para el tipo long int. 8 octetos para el tipo long long int.
float,
y un
long double
al menos
Ejemplo: En la mquina en la que se han redactado estos apuntes (un Pentium IV), y para la versin 3.4.1 del
compilador gcc, los tamaos utilizados por cada uno de los tipos anteriores es: 4 octetos para un float. 8 octetos para un double. 12 octetos para un long double.
_Bool,
habitualmente se utilizan los enteros (con y sin signo) para representar datos booleanos (que slo tienen dos
4-4
20132014
Se considera que el dato es VERDADERO si tiene un valor distinto de cero (sea positivo o negativo). Se considera que el dato es FALSO si tiene un valor igual a cero.
char.
Para codicar los diferentes caracteres, al ser estos un conjunto nito y bien denido, se utilizan tablas de correspondencia, donde a cada carcter se le asigna una determinada secuencia de bits que lo representa. Aunque no est normalizado, en la mayora de los casos se utiliza la codicacin segn el estndar ASCII, pudiendo reconocerse los siguientes subconjuntos: Caracteres alfabticos: Caracteres numricos:
+ - * / . , ; < > $ ?
La siguiente tabla recoge la codicacin ASCII. Ntese que en dicha tabla no aparecen algunos smbolos del espaol, como las vocales acentuadas o la ''. Eso signica que los cdigos de dichos caracteres no estn denidos en dicha codicacin, y pueden visualizarse bien en algunas mquinas, y mal en otras.
El tamao del tipo carcter suele ser de un octeto, aunque eso depender del conjunto de caracteres que componen el alfabeto de ejecucin.
4.2. Constantes
Decimos que son de un programa. En C existen cuatro tipos bsicos de constantes: constantes enteras
constantes
20132014
4-5
al
9,
y caracteres de la
a la
o de la
F).
int.
U L
(o (o
u), l),
LL
(o
ll),
UL
U puede combinarse con el sujo L, dando lugar a constantes de tipo unsigned long int (sujo LU), o con el sujo LL, dando lugar a constantes de tipo unsigned long long int (ULL o LLU).
0x12efUL (constante hexadecimal del tipo unsigned long int), 1234l (constante decimal de tipo long int), 0177lU (constante octal de tipo unsigned long int) 1235LLu (constante decimal de tipo unsigned long long int).
Los valores mximos de las constantes vienen determinados por el valor mximo correspondiente al tipo de dato al que representan.
(o
E).
32., .23, 32e7 23.5E-12. No lo seran, en cambio 1 (puesto que no tiene punto decimal ni exponente), 1,000.0 (puesto que incorpora una coma, que es un carcter ilegal) ni 2E+10.2 (puesto que el exponente no es un entero).
Las constantes en coma otante son, por defecto, de tipo aadir el sujo
(o
F)
que la convierte en
float,
o el sujo
4-6
20132014
\ \\ \" \? \t \n \r
representa el carcter comilla simple. representa el carcter barra invertida. representa el carcter comilla doble (aunque tambin valdra representa el carcter cierre de interrogacin (tambin vale representa el carcter tabulador horizontal. representa el carcter nueva lnea. representa el carcter retorno de carro.
").
?).
ooo.
\0, \50
No obstante, la utilizacin de valores octales o hexadecimales para representar caracteres presupone el conocimiento del cdigo utilizado, y limita la utilizacin del programa realizado a mquinas que utilicen dicho cdigo. Por eso de carcter.
20132014
4-7
La representacin interna de una cadena tiene un carcter nulo (\0) al nal, de modo que el almacenamiento fsico es uno ms del nmero de caracteres escritos en la cadena. Es importante destacar que no son equivalentes
"D".
carcter d mayscula, mientras que en el segundo caso estamos representando una cadena de caracteres de un solo carcter (el carcter d mayscula). Un carcter tiene un valor entero equivalente, mientras que una cadena de un solo carcter no tiene un valor entero equivalente y de hecho consta de dos caracteres, que son el carcter especicado y el carcter nulo (\0).
4.3. Variables
Las variables se utilizan para almacenar valores que pueden cambiar en el transcurso de la ejecucin del programa. Sin embargo solamente pueden tomar valores de un tipo: el tipo al que pertenecen.
Ejemplo: Si una variable es de tipo entero sin signo, podr contener valores enteros positivos, pero no negativos; si
es de tipo entero con signo, podr contener valores enteros (positivos o negativos), pero no nmeros con decimales. Si es de tipo con decimales, podr contener tambin valores con decimales.
formado por cualquier secuencia de caracteres alfabticos (se excluyen las vocales acentuadas y la ) tanto en maysculas como en minsculas, caracteres numricos y el carcter especial por un carcter alfabtico.
num_veces o paso2. No seran identicadores vlidos 2oPaso, porque empieza por un nmero, _recordar, porque empieza por el carcter de subrayado, ni ao, porque contiene el carcter .
Es muy conveniente que el identicador utilizado para designar una variable tenga relacin con aquello que representa (velocidad, resultado, suma, volumen, . . . ), para que al encontrarla dentro del cdigo podamos tener una idea de qu representa dicha variable.
reriendo. Sin embargo, si la llamamos diaSemana, de un simple vistazo podemos entender para qu la estamos usando.
Existen tres operaciones bsicas que pueden realizarse con una variable: declararla. utilizar el valor almacenado. asignarle un nuevo valor.
2 * temperatura,
de la variable
temperatura
no podramos saber ni cmo est codicada la informacin que almacena ni cunto ocupa
en memoria dicha variable. Y no podremos saberlo porque tanto una cosa como otra dependen del tipo de dato, y con la informacin de la sentencia no podemos determinar a qu tipo de dato pertenece la variable
temperatura.
4-8
20132014
Por eso en C, para poder utilizar una variable es necesario haberla declarado previamente. Declarar una variable es sencillamente dar cuenta de su existencia explcitamente en algn lugar del programa dedicado a tal efecto, especicando el tipo de dato al que pertenece. Una declaracin consta de un tipo de dato seguido de uno o ms nombres de variables separados por comas, y nalizando con un punto y coma.
A pesar de estar permitida la declaracin de varias variables en una nica sentencia (como en el ejemplo anterior), es aconsejable utilizar una sentencia distinta para declarar cada variable. El resultado es idntico, y mucho ms legible (y por tanto menos propenso a errores).
Ejemplo: Para declarar las mismas dos variables del ejemplo anterior, sera preferible escribir:
unsigned int mes; unsigned int anio;
Para declarar una variable que contendr el cdigo del carcter ledo desde el teclado:
char caracter_de_teclado;
Para declarar una variable que almacene el nmero de sumandos de una suma:
float temperatura;
Es importante destacar que la declaracin de una variable tiene un doble efecto: 1. Por una parte, permite que en el resto del programa, cuando nos reramos a ella por su identicador, podamos saber qu tipo de dato almacena dicha variable. 2. Por otro lado, permite reservar una zona de memoria, exclusiva y del tamao adecuado, para dicha variable.
Ejemplo: Si queremos obtener el doble de la temperatura de una sala, que est almacenada en la variable
temperatura, no tendremos ms que poner: 2 * temperatura.
20132014
4-9
Hay que tener en cuenta que si una variable se declara pero no se le asigna ningn valor tendr en general un valor indeterminado, y por lo tanto los resultados obtenidos al utilizar su valor sern impredecibles:
Ejemplo: La siguiente porcin de cdigo no dar ningn error al generar el ejecutable, pero los resultados obtenidos
sern indeterminados (podemos tener diferentes resultados si ejecutamos el cdigo varias veces):
Por ello es muy importante que toda variable que vaya a ser usada en un programa tenga un valor previamente asignado. Como la nica condicin que exige el lenguaje C es que toda variable sea declarada antes de ser utilizada, la mejor forma de conseguir que toda variable tenga siempre un valor asignado es realizar esta asignacin en la propia declaracin de la variable. Para realizar la asignacin de un valor a una variable en su declaracin, tendremos que poner el tipo de dato, el nombre de la variable, el operador de asignacin, dicha variable.
=,
Ejemplo: En este ejemplo puede observarse como, desde el momento en que una variable ha sido declarada puede
tiempo de vida
de una variable determina cundo se crea y se destruye. Con caracter general, una
variable en C slo existe durante la ejecucin del bloque en el que se ha denido. Esto signica que la variable se crea al declararla dentro de un bloque, y se destruye al salir del bloque, lo que trae consigo una serie de consecuencias: la primera, y ms importante, es que el valor de una variable en sucesivas ejecuciones del bloque es completamente independiente. la segunda, que el valor de inicializacin especicado en la declaracin de la variable se aplica en cada una de las ejecuciones del bloque. Si no existe inicializacin en la declaracin de la variable, el valor inicial es indeterminado; esto hace que sea muy aconsejable dar en su declaracin. la tercera es que las modicaciones que hagamos sobre la variable dentro del bloque en la que est denidas quedan sin efectos una vez que nalice este. A estas variables se les conoce como
1 Recuerde
20132014
Existe un procedimiento para evitar que las variables se creen y destruyan cada vez que se entra y se sale de un bloque: anteponer a la declaracin de la variable la palabra reservada de esta forma se les denomina
variables estticas.
static.
Esta especicacin tiene un triple efecto sobre la variable: hace que las variables se creen al inicio de la ejecucin del programa, y no al declararla dentro de un bloque, como sucede con las variables locales. hace que se inicialicen al comienzo de la ejecucin del programa: al valor indicado en la declaracin de la variable, o a cero si no se indica nada. Slo pueden utilizarse expresiones constantes para la inicializacin de variables estticas. hace que la variable no se destruya hasta que no nalice el programa. Como consecuencia de su comportamiento, estas variables conservan su valor de una ejecucin a la siguiente, lo que constituye su principal utilidad.
Ejemplo: Supongamos que queremos contar el nmero de veces que se llama a una determinada funcin. Podremos
int funcion(...) { static int numLlamadas = 0; ... numLlamadas = numLlamadas + 1; ... return numLlamadas;
Cada vez que se ejecute la funcin se sumar una unidad al contador de llamadas (numLlamadas). Sin embargo, si no utilizramos la especicacin static, la variable numLlamadas se creara de nuevo cada vez que se ejecutara la funcin, por lo que el valor devuelto sera siempre 1.
Variables Globales
Una excepcin a lo enunciado anteriormente lo constituyen las denominadas
variable globales.
Dichas
variables se declaran fuera de cualquier bloque y su comportamiento, desde el punto de vista del tiempo de vida, es similar al de las variables estticas: se crean al inicio de la ejecucin del programa, se inicializan una sola vez, y se destruyen a la nalizacin del programa. En esta asignatura, las variables globales no estn permitidas.
visibilidad o alcance de una variable indica dnde es posible utilizar una variable.
Evidentemente, el primer requisito para utilizar una variable es que exista. Aplicando este concepto a las variables locales, se deduce inmediatamente que las variables locales slo sern visibles (y por lo tanto utilizables) dentro del mbito del bloque en el que estn declaradas. Esto supone que dos variables automticas declaradas en bloques distintos son totalmente independientes, aunque tengan el mismo identicador. Pero la visibilidad tambin se ve afectada por los bloques anidados. Imaginemos el siguiente fragmento de cdigo:
20132014
{ // Comienzo de un bloque int num = 100; int i = 0; ... num = num + 10; ... { // Comenzamos un nuevo bloque dentro del anterior int num = 0; ... num = num + 4; } num = num * 2; ...
4-11
Tenemos denida dos variables con el mismo nombre (num) en dos bloques diferentes, siendo uno de ellos interior al otro. En la primera utilizacin de aplicaremos el operador. En la segunda utilizacin de la variable variable nos estamos reriendo: slo est denida la del bloque exterior, y por tanto ser a esa a la que
num (num = num + 4;) ya estamos dentro del bloque ms interno, num. En este caso, siempre nos referimos a la variable num (num = num * 2;), num
denida. la segunda variable
denida en el bloque ms interior, haciendo invisible la variable denida en el bloque ms exterior. En la tercera utilizacin de la variable estamos reriendo a la primera variable
num
ya no existe,
puesto que estamos fuera del bloque en el que se deni. Por tanto aqu tampoco existe ambigedad: nos
extern.
Desde el punto de vista de la deteccin y correccin de errores, es muy conveniente que la visibilidad de las variables sea lo ms reducida posible: podemos controlar el valor de dicha variable centrndonos en el contexto en el que es visible. Por esta razn es muy desaconsejable la utilizacin de variables globales en cualquier programa. Si adems tenemos en cuenta que
en ningn caso
la utilizacin de variables globales en un programa, en el contexto de esta asignatura las variables globales
ESTN PROHIBIDAS.
4-12
20132014
Tema 5
Expresiones
ndice
5.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-1 5.2. Operadores Aritmticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-2
5.3. Operadores Relacionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-3 5.4. 5.5. 5.6. 5.7. 5.8. 5.9. Operadores Lgicos . . . . . . . . . . . . . . . . . . . Operadores de Manejo de Bits . . . . . . . . . . . . Asignacin . . . . . . . . . . . . . . . . . . . . . . . . Asignacin Compacta . . . . . . . . . . . . . . . . . . Conversin Forzada . . . . . . . . . . . . . . . . . . . Operadores de Autoincremento y Autodecremento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.10. Operador Condicional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-11 5.11. Operador sizeof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-11 5.12. Reglas de Prioridad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-12
5.1. Introduccin
En cualquier programa es necesario procesar los datos de entrada para generar los datos de salida. El elemento bsico del que dispone C para procesar los datos son las expresiones, cuyo signicado es muy similar al utilizado en otras materias como el lgebra. Las expresiones son combinaciones de operandos, operadores y parntesis, que al evaluarse dan como resultado un valor. Los operandos determinan los valores que se utilizan para realizar una operacin determinada. Pueden ser: Constantes Variables Funciones (del estilo de las utilizadas en lgebra; se estudiarn ms adelante). Otra expresin
5-1
5-2
20132014
Los operadores determinan las operaciones a realizar. Pueden ser: Aritmticos Relacionales Lgicos De manejo de bits De asignacin De asignacin compacta De autoincremento y autodecremento Condicional. Los parntesis se utilizan para denir en qu orden deben evaluarse las subexpresiones que aparecen en una expresin.
donde:
9.23, 7.3 y 2 seran constantes angulo sera una variable *, + y / seran operadores cos() sera una funcin
+: -:
no tiene efecto aparente. convierte en positivos los valores negativos, y en negativos los positivos.
+: -: *: /:
operador suma operador resta operador multiplicacin operador divisin: devuelve el cociente de la divisin del operando de la izquierda por el de la
derecha. Si ambos operandos son enteros, el operador es la divisin entera (despreciando el resto); en cualquier otro caso, la divisin es con decimales.
20132014
5-3
Los operadores aritmticos se evalan de izquierda a derecha. Cuando se realiza una operacin aritmtica, los tipos de los operandos no tienen por qu ser los mismos (exceptuando el operador %, que necesita que ambos sean de tipo entero). El tipo resultante de evaluar una expresin aritmtica es el tipo del operador de mayor rango en la expresin; es lo que se denomina promocin automtica.
Ejemplo: Si operamos un
long double con un float, este ltimo se convierte internamente en long double antes de realizar la operacin, y el resultado sera de tipo long double. Si operamos un float con un long int, este ltimo se convierte internamente en float antes de realizar la operacin, y el resultado es de tipo float.
char
entre los cdigos de los dos caracteres. Esto es especialmente til para convertir un carcter en su correspondiente representacin numrica: se obtiene restando al carcter que contiene el cdigo del dgito en cuestin el carcter que contiene el cdigo del dgito
0:
8 - 0 = 8.
Si a un valor de tipo
char
char
que representa el carcter cuyo cdigo se diferencia del primero en tantas unidades como le
a + 2 = c
c - 2 = a.
Cualquier otra operacin aritmtica que utilice valores de tipo char como operandos se tratara como si fuera una operacin entre enteros.
<: >:
devuelve 1 si el valor del operando de la izquierda es menor que el de la derecha, y 0 en otro caso. devuelve 1 si el valor del operando de la izquierda es mayor que el de la derecha, y 0 en otro caso. devuelve 1 si el valor del operando de la izquierda no es mayor que el de la derecha, y 0 en otro
caso. devuelve 1 si el valor del operando de la derecha no es mayor que el de la izquierda, y 0 en otro
caso. devuelve 1 si los valores de los operadores de izquierda y derecha son iguales, y 0 en otro caso. devuelve 1 si los valores de los operadores de izquierda y derecha son distintos, y 0 en otro caso.
1 Un nmero tiene mayor rango cuanto mayor es el mximo nmero que se puede representar con dicho tipo. Segn eso, el tipo de mayor rango en C ser el long double, seguido del double, seguido del float, seguido del unsigned long long int, . . . , mientras que el de menor rango ser el signed char.
5-4
20132014
Ejemplo: Si i, j y k son tres variables de tipo int, con valores 1, 2 y 3 respectivamente, tendramos: Expresin
i < (i + j) (j + k) > k != j == j >= k (i + 5) 3 2
Resultado
1 1 0 0 1
Los operadores relacionales se evalan de izquierda a derecha. La cual signica que, suponiendo que la variable
valga
5,
la expresin:
7 > i > 2
dara como resultado mayor que
(puesto que
5,
1,
pero
no es
2,
0).
2 <= i <= 4
dara como resultado
(puesto que
es menor que
5,
1,
es menor que
4,
por lo
1). char tambin son tipos numricos, y por tanto pueden utilizarse
como operandos de las expresiones relacionales. En ese caso, las comparaciones se realizarn en funcin del cdigo que representa al carcter en cuestin, y no del carcter mismo. Para el cdigo ASCII (y en general para cualquier sistema de codicacin de caracteres) se pueden establecer las siguientes relaciones:
0 < 1 < 2 < ... < 9 A < B < C < ... < Z a < b < c < ... < z
Pero no debe suponerse ninguna otra relacin a la hora de codicar (como por ejemplo que que
0 < a,
A < a).
double
long double),
hay que tener cuidado con la precisin de estos, puesto que puede ser que el
resultado esperado no sea exactamente el obtenido. Por ejemplo, si sumamos tres nmeros decimales,
0.9999999999998,
que evidentemente no es
1,
0,
!: operador unario, que representa el NO de la lgica proposicional; devuelve 0 si el operando es distinto de cero, y 1 si es igual a cero.
20132014
5-5
&&: operador binario, que representa el Y de la lgica proposicional; devuelve 1 si los dos
operandos son distintos de cero, y 0 en caso contrario. Si el operando de la izquierda vale cero, el operando de la derecha no se evala y el resultado de la operacin es 0.
||: operador binario, que representa el O de la lgica proposicional; devuelve 0 si los dos
operandos son iguales a cero, y 1 en caso contrario. Si el operando de la izquierda vale distinto de cero, el operando de la derecha no se evala y el resultado de la operacin es 1.
Ejemplo: Si i es una variable de tipo int y valor 7, f es una variable de tipo de float y valor 5.5, y c es una
variables de tipo char y valor w, tendramos:
Expresin
(i >= 6) && (c == w) !((i >= 6) && (c == w)) (f < 11) && (i >= 100) (c != p) || ((i + f) <= 10)
Resultado
1 0 0 1
operador unario; invierte cada bit del operando; los bits que valan cero pasan a valer 1, y los que
&: |: ^:
operador binario; pone a 1 los bits si ambos son uno, y a 0 si alguno es igual a cero.
operador binario; pone a 0 los bits si ambos son cero, y a 1 si alguno es igual a uno.
<<: operador binario; desplaza a la izquierda el operando de la izquierda tantas veces como especique
el operando de la derecha. Los bits que 'aparecen' por la derecha se rellenan con ceros.
>>:
operador binario; desplaza a la derecha el operando de la izquierda tantas veces como especique
el operando de la derecha. Los bits que 'aparecen' por la izquierda son indenidos (pueden rellenarse con ceros o con unos).
Lo mejor para utilizar correctamente estos operadores es obtener la representacin del nmero en binario, realizar las operaciones, y volver a codicar en decimal si se desea.
Ejemplo:
126 = 0111 1110 = 1000 0001 = 129 126 & 3 = 0111 1110 & 0000 0011 = 0000 0010 = 2 126 | 3 = 0111 1110 | 0000 0011 = 0111 1111 = 127 126 ^ 3 = 0111 1110 ^ 0000 0011 = 0111 1101 = 125 126 << 3 = 0111 1110 << 3 = 1111 0000 = 240 126 >> 3 = 0111 1110 >> 3 = xxx0 1111 = indeterminado
5-6
20132014
5.6. Asignacin
El operador de asignacin admite dos operandos; el de la izquierda debe ser un identicador de variable, mientras que el de la derecha puede ser cualquier expresin, del mismo tipo que la variable. Este operador, que en C se representa por el smbolo resultado de la expresin.
=,
Ejemplo: La expresin
a = b = c = d = 1;
es equivalente a:
a = (b = (c = (d = 1)));
Sin embargo, este tipo de asignaciones 'en cascada' suelen inducir a error y convierten el cdigo en poco legible, por lo que su utilizacin debe reducirse al mximo. Es mucho mejor utilizar una sentencia para cada asignacin.
Ejemplo: La expresin en 'cascada' del ejemplo anterior sera mucho ms adecuado escribirla como:
d c b a = = = = 1; d; c; b;
Cuando se realiza la asignacin, el resultado de la expresin se convierte al tipo del operando de la izquierda. Si el tipo del operando de la izquierda es de mayor rango que el de la derecha, no hay ningn problema. Sin embargo, si es al revs s pueden producirse alteraciones en los valores de los datos: si un tipo entero se asigna a otro tipo entero pero de menor rango, se copian los bits menos signicativos, pudindose producir resultados errneos. si un valor en coma otante se asigna a una variable de tipo entero, se trunca antes de realizar la asignacin. si un valor en coma otante se asigna a una variable en coma otante, se puede producir un redondeo que ser tanto mayor cuanto menor sea la precisin del tipo de la variable.
20132014
5-7
int i; float f = 1.1111111111; printf("Asignacion a entero de menor capacidad\n"); c = 255; printf("\tc vale %d\n", c); printf("Asignacion de punto flotante a entero\n"); i = 3.3; printf("\tasignando 3.3 i vale %d\n", i); i = 3.9; printf("\tasignando 3.9 i vale %d\n", i); i = -3.9; printf("\tasignando -3.9 i vale %d\n", i); printf("Asignacion de punto flotante a punto flotante\n"); printf("\tf vale %.10f\n", f); return 0;
local> gcc -W -Wall -o truncado truncado.c local> ./truncado Asignacion a entero de menor capacidad c vale -1 Asignacion de punto flotante a entero asignando 3.3 i vale 3 asignando 3.9 i vale 3 asignando -3.9 i vale -3 Asignacion de punto flotante a punto flotante f vale 1.1111111641 local>
Es importante destacar que las asignaciones que puedan dar lugar a prdida de informacin (por redondeo o por truncado) no son ilegales, y por lo tanto el compilador nos va a dejar realizarlas; pero deben utilizarse con muchsimo cuidado.
op=
donde
op puede ser cualquiera de los siguientes: +, -, *, /, %, &, |, , o . No debe aparecer ningn espacio
=.
Son operadores binarios que necesitan como operador a la izquierda un identicador de variable, y a la derecha cualquier expresin compatible con el operador que se est utilizando. La forma general de utilizacin de un operador de asignacin compacta es:
5-8
20132014
A la vista de la expresin equivalente, puede observarse que, independientemente de la prioridad del operador que estemos utilizando, la expresin siempre se evala antes de proceder con la operacin nal y la asignacin. Los operadores de asignacin compacta, al igual que suceda con el operador de asignacin: tambin forman una expresin, y devuelven como resultado el valor asignado. tambin se evalan de derecha a izquierda.
#include <stdio.h> int main() { int i = 3; int j = 2; printf("i vale i += 5; printf("Tras i i -= j; printf("Tras i i /= 2; printf("Tras i j *= i; printf("Tras j i %= j - 2; printf("Tras i return 0; %d, y j vale %d\n", i, j); += 5, i vale %d, y j vale %d\n", i, j); -= j, i vale %d, y j vale %d\n", i, j); /= 2, i vale %d, y j vale %d\n", i, j); *= i, i vale %d, y j vale %d\n", i, j); % %= j-2, i vale %d, y j vale %d\n", i, j);
Es importante no perder de vista que en la asignacin compacta, primero se evala la expresin de la derecha, y despus se realiza la operacin con la variable de la izquierda, nalizando con la asignacin. Por tanto, la expresin
x = x * y + 1
no es equivalente a
x *= y + 1
ya que si desarrollamos esta ltima, la expresin equivalente sera
x = x * (y + 1)
20132014
5-9
situaciones en las que es necesario realizar un cambio de tipo sin involucrar una asignacin; podra ser el caso, por ejemplo, de una comparacin, o de un parmetro para pasarlo a una funcin. En esos casos es necesario aplicar el operador unario de cambio de tipo (suele aparecer como cast en la bibliografa), cuyo formato es:
(nuevo_tipo) expresion
Este operador convierte el resultado de la expresin al tipo indicado entre parntesis. Un caso especialmente til es cuando deseamos realizar una divisin con decimales entre dos enteros, o una divisin entera entre nmeros con decimales.
#include <stdio.h> int main() { float resultado = 0; float decimal = 5.0; int entero = 2; resultado = decimal / entero; printf("decimal/entero da %f\n", resultado); resultado = (int) decimal / entero; printf("(int) decimal/entero da %f\n", resultado); return 0;
conversion.c)
obtendramos lo si-
local> gcc -W -Wall -o conversion conversion.c local> ./conversion decimal/entero da 2.500000 (int) decimal/entero da 2.000000 local>
++ --
incrementa el valor de la variable en una unidad. decrementa el valor de la variable en una unidad.
La particularidad de estos operadores radica en que tienen dos comportamientos, dependiendo de si se colocan delante (prejos) o detrs (postjo) de la variable que hace de operando. En ambos casos, se modica el valor de la variable en una unidad y se devuelve el valor de la misma; la diferencia entre ambos radica en cundo se modica el valor de la variable: Si se utiliza como prejo, primero se modica el valor de la variable (incrementndola o decrementndola, dependiendo del operador) y se obtiene como valor de la expresin el valor ya modicado. Si se utiliza como postjo, primero se obtiene el valor de la variable (que es el resultado de la expresin) y posteriormente se modica el valor de la variable, incrementndola o decrementndola.
5-10
#include <stdio.h> int main() { int i = 1; int j = 0; printf("i vale j = ++i; printf("Tras j j = --i; printf("Tras j j = i++; printf("Tras j j = i--; printf("Tras j return 0;
20132014
%d, y j vale %d\n", i, j); = ++i, i vale %d, y j vale %d\n", i, j); = --i, i vale %d, y j vale %d\n", i, j); = i++, i vale %d, y j vale %d\n", i, j); = i--, i vale %d, y j vale %d\n", i, j);
autos.c)
obtendramos lo siguiente:
gcc -W -Wall -o autos autos.c ./autos 1, y j vale 0 = ++i, i vale 2, y j vale 2 = --i, i vale 1, y j vale 1 = i++, i vale 2, y j vale 1 = i--, i vale 1, y j vale 2
5.9.1. Efectos secundarios de ++ y -Los operadores de autoincremento y autodecremento pueden resultar muy tiles, pero presentan efectos secundarios que a menudo hacen que el programa no se comporte como debe, y sea muy difcil encontrar el motivo. Algo similar sucede en el paso de parmetros a funciones, como se ver en captulos posteriores. Por lo tanto se aconseja tener mucha precaucin en la utilizacin de los operadores de autoincremento y autodecremento, y en ningn caso utilizarlos: con variables que se empleen ms de una vez en una expresin. Por ejemplo, si siguiente expresin:
num
vale
5,
en la
num / 2 + 5 * (3 + num++)
no est garantizado cul de las veces que aparece que si se evala la primera, tomara (la que aparece con el operador de autoincremento), la primera tomara para
en argumentos de funciones, si la variable sobre la que opera se utiliza en ms de un argumento. Esto se ver ms adelante, pero el principio que se aplica es el mismo del caso anterior. como parte del segundo operando en una expresin lgica. En una expresin como:
es mayor que
7, j
7,
no
no modicara su valor.
20132014
5-11
expresion2, si expresion1 es verdadera (distinta de 0), y en este caso no se evaluara expresion3. expresion3,
si
expresion1
no se evaluara expresion2.
En el siguiente ejemplo:
(i < 0) ? 0 : 100
si el valor de
0,
100.
sizeof.
Es un operador unario, que admite como nico operando el nombre de un tipo de datos o el identicador de una variable. Devuelve un valor numrico que representa la cantidad de memoria necesaria para el tipo de datos que se le pasa como parmetro. Se mide en unidades de
sizeof(char)
devolver siempre
12 .
char,
El siguiente programa permite obtener para una mquina y un compilador determinados, el tamao correspondiente a cada uno de los tipos enteros sin signo del estndar C:
#include <stdio.h> int main() { printf("El tamano de un unsigned char es %d\n", sizeof(unsigned char)); printf("El tamano de un unsigned short int es %d\n", sizeof(unsigned short int)); printf("El tamano de un unsigned int es %d\n", sizeof(unsigned int)); printf("El tamano de un unsigned long int es %d\n", sizeof(unsigned long int)); printf("El tamano de un unsigned long long int es %d\n", sizeof(unsigned long long int)); return 0;
tamanio.c)
en la mquina en la que
Si compilamos y ejecutamos este programa (que hemos denominado se redactan estos apuntes, obtendramos lo siguiente:
2 El
tamao en bits de un char viene denido por la constante simblica CHAR_BIT, denida en limits.h.
5-12
20132014
local> gcc -W -Wall -o tamanio tamanio.c local> ./tamanio El tamano de un unsigned char es 1 El tamano de un unsigned short int es 2 El tamano de un unsigned int es 4 El tamano de un unsigned long int es 4 El tamano de un unsigned long long int es 8 local>
1 + 2 * 3
Existiran dos posibilidades de evaluar dicha expresin: primero la suma y despus la multiplicacin, con lo que el resultado obtenido sera sera
9,
7.
+,
por lo que
1 + (2 * 3)
y por lo tanto el resultado es
7.
La situacin est clara cuando mezclamos operadores de distinta prioridad pero, qu sucede si mezclamos operadores de la misma prioridad? En ese caso utilizamos el concepto de asociatividad. La asociatividad determina el orden en que se realizarn las operaciones cuando utilizamos operadores de igual prioridad. As, por ejemplo, considerando que la multiplicacin y la divisin tienen la misma prioridad, la expresin:
6 / 3 * 2
podra evaluarse de dos formas: primero la divisin y luego la multiplicacin, con lo que obtendramos resultado, o primero la multiplicacin y despus la divisin, lo que dara
4 como
duda se resuelve aplicando la asociatividad de ambos operadores: de izquierda a derecha, lo que supone que primero se aplica el operador de ms a la izquierda, y a continuacin los que le siguen por la derecha. Es decir, la forma en la que se evaluar la expresin es:
(6 / 3) * 2
y el resultado que se obtendr ser
4.
La siguiente tabla recoge la prioridad y la asociatividad de los operadores vistos anteriormente; sern ms prioritarios mientras antes aparezcan en la tabla. Dentro de los de igual prioridad, se evaluarn segn la asociatividad indicada (la asociatividad se aplica al orden en que aparecen los operadores en la expresin, no al orden en el que aparecen en la tabla):
20132014
5-13
PRIORIDAD Mayor
OPERADORES Parntesis
ASOCIATIVIDAD
()
Operadores unarios postjos
()
Operadores de tablas y estructuras
[] . ->
Operadores unarios prejos
! ++ -- + - (tipo) sizeof()
Operadores de punteros
* &
Operadores multiplicativos
* / %
Operadores aditivos
+ Operadores de desplazamiento:
<< >>
Operadores relacionales
== !=
Operador AND
&
Operador XOR
^
Operador OR
|
Operador lgico Y
&&
Operador lgico O
||
Operador condicional
?:
Menor Operadores de asignacin
Ejemplo: Si en 1
+ 2 * 3 queremos que se aplique antes la suma que la multiplicacin, debemos poner (1 + 2) * 3. Y si en 6 / 3 * 2 queremos aplicar antes la multiplicacin que la divisin, tendremos que escribir 6 / (3 * 2)
Tambin es conveniente la utilizacin de los parntesis en caso de duda, o para dejar ms claro en el cdigo cmo se va a evaluar una expresin. En denitiva: ante la duda, utilice siempre los parntesis. En la tabla aparecen algunos operadores que se vern ms adelante.
5-14
20132014
Tema 6
Estructuras de Control
ndice
6.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-1 6.2. Estructuras Secuenciales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-2 6.3. Estructuras Selectivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-2
6.3.1. 6.3.2. 6.3.3. 6.3.4. Estructura Selectiva Simple: if else . Sentencias Selectivas Simples Anidadas Ejemplo . . . . . . . . . . . . . . . . . . Estructura Selectiva Mltiple: switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.4.1. Sentencia while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-9 6.4.2. Sentencia do while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-11 6.4.3. Sentencia for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-12
6.1. Introduccin
En los ejemplos de programas que se han visto hasta ahora, las instrucciones seguan una estructura secuencial: se ejecutaba una sentencia, tras la nalizacin se ejecutaba la siguiente, y as sucesivamente hasta alcanzar el nal del programa. Evidentemente, esta estructura no es vlida para todos los casos. Imaginemos que necesitamos obtener la suma de los 100 primeros nmeros naturales. Con lo visto hasta ahora, la nica posibilidad consiste en utilizar 100 instrucciones, una a continuacin de otra, sumando un nmero diferente en cada una de ellas. Otro ejemplo en el que no es suciente con la estructura secuencial es aquel en el que slo debemos realizar una operacin si se cumple una condicin previa: por ejemplo, un programa que controla un cajero automtico, y slo entrega el dinero (y lo resta del saldo) si la cantidad solicitada es inferior o igual al saldo disponible. Realmente, lo difcil es encontrar un programa que pueda realizarse nicamente con la estructura secuencial. Por eso, el lenguaje C (y todos los lenguajes de programacin en general) incorporan una serie de estructuras de control que permiten resolver fcilmente los ejemplos mostrados anteriormente. Las estructuras que incorpora el C basadas en la programacin estructurada son: secuenciales
6-1
6-2
20132014
selectivas repetitivas
Se debe evitar el uso de cualquier otra estructura que no sea una de las anteriores, ya que conduce a cdigo no estructurado. Contrariamente a lo que pudiera parecer en un primer momento, esto no supone ninguna limitacin a la hora de escribir un programa, ya que se puede demostrar que cualquier programa se puede realizar utilizando nicamente estas estructuras. En concreto, est totalmente prohibido en el mbito de la asignatura la utilizacin de las sentencias (que realiza un salto incondicional) y repetitiva), y slo se permite la
goto continue (que fuerza una nueva iteracin dentro de de una estructura utilizacin de la sentencia break (que fuerza la salida de una estructura
#include <stdio.h> /* Obtiene en grados Celsius una temperatura dada en grados Fahrenheit, segun la expresion C = (5/9) * (F-32) */ int main() { float fahrenheit; float celsius; printf("Temperatura en grados Fahrenheit: "); scanf(" %f", &fahrenheit); celsius = (fahrenheit - 32) * 5 / 9; printf(" %f grados fahrenheit son %f grados celsius\n", fahrenheit, celsius); return 0;
Para poder considerar un grupo de sentencias como una sola, podemos encerrarlas entre llaves. A esta construccin se le denomina variables que se denen en l.
bloque,
sentencia. Recuerde adems que la formacin de bloques determinan la visibilidad y el tiempo de vida de las
la simple:
if else. switch.
la mltiple:
20132014
6-3
expresin
=0
=0
expresin
=0
=0
else
es opcional.
El funcionamiento de la estructura selectiva simple es el siguiente: 1. se evala la expresin que acompaa a la clusula
if
2. Si la expresin es cierta (el valor de la expresin es distinto de cero), se ejecuta el bloque que sigue a continuacin y se termina. 3. Si la expresin es falsa (el valor de la expresin es igual a cero) y existe la clusula bloque que sigue a la clusula o dicho de otra forma: el bloque que sigue a la clusula si existe una clusula es igual a cero. Ponga atencin en que lo que sigue a un
else,
se ejecuta el
else
y se termina.
if
else, el bloque que sigue a dicha clusula slo se ejecuta si el valor de la expresin
if
o a un
else
nica sentencia, o un grupo de sentencias encerradas entre llaves. Un problema muy comn cuando se utiliza una estructura selectiva simple consiste en utilizar ms de una sentencia (sin agruparlas en un bloque) bien en el bloque del
if
bien en el del
else:
if), el compilador detectara el problema, puesto que if (tal y como est escrito, la sentencia selectiva simple
else
6-4
20132014
negativo = 1).
es simple: formar un bloque con las dos sentencias encerrndolas entre llaves:
El segundo caso es ms grave, ya que no es ningn error de sintaxis. Desde el punto de vista del compilador, la sentencia selectiva simple nalizara con la ejecucin de la sentencia se ejecutara siempre, independientemente de si es menor que cero, las sentencias que se ejecutaran seran:
negativo = 0; suma += 1;
Encontrar este error, como se puede comprender, puede resultar muy laborioso. Por eso es muy conveniente escribir los programas en algn editor que sea capaz de entender la sintaxis de C e indente adecuadamente cada sentencia, en funcin del bloque al que pertenezca. Con un editor de este tipo, el resultado que habramos obtenido habra sido:
suma += 1,
que no pertenece al bloque de la
else,
como debera.
La solucin, al igual que en el caso anterior, pasa por encerrar entre llaves (y formar un bloque) las sentencias que forman parte del mbito del
else:
if o del else en una sentencia selectiva simple pueden utilizarse a su vez sentencias
20132014
6-5
En este caso, hay que tener en cuenta que la sentencia Por ejemplo, en el siguiente fragmento de cdigo:
else
se asocia siempre al
if
ms cercano.
if (n > if (a z = else z =
0) > b) a; b;
la clusula
else se asocia a if (a > b). Si quisiramos que se aplicara al primer if, bastar con encerrar if:
if
y
De hecho es muy conveniente, en el caso de sentencias de seleccin simples anidadas, encerrar siempre entre llaves los bloques correspondientes a cada una de las clusulas el mbito de cada una de ellas.
else
6.3.3. Ejemplo
Como ejemplo de utilizacin de estructura selectiva simple, escribiremos un programa en C que indique si un ao es bisiesto, utilizando el algoritmo ya usado en el tema 2:
#include <stdio.h> /* Calcula si un anio es bisiesto o no. Un anio es bisiesto si es divisible por 4 pero no por 100, excepto aquellos divisibles por 400. */ int main() { int year = 0; int bisiesto = 0; printf("Introduzca el anio: "); scanf(" %d", &year); if (0 == (year % 4)) { if (0 == (year % 100)) { if (0 == (year % 400)) bisiesto = 1; } else bisiesto = 1; } if (1 == bisiesto) printf("El anio %d es bisiesto\n", year); else printf("El anio %d NO es bisiesto\n", year); return 0;
6-6
20132014
expresin entera
valor 1
bloque 1
valor 2
bloque 2
default
switch (expresion) { case exprConst1: listaProp1 case exprConst2: listaProp2 case exprConstN: listaPropN default: propDefault }
switch.
2. Se compara el valor obtenido, secuencialmente, con los valores que acompaan los diferentes detenindose en el primero que coincida. Estos valores debern ser siempre
enteras.
expresiones constantes
case,
default,
que todas las estructuras selectivas mltiples la incorporen. Es muy importante que esta etiqueta, si se utiliza, se site como la ltima del bloque, puesto que las que se pongan detrs de ella nunca sern consideradas (como se ha obtenido la coincidencia en una etiqueta, se deja de comprobar el resto de etiquetas).
20132014
6-7
Cuando en una estructura de seleccin mltiple se encuentra concordancia con un todos los
case,
se ejecutan todas
las sentencias que se encuentren a partir de este hasta el nal de la estructura selectiva mltiple (incluyendo
case
que aparezcan a continuacin del coincidente). Lo que normalmente se desea no es esto, sino
que se ejecute slo hasta la aparicin del siguiente ltima sentencia dentro de cada mltiple:
case.
break
como
case,
switch (expresion) { case exprConst1: listaProp1 break; case exprConst2: listaProp2 break; case exprConstN: listaPropN break; default: propDefault break; }
expresin entera
valor 1
bloque 1
break;
valor 2
bloque 2
break;
default:
break;
El siguiente programa determina el nmero de das del mes indicado, agrupndolos segn tengan 28, 30 31 das:
#include <stdio.h> /* Programa que lea un numero de mes (en el rango de 1 a 12) e indique el numero de dias del mes. */
6-8
20132014
int main () { int mes = 0; int ndias = 0; printf("Introduzca el numero de un mes (1-12): "); scanf(" %2d", &mes); switch (mes) { case 2: ndias = 28; break; case 4: case 6: case 9: case 11: ndias = 30; break; default: ndias = 31; break ; } printf("\tEl mes %d tiene %d dias.\n", mes, ndias); return 0;
Observe cmo tras la ltima sentencia de cada bloque aparece una sentencia
El caso por defecto (default) suele utilizarse para detectar casos no vlidos o errneos, evitando que se produzcan errores en la ejecucin. El siguiente ejemplo determina si un nmero entre el 1 y el 10 es par o impar; el caso por defecto se ha utilizado en este programa para los nmeros que estn fuera de ese rango:
#include <stdio.h> /* Determina si un numero entre el 1 y el 10 es par o impar. */ int main() { int num = 0; printf("\nIntroduzca el numero: "); scanf(" %d", &num); switch (num) { case 1: case 3: case 5: case 7: case 9: printf("\n El numero %d es impar\n", num); break; case 2: case 4: case 6: case 8: case 10: printf("\n El numero %d es par\n", num); break; default: printf("\n FUERA DE RANGO\n");
20132014
6-9
break; } return 0;
while
expresin
=0
=0
bloque while
while
2. Si la expresin es cierta (el valor de la expresin es distinto de cero), se ejecuta el bloque que sigue a continuacin. 3. se vuelve al primer paso, y se repite el proceso. Algunos aspectos de inters sobre esta estructura seran:
6-10
20132014
while while.
Alguno de los valores que determinan la condicin debe ser modicado dentro del bloque. Si no fuera as y la condicin fuera cierta (distinta de cero) la primera vez que la comprobramos, pasaramos a ejecutar el bloque, y ya no saldramos nunca de l puesto que la condicin seguira siendo cierta de forma indenida. En la condicin, es conveniente utilizar los operadores de rango (<, operadores de igualdad y desigualdad (== o
>, <= o >=) en lugar de los !=). Con estos ltimos estamos esperando un valor exacto,
y si por cualquier motivo no se produce (por ejemplo, porque estemos saltando de tres en tres) el bucle se convierte en innito; si usamos los primeros, esta circunstancia se evita. Al igual que suceda en el caso de las sentencias selectivas simples, es un error muy comn querer utilizar ms de una sentencia dentro del bloque del como en el siguiente ejemplo:
while
#include <stdio.h> int main() { int num = 0; int suma = 0; while (10 > num) num++; suma += num; printf("La suma hasta el %d vale %d\n", num, suma); return 0;
salas@318CDCr12:$ gcc -W -Wall -o whileMal whileMal.c salas@318CDCr12:$ ./whileMal La suma hasta el 10 vale 10 salas@318CDCr12:$
Como puede observarse, y en contra de lo que pudiera parecer a simple vista, el fragmento de cdigo anterior no suma los diez primeros nmeros (del 1 al 10), sino nicamente el ltimo (10); a pesar de que el compilador
while est formado nicamente por la num++, que es la que se repite 10 veces. Una vez nalizado el bucle while es cuando se ejecuta la suma += num, que lgicamente slo se ejecuta una vez. La forma correcta sera la siguiente:
#include <stdio.h> int main() { int num = 0; int suma = 0; while (10 > num) { num++; suma += num; } printf("La suma hasta el %d vale %d\n", num, suma); return 0;
sentencia sentencia
20132014
6-11
del
salas@318CDCr12:$ gcc -W -Wall -o whileBien whileBien.c salas@318CDCr12:$ ./whileBien La suma hasta el 10 vale 55 salas@318CDCr12:$
Se pueden evitar estas confusiones si siempre agrupamos las sentencias que forman el mbito de aplicacin
while
entre llaves, aunque sea una nica sentencia. O utilizando un editor de textos que entienda la
sintaxis de C e indente adecuadamente cada sentencia, en funcin del bloque a que pertenezca.
while,
es muy importante
comprobar que los valores extremos de la condicin (el primer y el ltimo valor para los que se ejecuta el
while)
Para ello es de gran utilidad repasar mentalmente (o con ayuda de papel y lpiz) qu sucede en la condicin de la sentencia
while
la primera vez que se llega a ella y la ltima vez que se realiza la comprobacin (la
vez que se termina la sentencia). Vamos a utilizar como ejemplo para ver esto el del apartado anterior, de suma de los 10 primeros nmeros. La primera vez que llegamos al condicin
while, el valor de num vale 0 (lo acabamos de inicializar), que cumple la (10 > num), por lo que entraremos a ejecutar el bloque. Incrementamos el valor de num (que ahora pasar a valer 1) y lo sumamos. Comprobamos pues que el primer valor del bucle es el correcto. Supongamos ahora que num vale 9; la condicin se sigue cumpliendo, y por tanto entramos en el bucle: incrementamos num, que ahora pasa a valer 10, y lo sumamos. En la siguiente iteracin, la condicin no se cumple y saldremos de la sentencia while. Hemos comprobado por tanto que efectivamente hemos sumado
los nmeros del 1 al 10. Lo anterior se resume en la siguiente tabla:
num
0 ... 9 10
Valor sumado 1 10
num
1 10
do while
es:
bloque do-while
expresin
=0
=0
6-12
20132014
A diferencia de la sentencia
while, en esta estructura repetitiva primero se ejecuta el bloque y posteriormente do while se ejecuta siempre al menos
una vez. Se recomienda usar siempre las llaves para delimitar el bloque de sentencias a ejecutar dentro del bucle. Esto no es necesario cuando el bloque est formado por una nica sentencia.
for
es:
inicial
expresin
=0
=0
bloque for
nal
inicial.
2. se evala la expresin. 3. si es falsa (igual a cero), se naliza la ejecucin de la sentencia. 4. si es verdadera (distinta de cero): se ejecuta el bloque. se ejecuta el bloque se vuelve al paso 2.
final.
20132014
6-13
En una sentencia
for
puede omitirse cualquiera de los tres campos, pero es imprescindible mantener los
de separacin. Si se omite el segundo campo, la condicin se da por cierta, y se obtiene un En el siguiente ejemplo se utiliza una sentencia 0 y 1000:
bucle innito.
for
#include <stdio.h> /* Programa que obtiene la suma de los numeros pares comprendidos entre 2 y 1000. */ int main() { int i; int suma = 0; for ( i = 2; i <= 1000; i += 2 ) suma += i; printf("La suma de los pares hasta 1000 es %d\n", suma); return 0;
inicial
como en el bloque
final.
En ese caso,
#include <stdio.h> /* Programa que obtiene la suma de los numeros pares comprendidos entre 2 y 1000. */ int main() { int i; int suma; for ( suma = 0, i = 2; i <= 1000; suma += i, i += 2) ; printf("La suma de los pares hasta 1000 es %d\n", suma); return 0;
; aislado que aparece tras la clusula for. Esto es necesario porque una sentencia for siempre bloque for. Si no hubiramos incluido ese ; (que representa una sentencia vaca), el bloque hubiera constituido la sentencia printf, que se habra ejecutado 500 veces. for
se utiliza para implementar la funcin que eleva un nmero a una
for
lo
#include <stdio.h> /* Calcula la potencia n de un numero num */ int main() { int num int exponente int potencia int i
= = = =
0; 0; 1; 0;
printf("\nIntroduzca el numero y exponente: "); scanf(" %d %d", &num, &exponente); for (i = 1; i <= exponente; i++)
6-14
20132014
potencia *= num; printf(" %d elevado a %d vale %d\n", num, exponente, potencia); return 0;
En bucles anidados cada alteracin del bucle externo provoca la ejecucin completa del bucle interno, como puede comprobarse en el siguiente ejemplo, que imprime las tablas de multiplicar:
#include <stdio.h> /* Imprime tablas de multiplicar */ int main() { int i = 0; int j = 0; /* Primera aproximacion */ for (i = 1; i <= 10; i++) for (j = 1; j <= 10; j++) printf(" %d x %d = %d\n", i, j, i*j); /* Mas presentable */ for (i = 1; i <= 10; i++) { printf("\nTabla del %d\n=============\n", i); for (j = 1; j <= 10; j++) printf(" %d x %d = %d\t", i, j, i*j); } /* Ahora las tablas en paralelo. OJO a como se recorren los indices. */ for (i = 1; i <= 10; i++) { printf("\n"); for (j = 1; j <= 10; j++) printf(" %d x %d = %d\t", j, i, i*j); } printf("\n"); return 0;
20132014
6-15
getchar,
y escribe lo
#include <stdio.h>
putchar.
n de
/* Programa que copia la entrada estandar a la salida estandar */ int main() { int c = 0;
/* Almacena caracteres */
ax + b = 0
Para ello solicita los dos parmetros necesarios, y resuelve la ecuacin considerando de forma separada los casos especiales.
#include <stdio.h> /* Resuelve una ecuacion de primer grado: 0 = ax + b a <> 0 => x = -b/a a == 0 y b <> 0 => solucion imposible a == 0 y b == 0 => sol. indeterminada */ int main() { int a = 0; int b = 0; float x = 0;
printf("\nIntroduzca los coeficientes a y b: "); scanf(" %d %d", &a, &b); if (0 != a) { x = -(float) b / a; /* Razonar el resultado sin la conversion forzada */ printf("\nLa solucion es %f\n", x); } else if (b) /* La condicion anterior equivale a esta otra: 0 != b */ printf("\n Solucion imposible\n"); else printf("\n Solucion indeterminada\n"); return 0;
6-16
20132014
Tema 7
Funciones
ndice
7.1. Funciones: Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-1 7.2. Denicin de una Funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-2
7.2.1. 7.2.2. 7.2.3. 7.2.4. El identicador . . . . . Los parmetros . . . . . El Cuerpo de la Funcin El Tipo de la Funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-3 7-3 7-3 7-4 7.1.1. La funcin main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-2
7.3. Declaracin de una Funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-4 7.4. Utilizacin de una Funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-5 7.5. Recursividad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-7 7.6. Compilacin Separada y Descomposicin en Ficheros . . . . . . . . . . . . . . . 7-8 7.7. El preprocesador de C.
7.6.1. Funciones y cheros de cabecera . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-9 7.7.1. La directiva include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-10 7.7.2. La directiva define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-10 7.7.3. Directivas condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-12 7.4.1. Correspondencia de parmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-5
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-9
funcin
no es ms que un
conjunto de
Un aspecto importante a considerar en las llamadas a funciones en C es que el hilo de ejecucin no se pierde. Cuando desde un punto del programa llamamos a una funcin, la ejecucin sigue en el punto en que hemos
7-1
7-2
20132014
denido la funcin; cuando esta ltima termina, la ejecucin vuelve al punto desde el que se llam a la funcin, continuando la ejecucin normal. Por supuesto, este mecanismo puede repetirse de forma reiterada, de forma que desde una funcin se puede llamar a una funcin, en la que a su vez se llame a una funcin, ... pero siempre manteniendo que tras la nalizacin de la ejecucin de una funcin, el hilo de ejecucin vuelve a la funcin que hizo la llamada. Esto puede observarse en la siguiente gura:
En C, las funciones no se pueden anidar; es decir, no puede denirse una funcin dentro de otra funcin. Eso signica que todas las funciones estn denidas al mismo nivel, en el ms exterior dentro de un chero, y por lo tanto su visibilidad y alcance son los mismos que los de las variables denidas a ese nivel.
main. main.
Esto quiere
main.
main
denicin de la funcin.
identicador. parmetros.
los valores, y el tipo de dato al que pertenecen, que necesita para su ejecucin: los el tipo del valor que devuelve (si es que devuelve algn valor): el qu es lo que realmente hace: el
tipo.
cuerpo.
20132014
7-3
el identicador de la funcin es calculaMax, devuelve un int, necesita dos parmetros, ambos de tipo int, y su cuerpo sera el conjunto de sentencias encerradas entre las llaves.
7.2.1. El identicador
El identicador de una funcin permite identicar de forma nica la funcin a la que estamos haciendo referencia dentro del programa. Para formar el identicador de una funcin se utilizan los mismos criterios de formacin que los vistos para los identicadores de variables. En un programa no puede haber dos funciones con el mismo identicador, a no ser que las funciones se encuentren en cheros diferentes y ambas tengan restringida su visibilidad mediante la utilizacin de la clusula
static.
parmetros.
Ejemplo: Si denimos una funcin para que devuelva el mayor valor entre dos posibles, necesitamos poder decirle a la
funcin entre qu dos valores queremos que realice la comparacin. En el ejemplo anterior, los parmetros seran int a e int b.
Cada uno de los parmetros de una funcin se dene exactamente igual a como se denira una variable: indicando su tipo y un identicador que permita referirse a l en el cuerpo de la funcin. Los diferentes parmetros de una funcin van encerrados entre parntesis y separados por comas. Los parmetros que se utilizan en la denicin de una funcin se denominan
los efectos (visibilidad y tiempo de vida), los parmetros formales de una funcin son como variables locales denidas en el mbito de la denicin de la funcin. Eso signica que las variables se crearn al iniciar la ejecucin de la funcin, y se destruirn al nalizar.
7-4
20132014
calculaMax denida anteriormente sera int, puesto que el valor que devuelve
return,
return expresion;
siendo
expresion
y tantas veces como se desee, la utilizacin indiscriminada de dicha sentencia hace difcilmente legible el cdigo, por lo que en el mbito de la asignatura las funciones slo pueden tener debe ser necesariamente
return
no se corresponda con
el tipo de la funcin) debe realizarse una conversin forzada de tipo antes de devolver el valor:
void,
return.
antes de su uso
el compilador conozca
prototipo.
declaracin
de la funcin. A la
La declaracin de una funcin se hace de la misma forma que la denicin, pero sustituyendo el cuerpo de la misma por el carcter de n de sentencia
;.
20132014
7-5
El lenguaje permite que en los parmetros se especique slo el tipo; sin embargo, en el mbito de la asignatura, y para mayor claridad, la declaracin de una funcin debe ser idntica a su denicin (exceptuando el cuerpo, claro est). Hay una excepcin a esta regla, en caso que la funcin no admita parmetros. En ese caso es necesario indicarlo en la declaracin utilizando la palabra reservada
void,
Ejemplo: La declaracin de una funcin menu, que no devuelve nada y no admite parmetros, sera:
void menu(void);
La declaracin de una funcin, al contrario que la denicin, puede aparecer tantas veces como sea necesario dentro de un programa. Es adems conveniente que la declaracin aparezca antes que la propia denicin.
valores
como parmetro cualquier expresin del mismo tipo que el parmetro formal correspondiente.
Ejemplo: Podran ser llamadas a la funcin calculaMax declarada y denida anteriormente (supuesto que uno y
dos son variables de tipo int):
void),
la funcin
puede
utilizarse en
expresiones en los mismos lugares en los que podra utilizarse una expresin de su mismo tipo.
7-6
20132014
#include <stdio.h> /* Declaracion de la funcion */ int calculaMax(int a, int b); int main() { int i = 7; int j = 4; int maximo; maximo = calculaMax(i, j); printf("Maximo de %d y %d es %d\n", i, j, maximo); maximo = calculaMax(2 * i, 4 * j); printf("Maximo de %d y %d es %d\n", 2 * i, 4 * j, maximo); return 0; } /* Definicion de la funcion */ int calculaMax(int a, int b) { int resultado = a; if (b > a) resultado = b; return resultado;
tenemos dos llamadas a la funcin calculaMax. En la primera invocacin tendramos la siguiente ejecucin equivalente:
int calculaMax() { int a = 7; int b = 4; int resultado = a; if (b > a) resultado = b; return resultado;
int calculaMax() { int a = 14; int b = 16; int resultado = a; if (b > a) resultado = b; return resultado;
20132014
7-7
La primera es que puesto que los parmetros formales actan como variable locales, los cambios que hagamos sobre los parmetros formales en la funcin llamada no tienen ningn efecto sobre los valores de las variables utilizadas como parmetros reales en la llamada a la funcin, puesto que dichos cambios se estn realizando sobre una copia del valor de los parmetros reales.
calculaMax
de la siguiente forma:
La segunda consecuencia es que, puesto que vamos a utilizar los parmetros reales para realizar una asignacin sobre los parmetros formales, slo podremos utilizar como parmetros aquellos elementos que pueden aparecer en el lado derecho de una asignacin.
7.5. Recursividad
Ya sabemos que una funcin en C puede realizar llamadas a otras funciones. Un caso particular de esta tcnica es aquel en el que desde una funcin se realiza una llamada a la propia funcin. A este caso se le denomina los valores de los parmetros reales en los parmetros formales, por lo que cada llamada est utilizando sus propios valores. La tcnica de la recursividad se aplica a problemas que pueden denirse de modo natural de forma recursiva.
recursividad. La recursividad es posible en C porque en cada llamada a una funcin, esta copia
Ejemplo: Consideremos el caso de la funcin factorial: la denicin del factorial de un nmero, fact(n) se dene
como:
1 n * fact(n - 1)
si n = 0 si n > 0
Vemos que la especicacin de la funcin hace uso de la denicin de la misma, es decir: la funcin se llama a s misma.
No todos los problemas admiten soluciones recursivas, y si la admiten muchas veces no son inmediatas. S podemos asegurar que toda solucin recursiva tiene una solucin iterativa equivalente, aunque no siempre es inmediato obtenerla. En las deniciones de funciones recursivas es
terminacin de la propia funcin y devuelva el control a la funcin que realiz la llamada. En caso contrario, el programa entrara en lo que se denomina un bucle innito: la funcin estara llamndose a s misma de forma indenida. Para que pueda aplicarse el caso base, es necesario que en cada una de las llamadas a s misma que haga una funcin se modiquen los valores de los parmetros.
Ejemplo: En el caso del factorial el caso base sera el factorial de 1, como se reeja en la denicin.
Aunque el cdigo de una funcin que utiliza tcnicas recursivas pueda ser ms corto, habitualmente consume ms memoria durante la ejecucin, y puede llegar a ser ms lento en la ejecucin, debido a que en cada llamada que la funcin se hace a s misma es necesario copiar valores en variables y estas ocupan espacio en memoria, tanto ms cuanto mayor sea el nmero de llamadas.
7-8
20132014
Ejemplo: Para ver el funcionamiento paso a paso de una funcin recursiva, estudiaremos la siguiente funcin, que
int mcd(int a, int b) { int res; if (a == b) res = a; else if (a > b) res = mcd(b, a - b); else res = mcd(a, b - a); return res;
Si se llama a esta funcin con los parmetros reales 36 y 52 (en este orden), tendramos la siguiente secuencia de ejecucin: 1a 2a 3a 4a 5a 6a 7a
a b
36 36 16 16 4 4 4
52 16 20 4 12 8 4
Condicin
a a a a a a a <b >b <b >b <b <b =b
Nueva llamada
(a, (b, (a, (b, (a, (a, b a b a b b a) b) a) b) a) a)
caso base
compilacin separada.
La compilacin separada hace referencia al caso en que tengamos varios cheros fuente para generar un ejecutable. Es un concepto distinto al de la modularidad, aunque muy relacionado: la compilacin separada implica necesariamente modularidad, pero un programa modular puede estar compuesto por un nico chero, y por lo tanto no le es de aplicacin la compilacin separada. La divisin del programa en varios cheros tiene dos considerables ventajas: El programa queda ms estructurado y organizado. Si se necesita modicar un chero, slo se compilara el modicado, para posteriormente enlazarlo con el resto de cheros objeto. Cada chero en los que se descompone un programa fuente conforma lo que se denomina una
unidad de
Ejemplo: El programa visto en ejemplo.c podramos descomponerlo en las siguientes unidades de compilacin:
20132014
7-9
maximo = calculaMax(i, j); printf("Maximo de %d y %d es %d\n", i, j, maximo); maximo = calculaMax(2 * i, 4 * j); printf("Maximo de %d y %d es %d\n", 2 * i, 4 * j, maximo); return 0;
que hayamos dividido nuestro programa. La nica excepcin podra ser el chero que contiene la denicin
main.
En dicho chero de cabecera se incluirn las declaraciones o prototipos de todas las funciones denidas en el correspondiente chero fuente y que puedan ser utilizadas fuera del chero en que se denen. Las funciones denidas en un chero de cdigo que no vayan a ser utilizadas en otros cheros no deben aparecer en el chero de cabecera, y adems debern etiquetarse con la palabra reservada
static
Ejemplo: El chero de cabecera correspondiente al ejempo del clculo del valor mximo sera:
int maximo(int a, int b);
Una vez creado el chero de deniciones de funciones y el correspondiente chero de cabecera, se deber incluir este ltimo: en el propio chero de deniciones, para que el compilador pueda comprobar que los prototipos se corresponden con las deniciones. en los cheros que utilicen dichas funciones. Para realizar esta inclusin se utilizan las directivas del preprocesador de C.
7.7. El preprocesador de C.
El preprocesador de C trabaja con una serie de instrucciones especiales (directivas) que se ejecutan antes del proceso de compilacin. El preprocesado es un paso previo a la compilacin, aunque la llamada al preprocesador la realiza el mismo compilador, siendo completamente transparente al programador. Existen varios tipos de directivas del preprocesador, de los cuales slo veremos tres:
7-10
20132014
el de inclusin de cheros. el de denicin de macros. el de compilacin condicional. Las directivas del preprocesador de C empiezan siempre por el carcter
#.
#include
>)
Ejemplo: Para incluir el chero de cabecera por excelencia, stdio.h, tendramos que utilizar la instruccin:
#include <stdio.h>
si se utilizan las comillas dobles ("), se busca el chero primero en el directorio de trabajo, y si no lo encuentra lo busca en los directorios predenidos por la instalacin.
Ejemplo: Para incluir el chero de cabecera cuentas.h, situado en el subdirectorio listas, pondramos:
#include "listas/cuentas.h"
La directiva pero
SLO puede utilizarse para incluir cheros de cabecera, NUNCA cheros de cdigo.
#include
puede utilizarse tanto en cheros de cdigo (.c) como en cheros de cabecera (.h),
define
permite denir
macros,
nombre
La formacin del nombre de la macro sigue las reglas de formacin de identicadores en funciones y variables. Por convenio, los nombres de las macros irn siempre en maysculas. El texto de reemplazo comienza tras el ltimo espacio que sigue al nombre, hasta el carcter de n de lnea; si se desea que el texto de reemplazo ocupe ms de una lnea, se debe terminar cada lnea menos la ltima con el carcter de escape (\). Realizan la sustitucin del nombre indicado en la macro por la expresin indicada. Los cambios tienen efecto desde el momento de la denicin hasta el nal del archivo. La sustitucin no se lleva a cabo dentro de cadenas entrecomilladas ni en partes de un elemento. Las macros se suelen utilizar para denir smbolos que representes entidades que se derivan de la especicacin del programa.
20132014
7-11
Ejemplo: Las macros nos permiten eliminar constantes que se utilizan en varios lugares de un programa. Por ejemplo,
#define LON_NOM_FIC
Si cambiamos de mquina y queremos modicar la longitud de los nombres de cheros a 15 caracteres, slo tendremos que modicar una lnea (la de la denicin de la macro), en lugar de todas las lneas en las que se utiliza dicho valor.
#define #define ... x = i = MAX(A,B) ((A) > (B) ? (A) : (B)) CUADRADO(x) (x) * (x) MAX(p + q, r + s); CUADRADO(j + 1);
En este ltimo caso puede observarse la importancia de los parntesis rodeando los parmetros en el texto de reemplazo. Si no existieran dichos parntesis, tendramos la siguiente sustitucin:
i = j + 1 * j + 1;
USARSE.
NO PUEDEN
Anulacin de macros
Si no se desea que una macro denida previamente tenga validez hasta el nal del chero, puede anularse la denicin con la directiva
undef:
siendo
#undef NOMBRE
NOMBRE el identicador
7-12
20132014
#if expresion entera constante ... #elif expresion entera constante 2 ... #elif expresion entera constante 3 ... #else ... #endif
#elif y #else son opcionales. Una directiva #if #elif, pero slo puede haber una #else.
En la expresin entera se pueden usar: macros del preprocesador (en ningn caso variables del programa) y constantes operadores aritmticos, de comparacin y lgicos. el operador
defined,
denida y distinto de cero si s lo est. Si el resultado de la expresin es distinto de cero se ejecutan las siguientes lneas hasta llegar a un
#elif,
#else
#endif.
#elif, #else
#endif
que aparezca.
Ejemplo: El siguiente fragmento visualiza en la salida estndar, en funcin del valor de la macro
DEPURANDO,
#ifdef NOMBRE,
que es equivalente a
#ifndef NOMBRE,
que es equivalente a
Ejemplo: Usaremos las directivas condicionales para evitar lo que se denomina doble inclusin de los cheros de
cabecera. Todos los cheros de cabecera que escribamos deben comenzar por una directiva #ifndef seguida de una directiva #define. El chero de cabecera siempre nalizar con la directiva #endif:
El nombre de la macro que utilizaremos ser el del chero en maysculas, substituyendo el . por _. La primera vez que en una compilacin incorporemos el chero de cabecera, la macro no estar denida, con lo cual la deniremos y continuaremos con el chero. Las sucesivas veces que se intente incluir el mismo chero, la macro ya estar denida y no tendr ningn efecto.
20132014
7-13
#include <stdio.h> #include "maximo.h" int main() { int i = 7; int j = 4; int maximo; maximo = calculaMax(i, j); printf("Maximo de %d y %d es %d\n", i, j, maximo); maximo = calculaMax(2 * i, 4 * j); printf("Maximo de %d y %d es %d\n", 2 * i, 4 * j, maximo); return 0;
#endif #include <stdio.h> #include "maximo.h" /* Definicion de la funcion */ int calculaMax(int a, int b) { if (b > a) a = b; return a;
7-14
20132014
Tema 8
Punteros
ndice
8.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-1
8.1.1. Declaracin de Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-2 8.1.2. El valor NULL en los Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-3 8.2.1. Prioridades de los Operadores de Punteros . . . . . . . . . . . . . . . . . . . . . . 8-4
8.4. Punteros a Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-7 8.5. Uso de Punteros para Devolver ms de un Valor . . . . . . . . . . . . . . . . . 8-7
8.1. Introduccin
Como ya hemos visto, toda variable que se utilice en un programa en C ocupar una cierta cantidad de memoria. Para conocer el valor de una determinada variable, el ordenador necesita tres datos: en qu parte de la memoria est guardada esa variable. qu tamao tiene. cmo est codicada. Podemos ver la memoria del ordenador como una secuencia de octetos en los que la mquina va almacenando las variables que va a usar durante la ejecucin de un determinado programa. Por ejemplo, si tenemos una mquina en la que el tamao de un tipo octetos, las siguientes declaraciones:
int
es de 4 octetos, y un
float
de 8
8-1
8-2
20132014
3000
3004
3008
300c
3010
3014
3018
301c
3020
3024
numero
primero
segundo
numero comienza en la posicin de memoria 0x3000, segundo en la posicin 0x300c.
primero
en la posicin
0x3004,
y la variable
En C existe el concepto de
puntero.
memoria. El puntero siempre tiene un tipo asociado, que es el de la variable a la que apunta. Es decir, que desde el punto de vista del C, un puntero a entero tiene un tipo distinto de un puntero a punto otante, aunque tengan el mismo valor.
*.
El objetivo de un puntero es poder acceder a una determinada variable. Por eso, cuando declaramos un puntero tenemos necesariamente que especicar de qu tipo es la variable a la que apunta el puntero. Esto adems determina el tipo del puntero, de forma que dos punteros son del mismo tipo de datos si y slo si apuntan a variables del mismo tipo. Cuando un puntero almacena la direccin de comienzo de una variable, se dice que el puntero variable. Un tipo especial de puntero es el que se declara como puntero a tipo
apunta
a la
void:
apunta.
void * puntero;
Es una forma de indicar que slo sabemos que es un puntero, pero que no sabemos a qu tipo de variable
Vimos en un tema anterior que para poder utilizar una variable en un programa en C, primero debamos declararla, lo que permita a la mquina reservar memoria para dicha variable. Es importante destacar aqu que declarar un puntero a un determinado tipo
eso no supone que hayamos reservado memoria para una variable de tipo int, sino para un puntero a una variable de dicho tipo. Si queremos una variable de tipo int tendremos que declararla de forma explcita.
En C, el tamao de un puntero no est denido, sino que puede variar entre dos mquinas. Para conocer el tamao de un puntero en una mquina determinada, podemos utilizar el operador siguiente programa:
sizeof,
como en el
#include <stdio.h>
20132014
8-3
salas@318CDCr12:$ gcc -W -Wall -o tamPuntero tamPuntero.c salas@318CDCr12:$ ./tamPuntero El tamano de un puntero es 4 salas@318CDCr12:$
SIEMPRE
Hay circunstancias en las que no es posible saber en el momento de la declaracin a qu variable tiene que
NULL,
Ejemplo: Siguiendo esas indicaciones, la forma correcta de declarar los tres punteros anteriores, sera:
int * pEntero = NULL; float * pReal = NULL; void * puntero = NULL;
Obsrvese que se utiliza el mismo valor para inicializar los tres tipos de punteros, porque los tres, independientemente de su tipo, contienen la misma informacin: una direccin de memoria.
No lo olvide:
TODOS
NULL.
para los punteros es %p, obteniendo el valor del pun-
printf
scanf:
&: operador de direccin. Se aplica como prejo a cualquier variable, y devuelve la direccin en la que
se encuentra almacenada la variable.
*:
puntero,
direccin de memoria almacenada en el puntero. Este operador tiene sentido porque en la declaracin del puntero hemos especicado de qu tipo es la variable a la que apunta. Si no fuera as, el resultado de este operador sera indeterminado. De hecho, este operador no puede aplicarse si el puntero es codicada la informacin en memoria.
void *,
8-4
20132014
printf("Origen vale %d, y esta en la direccion %p\n", origen, pOrigen); /* Podemos usar el puntero para ver el valor */ printf("Origen vale %d\n", *pOrigen); origen = 1; /* Los cambios en el valor de la variable tambien se ven si usamos el puntero */ printf("Origen vale %d\n", *pOrigen); /* Tambien podemos usar el puntero para cambiar el valor de la variable */ *pOrigen = 2; /* Y el valor de la variable cambia */ printf("Origen vale %d\n", origen); return 0;
El valor del puntero puede variar de una mquina a otra, y dentro de la misma mquina, de una ejecucin a otra, porque en cada momento reejar en qu posicin de memoria est la variable origen, y eso puede cambiar. Lo que no variar nunca es en una misma ejecucin.
Observe que el operador
&
puede aplicarse a
porque estaramos aplicando el operador de direccin a una expresin, mientras que sera totalmente correcto escribir:
*(&suma);
20132014
8-5
Ejemplo: Supongamos el siguiente mapa de memoria en una mquina en la que los tipos int ocupan 4 octetos y
los tipos float ocupan 8 octetos:
3000 3004 3008 300c 3010 3014 3018 301c 3020 3024
uno
dos
tres
cuatro
primero
segundo
tercero
el valor del puntero ser 0x3000. Si le sumamos uno al puntero, y puesto que apunta a un tipo int que ocupa cuatro octetos, el resultado ser que el puntero incrementar su valor en cuatro unidades, pasar a valer 0x3004 y ahora apuntar a dos. Si ahora le sumamos 2, incrementar su valor en ocho unidades, pasar a valer 0x300c y apuntar a cuatro. Si ahora volvemos a incrementar el puntero en una unidad, volver a aumentar su valor en cuatro unidades, pasar a valer 0x3010, y ahora apuntar a primero. Observe que ahora el puntero apunta a una variable de tipo float, pero sigue siendo un puntero a int. Por eso, si volvemos a sumarle 1, el puntero incrementar su valor en 4 unidades (las correspondientes al tamao de un int, que es lo que reeja su tipo), pasando a valer 0x3014, y no en 8 unidades (que sera el tamao del float al que est apuntando en ese momento). Como resultado, el puntero apuntar a una zona de memoria que no se corresponde con ninguna variable. Si con el mismo mapa de memoria efectuamos la siguiente operacin:
float * pReal = &primero;
el valor del puntero ser 0x3010. Si le sumamos uno al puntero, y puesto que apunta a un tipo float que ocupa ocho octetos, el puntero incrementar su valor en ocho unidades, pasando a valer 0x3018 y apuntar a segundo.
Lo expresado para la suma es aplicable, lgicamente, al operador de autoincremento (++) y al operador de asignacin compacta con el operador de suma (+=). La operacin de restar un entero a un puntero es muy similar (restar un nmero no es ms que sumar un nmero negativo), de forma que cada vez que decrementamos en una unidad un puntero, su valor se ve reducido en el tamao del tipo al que apunta. Igual que antes, lo dicho para la resta de un entero es aplicable al operador de autodecremento (--) y al operador de asignacin compacta con el operador de resta (-=).
8-6
20132014
el valor de pEntero ser 0x3000 (apuntar a uno), y el valor de pReal ser 0x3010 (apuntar a primero).
pNuevo = pAntiguo + 2;
si consideramos la sentencia anterior como una expresin algebraica, y pasamos pAntiguo al primer miembro, nos queda la expresin:
pN uevo pAntiguo = 2
Es decir, la diferencia entre dos punteros es el nmero de veces que cabe entre los dos punteros el tipo de datos al que apuntan. Recuerde que es imprescindible para poder realizar la operacin que los dos punteros apunten al mismo tipo de datos. Adems, las direcciones a las que apuntan los dos punteros deben estar separadas un nmero exacto de veces el tamao del tipo al que apuntan; si no fuera as, el resultado de la operacin es indeterminado.
int uno int dos int * pUno int * pDos = = = = 1; 1; &uno; &dos;
si realizamos la comparacin pDos == pUno obtendremos como resultado 0, puesto que el valor de pDos es la direccin de la variable dos, y el valor de pUno es la direccin de la variable uno, que evidentemente son diferentes (cada variable ocupar una posicin diferente en la memoria).
Si lo que queremos es comparar los valores de las variables, deberemos utilizar el operador de contenido.
Lo mismo sucede para el resto de los operadores relacionales vistos con anterioridad.
20132014
8-7
y de tipo puntero a
pEntero,
una variable, y como a tal se le puede aplicar el operador de direccin, que utilizar dos veces el carcter
int, es decir un puntero a puntero a int. Para declarar una variable de este tipo tendremos *: * en la declaracin de una variable.
Esta operacin podemos repetirla tantas veces como queramos, obteniendo cada vez un nivel ms de indireccin. Por cada nivel de indireccin tendremos que utilizar un carcter
estaremos declarando una variable de nombre pPuntPuntReal, y de tipo puntero a puntero a puntero a float. Esta variable podemos utilizarla de diferentes formas en nuestro cdigo, obteniendo diferentes tipos de datos. As: si utilizamos pPuntPuntReal tendremos un tipo puntero a puntero a puntero a float. si utilizamos *pPuntPuntReal tendremos un tipo puntero a puntero a float. si utilizamos **pPuntPuntReal tendremos un tipo puntero a float. Por ltimo, si utilizamos ***pPuntPuntReal tendremos un tipo float.
Una regla muy simple para saber de qu tipo es una expresin con varios operadores de contenido aplicados a una variable con diversos grados de indireccin es utilizar la declaracin de dicha variable, y separar en ella la parte que nos encontramos en la expresin; lo que queda de la declaracin es el tipo de la expresin.
si en un programa nos encontramos con la expresin **pPuntPuntReal, aislamos esta parte en su declaracin:
float * ** pPuntPuntReal = NULL;
y lo que nos queda, float * sera el tipo de dicha expresin, es decir, un puntero a float.
El utilizar varios niveles de punteros hace que el cdigo sea menos legible, por lo que debe evitarse su uso siempre que sea posible.
8-8
20132014 la
direccin de la variable
Existe un mtodo que nos permite resolver este problema. Para ello debemos pasar como parmetro que queramos modicar, y operar dentro de la funcin con el
contenido
de los
parmetros formales. Lo que hacemos es pasarle la direccin de la variable: es decir, le estamos diciendo a la funcin dnde est el valor que queremos intercambiar, no cul es el valor. Las modicaciones que se realicen en la funcin al contenido de los parmetros formales s tienen efecto al nalizar la funcin, puesto que lo que deja de tener validez es la copia del puntero que se ha utilizado en la funcin.
Ejemplo: Una funcin que intercambia los valores de dos variables podra ser:
#include <stdio.h> /* Esta definicion hace al mismo tiempo de declaracion */ void intercambio (int *x, int *y) { int aux; aux = *x; *x = *y; *y = aux; } int main () { int a = 7; int b = 5; printf("Antes de intercambio (a,b)=( %d, %d)\n", a, b); intercambio(&a, &b); printf("Despues de intercambio (a,b)=( %d, %d)\n", a, b); return 0;
Si pasramos las variables como parmetros reales, las manipulaciones que la funcin realizara las hara sobre los parmetros formales, y las variables no se habran modicado. Sin embargo, al pasarle la direccin de las variables actuamos sobre los valores apuntados por los parmetros (y por eso es necesario utilizar el operador de contenido,
*,
Tema 9
Tablas
ndice
9.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-1 9.2. Declaracin de las Tablas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-2 9.3. 9.4. 9.5. 9.6. Acceso a los Elementos de una Tabla Recorrer los Elementos de una Tabla Utilizacin de Constantes Simblicas . Punteros y Tablas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9.7. Tablas como Resultado de una Funcin . . . . . . . . . . . . . . . . . . . . . . . 9-8 9.8. Tablas Multidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-8 9.9. Tablas de punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-9
9.8.1. Inicializacin de Tablas Multidimensionales . . . . . . . . . . . . . . . . . . . . . . 9-9 9.9.1. Tablas de punteros y tablas multidimensionales . . . . . . . . . . . . . . . . . . . . 9-10
9.1. Introduccin
Hemos visto que para representar una magnitud del mundo real en un programa utilizamos las variables. Pero qu sucede si lo que queremos representar es el valor de un conjunto idntico de variables, como por ejemplo las calicaciones de los alumnos de una clase? Aunque es posible resolver este problema con lo que hemos visto hasta ahora, sin ms que denir una variable por cada alumno:
y repetir el proceso de calcular la nota para cada uno de los alumnos, es evidente que este procedimiento puede resultar muy laborioso y propenso a errores.
9-1
9-2
20132014 tablas.
Por ello, C incorpora un mecanismo para manejar este tipo de situaciones, que son las Una tabla es un conjunto nito y ordenado de elementos del mismo tipo (int, bajo el mismo nombre.
Su principal caracterstica es el modo de almacenamiento en memoria: se realiza en posiciones consecutivas. Una caracterstica diferenciadora de las tablas con respecto a otros tipos de datos es que no soportan el operador de asignacin; para asignar una tabla a otra ser necesario asignar los elementos de la tabla uno a uno.
[],
Ejemplo: Si queremos declarar la tabla de las calicaciones de los 65 alumnos de una clase, tendramos que poner
int notas[65];
Ejemplo: Para inicializar una tabla que contenga los das del mes, podramos poner:
int diasMes[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
La asignacin se realiza en orden ascendente del ndice, de forma que si no hay sucientes valores para inicializar la tabla completa, se asignarn valores a los ndices ms bajos; los elementos para los que no haya valores se inicializarn a cero. Si se realiza la inicializacin de una tabla en la declaracin, el C permite que no se especique el tamao de la tabla; lo que har ser contar cuntos valores de inicializacin aparecen, y asumir que ese es el tamao de la tabla.
Ejemplo: La tabla con los das de los meses del ao podra declararse tambin de la siguiente forma:
int diasMes[] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
\0.
Esto permite que la inicializacin de las tablas que vayan a contener cadenas de caracteres pueda hacerse de dos formas diferentes: mediante una secuencia de caracteres individuales, encerrado cada uno de ellos entre comillas simples, como una tabla de cualquier otro tipo. En este caso, no hay que olvidar asignar un carcter de la tabla.
\0
tras el ltimo
20132014
9-3
mediante una cadena de caracteres (secuencia de caracteres encerradas entre comillas dobles). En este caso no es preciso incluir el carcter
\0
Ejemplo: Para inicializar una tabla de caracteres tenemos las siguientes posiblidades:
char char char char nombre[8] nombre[] nombre[8] nombre[] = = = = { P, r, i, m, e, r, o, \0 }; { P, r, i, m, e, r, o, \0 }; "Primero"; "Primero";
printf
scanf: %s.
\0),
scanf,
\0
nal; y
printf,
nal.
\0
Es muy importante recordar esto a la hora de solicitar una cadena de caracteres por teclado: al declarar la tabla que almacenar la cadena, debemos reservar sitio para todos los caracteres, y tambin para el que el sistema va a aadir.
\0
0.
Ejemplo: Para acceder a la calicacin del quinto alumno, tendramos que poner:
notas[4]
El ndice de una tabla siempre debe ser una expresin entera. Puesto que el primer elemento de la tabla es el de ndice el tamao de la tabla. El principal problema que tiene la utilizacin de tablas en C radica en que el compilador sabe siempre cul es el primer elemento de la tabla (el de ndice
0),
un ndice para acceder a un elemento de la tabla mayor que su tamao, y el compilador no detectara el error. Por eso hay que tener mucho cuidado al escribir programas, asegurndonos de que no sobrepasamos los lmites de las tablas.
y no se detectara que ese elemento ya no pertenece a la tabla, puesto que el ltimo elemento de la tabla es el de ndice 11.
El operador
[]
9-4
20132014
for
asignacin inicial (que el valor del ndice sea 0), una operacin tras cada iteracin (incrementar el ndice en una unidad), y una comparacin de control (que no se haya superado el lmite de la tabla).
Ejemplo: El siguiente cdigo calcula la media de las calicaciones de 10 alumnos, que se introducirn por teclado:
#include <stdio.h> int main() { int i; float notas[10]; float media = 0; for ( i = 0; i < 10; i++ ) { printf("\nNota de alumno: %d: ", i); scanf(" %f", ¬as[i]); } for ( i = 0; i < 10; i++ ) media += notas[i]; media /= 10; printf("\nLa media es %f\n", media); return 0;
Obsrvese que en la condicin de control de la estructura repetitiva for hemos puesto i < 10, y no i <= 10, puesto que en una tabla, el ndice del ltimo elemento es uno menos que el tamao de la tabla.
#define
un momento determinado queremos cambiar el tamao de una tabla, slo tendremos que modicar una lnea
#include <stdio.h> int main() { float notas[65]; int i = 0; float maxima = 0.0; for ( i = 0; i < 65; i++) { printf("Calificacion del alumno %d: ", i); scanf(" %f", ¬as[i]);
20132014
9-5
} for ( i = 0; i < 65; i++) if (notas[i] > maxima) maxima = notas[i]; for ( i = 0; i < 65; i++) notas[i] = notas[i] * 10 / maxima; for ( i = 0; i < 65; i++) printf("La nota final del alumno %d es %f\n", i, notas[i]); return 0;
Si aparece un nuevo alumno, tendremos que buscar en el cdigo todas las apariciones de 65 y cambiarlas por 66. Si utilizamos una constante simblica:
#include <stdio.h> #define NUMALUMNOS 65 int main() { float notas[NUMALUMNOS]; int i = 0; float maxima = 0.0; for ( i = 0; i < NUMALUMNOS; i++) { printf("Calificacion del alumno %d: ", i); scanf(" %f", ¬as[i]); } for ( i = 0; i < NUMALUMNOS; i++) if (notas[i] > maxima) maxima = notas[i]; for ( i = 0; i < NUMALUMNOS; i++) notas[i] = notas[i] * 10 / maxima; for ( i = 0; i < NUMALUMNOS; i++) printf("La nota final del alumno %d es %f\n", i, notas[i]); return 0;
slo tendremos que cambiarla en un sitio, y adems perfectamente localizado. Debe observarse que el nombre dado a la constante simblica es signicativo. Sera un error utilizar la siguiente sentencia para denir la constante:
#define SESENTAYCINCO 65
Primero, porque no nos proporcionara ninguna informacin sobre para qu se utiliza, y segundo, porque si tenemos que modicar su valor, representara un contrasentido.
Ejemplo: Las expresiones diaMes[0], y *diaMes son equivalentes, y pueden utilizarse indistintamente en cualquier
expresin.
Una de las caractersticas de las tablas es que almacenan sus valores en memoria de forma consecutiva. Como se vio al hablar de la aritmtica de punteros, si esto suceda se poda ir incrementando el valor del puntero
9-6
20132014
para acceder a las sucesivas variables. Aplicando estos dos conceptos, podemos encontrar que si
diaMes
diaMes + 1
apuntar al segundo,
diaMes + 2
siguiente gura:
3000
3004
3008
300c
3010
3014
3018
301c
3020
3024
diaMes[0] diaMes
diaMes[1] diaMes[2] diaMes[3] diaMes[4] diaMes[5] diaMes[6] diaMes[7] diaMes[8] diaMes[9] diaMes+1 diaMes+2 diaMes+3 diaMes+4 diaMes+5 diaMes+6 diaMes+7 diaMes+8 diaMes+9
tabla[indice]
es idntico a
donde la la inferior representa la direccin del elemento, y la superior el valor del elemento. Por tanto, en una tabla podemos decir que siempre se cumple que
*(tabla+indice).
#include <stdio.h> #define NUMALUMNOS 10 int main() { int i; float notas[NUMALUMNOS]; float media = 0; for ( i = 0; i < NUMALUMNOS; i++ ) { printf("\nNota de alumno: %d: ", i); scanf(" %f", notas + i); } for ( i = 0; i < NUMALUMNOS; i++ ) media += *(notas + i); media /= NUMALUMNOS; printf("\nLa media es %f\n", media); return 0;
donde simplemente se han sustituido la direccin de los elementos (notas + i en lugar de ¬as[i]) y los valores de los elementos (*(notas + i) en lugar de notas[i]).
Aunque el nombre de una tabla sea equivalente a la direccin del primer elemento, esto no signica que sea una variable de tipo puntero; y no lo es porque no es una variable, sino una constante. Por eso podemos utilizarla para acceder a los elementos de la tabla, pero no para modicar su valor.
Ejemplo: La expresin diaMes++ es errnea, puesto que estaramos intentando modicar el valor de una constante.
Para recorrer una tabla modicando el valor de un puntero, debemos utilizar una variable auxiliar, que inicializaremos con la direccin del primer elemento de la tabla, y que posteriormente iremos modicando.
Ejemplo: Con esta losofa, el clculo de la nota media quedara de la siguiente forma:
20132014
9-7
{ int i; float notas[NUMALUMNOS]; float * pAux = notas; float media = 0; for ( i = 0; i < NUMALUMNOS; i++, pAux++ ) { printf("\nNota de alumno: %d: ", i); scanf(" %f", pAux); } pAux = notas; for ( i = 0; i < NUMALUMNOS; i++, pAux++ ) media += *pAux; media /= NUMALUMNOS; printf("\nLa media es %f\n", media); return 0;
en el primer caso estamos declarando una tabla de caracteres que inicializamos con una cadena de caracteres. En el segundo caso estamos declarando un puntero a carcter, que inicializamos con la direccin de una cadena de caracteres.
Las consecuencias de estas dos declaraciones son varias: cuando declaramos una tabla estamos reservando memoria para dicha tabla, y si la inicializamos estamos copiando los valores asignados sobre la tabla creada. Eso supone que pueden modicarse los valores de los elementos de la tabla. Cuando declaramos un puntero y realizamos una asignacin, estamos copiando una direccin, y por lo tanto no podemos modicar la cadena (puesto que es una constante). El problema se agrava en tanto que el error no se detecta en tiempo de compilacin (es sintcticamente correcto) sino de ejecucin.
por otra parte, al declarar una tabla su nombre representa la direccin del primer elemento de la tabla, pero no es un puntero sino una constante, y por ello no puede modicarse.
Ejemplo: No podramos tener expresiones del tipo mensaje1++, aunque s sentencias del tipo mensaje2++,
puesto que ste s es un puntero.
9-8
20132014
En realidad, lo que se declara es una tabla cuyos elementos son a su vez tablas, y as sucesivamente hasta completar las dimensiones denidas.
Ejemplo: En la declaracin anterior se dene una tabla de 2 elementos, siendo cada uno de los elementos una tabla
de 3 elementos.
En la siguiente gura se recoge esa situacin:
3000
3004
3008
300c
3010
3014
3018
301c
3020
3024
tab[0][0]
tab[0][1] tab[0]
tab[0][2] tab
tab[1][0]
tab[1][1] tab[1]
tab[1][2]
tab[0][0] representa el primer elemento de la tabla bidimenint; tab[0] representa la direccin de comienzo de dicho elemento, por lo que ser un int; y tab representa la direccin de comienzo de tab[0], que es un puntero a una tabla de 3 tab[0] + 1 apuntar el elemento tab[0], que es tab[0][1], mientras que tab + 1 apuntar al siguiente elemento tab, que es tab[1]. tab[0] + 3
apuntar al tercer elemento a partir del que es
En consecuencia, y siguiendo la aritmtica de punteros, tendremos que siguiente al que apunta al que apunta
tab[0],
tab[1][0].
Aplicando indistintamente el operador de ndice o el operador de contenido y la aritmtica de punteros, podemos decir que:
20132014
9-9
Para saber de qu tipo es una expresin en la que se utiliza una tabla multidimensional, actuaremos de la misma forma que hicimos en el caso de varios punteros: en la declaracin de la tablas, eliminamos la parte que aparece en una expresin; lo que queda es el tipo que representa.
y nos encontramos con la expresin notas[alumno][asignatura], si la marcamos en la declaracin (considerando el nmero de [] que estamos utilizando):
int notas[ALUMNOS][ASIGNATURAS] [PARCIALES];
podemos ver que el tipo de la expresin resultante es una tabla de int de PARCIALES elementos.
Podemos ver que hemos inicializado una tabla de 2 elementos, donde cada elemento es una tabla de 3 elementos.
Las tablas multidimensionales en C tienen una estructura lineal, por lo que dichas tablas tambin pueden inicializarse utilzando un nico grupo de valores separados por comas y encerrados entre llaves. En este caso la asignacin se realizar de forma secuencial.
Al igual que sucede en las tablas de una dimensin, al inicializar una tabla multidimensional podemos eliminar uno de los ndices, siempre el ms externo (el que aparece antes en la declaracin). La dimensin de ese ndice ms externo se tomar del nmero de elementos que aparezcan en la denicin.
Es importante recordar que al inicializar una tabla puede suprimirse el primero de los ndices, y slo se.
9-10
20132014
estaremos deniendo una tabla de MAX elementos, en la que cada elemento es de tipo int *.
Ejemplo: El siguiente programa calcula la media de los alumnos en una funin aparte, a la que se le pasa la tabla
#include <stdio.h> #define NUMALUMNOS 10 float calculaMedia(float listaAlumnos[], int numAlumnos); int main() { int i; float notas[NUMALUMNOS]; for ( i = 0; i < NUMALUMNOS; i++ ) { printf("\nNota de alumno: %d: ", i + 1); scanf(" %f", ¬as[i]); } printf("\nLa media es %f\n", calculaMedia(notas, NUMALUMNOS)); return 0; }
20132014
9-11
float media = 0; for ( i = 0; i < numAlumnos; i++ ) media += listaAlumnos[i]; media /= numAlumnos; return media;
#include <stdio.h> #define NUMALUMNOS 10 float calculaMedia(float listaAlumnos[], int numAlumnos); int main() { int i; float notas[NUMALUMNOS]; for ( i = 0; i < NUMALUMNOS; i++ ) { printf("\nNota de alumno: %d: ", i + 1); scanf(" %f", ¬as[i]); } printf("\nLa media es %f\n", calculaMedia(notas, NUMALUMNOS)); return 0; }
float calculaMedia(float * listaAlumnos, int numAlumnos) { int i; float media = 0; for ( i = 0; i < numAlumnos; i++ ) media += listaAlumnos[i]; media /= numAlumnos; return media;
No obstante, es preferible utilizar tanto en la denicin como en la declaracin la notacin de tabla, porque explcitamente nos indica que el parmetro que se pasa es la direccin de una tabla, y no un simple puntero.
9-12
20132014
eliminar la primera dimensin (y nicamente la primera), utilizando en este caso la notacin de punteros. En este caso, es imprescindible encerrar entre parntesis el operador de contenido (*) y el nombre de la tabla, porque lo que se pasa como parmetro sera un puntero a tabla, y no una tabla de punteros. En cualquiera de los dos casos ser necesario tambin pasar como parmetros los tamaos de cada una de las dimensiones de la tabla multidimensional, para poder trabajar con ellas en el cuerpo de la funcin. Tambin se puede pasar como parmetro de una funcin una subtabla derivada de una tabla de ms dimensones; en este caso tendremos que aplicar los criterios correspondientes a la tabla resultante (si es de una o de ms dimensiones).
#include <stdio.h> #define NUMALUMNOS 10 #define ASIGNATURAS 2 float calculaMediaAlumno(float listaNotas[], int asignaturas); float calculaMedia(float listaAlumnos[][ASIGNATURAS], int numAlumnos, int asignaturas); float calculaMaxima(float (* listaAlumnos)[ASIGNATURAS], int numAlumnos, int asignaturas); int main() { int i; int j; float notas[NUMALUMNOS][ASIGNATURAS]; for ( i = 0; i < NUMALUMNOS; i++ ) for ( j = 0; j < ASIGNATURAS; j++ ) { printf("\nNota de alumno: %d para asignatura %d: ", i + 1, j + 1); scanf(" %f", ¬as[i][j]); } // Calculamos la media para cada uno de los alumnos for ( i = 0; i < NUMALUMNOS; i++ ) printf("La media del alumno %d es %f\n", i + 1, calculaMediaAlumno(notas[i], ASIGNATURAS)); // Calculamos la media de todo el curso printf("La media del curso es %f\n", calculaMedia(notas, NUMALUMNOS, ASIGNATURAS)); // Calculamos la nota maxima del curso printf("La nota maxima del curso es %f\n", calculaMaxima(notas, NUMALUMNOS, ASIGNATURAS)); return 0; }
float calculaMediaAlumno(float listaNotas[], int asignaturas) { int j; float media = 0; for ( j = 0; j < asignaturas; j++ ) media += listaNotas[j];
20132014
9-13
float calculaMedia(float listaAlumnos[][ASIGNATURAS], int numAlumnos, int asignaturas) { int i; int j; float media = 0; for ( i = 0; i < numAlumnos; i++ ) for ( j = 0; j < asignaturas; j++ ) media += listaAlumnos[i][j]; media /= (numAlumnos * asignaturas); return media; }
float calculaMaxima(float (* listaAlumnos)[ASIGNATURAS], int numAlumnos, int asignaturas) { int i; int j; float maximo = 0; for ( i = 0; i < numAlumnos; i++ ) for ( j = 0; j < asignaturas; j++ ) if (listaAlumnos[i][j] > maximo) maximo = listaAlumnos[i][j]; return maximo;
podemos observar los tres casos: la funcin calculaMedia es un ejemplo del primer procedimientos de pasar tablas multidimensionales como parmetros. la funcin calculaMaxima sera un ejemplo del segundo caso. Puede observarse que el nombre de la tabla y el operador de contenido se han encerrado entre parntesis para que el tipo del parmetro sea puntero a tabla, en lugar de tabla de punteros. en la funcin calculaMediaAlumno se pasa como parmetro una subtabla de la tabla notas; en concreto, las calicaciones de las asignaturas de un alumno determinado.
main.
Hasta ahora, se ha utilizado dicha funcin como si fuera una funcin sin parmetros que devuelve un entero. En realidad, la funcin
main
9-14
20132014
Los identicadores de los parmetros formales pueden ser cualesquiera, como es habitual, pero suelen utilizarse siempre
argc
para el primero y
argv
para el segundo.
main
realizar varias ejecuciones con diferentes valores sin que sea necesario volver a compilar el programa. El signicado de los parmetros es el siguiente: El primero,
argc,
van separados entre s por espacios. Si se desea que algn elemento incluya un espacio en blanco, ser necesario encerrarlo entre comillas (simples o dobles). el segundo,
argv
orden, a uno de los elementos de la lnea de comandos. El ltimo elemento de argv siempre apunta a
NULL,
El comando en s mismo forma parte de la lnea de comandos, por lo que entrar en la cuenta de elementos, y ser el primer elemento de la tabla de punteros.
Ejemplo: El siguiente programa escribe en la salida estndar el nmero de elementos en la lnea de comandos, y a
#include <stdio.h> int main(int argc, char *argv[]) { int i; printf("El numero de elementos es %d\n", argc); printf("Los elementos son:\n"); for ( i = 0; i < argc; i++) printf("\t %s\n", argv[i]); return 0;
Tema 10
Tipos Agregados
ndice
10.1. Estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-1
10.1.1. 10.1.2. 10.1.3. 10.1.4. 10.1.5. 10.1.6. 10.1.7. 10.1.8. 10.1.9. Declaracin de Estructuras y Variables . . . . Inicializacin de Estructuras . . . . . . . . . . Punteros a Estructuras . . . . . . . . . . . . Acceso a los campos de una estructura . . . . Estructuras como Campos de Estructuras . . El Operador de Asignacin en las Estructuras Tablas de Estructuras . . . . . . . . . . . . . Estructuras como Argumentos de Funciones . Estructuras como Resultado de una Funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-1 10-3 10-4 10-4 10-5 10-6 10-7 10-8 10-8
10.3. Campos de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-9 10.4. El tipo Enumerado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-11 10.5. La Denicin de Nuevos Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-12
10.5.1. Uso del typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-13 10.4.1. Declaracin de Enumerados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-11 10.4.2. Declaracin de Variables de Tipo Enumerado . . . . . . . . . . . . . . . . . . . . . 10-12
10.1. Estructuras
Una estructura es un conjunto nito de elementos de para hacer ms eciente e intuitivo su manejo. A cada elemento de la estructura se le denomina Al contrario de lo que sucede con las tablas,
cualquier tipo
10-1
10-2
20132014
la declaracin de la estructura, que consiste en enumerar los diferentes campos que la componen y asignarles un nombre. la declaracin de variables que respondan a esta estructura. El lenguaje nos permite completar cada uno de los componentes por separado o de forma conjunta.
struct,
estructura, seguido de la declaracin de cada uno de los campos encerrados entre llaves.
struct persona { char nombre[10]; int edad; float altura; int sexo; };
Estamos declarando una estructura que tiene cuatro campos: el primero es una tabla de 10 char, el segundo un int, el tercero un float y el cuarto otro int. A dicha estructura la hemos denominado persona. Y no hemos denido ninguna variable de este tipo.
Debe recordarse que al declarar una estructura slo estamos describiendo la composicin de la misma, y no estamos declarando ninguna variable, por lo que no estamos reservando ninguna memoria. A partir de su declaracin, podemos referirnos a un determinado tipo de estructura mediante la secuencia
struct <nombre>,
declaracin.
Es muy importante que los identicadores de los diferentes campos que componen una estructura tengan signicado, de forma que su simple lectura nos indique qu esta almacenando ese campo. Si no se hace as, el manejo de estructuras dentro de un programa medianamente complejo se hace imposible. La visibilidad de la declaracin de una estructura sigue las mismas reglas que la visibilidad de las variables. Por tanto, si declaramos una estructura dentro de una funcin, slo podremos utilizar dicho tipo de estructura en esa funcin.
20132014
10-3
struct persona { char nombre[10]; int edad; float altura; int sexo; } alumno;
en una nica sentencia estamos declarando la estructura de nombre persona y adems estamos declarando una variable de dicho tipo y de nombre alumno.
Si la estructura se va a utilizar en un nico punto del programa (lo cual es muy improbable), puede eliminarse el identicador de la estructura, deniendo nicamente su composicin (los campos que la componen) y declarando las variables necesarias de dicho tipo.
struct { char nombre[10]; int edad; float altura; int sexo; } alumno;
Esta declaracin, como queda dicho, nos imposibilita utilizar esta estructura en ningn otro punto del programa (no tenemos cmo identicarla), por lo que slo es de utilidad en casos en los que ya no vaya a hacerse ninguna otra referencia a la estructura.
persona se pondra:
Lgicamente, los tipos de los valores deben coincidir uno a uno con los tipos de los campos de la estructura. Si no hubieran sucientes valores para todos los campos, se asignarn los que haya a los primeros campos, y el resto se inicializar a cero.
10-4
20132014
comas, la secuencia de asignaciones, consistente cada asignacin en un punto, seguido por el identicador del campo, el operador de asignacin y el valor a asignar a dicho campo.
Ejemplo: Para la estructura anterior, slo quisiramos inicializar el nombre y la altura, podra ponerse:
struct persona alumno = { .altura = 1.8, .nombre = "Pablo" };
NUNCA
apunta, por lo que para poder utilizarlo ser preciso que apunte a una zona previamente reservada.
y el operador
->.
El primero se utiliza para acceder a los campos cuando tenemos una estructura, mientras que el segundo se utiliza cuando tenemos un puntero a estructura. En ambos casos, la parte izquierda del operador debe ser una expresin del tipo adecuado (estructura o puntero a estructura, respectivamente), y la parte derecha el nombre de uno de los campos de la estructura.
Ejemplo: El siguiente cdigo muestra un ejemplo de acceso a los campos de una estructura:
#include <stdio.h> struct persona { char nombre[10]; int edad; float altura; int sexo; }; int main() { struct persona alumno = { "Pablo", 21, 1.8, 0}; struct persona * pAlumno = &alumno; printf("El alumno se llama %s, y tiene %d anios de edad\n", alumno.nombre, pAlumno->edad); return 0;
Los dos operadores de acceso a los campos de una estructura tienen la misma prioridad y asociatividad que el operador
[],
20132014
10-5
Ejemplo: El siguiente fragmento de cdigo declara primero una estructura de tipo struct
fecha. A continuacin declara una estructura de tipo struct instante, uno de cuyos campos es del tipo estructura denido con anterioridad. Por ltimo, se declara e inicializa una variable de este ltimo tipo de estructura, dando valores para cada uno de los campos, incluyendo el de tipo estructura:
struct fecha { int dia; int mes; int ano; char nombre_mes[4]; char dia_semana[4]; }; struct instante { int seg; int min; int hora; struct fecha fec; }; struct instante ahora = { 0,30,23, {1,12,2006,"dic","vie"}};
Para acceder al nmero de mes, tendremos primero que aplicar el operador . a la variable ahora, para obtener el campo fec. Como dicho campo vuelve a ser una estructura, podremos volver a utilizar el operador . para obtener el campo mes, obteniendo as el valor deseado:
ahora.fec.mes
Ejemplo: El siguiente fragmento de cdigo es similar al anterior, pero el campo de la estructura instante no es
struct fecha { int dia; int mes; int ano; char nombre_mes[4]; char dia_semana[4]; }; struct fecha hoy = {1, 12, 2006, "dic", "vie"}; struct instante { int seg; int min; int hora; struct fecha * fec; }; struct instante ahora = { 0, 30, 23, &hoy};
Si en este ejemplo queremos acceder al nmero de mes, tendremos primero que aplicar el operador . a la variable ahora, para obtener el campo fec. Como dicho campo es un puntero a una estructura, tendremos que utilizar el operador -> para obtener el campo mes, obteniendo as el valor deseado:
10-6
ahora.fec->mes
20132014
Como los operadores . y -> son de igual prioridad, y se evalan de izquierda a derecha, en este caso tampoco son necesarios los parntesis.
Un caso particular de esta ltima construccin lo constituyen las estructuras que tienen como uno de sus campos un puntero a una estructura del mismo tipo, que se denominan estructuras autoreferenciadas.
Ejemplo: La siguiente declaracin es la de una estructura uno de cuyos campos es un puntero a la propia estructura:
struct fecha { int int int char char struct fecha * }; dia; mes; ano; nombre_mes[4]; dia_semana[4]; siguiente;
Estas construcciones son especialmente tiles para ls denicin de estructuras dinmicas de datos (que se vern en otra asignatura). Sin embargo, no es posible que uno de los campos de una estructura sea una estructura del mismo tipo. Intuye por qu?.
#include <stdio.h> int main() { struct persona { char nombre[10]; int edad; float altura; int sexo; } alumno = { .nombre = "Pablo", .altura = 1.8 }; struct persona alumno2 = alumno; alumno2.nombre[0] = T; printf("El alumno %s mide %f\n", alumno.nombre, alumno.altura); printf("El alumno %s mide %f\n", alumno2.nombre, alumno2.altura); return 0;
se tiene:
salas@318CDCr12:$ gcc -W -Wall asignacion1.c salas@318CDCr12:$ a.out El alumno Pablo mide 1.800000
20132014
10-7
Hay que tener especial cuidado si se utilizan punteros como campos de la estructura. En ese caso, lo que se copia son los punteros, no los valores apuntados por stos. Eso signica que, despus de la asignacin, los punteros de las dos estructuras tienen el mismo valor, lo que signica que apuntan al mismo sitio. Por lo tanto, si modicamos el contenido del valor apuntado, el efecto se ver en las dos estructuras.
#include <stdio.h> int main() { char cadena[10] = "Pablo"; struct persona { char * nombre; int edad; float altura; int sexo; } alumno = { .nombre = cadena, .altura = 1.8 }; struct persona alumno2 = alumno; alumno2.nombre[0] = T; printf("El alumno %s mide %f\n", alumno.nombre, alumno.altura); printf("El alumno %s mide %f\n", alumno2.nombre, alumno2.altura); return 0;
se tiene:
salas@318CDCr12:$ gcc -W -Wall asignacion2.c salas@318CDCr12:$ a.out El alumno Tablo mide 1.800000 El alumno Tablo mide 1.800000 salas@318CDCr12:$
#define TAM 40 #define NUMLIBROS 10 struct biblio { char titulo[TAM]; char autor[TAM]; float precio; };
10-8
20132014
estamos declarando una tabla de NUMLIBROS elementos, siendo cada uno de los elementos una estructura de tipo struct biblio.
Para acceder a un campo de un elemento tendremos primero que seleccionar uno de los elementos de la tabla, utilizando el operador utilizar el operador
[].
struct biblio,
para acceder a uno de sus campos. Si el campo seleccionado es una tabla, podremos
adems seleccinar uno de los elementos de la tabla, sin ms que volver a utilizar el operador
[].
Ejemplo: As, si queremos el tercer carcter del autor del primer libro, tendremos que poner:
tab_libro[0].autor[2]
[]
.,
10.2. Uniones
Las uniones son tipos agregados en C, del estilo de las estructuras, en las que todos los campos comienzan en la misma posicin de memoria. Se utilizan para considerar que una misma zona de memoria puede verse como de diferentes tipos, dependiendo del momento.
struct
por
union.
#define MAX 128 /* Caso 1 */ union registro { int entero; float real; char cadena[MAX]; }; /* Caso 2 */ union registro variable; /* Caso 3 */
20132014
10-9
En el primer caso estamos declarando una unin, en el segundo estamos declarando una variable de un tipo unin declarado anteriormente, y en el tercero estamos declarando la composicin de una unin y una variable de dicho tipo simultneamente.
..
#define MAX 128 union registro { int entero; float real; char cadena[MAX]; }; union registro variable1 = { 123 }; union registro variable2 = { .real = 0.123 };
->
struct nombre_campo { tipo nombre_1 : longitud_1; tipo nombre_2 : longitud_2; ... tipo nombre_n : longitud_n; }
_Bool, signed int y unsigned int (aunque unsigned int, sobre todo
algunos compiladores puede permitir otros). Lo ms habitual es usar el tipo utilizado en bits.
cuando la longitud es 1. La longitud es un nmero no negativo que como mximo ser la longitud del tipo
10-10
20132014
El compilador empaquetar todos los campos de la estructura lo ms compacto posible. As, una misma palabra de memoria puede contener varios campos siempre que estn todos completos y sean del mismo tipo. Si no ponemos el nombre a un campo de bits, no podremos utilizarlo posteriormente pero el compilador reservar espacio para l. Adems, si la longitud de un campo sin nombre es 0, se le est indicando al compilador que el siguiente campo de bits debe empezar en una palabra nueva. La manera en que se disponen los distintos campos dentro de la palabra (derecha e izquierda) es dependiente de la mquina y el compilador. El acceso a cada uno de los campos de bits se hace igual que al del resto de campos de una estructura o unin, y se pueden utilizar en operaciones aritmticas. No es posible crear tablas de campos de bits ni tampoco obtener la direccin de memoria. Por tanto, el operador
&
Los principales motivos de uso son: Si el almacenamiento es limitado, se pueden almacenar varias variables booleanas en un octeto. Ciertas interfaces de dispositivo transmiten informacin que se codica en bits dentro de un octeto. Ciertas rutinas de encriptacin necesitan acceder a los bits en un octeto.
struct bits { unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int }; bit0:1; bit1:1; bit2:1; bit3:1; bit4:1; bit5:1; bit6:1; bit7:1;
se vera en memoria de la siguiente forma: 15 14 13 12 11 10 9 <no usado> mientras que la siguiente declaracin:
struct bits2 { unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int }; campo1:3; campo2:4; : 0; campo3:2; : 4; campo4:2;
7 bit7
6 bit6
5 bit5
4 bit4
3 bit3
2 bit2
1 bit1
0 bit0
7 7
6 6 campo4
5 5
4 campo2
1 0 campo1 1 0 campo3
4 3 2 <no usado>
20132014
10-11
La principal desventaja es que el cdigo resultante puede no ser portable a otras mquinas o compiladores, ya que depende del tamao de cada tipo del lenguaje, que como ya se sabe no viene determinado por el estndar. Adems el cdigo resultante puede ser ms lento que si se hiciera con operaciones binarias. Una alternativa recomendada para disponer de variables de un bit (semforos), es el uso de
define.
#define #define #define #define #define #define #define #define #define Nada bitUno bitDos bitTres bitCuatro bitCinco bitSeis bitSiete bitOcho 0x00 0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80
// si primer bit ON... // pone bit dos ON // pone bit tres OFF
enum.
Dicho tipo nos permite denir un conjunto de valores que representarn algn parmetro cuyos valores sean discretos y nitos.
enum,
Ejemplo: La siguiente declaracin servira para declarar un tipo de datos nuevo, enum
enum meses {ene, feb, mar, abr, may, jun, jul, ago, sep, oct, nov, dic };
Ntese que lo que utilizamos son identicadores, no cadenas de caracteres. Internamente, el C convierte cada uno de estos identicadores en nmeros enteros, empezando por el
0,
10-12
20132014
ene
0,
feb
el valor
1,
. . . , y al identicador
dic
el valor
11.
El lenguaje permite modicar esta asignacin de valores, sin ms que asignarle un valor a un identicador. En ese caso, al identicador designado se le asigna el valor indicado, y a los sucesivos identicadores se le asignan los sucesivos valores.
al identicador ene se le habr asignado el valor 1, al identicador feb el valor 2, . . . , y al identicador dic el valor 12.
Esa asignacin de valor forzada se puede realizar en cualquier identicador, no slo en el primero, y tantas veces como se desee, incluso repitiendo valores.
int, por lo que le son aplicables todas las reglas y operaciones que
int.
Adems, los posibles valores de estas variables no estn restringidos a los denidos por el tipo enumerado: es vlido cualquier valor entero.
Ejemplo: El siguiente programa muestra que las variables de tipo enumerado no restringen sus valores a los identi
#include <stdio.h> int main() { enum dias { lun, mar, mie, jue, vie, sab, dom }; enum dias diasSemana = lun; printf("El diasSemana printf("El diasSemana printf("El return 0; dia de la semana es %d\n", diaSemana); += dom; dia de la semana es %d\n", diaSemana); += dom; dia de la semana es %d\n", diaSemana);
20132014
10-13
utilizacin (aunque es cierto que a veces ocultando informacin que puede resultar de ayuda a la hora de programar). Estas deniciones se hacen con la clusula pueda aplicarse al tipo original.
typedef.
sinnimo de un tipo ya existente, no un nuevo tipo. Por tanto, a este tipo le sern de aplicacin todo lo que
typedef
lista notas;
se dene un nuevo tipo, denominado lista que equivale a una tabla de MAX elementos de tipo int. En la ltima sentencia estamos declarando una variable de dicho tipo lista, por tanto la variable notas ser una tabla de MAX elementos de tipo entero, y como tal podramos tener expresiones como notas + 1 o notas[3].
10-14
20132014
Tema 11
Funciones de Biblioteca
ndice
11.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-1 11.2. Reserva dinmica de memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-2
11.2.1. 11.2.2. 11.2.3. 11.2.4. Funcin de reserva malloc . . . . . . . . . . . . . . Funcin de reserva calloc . . . . . . . . . . . . . . Funcin de liberacin free . . . . . . . . . . . . . . La reserva dinmica y las tablas multidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-2 11-3 11-3 11-4
11.1. Introduccin
Uno de los principales objetivos de la programacin modular es la reutilizacin de cdigo: alguien escribe alguna funcin, de forma que cualquiera pueda utilizarla en su cdigo. En el estndar C se establecen una serie de funciones, denominadas de biblioteca, que cualquier compilador debe incorporar, de forma que cualquier programa que utilice dichas funciones pueda ser compilado sin errores en cualquier mquina. En este tema se estudiarn algunas de estas funciones de biblioteca. Para obtener una mayor informacin sobre cualquier funcin de biblioteca del estndar C utilice el comando
man.
include
11-1
11-2
20132014
funcin de biblioteca en cuestin, y que deber incluir en todos los archivos que vayan a utilizar dicha funcin.
tamanio,
NULL
Hay tres aspectos importantes a resear en esta funcin. El primero es cmo calcular la memoria que necesitamos reservar. Eso depender de para qu queremos reservar la memoria, pero en cualquier caso siempre ser de utilidad el operador si queremos reservar memoria para una estructura de tipo reservar ser enteros de
sizeof.
struct generic,
sizeof(struct generic), mientras que si queremos reservar memoria tam elementos, la memoria a reservar ser de tam * sizeof(int).
El segundo aspecto a considerar es que devuelve un puntero de tipo genrico. La funcin ser la zona de memoria devuelta. Por eso es imprescindible que el valor devuelto por tendramos: en el primer caso el puntero devuelto lo sera a una estructura de tipo la llamada correcta a la funcin
malloc
se utiliza
para reservar memoria para cualquier tipo de datos, por lo que a priori no puede saber de qu tipo va a
malloc
lo asociemos
al tipo adecuado, mediante el operador de conversin forzada. Siguiendo con los dos ejemplos anteriores,
malloc
sera:
generic))
en el segundo ejemplo devolvera memoria reservada para una tabla de enteros, por lo que la llamada correcta sera:
Puesto que para poder utilizar una variable necesitamos espacio reservado para la misma, SIEMPRE hay que comprobar que se ha podido realizar la reserva comprobando el valor devuelto por la funcin Una llamada tpica a la funcin
malloc.
malloc
struct generic * paux = NULL; paux = (struct generic *) malloc(sizeof(struct generic)); if (NULL == paux) /* Codigo que gestiona el error de falta de memoria */ else /* Codigo normal de la funcion */
1 size_t
20132014
11-3
parmetro, y cuando queramos reservar memoria para una tabla necesitamos realizar la multiplicacin
calloc
malloc, calloc
devuelve
NULL
que SIEMPRE hay que comprobar que se ha podido realizar la reserva comprobando el valor devuelto por
calloc. calloc
sera por tanto de la siguiente forma (suponga que
tam
es un
int * paux = NULL; paux = (int if (NULL == /* Codigo else /* Codigo *) calloc(tam, sizeof(int)); paux) que gestiona el error de falta de memoria */ normal de la funcion */
malloc es adecuada cuando queremos reservar memoria para calloc lo es cuando queremos reservar
malloc
calloc
llamada. Lgicamente, esa zona de memoria debemos reservarla nica y exclusivamente durante el tiempo que sea necesario, de forma que cuando ya no la necesitemos pueda ser utilizada por otro programa. Por lo tanto, igual que existen funciones para reservar memoria, existe una funcin para liberarla: la funcin
free. void free(void * puntero); La funcin free libera la zona de memoria apuntada por puntero, que previamente ha debido ser reservada con malloc o calloc. Una vez realizada la llamada a free, la zona de memoria apuntada por puntero NO PUEDE volver a utilizarse. Si puntero vale NULL, la llamada a la funcin no tiene ningn efecto.
Aunque es evidente, no est de ms sealar que el valor de
puntero
free,
Como norma general, y para no mantener sin liberar ninguna zona de memoria, en los programas debe exitir una llamada a Si llamamos a
free free
malloc
calloc
existente.
malloc
calloc,
o con
un valor que haya sido liberado con anterioridad, el sistema de gestin de memoria dinmica del sistema operativo puede corromperse; este problema es tanto ms grave cuanto que sus efectos no se hacen visibles inmediatamente, sino transcurrido un tiempo variable, lo que hace muy difcil su localizacin. Por ello es conveniente que asignemos el valor llamada a
NULL
free
11-4
20132014
posible utilizacin de un puntero ya liberado, puesto que estaramos utilizando un puntero igualado a y la ejecucin del programa se abortara en ese punto exacto, pudiendo corregirlo. Una llamada tpica a la funcin valor devuelto por
NULL
malloc
paux
almacena un
nfilas
las y
int de dos ncolumnas columnas. Por lo visto en el tema de tablas, una tabla de esas sucesin de nfilas * ncolumnas ints, por lo que haramos la reserva de
int * pTabla = NULL; pTabla = (int *) calloc(nfilas * ncolumnas, sizeof(int)); if (NULL == pTabla) /* Codigo que gestiona el error de falta de memoria */ else /* Codigo normal de la funcion */
int)
se har calculando
El acceso a los elementos de la tabla (que como puede observarse es una tabla de columna
previamente la posicin del elemento dentro de esa tabla; as, para acceder al elemento de la la
fila
columna,
escribiremos:
El acceso a esta tabla no podra hacerse mediante la utilizacin de doble ndice puesto que no se conoca el nmero de columnas en tiempo de compilacin. Existe un segundo procedimiento para la reserva dinmica de tablas multidimensionales; este segundo mtodo complica la reserva de memoria, pero simplica el acceso a los elementos. Consiste en considerar la tabla multidimensional no como una tabla de tablas, sino como una tabla de punteros a tablas, segn se recoge en la siguiente gura:
20132014
int ** reservaTabla(int nfilas, int ncolumnas) { int ** pTabla = NULL; int i = 0; int j = 0; /* Reservamos memoria para la tabla de punteros a tabla */ pTabla = (int **) calloc(nfilas, sizeof(int *)); for ( i = 0; (NULL != pTabla) && (i < nfilas); i++ ) (1) { pTabla[i] = (int *) calloc(ncolumnas, sizeof(int)); if (NULL == pTabla[i]) { /* Si tenemos problemas en la reserva de un elemento, devolvemos la memoria previamente reservada ... */ for ( j = i - 1; j >= 0; j-- ) (2) free(pTabla[j]); (3) /* ... incluyendo la tabla de punteros original */ free(pTabla); pTabla = NULL; (4) } } return pTabla;
11-5
if
comprobando que se ha podido reservar memoria est implcito en la condicin del
El
for:
si ha
habido un error
pTabla
valdr
NULL
y no se entrar en el bucle.
La ltima reserva que hicimos correctamente fue la correspondiente al elemento anterior al actual; desde ese elemento (hacia atrs) tendremos que realizar la liberacin de memoria. En este caso no es necesario igualar Al igualar
pTabla[j]
NULL
pTabla
NULL,
gernos contra errores en ejecucin, evitamos que vuelva a entrar en el bucle que tiene que ser devuelto por la funcin en caso de error.
for
y asignamos el valor
Cuando reservamos memoria para una tabla de esta forma, obtenemos un puntero a puntero a sabemos podemos tratar como el nombre de una tabla de punteros a
int, que como int; si lo indexamos (pTabla[i], por ejemplo), lo que obtenemos es un puntero a int, que podemos tratar como el nombre de una tabla de int; si lo volvemos a indexar (pTabla[i][j], por ejemplo) obtendremos un int. Es decir, con este mtodo
de reserva de memoria dinmica para tablas multidimensionales podemos utilizar la indexacin mltiple sin necesidad de conocer el valor mximo de ninguna de las componentes en tiempo de compilacin.
11-6
20132014
binarios: se trabaja con una secuencia de octetos que representan zonas de memoria. Cuando se trabaja con archivos el primer paso es establecer un rea de ber, donde la informacin se almacena temporalmente mientras se est transriendo entre la memoria del ordenador y el archivo de datos. Este rea de ber (tambin llamada
ujo
stream)
permite leer y escribir informacin del archivo ms rpidamente de lo que sera posible de otra manera. Para poder intercambiar informacin entre el rea de buer y la aplicacin que utiliza las funciones de biblioteca, se utiliza un puntero a dicha zona de buer. En la siguiente declaracin:
FILE *ptvar; FILE (se requieren letras maysculas) es ptvar es la variable puntero que indica
un tipo especial de estructura que establece el rea de ber, y el principio de este rea. El tipo de estructura
stdio.h,
descriptor de chero.
FILE,
que est
Las operaciones de entrada y salida en cheros se realiza a travs de funciones de la biblioteca estndar cuyos prototipos estn en
stdio.h,
Funcin fopen
FILE *fopen(const2 char *nombre, const char *modo); Esta funcin abre el archivo nombre y devuelve como resultado de la NULL si falla en el intento.
La cadena de caracteres funcin el descriptor de chero, o
modo
Modo
r w a
Operacin
lectura escritura escritura
Si existe ...
lo abre y se posiciona al principio borra el antiguo y crea uno nuevo lo abre y se posiciona al nal
Si no existe ...
error lo crea lo crea
Funcin fclose
int fclose(FILE *fp);
Esta funcin cierra el archivo referenciado por error y
fp.
EOF
si ocurre cualquier
en caso contrario.
Antes de terminar cualquier programa, debe asegurarse de que ha cerrado todos los archivos que haya abierto previamente.
calicador de tipo const indica que el valor del parmetro no se modica dentro de la funcin llamada.
20132014
11-7
fp
como un
int,
EOF
si se encontr el n
Funcin fgets
char * fgets(char *s, int n, FILE *fp); Esta funcin lee hasta n-1 caracteres (a partir del ltimo carcter ledo de fp) en la cadena s, detenindose antes si encuentra nueva lnea o n de chero. Si se lee el carcter de nueva lnea se incluye en la cadena s. La cadena s se termina con \0. La funcin devuelve s, o NULL si se encuentra n de chero o se produce
un error.
Funcin fputc
int fputc(int c, FILE *fp); Esta funcin escribe el carcter c (convertido a unsigned char) a continuacin del ltimo caracter escrito en fp. Devuelve el carcter escrito, o EOF en caso de error.
Funcin fputs
int fputs (const char *s, FILE *fp);
Esta funcin escribe la cadena s (no necesita que contenga Devuelve un nmero no negativo si no ha habido problemas, o
en
\0)
en
fp.
#include <stdio.h> int main(int argc, char *argv[]) { FILE * fent = NULL; FILE * fsal = NULL; int caracter; if (argc != 3) puts("Se necesitan dos parametros: origen y destino\n"); else { if ((fent = fopen(argv[1], "r")) == NULL) puts("No se puede abrir el fichero origen\n"); else { if ((fsal = fopen(argv[2], "w")) == NULL) puts("No se puede abrir el fichero destino\n"); else { // Leemos cada caracter del fichero de entrada ... while ((caracter = fgetc(fent)) != EOF) // ... y lo escribimos en el fichero de salida. fputc(caracter, fsal); fclose(fsal); } fclose(fent); }
11-8
20132014
} return 0;
int fprintf(FILE *stream, const char *format, ...); Esta funcin escribe la cadena de formato format en stream. Devuelve
un valor negativo si se produce algn error. La cadena de formato contiene dos tipos de objetos: caracteres ordinarios (que se copian en el ujo de salida), y especicaciones de conversin (que provocan la conversin e impresin de los argumentos). Cada especicacin de conversin comienza con el carcter % y termina con un carcter de conversin. Los caracteres de conversin ms utilizados son los siguientes:
unsigned char.
u,o,X,x
para representar enteros sin signo en notacin decimal, octal (sin ceros al principio), hexadecimal
0X
0x
y con los
en minsculas), respectivamente.
d s
para representar enteros con signo en notacin decimal. para representar cadenas de caracteres; los caracteres de la cadena son impresos hasta que se alcanza un
\0 f,E,e
double,
nente
G,g
double;
es equivalente a la especicacin
E,e
si el exponente del
nmero expresado en notacin cientca es inferior a -4 o mayor o igual que la precisin (que por
en caso contrario.
1.
lugar de
antes).
Cada dato numrico es precedido por un signo (+ o son precedidos por el signo
espacio 0
Hace que se presenten ceros en lugar de espacios en blanco en el relleno de un campo. Se aplica slo a datos que estn ajustados a la derecha dentro de campos de longitud mayor que la del dato
20132014
11-9
y tipo
x,
0x,
e,
tipo
y tipo
g, g.
todos los nmeros en coma otante con un punto, aunque tengan un valor entero. Impide el truncamiento de los ceros de la derecha realizada por la conversin tipo
nmero
Un nmero que estipula un ancho mnimo de campo. El argumento convertido ser impreso
en un campo de por lo menos esta amplitud, y en una mayor si es necesario. Si el argumento convertido tiene menos caracteres que el ancho de campo, ser rellenado a la izquierda (o derecha, si se ha requerido justicacin a la izquierda) para completar el ancho de campo. El carcter de relleno normalmente es espacio, pero es
Un punto, que separa el ancho de campo (nmero previo) de la precisin (nmero posterior). Un nmero, la precisin, que estipula el nmero mximo de caracteres de una cadena que sern o
nmero e, E
impresos, o el nmero de dgitos que sern impresos despus del punto decimal para conversiones
f,
G,
o el nmero mnimo de
dgitos que sern impresos para un entero (sern agregados ceros al principio para completar el ancho de campo necesario). 2. Un modicador de tipo:
hh h l L
char
o o
indica que el argumento correspondiente va a ser impreso como indica que el argumento es
short
long
unsigned long.
o
ll
long long
long double.
format,
convertidos a travs de los argumentos, cada uno de los cuales debe ser un puntero. La funcin devuelve
EOF
si se llega al nal del archivo o se produce un error antes de la conversin; en cualquier otro caso devuelve el nmero de valores de entrada convertidos y asignados. La cadena de formato contiene especicaciones de conversin, que se utilizan para interpretar la entrada. La cadena de formato puede contener: espacios o tabuladores, que se ignoran. caracteres ordinarios (distintos de %), que se espera que coincidan con los siguientes caracteres que no son espacio en blanco del ujo de entrada. especicaciones de conversin, consistentes en %, un carcter optativo de supresin de asignacin nmero optativo que especica una amplitud mxima de campo, una amplitud del objetivo, y un carcter de conversin. Una especicacin de conversin determina la conversin del siguiente campo de entrada. Normalmente el resultado se sita en la variable apuntada por el argumento correspondiente, excepto en el caso que se indique supresin de asignacin con
*,
un
h, l
Un campo de entrada est denido por una cadena de caracteres diferentes del espacio en blanco; se extiende hasta el siguiente carcter de espacio en blanco o hasta que el ancho de campo, si est especicado, se haya agotado. Esto implica que
fscanf
leer ms all de los lmites de la lnea para encontrar su entrada, ya que las
nuevas lneas son espacios en blanco. Los caracteres de espacio en blanco son el blanco, tabulador, nueva lnea, retorno de carro, tabulador vertical y avance de lnea.
11-10
20132014
El carcter de conversin indica la interpretacin del campo de entrada. El argumento correspondiente debe ser un puntero. Los caracteres de conversin pueden ser los siguientes:
entero decimal. entero. El entero puede estar en octal (iniciado con entero octal (con o sin cero al principio). entero decimal sin signo.
0)
0x
0X).
0x
0X). 1.
No se agrega
c char *;
\0.
En este
caso se suprime el salto normal sobre los caracteres de espacio en blanco; para leer el siguiente carcter que no sea blanco, hay que usar %1s (lee un carcter y lo guarda como una cadena).
s char *;
cadena de caracteres que no es espacio en blanco (no entrecomillados). Apunta a una cadena de
\0
e, f, g float *;
una
float
es un signo optativo,
una cadena de nmeros posiblemente con un punto decimal, y un campo optativo de exponente con
n int *;
escribe en el argumento el nmero de caracteres consumidos o ledos hasta el momento por esta
11-11
stdin
stdout stderr
A partir de ese momento se puede utilizar estos identicadores (del tipo funciones vistas previamente. As, por ejemplo:
FILE *)
scanf
en vez de
Recuerde que cuando se trata de leer cadenas de caracteres se le pasa el nombre de la tabla.
Otro error relacionado con el uso de punteros es que el puntero utilizado no apunte a una zona de memoria rerservada. En denitiva, es necesario comprobar antes de utilizar una variable de tipo puntero si apunta a una zona de memoria vlida, y con el tamao suciente.
scanf no es perfecto
Otro posible error en el uso de scanf es suponer que siempre funcione como nosotros esperamos (cosa que en general no se debe suponer de ninguna funcin, ya sea de biblioteca o codicada por nosotros, y especialmente si estamos depurando el programa). Por ejemplo, edite y compile el siguiente cdigo:
11-12
20132014
fprintf(stdout, "Introduzca accion (D) y saldo\n"); scanf(" %c %f", &accion, &saldo); if (D == accion) saldo = saldo * 2; else saldo = saldo / 2.0; fprintf(stdout, "Accion : %c\n", accion); fprintf(stdout, "Saldo : %f\n", saldo); return(0);
salas@318CDCr12:$ a.out Introduzca accion (D) y saldo D 1000 Accion : D Saldo : 2000.000000 salas@318CDCr12:$
salas@318CDCr12:$ a.out Introduzca accion (D) y saldo D q123 Accion : D Saldo : -316884466973887584331540463616.000000 salas@318CDCr12:$
El programa dejar valores inesperados en la variable Este valor podr variar en distintas ejecuciones. Por lo tanto, habra que comprobar que
scanf
(o
existe la posibilidad de que las variables utilizadas en valor no esperado si no estaban iniciadas. Observe tambin que y tecleamos en vez
fscanf) devuelve tantos valores como se espera. Si no, scanf conserven los valores previos, o incluso cualquier
scanf se detiene en la lectura del nmero en el momento que encuentra un espacio en 1000 de un cero nal el carcter o :
scanf
salas@318CDCr12:$ a.out Introduzca accion (D) y saldo D 100o Accion : D Saldo : 200.000000 salas@318CDCr12:$
lee el nmero 100, y continua su ejecucin normalmente, ignorando el carcter 'o'.
scanf
. Comprubelo con el
siguiente programa, que deja un inocente espacio en blanco antes de terminar la cadena de formato:
20132014
#include <stdio.h> int main() { int precio; printf("Precio del libro?: "); scanf(" %d ", &precio); printf("\n %d \n ", precio); return 0;
11-13
scanf
}
lee el precio, y a continuacin se queda descartando los espacios en blanco (o tabuladores, caracteres
de nueva lnea, etc.). Parece que el programa est bloqueado. Para terminar la lectura del precio es necesario que se teclee algn carcter distinto de espacio. Para corregir este programa basta eliminar ese espacio en blanco nal. Esta propiedad de
scanf
se puede utilizar para leer datos con un formato conocido previamente. Por
dd/mm/yyyy:
#include <stdio.h> int main() { int dia; int mes; int year; printf("Fecha de nacimiento ? (dd/mm/yyyy): "); scanf(" %d/ %d/ %d", &dia, &mes, &year); printf("Fecha de nacimiento es %d- %d- %d \n", dia, mes, year); return 0;
scanf y fgets
Otro error en el uso de funcin
scanf
fgets:
fgets.
#include <stdio.h> #define MAX 25 int main() { int precio; char nombre[MAX]; printf("Precio del libro?: "); scanf(" %d", &precio); printf("Nombre del libro?: "); fgets(nombre, MAX-1, stdin); printf("\nNombre: %s Precio: %d euros\n ", nombre, precio); return 0 ;
11-14
20132014
salas@318CDCr12:$ Precio del libro?: Nombre del libro?: Nombre: salas@318CDCr12:$ Precio del libro?: Nombre del libro?: Nombre: El perfume salas@318CDCr12:$
El comportamiento observado se debe a lo siguiente: en la primera ejecucin, la sentencia del libro, y se detiene en el carcter de nueva lnea, sin leerlo. La funcin detiene su ejecucin, ignorando el resto de la entrada. En la segunda ejecucin, carcter de nueva lnea.
scanf la dej, lee el carcter nueva lnea, y como su condicin de nalizacin es leer un carcter nueva lnea scanf lee el precio y se detiene al detectar un separador (en este caso un espacio fgets contina leyendo donde scanf se qued, y no se detiene hasta llegar al
Para no depender de cmo el usuario introduzca los datos, una opcin es leer todos los caracteres de nueva lnea pendientes antes de lectura del ttulo:
fgets,
printf
scanf.
printf %f %f %Lf
scanf,
espacios en blanco separadores de campo. Suponga que desea leer el ttulo de un libro con no conoce cuantas palabras va a contener el ttulo del libro, ni el autor, etc. Para solucionar este 'problema' se utiliza la funcin lnea (y no un espacio genrico como hace Otro inconveniente de
scanf:
a priori
fgets,
scanf).
scanf
la cadena donde se almacena la entrada estndar. Ya se sabe que rebasar los lmites del espacio de memoria reservado conduce a errores de memoria, muchos de ellos de difcil localizacin. Por eso es tambin preferible usar
fgets,
fgets
\n
por el terminador
\0.
Otra solucin a la lectura de datos genrica es leer lneas de texto completas (de chero o de entrada estndar) con
fgets.
Una vez que tengamos una cadena de caracteres, podemos procesarla para determinar si
es correcta o no, qu formato tiene, etc. Incluso se pueden procesar con la funcin nes creadas por el programador.
sscanf
scanf
diferencia que lee los datos de una cadena que se le pasa como primer parmetro:
20132014
11-15
string.h,
y enunciaremos algunas
dest.
Rellena con
char * strncpy(char *dest, const char *orig, int n); Copia hasta n caracteres de la cadena orig en la cadena dest. Devuelve dest. tiene menos de n caracteres.
\0
si
orig
\0.
==.
int strcmp(char *orig, char *dest); Compara la cadena orig con la cadena dest. Devuelve un nmero negativo si orig un nmero positivo si orig es anterior a dest, o cero si las dos cadenas son iguales.
es anterior a
dest,
int strncmp(char *orig, char *dest, int n); Compara hasta n caracteres de la cadena orig con la cadena dest. Devuelve un nmero negativo si orig es anterior a dest, un nmero positivo si orig es anterior a dest, o cero si las dos cadenas son iguales.
en
s,
o
NULL
si no est presente.
en
s,
NULL
si no est presente.
strlen
devuelve la longitud efectiva de la cadena, sin contar el terminador. Luego para copiar
una cadena en otra hay que reservar un carcter ms que la longitud que devuelva
strlen.
Por otra parte, las funciones de copia y concatenacin de caracteres NO reservan espacio en memoria para las cadenas destino, y adems suponen que el espacio reservado es suciente.
11-16
20132014
stdlib.h
son:
int atoi (const char *s); Devuelve el valor de s convertido a int. double atof (const char *s); Devuelve el valor de s convertido a double.
Parte II
Anexos
11-17
Anexo 1
1.1. Introduccin
En este anexo se presenta el entorno en el que se realizarn las prcticas correspondientes a la asignatura de Fundamentos de la Programacin. Las prcticas se realizan en los equipos disponibles en el Centro de Clculo de la Escuela Tcnica Superior de Ingenieros, por lo que esta explicacin se centrar en el entorno que presentan dichos equipos.
UBUNTU.
Una vez seleccionada la opcin correcta, el sistema se cargar en tres fases: en la primera fase, se actualiza la imagen del disco con el S.O. seleccionado. Este proceso puede ser lento si la imagen no est disponible en la cach del disco, as que sea paciente. en la segunda fase se arranca el sistema operativo linux, que es el que se utilizar en el laboratorio. en la tercera y ltima fase se inicia el entorno de ventanas utilizado, que en el caso del laboratorio de programacin es Gnome.
1-1
1-2
20132014
1.3.1. El terminal
Todas las rdenes que debe realizar el sistema operativo se deben introducir por teclado. Para ello, es necesario que se utilice la aplicacin de ventanas utilizado. Para arrancar la aplicacin, bastar con pulsar sobre el icono de la barra de men segn se indica en la siguiente gura:
gnome-terminal,
20132014
1-3
Cuando se inicia dicha aplicacin, aparece el prompt, indicando que el procesador de rdenes est preparado para ejecutarlas. En el entorno del centro de clculo, el prompt es:
salas@<sala>CDCr<num>:<directorio>$
siendo y
Ejemplo: Si nos encontramos en el directorio /var/lib, utilizando el ordenador nmero 12 de la sala 318 del Centro
de Clculo, el prompt sera:
salas@318CDCr12:/var/lib$
La siguiente gura presenta el entorno una vez arrancado el terminal, y a la espera de recibir las rdenes:
<sala> el nmero de la sala en la que est el equipo, <num> el nmero del equipo dentro de la sala, <directorio> el directorio en el que se est trabajando.
Cuando se teclea una orden, esta ir apareciendo en el terminal, a continuacin del prompt. Durante la escritura se podr editar la lnea, corrigiendo lo que se considere necesario. Una vez terminada de escribir la orden, es necesario pulsar la tecla
<INTRO>
1-4
20132014
1.4.1. Conexin
Para acceder a un dispositivo de memoria USB es suciente con conectar el dispositivo al ordenador. El equipo lo detecta automticamente y abre un explorador de cheros con el contenido del dispositivo. La siguiente gura recoge el explorador de cheros abierto cuando se inserta un dispositivo USB con la etiqueta
DATOS:
Para hacer referencia al contenido del dispositivo desde la lnea de comandos, se debe hacer referencia al directorio utilizara la etiqueta
/media/<etiqueta>, disk.
siendo
<etiqueta>
1.4.2. Desconexin
Antes de retirar el dispositivo USB del ordenador, es necesario desmontarlo. No debe olvidar
NUNCA rea-
lizar esta operacin antes de desconectar el dispositivo, porque podra dar lugar a la prdida de informacin. Para desmontar el dispositivo debe ejecutar en un terminal la siguiente orden:
umount /media/<etiqueta>
Para poder desmontar el dispositivo, el directorio sobre el que se ha montado (/media/<etiqueta>) no debe estar ocupado; esto signica que no haya ningn comando ejecutndose que utilice algn archivo de dicho directorio, o que exista algn terminal ubicado en dicho directorio. Si se intenta desmontar el dispositivo dndose alguna de estas circunstancias, el sistema operativo avisar de ello, dando la posibilidad de cancelar la operacin o continuar con ella. Para evitar posibles problemas, recomendamos cancelar la
20132014
1-5
operacin, corregir esta situacin (esperando a que los comandos nalicen, y cerrando todos los terminales que estuvieran ubicados en el directorio) y volver a intentarlo. La siguiente gura muestra el mensaje de error cuando se intenta desmontar el dispositivo; observe cmo el terminal de la gura est ubicado justamente en el directorio de montaje del dispositivo, que es lo que da lugar al error:
Si se cierra el terminal y se repite la orden, no se producir el error y el dispositivo se desmontar normalmente. Una vez desmontado el dispositivo, puede retirarlo del ordenador sin peligro.
1-6
20132014
Anexo 2
2.1. Introduccin
En este anexo se explica de manera resumida el conjunto de rdenes de linux ms utilizado en el desarrollo de la asignatura. Aunque las explicaciones de los comandos se reeren al sistema operativo linux, que es el que utilizan los ordenadores del Centro de Clculo, casi en su totalidad son aplicables a cualquier sistema operativo del tipo UNIX.
2-1
2-2
20132014
directorio raz
/.
subdirectorios y dentro de estos puede haber a su vez otros subdirectorios hasta todos los niveles que se quiera. Para separar los niveles tambin se usa el carcter
/.
Dentro de un directorio NO pueden coexistir dos cheros con el mismo nombre, aunque s lo podran hacer en directorios distintos. Por otra parte, los nombres de cheros discriminan entre las maysculas y las minsculas, de manera que etc. Cada usuario tiene un directorio de inicio de sesin, que en el caso de los alumnos de este laboratorio es el directorio
integrales.c
es distinto de
/home/salas,
en el que se encuentra el procesador de rdenes en cada momento. Al iniciar la sesin, el directorio de trabajo es el directorio de inicio de sesin. El directorio de trabajo se puede cambiar con la orden mismo. La orden
cd (Change Directory), pero el directorio pwd (Print Working Directory) muestra en pantalla el
Para seleccionar un chero dentro de una estructura de directorios, podemos utilizar cualquiera de los tres mtodos siguientes: especicando un camino absoluto desde el directorio raz. Para especicarlo de esta forma se empieza el camino con de l hasta llegar al chero que se quiere especicar, todos ellos separados por
/ indicando el directorio raz, y se contina con todos los directorios que hay por debajo /. /, seguida de la ruta desde el directorio de inicio de sesin hasta el chero
especicando un camino relativo desde el directorio de inicio de sesin. En este caso, la especicacin del directorio comienza por deseado. especicando un camino relativo desde el directorio de trabajo. El directorio de trabajo se puede especicar con un punto (.). Para subir al nivel superior en la jerarqua de directorios se utilizan los dos puntos (..). Si el camino comienza con un nombre de directorio (sin camino comienza en el directorio de trabajo.
ni
/)
se considera que el
Ejemplo: Supongamos por ejemplo que una parte de la estructura de directorios fuera similar a la siguiente:
/ | +-----------------------+----- . . . | | | | bin dev lib home . . . | salas
20132014
2-3
Si el directorio de inicio de sesin es /home/salas, y el directorio de trabajo actual es /home/salas/prog/ejecuta podemos hacer referencia al chero integrales.c de las tres formas siguientes: mediante su camino absoluto: /home/salas/prog/fuentes/integrales.c mediante su camino relativo desde el directorio de inicio de sesin: /prog/fuentes/integrales.c mediante su camino relativo desde el directorio actual: ../fuentes/integrales.c
En general, esta forma de especicar un camino es vlida para cualquier orden de UNIX que necesite un nombre de directorio o chero.
2.2.2. Permisos
En los sistemas operativos de tipo UNIX los cheros y directorios presentan tres propiedades especiales: el
El propietario de un chero, por defecto, es el usuario que lo ha creado, y el grupo de un chero, el grupo al que pertenece el usuario que la ha creado. Los permisos de los cheros y directorios se organizan en tres conjuntos de tres tipos de permisos cada uno, lo que da un total de nueve permisos diferentes. Los tipos de permisos son: 1. permiso de lectura. 2. permiso de escritura. 3. permiso de ejecucin (para los cheros) o de bsqueda (para los directorios). Y los conjuntos: 1. el usuario que es propietario del chero. 2. otros usuarios que pertenecen al mismo grupo que el propietario. 3. el resto de usuarios. Para representar estos nueve permisos se utilizan nueve caracteres, agrupados de tres en tres. El primer conjunto de tres caracteres representa los permisos del propietario, el segundo los del grupo, y el tercero el del resto de los usuarios. Dentro de cada conjunto, el primer carcter representa el permiso de lectura, que puede ser representa el permiso de escritura, que puede ser
r o -, el segundo
-,
-.
Los permisos se asignan de forma independiente para cada una de los nueve casos posibles, de forma que un chero puede tener permiso de lectura para todos los usuarios, pero permiso de ejecucin slo para el propietario.
Ejemplo: Si los permisos de un chero son rwxr-xr--, signica que el propietario tiene permiso de lectura, escritura
y ejecucin (rwx), los usuarios que pertenecen al mismo grupo al que pertenece el chero tienen permiso de lectura y ejecucin pero no de escritura (r-x), y el resto de los usuarios slo tienen permiso de lectura (r--).
2-4
20132014
])
no forman parte de la orden, sino que indican que lo que va dentro es opcional. Se
<INTRO>
comience a ejecutarla. Las opciones y los argumentos dependern de cada orden. La orden, las opciones y los argumentos van separados unos de otros por uno o ms espacios en blanco; las opciones van precedidas normalmente de un guin:
-.
ls, passwd
who.
Las rdenes en linux (como en la mayora de los sistemas operativos de tipo UNIX) se ejecutan de forma
silenciosa. Esto quiere decir que en la mayora de los casos, si la orden se ejecuta correctamente no recibiremos
ninguna indicacin del intrprete de comandos; slo obtendremos alguna salida por el terminal si se ha producido algn error. Esta regla general no se aplica, lgicamente, a aquellas rdenes cuyo objetivo es presentar alguna informacin en el terminal.
man.
Su formato es:
man nombredelaorden
q.
less,
ls [opciones] [nombre]
Si no se le da ningn argumento,
como argumento un nombre de chero, muestra por pantalla el nombre del chero si existe en el directorio de trabajo. Si se le da como argumento un nombre de directorio, muestra el nombre de todos los cheros de ese directorio. Opciones:
-l
formato largo. Muestra el modo de acceso (permisos), nmero de enlaces (que no se estudian en estas prcticas), propietario, tamao, fecha de modicacin y nombre de cada chero.
-a -t
muestra tambin los cheros que empiezan por punto, que sin esta opcin no se muestran. los cheros mostrados aparecen ordenados por la fecha de ltima modicacin.
20132014
2-5
-d -r -R -F
muestra el nombre del directorio pero no su contenido. Sirve para ver informacin acerca del directorio. invierte el orden de salida. muestra recursivamente los directorios. Muestra los cheros del directorio y tambin los de todos sus subdirectorios.
/ indica los que son directorios * los que son ejecutables. Ejemplo: La siguiente secuencia de comandos realiza las operaciones descritas: ls muestra los cheros del directorio de trabajo ls /dev muestra los cheros del directorio /dev ls -l /usr/lib muestra en formato largo los cheros del directorio /usr/lib ls -a muestra los cheros del directorio de trabajo, incluso los que empiezan por un punto ls -R /etc muestra todos los cheros y todos los subdirectorios que estn en el directorio
y con
indica con un carcter al lado del nombre del chero su naturaleza, con
muestra las propiedades del directorio /usr/lib, no su contenido. muestra el contenido del directorio de trabajo y da informacin acerca e la naturaleza de los cheros muestra los cheros en formato largo, ordenados por fecha de ltima modicacin igual que el ejemplo anterior pero con el orden de salida invertido
si se trata de un chero normal,
ls -l
si se trata de un
si es un enlace. Los restantes 9 caracteres representan los permisos del chero o directorio.
Orden chmod
Cambia los permisos de un chero o directorio. El formato de este comando es:
u (usuario o dueo), g o (otros, no es el usuario ni tampoco pertenece a su grupo), un signo + o - para dar o quitar y la letra r, w o x:
u+x g+r
Con este mtodo, los permisos a los que no se hace referencia no se modican. Un nmero de 3 dgitos en octal. El primer dgito indica los permisos del usuario, el segundo dgito los permisos del grupo y el tercer dgito los permisos para los dems. Cada dgito se calcula sumando 4 si se quiere dar permiso de lectura, 2 si se quiere dar permiso de escritura y 1 si se quiere dar permiso de ejecucin:
700 644
da todos los permisos al usuario (4+2+1) y ningn permiso al grupo ni a los dems (recomendable para el directorio de inicio de sesin). da permiso de lectura y escritura al usuario (4+2+0), y de lectura al grupo y a los dems (4+0+0).
quita el permiso de bsqueda al grupo para el subdirectorio bin da al grupo el permiso quitado con el ejemplo anterior
2-6 Orden cp
20132014
-r
Copia directorios. Se copia en el directorio de destino el directorio especicado como origen, incluyendo toda la estructura de directorios que est por debajo y los cheros que contienen. Las copias tienen los mismos nombres que los originales. Si se va a copiar un directorio en otro directorio, el de destino puede no existir, y en ese caso no se copia el directorio sino lo que contiene. Cuidado con esta opcin ya que no se debe dar como directorio de destino el nombre de un subdirectorio del directorio a copiar.
p1 y los directorios prac1 y prac2, la siguiente secuencia de comandos realiza las operaciones descritas: cp p1 p2 copia el chero p1 en p2 (se crea el chero p2 que es una copia de p1) cp p1 p3 copia el chero p1 en p3 cp p1 p2 p3 prac1 copia los cheros p1, p2 y p3 en el subdirectorio prac1 (se crean los cheros prac1/p1, prac1/p2 y prac1/p3) cp -r prac1 prac2 como prac2 ya existe, copia el directorio prac1 y todo lo que est por debajo en prac2 cp -r prac1 prac3 como prac3 no existe, copia el contenido del directorio prac1 (pero no el propio directorio) con todo lo que est por debajo en prac3 (se crea prac3 ya que no exista)
Para poder aplicar el comando, es necesario que el directorio de destino disponga de permiso de escritura.
Orden mv
Cambia de nombre o ubicacin un chero. El formato del comando es:
Ejemplo: Suponiendo que en el directorio de trabajo existen el chero p1 y el directorio prac1, la siguiente secuencia
de comandos realiza las operaciones descritas: mv p1 p2 El chero p1 se llama ahora p2 mv p2 prac1 El chero p2 est ahora en el subdirectorio prac1 (su nuevo nombre es prac1/p2)
Para poder aplicar el comando, es necesario que tanto el directorio de origen como el de destino dispongan de permiso de escritura.
Orden rm
Borra cheros (o directorios, si se utiliza la opcin
-r).
rm [opciones] nombreficheroodirectorio
Opciones:
20132014
2-7
-i -r
pregunta si se quiere borrar o no. Esta opcin est activa por defecto. Si se quiere borrar el chero se debe responder a la pregunta con
yes
y.
Ejemplo: Suponiendo que en el directorio de trabajo existen el chero p1 y el directorio prac1, la siguiente secuencia
de comandos realiza las operaciones descritas: rm p1 borra el chero p1 rm prac1 da error, puesto que prac1 es un directorio rm -r prac1 borra el directorio prac1, su contenido y todo lo que est por debajo
Para poder aplicar el comando, es necesario que el directorio padre del chero o directorio disponga de permiso de escritura.
pwd
Orden cd
Cambia el directorio de trabajo. Su formato es:
cd [directorio]
Si se utiliza
cd
El directorio al que se desea cambiar puede especicarse con cualquiera de los tres mtodos vistos anteriormente: Mediante su ruta absoluta desde el directorio raiz. Mediante su ruta relativa al directorio de inicio de sesin. Mediante su ruta relativa al directorio de trabajo.
cd cd bin
cambia al directorio /usr muestra el directorio de trabajo cambia al directorio de inicio de sesin cambia al directorio que se encuentra en el directorio que est dos niveles por encima del directorio de trabajo dentro del rbol de directorios. Suponiendo que el directorio de trabajo es /home/salas/prog, con el primer .. nos referimos al directorio salas (padre del directorio de trabajo) y con el siguiente .. nos referimos a home (padre del directorio salas) nos sita en el directorio de inicio de sesin cambia al directorio bin que est dentro del directorio de inicio de sesin. Si no existe da un mensaje de error.
Orden mkdir
Crea un nuevo directorio. El formato de la orden es:
mkdir
2-8
20132014
man mkdir
crea el directorio bin dentro del directorio de trabajo crea el directorio dirnuevo dentro del subdirectorio bin crea el directorio otrobin dentro del directorio padre intentamos crear un directorio que ya existe. El sistema nos da un error
Para poder crear un directorio, es necesario disponer de permiso de escitura en el directorio en el que se va
Orden rmdir
Borra un directorio. Su formato es:
rmdir directorio
El directorio a borrar no puede contener ningn chero ni ningn otro directorio, es decir, tiene que estar vaco. El directorio a borrar no puede ser el directorio de trabajo.
no borra el directorio dirnuevo debido a que no est vaco borra dirnuevo dentro de bin borra el subdirectorio otrobin del directorio padre
Para poder borrar un directorio, es necesario disponer de permiso de escitura en el directorio en el que se va
-n
presenta por pantalla el contenido del chero p1 presenta por pantalla el contenido de los cheros p1 y prac/p4, sin pausa entre ellos presenta por pantalla el contenido del chero p1, anteponiendo en cada lnea el nmero de secuencia de la misma
Orden less
Es un ltro para ver el contenido de un chero fcilmente. La diferencia con respecto al anterior consiste en que slo presenta lo que cabe en la ventana, y es necesaria una accin por parte del usuario para continuar. Su formato es:
avanza una pgina retrocede una pgina avanza una lnea termina ayuda
20132014
2-9
presenta por pantalla el contenido del chero p1, una pgina cada vez la salida del comando man utiliza el comando less, por lo que son de aplicacin las acciones de dicho comando
Orden tail
Muestra parte del contenido de un chero. El fomato del comando es:
+n -n
n.
lneas.
muestra las 10 ltimas lneas del chero p1 muestra desde la segunda lnea del chero p1 muestra las tres ltimas lneas del chero p1
wc [opciones] fichero
Opciones:
-c -l -w
muestra slo el nmero de caracteres (letra ele) muestra slo el nmero de lneas muestra slo el nmero de palabras
Muestra el nmero de lneas, palabras y caracteres del chero p1 Muestra slo el nmero de lneas del chero p1
Orden du
Muestra el espacio en disco (normalmente en KB) ocupado por los cheros y directorios dados como parmetros. Se considera el espacio ocupado por un directorio al espacio ocupado por todos los cheros y, recursivamente, directorios dentro de este. El formato del comando es:
du [opciones] [nombre]
Si no se especica Opciones:
nombre
se supone
-a -s
2-10
20132014
Si no se especica ninguna opcin, slo hace un resumen por directorios, sin especicar lo que ocupa cada
chero.
muestra informacin del directorio de trabajo y de todos los subdirectorios muestra informacin del directorio prac muestra informacin del directorio prac y de sus subdirectorios (especicando lo que ocupa cada chero)
-r
Orden echo
Escribe sus argumentos en la salida estndar. Es til para comprobar el uso correcto de los metacaracteres del shell (se ver en el siguiente apartado de esta prctica).
Orden diff
Busca diferencias entre dos cheros. Su formato es:
destino
destino
el
chero de nombre
ficheroorigen
y los compara.
La salida de la orden muestra las diferencias encontradas entre ambos cheros. Cuando el comando encuentra diferencias en lneas consecutivas del chero, las agrupa. Al presentar las diferencias, las referencias al primer chero aparecen precedidas por en la lnea de comandos), y las correspondientes al segundo por
>
(chero de la derecha).
A continuacin se presentan los tres casos que pueden aparecer al ejecutar este comando: Si la lnea (o lneas) slo aparece en el primero de los cheros, slo aparecern las lneas precedidas del smbolo
<.
Si la lnea (o lneas) slo aparece en el segundo chero, slo aparecern lneas precedidas del smbolo
>.
Si las lneas aparecen en los dos cheros, pero son diferentes, aparecern primero las lneas del primer chero, precedidas del smbolo
<,
a continuacin una lnea formada por tres guiones, y por ltimo las
>.
Los nmeros y letras que anteceden a cada cambio especican qu comandos habra que ejecutar en el editor de lnea
sed
para obtener el segundo chero a partir del primero. Puesto que dicho editor no se utiliza en
la asignatura, pueden ignorarse, sirviendo nicamente para separar cada grupo de cambios.
20132014
2-11
bash (Bourne-Again
man bash
<INTRO>
el cursor al principio de la siguiente lnea de la pantalla. La tecla sistema interprete los caracteres que se acaban de teclear.
<INTRO>
Otros caracteres de control no tienen una tecla propia sino que deben ser escritos manteniendo pulsada la tecla tecla
<CONTROL> y pulsando simultneamente otra tecla, por lo general una letra. Por ejemplo, pulsar la <CONTROL> y a la vez la tecla <S> se indica con ^S. Algunos de estos caracteres son los siguientes:
pantalla).
^S ^Q ^D
Detiene el scroll de la pantalla (desplazamiento hacia arriba de la informacin cuando no cabe en una
Reanuda el scroll de la pantalla. Enva una marca de n de chero desde el teclado. Si se da cuando el sistema espera una orden es como si se diera la orden de n de sesin.
2-12
20132014
2.4.2. Metacaracteres
Hay caracteres que para el shell tienen un signicado especial, los metacaracteres. Con los metacaracteres se pueden construir expresiones que el shell sustituye por nombres de cheros que ya existan y que estn de acuerdo con la expresin. Los metacaracteres que se van a estudiar son:
* ?
sustituye cualquier carcter o grupo de caracteres (excepto un punto inicial). sustituye un nico carcter cualquiera. sustituye cualquier carcter situado entre los corchetes. Tambin se pueden utilizar rangos, separando el carcter de inicio y el carcter de nal del rango con un guin.
[]
Para que el shell no haga estas sustituciones con los metacaracteres se pueden poner entre comillas simples (se encuentran sobre la tecla a la derecha de la tecla del cero) o anteponiendo el carcter
si slo es un
carcter.
muestra en pantalla el nombre de todos los cheros del directorio actual que empiezan por d muestra en pantalla el nombre de todos los cheros del directorio actual que se llaman dat0 seguido de 1, 2, 3, 4 5, seguido de un punto y con cualquier extensin muestra el nombre de todos los cheros del directorio actual excepto los que empiezan por punto muestra un * en pantalla muestra el nombre de todos los cheros del directorio actual que se llaman dat0 seguido de un carcter cualquiera, un punto y otro carcter cualquiera muestra en pantalla dat0?.? muestra el nombre de todos los cheros del directorio actual que se llaman dat seguido de un 0 1, seguido de 1, 2, 3, 7, 8 9, seguido de punto y con cualquier extensin
echo, los metacaracteres ls en lugar de echo.
del shell se utilizan con cualquier
Aunque en los ejemplos slo se utiliza la orden orden. Puede comprobarlo utilizando la orden
bash,
tendramos:
orden > nomf orden >> nomf orden 2> nomf orden 2>> nomf orden < nomf
nomf,
y a l va la salida estndar. Si
nomf
ya
Se aade la salida estndar al nal del chero Se crea un chero de nombre ya exista se pierde.
nomf.
Si no existe, se crea.
nomf,
y a l va la salida de errores. Si
nomf
Se aade la salida de errores al nal del chero La entrada estndar es leda del chero
nomf.
Si no existe, se crea.
nomf
Si se desea redireccionar las salidas estndar y de errores al mismo chero, deber utilizarse el doble smbolo para que una salida no oculte a la otra:
20132014
2-13
guarda en el chero temp el listado de todos los cheros del directorio actual cuenta el nmero de lneas, de palabras y caracteres del chero temp guarda en el chero ordenados el contenido de temp una vez ordenado guarda en el chero errores los mensajes de error generados por la ejecucin del comando
2.5. Procesos
Un proceso (o tarea) es la ejecucin de un programa. Un programa se puede ejecutar simultneamente por uno o varios usuarios, existiendo as varios procesos y un solo programa. Cada proceso tiene una serie de atributos que el sistema operativo mantiene en una tabla. El ms importante es el
|.
entrada estndar
Proceso 1
salida de errores
salida estndar
PIPE
entrada estndar
Proceso 2
salida de errores
salida estndar
Los programas se ejecutan a la vez, y es el sistema operativo el que se encarga de pasar la informacin de uno a otro. Se puede usar para cualquier programa que use la E/S estndar, por ejemplo las rdenes vistas anteriormente.
|&.
du .. |& wc -l
la salida de la orden ls se utiliza como entrada para la orden wc. Como consecuencia, el resultado de la operacin es el nmero de cheros y directorios del directorio actual cuenta el nmero de lneas de error que genera la orden du; el resultado del comando du sigue apareciendo por el terminal.
ls -lR
slo involucra un proceso, mientras que este otro: (que da una idea aproximada del nmero de cheros del directorio
ls -lR /usr/lib | wc -l
/usr/lib
y todos sus subdirectorios) se ha creado un trabajo con dos procesos. Un trabajo se puede encontrar en uno de tres modos de ejecucin:
2-14
20132014
en foreground (o modo interactivo): es el estado normal de un proceso. El terminal est controlado por el proceso. No se puede hacer otra cosa hasta que no termine de ejecutarse. en background (o segundo plano): el terminal queda libre para seguir trabajando en otras cosas (es til si se est ejecutando un proceso que tarda bastante). El trabajo sigue ejecutndose. parado: el trabajo deja de ejecutarse, a la espera de que se le especique un nuevo cambio del modo de ejecucin, pasndolo a background o a foreground, o se nalice. Por defecto, todos los trabajos se ejecutan en modo interactivo. Para ejecutar una orden en segundo plano se naliza la orden con el smbolo de ejecucin de los trabajos. Un trabajo queda totalmente determinado por su identicador, que es un nmero entero, asignado por el intrprete de comandos cuando se lanz la orden. No debe confundirse el identicador de un trabajo con los PIDs de los diferentes procesos que componen dicho trabajo. Para hacer referencia a un trabajo se antepone el smbolo % al identicador de dicho trabajo.
&.
se crea un trabajo en segundo plano con un nico proceso. Se crea el chero b0.dat con el listado del directorio /usr y todos sus subdirectorios se crea un trabajo en segundo plano con dos procesos. Cuenta el nmero de cheros y directorios que hay dentro del directorio /usr
ps [opciones]
Opciones:
a x u
muestra los procesos de todos los usuarios muestra todos los procesos aunque no estn asociados a un terminal muestra ms informacin ms detallada
PID TT S
nmero de identicacin del proceso terminal lgico asociado estado del proceso. Puede ser del procesador), evento) o
(en ejecucin),
(a la espera
(parado).
TIME COMMAND
2-15
jobs
La diferencia con la anterior es que esta orden especica trabajos, mientras que la anterior especica procesos; y un trabajo involucra al menos un proceso, pero puede involucrar cualquier nmero de ellos.
Ejemplo: La orden
ls -lR /usr/lib | wc -l & ps ; jobs que da una idea aproximada del nmero de cheros del directorio y todos sus subdirectorios, crea un trabajo con dos procesos. Si utilizamos la orden jobs obtendremos una nica entrada (puesto que es un nico trabajo) mientras que si utilizamos la orden ps
<Control>
<C>.
Orden ^Z
Pasa un trabajo del modo interactivo al estado parado. El comando se ejecuta pulsando simultneamente las teclas
<Control>
<Z>.
Orden bg
Pasa un trabajo de parado a segundo plano. Su formato es:
bg [identificacindetrabajo]
Si no se indica ninguna identicacin de trabajo, por defecto se acta sobre el ltimo trabajo parado.
Orden fg
Pasa un trabajo de parado o en segundo plano a modo interactivo:
fg identificacindetrabajo
Orden kill
Enva la seal de terminacin a un proceso o trabajo del usuario (lo destruye). El formato de la orden es:
-9
Resumen
La siguiente gura resume los diferentes estados en los que se puede encontrar un trabajo, y las rdenes necesarias para pasarlo de un estado a otro.
2-16
20132014
Interactivo
fg ^Z
Parado
^C
fg bg kill
Segundo plano
Destruido
kill
Anexo 3
El compilador gcc
ndice
3.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-1 3.2. El comando
gcc
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-4
3.1. Introduccin
Las fases del desarrollo de un programa responden generalmente al siguiente esquema:
INICIO
Editar
Compilar
Errores? S
NO
Ejecutar
S Leer errores
Errores? NO FIN
3-1
3-2
20132014
Como se puede apreciar en el diagrama de ujo anterior, debemos comenzar utilizando un editor para escribir nuestro programa fuente. Los cheros que contienen texto fuente en C debern tener la extensin si se trata de un chero de preprocesado). Una vez escrito, debemos compilarlo y eliminar los errores de codicacin que indique el propio compilador, que incluso indica la lnea de cdigo donde se ha producido cada error. Para ello debemos leer atentamente los mensajes de error y, posiblemente, buscar ayuda en el manual (man). Cuando el programa no tenga errores de compilacin debemos ejecutarlo y comprobar que realmente hace lo que queramos. Si no fuera as, deberamos volver a la fase de edicin para corregir los errores detectados. Slo cuando el programa funciones como debe podremos decir que hemos terminado el programa con xito. Todo programa fuente debe ser compilado antes de poder ser ejecutado, por ello, el objetivo de esta seccin es el aprendizaje de las herramientas de compilacin, que permiten obtener ejecutables a partir del cdigo fuente.
.c
(o
.h
#define, #ifndef,
etc.). Al realizar esta operacin sobre un chero fuente se obtiene como resultado una unidad de compilacin. La unidad de compilacin no se almacena en un chero intermedio, pero se puede ver en pantalla con la opcin realizar la compilacin.
-E
de
gcc.
Compilacin: obtiene el cdigo objeto a partir del cdigo fuente. El cdigo fuente es la unidad de compilacin resultante del preprocesado. El cdigo objeto (o cdigo intermedio) se almacena en un chero con el mismo nombre que el fuente pero terminado en
pero que no se puede ejecutar porque contiene referencias no resueltas (por ejemplo llamadas a funciones denidas en otros cheros). Adems, durante la compilacin se analiza la sintaxis del chero fuente y si se detectan errores se informa al usuario y no se obtiene cdigo intermedio. Si el compilador detecta sentencias sintcticamente correctas, pero que pueden ser fruto de una equivocacin del programador muestra el correspondiente mensaje de aviso (o warning) pero s genera el cdigo intermedio. Se debe corregir el programa para que no aparezca ningn aviso.
Enlazado o linkado: una vez compiladas con xito todas las unidades de compilacin obtenidas en el preprocesado, todos los cheros con cdigo intermedio (los que terminan en conjuntamente para obtener un nico programa ejecutable (por defecto
conoce como enlazado o linkado del programa y la realiza el enlazador o linker. En el enlazado tambin se resuelven las referencias a funciones de biblioteca (que normalmente se hace de forma automtica).
La siguiente gura recoge el proceso de generacin de un ejecutable a partir de varios cheros de cdigo fuente:
20132014
3-3
gcc.
Por defecto, si
gcc
.c,
.o
intermedios generados. Si
gcc
.o
los
#include <stdio.h> int main() { int n = 7; int i = 1; int j; while (i <= n) { for ( j = i; j < n ; j++) printf(" "); for ( j = 1; j <= 2*i-1; j++) printf("*"); i++; printf ("\n"); } for ( i = 1; i < n; i++) printf(" "); printf("#\n"); return 0;
3-4
20132014
Aparecer una versin ejecutable del programa en un chero de nombre a.out. Si lo ejecutamos:
salas@318CDCr12:$ a.out * *** ***** ******* ********* *********** ************* # salas@318CDCr12:$
gcc
es el siguiente:
-E
Slo realiza el preprocesado del chero, enviando el resultado a la salida estndar. Avisa de todos los tipos de errores posibles (sintcticos y de construccin).
-Wall -W -c
Muestra mensajes de aviso adicionales. realiza la compilacin y por tanto crea el chero objeto pero no llama al enlazador y por tanto no crea el chero ejecutable.
-g
Produce informacin necesaria para el depurador (se ver en una prctica posterior). en vez de en
-o <nombrefichero> El programa ejecutable se guarda en el chero <nombrefichero> a.out. Si previamente ya exista el chero a.out, ste no se modica.
-l<nombre> Busca una biblioteca llamada lib<nombre>.a. Por ejemplo, -lm busca la biblioteca llamada libm.a. En esta biblioteca se encuentran las funciones matemticas (seno, coseno ... ). Es importante
el orden en que se coloca esta opcin; se pone normalmente al nal, despus de los cheros a compilar.
20132014
#include <stdio.h> int main() { int numero; printf("Introduzca un numero: "); scanf(" %d", &numero); if (numero = 1) printf("Vale uno\n"); else printf("No vale uno\n"); return 0;
3-5
Si se genera el ejecutable sin utilizar las opciones -W ni -Wall, y se realizan algunas ejecuciones, se obtienen los siguientes resultados:
salas@318CDCr12:$ gcc avisos.c salas@318CDCr12:$ a.out Introduzca un numero: 1 Vale uno salas@318CDCr12:$ a.out Introduzca un numero: 8 Vale uno salas@318CDCr12:$
Como puede observarse, el funcionamiento del programa es incorrecto. Existe un error en la codicacin que no ha sido detectado. Sin embargo, si utilizamos las opciones -W y -Wall, el propio compilador nos habra puesto sobre aviso:
salas@318CDCr12:$ gcc -W -Wall avisos.c avisos.c: In function main: avisos.c:9: warning: suggest parentheses around assignment used as truth value salas@318CDCr12:$
El ejecutable se ha generado igualmente, pero el compilador nos avisa de que en la lnea 9 hay algo que puede no estar bien. Efectivamente, si observamos dicha lnea podemos comprobar que estamos utilizando el operador = (de asignacin) en lugar del operador == (de comparacin), que es el que queramos utilizar.
Slo los programadores expertos pueden interpretar adecuadamente los informes de aviso del compilador, y estar completamente seguros de que el programa funciona como se desea a pesar de producirse avisos. Por ello, es imprescindible que los programas de los alumnos carezcan tanto de errores (por supuesto), como de avisos durante la fase de compilacin. Y para asegurarse de ello, siempre habr que incluir las opciones
-W
-Wall
en todas las compilaciones que se realicen durante el curso (y se aconseja que se contine haciendo
-c.
a.out,
especicados. Si queremos obtener adems el ejecutable, deberemos a continuacin invocar el comando (.c). Puede comprobarlo ejecutando la siguiente serie de comandos:
gcc
para que realice el enlazado, pasndole como parmetros los cheros objeto (.o) y no los cheros fuente
3-6
20132014
salas@318CDCr12:$ salas@318CDCr12:$ a.out not found abeto.o salas@318CDCr12:$ salas@318CDCr12:$ a.out abeto.o
Uso de la opcin -o
Si se desea que el nombre del chero ejecutable tenga un nombre diferente, deber indicrselo al compilador con la opcin
Ejemplo: El siguiente ejemplo genera el ejecutable correspondiente al dibujo del abeto utilizando
nombre del ejecutable:
salas@318CDCr12:$ gcc -Wall -W -o abeto abeto.o salas@318CDCr12:$ abeto * *** ***** ******* ********* *********** ************* # salas@318CDCr12:$
-o,
abeto como
Puesto que si no se utiliza esta opcin cada vez que generemos un nuevo ejecutable borraremos el ejecutable anterior que pudiera existir en el directorio en el que estamos trabajando, se aconseja utilizarla siempre, y ponerle un nombre al ejecutable que indique qu es lo que hace.
Anexo 4
4.4.1. Objetivos sin Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-5 4.4.2. Forzar la reconstruccin completa . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-5
4.1. Introduccin
Para conseguir modularidad en la programacin es conveniente (y necesario) organizar un programa en distintos cheros. Si un programa es complejo, podra estar estructurado en un gran nmero de cheros. La gestin de mltiples cheros es una tarea que puede ser abrumadora sin herramientas que la faciliten. El procesador de rdenes permite automatizar procesos repetitivos de forma automtica, como por ejemplo generar el ejecutable de un programa a partir de sus diferentes componentes. El procesador de rdenes que se analizar en este captulo es el La herramienta
make.
make make
matiza el proceso de compilacin y enlace de un programa aplicando reglas predenidas. La herramienta se dise originalmente para mantener programas, y hablaremos de ella en este con-
texto. Esta herramienta especica y controla el proceso mediante el cual se genera un ejecutable que puede constar de mltiples cheros. La ventaja de utilizar
make
pretar las dependencias de un programa, puede compilar de manera selectiva slo aquellos cheros que hayan sido modicados. La alternativa sera recompilar todo, que da lugar a una prdida de tiempo considerable en los programas extensos.
4.2. Descripcin
La herramienta
make
ponentes de un programa estn relacionados entre s y cmo procesarlos para crear una versin actualizada del programa. Revisa las fechas en las que los distintos componentes fueron modicados por ltima vez,
4-1
4-2
20132014
encuentra la cantidad mnima de recompilacin que debe hacerse para tener una nueva versin, y entonces compila y enlaza. La herramienta contiene tres el cmo. El qu, es(son) el(los) nombre(s) de lo que se pretende construir. En la terminologa llama
make construye un programa (o varios) de acuerdo con un conjunto de criterios contenidos en makefile. Un chero makefile tipos de informacin que make examina. Bsicamente se puede dividir en el qu, el por qu y
objetivo.
make,
el qu se
make.
vo determinado debera construirse a partir de un conjunto especco de componentes. En general, se reconstruye un objetivo a partir de sus componentes cuando el objetivo tiene una fecha anterior con respecto a los componentes de los que depende. Por ello a la informacin por qu se llama o
dependencias.
prerrequisitos
make debera invocar para construir los objetivos (normalmente make, el cmo se llama regla.
# Linea de comentario objetivo_1: dependencias_1 regla_1_1 regla_1_2 ... regla_1_n # Otra linea de comentario objetivo_2: dependencias_2 regla_2_1 regla_2_2 ... regla_2_m
:.
Las reglas correspondientes a un objetivo deben estar en las lneas
Para especicar un objetivo el formato que se sigue es escribir el objetivo al principio de una lnea, y separarlo de los prerrequisitos por el carcter siguientes y deben comenzar cada una de ellas por el carcter tabulador. Las reglas a aplicar nalizan cuando aparece una lnea en blanco o un nuevo objetivo. En el chero de descripcin de dependencias pueden aparecer lneas de comentario, que comienzan por el carcter
#.
La herramienta
make
4.3. Sintaxis
La sintaxis de la orden
make
es la siguiente:
-f dependencias
Dene el nombre del chero en el que estn denidas las normas para construir el
Makefile,
con
make sin la opcin -f, utilizar como chero de descripcin de dependencias makefile o Makefile (en este orden). Es prctica comn llamar al chero una M mayscula, porque as aparece al principio de la lista de cheros en un listado
del directorio, puesto que los nombres de los cheros fuente suelen comenzar con minsculas, que alfabticamente van detrs.
20132014
4-3
objetivo
Es el nombre del objetivo a construir, y que ser necesario buscar en el chero de descripcin de
# Esto es un fichero makefile de ejemplo # Makefile de arbol arbol: arbol.o pinta.o gcc -o arbol arbol.o pinta.o arbol.o: arbol.c pinta.h gcc -W -Wall -c arbol.c pinta.o: pinta.c pinta.h gcc -W -Wall -c pinta.c borra: rm *.o
y trabajo:
# Esto es un fichero makefile de ejemplo # Makefile de arbol programa: principal.o libreria.o gcc -o programa principal.o libreria.o
principal.o: principal.c libreria.h gcc -W -Wall -c principal.c libreria.o: libreria.c libreria.h gcc -W -Wall -c libreria.c
las siguientes rdenes tendrn el siguiente resultado: make genera el objetivo arbol (que es el que primero aparece) denido en el chero Makefile make -f trabajo genera el objetivo programa (que es el que primero aparece) denido en el chero trabajo make borra genera el objetivo borra denido en el chero Makefile make -f trabajo programa genera el objetivo programa denido en el chero trabajo make programa Producira un error, puesto que el objetivo programa no est denido en el chero Makefile
make
es el siguiente. que siempre ser un chero, primero busca dicho chero en el directorio de
objetivo,
objetivo
existe, entonces compara la fecha de dicho chero con la de todos los cheros
Si
ALGUNA
objetivo,
4-4
20132014 TODAS las dependencias tienen fecha anterior a objetivo, no se hace nada.
Si
Este proceso se aplica de forma recursiva, de forma que si alguna de las dependencias no existe, se busca una descripcin en la que dicha dependencia aparezca como objetivo, y se aplica el algoritmo anterior. El siguiente listado recoge un ejemplo de un chero
makefile:
# Esto es un fichero makefile de ejemplo # Makefile de arbol arbol: arbol.o pinta.o gcc -o arbol arbol.o pinta.o arbol.o: arbol.c pinta.h gcc -W -Wall -c arbol.c pinta.o: pinta.c pinta.h gcc -W -Wall -c pinta.c borra: rm *.o
arbol.o, pinta.o
y
borra).
de un cierto objetivo pueden ser a su vez objetivos que haya que construir. Imaginemos que en el directorio de trabajo slo existen los cheros fuente (.c y siguiente comando:
.h),
y que se invoca el
make
En ese caso, el objetivo a construir ser el primero que se encuentre en el chero, secuencia que sigue
arbol
en el ejemplo. La
make
es la siguiente:
busca
arbol
en el directorio de trabajo y no lo encuentra, por lo que debe aplicar las reglas secuen-
arbol
arbol.o
pinta.o.
arbol.o
arbol.o
como objetivo, que es la dcima del ejemplo. Intenta aplicar
pinta.o
pinta.o
y
arbol.o
pinta.o,
arbol:
arbol
20132014
4-5
make,
make
borra,
chero.
.o.
Este caso es el nico en el que el nombre del objetivo no tiene que corresponderse con el nombre de un
make
touch:
En el ejemplo se actualiza la fecha de modicacin de todos los cheros terminados en invocacin subsiguiente a
.c
y en
.h.
Una
make
makefile.
Makefile
la siguiente dependencia:
y ejecutar:
make all
<=>,
y a continuacin el valor.
pinta.o:
Una macro se referencia haciendo preceder su nombre con un signo tesis (como en el ejemplo) o entre llaves.
<$>,
Ejemplo: El siguiente fragmento de cdigo utiliza la macro anterior para especicar una regla:
arbol:
4-6
20132014
La utilizacin de las macros permite que si se aaden o eliminan cheros para generar un ejecutable, es suciente con modicar la denicin de la macro que dene los cheros necesarios. Tambin es de utilidad el uso de las macros para especicar parmetros globales del desarrollo, como seran el nombre del compilador a utilizar o las opciones con las que debe funcionar el compilador.
Ejemplo: El siguiente listado recoge el chero Makefile original, modicado para hacer uso de las macros:
# Esto es un fichero makefile de ejemplo # Makefile de arbol CC = gcc OPCIONES = -g -W -Wall OBJETOS = arbol.o pinta.o arbol: $(OBJETOS) $(CC) $(OPCIONES) -o arbol $(OBJETOS) arbol.o: arbol.c pinta.h $(CC) $(OPCIONES) -c arbol.c pinta.o: pinta.c pinta.h $(CC) $(OPCIONES) -c pinta.c borra: rm *.o
Anexo 5
5.4. Ejecucin de programas bajo gdb: orden run . . . . . . . . . . . . . . . . . . . . 5-4 5.5. Puntos de parada (breakpoints) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-5
5.5.1. 5.5.2. 5.5.3. 5.5.4. 5.5.5. 5.5.6. 5.5.7. Establecimiento de puntos de parada . . Informacin sobre los puntos de parada Deshabilitar un punto de parada . . . . Habilitar un punto de parada . . . . . . Borrado de un punto de parada . . . . . Ejecucin automtica en la parada . . . Continuacin de la ejecucin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.6. Examinar los Ficheros Fuente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-7 5.7. Examinar la Pila . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-8 5.8. Examinar los datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-9
5.1. Introduccin
El depurador de programas es una herramienta que permite encontrar y corregir errores de un programa mediante la ejecucin paso a paso del mismo. Su propsito es permitir ver qu est ocurriendo dentro de un programa cuando se est ejecutando, o ver qu estaba haciendo un programa en el momento en que se interrumpi su ejecucin de forma inesperada. Cuando durante la ejecucin de un programa se produce un error que impide que se pueda continuar con dicha ejecucin, por ejemplo por acceder a una posicin de memoria no autorizada, la shell (procesador de rdenes) enva un mensaje a la salida estndar de errores similar a:
5-2
20132014
En tales casos, el propio sistema operativo se encarga de generar un chero llamado facilitan datos para depurar el error producido.
core
en el que se
El sistema operativo puede imponer lmites al tamao de dicho chero, e incluso evitar que se cree. Para eliminar esos lmites, debe ejecutarse la siguiente sentencia en el terminal en la que se va a realizar la ejecucin:
ulimit -c unlimited
Para evitar tener que ejecutarlo cada vez que se abra un nuevo terminal, es conveniente incluirlo en el chero
.bashrc, -g
Para poder depurar un programa con el depurador es necesario haberlo compilado previamente con la opcin del compilador:
tipos de acciones que nos ayuden a averiguar dnde se est produciendo el fallo de nuestro programa: Hacer que nuestro programa se detenga bajo ciertas condiciones. Examinar qu ha ocurrido, una vez que nuestro programa se ha parado. Cambiar cosas del programa de forma que podamos corregir los efectos de los fallos para continuar examinando nuestro programa. Examinar los valores de las variables durante la ejecucin de un programa.
aade el directorio
dir
fuente. Por defecto, busca los cheros en el directorio actual. es el nombre del programa ejecutable que queremos depurar
es el nombre del chero que genera el sistema operativo cuando un programa tiene un error
durante la ejecucin, por ejemplo cuando se intenta acceder a una posicin de memoria que no pertenece al programa. A la vista del formato de la orden anterior, podemos distinguir tres formas de arrancar el depurador: 1. Sin indicar ni el programa ni el chero de por lo que no entraremos en l. 2. Indicando el programa, pero no el chero de
core.
core.
para el ejecutable indicado, pero sin tener en cuenta ninguna ejecucin anterior:
salas@318CDCr12:$ gdb a.out GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. <---- Texto oculto ---->
20132014
5-3
This GDB was configured as "i486-linux-gnu"... (gdb) run Starting program: /home/salas/a.out Program received signal SIGSEGV, Segmentation fault. 0x0804838f in main () at pNulo.c:7 7 printf(" %d", *pint); (gdb)
gdb,
salas@318CDCr12:$ a.out Fallo de segmentacion (core dumped) salas@318CDCr12:$ gdb a.out core GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. <---- Texto oculto ----> This GDB was configured as "i486-linux-gnu"... warning: Cant read pathname for load map: Input/output error. Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 Core was generated by a.out. Program terminated with signal 11, Segmentation fault. [New process 6176] #0 0x0804838f in main () at pNulo.c:7 7 printf(" %d", *pint); (gdb)
Cuando invocamos al depurador, entramos en una sesin de depuracin. Podemos comprobar que estamos dentro de una sesin de depuracin porque el prompt es diferente. En lugar del habitual, tendremos uno como el siguiente:
(gdb)
lo que nos indica que quien va a interpretar las rdenes que introduzcamos por el teclado ser el depurador, y no el intrprete de comandos. Para salir del depurador, y volver al intrprete de comandos, debemos utilizar la orden
quit:
(gdb) quit
gdb
La primera es que no necesita que las rdenes se escriban completamente, sino que es suciente que no haya ambigedad con ninguna otra orden. Por ejemplo, hay cinco rdenes que empiezan por slo una (monitor) que empieza por escribamos sus dos primeras letras. En otros casos, se priorizan las rdenes ms habituales; as, por ejemplo, aunque hay 17 comandos que empiezan por
m,
pero
mo.
s,
si slo se indica esa letra el intrprete asume que la orden que se desea ejecutar es
step,
En la explicacin de los diferentes comandos que se van a utilizar, se indicar el nombre completo del comando, pero indicando la parte que se puede eliminar.
5-4
20132014
La segunda es que el intrprete de comandos tiene memoria, por lo que podemos utilizar las teclas de cursor hacia arriba y hacia abajo para recuperar un comando ejecutado con anterioridad, y una vez en la lnea, las teclas de cursos hacia la izquierda y hacia la derecha para editar la lnea correspondiente. Por ltimo, si se teclea
<Intro>
gdb
gdb.
breakpoint),
locals),
argumentos recibidos . . .
info,
sho[w]
mv...)
shell,
desea ejecutar.
she[ll] <comando>
siendo
<comando>
r[un] [<argumentos>]
20132014
5-5
siendo
<argumentos>
Se pueden comprobar los argumentos con los que se ejecut el programa mediante
show args.
antes de ejecutar la instruccin correspondiente a este punto). Tambin podemos establecer la parada de un programa en un punto de parada cuando el programa al llegar a dicho punto cumpla ciertas condiciones. Los puntos de parada se pueden borrar, o bien deshabilitar para posteriormente poder utilizarlos.
<situacion>
especica dnde queremos poner el punto de parada; podemos utilizar cualquiera de las
siguientes frmulas: indicando el nombre de una funcin. El programa se detendr al entrar en la funcin:
break
<funcin>
indicando un nmero de lnea. La ejecucin se detendr antes de ejecutar la lnea cuyo nmero hemos indicado del chero fuente actual:
break <nmerodelnea>
En ambos casos hacemos referencia al chero actualmente activo (en el que se encuentra la sentencia en la que estamos parados). Si queremos hacer referencia a un chero distinto, antepondremos a la ubicacin el nombre del chero seguido del carcter dos puntos:
hemos indicado. Si no se pone ninguna condicin, el programa se detendr siempre que llegue al punto de parada. De esta forma, al llegar al punto de parada se evala la condicin, y se detiene el programa slo si es cierta:
Si en lugar de
tbreak gdb
desactiva, no teniendo ningn efecto posterior. Al detenerse en un punto de parada, muestra la siguiente sentencia que se debe ejecutar.
i[nfo] breakpoint
5-6
20132014
clear [<situacin>]
donde
<situacin>
de lnea, nombre de funcin, . . . Si no se especican argumentos, borra todos los puntos de parada. Indicando el nmero del punto de parada a borrar:
info break.
comm[ands] <nmeropuntoparada>
Tras introducir dicha orden, escribiremos en lneas separadas las rdenes a ejecutar cuando se alcance dicho punto de parada. Cuando terminemos la lista de rdenes debemos escribir Por ejemplo:
end.
(gdb) commands 1 Type commands for when breakpoint 1 is hit, one per line. End with a line saying just "end". >print n >printf "x tiene el valor %d \n",x >set x = x+4 >continue >end (gdb)
n,
luego
Este cdigo en
gdb
x,
x,
20132014
5-7
c[ontinue] [<nmero>]
donde el parmetro
<nmero>
parada en el que se ha detenido nuestro programa sin que se vuelva a detener la ejecucin del programa (ignorando dicho punto de parada) al llegar a l. Este parmetro resulta de utilidad cuando el punto de parada se encuentra dentro de un bucle y queremos pasar un cierto nmero de iteraciones sin detener el programa. Si no se especica ningn nmero, la ejecucin del programa contina hasta alcanzar el siguiente punto de parada (o el nal del programa). Si queremos continuar la ejecucin del programa en otro punto distinto utilizaremos la orden:
j[ump] <nmerodelnea>
Podemos especicar que deseamos ejecutar la siguiente lnea del cdigo y luego retomar el control, es decir realizar una nueva parada en la siguiente instruccin. Esto se conoce como ejecucin paso a paso, y se consigue con la orden:
s[tep]
Una tcnica habitual consiste en establecer un punto de parada al comienzo de una funcin donde pensamos que podra estar el problema y luego ejecutarla paso a paso con
n[ext]
es parecida a la orden
step, slo que si la instruccin a ejecutar es una llamada a una funcin, no se detiene
en la primera instruccin ejecutable de la funcin, sino que se detiene al terminar la ejecucin de la funcin.
l[ist] [- | <situacion>]
siendo: imprime las lneas anteriores a las ltimas visualizadas. imprime lneas de cdigo centrndose en el punto especicado, donde
<situacion>
<situacion>
se
puede especicar de idntica forma a lo visto para el establecimiento de puntos de ruptura (nmero de lnea o funcin, del chero actual o de otro chero). Por defecto se imprimen 10 lneas, para variar dicho valor se emplear la orden:
sea[rch] <expresin>
que busca la expresin en las lneas de cdigo que van detrs de la ltima lnea que se mostr (con utilizar la orden:
list)
hasta el nal del cdigo. Para buscar hacia atrs (desde la ltima lnea mostrada hasta el comienzo) podemos
rev[erse-search] <expresin>
Si no se especicaron los directorios en los que encontrar los cheros fuentes en la invocacin del depurador (con la opcin
-d),
5-8
20132014
dir[ectory] <nombrededirectorio>
que aade un nuevo directorio a la lista de directorios donde buscarlos.
trama.
Las tramas
se van almacenando en una pila de llamadas cada vez que se llama a una funcin, y desaparecen de la pila
main,
que se denomina
outermost frame
initial frame.
crea una nueva trama y se guarda en la pila, y cuando la funcin naliza se elimina dicha trama de la pila. La trama de la funcin que se estaba ejecutando cuando se detuvo la ejecucin del programa (en un punto de parada) se denomina: comenzando con
para la trama
innermost frame. gdb asigna un nmero a todas las tramas existentes en la pila, innermost.
ba[cktrace]
que proporciona un resumen de cmo nuestro programa lleg al punto de parada actual. Muestra toda la pila de tramas, comenzando por la actual.
(gdb) backtrace #0 pregunta (p_numero=0xbfb66084, mensaje=0xbfb660ab "Introduzca un numero") at pregunta.c:30 #1 0x080484b1 in inicializa (mensaje=0xbfb660ab "Introduzca un numero") at pregunta.c:22 #2 0x08048464 in main () at pregunta.c:11 (gdb)
#0 #1 #2
pregunta() inicializa()
main() main
llam a
Interpretndolo desde la trama nal: la funcin donde estamos detenidos actualmente. La orden:
inicializa
que llam a
pregunta,
que es
f[rame] <nmerodetrama>
nos permite movernos en la pila a la trama especicada en
<nmerodetrama>.
Al movernos a una trama, se imprimirn dos lneas donde se muestra el nombre de la funcin correspondiente a esta trama con sus argumentos, y la lnea que se est ejecutando en esta funcin. Si ahora invocamos la orden
list
Con la orden:
i[nfo] frame
se nos muestra informacin de la trama seleccionada en la pila (sobre todo informacin relacionada con las direcciones de memoria que se estn utilizando).
20132014
5-9
Con la orden:
i[nfo] args
podemos ver los argumentos de la trama seleccionada.
p[rint] <expresin>
donde
<expresin>
es una expresin del lenguaje del cdigo fuente, en este caso 'C'. Cualquier tipo de
variable, constante u operador denido en el lenguaje de programacin, se acepta como expresin, incluyendo expresiones condicionales, llamadas a funciones, . . . , a excepcin de los smbolos denidos en el preprocesador (con
#define). frame).
Cuando se imprime el valor de una variable, se entiende que es de la trama seleccionada. Si queremos referirnos a una variable en otra trama, debemos movernos a dicha trama (con la orden Por defecto
nos muestra una variable en su formato correcto. Puede forzarse a que el formato de
/d /u /t /a /c /f
Entero decimal con signo Entero decimal sin signo Binario Imprime una direccin, tanto en valor absoluto (hexadecimal) como en forma de desplazamiento respecto al smbolo que le precede de forma ms cercana. Constante de carcter Nmero en coma otante.
Si se quiere imprimir el valor de una expresin frecuentemente (por ejemplo, para ver cmo va cambiando), puede aadirse a una lista de display automtica, que se imprime cada vez que gdb detiene la ejecucin de un programa. Para aadir una expresin o variable a dicha lista, utilizaremos la orden:
und[isplay] <nmerodelaexpresin>
Con:
i[nfo] display
obtendremos una lista de todas las expresiones que se encuentran en la lista, con su nmero de expresin correspondiente. Todos los valores que obtenemos con la orden con:
variables auxiliares creadas por gdb automticamente al mostrar el resultado ($1, $2, . . . ). Se pueden ver
sho[w] values
Puede imprimirse el contenido de un array o tabla mediante el operador programa con una lnea de cdigo tal como:
@.
5-10
20132014
podemos imprimir los valores de los elementos de la tabla, indicando detrs del carcter elementos:
el nmero de
print *array@elementos
Otra frmula muy utilizada es usar variables auxiliares como contadores, y luego repetir las expresiones mediante
<Intro>
(ya que cuando se pulsa esta tecla se repite la ltima orden). Por ejemplo, si se tiene
dtab.
fv
valor
$i x:
(todas las variables auxiliares comienzan por
$)
con el
0.
x /nfu [<direccininicial>]
donde:
/u
print):
s cadena de caracteres terminada en \0 i instruccin en lenguaje mquina x hexadecimal (valor por defecto) c carcter
En el siguiente ejemplo se imprime la variable utilizando diferentes formatos:
(gdb) print mensaje $17 = "Introduzca un numero" (gdb) x /6 mensaje 0xbfb660ab: 0x72746e49 0xbfb660bb: 0x6f72656d (gdb) x /21c mensaje 0xbfb660ab: 73 I 110 n 0xbfb660b3: 99 c 97 a 0xbfb660bb: 109 m 101 e (gdb)
0x7a75646f 0x0a000000
0x75206163
0x756e206e
116 t 114 r 111 o 100 d 117 u 122 z 32 117 u 110 n 32 110 n 117 u 114 r 111 o 0 \0
se ve la cadena de caracteres que contiene la variable. se muestra en hexadecimal 6 grupos de 4 octetos a partir de la
mensaje)
/6 mensaje)
Anexo 6
6.8. Otros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-7 6.9. Organizacin en cheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-8 6.10. La sentencia exit . . . . 6.11. Orden de evaluacin . . . 6.12. Los operadores ++, -- . . 6.13. De nuevo los prototipos .
6.9.1. Ficheros de cabeceras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-8
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
6.1. Introduccin
Se ha insistido a lo largo del curso de lo importante de programar de forma estructurada (utilizando las estructuras de control vistas en clase), y de la utilidad de hacerlo de forma modular (mdulos o subprogramas reutilizables, en los que resulta ms fcil la deteccin de errores que considerando la totalidad). Al mismo tiempo se hace hincapi en que el cdigo de nuestros programas debe ser legible. Esto implica claridad en su escritura, para facilitar su posterior lectura, mantenimiento y modicacin. Es tambin importante el uso de comentarios que permitan el entendimiento del programa a personas ajenas al mismo o al propio programador una vez pasado un cierto tiempo. Para ello intentaremos aplicar una serie de normas de estilo a la hora de programar. Aqu daremos las principales, y a lo largo de las prcticas iremos insistiendo en ellas y aadiremos algunas otras.
6-1
6-2
20132014
.h),
include.
stdlib.h,
etc.) y luego
los propios de la aplicacin. 3. Constantes simblicas y tipos denidos por el usuario. 4. Variables estticas si las hubiera (en esta asignatura no estn permitidas, ya que pueden da lugar a errores difciles de detectar). 5. Prototipos de funciones locales al chero (que no se usan en otros cheros). 6. Funciones. Primero
main
existe criterio, al menos alfabticamente. Se recuerda que la declararacin o prototipo de una funcin debe aparacer antes de ser llamada.
Cada una de estas secciones debe tener un comentario que la preceda. El siguiente fragmento es un ejemplo de lo anterior:
/* ** ** ** ** ** ** */
Descripcion:
/* Includes del sistema */ #include <stdio.h> /* Includes de la aplicacion */ #include "ejemplos.h" /* constantes */ #define MAXIMO 100 #define ERROR "Opcion no permitida" /* tipos definidos por el usuario */
20132014
6-3
en vez de
Las distintas partes o secciones del chero deben ir separadas por al menos una lnea en blanco. Por ejemplo, la parte de include de la seccin de constantes, etc. De la misma manera, dentro de una funcin los distintos bloques deben ir separados por lneas en blanco:
#include <stdio.h> int main() { int num=0; int contador=0; int impares=0; int positivos=0; int resp;
/* /* /* /* /*
numero a leer de teclado */ contador total */ contador de impares */ contador de positivos */ respuesta del usuario */
/* Separamos definiciones de codigo */ do { /* Solicitamos el numero por teclado */ printf("\nIntroduzca un numero: "); scanf(" %d", &num); /* Contamos los numeros, los positivos y los impares */ contador++; if (num > 0) positivos++; if (num % 2) impares++; while (getchar() != \n) ; printf("Terminar (S/N)?"); resp = getchar(); } while (resp!=S); printf("Numeros: %d \t Pares: %d \t Positivos: %d \n", contador, contador - impares, positivos); return 0; /* leer hasta */ /* nueva_linea */
Hay distintos estilos a la hora de utilizar las llaves de bloques, bucles, etc. Se recomienda utilizar siempre el mismo. El propuesto es:
6-4
20132014
ya que es visualmente sencillo comprobar los pares de llaves. Cuando tengamos una expresin condicional de bastante longitud, se debe separar en varias lneas, tabulando el comienzo de cada una para que queden alineadas las condiciones:
/* Incorrecto: !No demasiado legible! */ while ( cond1 != "error" && cond2 !=TRUE && cond3 >= 7.5 && cond4 <=10 && cond5 !="esto es verdad") /* correcto */ while (cond1 != "error" && cond2 != TRUE && cond3 >= 7.5 && cond4 <= 10 && cond5 != "esto es verdad")
Debido a que es posible que nuestro chero se utilice en distintas plataformas, y se edite con distintos editores de texto, se debe limitar la longitud de las lneas a 70 caracteres. Por ejemplo, si estamos realizando comentarios al cdigo y sobrepasamos esta longitud se debe introducir un retorno de carro y continuar en la siguiente lnea. Por la misma razn, se deben evitar en los comentarios los caracteres acentuados, la ee, y en general todo carcter ASCII superior al cdigo 127. Lneas de gran longitud debidas a una tabulacin demasiado profunda a veces reejan una pobre organizacin del cdigo.
6.4. Comentarios
Cuando el cdigo y los comentarios no coinciden, probablemente ambos sean incorrectos. Norm Schryer. Los comentarios breves se pueden realizar a la derecha del cdigo, y con una cierta tabulacin para que no se confunda con el cdigo. Se debe procurar que todos los comentarios estn al mismo nivel. Cuando se comenta un bloque de cdigo se puede comentar previamente y al mismo nivel de sangrado.
/* ** bucle que mientras el deposito no esta lleno aumenta ** el caudal siguiendo las tablas de Leibniz-Kirchoff */ while (nolleno) { ... }
Tan malo es no comentar como comentar en exceso o innecesariamente. Hay que evitar comentarios que se puedan extraer o deducir fcilmente del cdigo:
i++;
Cuando se dene una funcin se debe preceder de un comentario con el funcionamiento bsico de la funcin. Se deben comentar brevemente los prototipos de las funciones en los cheros de cabecera, explicando el signicado de los parmetros y el valor devuelto por la funcin. Los detalles del funcionamiento interno estaran en los comentarios de la denicin de la funcin.
20132014
6-5
en vez de
Si se usan variables locales en distintas funciones, pero con el mismo signicado, ayuda a la legibilidad mantener el identicador. En cambio, confunde utilizar el mismo identicador con diferentes signicados. A la hora de denir variables, se debe denir una por lnea. Al mismo tiempo los tipos, identicadores y comentarios deben aparecer al mismo nivel para dar mayor claridad y facilidad de lectura.
int trayecto[NUMPARADAS]; /* comentario sobre trayecto */ int origen; /* comentario sobre origen */ char destino[LONDESTINO]; /* comentario sobre destino */
Evitar nombres que dieran en una sola letra de otros, la confusin es muy probable. Evitar el uso de identicadores que puedan coincidir con bibliotecas del sistema. Para evitar la confusin entre asignaciones y comparaciones, es conveniente utilizar en las comparaciones la constante a la izquierda de la variable para obtener un error en compilacin si relizamos una asignacin (=) en lugar de una comparacin (==): if ( 4 == a ) en lugar de if ( a == 4 ) Las constantes simblicas deben ir en MAYSCULAS. Las funciones, nombres de variables, tipos denidos por el usuario, etiquetas de estructuras y uniones irn en minsculas. Las constantes de una enumeracin van todas en maysculas, o al menos con la primera letra en maysculas. En general, los valores numricos no deben ser usados directamente en el cdigo, dan lugar a lo que se conoce como nmeros mgicos, que hacen el cdigo difcil de mantener y de leer. Para ello se usan las constantes simblicas, o las enumeraciones. Las constantes de tipo carcter deben ser denidas como literales y no como sus equivalentes numricos:
48 0 A
/* INCORRECTO */ /* CORRECTO */
int *pi;
int x, y, *z;
En una estructura o unin, los elementos de sta deben ir cada uno en una lnea, tabulados y con un comentario:
6-6
20132014
};
goto
continue.
goto:
Su uso est restringido en una programacin estructurada, pues tiende a generar programas ilegibles de difcil entendimiento y comprensin, lo que diculta, y en ocasiones impide, cualquier tipo de actualizacin o modicacin sobre los mismos, obligando al programador a desarrollar un nuevo programa o aplicacin. Fundamentos de Programacin, J. Lpez Herranz, E. Quero Catalina, Ed. Paraninfo. In the case of the goto statement, it has long been observed that unfettered use of goto's quickly leads to unmaintainable spaghetti code. Indian Hill Recommended C Style and Coding Standards.
La sentencia
break slo tiene justicado su uso en la selectiva mltiple, es decir, con la sentencia compuesta return),
que adems deber ser la
switch.
Toda funcin tendr un nico punto de retorno (una nica sentencia sistema. Aunque no existen reglas estrictas, el tamao de una funcin no debera superar una o dos pginas en pantalla, para permitir una visualizacin correcta de la misma. Si no es as, posiblemente no se est descomponiendo el programa adecuadamente. Siempre se debe especicar el tipo de retorno de una funcin. Si no lo tiene, especicar ltima lnea de la funcin. Adems, deber devolver el control a la funcin que la invoc, y no a otra o al
void.
En cheros que sean parte de un programa ms grande, hay que procurar que las funciones y variables sean lo ms "locales"posible. Para ello se recomienda usar el calicador
static.
La divisin en funciones se debe hacer de forma que cada funcin tenga una tarea bien denida. Al menos deben codicarse en forma de funcin aquellas tareas con cierta entidad que se realizan dos o ms veces en un programa.
Si el cuerpo de un bucle o funcin es nulo debe estar slo en una lnea, y comentado para saber que no es un error u olvido, sino intencionado:
do-while
20132014
6-7
switch (expresion_entera) { case ETIQ_1: /* igual que ETIQ_2 */ case ETIQ_2: sentencia; break; case ETIQ_3: sentencia; break; }
if-if-else
else
if). if-else if-else if ... se debe codicar con los else if alineados a la izquierda, para
La sentencia
if (expresion) { ... } else if (expr2) { ... } else if (expr3) { ... } else { ... }
6.8. Otros
Es preferible realizar la comparacin con 0 que con 1, ya que la mayora de las funciones garantizan el cero si es falso, y no cero si es verdadero. Por lo tanto:
if (VERDADERO == func())
if (FALSO != func())
(suponiendo que
FALSO
VERDADERO
son 0 y 1 respectivamente).
Las asignaciones embebidas deben evitarse ya que hacen el cdigo ms difcil de leer, como en:
d = (a = b + c) + r;
/* Incorrecto */
6-8
20132014
En cambio, cuando se trate de funciones de la biblioteca estndar que devuelvan un resultado que se utilice en la condicin de una expresin condicional, puede resultar til la llamada en la propia expresin, como en:
++
y
--,
.c
.c NO
.h. Los cheros de cabecera del sistema (p.e. stdio.h, string.h, etc..) se encuentran
en un directorio predeterminado que el compilador conoce, por ello para incluirlos en nuestro programa no es necesario especicar la ruta de los cheros. Cada chero de denicin de funciones debe llevar asociado un chero de cabecera, con los prototipos de las funciones que vayan a ser utilizadas en otros cheros. Ambos cheros deben tener el mismo nombre, pero el de denicin terminado en .c y el de prototipos terminado en .h . No usar los nombres de cheros de cabecera del sistema para los de la aplicacin. No usar caminos absolutos para la inclusin de los cheros de cabecera denidos por el usuario. Usar caminos relativos al directorio de trabajo entrecomillados. Los cheros de cabecera deben ser incluidos en el chero que dene la funcin o variable. De esta forma se hace comprobacin de tipos, ya que el compilador comprueba que la declaracin coincide con la denicin (cada chero .c incluye al menos a su correspondiente .h). No se debe incluir en ellos deniciones de variables. No se deben anidar cheros de cabecera. Para evitar doble inclusin se deben utilizar las rdenes de preprocesado que hacen que se dena una etiqueta en el momento de la primera inclusin del chero de cabecera. Por ejemplo, si se tuviera un chero de cabecera de nombre funcin
cuentaPalabras.h
con el prototipo de la
cuentaPalabras,
#ifndef CUENTAPALABRAS_H #define CUENTAPALABRAS_H void cuentaPalabras(int argc,char **argv, int *palabras,int *total);
#endif
20132014
6-9
De esta manera, si se intentara incluir de nuevo el mismo chero de cabecera, al comprobar que la etiqueta (CUENTAPALABRAS_H) ya est denida, el preprocesador se saltara todo lo que hay hasta el nal del chero (donde est
#endif).
Se deben declarar todas las funciones que se utilicen. Para usar funciones que estn denidas en otros cheros, se deben incluir los cheros de cabecera correspondientes. Si existen funciones locales al chero (slo se usan en ese chero) sus prototipos no irn en el correspondiente propio chero
.h,
.c.
se puede asegurar que la multiplicacin se realiza antes que la suma, pero no en qu orden se llama a las funciones. Pero, qu ocurre con los operadores lgicos && y ||? Son una excepcin, y se asegura la evaluacin de izquierda a derecha. Esto permite sentencias como:
&& c != \n)
Adems, en la evaluacin de una expresin con operadores lgicos, se deja de evaluar cuando se puede asegurar el resultado de la expresin. Por ejemplo, en la siguiente condicin, no se llama a la funcin comprueba si p vale NULL (ya que si p vale NULL la condicin completa es falsa independientemente del valor que devolviera la funcin comprueba):
6-10
20132014
** Operador de incremento */ #include <stdio.h> int main() { int i = 7; printf(" %d \n", i++ * i++); printf(" %d \n", i); return 0;
++
provoca el incremento de la variable despus
de su uso. Este despus es ambiguo, y el estndar lo deja indenido. S aclara que se incrementa antes de ejecutar la siguiente sentencia. Luego el compilador puede optar por multiplicar 7*7, o 7*8. El conocer cmo realiza esta operacin nuestro compilador no debe servirnos de referencia, ya que no conocemos cmo se realiza en otros compiladores.
/* ** ** ** ** ** ** */
Fichero: proto.c Demostracion de error debido a falta de prototipo de funcion. En este caso, al no existir el prototipo, el compilador supone que la funcion multip devuelve un valor de tipo int.
#include <stdio.h> int main() { double prod; int res; prod = multip(7.3, 3.5); printf("\n Prod= %f \n", prod); return 0;
/* ** Fichero: proto_aux.c ** ** Fichero de demostracion del problema de no ** utilizar prototipos de las funciones */ double multip(double a, double b) { return a * b; }
20132014
6-11
%> a.out
6-12
20132014
Parte III
Ejercicios
6-13
Ejercicios tema 3
Codicacin de la Informacin
1. Determinar la representacin binaria de 128. 2. Obtener la representacin binaria de 1254674, utilizando como paso intermedio la codicacin octal y la hexadecimal. 3. Obtener la representacin en complemento a dos utilizando 2 octetos de las siguientes cantidades:
135 13000
13000 4. Obtener la representacin decimal de los siguientes nmeros, sabiendo que estn codicados en complemento a dos:
10010011101010012 ,
3-1
3-2
20132014
Ejercicios tema 4
4-1
4-2
20132014
Ejercicios tema 5
Expresiones
1. Determinar el orden de ejecucin de los operadores en la siguientes expresiones:
int,
4,
expresiones:
5 > 2 3 + 4 > 2 && 3 < 2 x >= y || y > x d = 5 + (6 > 2) X > T ? 10 : 5 x > y ? y > x : x > y
4. Construya una expresin para indicar las siguientes condiciones:
numero es igual o mayor que 1 y menor que 9. ch (variable de tipo carcter) no es una q ni una k. numero est entre 1 y 9, pero no es un 5. numero no est entre 1 y 9.
5. Indique cules de las siguientes proposiciones son ciertas y cules son falsas:
5-1
5-2
20132014
Ejercicios tema 6
Estructuras de Control
1. Repita el programa del ao bisiesto, partiendo de la suposicin de que el ao s es bisiesto. 2. Modique el programa
whileBien.c
while
es:
for, while
do while
nmero que se lee desde el teclado. 4. El siguiente programa imprime los 10 primeros enteros utilizando una sentencia
for:
#include <stdio.h> #define MAX 10 /* Representacion de un bucle desde con mientras y hacer-mientras. */ int main() { int i = 0;
printf("\n Bucle DESDE con for\n"); for (i = 1; i <= MAX; i++) printf(" %d\t", i); return 0;
while.
Repita el ejercicio
Escriba un programa que realice la misma funcin utilizando un bucle utilizando un bucle
do-while.
#include <stdio.h> /* Determina si un numero cualquiera (incluso negativo) es par o impar. */ int main() { int num = 0;
6-1
6-2
20132014
printf("\nIntroduzca el numero: "); scanf(" %d", &num); switch (num % 2) { case 0: printf("\n El numero %d es par\n", num); break; case 1: case -1: printf("\n El numero %d es impar\n", num); break; default: printf("\n El modulo es distinto de 0, 1, -1\n"); break; } return 0;
switch
por una simple sentencia selectiva
if.
6. Realizar un programa que pida un nmero positivo y escriba el cuadrado del mismo si dicho nmero se encuentra comprendido dentro del rango de 0 a 100, en caso contrario dar un mensaje. 7. Programa que calcule el valor medio de los nmeros del 100 al 1000. 8. El siguiente programa (que denominaremos
cuentaLineas.c)
#include <stdio.h> /* Programa que lee el numero de lineas a la entrada. El texto de entrada es una secuencia de lineas, cada una de ellas terminada con un \n (caracter nueva linea). El caracter que marca el final del texto es EOF, que en los terminales se consigue con Ctrl-D. */ int main() { int c = 0; int nl = 0;
c = getchar(); while (c != EOF) { if (c == \n) ++nl; c = getchar(); } printf("El numero de lineas es %d\n", nl); return 0;
cuentaLineas
10. Escriba un programa que pida un nmero y un exponente, ambos enteros, y calcule el valor del nmero elevado al exponente, utilizando la propiedad de que elevar un nmero a un exponente equivale a multiplicar el nmero tantas veces como indique el exponente. 11. Escribir un programa que lea nmeros enteros del teclado y los sume. El programa terminar cuando se introduzca un nmero negativo, imprimindose la suma.
20132014
6-3
12. Realizar un programa que multiplique, sume o reste dos nmeros en funcin de la opcin elegida.
6-4
20132014
Ejercicios tema 7
Funciones
1. Funcin que devuelva la suma de 100 nmeros dados por teclado. 2. Funcin que devuelva verdadero o falso si el nmero dado por teclado es o no menor que el parmetro que se le pase al mdulo. 3. Funcin que recibe un nmero positivo y calcula la suma de los nmeros desde 1 a dicho nmero 4. Funcin que recibe un nmero positivo y calcula la suma y el producto de los nmeros desde 1 a dicho nmero. 5. Funcin que imprima por pantalla la sucesin de Fibonacci hasta el trmino parmetro. Se dene esta sucesin de la siguiente forma:
n,
= =
0 1
= F (n 2) + F (n 1)
recursiva.
/* ** Ejemplo de funcion potencia con recursividad */ #include <stdio.h> double potencia(double, int); int main()
7-1
7-2
20132014
{ double x; int n; printf("Teclee base: "); scanf(" %lf", &x); printf("Teclee exponente: "); scanf(" %d", &n); printf("Potencia %d de %f es : %f\n", n, x, potencia(x, n)); return 0; } double potencia(double x, int n) { double local; if (n == 0) local = 1; else local = (x * potencia(x, n - 1)); return local;
7. En el siguiente ejemplo, se obtiene la divisin entera de dos nmeros aplicando la propiedad de que el
#include <stdio.h> int div_entera(int a, int b); int main() { int dividendo; int divisor; int cociente; printf("Introducir dividendo: "); scanf(" %d", ÷ndo); printf("Introducir divisor: "); scanf(" %d", &divisor); cociente = div_entera(dividendo, divisor); printf("La division entera de %d y %d es %d\n", dividendo, divisor, cociente); return 0; }
int div_entera(int a, int b) { int coc; if (a < b) coc = 0; else coc = 1 + div_entera(a - b, b); return coc;
20132014
7-3
/* ** Factorial de forma iterativa */ #include <stdio.h> int factorial(int n); int main() { int numero, resultado; printf("Introducir numero \n"); scanf(" %d",&numero); resultado = factorial(numero); printf("El factorial del %d es = %d", numero, resultado); return 0; }
int factorial(int n) { int facto = 1; while (n > 0) { facto = facto * n ; n--; } return facto;
***/
/*** Factorial de forma recursiva #include <stdio.h> int factorial(int n); int main() { int numero, resultado;
printf("Introducir numero: "); scanf(" %d", &numero); resultado = factorial(numero); printf("El factorial de %d es %d\n", numero, resultado); return 0; } int factorial(int n) { int x; if (n == 0) /* Caso BASE */ x = 1; else x = n * factorial(n - 1); return x;
7-4
20132014
9. Este ejemplo permite resolver el problema de las torres de Hanoi de forma recuriva: Hay 3 varillas, en la primera de ellas existen n discos concntricos, cada uno de un tamao diferente, y estn ordenados de mayor (el de ms abajo) a menor (el de ms arriba; inicialmente las otras dos varillas estn vacas. Consiste en mover todos los discos de una varilla a otra, sabiendo que un disco no puede situarse encima de otro si el tamao del disco que hay debajo es menor que el tamao del disco de arriba.
#include <stdio.h> void Torres(int n, int i, int j); int main() { int discos; enum {primera = 1, segunda, tercera}; printf("Escribir el numero de discos en varilla origen: "); scanf(" %d", &discos); Torres( discos, primera, tercera); return 0; } void Torres(int discos, int origen, int destino) { int paso; if (discos == 1) printf("Mueve un disco de la varilla %d a la %d\n", origen, destino); else { /* Si las varillas van del 1 al 3, suman 6 */ paso = 6 - origen - destino; Torres( discos - 1 , origen , paso); Torres( 1 , origen , destino); Torres( discos - 1 , paso , destino); }
$> gcc -W -Wall -o hanoi hanoi.c $> ./hanoi Escribir el numero de discos en varilla origen: 4 Mueve un disco de la varilla 1 a la 2 Mueve un disco de la varilla 1 a la 3 Mueve un disco de la varilla 2 a la 3 Mueve un disco de la varilla 1 a la 2 Mueve un disco de la varilla 3 a la 1 Mueve un disco de la varilla 3 a la 2 Mueve un disco de la varilla 1 a la 2 Mueve un disco de la varilla 1 a la 3 Mueve un disco de la varilla 2 a la 3 Mueve un disco de la varilla 2 a la 1 Mueve un disco de la varilla 3 a la 1 Mueve un disco de la varilla 2 a la 3 Mueve un disco de la varilla 1 a la 2 Mueve un disco de la varilla 1 a la 3 Mueve un disco de la varilla 2 a la 3 $> $
Ejercicios tema 8
Punteros
1. En una mquina con tipo
int
float
de ocho octetos, se
int uno = 1; int dos = 2; int tres = 3; int * pEntero = &uno; float primero = 1.1; float segundo = 2.2; float * pReal = &segundo;
3000
3004
3008
300c
3010
3014
3018
301c
3020
3024
uno
dos
tres
pEntera
primero
segundo
pReal
Indique el valor de cada una de las variables tras cada una de las sentencias que siguen:
8-1
8-2
20132014
Ejercicios tema 9
Tipos Agregados
1. Indicar cmo quedara inicializada la tabla
short q[4][3][2] = { { { 1 }, }, { { 2, 3 }, }, { { 4, 5 }, { 6 }, } };
2. dem con:
short q[4][3][2] = { { 1 }, { 2, 3 }, { 4, 5, 6 } };
9-1