You are on page 1of 525

Programacin en C

FRANCISCO JOS GARCA PEALVO


JUAN ANDRS HERNNDEZ SIMN
ROBERTO THERN SNCHEZ
VIVIAN FLIX LPEZ BATISTA
IVN LVAREZ NAVIA

PROGRAMACIN EN C 3 EDICIN
AUTORES:
Francisco J. Garca Pealvo
Juan Andrs Hernndez Simn
Roberto Thern Snchez
Vivian Flix Lpez Batista
Ivn lvarez Navia

Edicin publicada por el Departamento de Informtica y Automtica de la


Universidad de Salamanca.
I.S.B.N: 84-689-3460-7
Depsito Legal: S-1228-2005
Tercera Edicin: Septiembre de 2005
Tercera Reimpresin: Septiembre de 2007
Este libro se distribuye mediante una licencia Creative Commons ReconocimientoNoComercial-CompartirIgual 4.0 (CC BY-NC-SA 4.0). Tal y como se puede leer en
este enlace de la web de Creative Commons, a cambio de respetar algunos derechos
del autor, como citar la autora o no usarlo para fines comerciales o utilizar esta
misma licencia en cualquier obra o material creado a partir del actual, el lector es
libre de copiar o distribuir el material por cualquier medio o formato.

F.J.G.P.
J.A.H.S.
V.F.L.B.
I..N.

A Mary Cruz, Anta y Marco


A Mara Jess, David y Lidia
A mis padres, por inculcarme la perseverancia y
quererme tanto que hasta me dejaron marchar
A mis nias, Rosa, Carmen e Iria

TABLA DE CONTENIDOS
Prlogo _______________________________________________ 1
Prlogo a la Segunda Edicin _____________________________ 3
Prlogo a la Primera Edicin _____________________________ 5
1. Introduccin _________________________________________ 7
1.1. Conceptos bsicos ______________________________________ 7
1.1.1. Definiciones _____________________________________________ 7
1.1.2. Informacin _____________________________________________ 10
1.1.2.1. La informacin en los ordenadores _______________________ 11
1.1.2.2. Codificacin de los datos _______________________________ 12
1.1.3. Informacin _____________________________________________ 13
1.1.3.1. Unidad de entrada_____________________________________ 14
1.1.3.2. Unidad de salida ______________________________________ 14
1.1.3.3. Memoria ____________________________________________ 14
1.1.3.4. Unidad central de proceso ______________________________ 15
1.1.3.5. Buses ______________________________________________ 15
1.1.4. Clasificacin de los ordenadores _____________________________ 16

1.2. Las generaciones de ordenadores ________________________ 19


1.2.1. Primera generacin (1937-1953) _____________________________ 19
1.2.2. Segunda generacin (1954-1962) ____________________________ 20
1.2.3. Tercera generacin (1963-1972) _____________________________ 21
1.2.4. Cuarta generacin (1972-Actualidad) _________________________ 21

1.3. Resumen _____________________________________________ 22


1.4. Lecturas complementarias ______________________________ 23
1.5. Cuestiones y ejercicios _________________________________ 24
1.6. Referencias___________________________________________ 24

2. Sistemas de numeracin y representacin de la informacin _ 25


2.1. Introduccin _________________________________________ 25
2.2. Sistemas de numeracin ________________________________ 27
2.2.1. Definicin de sistema de numeracin _________________________ 28
2.2.2. Generalidades de los sistemas de numeracin posicionales ________ 28
2.2.3. El sistema binario ________________________________________ 30
2.2.3.1. Transformacin de base binaria a base decimal ______________ 31
2.2.3.2. Transformacin de base decimal a base binaria ______________ 31
2.2.3.3. Operaciones aritmticas en el sistema binario _______________ 32
2.2.4. El sistema octal __________________________________________ 33
2.2.4.1. Transformacin de base octal a base decimal _______________ 33
v

vi

TABLA DE CONTENIDOS
2.2.4.2. Transformacin de base decimal a base octal _______________ 33
2.2.4.3. Transformacin de base octal a base binaria ________________ 34
2.2.4.4. Transformacin de base binaria a base octal ________________ 34
2.2.5. El sistema hexadecimal ____________________________________ 34
2.2.5.1. Transformacin de base hexadecimal a base decimal _________ 34
2.2.5.2. Transformacin de base decimal a base hexadecimal _________ 34
2.2.5.3 Transformacin de base hexadecimal a base binaria ___________ 35
2.2.5.4 Transformacin de base binaria a base hexadecimal ___________ 35
2.2.6. Cambio de base K a base P _________________________________ 35

2.3. Mtodos de representacin interna de la informacin _______ 36


2.3.1. Representacin interna de los nmeros reales ___________________ 36
2.3.2. Sistemas posicionales y sistemas de residuos ___________________ 37
2.3.3. Punto fijo sin signo _______________________________________ 38
2.3.4. Punto fijo con signo (signo-magnitud) ________________________ 38
2.3.5. Punto fijo con complemento a la base. Complemento a Dos _______ 39
2.3.6. Complemento a Uno ______________________________________ 41
2.3.7. Punto Fijo BCD __________________________________________ 43
2.3.8. Punto Flotante ___________________________________________ 43

2.4. Cdigos ______________________________________________ 47


2.4.1. Cdigos de E/S __________________________________________ 47
2.4.2. Cdigos redundantes ______________________________________ 50
2.4.2.1. Cdigo de paridad ____________________________________ 50
2.4.2.2. Cdigos correctores de Hamming ________________________ 51
2.4.2.3. Cdigos polinomiales __________________________________ 51

2.5. Resumen _____________________________________________ 52


2.6. Lecturas complementarias ______________________________ 53
2.7. Cuestiones y ejercicios _________________________________ 54
2.8. Referencias___________________________________________ 55

3. Diseo de Programas. Programacin Estructurada _________ 57


3.1. Conceptos bsicos _____________________________________ 57
3.2. Ciclo de vida del software _______________________________ 61
3.2.1. Fase de definicin ________________________________________ 62
3.2.2. Fase de desarrollo ________________________________________ 62
3.2.3. Fase de mantenimiento ____________________________________ 63

3.3. Creacin de un programa_______________________________ 63


3.3.1. Anlisis del problema _____________________________________ 64
3.3.2. Diseo y verificacin del algoritmo __________________________ 66
3.3.2.1. Herramientas para la representacin de algoritmos ___________ 67
3.3.2.2. Ejemplos de diseo de algoritmos ________________________ 71
3.3.3. Codificacin ____________________________________________ 74
3.3.3.1. Lenguajes de programacin _____________________________ 74

TABLA DE CONTENIDOS

vii

3.3.4. Compilacin y enlazado ___________________________________ 82


3.3.4.1. Compiladores ________________________________________ 83
3.3.4.2. Intrpretes ___________________________________________ 86
3.3.5. Verificacin y depuracin __________________________________ 86
3.3.6. Documentacin __________________________________________ 88
3.3.6.1. Documentacin tcnica: Interna y externa __________________ 88
3.3.6.2. Documentacin para el usuario final del programa ___________ 90

3.4. Otros paradigmas de desarrollo _________________________ 90


3.4.1. Orientacin a Objetos _____________________________________ 91
3.4.2. Desarrollo basado en componentes ___________________________ 93
3.4.3. Programacin visual ______________________________________ 93

3.5. Resumen _____________________________________________ 94


3.6. Lecturas complementarias ______________________________ 95
3.7. Cuestiones y ejercicios _________________________________ 97
3.8. Referencias___________________________________________ 98

4. Elementos bsicos de un lenguaje de programacin _______ 101


4.1. Identificadores y palabras reservadas ____________________ 101
4.1.1. Palabras reservadas ______________________________________ 102
4.1.2. Identificador ___________________________________________ 102

4.2. Los datos ___________________________________________ 104


4.3. Descripcin de los tipos de datos bsicos _________________ 105
4.3.1. Tipo entero ____________________________________________ 105
4.3.2. Tipo real ______________________________________________ 108
4.3.3 Tipo carcter ____________________________________________ 109
4.3.4 Tipo cadena de caracteres__________________________________ 111
4.3.5. Tipo lgico ____________________________________________ 111

4.4. Constantes y variables ________________________________ 111


4.4.1. Constantes _____________________________________________ 111
4.4.2. Variables ______________________________________________ 113

4.5. Tipos de datos bsicos en C ____________________________ 114


4.5.1. Tipo entero (int) ________________________________________ 116
4.5.2. Tipo entero corto (short int o simplemente short) _______________ 117
4.5.3. Tipo entero largo (long int o simplemente long) ________________ 117
4.5.4. Tipo entero sin signo (unsigned int) _________________________ 118
4.5.5. Tipo entero corto sin signo (unsigned short) ___________________ 118
4.5.6. Tipo entero largo sin signo (unsigned long) ___________________ 119
4.5.7. Tipos reales (float, double, long double) ______________________ 120
4.5.8. Tipo carcter (char, unsigned char) __________________________ 121
4.5.9. Tipo de dato vaco (void) _________________________________ 123

4.6. Tipo cadena de caracteres en C _________________________ 123

viii

TABLA DE CONTENIDOS

4.7. Constantes simblicas en C ____________________________ 126


4.8. Comentarios en el lenguaje C___________________________ 128
4.9. Estructura de un programa en C ________________________ 129
4.10. Cuestiones y ejercicios _______________________________ 129

5. Expresiones y operadores _____________________________ 133


5.1. Operadores _________________________________________ 133
5.2. Expresiones _________________________________________ 133
5.3. Operadores en C _____________________________________ 135
5.3.1. Operador de asignacin ___________________________________ 135
5.3.2. Operadores aritmticos ___________________________________ 136
5.3.2.1. Conversiones de tipo implcitas _________________________ 139
5.3.3. Operadores de asignacin compuestos _______________________ 141
5.3.4. Operadores relacionales __________________________________ 142
5.3.5. Operadores lgicos ______________________________________ 143
5.3.6. Operadores de tratamiento de bits ___________________________ 145
5.3.7. Operador tamao ________________________________________ 146
5.3.8. Operador de conversin explcita (casting) ____________________ 147
5.3.9. Operador coma _________________________________________ 149
5.3.10. Operador condicional ___________________________________ 150

5.4. Reglas de prioridad de los operadores ___________________ 150


5.5. Funciones de intrnsecas o de biblioteca __________________ 153
5.6. Funciones intrnsecas bsicas en C ______________________ 155
5.6.1. Archivos cabecera en C ___________________________________ 156

5.7. Cuestiones y ejercicios ________________________________ 158

6. Entrada y salida de datos por pantalla __________________ 161


6.1. Operaciones de entrada/salida __________________________ 161
6.2. Funciones de entrada/salida en C _______________________ 162
6.2.1. Funcin puts() __________________________________________ 162
6.2.2. Funcin gets() __________________________________________ 163
6.2.3. Funcin putchar() _______________________________________ 164
6.2.4. Funciones getch() y getche() _______________________________ 165
6.2.5. Funcin getchar() _______________________________________ 165
6.2.6. Funcin printf() _________________________________________ 166
6.2.6.1. Detalles adicionales de la funcin printf() _________________ 171
6.2.7. Funcin scanf() _________________________________________ 176
6.2.7.1. Detalles adicionales de la funcin scanf() _________________ 180

6.3. Cuestiones y ejercicios ________________________________ 184

TABLA DE CONTENIDOS

ix

7. Sentencias de control ________________________________ 189


7.1. Sentencias condicionales o alternativas ___________________ 189
7.1.1. Sentencias alternativas anidadas ____________________________ 192

7.2. Sentencias de salto ____________________________________ 193


7.3. Sentencias repetitivas _________________________________ 194
7.3.1. Bucle mientras ________________________________________ 195
7.3.2. Bucle repetir mientras __________________________________ 196
7.3.3. Bucle desde __________________________________________ 197
7.3.4. Terminacin de los bucles mientras y repetir mientras __________ 199
7.3.4.1. Bucles controlados por contadores _______________________ 199
7.3.4.2. Bucles controlados por acumuladores ____________________ 200
7.3.4.3. Bucles controlados por un valor centinela _________________ 201
7.3.4.4. Bucles controlados por interruptores, banderas o flags _______ 201
7.3.5. Bucles anidados _________________________________________ 202

7.4. Sentencias condicionales o alternativas en C ______________ 204


7.4.1. El operador condicional ? : ________________________________ 211

7.5. Sentencia de salto en C ________________________________ 212


7.6. Sentencias repetitivas en C _____________________________ 213
7.6.1. El bucle mientras en C __________________________________ 213
7.6.2. El bucle repetir mientras en C ____________________________ 217
7.6.3. El bucle desde en C ____________________________________ 220
7.6.4. Bucles anidados en C ____________________________________ 223
7.6.5. La sentencia break dentro de un bucle________________________ 226
7.6.6. La sentencia continue ____________________________________ 226

7.7. Cuestiones y ejercicios ________________________________ 227

8. Estructuras estticas de datos _________________________ 233


8.1. Conceptos bsicos y terminologa _______________________ 233
8.2. Operaciones bsicas con matrices _______________________ 236
8.2.1. Definicin _____________________________________________ 236
8.2.2. Iniciacin en la definicin _________________________________ 237
8.2.3. Acceso secuencial a la matriz (recorrido) _____________________ 237

8.3. Caractersticas de las matrices en C _____________________ 239


8.3.1. Matrices unidimensionales (vectores) en C ____________________ 240
8.3.2. Matrices bidimensionales (tablas) en C _______________________ 247

8.4. Cuestiones y ejercicios ________________________________ 258

9. Punteros __________________________________________ 261


9.1. Almacenamiento de las variables en memoria _____________ 261
9.1.1. Cmo obtener la direccin de una variable en memoria? ________ 262

TABLA DE CONTENIDOS
9.1.2. Dnde almacenar la direccin de una variable? _______________ 263

9.2. Punteros ____________________________________________ 263


9.2.1. Definicin de variables punteros ____________________________ 263
9.2.2. Iniciacin de variables punteros ____________________________ 264
9.2.3. Acceso a variables a travs de punteros ______________________ 264
9.2.3.1. Asignacin de la direccin de memoria de una variable a un
puntero _____________________________________________________ 264
9.2.3.2. Indireccin _________________________________________ 265
9.2.4. Operaciones bsicas con punteros ___________________________ 267
9.2.4.1. Asignacin de punteros _______________________________ 267
9.2.4.2. Comparacin para igualdad/desigualdad __________________ 268

9.3. Punteros en C _______________________________________ 269


9.3.1. Definicin de variables punteros ____________________________ 269
9.3.2. Iniciacin de variables punteros ____________________________ 269
9.3.3. Iniciacin de variables punteros ____________________________ 270
9.3.3.1. Asignacin de la direccin de memoria de una variable a un
puntero _____________________________________________________ 270
9.3.3.2. Indireccin _________________________________________ 271
9.3.4. Operaciones con punteros _________________________________ 273
9.3.4.1. Asignacin de punteros _______________________________ 273
9.3.4.2. Comparacin de punteros ______________________________ 274
9.3.4.3. Aritmtica de punteros ________________________________ 274

9.4. Punteros y matrices en C ______________________________ 277


9.4.1. Punteros y vectores ______________________________________ 278
9.4.2. Punteros y tablas ________________________________________ 281

9.5. Cuestiones y ejercicios ________________________________ 285

10. Cadenas de caracteres en C __________________________ 289


10.1. Concepto de cadena de caracteres en C _________________ 289
10.1.1. Definicin ____________________________________________ 289
10.1.2. Iniciacin _____________________________________________ 290
10.1.3. Lectura y escritura de cadenas de caracteres __________________ 291

10.2. Acceso individual a los caracteres de una cadena _________ 293


10.3. Funciones para el manejo de cadenas de caracteres _______ 294
10.3.1. Funcin strlen() ________________________________________ 296
10.3.2. Funciones strcpy(), strncpy() y strdup() _____________________ 296
10.3.3. Funciones strcat() y strncat() ______________________________ 297
10.3.4. Funciones strcmp() y strncmp() ___________________________ 298
10.3.5. Funciones strchr () y strrchr () _____________________________ 299
10.3.6. Funcin strstr() ________________________________________ 300
10.3.7. Funcin strpbrk() _______________________________________ 301
10.3.8. Funciones strspn() y strscpn() _____________________________ 301
10.3.9. Funciones strupr() y strlwr() ______________________________ 302

TABLA DE CONTENIDOS

xi

10.3.10. Funcin strset() _______________________________________ 303


10.3.11. Funciones atoi(), atol() y atof() ___________________________ 303
10.3.12. Funcin strtok() _______________________________________ 304
10.3.13. Funcin strrev() _______________________________________ 304
10.3.14. Funciones is...() _______________________________________ 305

10.4. Cuestiones y ejercicios _______________________________ 306

11. Funciones en C ____________________________________ 309


11.1. Empleo de funciones _________________________________ 310
11.1.1. Definicin de las funciones _______________________________ 310
11.1.1.1. Cmo devuelve una funcin el resultado calculado _________ 312
11.1.2. Declaracin de las funciones ______________________________ 313
11.1.3. Llamada a una funcin __________________________________ 314

11.2. Tipos de variables segn su lugar de definicin ___________ 316


11.2.1. Especificadores de clase de almacenamiento _________________ 318
11.2.1.1. Variables externas __________________________________ 319
11.2.1.2. Variables registro ___________________________________ 319
11.2.1.3. Variables estticas __________________________________ 320
11.2.1.4. Variables automticas________________________________ 321

11.3. Parmetros formales y parmetros reales _______________ 321


11.4. Efectos laterales en funciones __________________________ 324
11.5. Tipos de paso de parmetros a una funcin ______________ 326
11.5.1. Paso por valor _________________________________________ 327
11.5.2. Paso por referencia _____________________________________ 327
11.5.3. Paso por referencia _____________________________________ 328

11.6. Recursividad _______________________________________ 333


11.6.1. Conceptos bsicos ______________________________________ 333
11.6.1.1. Algoritmo Recursivo ________________________________ 334
11.6.2. Tipos de recursividad ___________________________________ 334
11.6.2.1. Recursividad simple _________________________________ 335
11.6.2.2. Recursividad mltiple________________________________ 335
11.6.2.3. Recursividad anidada ________________________________ 335
11.6.2.4. Recursividad cruzada o indirecta _______________________ 335
11.6.3. Diseos recursivos vs. diseos iterativos ____________________ 336
11.6.4. Criterios a tener en cuenta en la eleccin de una versin recursiva o la
iterativa ______________________________________________________ 338
11.6.5. Pasos para emplear la recursividad _________________________ 339
11.6.6. La pila de recursividad __________________________________ 339
11.6.7. Cundo utilizar la recursividad ____________________________ 347

11.7. Punteros a funciones _________________________________ 347


11.7.1. Uso _________________________________________________ 348
11.7.2. Funciones como parmetros ______________________________ 349

xii

TABLA DE CONTENIDOS

11.8. Argumentos de la funcin main() ______________________ 350


11.9. Cuestiones y ejercicios _______________________________ 350

12. Tipos de datos en C definidos por el usuario _____________ 355


12.1. Estructuras ________________________________________ 355
12.1.1. Declaracin de estructuras ________________________________ 355
12.1.2. Definicin de variables estructura __________________________ 356
12.1.3. Estructuras anidadas ____________________________________ 357
12.1.4. Procesamiento de una estructura ___________________________ 358
12.1.4.1. Referencia a elementos individuales de una estructura ______ 358
12.1.4.2. Iniciacin de una estructura en su definicin ______________ 359
12.1.5. Matrices de estructuras __________________________________ 360
12.1.6. Punteros y estructuras ___________________________________ 362
12.1.7. Operaciones con estructuras como unidades __________________ 363

12.2. Campos de bits _____________________________________ 364


12.3. Uniones ____________________________________________ 365
12.4. Enumeraciones _____________________________________ 367
12.4.1. Referencia a las variables enumeracin______________________ 369

12.5. Construccin typedef ________________________________ 371


12.6. Cuestiones y ejercicios _______________________________ 373

13. Estructuras externas de datos - Ficheros _______________ 377


13.1. Nocin de archivo: estructura jerrquica ________________ 378
13.1.1. Conceptos y definiciones: terminologa _____________________ 379
13.1.1.1. Clave primaria _____________________________________ 379
13.1.1.2. Registro fsico o bloque ______________________________ 380
13.1.1.3. Factor de bloqueo ___________________________________ 380

13.2. Soportes secuenciales y direccionables __________________ 381


13.3. Organizacin de archivos _____________________________ 382
13.3.1. Organizacin secuencial _________________________________ 382
13.3.2. Organizacin directa ____________________________________ 383
13.3.3. Organizacin secuencial indexada _________________________ 383

13.4. Acceso a archivos ___________________________________ 385


13.5. Ficheros en el lenguaje C: Una introduccin _____________ 385
13.6. Operaciones con ficheros en el lenguaje C _______________ 387
13.6.1. Apertura de un fichero ___________________________________ 387
13.6.2. Cierre de un fichero _____________________________________ 389
13.6.3. Vaciado del buffer de lectura/escritura de un fichero ___________ 390
13.6.4. Comprobacin de final de fichero __________________________ 390

TABLA DE CONTENIDOS

xiii

13.7. Ficheros de texto. Funciones de entrada/salida ___________ 390


13.7.1. Funcin para escribir un carcter en un fichero _______________ 390
13.7.2. Funcin para leer un carcter de un fichero __________________ 391
13.7.3. Funcin para escribir una cadena caracteres en un fichero _______ 392
13.7.4. Funcin para leer una cadena caracteres de un fichero __________ 392
13.7.5. Funcin para escribir datos formateados en un fichero __________ 393
13.7.6. Funcin para leer datos formateados de un fichero _____________ 395

13.8. Escritura en archivos de texto _________________________ 396


13.8.1. Fichero de texto sin formato ______________________________ 397
13.8.2. Fichero de texto delimitado _______________________________ 397
13.8.3. Fichero de texto encolumnado_____________________________ 398

13.9. Lectura de archivos de texto __________________________ 398


13.9.1. Fichero de texto sin formato ______________________________ 398
13.9.2. Fichero de texto delimitado _______________________________ 398
13.9.3 Fichero de texto encolumnado _____________________________ 401

13.10. Ficheros binarios ___________________________________ 403


13.10.1. Funciones de lectura y escritura para ficheros binarios _________ 403
13.10.2. Otras funciones para el manejo de ficheros binarios ___________ 404
13.10.3. Ejemplos de manejo de ficheros binarios ___________________ 406

13.11. Cuestiones y ejercicios ______________________________ 411

14. Gestin de la memoria dinmica ______________________ 413


14.1. Asignacin dinmica de memoria ______________________ 413
14.1.1. Creacin de una variable dinmica _________________________ 414
14.1.2. Destruccin de una variable dinmica _______________________ 415

14.2. Asignacin dinmica de memoria en C __________________ 416


14.2.1. Funcin calloc() ________________________________________ 416
14.2.2. Funcin malloc() _______________________________________ 417
14.2.3. Funcin realloc() _______________________________________ 418
14.2.4. Funcin free() _________________________________________ 419
14.2.5. Creacin de matrices dinmicas ___________________________ 420
14.2.5.1. Vectores dinmicos _________________________________ 420
14.2.5.2. Matrices dinmicas __________________________________ 422

14.3. Estructuras de datos dinmicas ________________________ 428


14.3.1. Definicin de datos _____________________________________ 428
14.3.2. Fundamentos tericos ___________________________________ 429
14.3.2.1. Lista enlazada ______________________________________ 431
14.3.2.2. Puntero nulo _______________________________________ 431
14.3.2.3. Punteros de cabecera y de cola _________________________ 431
14.3.2.4. Clasificacin de las listas enlazadas _____________________ 432

14.4. Operaciones con listas simplemente enlazadas ____________ 433


14.4.1. Declaracin de un nodo __________________________________ 434

xiv

TABLA DE CONTENIDOS
14.4.1.1. El operador - > de seleccin de miembro _________________ 434
14.4.2. Construccin de una lista ________________________________ 435
14.4.3. Insercin de un elemento en una lista _______________________ 438
14.4.3.1. Insercin de un nuevo nodo en la cabeza de la lista _________ 438
14.4.3.2. Insercin de un nuevo nodo que no est en la cabeza de la lista 444
14.4.3.3. Insercin por la cola _________________________________ 445
14.4.3.4. Insercin en una posicin dada_________________________ 452
14.4.3.5. Insercin ordenada __________________________________ 453
14.4.4. Supresin de un nodo en la lista ___________________________ 453
14.4.4.1. Borrado de la cabeza ________________________________ 456
14.4.4.2. Borrado de la cola __________________________________ 457
14.4.5. Bsqueda de un elemento en la lista ________________________ 459

14.5. Lista doblemente enlazada ____________________________ 464


14.5.1. Procedimiento Inserta ___________________________________ 465
14.5.1.1. Insercin por cabeza _________________________________ 465
14.5.1.2. Insercin en una posicin determinada __________________ 466
14.5.2. Procedimiento Suprime __________________________________ 469
14.5.2.1. Borrado de la cola __________________________________ 469
14.5.2.2. Borrado de la primera aparicin de un elemento ___________ 470

14.6. Otras estructuras de datos dinmicas ___________________ 472


14.6.1. Listas circulares ________________________________________ 473
14.6.2. Pila _________________________________________________ 473
14.6.3. Cola _________________________________________________ 477

14.7. Resumen ___________________________________________ 480


14.8. Cuestiones y ejercicios _______________________________ 481

15. Gestin del cdigo fuente en proyectos software en C _____ 483


15.1. Gestin de proyectos con MAKE _______________________ 485
15.1.1. Proyecto basado en varios ficheros _________________________ 487
15.1.2. Funcionamiento bsico de MAKE _________________________ 489
15.1.3. Facilidades que proporciona MAKE ________________________ 493
15.1.4. Objetivos ficticios ______________________________________ 496

15.2. Entornos de Desarrollo Integrado ______________________ 497


15.2.1. Creacin de un proyecto software __________________________ 499
15.2.2. Edicin del cdigo fuente ________________________________ 503
15.2.3. Construccin de un ejecutable _____________________________ 504
15.2.4. Utilizacin del depurador ________________________________ 508

15.3. Conclusiones _______________________________________ 510


15.4. Bibliografa ________________________________________ 510

Prlogo
Con sta llegamos a la tercera edicin de este libro, que entre otras cosas ratifica el
alto grado de aceptacin que est obteniendo en las diferentes asignaturas que se toma
como texto de referencia o de apoyo, lo que viene demostrando la vigencia del
lenguaje C como lenguaje de programacin para el aprendizaje, pero tambin para el
desarrollo de sistemas reales.
Esta tercera edicin se caracteriza por estar dedicada a la limpieza del texto, es
decir, no hay cambios en la estructura del libro con respecto a la segunda edicin,
pero si se ha hecho un esfuerzo por eliminar erratas, actualizar algunas referencias y
URLs y reescribir diversos fragmentos buscando una mejor legibilidad y
comprensin. Desde aqu agradecer a todos los profesores y alumnos que nos han
hecho las oportunas indicaciones para mejorar este texto, y de nuevo invitar a que
cualquier problema o sugerencia de mejora se nos sea comunicada para hacer de este
libro algo vivo y que se adapta a las necesidades de su pblico.

Los autores Septiembre de 2005


Departamento de Informtica y Automtica
Universidad de Salamanca

-1-

Prlogo a la Segunda Edicin


Con la primera edicin de este libro se persegua poner al alcance de nuestros
alumnos un material cuidado que les guiara en sus primeros pasos en el apasionante
mundo de la programacin, tomando como base un lenguaje clsico, pero a la vez
potente y hoy por hoy imprescindible, como es el lenguaje C. Y creemos que hemos
conseguido nuestro objetivo al verse prcticamente agotada la primera edicin.
Pero inevitablemente la primera edicin no era perfecta, en ella aparecieron las
mayores pesadillas de los autores, las erratas, que hemos ido detectando y
corrigiendo, con la inestimable ayuda (as como interesantes aportaciones) de nuestros
propios alumnos. A todos los que colaboraron con nosotros, gracias.
As, en esta segunda edicin hemos corregido todas las erratas detectadas y se han
actualizado algunos contenidos. El captulo que ms cambios ha sufrido ha sido el
captulo 14, en el que se ha unificado el estilo de los grficos que ilustran las
estructuras de memoria dinmica, pero adems se han reescrito diferentes apartados y
fragmentos de cdigo para lograr que estos conceptos, siempre inicialmente
complicados, sean ms accesibles y comprensibles para los alumnos.

Los autores Septiembre de 2004


Departamento de Informtica y Automtica
Universidad de Salamanca

-3-

Prlogo a la Primera Edicin


Cada vez es ms frecuente que ms titulaciones, a parte de las Ingenieras en
Informtica, incorporen conceptos de programacin de ordenadores en sus planes de
estudios. Claros ejemplos de esta poltica son las Licenciaturas en Fsica o en
Matemticas, diversas Ingenieras como Industriales o Telecomunicaciones, o
Diplomaturas con Estadstica.
Existen diversas posibilidades a la hora de afrontar esta materia, tanto en el mbito
metodolgico como en el mbito prctico, pero es hbito comn basarse en un
determinado lenguaje de programacin de alto nivel que de cobertura a los conceptos
impartidos en la asignatura. Entre estos lenguajes, el lenguaje C es una eleccin
comn por el hecho de ser un lenguaje de propsito general, aunque que una
orientacin clara hacia la programacin de sistemas, que, adems, transmite muy bien
los principios de la programacin estructurada.
En este libro se recoge la experiencia de los autores en la enseanza de los
principios de programacin a alumnos de diversas titulaciones en la Universidad de
Salamanca, hacindolo idneo para la planificacin de una asignatura de introduccin
a la programacin, ya sea a un nivel ms profundo, adecuado para las Ingenieras, ya
sea a un nivel ms introductorio, adecuado para el resto de las titulaciones
mencionadas.
Los contenidos del libro se organizan en quince captulos, que se pueden clasificar
en tres grupos, as los tres primeros captulos sirven como introduccin al lector,
introducciendo conceptos bsicos relacionados con la informtica en general (captulo
1), con los sistemas de numeracin (captulo 2) y con la programacin en general
(captulo 3). El segundo bloque sera el destinado a la programacin en C pero en un
nivel introductorio, y estara formado por los captulos del 4 al 13. En el ltimo
bloque, formado por los dos ltimos captulos, se abordan temas ms avanzados
siguiendo con el denominador comn de la programacin en C, as el captulo 14 trata
el tema de la programacin haciendo uso de memoria dinmica y el captulo 15 la
gestin de proyectos software en C, haciendo uso de tcnicas que facilitan la
compilacin separada, ya sea empleando rdenes clsicas, como es el caso de make,
o entornos de desarrollo integrados, con potentes interfaces grficas.

Los autores Agosto de 2003


Departamento de Informtica y Automtica
Universidad de Salamanca

-5-

1. Introduccin
La informtica, y los conceptos que de ella se derivan, se han introducido en la vida
cotidiana de millones de personas. El computador ha dejado de ser un elemento de
uso exclusivo para el personal de laboratorios, grandes empresas o fanticos de la
informtica, para sigilosamente penetrar en los hogares y en los pequeos y medianos
negocios. Actualmente, quien ms quien menos tiene algn contacto con el mundo de
la informtica, ya sea de forma directa manejando un computador, ya sea de forma
indirecta a travs del bombardeo de los medios de comunicacin o manejando alguno
de los mltiples electrodomsticos digitales que hoy en da existen en cualquier hogar
medio.
Todo esto influye en que cualquier persona se convierta en un usuario potencial de
computadores, sin que por ello tenga que convertirse en un experto en informtica,
aunque si debe contar con una cultura informtica bsica. Esta idea ha calado
profundamente en la Universidad espaola, donde ya son muchas las titulaciones que
en sus planes de estudios cuentan con al menos una asignatura troncal u obligatoria de
introduccin a la informtica.
En este primer captulo se va a proceder a introducir los conceptos bsicos que se
manejarn a lo largo de la asignatura, para lo cual el captulo se ha organizado en tres
apartados principales. El primero de ellos aborda las definiciones de la terminologa
bsica de la informtica, haciendo un especial hincapi en el concepto de informacin
y de su procesamiento automtico. El apartado dos se dedica a presentar las diferentes
generaciones de ordenadores. Por ltimo, el tercer apartado cierra el captulo con unas
conclusiones a modo de resumen del mismo.

1.1. Conceptos bsicos


La informtica, aunque es una ciencia de corta edad, junto a la electrnica han sido las
disciplinas que han experimentado una mayor evolucin en la segunda mitad del siglo
XX. Esto, unido al impacto social y cultural que la informtica est teniendo en la
sociedad actual, hace que la terminologa y conceptos bsicos que de ella se derivan
sean de uso cotidiano. Es por ello, que se va a comenzar esta asignatura con un repaso
a los conceptos que se suponen los cimientos o la base de la informtica.

1.1.1. Definiciones
El trmino informtica es un vocablo que se deriva de la palabra francesa
informatique, formada por la contraccin de otras dos palabras informacin y
automtica.
Se puede definir informtica como el conjunto de conocimientos cientficos y
tcnicas que hacen posible el tratamiento automtico de la informacin por medio de
ordenadores [RAE, 2001].

-7-

Programacin en C

Otra definicin de informtica puede ser el campo de conocimiento que abarca


todos los aspectos del diseo y uso de los computadores [Prieto et al., 2002].
Existen otros trminos para referirse a la informtica, entre los cuales cabe destacar
ciencia de la computacin o ciencia de los computadores ya que es la traduccin del
trmino ingls Computer Science, el cual se utiliza con asiduidad en la bibliografa
anglosajona para referirse a la informtica.
La informtica es una disciplina que tiene un importante cuerpo de desarrollos de
investigacin tericos y empricos, adems de tener una enorme incidencia en el
campo de las aplicaciones reales utilizando los mtodos y herramientas producidos en
dichos esfuerzos de investigacin, por lo que se puede considerar a la informtica
como una ciencia o como una ingeniera. As, en [Tucker et al., 1994] se define la
disciplina de la informtica como el cuerpo de conocimiento que trata del anlisis,
diseo, implementacin, eficiencia y aplicacin de procesos que transforman la
informacin.
Si el objetivo final de la informtica es el procesamiento automtico de la
informacin, se necesita la mquina capaz de realizar dicho tratamiento, esto es el
ordenador o computador.
Existe cierta controversia por la utilizacin de uno u otro trmino [Vaquero, 1999].
Para dilucidar este conflicto se ha recurrido al diccionario de la Real Academia
Espaola [RAE, 2001], y ambos vocablos estn admitidos como vlidos 1,
definindose (dentro del contexto de la informtica) como:
Computador: Aparato electrnico que realiza operaciones matemticas
y lgicas con gran rapidez [RAE, 2001].
Ordenador2: Mquina electrnica dotada de una memoria de gran
capacidad y de mtodos de tratamiento de la informacin, capaz de
resolver problemas aritmticos y lgicos gracias a la utilizacin
automtica de programas registrados en ella [RAE, 2001].
A la vista de ambas definiciones, parece que el trmino ordenador se ajusta mejor a
la definicin de informtica dada por la Real Academia, y por lo tanto ser el ms
ampliamente utilizado a lo largo de este libro, sin descartar el uso puntual de las
palabras computador y computadora como sinnimos de ordenador.
Otras definiciones de ordenador que pueden encontrarse en la bibliografa son:
(1) Todo aparato o mquina destinada a procesar informacin, entendindose
por proceso las sucesivas fases, manipulaciones o transformaciones que
sufre la informacin para resolver un problema determinado [Miguel,
1994].
(2) Mquina capaz de aceptar unos datos de entrada, efectuar con ellos
operaciones lgicas y aritmticas, y proporcionar la informacin resultante
a travs de un medio de salida; todo ello sin intervencin de un operador
humano y bajo el control de un programa de instrucciones previamente
almacenado en el propio ordenador [Prieto et al., 2002].
1

Al igual que los trminos ordenadora (de raro uso en la literatura en castellano) y
computadora (de uso tan generalizado como su homnimo masculino).
2 Como curiosidad decir que el trmino ordenador fue acuado en Francia en 1956 por el
profesor Jacques Perret, haciendo referencia de forma genrica a un sistema para procesar
datos.

Introduccin

(3) Mquina digital electrnica para el tratamiento de la informacin.


La definicin (1) es muy general, no tocando aspectos relativos a la tecnologa
empleada para la construccin del ordenador. Hoy en da la tecnologa empleada se
basa en la electrnica (aunque en los orgenes de los computadores se utilizaban las
tecnologas mecnica y electromecnica).
Por su parte la definicin (2) presenta el concepto de ordenador desde el punto de
vista de caja negra, la cual presenta unas salidas que son funcin de sus entradas, esto
es, el ordenador recibe una serie de datos en su entrada, los procesa y presenta unos
datos de salida (ver Figura 1.1).

Entradas
Datos
+
Instrucciones

Ordenador

Salidas
Datos

Figura 1.1. Ordenador como caja negra que recibe, procesa y ofrece datos

Por ltimo, de la definicin (3), aunque escueta, conviene hacer un repaso por el
significado de cada uno de los trminos que la componen. As, la palabra digital hace
referencia a que los ordenadores trabajan con datos en formato digital; es decir, en
cdigos que representan las letras o los dgitos de los nmeros. Cualquier otro tipo de
datos, como puedan ser grficos o sonido, se almacena tambin en formato digital. El
formato digital significa que se emplea el sistema binario, esto es, los datos se
almacenan en el ordenador por secuencias de 0s y 1s. Esto es as porque el ordenador
es un conjunto de sistemas fsicos que slo pueden entender dos estados. La palabra
electrnica implica que un ordenador se construye usando componentes electrnicos
de estado slido, conocidos por circuitos integrados, o ms comnmente por chips. El
trmino tratamiento de la informacin es un concepto general que da cabida a un
enorme rango de actividades y trabajos que un ordenador puede llegar a realizar. Por
ltimo, la palabra mquina hace referencia a que los ordenadores estn en la misma
lnea de sucesin que otras mquinas menos sofisticadas y, como una mquina que es,
puede funcionar bien o mal, pero no es infalible3.
Cualquier ordenador se apoya en dos pilares bsicos que lo definen en s mismo y
le dan sentido a su existencia. Estos dos pilares son el soporte fsico y el soporte
lgico.
El soporte fsico, ms conocido por hardware4, es la mquina en s, la parte fsica y
tangible de un ordenador. As pues, puede definirse el hardware como el conjunto de
dispositivos fsicos (cables, armarios...) y circuitos electrnicos (tarjetas de red,
controladoras, circuitos integrados...) que constituyen un ordenador.
3

En este sentido se poda entrar en polmicas sobre la inteligencia, sentimientos, etc. que un
ordenador puede tener o experimentar, pero esto se aleja de los objetivos de este libro, y se
dejar para los defensores y detractores de la Inteligencia Artificial fuerte. En este sentido, se
recomienda a los lectores interesados por el tema la lectura del libro La nueva mente del
emperador [Penrose, 1991].
4 Este anglicismo significa literalmente herrajes o partes duras.

10

Programacin en C

El soporte lgico, que usualmente es conocido con el trmino software5, es la parte


que permite la explotacin del hardware. El software se puede definir como el
conjunto de programas que dirigen el funcionamiento del ordenador.
El software y el hardware se encuentran ntimamente relacionados entre s. El
hardware establece la plataforma donde el software puede ejecutarse, y el software le
da sentido a unos elementos fsicos al ponerlos en explotacin.
A la vista de las definiciones de software y hardware puede parecer que son dos
conceptos claramente delimitados, pero esta frontera se diluye con el concepto de
firmware, que se puede definir como el conjunto de microprogramas empotrados en
memorias de slo lectura y destinados a resolver un proceso o problema particular y
frecuente. Por lo tanto el firmware es un soporte para programas que no se pueden
alterar, siendo un estadio intermedio entre hardware y software.

1.1.2. Informacin
La informacin es un trmino de acepcin general que abarca hechos y
representaciones que pueden no estar relacionados. La informacin puede ser trivial o
trascendente, verdadera o falsa.
La informacin es la materia prima que las tcnicas y mtodos informticos permiten elaborar,
tratar, manipular o procesar.

Aunque el concepto que encierra la palabra informacin es de sobra conocido y


aceptado se van a presentar a continuacin algunas definiciones de este trmino.
(1) Conjunto de hechos o elementos de conocimiento (posedos en un momento
determinado por un sujeto dado) que son objeto de una comunicacin, de
una interpretacin o de un tratamiento.
(2) Comunicacin o adquisicin de conocimientos que permiten ampliar o
precisar los que se poseen sobre una materia determinada [RAE, 2001].
(3) Conjunto de datos integrados en un contexto que da pie a su evaluacin
cualitativa y cuantitativa [Goi, 1986].
La informtica se ocupa del tratamiento de la informacin, pero para que la
informacin pueda ser tratada tiene que ser cuantificable, es decir, ha de poder
medirse.
Sea E un suceso que puede presentarse con una probabilidad P(E). Cuando E tiene
lugar se dice que se ha recibido I(E) unidades de informacin.

I (E)

log

1
P( E )

Ecuacin 1.1. Medida de la informacin

La eleccin de la base del logaritmo que interviene en la definicin equivale a


elegir una determinada unidad de informacin, ya que:

Este anglicismo que se deriva del vocablo ingls soft (blando) tiene una difcil traduccin al
espaol. En algunos libros aparece referenciado con la palabra logical, aunque este trmino no
tiene demasiada aceptacin.

Introduccin 11

log a x

1
log b x
log b a

Ecuacin 1.2. Cambio de base en los logaritmos

Si se utilizan logaritmos decimales la unidad de informacin es el Hartley6. Si se


emplean logaritmos neperianos o naturales la unidad de informacin recibe el nombre
de nat.

I (E)

ln

1
nats
P( E )

Ecuacin 1.3. Definicin de nat

En el caso de usar logaritmos en base dos, la unidad correspondiente se denomina


bit.

I (E)

lg 2

1
bits
P( E )

Ecuacin 1.4. Definicin de bit

Es importante recalcar el hecho de que si P(E) = 1/2 entonces I(E) = 1 bit. Esto
permite definir al bit como la cantidad de informacin obtenida al especificar una
de dos posibles alternativas igualmente probables.
Aunque en la literatura especializada en informtica se suelen utilizar
indistintamente los conceptos de dato e informacin, stos no son sinnimos.
Un dato se define como una representacin de la informacin. La informacin
codificada mediante un conjunto de smbolos en la forma adecuada para ser objeto
de tratamiento. Los datos son conjuntos de smbolos utilizados para expresar o
representar un valor numrico, un hecho, un objeto o una idea; en la forma adecuada
para ser objeto de tratamiento [Prieto et al., 2002].
Es importante recalcar el hecho de que los datos por s solos no tienen significado,
adquirindolo nicamente cuando se hace una interpretacin de los mismos.
Datos + Interpretacin = Informacin til
1.1.2.1. La informacin en los ordenadores
El concepto de informacin en los ordenadores viene dado en funcin de un proceso o
tratamiento de datos. Dicho proceso es en una elaboracin consistente en la seleccin,
tratamiento y combinacin de los datos, con objeto de obtener un mensaje
significativo para alguien.
Los datos van a ser por tanto la representacin de la informacin que se tiene
acerca de un hecho. En relacin con el tratamiento se puede clasificar los datos en:
Datos elementales o datos base: Se trata de la constatacin de una
situacin o de un hecho, no habiendo habido tratamiento alguno.
Datos elaborados o resultantes: Son aqullos que proceden de la
combinacin de datos base previamente relacionados para su posterior

Hartley fue el primero que sugiri la medida logartmica de la informacin.

12

Programacin en C

utilizacin en la toma de decisiones. Es en lo que la prctica se conoce


como resultados.
Datos que definen las operaciones de tratamiento: Representan la
lgica de accin; es decir, definen la naturaleza del tratamiento a
realizar. Constituyen las instrucciones7 que han de regir los procesos
para obtener los datos resultantes.
Como se ha reseado con anterioridad, la interpretacin de los datos resultado de
un proceso computacional es lo que proporciona la informacin til.
Las ventajas que tiene el procesamiento de los datos con un ordenador son muy
grandes, destacando especialmente el gran volumen de datos que puede llegar a
procesarse, la alta velocidad con la que se tratan y el grado de fiabilidad de los
resultados.
Un ordenador trabaja con un conjunto de rdenes precisas, pero no tiene capacidad
de discernir, hacindose necesario que los datos le sean presentados con una
estructuracin predeterminada.
Los datos normalmente se organizan dentro de unas entidades denominadas
archivos o ficheros. Un archivo o fichero se puede definir como un conjunto de
informacin del mismo tipo (homognea) referente a unos determinados elementos,
tratada como una unidad de almacenamiento y organizada de forma estructurada
para la recuperacin de un elemento o dato individual [Prieto et al., 2002].
Con una organizacin de los datos en ficheros se les da a stos un criterio de
pertenencia que les permite ser identificados como miembros o elementos del
conjunto que constituye el archivo.
1.1.2.2. Codificacin de los datos
La existencia de grandes cantidades de datos, as como la necesidad de representarlos
dentro de los ordenadores en un formato que slo admite dos valores (cdigo binario),
exige la utilizacin de sistemas que los clasifiquen y normalicen. Este es el objetivo
de la codificacin de los datos, que consiste en una transformacin que representa los
elementos de un conjunto mediante los de otro, de forma tal que a cada elemento del
primer conjunto le corresponda un elemento distinto del segundo [Prieto et al., 2002].
Se denomina cdigo a la representacin simplificada y simblica de un dato
mediante un conjunto de smbolos y reglas.
Un cdigo tiene que cumplir una serie de condiciones:
Debe ser sencillo y prctico de usar.
No bebe ser ambiguo, de forma que cada objeto tiene que tener su
propio cdigo y cada cdigo referirse a un solo objeto.
Debe facilitar la clasificacin de los datos.
Debe aportar flexibilidad para su expansin.
La codificacin con dicho cdigo debe ser significativa; es decir, el
cdigo en s debe reflejar una serie de funciones y propiedades del
objeto codificado a efectos de conseguir un tratamiento lgico de los
datos.

Un conjunto de dichas instrucciones es a lo que se denomina programa.

Introduccin 13

Tal y como se tratar en el siguiente captulo, en el interior de los ordenadores la


informacin se almacena y se transfiere mediante cdigos binarios - compuestos por
elementos que slo pueden tomar dos valores posibles (el 0 y el 1). En la entrada y
salida de los ordenadores se efectan de forma automtica los cambios de cdigo
oportunos para que en el exterior el usuario maneje e interprete los datos
directamente.
La unidad ms elemental de informacin dentro de un ordenador es un valor
binario que recibe el nombre de bit. El origen de este trmino est en la contraccin
de dos palabras inglesas que significan dgito binario (binary digit). De esta forma se
puede decir que un bit es una posicin o variable que toma el valor 0 1.
Se podra pensar que la unidad de almacenamiento de informacin mnima dentro de un
ordenador es un bit, pero por motivos obviamente prcticos esto no se hace as, al ser un bit
algo sumamente pequeo con relacin a la cantidad de datos que se almacenan en un
ordenador. As pues, las unidades funcionales de un ordenador suelen disearse para trabajar
con una cadena de bits de un determinado tamao, existiendo unos tamaos de representacin
de datos privilegiados con una denominacin estandarizada.

Entre todos los tamaos privilegiados cabe destacar la unidad denominada byte u
octeto que expresa la cantidad de informacin empleada en un ordenador para
representar un carcter alfanumrico8. En su aceptacin ms generalizada consiste en
una cadena de ocho bits, aunque algunos ordenadores antiguos emplearon un nmero
diferente de bits.
Tomando el byte como unidad base aparecen otras unidades para medir los datos.
La primera unidad es el nibble o semibyte que es una cadena de cuatro bits.
Dado que el byte sigue siendo una unidad relativamente pequea, es ms usual
utilizar mltiplos suyos:
1 Kiliobyte (KB) = 210 bytes = 1.024 bytes
1 Megabyte (MB) = 220 bytes = 1.048.576 bytes
1 Gigabyte (GB) = 230 bytes = 1.073.741.824 bytes
1 Terabyte (TB) = 240 bytes = 1.099.511.627.776 bytes
1 Petabyte (PB) = 250 bytes

1.1.3. Informacin
El esquema funcional de un ordenador de hoy en da contina fiel a la arquitectura
propuesta por John von Neumann en 1945. Segn esta arquitectura un ordenador
est formado por un conjunto de unidades funcionales especializadas que se
encuentran interconectadas entre s.

Por este motivo carcter es otro sinnimo de byte.

14

Programacin en C

Figura 1.2. Esquema de las unidades funcionales de un ordenador

Como puede apreciarse en la Figura 1.2, un ordenador consta de las siguientes


unidades funcionales:
1.1.3.1. Unidad de entrada
Son los dispositivos por donde se introducen al ordenador datos e instrucciones. Estos
elementos de entrada transforman los datos de entrada en seales binarias. Un
computador puede tener varias unidades de entrada. Ejemplos de dispositivos o
perifricos de entrada son un teclado, una lectora de cdigos de barras...
1.1.3.2. Unidad de salida
Una unidad de salida est formada por cada uno de los dispositivos o perifricos por
donde se obtienen los resultados de los programas ejecutados en un ordenador. Un
ordenador puede tener varias unidades de salida, dedicndose la mayora de ellas a
transformar seales binarias en caracteres escritos o visualizados. Ejemplos de
unidades de salida son una pantalla o monitor, una impresora...
1.1.3.3. Memoria
La memoria es la unidad funcional del ordenador donde se almacenan tanto datos
como instrucciones. Existen dos tipos bsicos de memoria.
Memoria principal: Est compuesta por un conjunto de celdas contiguas e
idnticas en tamao. En cualquier instante se puede seleccionar cualquiera de
las celdas directamente, sin tener que por ello pasar por las celdas anteriores;
esto es lo que se denomina acceso aleatorio. Cuando una memoria slo se
puede leer, y por lo tanto su contenido es permanente (al desconectar el
ordenador no se pierde), se habla de memoria ROM (Read Only Memory).
Cuando en la memoria se pueden hacer operaciones de lectura y de escritura

Introduccin 15

se est haciendo referencia a una memoria RAM (Random Access Memory),


siendo sta una memoria voltil (su contenido desaparece al apagar el
ordenador). Para que un programa se pueda ejecutar debe estar cargado en la
memoria principal. En los ordenadores actuales la memoria principal est
formada por circuitos integrados.
Memoria masiva auxiliar: Tambin conocida como memoria secundaria o
memoria externa. Aunque ms lenta que la memoria principal, la memoria
secundaria tiene una mayor capacidad para almacenar informacin de forma
permanente. Su misin es la de guardar masivamente datos, utilizndose para
ello discos de diferentes tipos (magnticos, pticos y magneto-pticos) y
cintas magnticas.
1.1.3.4. Unidad central de proceso
Se denomina unidad central de proceso de un ordenador (tambin llamada procesador
o microprocesador o referenciada por las siglas del trmino correspondiente en ingls
CPU Central Processing Unit) al conjunto de la unidad de control y la unidad
aritmtico-lgica (junto con los registros oportunos para almacenar los operandos y
los resultados).
La unidad aritmtico-lgica (normalmente referenciada por las siglas del
correspondiente trmino en ingls ALU) contiene la circuitera electrnica adecuada
para realizar operaciones aritmticas (suma, resta...) y lgicas (AND, OR...).
Dado que el byte es una unidad relativamente pequea, la ALU utiliza otra unidad
superior denominada palabra. Una palabra est formada por un nmero entero de
bytes (1, 2, 4, 8, 16,32, 64...).
La unidad de control detecta seales de estado procedentes de las distintas
unidades, las cuales indican su condicin de funcionamiento. Se encarga de leer las
instrucciones mquina almacenadas en la memoria principal que conforman el
programa en ejecucin, generando las seales de control oportunas dirigidas a cada
una de las unidades, monitorizando las operaciones que conlleva la ejecucin de la
instruccin en curso.
La unidad de control contiene un reloj que sincroniza todas las operaciones
elementales de un ordenador. El perodo de dicha seal recibe el nombre de tiempo
de ciclo. La frecuencia de este reloj se mide en millones de ciclos por segundo o
megahercios (MHz), siendo este un parmetro muy importante para determinar la
velocidad de funcionamiento de un ordenador.
1.1.3.5. Buses
Aunque los buses no son una unidad funcional del ordenador, conviene resaltar su
importancia porque son los caminos que interconectan dichas unidades funcionales.
En relacin con los buses existe un parmetro muy importante que es el ancho de
banda, que representa la cantidad de informacin transferida por segundo de una
unidad a otra.
As pues, la longitud de la palabra, el tiempo de ciclo, el ancho de banda y la
capacidad de la memoria principal, constituyen los principales factores para
determinar la potencia de un ordenador [Prieto et al., 2002].

16

Programacin en C

1.1.4. Clasificacin de los ordenadores


El criterio ms genrico por el que se pueden clasificar las mquinas de clculo es el
que las distingue segn la forma que toman las magnitudes fsicas que representan los
datos de entrada y salida, tenindose as calculadoras analgicas (donde los datos se
representan por seales fsicas proporcionales a los valores de las variables),
calculadoras digitales u ordenadores (donde los datos se representan por seales
elctricas discretas, no continuas, que slo pueden tomar dos valores) y calculadoras
hbridas (donde aparecen elementos analgicos y digitales).
No obstante, el inters del presente libro se centra en los ordenadores, por lo que
interesa buscar criterios de clasificacin que se centren exclusivamente en ellos.
El primer criterio de clasificacin de los ordenadores viene de la mano del
propsito para el que fueron construidos, distinguindose ordenadores de uso
general (aqullos que se emplean para distintos tipos de aplicaciones) y ordenadores
de uso especfico (aqullos que se utilizan para una aplicacin concreta, como puede
ser una vdeo consola o el ordenador de un coche, este ltimo es un ejemplo de lo que
se denomina ordenadores embebidos).
Sin embargo, la clasificacin ms frecuente de los ordenadores es aqulla que se
hace en funcin de la potencia, capacidad o tamao del ordenador. Aunque es una
clasificacin con unas fronteras bastante difusas y que se ve alterada con el paso del
tiempo, tradicionalmente se distinguen de mayor a menor potencia los siguientes tipos
de ordenadores:
Supercomputadores: Ordenadores rpidos, con gran longitud de palabra,
capaces muchos de ellos de trabajar directamente con vectores de datos
(ordenador vectoriales), estando constituidos por varias unidades centrales de
proceso trabajando en paralelo. Su uso est destinado a clculos de gran
complejidad, que requieren mucha velocidad, como por ejemplo realizar
predicciones meteorolgicas. Algunos ejemplos de estos ordenadores son:
CRAY X-MP, CRAY Y-MP, Fujitsu VP200, Control Data Cyber/250,
NEC SX/2 o Hitachi S820. Los computadores de menor escala dentro de
este grupo se denominan mini-supercomputadores, como por ejemplo el
Convex C-2.
En junio de 2001 el ordenador ms potente del mundo era el ASCI White.
Este ordenador fabricado por IBM se ubica en el Departamento de Energa
de los EEUU en el Lawrence Livermore National Laboratory y se dedica al
control de la seguridad y fiabilidad nuclear. Est formado por 8.192
microprocesadores SP Power3 a 375 MHz, logrando una capacidad de
clculo de 12,3 TeraFLOPS9. Dispones de 6 Tbytes de memoria principal y
unidades de disco con una capacidad total de 160 Tbytes. Su costo fue de
110 millones de dlares.

FLOPS (FLotating-Points Operations Per Second) es una unidad de medida de las


operaciones de clculo con unidades en punto flotante que puede hacer un ordenador. Un
TeraFLOP equivale a 240 FLOPS.

Introduccin 17

Figura 1.3. ASCI White

Grandes ordenadores, macrocomputadores o mainframes: Son grandes


ordenadores de uso general con grandes posibilidades de procesamiento,
memoria y E/S. Requieren gran cantidad de espacio fsico para su
instalacin, y normalmente se los destina a organizaciones que requieren el
proceso de grandes bancos de datos (bancos, universidades, compaas
areas...). Suelen ser utilizados por un alto nmeros de usuarios a travs de
accesos en red. Un ejemplo de esta categora de ordenadores es el
IBM/4361.
Servidores de red: Son equipos de rango medio, para utilizar
interactivamente por mltiples usuarios simultneamente, similares a los
macrocomputadores, pero a escala reducida de prestaciones y precio. Suelen
utilizarse en empresas o departamentos medianos. Los antecedentes de este
tipo de ordenadores se encuentra en los minicomputadores. Actualmente,
las funciones de los equipos multiusuario de rango medio se cubre con
servidores de red. Son potentes estaciones de trabajo en configuracin
monoprocesador o multiprocesador que pueden llegar a tener 1 GB de
memoria principal y varios GB de disco. Estos ordenadores actan
intercontectados en una red local, a veces con conexin a Internet,
atendiendo simultneamente varios accesos de estaciones de trabajo, PCs o
terminales conectadas a la red.
Estaciones de trabajo o workstations: Ordenadores de gran potencia,
usualmente monousuarios, aunque se encuentran conectados a redes a travs
de las cuales acceden a otros ordenadores que les proveen de ciertos
servicios (servidores). Su uso ms extendido es para aplicaciones cientficas
y de diseo grfico.
Ordenadores personales o microcomputadores: Ms conocidos como PC
(Personal/Profesional Computer), han tenido una gran influencia en el
desarrollo de la informtica, al acercar los ordenadores a un sector de
mercado muy grande formado por las pequeas y medianas empresas y los
hogares. Estos ordenadores inicialmente partieron de unas caractersticas un
tanto limitadas (longitud de palabra de 16 bits, capacidad de memoria
principal no superior a los 2 MB, 1 o 2 unidades de discos flexibles, un disco
duro inferior a las 500 MB, y una frecuencia de reloj inferior a los 33 MHz).
Sin embargo, el gran avance de la informtica y la electrnica se ha visto
reflejada tambin en este apartado y a fecha de hoy un equipo personal es un
ordenador que bien se poda considerar una estacin de trabajo al contar con
unas caractersticas10 tales como longitud de palabra de 32-64 bits, capacidad
10

Caractersticas que se vern superadas rpidamente.

18

Programacin en C

de memoria no inferior a los 256 MB de RAM (no siendo infrecuente llegar


a 512 1024 MB en ordenadores de uso domstico), una unidad de disco
flexible, un disco duro no inferior a los 60 GB, una frecuencia de reloj no
inferior a 1 GHz (existiendo actualmente equipos que superan los 3 GHz),
con capacidades multimedia y conexin a Internet a travs de un mdem,
una tarjeta RDSI (Red Digital de Servicios Integrados), cable o ADSL
(Asymmetric Digital Subscriber Line) entre otras opciones. Ejemplos
actuales de este tipo de ordenadores, son el iMac de Apple o los
ordenadores basados en los microprocesadores de Intel Pentium 4 o en los
microprocesadores de AMD Athlon. Existen diferentes configuraciones para
estos equipos: torre, sobremesa o porttil.
Asistentes personales digitales: Tambin denominados ordenadores de
mano o de bolsillo (del ingls hand-held, palmtops o pocket computer) o
simplemente PDAs (Personal Digital Assistant). Son pequeos ordenadores
que combinan posibilidades de agenda, computador, telfono, fax, conexin
a Internet o reproductor de ficheros de msica (como MP3 MPEG Audio
Layer 3) entre otras. Incluyen un microprocesador que, a fecha de hoy,
pueden llegar a superar los 600 MHz, y un sistema operativo especialmente
preparado para ellos, almacenado en ROM junto con el resto del software
que est presente. La entrada de datos puede hacerse de muchas formas
dependiendo de los modelos, pueden llevar pequeos teclados incorporados,
aunque la mayora soporta lpices pticos, llegando algunos modelos a
presentar capacidad de reconocimiento de voz.

Samsung Yopi

Palm VIIx
Cassiopeia EM-500
Figura 1.4. Diferentes PDAs

Nanocomputadores: Eran ordenadores de bajo coste y capacidad muy


limitada, que tuvieron su mayor auge a comienzos de la dcada de los 80s,
cuando un PC era inasequible para la mayora de los hogares.
Frecuentemente utilizaban un aparato de televisin como pantalla de salida,
tenan una longitud de palabra de 8 bits, eran utilizados por un solo usuario,
y su uso estaba destinado principalmente al ocio (especialmente vdeo
juegos) aunque tambin existan sencillas contabilidades y presentaban
capacidad de ser programados (normalmente en lenguaje BASIC). Modelos
de ordenadores representativos fueron Sinclair Spectrum, Atari ST o
Commodore 64C.

Introduccin 19

Calculadoras programables de bolsillo: La principal caracterstica de estas


mquinas era su tamao pequeo, alimentacin a pilas, entrada mediante un
sencillo teclado, salida a travs de un visualizador ptico, con una capacidad
de memoria limitada (entre 48 KB y 64 KB aproximadamente en la mayora
de los modelos) y con un lenguaje de programacin bastante simple.

1.2. Las generaciones de ordenadores


Desde los primeros ordenadores a la actualidad la evolucin de los ordenadores se
suele agrupar en una serie de generaciones, caracterizando a cada una de ellas por la
tecnologa utilizada, aunque se puede hablar tambin de la evolucin de los sistemas
operativos y software general utilizado por dichos ordenadores.
El nmero de generaciones suele variar de unos autores a otros, yendo de cuatro
generaciones (opcin ms extendida) [Prieto et al., 2002] hasta seis generaciones
[Allen et al., 1996], no existiendo tampoco un claro consenso en las fechas de inicio y
final de cada generacin.

1.2.1. Primera generacin (1937-1953)


La primera generacin de ordenadores utilizaba tubos de
vacos como dispositivos electrnicos. Los ordenadores
mecnicos tenan dos serios problemas: la velocidad de
cmputo estaba limitada por la inercia de las partes mviles,
y la transmisin de informacin por medios mecnicos era
poco fiable. En los ordenadores electrnicos las partes Figura 1.5. Tubo de vaco
mviles son los electrones y la informacin se transmite por
corrientes elctricas que se desplazan a velocidades cercanas a la velocidad de la luz.
Las memorias construidas con vlvulas de
vaco resultaban extremadamente caras, por lo
que se pens en otra tecnologa basada en
recircular las seales por una lnea de retardo de
mercurio. La informacin se transmita en este
medio a velocidades relativamente lentas con lo
que recirculndola se poda mantener all. Una
lnea de mercurio almacenaba unos 512 bits, y el
coste de almacenamiento del bit resultaba unas
Figura 1.6. ENIAC
100 veces menor que con tubos de vaco.
En resumen, se puede decir que las caractersticas de los ordenadores de esta
generacin son: utilizacin de vlvulas de vaco, elevado precio, voluminosos,
elevado consumo, gran disipacin de calor y limitada vida de funcionamiento.
En cuanto a las caractersticas del software de los ordenadores de esta generacin
se puede comentar que no disponan de sistema operativo, los programas eran
introducidos y controlados de forma manual, slo un programador tena acceso al
ordenador al tiempo, se programaban en lenguaje mquina. Como curiosidad histrica

20

Programacin en C

se puede comentar que a comienzos de la dcada de los 50 se idean los primeros


lenguajes simblicos.
Algunos ordenadores representativos de esta generacin son: ABC - J. V.
Atanasoff (1938), Colossus - Alan Turing (1943), ENIAC (Figura 1.6) - J. Presper
Eckert y John V. Mauchly (1946), EDSAC - M. Wilkes (1949), EDVAC - J. Presper
Eckert y John V. Mauchly (1950) y UNIVAC I (1951).

1.2.2. Segunda generacin (1954-1962)


Las vlvulas de vaco tenan como problemas su gran volumen, el alto consumo de
energa y la disipacin de una gran cantidad de calor. Adems, el perodo de vida de
stas es muy limitado. Todos estos problemas se solucionan con la sustitucin de los
tubos de vaco por transistores.
Para la construccin de la memoria principal se utiliza de forma masiva la
tecnologa de ncleos de ferrita.
La primera computadora puramente basada en transistores fue la TX-0
(Transitorized eXperimental computer 0), del MIT (Masschusetts Institute of
Technology), y que se muestra en la Figura 1.7. Sus innovaciones ms importantes, a
parte del uso de transistores, consistan en el tubo de rayos catdicos, en el lpiz
ptico y en la memoria de ncleos de ferrita.

Figura 1.7. TX-0

En general se puede decir que los ordenadores de esta generacin se pueden


caracterizar por la sustitucin de los tubos de vaco por transistores, el aumento de la
velocidad de clculo, la disminucin del tamao, del consumo elctrico y de la
disipacin de calor, el uso de memorias magnticas de ncleos de ferrita y tambores,
almacenamientos secundarios de gran capacidad y nuevos perifricos de E/S.
En cuanto al tema del software cabe destacar los siguientes hechos: aparecen los
lenguajes de alto nivel como FORTRAN, ALGOL, LISP, SNOBOL y COBOL, surge
el procesamiento por lotes11 (batch processing), aparecen los programas supervisores
o monitores que controlan la ejecucin y paso de un programa a otro, y se comienza a
proporcionar software con los ordenadores.
Algunos ejemplos de ordenadores de esta generacin pueden ser IBM 7030, IBM
7090 e IBM 7094, UNIVAC 1004, IBM 1620, y CDC 1604.
11

Ejecucin secuencial de un lote de trabajos sin intervencin humana.

Introduccin 21

1.2.3. Tercera generacin (1963-1972)


La circunstancia tecnolgica que marca los ordenadores de esta generacin es la
utilizacin de circuitos integrados monolticos para la construccin de stos. Los
primeros desarrollos comerciales se deben al desarrollo de la tecnologa planar,
propuesta por Noyce y Moore, consistente en la fabricacin de dispositivos utilizando
procesos fotolitogrficos y de difusin trmica. Algunos de los hechos ms relevantes
de la nueva tecnologa son los siguientes:
El bajo coste de los transistores que incluye el circuito integrado, permiti
diseos de circuitos cada vez ms complejos y perfectos.
Al estar todos los componentes del circuito muy prximos, en el mismo
cristal, los retardos en la transmisin de seales entre distintos puntos son
mnimos.
Se reduce el consumo de energa y aumenta la fiabilidad de los circuitos.
Se consigue una gran reduccin tamao.
Se reduce de forma considerable el coste, al poder construirse en serie de
forma automtica.
Tecnolgicamente los ordenadores de esta
generacin se caracterizan por la utilizacin de circuitos
integrados SSI y MSI, reemplazando a los transistores
discretos, y consiguindose as una reduccin
significativa en coste y tamao fsico. Tambin
paulatinamente se fueron imponiendo las memorias
realizadas con circuitos integrados, desplazando a las
memorias de ncleos de ferrita.
En cuanto al software de esta generacin se pueden
citar los siguientes aspectos relevantes: aparecen
nuevos lenguajes de alto nivel: BASIC (1964), PL/I Figura 1.8. Obleas de circuitos
integrados
(IBM, 1966), APL (1957-60) y Pascal (1970-73), se
generaliza el uso de sistemas operativos, aparecen los primeros sistemas interactivos,
se emplean tcnicas como multiprocesamiento, multiprogramacin y se aplica el
concepto de memoria virtual.
Como ordenadores representativos se pueden citar: La serie IBM 360, UNIVAC
1108, IBM 370, CDC 6600 (1963-64), CDC 7600 (1969), CDC Serie Cyber, o la
serie PDP.

1.2.4. Cuarta generacin (1972-Actualidad)


En este perodo de tiempo han surgido numerosos avances en la informtica, como se
ha podido comprobar del rpido repaso que se ha hecho en el apartado anterior, lo
cual lleva a varios autores a distinguir varias generaciones en este perodo.

22

Programacin en C

Una de las particularidades de los ordenadores de esta generacin es el concepto de


sistema abierto, los ordenadores de las generaciones anteriores estaban muy ligados
a un determinado fabricante (sistemas propietarios).
En cuanto a la tecnologa se siguen utilizando sistemas integrados de una mayor
escala de integracin (LSI y VLSI). Se generaliza el uso del microprocesador y las
memorias de semiconductores. Se consigue aumentar la velocidad y disminuir el
tamao. Otras caractersticas relevantes son la aparicin de los ordenadores
personales y de las estaciones de trabajo para aplicaciones cientfico-tcnicas, el
desarrollo de nuevas arquitecturas: microprocesadores RISC (conjunto reducido de
instrucciones), la comercializacin de supercomputadores con varios
microprocesadores, el auge de la teleinformtica y las redes de ordenadores o el
procesamiento masivamente paralelo.

Figura 1.9. Circuitos integrados realizados sobre soporte flexible (Archivos de Bull)

En lo referente al software aparece una gran variedad de sistemas operativos (de


red, distribuidos), se consolidan nuevas tcnicas de programacin (programacin
lgica, programacin funcional, programacin orientada a objetos), tcnicas de
inteligencia artificial.
Hay muchos ordenadores representativos. Por citar algn modelo se pueden
mencionar CRAY 1, CRAY X-MP, CYBER 205, CRAY 2, Altair 8800, Apple II o
IBM PC.

1.3. Resumen
En el presente captulo se ha pretendido hacer una introduccin a la informtica, en la
que se ha hecho hincapi a los conceptos ms bsicos que se van a ir repitiendo a lo
largo de esta asignatura ya sea de forma explcita o de forma implcita.
Parece necesario recalcar el sentido del trmino informtica en cuanto a su
acepcin de tratamiento automtico de los datos, de forma que el usuario pueda
obtener un provecho de este tratamiento para interpretarlo y obtener verdadera
informacin til.
Para la realizacin de este proceso automtico la informtica se apoya en los
ordenadores o computadores, los cuales actualmente son dispositivos electrnicos que
bajo el control de un software adecuado y con la interaccin humana pertinente a cada
caso son capaces de realizar diferentes procesos.
Otro aspecto importante que se ha abordado es la forma en que se almacenan los
datos dentro de un ordenador, utilizando cdigos derivados del sistema de numeracin

Introduccin 23

binario. Esto lleva a manejar magnitudes derivadas del bit que es la unidad ms
elemental de informacin dentro de un ordenador, y que en definitiva se reduce a un
simple valor binario (un cero o un uno).
Como complemento a la introduccin al concepto de ordenador se ha presentado
muy someramente las unidades funcionales de un ordenador, y que prcticamente
coincide con lo que se ha dado en denominar la arquitectura von Neumann, en la que
aparecen tres grandes unidades funcionales: la unidad de entrada/salida, la unidad
central de proceso y la memoria central.
En la ltima parte del captulo se han repasado los hitos hardware y software que
sirven para dividir la historia moderna de los computadores en cuatro generaciones, la
primera de ellas basada en las vlvulas de vaco, la segunda en los transistores, la
tercera en los circuitos integrados, y la cuarta en los circuitos integrados de muy alta
escala de integracin y, especialmente, en la introduccin del microprocesador.
Hay autores que hablan de una quinta, e incluso una sexta, generacin de
ordenadores, tomando como referencia ciertos proyectos japoneses de desarrollar
ordenadores basados en la inteligencia artificial, pero la realidad es que los
ordenadores actuales, cada vez ms potentes, continan siguiendo el modelo de von
Neumann y su unidad central de proceso sigue basada en microprocesador realizados
con semiconductores.

1.4. Lecturas complementarias


Como libro complementario a este captulo se recomienda La nueva mente del
emperador de Roger Penrose [Penrose, 1991], donde se abordan interesantes aspectos
de las ciencias y de la computacin.
Un libro introductorio que presenta adems un interesante material adicional en
formato CD-ROM es Introduccin a la Informtica: Hardware, Software y
Teleinformtica, de Miguel ngel Snchez Vidales, publicado en 2001 por
Publicaciones Universidad de Pontificia de Salamanca.
En la direccin Web http://www.netlib.org/benchmark/top500/top500.list.html se
mantiene una lista con los 500 ordenadores ms potentes del mundo.
Tambin es interesante contar con algn diccionario de trminos informticos,
como por ejemplo el Websters New World Diccionario de Trminos de
Computacin de Bryan Pfaffenberger, editado por Prentice-Hall, o el Glossary of
Computing Terms, novena edicin, realizado por la British Computer Society y
editado por Longman. En Internet tambin existen diccionarios de trminos
informticos en lnea, pudiendo citar entre otros al FOLDOC - Free On-Line
Dictionary
of
Computing

(http://www.foldoc.org),
al
Webopedia
(http://www.pcwebopedia.com) o al Computer User High-Tech Dictionary
(http://www.computeruser.com/resources/dictionary). Es destacable la enciclopedia
libre Wikipedia, em la que se pueden encontrar definiciones y referencias a diferntes
tpicos relacionados con el mundo de los ordenadores; a su versin en espaol se
puede acceder a travs de http://es.wikipedia.org/wiki/Portada.

24

Programacin en C

1.5. Cuestiones y ejercicios


1.
2.
3.
4.
5.

Cuntos MB, KB, bytes y bits son 20 GB?


Cuntos MB son 13631488 bytes?
Busca en informacin en revistas, tiendas de informtica y en Internet sobre la
configuracin bsica de un ordenador personal, haciendo un informe de las
caractersticas que en la actualidad definen uno de estos equipos.
Busca informacin en revistas, tiendas de informtica y en Internet sobre las
caractersticas de los Asistentes Personales Digitales y compralas con las
caractersticas de un ordenador personal.
Busca en Internet caractersticas de algunos superordenadores, comparndolas
con las caractersticas de los ordenadores personales.

1.6. Referencias
[Allen et al., 1996] Allen, R. C., Bottcher, C., Bording, P., Burns, P., Conery, J., Davies, T.
R., Demmel, J., Johnson, C., Kantha, L., Martin, W., Parks, G., Piacsek, S.,
Pryor, D., Schlick, T., Strayer, M. R., Umar, V. M., Voigt, R., Wagener, J.,
Zachmann, D. y Ziebarth, J. Computational Science Education Project.
http://csep1.phy.ornl.gov/. [ltima vez visitado, 27-7-2005]. 1996.
[RAE, 2001] Real Academia Espaola Diccionario de la Lengua Espaola. Vigsimo
segunda edicin. http://www.rae.es. [ltima vez visitado, 27-7-2005]. 2001
[Goi, 1986] Goi, M. J. (Ed.). Diccionario Informtico. Gran Enciclopedia Informtica.
Ediciones Nueva Lente. Vol. 18. 1986.
[Miguel, 1994] Miguel Anasagasti, P. de. Fundamentos de los Computadores. 4 Edicin.
Paraninfo, 1994.
[Penrose, 1991] Penrose, R. La Nueva Mente del Emperador. Mondadori, 1991.
[Prieto et al., 2002] Prieto, A., Lloris, A. y Torres, J. C. Introduccin a la Informtica. 3
Edicin. McGraw-Hill, 2002.
[Tucker et al., 1994] Tucker, A. B., Bradley, W. J., Cupper, R. D. y Garnick, D. K.
Fundamentos de la Informtica. McGraw-Hill, 1994.
[Vaquero, 1999] Vaquero Snchez, A. La Lengua Espaola en el Contexto Informtico.
Revista de Enseanza y Tecnologa ADIE. (13):5-12. Enero-Abril, 1999.

2. Sistemas de numeracin y representacin de la


informacin
Como ya se indic en el captulo de introduccin, el universo de los ordenadores es
un espacio binario, lo cual dista bastante del mundo de la informacin manejada por
el hombre, compuesta principalmente por textos, cifras numricas, representaciones
grficas y sonidos. Por este motivo es necesario establecer unos mecanismos de
traduccin que permitan convertir los datos, sea cual sea su naturaleza, a cdigos
binarios. Todos los asuntos relacionados con esta necesidad de codificacin
pertenecen a lo que se conoce como representacin de la informacin, que ser el
tema central de este captulo.
A continuacin, en un primer apartado se va ha hacer una introduccin a la
problemtica derivada de la necesidad de representar informacin de naturaleza
heterognea en un ordenador. El segundo apartado est dedicado a introducir los
conceptos bsicos sobre los sistemas de numeracin en general y, en particular,
aquellos ms relacionados con la informtica, haciendo tambin un especial hincapi
en las reglas prcticas para cambiar un nmero de una base a otra. El apartado tres
estudia los mtodos de representacin ms usuales en informtica, haciendo una breve
incursin en las reglas aritmticas de los mtodos ms relevantes. El siguiente
apartado se ocupa de los cdigos de entrada y salida para los datos, as como de los
denominados cdigos redundantes, destinados a la deteccin (y en ocasiones
correccin) de los errores que se presentan en la transmisin y almacenamiento de los
datos. Por ltimo, el quinto apartado cierra el captulo con unas conclusiones
generales a modo de resumen del mismo.

2.1. Introduccin
Un ordenador es una mquina que procesa datos mediante la ejecucin de programas.
La ejecucin de un programa conlleva el procesamiento de una combinacin de datos,
segn especifica el conjunto ordenado de instrucciones que componen dicho
programa. Para que la ejecucin del mismo se lleve a cabo, el ordenador debe recibir
dos flujos de informacin, tal y como se muestra en la Figura 2.1; el primero de ellos
se corresponde con el flujo de datos de entrada que va a ser manipulado para
producir los resultados o datos de salida. El otro flujo es el de instrucciones de
mquina, o flujo de control, cuyo objetivo no es otro que el de expresar el proceso de
tratamiento de los datos de entrada.

Figura 2.1. Flujos de informacin de un ordenador


- 25 -

26

Programacin en C

Uno de los aspectos ms importantes dentro de la arquitectura de un ordenador es


la forma de representar y registrar fsicamente los datos y las instrucciones.
El proceso de representacin es el conjunto de pasos o niveles de codificacin que
transforman la informacin, expresada en el formato usual de los seres humanos, al
tipo que utiliza un ordenador.
El registro de la informacin en un ordenador se lleva a cabo en el espacio material
del ordenador, que viene definido por los elementos fsicos que lo componen. Este
espacio viene condicionado por cuatro caractersticas fundamentales [Miguel, 1994]:
1. Los elementos fsicos habituales slo permiten diferenciar dos estados
distintos, con lo que se puede decir que el espacio material es de tipo
binario. Cada uno de estos elementos es capaz de almacenar o de tratar
un dgito binario o bit.
2.

El espacio material es finito. De forma que las representaciones han de


ser forzosamente acotadas.

3.

Las unidades funcionales de un ordenador se disean para trabajar con


una cadena de bits de un cierto tamao.

4.

Los elementos de comunicacin entre las unidades funcionales, los


buses, tienen un cierto ancho, de manera que permiten transmitir
simultneamente un nmero determinado de bits.

El caso ms habitual es aqul en que los datos de entrada se suministran con ayuda
de un alfabeto o conjunto de smbolos que se denominan caracteres. Estos caracteres
suelen agruparse en cinco categoras [Prieto et al., 2002]:
1. Caracteres alfabticos: Son las letras, tanto maysculas como
minsculas del abecedario ingls.
A, B, C, ..., X, Y, Z, a, b, c, ..., x, y, z
2. Caracteres numricos: Son las diez cifras del sistema numrico
decimal.
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
3. Caracteres especiales: Son los smbolos no incluidos en los dos
grupos anteriores, y engloba: smbolos de puntuacin, caracteres
propios de otros alfabetos (como la o las vocales acentuadas),
operadores aritmticos y de relacin, smbolos de moneda, el
espacio en blanco1, etc. Como muestra, y entre otros muchos, se
pueden presentar los siguientes:
(, *, /, ;, , , , , , !, :, $, , +, >
4. Caracteres de control: Representan rdenes de control, como
puede ser el carcter indicador de fin de lnea, el carcter de final de
fichero, etc.
5. Caracteres geomtricos y grficos: Son smbolos con los que se
pueden crear figuras o iconos elementales.
, , , ,

Aqul que separa dos palabras.

Sistemas de numeracin y representacin de la informacin

27

A las tres primeras categoras se las suele denominar como caracteres de texto,
mientras que el conjunto unin de las dos primeras categoras recibe el nombre de
caracteres alfanumricos.
Dado que un ordenador slo es capaz de trabajar con elementos binarios, se tiene
que traducir toda la informacin suministrada a secuencias binarias, por lo que se
establece una correspondencia entre el conjunto de todos los caracteres y el conjunto
binario.

A, B, C,..., X , Y , Z , a, b, c,..., x, y, z,0,1,...,9, /, , (, ), [, , :,...


0,1

Esto es, se disea una codificacin de los elementos del conjunto mediante los
elementos del conjunto , de forma que a cada elemento de
le corresponda un
patrn nico de n bits perteneciente al conjunto .
Los cdigos de transformacin reciben el nombre de cdigos de entrada/salida
(E/S) o cdigos externos, pudindose definir de forma arbitraria, aunque existen
cdigos de E/S normalizados que son utilizados de forma estndar en varias
arquitecturas de computadores.
Sin embargo, para la realizacin de operaciones aritmticas con datos numricos se
utiliza una representacin ms adecuada que la correspondiente a los cdigos
externos. Para ello, internamente se realiza una conversin entre cdigos binarios para
conseguir una representacin basada en el sistema de numeracin en base 2,
pasndose a utilizar una representacin posicional, que se adecua ms a la realizacin
de operaciones aritmticas.

2.2. Sistemas de numeracin


El sistema decimal, que utiliza lo que se llama base 10 (derivado de los diez dedos
que tiene el hombre y en los que se apoyaba para contar), no ha sido el nico sistema
de numeracin utilizado por ste. Se han encontrado numerosas muestras de culturas
que utilizaban otras bases diferentes a la 10 para contar. As, por ejemplo en algunos
lugares de Sudfrica empleaban lo que se podra asimilar a una base 3: contaban en
forma de uno, dos, dos y uno, dos y dos, dos y muchos, etc. Las bases 5 y 20 tambin
han sido frecuentemente utilizadas, (de nuevo debido al nmero de dedos con que
cuenta el hombre), y as se ha documentado el empleo de la base 20, entre otros, por
los esquimales2 o por pueblos indo-europeos3.
Desde una perspectiva puramente histrica los sistemas de numeracin se pueden
catalogar en sistemas aditivos y sistemas posicionales. Entre los primeros parece
obligado destacar, por su difusin y trascendencia histrica, el egipcio (de tipo
jeroglfico) y el romano, mientras que entre los segundos se encuentra el indoarbigo, utilizado actualmente como estndar internacional.
2

El pueblo esquimal contaba en hombres y en dedos, de forma que por ejemplo, un hombre y
siete dedos representaban el nmero 27.
3 Una reminiscencia de este hecho es la forma de escribir ciertos nmeros franceses, por
ejemplo 80 es quatre-vingt (cuatro-veintes).

28

Programacin en C

El sistema romano ms primitivo empleaba tan slo siete smbolos: M (1.000), D


(500), C (100), L (50), X (10), V (5) y I (1), con los que puede expresarse cualquier
nmero positivo menor que 5.000 sin repetir cualquiera de ellos ms de cuatro veces 4.
El orden de los smbolos no importaba, aunque se solan ordenar de mayor a menor,
siendo la suma una operacin inmediata (por esto el nombre de sistema aditivo), ya
que consista en mezclar los smbolos de los sumandos, ordenarlos de mayor a menor
y simplificar cuando hubiera posibilidad.
El origen del sistema de numeracin decimal se pude rastrear hasta la civilizacin
hind en el siglo I o II, en el este de la actual Indochina, dndose tres hechos
fundamentales:
La representacin posicional de las cifras.
La base decimal.
El concepto de cero como un dgito ms.
Gracias a estas ideas cualquier nmero, por grande que sea, puede expresarse como
una secuencia ordenada de diez dgitos.
Se cree que el sistema de numeracin hind pas a Bagdag, donde se
perfeccionaron los distintos procedimientos de clculo. La primera constancia escrita
de la introduccin del sistema indo-arbigo en Europa se encuentra en Espaa en la
crnica Albeldense5 del ao 976. Sin embargo, el responsable de la difusin de este
sistema de numeracin en Europa fue el matemtico Leonardo de Pisa, ms
conocido como Fibonacci6, hacia el ao 1200.

2.2.1. Definicin de sistema de numeracin


El desarrollo de un sistema de numeracin universal es un claro sntoma de la
evolucin de la especie humana, tal y como se puede deducir de la lectura de la
introduccin a este apartado.
En general, se puede definir un sistema de numeracin como un Sistema para la
representacin de cantidades con el que se pueden realizar operaciones basndose en
una serie de reglas.

2.2.2. Generalidades de los sistemas de numeracin posicionales


Un sistema de numeracin en una determinada base k se dice que es un sistema de
numeracin posicional cuando cualquier nmero se expresa mediante un conjunto de
dgitos, contribuyendo cada uno de ellos con un valor que depende del dgito en s y
de la posicin que ocupa ste dentro del nmero.

En esta poca no existan las formas reducidas IV = IIII y IX = VIIII.


Dentro del Codex Vigilanus, el monje Vigila escriba con cifras muy enguirnaldadas al modo
de las grafas semitas, de derecha a izquierda, los dgitos del 1 al 9, pues en esta poca se
desconoca an el cero.
6 Su libro Liber Abacci (1202) describe las reglas para sumar, restar, multiplicar y dividir con el
nuevo sistema de numeracin.
5

Sistemas de numeracin y representacin de la informacin

29

Adems, se cumple que para la representacin de cualquier nmero en base k se


cuenta con un alfabeto de k dgitos o cifras que van desde el 0 al k-1.
Tomando como ejemplo al sistema decimal, dado que la base es 10, se tiene que el
alfabeto de dgitos de este sistema de numeracin est compuesto por el conjunto {0,
1, 2, 3, 4, 5, 6, 7, 8, 9}.

Formalizando este concepto, se puede decir que un determinado nmero N en una


determinada base k, se representa por una cadena de dgitos pertenecientes al alfabeto
determinado por la base.

n4 n3 n2 n1n0 , n 1n 2 n 3 n

Sea un vector de pesos P

p4 p3 p2 p1 p0 p 1 p 2 p 3 p

. Para calcular el

valor, V(N), del nmero N, se selecciona el peso adecuado del vector P para aplicar
el factor de escala oportuno a cada una de las cifras del nmero N de la siguiente
forma:
V N

p3 n3

p2 n2

p1 n1

p0 n0

p1 n1

n3

pi ni
i

Ecuacin 2.1. Valor de un nmero N en una base k

Lo ms frecuente es expresar los pesos en funcin de la base k, de forma que el


vector de pesos pasa a tener el siguiente formato:

k 4 k 3 k 2 k 1k 0 k 1k 2 k 3 k

De forma que la Ecuacin 2.1 se puede expresar como sigue:


k i ni

V N
i

Ecuacin 2.2. Valor de un nmero N en una base k

As, la representacin de un nmero cualquiera en una determinada base k, no


necesita el vector de pesos, siendo slo necesario indicar la base, como se muestra a
continuacin:

n4 n3 n2 n1n0 , n 1n 2 n 3 n

De nuevo, tomando el sistema decimal como ejemplo, cualquier nmero se puede


representar segn la expresin:

V N

d i 10i

10
i

Ecuacin 2.3. Valor de un nmero N en base decimal

As, por ejemplo, el nmero 597,5)10 se puede representar como:

597,5)10 = 500 + 90 + 7 + 0,5 = 5102 + 9101 + 7100 + 510-1

Adems, utilizando este proceso se puede convertir cualquier nmero expresado en


una base k a su equivalente en base 10. Por ejemplo, sea el 310)4 su equivalente en
base 10 es:
310)4 = 3 42 + 1 41 + 0 40 = 3 16 + 4 = 52)10

Algunas caractersticas interesantes de los sistemas posicionales son:


1. La base de los sistemas de numeracin suele ser un nmero
natural, pero puede ser tambin un nmero entero, es decir, se
podra emplear un sistema de representacin con base

30

Programacin en C

negativa, pudindose representar todas las cantidades positivas


y negativas sin tener que aadir el signo al nmero. As, el
siguiente ejemplo toma k = -10.
123)-10 = 1 (-10)2 + 2 (-10)1 + 3 (-10)0 = 83)10
97)-10 = 9 (-10)1 + 7 (-10)0 = -83)10

2.

Dadas dos bases

tal que = n, se cumple que:

b2 b1b0 , b 1b

a2 a1a0 , a 1a

donde:

ai

3.

bni

n 1

bni 1bni

Esto es, los dgitos del nmero en base = n se obtienen


agrupando los dgitos del nmero en base
en grupos de
longitud n.
Aunque un nmero racional puede tener una representacin
exacta en una determinada base k, puede ser necesaria una
representacin peridica para otra base b. Por ejemplo, el
nmero 0,2)10, que tiene una representacin exacta en la
base decimal, tiene una representacin peridica en el sistema
binario:

0,2 10

0,001100110011 2

Este hecho plantea un problema, ya que al tener que


representar este nmero con una cadena finita de n bits se tiene
que truncar el perodo, resultando una representacin no
exacta.

2.2.3. El sistema binario


El sistema de numeracin binario es aqul que tiene como base k = 2, esto es, slo
necesita dos smbolos {0, 1} para representar cualquier nmero. Los elementos de
este alfabeto se denominan cifras binarias o bits.
Binario Decimal
Binario Decimal
0000
0
1000
8
0001
1
1001
9
0010
2
1010
10
0011
3
1011
11
0100
4
1100
12
0101
5
1101
13
0110
6
1110
14
0111
7
1111
15
Tabla 2.1. Los 16 primeros nmeros binarios

En la Tabla 2.1 se muestran los valores que se pueden formar con 4 bits, que se
corresponden con los nmeros decimales del 0 al 15.

Sistemas de numeracin y representacin de la informacin

31

2.2.3.1. Transformacin de base binaria a base decimal


Teniendo en cuenta que el sistema binario es un sistema posicional, se puede aplicar
la Ecuacin 2.2 con k = 2 para obtener el equivalente en decimal de cualquier
nmero binario.
EJEMPLOS
11001101,1)2 = 1 27 + 1 26 + 1 23 + 1 22 + 1 20 + 1 2-1 = 128 + 64 + 8 + 4 + 1 + 0,5 = 205,5)10
1,101)2 = 1 20 + 1 2-1 + 1 2-3 = 1 + 0,5 + 0,125 = 1,625)10

De los ejemplos anteriores se puede concluir que para transformar de binario a


decimal lo que se hace es sumar los pesos de las posiciones que contienen un bit con
valor 1.
1 0 1 0 , 1 )2 = 8 + 2 + = 10.5)10
8421,

peso:

Dado que el mayor peso se encuentra en la posicin ms a la izquierda, al


correspondiente bit se le denomina Bit Ms Significativo (MSB: Most Significant
Bit), y anlogamente el situado ms a la derecha es el Bit Menos Significativo (LSB:
Least Significant Bit).
2.2.3.2. Transformacin de base decimal a base binaria
El paso o transformacin de un nmero expresado en base decimal a su equivalente
en base binaria se realiza en dos partes: por un lado se transforma la parte entera del
nmero y por otro la parte fraccionaria; para lo cual se sigue el siguiente algoritmo:
1. La parte entera del nmero binario se obtiene dividiendo, sin obtener
decimales, por dos la parte entera del nmero decimal de partida. Se repite
esta divisin con los sucesivos cocientes que se van obteniendo (se
seguir dividiendo siempre y cuando el cociente resultante sea mayor que
dos). Los restos de estas divisiones y el ltimo cociente (que son siempre
ceros y unos) forman las cifras binarias del nmero equivalente en base
dos, de manera que el bit ms significativo es el ltimo cociente, y el bit
menos significativo es el primer resto.
2. La parte fraccionaria del nmero binario se obtiene multiplicando
sucesivamente por dos la parte fraccionaria del nmero decimal de partida
y las partes fraccionarias que se van obteniendo en los productos
sucesivos. La parte fraccionaria del nmero binario se forma con las
partes enteras (que son ceros y unos) de los productos obtenidos.
EJEMPLO

Transformar a binario el nmero decimal 74,1875.


74
14
0

2
37
17
1

2
18
0

2
9
1

2
4
0

2
2
0

2
1

32

Programacin en C

La parte entera es 74 = 1001010)2


0,1875
x2
0,3750

0,3750
x2
0,7500

0,7500
x2
1,5000

0,5000
x2
1,0000

La parte fraccionaria es 0,1875 = 0,0011)2


74,1875)10 = 1001010,0011)2
2.2.3.3. Operaciones aritmticas en el sistema binario
Las operaciones aritmticas bsicas son la suma, la resta, la multiplicacin y la
divisin. Al ser el sistema binario un sistema posicional o ponderado, estas
operaciones son similares a las realizadas en el sistema decimal, utilizando para cada
bit las reglas representadas en las tablas, segn la operacin aritmtica a realizar.
a
b
a+b
a
b
a-b
0
0
0
0
0
0
0
1
1
0
1
1 y se debe 1
1
0
1
1
0
1
1
1
0 y se lleva 1
1
1
0
Tabla 2.2. Suma binaria

a
0
0
1
1

b
0
1
0
1

Tabla 2.3. Resta binaria

ab
0
0
0
1

a
0
0
1
1

Tabla 2.4. Multiplicacin binaria

b
0
1
0
1

a/b
Indeterminacin
0

EJEMPLO

Con flechas se indican los acarreos.


1110110 = 118

1011101 = 93

1110101 = 117

+ 1000100 = 68

11101011 = 235

10100001 = 161

1101010 = 106

1011101 = 93

87

1000100 = 68

0010011 = 19

0011001 = 25

1010111 =

1101010 = 106
11 =
3
1101010
1101010

100111110 = 318

Tabla 2.5. Divisin binaria

1101010 = 106
10 =

0000000
1101010
11010100 = 212

Sistemas de numeracin y representacin de la informacin

33

En la aritmtica binaria se cumple que multiplicar un nmero binario por 10)2 (2


en decimal), equivale aadir un 0 a la derecha del nmero (lo cual es similar a
multiplicar por 10)10 un nmero decimal). De la misma manera, dividir un nmero
binario por 10)2 equivale a desplazar la coma decimal una posicin a la izquierda.

2.2.4. El sistema octal


El sistema de numeracin octal es aquel que tiene como base k = 8, siendo {0, 1,
2, 3, 4, 5, 6, 7} el conjunto de smbolos permitido.
El sistema octal es uno de los llamados cdigos intermedios, por presentar una
gran facilidad de transformacin de un nmero octal a binario y viceversa. Esta
facilidad se debe a que la base 8 es potencia de 2 k 8 2 3 .
2.2.4.1. Transformacin de base octal a base decimal
Teniendo en cuenta que el sistema octal es un sistema posicional, se puede aplicar la
Ecuacin 2.2 con k = 8 para obtener el equivalente en decimal de cualquier nmero
octal.
EJEMPLO
7612)8 = 7 83 + 6 82 + 1 81 + 2 80 = 3584 + 384 + 8 + 2 = 3978)10

2.2.4.2. Transformacin de base decimal a base octal


Se sigue un procedimiento similar al seguido para convertir un nmero decimal en
binario, con la diferencia de que ahora se divide por 8 para transformar la parte entera
del nmero, y se multiplica por 8 para hacer lo propio con la parte fraccionaria.
EJEMPLO
740,33)10 = 1344,25075)8
740
20

8
92
12

4
0,33
x 8
2,64

0,64
x 8
5,12

0,12
x 8
0,96

8
11

0,96
x 8
7,68

1
0,68
x 8
5,44

...

34

Programacin en C

2.2.4.3. Transformacin de base octal a base binaria


Cualquier nmero octal puede transformarse a su equivalente en binario fcilmente
sin ms que tener en cuenta que k 8 2 3 .
Para pasar un nmero octal a binario se convierte cada cifra del nmero octal a su
equivalente
en
binario
de
tres
bits.
EJEMPLOS
7330,6)8 = 111 011
7
3

011
3

000,
0,

110)2
6

120,05)8 = 001 010


1
2

000,
0,

000
0

101)2
5

2.2.4.4. Transformacin de base binaria a base octal


Para transformar un nmero binario a su correspondiente en base octal se forman
grupos de tres bits desde el punto decimal hacia izquierda y derecha, para
posteriormente realizar la conversin de cada grupo a la cifra octal correspondiente.
EJEMPLO
1101010010,01001)2 = 001
1

101
5

010
2

010,
2,

010
2

010)2 = 1522,22)8
2

2.2.5. El sistema hexadecimal


El sistema de numeracin hexadecimal es aquel que tiene como base k = 16, siendo
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F} el conjunto de
smbolos permitido.
El sistema hexadecimal es otro de los cdigos intermedios, siendo extremadamente
sencillo transformar un nmero hexadecimal a binario y viceversa. Esta facilidad se
debe a que la base 16 es potencia de 2 k 16 2 4 .
2.2.5.1. Transformacin de base hexadecimal a base decimal
Teniendo en cuenta que el sistema hexadecimal es un sistema posicional, se puede
aplicar la Ecuacin 2.2 con k=16 para obtener el equivalente en decimal de cualquier
nmero hexadecimal.
EJEMPLO
FA1E)8 = 15 163 + 10 162 + 1 161 + 14 160 = 61440 + 2560 + 16 + 14 = 64030)10

2.2.5.2. Transformacin de base decimal a base hexadecimal


Se sigue un procedimiento similar al seguido para convertir un nmero decimal en
otra base, con la diferencia de que ahora se divide por 16 para transformar la parte
entera del nmero, y se multiplica por 16 para hacer lo propio con la parte
fraccionaria.

Sistemas de numeracin y representacin de la informacin

35

EJEMPLO
7011,35)10 = 1B63,5999)16

7011
64
61
48
131
128

16
438 16
32
27 16
118 16 1
112 11

3
0,35
x16
5,6

0,60
x16
9,6

0,60
x16
9,6

0,60
x16
9,6

2.2.5.3 Transformacin de base hexadecimal a base binaria


Cualquier nmero hexadecimal puede transformarse a su equivalente en binario
fcilmente sin ms que tener en cuenta que k 16 2 4 .
Para pasar un nmero hexadecimal a binario se convierte cada cifra del nmero
hexadecimal a su equivalente en binario de cuatro bits.
EJEMPLOS
AA0F2,6)16 = 1010

20B,02)16 = 0010

1010
A

0000
0

1111
F

0010,
2,

0000
0

1011,
B,

0000
0

0010)2
2

0110)2
6

2.2.5.4 Transformacin de base binaria a base hexadecimal


Para transformar un nmero binario a su correspondiente en base hexadecimal se
forman grupos de cuatro bits desde el punto decimal hacia izquierda y derecha, para
posteriormente realizar la conversin de cada grupo a la cifra hexadecimal
correspondiente.
EJEMPLO
1101010010,01001)2 =

0011
3

0101
5

0010,
2,

0100
4

1000)2 = 352,48)16
8

2.2.6. Cambio de base K a base P


Para efectuar un cambio de representacin de un nmero en base k a otra base p, se
divide sucesivamente por p en la base de partida k. Pero este procedimiento es
incmodo y se puede conseguir de manera ms sencilla efectuando el cambio de base
k a decimal, y de base decimal se pasa a base p. Es decir, se utiliza la base 10 como
base intermedia o de transformacin.

36

Programacin en C

EJEMPLO
Sea k = 16 y p = 8
F1)16 = 15 161 + 1 160 = 15 16 + 1 = 240 + 1 = 241)10

241 8
1 30 8
3
6
241)10= 361)8
F1)16

241)10

361)8

2.3. Mtodos de representacin interna de la informacin


Todos los datos procesados por la unidad aritmtico-lgica de un ordenador, o
transferida a los distintos dispositivos de memoria o perifricos, deben estructurarse
en palabras7. Para aprovechar ms eficientemente la memoria, como longitud de
palabra se utiliza un mltiplo entero del nmero de bits utilizado para representar un
carcter.
Los datos se introducen en el ordenador segn un cdigo de entrada/salida,
independientemente de si son de tipo alfabtico o de tipo numrico. No obstante, los
datos numricos se utilizan para realizar operaciones aritmticas, y la representacin
simblica obtenida con el cdigo entrada/salida no es la adecuada para este tipo de
operaciones.
Para representar datos numricos es ms adecuado utilizar un cdigo basado en los
fundamentos matemticos de los sistemas de numeracin.
La representacin interna final del dato numrico depender de la arquitectura del
ordenador que se est utilizando, as como del propio lenguaje de programacin con
el que se haya realizado la aplicacin que va a procesar los datos numricos.
A lo largo de este apartado se va a hacer un repaso de los mtodos de
representacin ms difundidos para los tipos de datos numricos ms habituales (los
enteros y los reales), haciendo finalmente una breve presentacin de los cdigos de
entrada/salida.

2.3.1. Representacin interna de los nmeros reales


Dado que todos los subconjuntos de nmeros que conforman el conjunto de los
nmeros reales naturales, enteros, racionales e irracionales son infinitos, a la
hora de representarlos mediante computadores aparece una barrera infranqueable,
puesto que siempre se tiene una limitacin en cuanto al espacio material.
Se ha llegado a la situacin en que se utiliza un nmero fijo n de bits para
representar cada nmero. Esto conduce a un sistema con las siguientes caractersticas:
Slo se pueden representar 2n nmeros diferentes.
7

Recurdese que la palabra del procesador es el nmero de bits que ste puede manipular al
mismo tiempo.

Sistemas de numeracin y representacin de la informacin

37

Existe un nmero mayor y menor representables que constituyen el


rango de representacin.
Para el caso de nmeros racionales slo son representables aqullos que
tengan un nmero determinado de cifras fraccionarias. El resto se deben
aproximar.
La diferencia entre dos nmeros representables consecutivos establece la
resolucin de representacin o mximo error cometido en la
representacin de una cantidad.
De forma general, se muestra en la Tabla 2.6 los sistemas de representacin ms
utilizados y los conjuntos de nmeros que estos pueden representar:
Sistemas de representacin
Conjunto
Punto fijo sin signo
Naturales (con el 0)
Punto fijo con signo
Enteros
Punto fijo con
Enteros
complemento a la base
Punto fijo con
complemento
Enteros
Sistemas Posicionales
restringido a la base
Punto fijo BCD
Enteros
Punto flotante
Racionales
normalizado
Punto flotante no
Racionales
normalizado
Sistemas de Residuos
Tabla 2.6 Diferentes sistemas de representacin para nmeros reales

En las siguientes secciones se repasan estos sistemas de representacin.

2.3.2. Sistemas posicionales y sistemas de residuos


Como ya se ha visto en el apartado 2.2.2, en un sistema de numeracin posicional
cualquier nmero se expresa mediante un conjunto de dgitos, contribuyendo cada
uno de ellos con un valor que depende del dgito en s y de la posicin que ocupa ste
dentro del nmero.
Un enfoque distinto a la hora de representar nmeros es el que utilizan los sistemas
de residuos que se basan en la utilizacin de la operacin mdulo o residuo.
Sea una base b, se define como mdulo m de un nmero n, al resto r de la divisin
n/b.
La representacin habitual de esta operacin es la que sigue
r = n mod b
Ecuacin 2.4 Operacin mdulo

Como resulta obvio, con este mtodo se pueden representar b cantidades distintas,
es decir, desde 0 hasta b1. Basta con elegir una base lo suficientemente grande para

38

Programacin en C

alcanzar el rango de representacin deseado. Haciendo b = 2m, se obtiene una


representacin equivalente a una en binario puro con una longitud de palabra m.
Sin embargo, los sistemas de residuos encuentran su potencia al combinar varias
bases, de forma que, un nmero se representa mediante el conjunto de p residuos
(r1, r2, ..., rp) respecto a p bases (b1, b2, ... bp).
Cuando las p bases son primas entre s se obtiene un rango de representacin para
b1b2 ...bp.
Se trata de un sistema de representacin mucho menos utilizado que los sistemas
posicionales, siendo ventajoso su uso para la construccin de mquinas rpidas ya que
las operaciones aritmticas (suma, resta, multiplicacin) son independientes de los
residuos. Sin embargo, la divisin es ms complicada de realizar.
Por otro lado resulta ms compleja la conversin a/desde decimal y es muy
complicado el clculo relacional (qu nmero es mayor).

2.3.3. Punto fijo sin signo


Esta representacin se corresponde con la representacin en binario puro, es decir, un
sistema posicional con base 2 y sin parte fraccionaria. Para una palabra de longitud n,
cualquier nmero vendr representado de la siguiente forma:

V N

n 1
2

d i 2i

i 0

Ecuacin 2.5. Representacin de un nmero en un sistema posicional con base 2

Por tanto se podrn representar 2n - 1 nmeros distintos, con lo que el rango de


representacin ser [0, 2n - 1] y su resolucin, 1.
Ya se ha visto en la seccin 2.2.3.3 las formas de realizar las distintas operaciones
aritmticas con este sistema.
Para ese sistema se pueden presentar una serie de objeciones:
Desbordamientos: El resultado de la suma pude ser un nmero que cae
fuera del rango de representacin, es decir, puede que se necesite un bit
ms para representar el nmero resultado. Lo misma posibilidad se tiene
al multiplicar dos nmeros.
Negatividad. Al hacer restas se debe tener la precaucin de comprobar
que el primer operando es mayor que el segundo, para no obtener un
resultado negativo, no representable con este mtodo.

2.3.4. Punto fijo con signo (signo-magnitud)


Derivado del anterior, se trata de una forma de representacin que permite especificar
el signo del nmero representado. Basta con dedicar uno de los n bits para indicar si
se trata de un nmero positivo (normalmente bit de signo a cero) o negativo (bit de
signo normalmente a uno).

Sistemas de numeracin y representacin de la informacin

39

El nombre de signo-magnitud es por tanto derivado de la utilizacin de un bit para


representar el signo (normalmente el de ms a la izquierda) y el resto, n 1, para
representar la magnitud. As:
signo
magnitud
valor
0
1
0
0
0
0
0
1
+ 65)10
1
0
0
1
0
1
0
0
- 20)10
1 bit
n 1 bits
Por tanto, en este caso se tienen dos expresiones distintas:

V N

n 2
2

d i 2i

si el nmero es positivo (di-1 = 0)

i 0

Ecuacin 2.6. Represtacin en signo-magnitud de un nmero positivo

V N

n 2
2

d i 2 i si el nmero es negativo (di-1 = 1)

i 0

Ecuacin 2.7. Represtacin en signo-magnitud de un nmero negativo

De donde se deducen el rango de representacin, [-2n-1 +1, 2n-1 - 1], y la


resolucin, 1.
Este sistema presenta algunas variaciones en cuanto a las operaciones aritmticas
respecto al punto fijo sin signo. Por un lado las sumas y restas tienen que hacerse
teniendo en cuenta el signo de los operandos, para poder realizar una resta en vez de
una suma con operandos negativos, mientras que en las operaciones de multiplicacin
y divisin se opera por separado con signo y con magnitudes.

2.3.5. Punto fijo con complemento a la base. Complemento a Dos


Este mtodo de representacin consiste en representar los nmeros positivos segn la
Ecuacin 2.8:
V N

n 1

di bi

i 0

Ecuacin 2.8. Valor de un nmero para un sistema de representacin con cualquier base

Mientras que los nmeros negativos se deben representar mediante la realizacin


del complemento, es decir, la resta siguiente:

bn

Obtenindose as un nmero positivo (siendo P el nmero que se quera


representar, sin signo). Y se indica que el resultado es negativo mediante el bit de
signo.
Como los ordenadores utilizan nicamente el Complemento a Dos, se comenta a
continuacin este caso particular de complemento a la base.
En primer lugar, se utiliza al bit de signo a 0 para indicar un nmero positivo y el
bit de signo a 1, para los nmeros negativos. Sin embargo, en este mtodo el bit de
signo tambin contribuye a la magnitud, por lo que se utilizan todos los bits de la
representacin a la hora de realizar las operaciones aritmticas.

40

Programacin en C

( N) = 2n (2n P) = P

Cambio de signo
n

( N) + ( M) = (2n P) + (2n Q) =

2 + (2n P Q )
eliminando el
bit n + 1
(2n P Q )

( 7) + (9) con n = 8
1111 1001

Suma de dos nmeros negativos

1111 0111

1 1111 0000
-16

Se elimina el bit sobrante


n-1

Cuando A + C > 2 puede


haber desbordamiento

N + ( M) = N + (2n P), con N < P


Ej: 6 + (7) con n = 8

0000 0110
1111 1001

1111 1111
Suma de un nmero positivo y otro
negativo

-1
N + ( M) = N + (2 P), con N > P
n

Ej: 8 + (7) con n = 8

0000 1000
1111 1001
+

1 0000 0001
Se elimina el bit sobrante

Tabla 2.7. Operaciones aritmticas en Complemento a Dos

Se utiliza la representacin en binario puro para los nmeros positivos, y para los
nmeros negativos se realiza el complemento a dos, es decir, se resta el nmero
negativo, sin signo, de 2n, de forma que el resultado tendr el bit de signo a 1. Por
ejemplo:

Sistemas de numeracin y representacin de la informacin

41

signo
magnitud
valor
0
1
0
0
0
0
0
1
+ 65)10
1
0
1
1
1
1
1
1
- 65)10
1 bit
n 1 bits
Ya que 2n P = 28 0100 0001 =1 0000 0000 0100 0001 = 1011
1111.
Para el caso del Complemento a Dos, se tiene el rango de representacin
correspondiente a [-2n-1, 2n-1 -1] y una resolucin de 1.
Este sistema fue ideado con la intencin de simplificar las operaciones de suma y
resta cuando se haca entre nmeros positivos y negativos. Se tiene el inconveniente,
sin embargo, de que se complican multiplicacin y divisin.
En la Tabla 2.7 se exponen diferentes posibilidades ilustrativas de la simplificacin
en el clculo de sumas y restas.
Se pueden destacar las siguientes caractersticas en el Complemento a Dos:
El signo no se utiliza para realizar sumas y resta.
La extensin de signo se hace colocando ceros a la izquierda en caso de
nmeros positivos, y unos en caso de nmeros negativos.

2.3.6. Complemento a Uno


De forma similar al caso anterior se procede a representar los nmeros en
Complemento a Uno o Complemento Restringido a la Base segn estos sean
positivos, en binario puro, o negativos, restando el nmero negativo, sin signo, de
2n1, de forma que el resultado tendr el bit de signo a 1.
Este mtodo de representacin se usa extensamente en los ordenadores y tiene la
ventaja de coincidir con el complemento lgico. A continuacin se presenta un
ejemplo.
Signo
Magnitud
valor
0
1
0
0
0
0
0
1
+ 65)10
1
0
1
1
1
1
1
0
- 65)10
1 bit
n 1 bits
Puesto que 2n 1 P = 28 1 0100 0001 =1 0000 0000 1
0100 0001 = 1111 1111 0100 0001 = 1011 1110.
Para este caso el rango de representacin es [0, 2n 1 1] para los nmeros
positivos y, para los negativos, [ (2n 1 1), 0], es decir, se trata de un
rango simtrico y con doble representacin en el cero. La resolucin, de nuevo, es 1.
Sin embargo, de nuevo se vuelven a complicar las operaciones bsicas, como se
muestra en la Tabla 2.8.
Se pueden destacar las siguientes caractersticas en el Complemento a Uno:
Las operaciones de multiplicacin y divisin son ms difciles de calcular.
Existe posibilidad de desbordamiento, en cuyo caso, es necesario un
determinado mecanismo de deteccin.
La extensin de signo se hace colocando ceros a la izquierda en caso de
nmeros positivos, y unos en caso de nmeros negativos.

42

Programacin en C

Basta hacer el
complemento lgico

Cambio de signo

( N) + ( M) = (2n 1 P) +

(2 1 Q) = 2 1 + (2 1 P
Q)
reciclando el bit n + 1
n
(2 1 P Q )

( 7) + (9) con n = 8
1111 1000
+

1111 0110

Suma de dos nmeros negativos

1 1110 1110
Se recicla el bit sobrante

1110 1111
-16
n-1

Cuando A + C > 2 puede


haber desbordamiento
n

N + ( M) = N + (2 1 P),
con N < P,
resultado negativo generado
directamente
Ej: 6 + (7) con n = 8

0000 0110
+

1111 1000
1111 1110
-1
n

Suma de un nmero positivo y otro


negativo

N + ( M) = N + (2 1 P),
con N > P,
Reciclando el bit n + 1 se obtiene
el resultado positivo esperado
Ej: 8 + (7) con n = 8

0000 1000
+

1111 1000
1 0000 0000

Se recicla el bit sobrante

1
0000 0001
1

Tabla 2.8. Operaciones aritmticas en Complemento a Uno

Sistemas de numeracin y representacin de la informacin

43

En todo lo expuesto anteriormente resulta evidente que el Complemento a Dos se


diferencia del Complemento a Uno solamente en una unidad, por tanto se puede
reducir el clculo del Complemento a Dos a la realizacin del complemento lgico y
sumarle uno al resultado.

2.3.7. Punto Fijo BCD


BCD (Binary Coded Decimal) como indican sus siglas en ingls es un sistema
decimal codificado en binario. Consiste en traducir uno a uno los dgitos decimales al
sistema binario. Dado que hay 10 dgitos diferentes en el sistema decimal, se
necesitar un nmero n de bits que cumpla 2n>10, lo que significa que se requieren,
como mnimo, 4 bits. Esto presenta un problema de infrautilizacin del espacio, ya
que con 4 bits podemos representar 16 smbolos diferentes, es decir, 6 ms de los
que se necesitan8. Sin embargo, se justifica su uso porque en ocasiones es ms
rentable que realizar una doble traduccin Entrada:decimal:binario:decimal:Salida;
es til para seguir las operaciones con el redondeo decimal en vez del binario;
tambin, gracias al espacio libre, se puede utilizar para codificar el signo o la coma
decimal.
Algunos sistemas decimales codificados en binario se presentan en la Tabla 2.9.
Sistema

Se asocia a cada dgito...

Ej: 54)10 se representa como

BCD

su valor en binario Puro

0101 0100

Exceso a tres

el valor incrementado en tres, en binario

1000 0111

2-4-2-1

el binario, usando 2421 como vector de 0101 0100


pesos en lugar de 8421

2 entre 5

Un cdigo con dos bits a 1 y tres a 0

01010 01001

Tabla 2.9. Diferentes sistemas decimales codificados en binario

Estos cdigos presentan una dificultad adicional a la hora de generar los acarreos
cuando se realizan operaciones aritmticas.
Se pueden dar dos tipos de codificacin segn utilicen 4 u 8 bits de cada byte para
almacenar informacin, denominndose desempaquetado y empaquetado,
respectivamente.

2.3.8. Punto Flotante


Se ha visto hasta ahora la forma de representar nmeros enteros; se estudian ahora las
distintas formas de representar nmeros racionales. Considerada esta necesidad desde
los albores de los ordenadores, siempre salvo un perodo propiciado por la
influencia de John von Neumann se ha dotado a stos de la capacidad de manejar
nmeros que llevan asociado un factor de escala. Este tipo de representacin es

Esto significa un 37% de desperdicio.

44

Programacin en C

conocida con cuatro nombres: Punto Flotante, Coma Flotante, Notacin Cientfica o
Notacin Exponencial.
A continuacin (Ecuacin 2.9), se muestra la expresin general de un nmero
representado con punto flotante.

V N

M BE

Ecuacin 2.9. Representacin de un nmero en Punto Flotante

De forma que M es la mantisa o parte fraccionaria, B es la base, y E el exponente.


Si se utilizan n bits, entonces se tendrn m bits para la parte fraccionaria, y e bits para
la para el exponente; resulta obvio que n = m + e.
Tanto la mantisa como el exponente se representa en uno de los sistemas de punto
fijo vistos utilizando la base B. Normalmente, se utilizar base 2 o base 16.
Si al representar la mantisa, sta se representa entera entonces se est ante un
sistema de Punto Flotante con Mantisa Entera.
Por ejemplo:
12, 5)10 = 125 10-1 = 00011001 2-1

Hasta la dcada de los 80 cada fabricante utilizaba un sistema propio de


representacin de los nmeros reales por lo que se hizo necesaria una estandarizacin
y por ello se impuso la norma 7549 de IEEE sobre aritmtica en punto flotante y que
corresponde a un caso de Punto Flotante con Mantisa Fraccin.
Para un sistema con mantisa fraccin la coma se coloca dependiendo del sistema
de representacin. Normalmente, la mantisa se representa en signo magnitud,
colocndose la coma entre el bit de signo y el primero de magnitud. Para B = 2, la
mantisa est normalizada cuando el primer dgito significativo vale uno por tanto,
para normalizar una mantisa hay que modificar adecuadamente el valor del
exponente. Se utiliza la normalizacin para que las cifras significativas estn
ocupando posiciones de mayor peso y conservar la precisin.
Una caracterstica que diferencia a los sistemas de punto fijo de los de punto
flotante es la resolucin: en los primeros sta es uniforme e igual a uno en todo el
rango de representacin, mientras que en los ltimos, la resolucin va variando en el
rango segn el valor del exponente.
El estndar IEEE 754 tiene las siguientes especificaciones:
V N M BE .
La base es 2 y como es bien conocida no se almacena.
Slo se almacenan mantisa, exponente signo, a travs de sus respectivos
campos. De forma que se reserva un bit s para el signo del nmero, s;
ne bits para el campo del exponente (incluido el signo), e; y nm bits
para el campo de la mantisa, m.
Se cumple que n = 1 + ne + nm.
El orden de almacenamiento es s e m.
9

La norma 854-1987 generaliza la 754-1985 para cubrir aritmtica decimal adems de la


binaria.

Sistemas de numeracin y representacin de la informacin

45

Si s = 0 el nmero es positivo; si s = 1, nmero negativo.


El exponente sigue la notacin de Entero Sesgado: e se calcula sumando
al exponente (E) un sesgo, S este mtodo permite almacenar
exponentes positivos y negativos sin la necesidad de un bit de signo.
De la siguiente forma:

2 ne 1 1; e

2 ne 1

Cuando el nmero est normalizado M 1 , o lo que es lo mismo, a la


izquierda del punto decimal hay un uno. En este caso no se almacena la
parte entera, slo la parte fraccionaria; no almacenando el 1., que se
considera implcito, se ahorra espacio y se dice que el nmero est
empaquetado. Se guarda el campo de mantisa, m, que verifica lo que
sigue:

1.m

con

1 M

Por ejemplo, supngase n = 16 bits y ne = 8, el valor decimal de un


nmero cuya representacin sea N = 1 0110 1000 1100 001
ser:
s = 1, luego el nmero es negativo.
e = 0110 1000 = 104)10 y, por tanto: E = e S = 104
127 = 23
m = 1100 001 y, por tanto: M = 1.1100 001 = 1 + 2-1 +
2-2 + 2-7 = 1 + 0.5 + 0.25 + 0.0078125 =
1.7578125)10
Por tanto: N = M 2E = 1.7578125
2-23 = 2,09547579288482666015625 10-7
Se dan una serie de casos particulares:
o

Cuando e = 0 se almacena la mantisa desnormalizada,


utilizndose el sesgo S 2 ne 1 2;

El nmero N = 0 se corresponde con los campos de exponente


y mantisa iguales a cero.

Cuando e toma su valor mximo, con todos sus bits a 1, el


valor del nmero representado es:
+ - , si m = 0.
NaN (Not a Number), si m 0.

En el caso de los nmeros reales es necesaria la utilizacin de alguna


tcnica de redondeo, ya que, en la mayora de los casos, no se podr
representar exactamente con un nmero fijo de bits. La norma especifica

46

Programacin en C

un redondeo al nmero par cuando se produce el mismo error al


aproximar por arriba o por abajo.
Se tienen los siguientes valores mximos y mnimos representables:
o

Valor absoluto del nmero mayor (b)


Mantisa:

M 2 2 ( nm 1)
E 2S 2 ne 2

Exponente

M 2E

( nm 1)

(2 2

)2 2

ne

Tabla 2.10. Valores de mantisa y exponente para b

Valor absoluto del nmero menor (a)


Mantisa: (normalizada)
Mantisa: (desnormalizada)

M 1
M des 2

Exponente

E
M 2E

a' M des 2 E

(2

nm

(2 ne 1

ne 1

nm

2)

2)

( 2 ne

2)

Tabla 2.11. Valores de mantisa y exponente para a

Con estas condiciones hay dos grupos de nmeros no


representables:
con
N 0 . Los nmeros
a N a,
correspondientes a esta zona provocan lo que se
conoce como desbordamiento a cero (underflow) y
desbordamiento gradual a cero cuando incluye a los
datos desnormalizados.

N b, N
b . Los nmeros correspondientes a
esta zona provocan lo que se conoce como
desbordamiento (overflow).
N<0
-b

-a

N>0
0

+a

underflow

overflow

Figura 2.2. Rangos de representacin para el estndar IEEE 754

Se especifican dos formatos bsicos:


o

Simple precisin.

+b
overflow

Sistemas de numeracin y representacin de la informacin

47

Nmero de bits: n = 32; ne = 8 y nm = 23


7

Sesgo: S 2
Si e 255 y m
Si e 255 y m

1 127
NaN

0
0

Si 0

e 255

Si e

0 ym 0

Si e

0 ym

( 1) s

( 1) s 2 e

127

1.m

(normalizado)

( 1) s 2

126

0.m

(desnormalizado)

( 1) s 0

Tabla 2.12. Formato de simple precisin


o

Doble precisin.

Nmero de bits: n = 64; ne = 11 y nm = 52


Sesgo:

210 1 1023

Si e
Si e

2047 y m 0
2047 y m 0

NaN

( 1) s

Si 0

e 2047

( 1) s 2 e

1023

1.m

(normalizado)
Si e

0 ym 0

( 1) s 2

1022

0.m

(desnormalizado)
Si e

0 ym

( 1) s 0

Tabla 2.13. Formato de doble precisin

2.4. Cdigos
2.4.1. Cdigos de E/S
Como ya se dijo en la introduccin de este captulo, en la ejecucin de un programa
estn involucrados dos flujos de informacin: datos de entrada y datos de salida. Esta
informacin, compuesta por caracteres de tipo numrico, alfabtico o especial, ha de
ser traducida a unas determinadas combinaciones de bits. Mediante los cdigos de E/S
se consigue esta transformacin. Por tanto, un cdigo de E/S es una correspondencia
entre los conjuntos

A, B, C,..., X , Y , Z , a, b, c,..., x, y, z,0,1,...,9, /, , (, ), [, , :,...


y

0,1

De forma que el nmero m de elementos del conjunto


depender de la
codificacin de E/S que se est utilizando, suponiendo que se utilice un nmero fijo,

48

Programacin en C

n, de bits para la codificacin. El nmero de smbolos del conjunto que se pueden


codificar es directamente dependiente de n. O visto desde el otro lado del espejo: el
valor mnimo de n depender del nmero m de elementos de . Por tanto:
Con:
n = 2 (bits para la codificacin)
se tiene:
m = 4 (hasta 4 smbolos codificados)

00
01
10
11

Figura 2.3. Combinaciones para 2 bits de codificacin

000
001
010
011
100
101
110
111

Con:
n = 3 (bits para la codificacin)
se tiene:
m = 8 (Hasta 8 smbolos codificados)

Figura 2.4. Combinaciones para 3 bits de codificacin

De lo que se deduce que para un n dado,


m = 2n
Ecuacin 2.10. Combinaciones posibles para un n dado

O lo que es lo mismo, para codificar m smbolos se necesitarn:

log 2 (m)

3.32 log( m)

Ecuacin 2.11. Nmero de bits necesarios para codificar m smbolos

En realidad, n debe ser el menor nmero entero que satisfaga la relacin anterior,
ya que slo se puede trabajar con un nmero entero de bits. Adems, se pueden
crearse cdigos que utilicen ms bits de los estrictamente necesarios.
Desafortunadamente, con el paso del tiempo, se han diseado muchos de esos
cdigos y se han utilizado con diferentes equipos, producindose una proliferacin de
los problemas de comunicacin [Glenn, 1995].
Debido a lo expuesto, es lgico pensar en la necesaria existencia de cdigos
normalizados que puedan utilizarse por todos los fabricantes de ordenadores, en lugar
de tener que enfrentar a un maremagno de cdigos arbitrarios. A continuacin se
realiza un rpido repaso a algunos de los ms difundidos.
Se ver que histricamente la evolucin de los distintos cdigos adoptados ha sido
influida por la creciente cantidad de caracteres que se han impuesto como una
necesidad y, por lo tanto, varan en funcin del nmero bits utilizados para realizar la
codificacin.

Sistemas de numeracin y representacin de la informacin

49

Inicialmente slo se trabajaba con 26 letras (A..Z), 10 nmeros (0..9), signos


de puntuacin (. , ; :...) y caracteres especiales (+ - ]). De forma que en los
aos 50 se utilizaron varios cdigos de 6 bits, lo que permita codificar hasta 64
caracteres deferentes. Los ms conocidos son el Fieldata, el BCD (Binary Coded
Decimal) o el X.3.
Una dcada ms tarde, nuevas necesidades, como caracteres de control para
perifricos, dieron lugar a cdigos de 7 bits, que incluyen a el ASCII (American
Standard Code for Information Interchange). Ms tarde aparecieron algunos cdigos
de 8 bits como el EBCDIC (Extended Binary Coded Decimal Interchange Code).
Sin embargo, en la actualidad el ms difundido sin duda es el ASCII extendido, de
8 bits, que incluye, entre otros muchos smbolos, las letras acentuadas y la .
Adems, hay que aadir que en estos ltimos los caracteres se ordenan de forma
que sea ms cmodo tratarlos; algunas de las ideas que contribuyen a este objetivo
consisten en agrupar los caracteres numricos o colocar las letras maysculas y
minsculas de forma que les correspondan cdigos que en binario difieran en un
nico bit.
0
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

NUL
LF
DC4
RS
(
2
<
F
P
Z
d
n
x

SOH
STX
ETX
VT
FF
CR
NAK
SYN
ETB
US Espacio
!
)
*
+
3
4
5
=
>
?
G
H
I
Q
R
S
[
\
]
e
f
g
o
p
q
y
z
{

EOT
SO
CAN

,
6
@
J
T
^
h
r
|

ENQ
SI
EM
#
7
A
K
U
_
i
s
}

ACK
DLE
SUB
$
.
8
B
L
V
`
J
T
~

BEL
DC1
ESC
%
/
9
C
M
W
a
k
u
DEL

BS
DC2
FS
&
0
:
D
N
X
b
l
v

TAB
DC3
GS

1
;
E
O
Y
c
m
w

Tabla 2.14. Sistema de Codificacin ASCII

50

Programacin en C

En la Tabla 2.14 se muestran los caracteres correspondientes al cdigo ASCII


extendido para MS-DOS (existen otras distribuciones como la correspondiente a la
norma ANSI, American National Standars Institute). Entre ellos hay un grupo no
imprimible que forman el grupo de los caracteres de control necesarios, por
ejemplo, para el control de perifricos y que se muestran en la Tabla 2.15 y el
carcter blanco o Espacio y que se corresponde con la separacin entre palabras,
que s es imprimible.
NUL Null
SOH Start of heading
STX Start of text
ETX End of text
EOT End of transmission ENQ Enquiry
Backspace
ACK Acknowledge
BEL Bell
BS
Line feed, new line VT
Vertical tab
TAB Horizontal tab
LF
Carriage return
Shift out
Shift in
CR
SO
SI
DLE Data link escape
DC1 Device control 1
DC2 Device control 2
Negative
DC3 Device control 3
DC4 Device control 4
NAK
Acknowlwedge
SYN Syncrhronous idle ETB End of trans. block CAN Cancel
End of medium
EM
SUB Substitute
ESC Escape
File separator
Group separator
Record separator
FS
GS
RS
Unit separator
US
DEL Delete
Tabla 2.15. Caracteres de control del ASCII extendido

2.4.2. Cdigos redundantes


Hasta ahora se ha visto cmo se codifica, pero desgraciadamente no es suficiente con
disear estos cdigos puesto que la informacin no est libre de errores producidos
durante su almacenamiento, manipulacin o transmisin. Con intencin de resolver
este problema se han ideado numerosas tcnicas capaces de detectar e incluso corregir
estos errores. La mayora de estas tcnicas de codificacin pasan desapercibidas para
los usuarios de un ordenador ya que se incluyen en los componentes internos de la
mquina.
Estos cdigos han recibido el nombre de cdigos redundantes.
La idea es introducir informacin aadida o redundante de forma que junto con la
informacin significativa forme un par nico que cumpla una regla, de forma que si el
par no la cumple se pueda detectar que ha ocurrido un error. Con la suficiente
cantidad de informacin redundante se puede localizar el error y corregirlo.
Una buena regla debe ser capaz de detectar (y corregir en su caso) todos los errores
posibles (pues un error podra producir un par que cumpla la regla).
A continuacin se resumen algunos de los cdigos redundantes ms utilizados.
2.4.2.1. Cdigo de paridad
Se trata del mtodo de deteccin de errores ms sencillo. Simplemente consiste en
aadir un bit a cada dato codificado, de forma que este bit adicional llamado de
paridad, es un cero si el nmero de unos que forma el dato es par y uno en caso
contrario. As, por ejemplo:
0
1
1
1
1
1
1
1
1

Sistemas de numeracin y representacin de la informacin

51

0
0
1
0
1
0
1
0
1
0
0
1
1
0
0
1
1
0
Sin embargo, slo permite detectar error en un bit, ya que si se produjera error en
dos bits (o en un nmero par de bits), la paridad resultara correcta.
2.4.2.2. Cdigos correctores de Hamming
Los cdigos correctores mejoran el caso anterior, ya que permiten detectar errores en
ms de un bit adems de corregirlo, mediante una inversin de los bits errneos.
Para conseguir llevar a cabo tales propsitos se utilizan un nmero N de bits de
paridad que afectan individualmente a diferentes partes del cdigo, de forma que se
pueda decidir cules son las posiciones afectadas por el error mediante un mecanismo
de exclusin. Un mtodo bien conocido consiste en utilizar bits de paridad en
horizontal y vertical, es decir, para cada matriz de 8x8 bits se tiene un bit de paridad
por fila y otro por columna. As, un error en una posicin determinada de la matriz
ser detectado por los bits de paridad que indican sus coordenadas. Por ejemplo:
0
1
1
1
1
1
1
1
1
0
0
1
E
1
0
1
0
0
0
0
1
1
0
0
1
1
0
1
0
0
1
0
1
0
1
0
0
0
0
0
0
1
1
1
1
1
1
1
1
1
0
0
0
1
1
0
1
0
1
0
1
0
0
0
1
0
1
0
1
0
1
0
1
1
1
0
0
0
1
1
1
En caso de aparecer errores en ms de un bit, se podrn detectar, pero no corregir.
Los cdigos de Hamming tratan de optimizar este problema. Para ello, utilizando p
bits de paridad podrn corregir errores sencillos en datos de longitud n, siempre que
se cumpla que 2 p n p 1 .
Para un ejemplo como el anterior, en que se tienen 64 bits, n = 64, y dado que
128 > 64 + 7 +1, p ser 7, lo que mejorara sustancialmente el nmero de bits
de paridad necesarios (17 en el ejemplo anterior).
Un ejemplo es el cdigo de Hamming 11 (datos) + 4 (paridad), que tiene la
estructura que se muestra en la Figura 2.5.
2.4.2.3. Cdigos polinomiales
Los cdigos polinomiales o cclicos permiten detectar errores mltiples cuando estos
se producen en bits consecutivos. Se basan en aadir al dato el resultado de realizar la
operacin mdulo del mismo respecto a una determinada base, por tanto el dato pasa
a ser [dato, dato mod base].
Son tpicamente aplicados en las operaciones de transmisin en serie de
informacin donde un fallo en el medio de transmisin produce un error en un cierto
nmero de bits consecutivos.

52

Programacin en C

Las potencias de 2 son


los bits de paridad
d11

d10

d9

d8

d7

d6

d5

p8

d4

d3

d2

p4

d1

p2

p1

b15

b14

b13

b12

b11

b10

b9

b8

b7

b6

b5

b4

b3

b2

b1

15
p1 1
p2 1
p3 1
p4 1

14

13
p1 1
0
p3 1
p4 1

12

11
p1 1
p2 1
0
p4 1

10

9
p1 1
0
0
p4 1

7
p1 1
p2 1
p3 1
0

5
p1 1
0
p3 1
0

3
p1 1
p2 1
0
0

1
p1 1
0
0
0

0
p2 1
p3 1
p4 1

0
0
p3 1
p4 1

0
p2 1
0
p4 1

8
0
0
0
p4 1

0
p2 1
p3 1
0

4
0
0
p3 1
0

0
p2 1
0
0

Cada bit est protegido por los bits de paridad que indica su orden expresado en binario
p1 = b3 + b5 + b7 + b9 + b11 + b13 + b15 =
= d1 + d2 + d4 + d5 + d7 + d9 + d11
Cada bit de
paridad se
calcula como
la suma de
todos los bits
de datos a los
que protege

p2 = b3 + b6 + b7 + b10 + b11 + b13 + b15 =


= d1 + d3 + d4 + d6 + d7 + d10 + d11
p3 = b5 + b6 + b7 + b12 + b13 + b14 + b15 =
= d2 + d3 + d4 + d8 + d9 + d10 + d11
p4 = b9 + b10 + b11 + b2 + b13 + b14 + b15 =
= d5 + d6 + d7 + d8 + d9 + d10 + d11

Figura 2.5. Cdigo de Hamming 11 + 4

Una base tpica es 1100000001011 que se corresponde con el polinomio X12 +


X + X3 + X + 1.
A continuacin se muestran algunos de los polinomios ms utilizados:
CRC-12: X12 + X11 + X3 + X + 1
11

CRC-16: X16 + X15 + X2 + 1


CRC-CCITT: X16 + X12 + X5 + 1
CRC-32: X32 + X26 + X23 + X22 + X16 + X12 + X11 + X10 +
X8 + X 7 + X 5 + X4 + X2 + X + 1

2.5. Resumen
Se ha realizado en el presente captulo una revisin de los sistemas numricos y de
representacin de la informacin ms destacable, partiendo de los ms sencillos hasta
los ms elaborados, para facilitar la comprensin al lector.
Se ha recorrido el camino que ha seguido la humanidad en su evolucin hasta los
sistemas de representacin de la informacin utilizados por los ordenadores. Se han

Sistemas de numeracin y representacin de la informacin

53

visto cules son las restricciones que aparecen al disponer siempre de un espacio
limitado para codificar los datos.
Partiendo del sistema decimal, que se utiliza en la vida cotidiana, se ha
generalizado a un sistema posicional de cualquier base. Se ha hecho un especial
hincapi en el sistema binario, el sistema de numeracin de la informtica por
excelencia, y se han repasado las principales operaciones aritmticas, analizando sus
ventajas y problemas. Se ha resaltado convenientemente los cambios entre las
distintas bases que utilizan los computadores.
Dada la relevancia de las operaciones aritmticas en cualquier sistema informtico,
se han introducido los fundamentos matemticos necesarios para comprender cmo se
puede manejar internamente los nmeros, tanto de tipo entero, ms sencillos, como
los de tipo real, con la complejidad que estos llevan asociada. A partir de este
momento ya se est en disposicin de poder destacar las propiedades de cada uno de
los sistemas de representacin interna de la informacin.
Finalmente, dado el carcter introductorio de este captulo, se han comentado
brevemente los principales cdigos de Entrada/Salida, as como algunos de los
mecanismos de proteccin de la informacin y de correccin de errores.

2.6. Lecturas complementarias


En este apartado se recopilan algunas referencias bibliogrficas, as como localizacin
de documentacin en la Web, que permitirn al lector profundizar en aquellos
conceptos relacionados, en mayor o menor medida, con los expuestos en este captulo.
Para comprender la evolucin histrica de los sistemas de numeracin, de forma
amena y no dirigida a especialistas, es interesante el libro Por qu el mundo es
matemtico? (Grijalbo-Mondadori, 1997), de John D. Barrow, en el que se recogen
una serie de conferencias dictadas en la Universidad de Miln. Si se quiere tener una
idea general del papel de los sistemas de numeracin en un computador el libro
Structured Computer Organization 4rd Edition (Prentice May, 1999), del renombrado
Andrew S. Tanenbaum. En sus apndices se pueden encontrar dos buenos repasos de
los nmeros binarios y de los nmeros en punto flotante.
El
estndar
754
de
IEEE
est
publicado
en
la
pgina
http://grouper.ieee.org/groups/754/, donde se aloja otra informacin relacionada con
el grupo de trabajo y, entre otra informacin valiosa, se puede encontrar una lista de
preguntas frecuentes sobre la aritmtica en punto flotante. El artculo de David
Golberg, What Every Computer Scientist Should Know About Floating-Point
Arithmetic (ACM Computing Surveys, pp. 5-48, vol. 23#1, 1991) realiza un completo
resumen del estndar 754 y profundiza en aspectos como la forma en que se deberan
construir las mquinas que soporten este tipo de aritmtica.
Desde el punto de vista del programador, Douglas M. Priest realiza en su tesis, On
Properties of Floating Point Arithmetics: Numerical Stability and the Cost of
Accurate Computation (disponible en ftp://ftp.icsi.berkeley.edu/pub/theory/priestthesis.ps.Z), un estudio de las propiedades de la aritmtica en punto flotante que
influyen en la computabilidad, precisin, coste y portabilidad.

54

Programacin en C

Unos buenos libros de referencia en cuanto a los cdigos correctores de errores


son: Error Control Coding: Fundamentals and Applications de S. Lin y D. J.
Costello, Jr. (Prentice Hall: Englewood Cliffs, NJ, 1983), Error-Correcting Codes.
2nd edition, de W.W. Peterson y E.J. Weldon, Jr. (MIT Press: Cambridge, Mass.,
1972), y The Theory of Error-Correcting Codes de F.J. MacWilliams y N.J.A.
Sloane (North-Holland: New York, NY, 1977). En la pgina web The Error
Correcting Codes (ECC) Page (http://www.eccpage.com/) se mantiene una biblioteca
de programas escritos en C con los cdigos correctores ms conocidos.

2.7. Cuestiones y ejercicios


1.
2.

3.
4.
5.
6.

7.

8.
9.

Escribe, si es posible, los siguientes nmeros en binario puro con una longitud de
palabra m = 5:
10, 20, 30, 40
Calcula el rango de representacin para una palabra de m = 16 en los siguientes
sistemas:
Punto fijo sin signo.
Punto fijo con complemento a uno.
Punto fijo con complemento a dos.
Cmo se representa el nmero 7 en ASCII? Y en binario puro? Por qu se
utilizan ambas representaciones?
Cules son los cdigos intermedios y por qu reciben este nombre?
Escribe todos los nmeros entre 1 y 50 en binario puro, en hexadecimal y en
octal.
A qu operaciones corresponden los desplazamientos de las cifras a izquierda y
derecha en un sistema posicional? Encuentra la correspondencia en decimal de la
siguientes series de desplazamientos:
00101110; 01011100; 10111000
10101000; 01010100; 00101010
00000001; 00000100; 00010000
Realiza las siguientes operaciones en binario puro, complemento a uno y
complemento a dos:
234 + 512
67 78
42 61
Codifica los siguientes nmeros en BCD:
1234, 999901, 312
Se tienen los siguientes datos de una representacin interna de nmeros reales,
n=16, ne=6 y B=2. Determinar qu nmeros representan cada una de las
siguientes codificaciones:
0111 0000 1110 1111
1111 1111 0000 1111
1010 1010 1010 1010
0111 1111 1111 1111

Sistemas de numeracin y representacin de la informacin

55

10. Representa los siguientes nmeros siguiendo el estndar 754 en sus dos formatos
bsicos: simple precisin y doble precisin.
23674.758423
1.5784343345
4.5
-89999.000056732
11. Encuentra y corrige el bit errneo en la siguiente informacin transmitida:
1
1
1
1
1
1
1
1
0
1
0
1
1
1
1
1
0
0
1
1
0
1
0
0
1
1
1
0
0
0
0
0
1
0
1
1
0
0
0
1
0
1
0
0
0
1
0
0
1
1
0
0
0
1
1
0
0
1
1
0
0
0
1
1
1
0
1
0
1
1
1
0
0
0
0
1
0
1
0
0
0
12. Utilizando el Cdigo de Hamming 11 + 4, calcula los bits de paridad para
proteger los siguientes datos:
111 0010 0010
111 1111 1111
000 0000 0000
010 1010 1010

2.8. Referencias
[Glenn, 1995] Glenn Brookshear, J.. Introduccin a las Ciencias de la Computacin. 4
Edicin. Addison-Wesley Iberoamericana, 1995.
[Miguel, 1994] Miguel Anasagasti, P. de. Fundamentos de los Computadores. 4 Edicin.
Paraninfo, 1994.
[Prieto et al., 2002] Prieto, A., Lloris, A. y Torres, J. C. Introduccin a la Informtica. 3
Edicin. McGraw-Hill, 2002.

3. Diseo de Programas. Programacin Estructurada


El desarrollo de programas de ordenador, con independencia de su tamao y
complejidad, conlleva un proceso que involucra diferentes mtodos de desarrollo y
diversas herramientas que, de alguna forma, automatizan y apoyan la creacin de
dichos programas.
En este captulo se van a introducir los conceptos bsicos necesarios para entender
el proceso de creacin de programas de ordenador, ubicando la fase de
implementacin (tambin denominada programacin o codificacin) dentro del ciclo
de vida del producto software.
En el contexto introductorio en el que se desarrolla este texto, se va a hacer un
especial hincapi en un conjunto de tcnicas que faciliten el diseo, implementacin y
mantenimiento del software desarrollado.
Este captulo se ha organizado en cuatro secciones. La primera de ellas sirve para
establecer el vocabulario bsico que se va a manejar a lo largo del captulo,
definiendo los conceptos elementales relacionados con la programacin. La segunda
seccin explica, sin entrar en muchos detalles, lo que sera un ciclo de vida del
software genrico, con intencin de hacer entender que en la concepcin de un
producto software existen ms actividades que la codificacin en un lenguaje de
programacin concreto. En la tercera seccin de este captulo se detallan las fases que
involucran el diseo detallado y codificacin de los mdulos del programa a realizar;
se aprovecha este repaso para introducir diferentes tcnicas y herramientas que se
utilizan en estas fases. La ltima seccin presenta, con fines informativos, otros
paradigmas de desarrollo muy arraigados en la actualidad en la construccin de
software.

3.1. Conceptos bsicos


Segn se ha comentado ya previamente, un ordenador es una mquina especializada
en el procesamiento o tratamiento de datos, donde la naturaleza de los datos de
entrada y los procesos de transformacin en datos de salida pueden llegar a ser
tremendamente dispares y complejos. Sin embargo, las operaciones primitivas que un
ordenador es capaz de llevar a cabo son sumamente sencillas: operaciones aritmticas,
lgicas y transferencia de datos. Esto conduce a que, para la resolucin de un
problema real, sea necesario encontrar un mtodo de solucin, para despus
transformarlo en un conjunto finito de instrucciones que el ordenador sea capaz de
realizar.
Sin embargo, no todos los mtodos de solucin de un problema se pueden
implementar en un ordenador. Existen problemas que se consideran intratables, esto
es, aunque resolubles en principio, son irresolubles en la prctica, ya que para un
tamao del problema razonablemente grande, el tiempo que se necesitara para
obtener una solucin se hara rpidamente mayor que la edad del universo, con
independencia de aumentos, de cualquier tipo previsible, en la velocidad de operacin
de un ordenador.
- 57 -

58

Programacin en C

Para que un procedimiento de solucin pueda implementarse en un ordenador debe


cumplir las siguientes restricciones:
Debe estar bien definido: Las acciones que lo componen no pueden ser
ambiguas, de forma que el significado de cada accin tiene que ser nico en
el contexto en que aparecen.
Debe ser preciso: Debe estar compuesto por una secuencia finita de
operaciones, quedando perfectamente definido el orden en que se deben
realizar las mismas.
Debe ser finito: Debe acabar en un tiempo finito, ya que algo que no tiene
fin no es til para resolver un problema.
Un mtodo de solucin a un determinado problema que cumpla las restricciones
anteriores se dice que es un algoritmo que resuelve dicho problema.
El vocablo algoritmo procede del nombre del matemtico persa del siglo IX Abu
Jafar Mohammed ibn Msa al-Khowrizm, autor de un importante texto
matemtico, en torno al ao 825 d.C., titulado 1 Kitab al abr wal-muqabala. Que
hoy en da se emplee la palabra algoritmo, en lugar de la antigua y ms aproximada
algorism, parece que se debe a una asociacin con la palabra aritmtica.
No obstante, mucho antes del libro de al-Khowrizm ya se conocan ejemplos de
algoritmos. Uno de los ms conocidos es el hoy llamado algoritmo de Euclides para
encontrar el mximo comn divisor de dos nmeros, y que data del ao 300 a.C. Este
procedimiento de solucin se puede representar como se muestra en el Cuadro 3.1.
Leer dos nmeros naturales A, B
Repetir
C
A mdulo B
Si C <> 0 entonces
A
B
B
C
Hasta C = 0
Imprimir la solucin es B
Cuadro 3.1. Algoritmo de Euclides

El algoritmo de Euclides es un procedimiento sistemtico por el que se encuentra


el mximo comn divisor buscado. Este algoritmo se puede aplicar con toda
generalidad a nmeros de cualquier magnitud. En cualquier caso concreto siempre se
llega a una solucin en un nmero finito de pasos, estando perfectamente claro en
cada paso que accin debe realizarse, estando totalmente definido cual es la decisin
que indica el final de todo el proceso.
En general un algoritmo se puede definir como el procedimiento no ambiguo que
resuelve un problema. Un procedimiento es una secuencia de operaciones bien
definidas, cada una de las cuales requiere una cantidad finita de memoria y se realiza
en un tiempo finito [Goodman y Hedetniemi, 1977].
Un programa puede entenderse como la forma de expresar la solucin a un
problema de manera que sea comprensible para el ordenador. En otras palabras, un
programa se puede definir como un conjunto ordenado de instrucciones que se dan a
la computadora indicando el conjunto de operaciones o tareas que se desea llevar a
1

Es digno de mencin que la palabra lgebra procede del rabe al jabr que aparece en el ttulo
del libro.

Diseo de Programas. Programacin Estructurada

59

cabo. Por su parte, una instruccin es un conjunto de smbolos que representa una
orden de operacin o tratamiento para el ordenador.
Existen diversos estilos de programacin (estructurada, orientada a objetos, basada
en reglas, lgica, funcional...), sin embargo esta introduccin se centra
exclusivamente en la programacin estructurada.
Utilizar programacin estructurada implica escribir un programa de acuerdo a las
siguientes reglas:
Diseo modular atendiendo a diferentes niveles de abstraccin.
Utilizacin de diseo descendente (top-down) en la definicin de los
mdulos.
Cada mdulo se construye utilizando las tres estructuras de control bsicas:
secuencia, seleccin y repeticin.
Cuando se dice que un programa tiene un diseo modular se est haciendo
referencia a la denominada programacin modular, que es la tcnica por la cual el
programa se divide en mdulos (partes independientes) cada uno de los cuales lleva a
cabo una nica actividad, codificndose y depurndose de forma independiente de
los otros mdulos.
Segn Dijkstra, descomponer un programa en trminos de recursos abstractos
consiste en descomponer una determinada accin compleja en trminos de un nmero
de acciones ms simples capaces de ejecutarlas o que constituyan instrucciones de
ordenador disponibles.
El diseo descendente es el proceso por el
que un problema se descompone en una serie
de niveles o pasos sucesivos de refinamiento.
Recibe el nombre de descendente o top-down
porque va desde lo general a lo particular, de
niveles de abstraccin ms altos a niveles ms
concretos, aplicando sucesivos refinamientos
que aaden informacin, esto es, el diseo se
refina como una jerarqua de niveles de
Nivel n
qu hace?
detalles creciente [Wirth, 1971]. Se tiene una
Nivel n+1 cmo lo hace?
descomposicin del problema en etapas.
Figura 3.1. Diseo descendente
Las estructuras de control son los
mtodos para especificar el orden en el que
las instrucciones de un algoritmo van a ser ejecutadas. El orden de ejecucin de las
instrucciones determina el flujo de control. Existen tres tipos de estructuras de
control: secuencia, seleccin y repeticin (ver Figura 3.2).
La secuencia establece un conjunto de instrucciones que se ejecutarn de
forma secuencial.
Las estructuras de seleccin, tambin denominadas estructuras alternativas
son aqullas que controlan la ejecucin de una o ms instrucciones en
funcin de que se cumpla o no una condicin previamente establecida.
Las estructuras repetitivas son aqullas que permiten variar la secuencia
normal de ejecucin de un programa haciendo posible que un grupo de
instrucciones se ejecute ms de una vez de forma consecutiva. A estas
estructuras se las denomina tambin bucles o lazos.

60

Programacin en C

Figura 3.2. Estructuras de control

En resumen, un programa elaborado siguiendo las tcnicas de programacin


estructurada, tendr las siguientes caractersticas:
Consta de un mdulo especial, denominado programa principal, que controla
todo lo que sucede.
El programa principal transfiere el control a los diferentes mdulos
(subprogramas), para que estos ejecuten sus funciones.
Cada subprograma devuelve el control al programa principal al terminar su
tarea.
Si la tarea a realizar por un mdulo es demasiado compleja, ste puede
subdividirse a su vez en otros mdulos ms pequeos (ver Figura 3.3).
Un mdulo puede transferir temporalmente el control a otro mdulo, pero
cada mdulo debe devolver el control al mdulo del cual lo recibi.
Los mdulos se codifican y se prueban por separado, permitiendo que
varias personas puedan trabajar en el desarrollo de forma simultnea en
diferentes partes del mismo programa.
P ro g ra m a ci n M o du la r
P ro gram a P rin cip a l
M d u lo 1

M d u lo 1 .1

M d u lo 1.2

Nivel 1
Nivel 2

M d ulo 2

M d u lo 3

M d u lo 2 . 1

M d u lo 3 .1

M du lo 2 .1 .1

M d ulo 2 .1 .2

M d u lo 4

Nivel 3

Nivel 4

Figura 3.3. Estructura de un programa modular

Previamente a la construccin de cada mdulo se debe disear un algoritmo que


especifique la secuencia de pasos de se tienen que cumplir para resolver la tarea que
representa. Mientras que para representar un algoritmo se puede utilizar el lenguaje
humano o un lenguaje especfico de descripcin de algoritmos, en la implementacin
del mdulo se utiliza un lenguaje de programacin.
Del uso de tcnicas de programacin modular se derivan dos importantes hechos:
Se reduce la complejidad del problema a tratar, reducindose los errores en la
codificacin.
Aumenta la productividad en el desarrollo, facilitando tambin el
mantenimiento.

Diseo de Programas. Programacin Estructurada

61

3.2. Ciclo de vida del software


La evolucin del software representa el ciclo de actividades involucradas en el
desarrollo, uso y mantenimiento de sistemas software [Scacchi, 1987]. Los proyectos
software se desarrollan en una serie de fases que van desde la concepcin del software
y su desarrollo inicial hasta su puesta en funcionamiento y posterior retirada por otra
nueva generacin de software. Estas fases se conocen como ciclo de vida del
software. Dichas fases pueden ser temporales, es decir, forman una secuencia en el
tiempo, o lgicas, cuando representan pasos o etapas que no constituyen una
secuencia temporal2.

Figura 3.4. Ciclo de vida del software

Se denomina ciclo de vida del software al perodo de tiempo que comienza


cuando se concibe un producto software y finaliza cuando el producto pierde su
utilidad. El ciclo de vida del software incluye las siguientes fases: fase de requisitos,
fase de diseo, fase de realizacin, fase de pruebas, fase de instalacin y aceptacin,
fase de operacin y mantenimiento y, algunas veces, fase de retirada [AECC, 1986].
Otro concepto que se maneja en relacin con el ciclo de vida del software es el
ciclo de desarrollo del software que se define como el perodo de tiempo que
comienza con la decisin de desarrollar un producto software y finaliza cuando se ha
entregado ste. Este ciclo incluye, en general, una fase de requisitos, una fase de
diseo, una fase de implantacin, una fase de pruebas, y a veces, una fase de
instalacin y aceptacin [AECC, 1986].
Un ciclo de vida genrico de software contiene tres bloques de actividades
esenciales, y que son independientes del paradigma o modelo de ciclo de vida que se
elija. Estas son: definicin, desarrollo y mantenimiento (ver Figura 3.5). Estas
actividades se encuentran en todos los desarrollos software, independientemente de su
rea de aplicacin, su tamao o su complejidad3.
2

Por ejemplo, la codificacin es una etapa que precede a la fase de pruebas. Sin embargo,
partes de la fase de codificacin y partes de la fase de pruebas pueden realizarse de forma
simultnea.
3 El ciclo de vida debe preverse para proyectos de cualquier dimensin y complejidad, si bien
en el caso de proyectos pequeos, varias etapas pueden ser agrupadas y algunas no existir.

62

Programacin en C

Definicin
Desarrollo
Fallos de
definicin

Mantenimiento
Modificaciones y
adaptaciones

Errores

Figura 3.5. Fases genricas del ciclo de vida de software

3.2.1. Fase de definicin


El principal objetivo de esta fase es establecer qu es lo que debe hacer el producto
software que se va a construir. Dependiendo de los diferentes modelos de ciclo de
vida las actividades a realizar en esta fase pueden variar, pero en general se busca
estudiar la viabilidad del proyecto, la planificacin del mismo y la elicitacin y
especificacin de los requisitos que se quieren satisfacer con el software a construir.
Por lo que respecta a los programas que se realizan en un primer acercamiento a la
programacin, o a modo personal para solucionar pequeos problemas, la fase de
definicin se reduce a establecer de forma concreta las funcionalidades que debe
contemplar el programa, los elementos de informacin tiles para obtener la solucin
final y la interaccin entre el usuario y el programa.

3.2.2. Fase de desarrollo


La fase de desarrollo se centra en el cmo. Los objetivos que se buscan en esta fase
son los siguientes:
i)
Diseo de las estructuras de datos y arquitectura del software.
ii)
Diseo de los detalles procedimentales.
iii)
Traduccin del diseo a un lenguaje de programacin.
iv)
Realizacin de pruebas.
Aunque los mtodos, o etapas, aplicados pueden variar de un modelo a otro, se
tienen tres pasos concretos:
Diseo: Traduce los requisitos del software a un conjunto de
representaciones4 que describen la estructura de los datos, la arquitectura, el
procedimiento algortmico y las caractersticas de la interfaz.
Codificacin: Traduccin de las representaciones del diseo a un lenguaje
de programacin, dando como resultado instrucciones ejecutables por el
ordenador.
Prueba: El software es probado en partes, que se ensamblan en conjuntos
progresivamente ms grandes. El objeto es corregir los defectos que puedan
existir. Las pruebas han de ser completas y, tericamente, deberan probarse
4

Grficas, tabulares o basadas en lenguajes.

Diseo de Programas. Programacin Estructurada

63

todos los caminos, con todos los casos posibles. En general, no se puede
asegurar totalmente la ausencia de errores.

3.2.3. Fase de mantenimiento


La fase de mantenimiento se centra en el cambio que va asociado a la correccin de
errores (mantenimiento correctivo), a las adaptaciones requeridas por la evolucin del
entorno software (mantenimiento adaptativo), a las modificaciones debidas a los
cambios de los requisitos dirigidos a reforzar o ampliar el sistema software
(mantenimiento perfectivo) o a las previsiones que faciliten cualquiera de los casos
anteriores (mantenimiento preventivo).
La fase de mantenimiento vuelve a aplicar los pasos de las fases de definicin y de
desarrollo, pero en el contexto del software ya existente.

3.3. Creacin de un programa


Para ser un programador eficiente, es necesario que se sepa resolver problemas de un
modo riguroso y sistemtico. El proceso de diseo de un programa de ordenador se
caracteriza por ser un proceso creativo, en el que no va a existir un conjunto completo
de reglas que indiquen como crearlo, debindose recurrir en muchas ocasiones a la
experiencia del propio desarrollador o a diferentes heursticos para elaborar una
solucin optimizada.
La construccin de programas en una primera fase de introduccin a la
programacin estructurada (o de introduccin a un lenguaje de programacin) se
centra en el diseo y codificacin de los diferentes algoritmos que dan solucin a la
funcionalidad que se quiere recoger en dichos programas. Desde esta perspectiva se
puede enunciar un mtodo sistemtico de programacin compuesto por los siguientes
pasos:
1. Anlisis del problema. Se especifica cules son los requisitos a los que se
debe dar solucin desde el programa a realizar.
2. Diseo y verificacin del algoritmo. Por escrito, se disea con detalle el
algoritmo (o algoritmos) que solucionar el problema.
3. Codificacin. El algoritmo diseando en el paso anterior, se escribe en un
lenguaje de programacin.
4. Compilacin y enlazado. Se traduce el cdigo fuente escrito a cdigo
mquina mediante el empleo de compiladores, generndose el programa
ejecutable por el ordenador.
5. Verificacin y depuracin. Se comprueba el correcto comportamiento del
programa. En caso de encontrar errores, se solucionan (depuran).
6. Documentacin. Se crea la documentacin del programa realizado. Se
distinguen dos tipos de documentacin:
Tcnica: para que cualquier programador ajeno entienda qu es lo
que hace el programa y cmo lo hace.

64

Programacin en C

De usuario: para los usuarios que manejarn y explotarn el


programa.
Aunque estos pasos se han enunciado de forma secuencial, existen interacciones y
realimentaciones entre ellos, tal y como refleja el organigrama de la Figura 3.6.

Figura 3.6. Pasos para la creacin de un programa

3.3.1. Anlisis del problema


El objetivo principal de esta fase es determinar qu ha de hacer el programa. Para lo
que:
Se define el problema lo ms exactamente posible, para poder conseguir una
solucin efectiva y eficaz.
Se identifican los elementos de informacin y clculo tiles para obtener la
solucin.
Se describen de forma precisa las especificaciones de entrada y salida.
Esta fase requiere mucha comunicacin entre el analista y los futuros
usuarios.
Se ha de utilizar imaginacin, creatividad y experiencia.
En esta etapa se debe responder a preguntas como:
Cules son los datos de entrada?
Qu tipo de validacin debe hacerse a los datos de entrada?
Quin utilizar el sistema: especialistas o usuarios sin formacin?
Qu interfaces de usuario se utilizarn?
Cul es el soporte y formato de la salida?
Qu documentacin es necesaria?

Diseo de Programas. Programacin Estructurada

65

Qu mejoras se introducirn probablemente al programa en el


futuro?

Descripcin
entrada
Anlisis

Descripcin salida
Definicin del
problema

Figura 3.7. Actividades a realizar en el anlisis del problema5

Ejemplo 1: Realizar el anlisis que conduzca a la creacin de un programa que


convierta grados Centgrados a grados Fahrenheit.
Datos de entrada: Temperatura en grados Centgrados.
Magnitud de tipo real. Se leer de teclado.
Datos de salida: Temperatura en grados Fahrenheit.
Magnitud de tipo real. Se mostrar en pantalla.
Definicin del problema: La ecuacin que convierte grados Centgrados a
grados Fahrenheit es F = 9/5 * C + 32, donde F representa la
temperatura en grados Fahrenheit y C en Centgrados.
Deber leerse de teclado la temperatura en grados Centgrados a convertir. Se
le aplicar la ecuacin anterior, obtenindose como una magnitud real la
temperatura en grados Fahrenheit.
Ejemplo 2: Realizar el anlisis que conduzca a la creacin de un programa que
sea capaz de determinar si un nmero natural mayor que 1 es primo o no.
Datos de entrada: Nmero a analizar.
Valor de tipo entero. Se leer de teclado.
Datos de salida: Se mostrar en pantalla un literal indicando si el nmero es
o no primo.
Definicin del problema: Un nmero es primo si slo tiene como divisores a
l mismo y la unidad. Por ejemplo los nmeros 1, 2, 3, 5, 7 y 11 son
primos, mientras que los nmeros 4, 6, 8, 9 y 10 no lo son.

No se pretende en este texto en profundizar en una fase de tanta importancia y de tanta


complejidad como es la fase de anlisis, sino simplemente dar a entender al lector que por
sencillo que sea el problema al que se pretende dar una solucin desde un programa de
ordenador, debe comenzarse con la definicin oportuna del problema, basada en trminos
abstractos y del dominio del problema, que permita la perfecta comprensin del mismo antes de
adentrarse en el dominio de la solucin de la mano del diseo.

66

Programacin en C

Deber leerse de teclado el nmero a analizar. Se ir dividiendo


consecutivamente dicho nmero por los nmeros naturales mayores que 1 y
menores que l, terminando bien cuando alguna de estas divisiones de resto
cero (en cuyo caso se habr encontrado un divisor de dicho nmero, por lo
que NO ser primo), o bien cuando se llegue a dividir el nmero en estudio
por l mismo, en cuyo caso por no haber encontrado ningn divisor, el
nmero S ser primo.

3.3.2. Diseo y verificacin del algoritmo


El objetivo principal es determinar cmo hace el programa la tarea solicitada. Se ha
pasado de tener el centro de atencin en el qu hacer (en el denominado dominio del
problema) propio del anlisis, a tenerlo en el cmo hacerlo (en el denominado
dominio de la solucin) propio del diseo.
Desde el punto de vista que se est siguiendo la fase de diseo se centra en la
definicin de las estructuras de datos necesarias para la resolucin del problema, y
especialmente en el diseo de los algoritmos a emplear para la resolucin del mismo.
La utilizacin de mtodos algortmicos para la solucin de problemas se opone al
uso de mtodos heursticos que se rigen por juicios personales y una interpretacin
excesivamente subjetiva.
Los algoritmos deben ser independientes tanto del lenguaje de programacin
(medio para expresar el algoritmo) como de la computadora (procesador para
ejecutarlo) en que se implementen posteriormente.
Uno de los mtodos ms utilizados para el diseo de algoritmos es el denominado
divide y vencers. Consiste en dividir un problema complejo en subproblemas y a su
vez stos en otros de nivel ms bajo hasta obtener subproblemas fciles de solucionar
e implementar (refinamiento sucesivo [Wirth, 1971]). Este mtodo se conoce como
diseo descendente, top-down o modular. En general, este mtodo se desarrolla
teniendo en cuenta los siguientes aspectos:
Se identifican las tareas ms globales e importantes que resuelven el
problema, y se ordenan en la secuencia en que sern ejecutadas.
Se ampla la descripcin de cada una de las primeras tareas (se refinan o
detallan), aadiendo ms detalles.
Algunas tareas en segundo nivel pueden necesitar volver a ser refinadas,
obtenindose un tercer nivel de descripcin.
Problemas complejos necesitarn varios niveles de refinamiento antes de que
se pueda obtener un algoritmo claro, preciso, completo e implementable.
Finalmente debe verificarse. Normalmente se hace una ejecucin manual
del mismo usando conjuntos significativos de datos de entrada que abarquen
el rango de posibles valores con los que podr trabajar el programa
resultante.
Muchos programadores principiantes tratan de omitir este proceso solucionando el
problema con la codificacin del programa directamente, pensando que as se ahorra
tiempo. Esto en principio parece razonable, pero no es as. La experiencia demuestra
que el proceso completo de diseo de un programa con la realizacin de las dos fases
enunciadas hasta el momento, simplifica la solucin y reduce el tiempo. Aunque en

Diseo de Programas. Programacin Estructurada

67

problemas sencillos es donde ms fcil resulta la codificacin directa del programa, es


aconsejable realizar siempre el anlisis y diseo del algoritmo, ya que su posterior
codificacin en un determinado lenguaje resultar muy fcil con slo conocer las
reglas de sintaxis especficas del mismo.
3.3.2.1. Herramientas para la representacin de algoritmos
Para la representacin o diseo de algoritmos existen diferentes tcnicas de
representacin, que van desde el mtodo de narrar o enunciar el algoritmo hasta la
utilizacin de tcnicas grficas o diagramticas normalizadas. Entre las tcnicas de
descripcin textual de los algoritmos destaca la utilizacin de un lenguaje de
descripcin de algoritmos o pseudocdigo. Entre las tcnicas grficas destacan los
diagramas de flujo y los diagramas de Nassi-Schneiderman.
Pseudocdigo
Se utiliza para la representacin narrativa de los algoritmos. Es un lenguaje
intermedio entre el lenguaje natural y el lenguaje de programacin seleccionado
(utilizando como medio bsico de expresin la lengua nativa del programador).
No existen unas reglas fijas, es decir no est estandarizado, siendo una mezcla de
lenguaje natural, smbolos, trminos y otras caractersticas comnmente utilizadas en
los lenguajes de programacin de alto nivel. Por ejemplo, est muy extendido el uso
de estructuras de control de lenguajes como Pascal o APL.
El pseudocdigo se caracteriza por los siguientes aspectos:
Es una forma de representacin muy sencilla de aprender y utilizar.
Es conciso, lo que permite una redaccin rpida del algoritmo.
Permite el diseo y desarrollo de algoritmos totalmente independientes
del lenguaje de programacin posteriormente utilizado en la fase de
codificacin.
Facilita el paso del algoritmo al correspondiente lenguaje de
programacin.
Ofrece flexibilidad en el diseo del algoritmo a la hora de expresar
acciones concretas.
Facilita la realizacin de futuras correcciones o actualizaciones gracias a
su flexibilidad.
Es un mtodo que facilita el diseo descendente y el refinamiento
sucesivo.
Sus principales ventajas sobre las tcnicas diagramticas son su facilidad de
creacin, evolucin y mantenimiento (frente a los diagramas que son lentos de crear y
poco flexibles a la hora de hacer modificaciones) y la facilidad para expresarlos en un
lenguaje de programacin.
Su mayor inconveniente es su falta de estandarizacin y la dificultad para su
lectura cuando su tamao crece. En este sentido se debe buscar una presentacin que
favorezca su legibilidad, utilizando sangras adecuadas para la preservacin de los
bloques estructurados que forman el algoritmo.
Un ejemplo de algunas palabras, smbolos y operadores utilizados en pseudocdigo
se muestra en la Tabla 3.1.

68

Programacin en C

PSEUDOCDIGO
+
*
/
=, <, >, <>
div
mod
** ^
and y
or o
entero
inicio
si
desde

(* *)
//
real
final
entonces
mientras

(suma)
(resta)
(multiplicacin)
(divisin real)
(igual, menor, mayor, distinto)
(divisin entera)
(resto entero)
(potenciacin)
(operador lgico and)
(operador lgico or)
(asignacin)
}
(comentario)
carcter
cadena
leer
escribir
si no
segn
repetir
hasta que

Tabla 3.1. Algunos elementos de un lenguaje de descripcin de algoritmos

Diagramas de flujo
Los diagramas de flujo (flowcharts en la bibliografa en ingls o en algunos textos
donde no se ha traducido el trmino) son herramientas grficas para la representacin
visual de algoritmos. Estn compuestos por una serie de smbolos icnicos unidos por
flechas. Los smbolos representan acciones y las flechas el orden de realizacin de las
mismas, marcando el sentido o flujo lgico del algoritmo. Por tanto, cada smbolo
tendr al menos una flecha que conduzca a l y una flecha que parte de l, a
excepcin de los terminadores y conectores. Al igual que un libro, un diagrama de
flujo se lee de arriba a abajo y de izquierda a derecha.
Los smbolos estn normalizados para facilitar el intercambio de documentacin
entre el personal. Para ello existen normas en las que basarse, dictadas por las
organizaciones de estandarizacin internacionales ANSI (American National
Standard Institute) e ISO (International Standard Organization). Los principales
smbolos normalizados por ANSI/ISO se recogen en la Figura 3.8 y en la Figura 3.9.
Tradicionalmente los diagramas de flujo se pueden clasificar en dos grandes
grupos: organigramas y ordinogramas. La principal diferencia es que los
organigramas eran diagramas que se realizaban en la fase anlisis y los ordinogramas
en la fase de diseo, pero con la evolucin de las metodologas estructuradas, los
organigramas han sido sustituidos por otras herramientas grficas ms expresivas,
quedando relegados exclusivamente para la descripcin de algoritmos en el apartado
del diseo de procedimientos, emplendose la palabra organigrama para
referenciarlos en detrimento del trmino ordinograma.
La principal ventaja de los diagramas de flujo viene de la mano de sus
caractersticas grficas y visuales que los convierten en elementos muy sencillos de
entender. Otra caracterstica importante es su estandarizacin lo que permite la
comunicacin entre miembros del mismo o de diferentes equipos de desarrollo.
Su mayor desventaja es la dificultad de mantenimiento y actualizacin,
dependiendo de editores grficos.

Diseo de Programas. Programacin Estructurada

Representa el comienzo (inicio) o el final (fin) de un programa.


Puede representar tambin una parada o interrupcin prevista
que sea necesario realizar en el programa.

INICIO o FIN

Representa cualquier tipo de introduccin de datos en la


memoria desde los perifricos de entrada, o env o de
informacin procesada a un perifrico de salida.

ENTRADA/
SALIDA

Representa cualquier tipo de operacin que pueda originar


cambio de v alor, formato o posicin de la informacin
almacenada en memoria.

PROCESO

DECISION

NO

SI

DECISION
MULTIPLE

Representa operaciones lgicas de comparacin entre datos, y


en funcin del resultado de la misma (verdadero o falso, si o no)
determina cul de los dos caminos alternativ os seguir el
programa.
E n funcin del contenido de una variable (su v alor en un
momento dado) el programa seguir uno de los diferentes
caminos alternativos.

SUBPROCESO

Llamada a una subrutina o un subproceso determinado. Una


subrutina es un mdulo independiente del programa principal,
que recibe una entrada procedente de dicho programa, realiza
una tarea determinada, regresndose -al terminar-, al
programa principal.

CONECTOR

Sirve para enlazar dos partes cualesquiera de un organigrama


a travs de un conector en la terminacin del primero y otro al
comienzo del segundo. Se refiere a la conexin de partes del
diagrama en la misma pgina.

FLECHAS Y
LINEAS

Sirven de unin entre dos smbolos e indican el sentido del


flujo (sentido de ejecucin de las operaciones).

C onex in entre dos puntos del organigrama situados en


distintas pginas.
CONECTOR

Figura 3.8. Smbolos estndar para diagramas de flujo (ANSI/ISO)

69

70

Programacin en C

PANTALLA

Representa una salida de datos a pantalla.


Se utiliza a veces en lugar del smbolo estndar de
entrada/salida.

IMPRESORA

Representa una salida de datos a impresora.


Se utiliza a veces en lugar del smbolo estndar de
entrada/salida.
Representa una entrada manual de datos desde el teclado.
Se utiliza a veces en lugar del smbolo estndar de
entrada/salida.

TECLADO

Representa una entrada o salida de datos a un sistema


de almacenamiento de acceso secuencial de datos como
una banda magntica.
Se utiliza a veces en lugar del smbolo estndar de
entrada/salida.

BANDA

Representa una entrada o salida de datos a un


sistema de almacenamiento de acceso directo de datos
como un disco magntico.
Se utiliza a veces en lugar del smbolo estndar de
entrada/salida.

DISCO

Se utiliza para aadir comentarios clarificadores a


cualquier smbolo del diagrama de flujo.

COMENTARIO

Figura 3.9. Smbolos estndar para diagramas de flujo (ANSI/ISO) Smbolos secundarios

Diagramas de Nassi-Schneiderman
Los diagramas de Nassi-Schneiderman se conocen tambin como diagramas de
Chapin o simplemente como diagramas N-S. Estos diagramas aparecen contenidos en
un rectngulo, donde un conjunto de smbolos adyacentes representan las estructuras
de control bsicas de la programacin estructurada.
Condicin
V

Tareas

Tareas

Condicin

Tareas Tareas

Caso de condicin
Valor 1 Valor 2

...

Tareas Tareas

...

Condicin

repeticin

seleccin

seleccin mltiple

Tarea 1
Tarea 2
Tarea 3

Figura 3.10. Construcciones de los diagramas de Nassi-Schneiderman

Esta tcnica grfica para la representacin de algoritmos surge con la idea de no


permitir la violacin de las construcciones estructuradas, adecundose ms a este
paradigma de programacin que el resto de las representaciones.
La forma de leer uno de estos diagramas es de arriba abajo, al igual que un
programa estructurado.

Diseo de Programas. Programacin Estructurada

71

Estos diagramas favorecen las tcnicas de diseo descendente, as como la


particin de los programas en mdulos pequeos; resaltando ms las partes generales
sobre los detalles concretos, que quedan ms pequeos cuanto ms especficos son.
La principal ventaja de los diagramas N-S es su adecuacin a la programacin
estructurada, siendo su dificultad de modificacin su inconveniente ms grande.
En la Figura 3.10 se muestran las principales construcciones de los diagramas de
Nassi-Schneiderman.
3.3.2.2. Ejemplos de diseo de algoritmos
Ejemplo 1: Diseo del algoritmo de conversin de grados Centgrados a grados
Fahrenheit.
Lenguaje natural
Se utilizarn las variables reales temp_c, temp_f.
1. Inicio.
2. Leer de teclado el valor de la temperatura en grados centgrados
(temp_c).
3. Obtener el valor de la temperatura en grados Fahrenheit utilizando la
ecuacin:
temp_f
(9/5) * temp_c + 32
4. Presentar en pantalla el valor de temp_f calculado.
5. Final.
Pseudocdigo
real temp_c, temp_f
inicio
leer (temp_c)
temp_f
(9/5) * temp_c + 32
escribir (temp_f)
final
Diagrama de flujo

72

Programacin en C

Diagrama de Nassi-Schneiderman
Inicio
Leer temp_c
temp_f

(9/5) * temp_c +32

Mostrar temp_f
Fin

Ejemplo 2: Diseo del algoritmo para determinar si un determinado nmero


natural mayor que 1 es primo.
Lenguaje natural
Se utilizan las variables enteras num, temp, resto.
1. Inicio.
2. Leer de teclado el nmero a estudiar (num).
3. Establecer temp a 2.
(temp ser un nmero auxiliar por el que se ir dividiendo num para
posteriormente comprobar si el resultado es o no 0).
4. Obtener el resto de la divisin de num / temp.
5. Si el resto es 0, entonces se determina que el nmero num no es primo
(temp ser su divisor). Bifurcamos al punto 9.
6. Incrementamos temp (temp
temp + 1).
7. Si temp es igual a num entonces se determina que num es un nmero
primo. Bifurcamos al punto 9.
8. Bifurcamos al punto 4.
9. Final.
Pseudocdigo (primera versin)
entero num, x, resto
inicio
leer (num)
x
2
Obtener resto:
resto
num mod x (* mod: resto de la divisin entera *)
si (resto = 0) entonces
escribir ("El nmero no es primo")
bifurcar a final
si no
x
x + 1
fin si
si (x = num) entonces
escribir ("El nmero es primo")
bifurcar a final
si no
bifurcar a Obtener resto
fin si
final:

Diseo de Programas. Programacin Estructurada

Pseudocdigo (segunda versin)


entero num, x, resto
inicio
leer (num)
x
2
resto
num mod x
mientras ( (resto <> 0) and (x < num) )
hacer
x
x + 1
resto
num mod x
fin hacer
si (x = num) entonces
escribir ("El nmero es primo")
si no
escribir ("El nmero no es primo")
fin si
final

Diagrama de flujo
Primera versin

Segunda versin

73

74

Programacin en C

Diagrama de Nassi-Schneiderman
El diagrama de Nassi-Schneiderman slo se va a hacer de la segunda versin porque
la primera no cumple las reglas de la programacin estructurada al incluir estructuras
de control con saltos incondicionales.
Inicio
Leer num
x 2
num mod x

resto

resto <> 0 and x < num


x x+1
resto
Si

num mod x

x = num

Mostrar Es Primo

No

Mostrar No Es Primo

Fin

3.3.3. Codificacin
Esta fase consiste en la traduccin de la solucin obtenida en el diseo de los
algoritmos al lenguaje de programacin escogido.
Las caractersticas generales de esta fase son:
Se sustituyen las palabras del pseudocdigo, por sus homnimos o
conjunto de sentencias equivalentes en el lenguaje de programacin
escogido.
Se requiere conocer el juego de instrucciones del lenguaje de ordenador
escogido.
Utilizando un editor de textos se genera en un fichero plano en disco
el programa fuente.
Esta actividad se constituye la programacin propiamente dicha.
3.3.3.1. Lenguajes de programacin
Los programas indican al ordenador qu tiene que hacer, y ste nicamente realiza las
operaciones que el programa incluye. Un programa y las sentencias que lo constituyen
se construyen con unos smbolos, y de acuerdo con unas reglas, que constituyen la
gramtica del lenguaje de programacin.
Se puede definir un lenguaje de programacin como el conjunto de smbolos y de
reglas para combinarlos, que se usan para expresar algoritmos.
Los lenguajes de programacin se pueden denominar con otros nombres, tales
como lenguajes formales o lenguajes artificiales, con el fin de distinguirlos de los
lenguajes naturales (espaol, ingls...).
Los lenguajes de programacin, a semejanza de los lenguajes que usan los
humanos para comunicarse, poseen un lxico (vocabulario o conjunto de smbolos
permitidos), una sintaxis que indica cmo realizar construcciones del lenguaje y una
semntica que determina el significado de cada construccin correcta.

Diseo de Programas. Programacin Estructurada

75

Los lenguajes de programacin se pueden clasificar de diferentes maneras pero su


clasificacin ms habitual es en funcin de la aproximacin del lenguaje de
programacin al hombre o a la mquina, distinguindose tres categoras:
Lenguaje mquina.
Lenguaje ensamblador.
Lenguaje de alto nivel.
Las dos primeras categoras forman los denominados lenguajes de bajo nivel.
Lenguaje mquina
La unidad central de proceso de todo ordenador posee un repertorio de instrucciones
elementales que es capaz de reconocer y ejecutar. Un programa escrito en lenguaje
mquina es directamente inteligible (ejecutable) por el microprocesador de la
computadora. Este cdigo mquina es el que en ltima instancia permite programar
los circuitos del ordenador para que realice las tareas de proceso que el programador
desea.
En su intimidad, el lenguaje de los circuitos de la mquina es una complicada
secuencia de niveles de tensin: estados de conexin y desconexin que se
representan en los lenguajes mquina como cadenas binarias (cadenas de dgitos 0 y
1). El cdigo mquina es conocido por ello como cdigo binario.
El lenguaje mquina se caracteriza por ser propio de cada microprocesador o UCP
(Unidad Central de Proceso), lo cual lo hace dependiente de cada familia de
microprocesadores.
No se programa en cdigo mquina porque es de gran dificultad para los
programadores y conllevara una gran lentitud en la codificacin. Sin embargo, todo
programa escrito en un lenguaje ensamblador o en un lenguaje de alto nivel, para que
pueda ser ejecutado por el ordenador, debe ser convertido a cdigo mquina por
una herramienta que llamaremos ensamblador o compilador, respectivamente.
Cada instruccin de nivel de mquina contiene:
Un cdigo de operacin, que indica a la UCP qu operacin debe
efectuar.
Operando/s, dato/s con el/los que operar o una referencia a la posicin
de memoria donde est/n ubicado/s.
EJEMPLO

Parte de un de programa escrito en cdigo mquina


C000
C002
C005
C007
C00A
C00D
C00F
C012
C013
C015
C018
C01A
C01D
C01F
C022

A9
8D
A9
8D
AD
09
8D
60
A9
8D
A9
8D
A9
8D
60

16
00
08
18
11
20
11
15
18
1B
11
17
00

DD
D0
D0
D0
D0
D0
D0

76

Programacin en C

Lenguaje ensamblador
Surge como sustituto del lenguaje mquina para evitar la dificultad de ste. Est
basado en el uso de nemotcnicos (palabras abreviadas procedentes del ingls) para
representar los cdigos de operacin y los operandos.
La programacin en lenguaje ensamblador precisa de un amplio conocimiento
sobre la constitucin, estructura y funcionamiento interno del computador, as como
habilidad en el manejo de cdigos y sistemas de numeracin, en especial binario y
hexadecimal.
Con el lenguaje ensamblador se sigue teniendo una dependencia de la mquina,
ms concretamente del microprocesador.
A diferencia del lenguaje mquina s se programa en ensamblador en la
programacin de dispositivos electrnicos de control de procesos en tiempo real, o en
situaciones donde la rapidez de ejecucin es crtica, porque los programas realizados
en lenguaje ensamblador son ms rpidos que los realizados en lenguajes de alto
nivel.
Por el contrario, los programas realizados en lenguaje ensamblador son ms
difciles de escribir y depurar, complicando todas las actividades de codificacin,
prueba y mantenimiento.
Un programa escrito en lenguaje ensamblador no puede ser ejecutado directamente
por la computadora, sino que requiere una traduccin al lenguaje mquina.
Todas las computadoras tienen un ensamblador (traductor) propio del
microprocesador que posean, como se puede apreciar en la Figura 3.11

Figura 3.11. Proceso de conversin del cdigo ensamblador a cdigo mquina

En ingls al programa ensamblador (el que traduce el programa fuente a lenguaje


mquina), se le denomina assembly, y al lenguaje ensamblador (lenguaje de
programacin con una estructura y gramtica definidas), assembler. Aunque en
castellano ambos trminos se han traducido por ensamblador, no deben
confundirse, pues son dos conceptos distintos.
EJEMPLO

Parte de un programa escrito en ensamblador.


.file
"ejemplo2.c"
.version "01.01"
gcc2_compiled.:
.section .rodata
.align 32
.LC0:
.string "Introduzca el valor de la temperatura "
.LC1:
.string "en grados cent\355grados: "
.LC2:
.string "%f"
.align 32
.LC5:
.string "Temperatura en grados fahrenheit: %f.\n"
.align 8
.LC3:

Diseo de Programas. Programacin Estructurada

77

.long
0xcccccccd,0x3ffccccc
.align 8
.LC4:
.long

0x0,0x40400000

.text
.align 4
.globl main
.type
main,@function
main:
pushl
%ebp
movl
%esp, %ebp
subl
$24, %esp
subl
$12, %esp
pushl
$.LC0
call
printf
addl
$16, %esp
subl
$12, %esp
pushl
$.LC1
call
printf
addl
$16, %esp
subl
$8, %esp
leal
-4(%ebp), %eax
pushl
%eax
pushl
$.LC2
call
scanf
addl
$16, %esp
flds
-4(%ebp)
fldl
.LC3
fmulp
%st, %st(1)
fldl
.LC4
faddp
%st, %st(1)
fstps
-8(%ebp)
subl
$4, %esp
flds
-8(%ebp)
leal
-8(%esp), %esp
fstpl
(%esp)
pushl
$.LC5
call
printf
addl
$16, %esp
leave
ret
.Lfe1:
.size
main,.Lfe1-main
.ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.1 2.96-85)"

Lenguajes de alto nivel


Hoy en da son los lenguajes de programacin ms utilizados, debido a que su
estructura es muy prxima a la de los lenguajes naturales, y alejan al programador de
las complejidades intrnsecas de los lenguajes de bajo nivel, porque son
independientes de la mquina; idealmente los programas escritos con un lenguaje de
alto nivel son transportables, podran llevarse a un ordenador diferente y con pocas
modificaciones funcionara.
Se puede decir que un lenguaje de alto nivel est orientado hacia la resolucin de
una determinada clase de problemas, mientras un lenguaje de bajo nivel es un
lenguaje orientado a una determinada mquina o clase de mquinas.
Un programa escrito en un lenguaje de alto nivel no puede ser ejecutado
directamente por la computadora, sino que requiere una traduccin al lenguaje
mquina. El traductor de programas fuente al programa ejecutable podr ser un
compilador o un intrprete, dependiendo de la filosofa que utilice.

78

Programacin en C

Figura 3.12. Proceso de compilacin de un programa fuente en lenguaje de alto nivel

Los lenguajes de alto nivel presentan las siguientes ventajas:


Los lenguajes de alto nivel son independientes de la arquitectura del
ordenador que lo soporta.
Un programa escrito en un lenguaje de alto nivel puede ser portado a
distintos equipos.
El tiempo de formacin de los programadores es ms corto cuanto ms
alto sea el nivel del lenguaje.
Se basa en reglas sintcticas similares a los lenguajes humanos: las
sentencias son normalmente verbos ingleses.
El tiempo necesario para codificar y poner a punto un programa en un
lenguaje de alto nivel es inferior al necesario en el caso de los lenguajes
de bajo nivel.
Los cambios y correcciones son ms fciles.
Entre los inconvenientes de los lenguajes de alto nivel cabe citar:
No se aprovechan plenamente las posibles ventajas de la arquitectura
interna del sistema.
Se incrementa la ocupacin de la memoria interna del sistema.
El tiempo de ejecucin es mayor.
Estos tres inconvenientes estn relacionados con el proceso de traduccin del
programa fuente al ejecutable.
Dificultad programacin
Dependencia del
microprocesador
Se programa en l

MQUINA

ENSAMBLADOR

ALTO NIVEL

Muy alta

Alta

Baja

SI

SI

NO

Prcticamente
NO

Control de procesos
donde el tiempo de
ejecucin es crtico
Sistemas Operativos

Todo tipo de
aplicaciones:
gestin,
cientficas...

El programa es
directamente ejecutable
SI
NO
por el ordenador
Tabla 3.2. Comparativa entre los tres tipos de lenguajes

NO

El nmero de lenguajes de programacin crece de forma constante, hasta el punto


que resulta casi imposible su completa clasificacin.
En una primera clasificacin de los lenguajes se tendran dos tipos:
Lenguajes declarativos: Generalmente se utilizan para establecer
dependencias y declarar entidades. Estn orientados normalmente a la
descripcin de los objetivos, ms que al modo de llegar a stos. Son los

Diseo de Programas. Programacin Estructurada

79

denominados lenguajes de rdenes. Ejemplos: SAS, SPSS, NATURAL,


IMS, PROLOG.
Lenguajes imperativos o procedimentales: Indican secuencias de
acciones con el fin de llegar a un objetivo. Contienen generalmente una
parte declarativa. Dan rdenes a la mquina. Ejemplos: Ada, C, Pascal,
BASIC, C++, Java, C#.
Otra clasificacin de los lenguajes de alto nivel puede conducir a los dos siguientes
grupos de lenguajes:
Lenguajes de propsito general: Se emplean en cualquier campo, ya sea en
los negocios, en las aplicaciones cientficas, en el desarrollo de software de
sistemas... Ejemplos: Ada, C, Pascal, Java, C++, C#.
Lenguajes de propsito especial: Son aqullos que restringen su campo de
actuacin a un determinado sector. Ejemplos: MATLAB, FORTRAN,
COBOL.
Por ltimo, una tercera clasificacin de los lenguajes de alto nivel podra ser por su
proximidad a un tipo de problemas. Esta clasificacin es muy difcil por el constante
incremento del nmero de lenguajes y la evolucin de los existentes. De forma
genrica y teniendo en cuenta que las divisiones no son absolutamente disjuntas se
tendra:
Lenguajes cientficos: Destinados a las aplicaciones cientficas.
Histricamente son los primeros. Los ms conocidos son: ALGOL,
FORTRAN, APL, SPSS, BASIC, PASCAL, C, MATLAB.
Lenguajes de gestin: Lenguajes orientados a la solucin de problemas de
tratamiento de datos para gestin. Predominan las instrucciones dedicadas a
procesos de entrada/salida. Los ms caractersticos son: COBOL, RPG II,
NATURAL.
Lenguajes polivalentes: Son lenguajes que cubren tanto el rea cientfica
como el rea de gestin de una forma equilibrada. Ejemplos de stos son:
PL/1, FORMULA ALGOL, FORTH, ADA, Delphi, Java, C, C++.
Lenguajes para el proceso de cadenas y listas: Es un grupo muy
especializado. Cabe mencionar a: IPL-V, LISP, COMIT, SNOBOL.
Lenguajes para expresiones algebraicas formales: Son lenguajes que
permiten el uso de expresiones matemticas sin referirse a valores numricos
concretos. Los principales son: MATLAB, MATHEMATICA, SML.
Lenguajes para el manejo de ficheros y bases de datos: Creados para el
tratamiento de los grandes bancos de datos. Los principales son: SQL,
ACCESS BASIC, FOX PRO, CLIPPER.
Lenguajes para la generacin de software de sistemas: Son los lenguajes
que se emplean para la creacin de sistemas operativos y sus utilidades. Sin
duda alguna los lenguajes ms importantes en este campo son Java, C, C++
y C#. Otros lenguajes que se utilizan son: PASCAL, ADA, MODULA-2,
Objetive C.
Lenguajes de inteligencia artificial: Este grupo de lenguajes esta orientado
a la resolucin de los problemas del conocimiento. Cabe destacar:
PROLOG, LISP, ART-IM.

80

Programacin en C

A continuacin se presentan algunos ejemplos de programas fuente escritos en


lenguajes de alto nivel.
EJEMPLOS

Codificacin en PASCAL del algoritmo conversor de grados centgrados a grados


Fahrenheit.
program fahrenheit;
uses winctr;
var

temp_c, temp_f

: real;

begin
write ('Introduzca el valor de la temperatura ');
write ('en grados centgrados: ');
readln (temp_c);
temp_f := (9.0/5.0) * temp_c + 32;
write ('Temperatura en grados fahrenheit: ', temp_f);
end.

Codificacin en C del algoritmo conversor de grados centgrados a grados Fahrenheit.


#include <stdio.h>
void main(void) {
float temp_c, temp_f;
printf ("Introduzca el valor de la temperatura ");
printf ("en grados centgrados: ");
scanf ("%f", &temp_c);
temp_f = (9.0/5.0) * temp_c + 32;
printf ("Temperatura en grados fahrenheit: %f.\n", temp_f);
}

Codificacin en PASCAL del algoritmo que calcula si un nmero es primo (segunda


versin del algoritmo).
program primos;
uses wincrt;
var num, resto, x : integer;
begin
write ('Introduzca el nmero a investigar: ');
readln (num);
x := 2;
resto := num mod x;
while ( (resto <> 0) and (x < num) ) do
begin
x := x + 1;
resto := num mod x;
end;
if (x = num)
then
writeln ('El nmero es primo.')
else
writeln ('El nmero NO es primo.');
end.

Diseo de Programas. Programacin Estructurada

81

Codificacin en C del algoritmo que calcula si un nmero es primo (segunda versin


del algoritmo).
#include <stdio.h>
void main(void) {
int num, resto, x;
printf ("Introduzca el nmero a investigar: ");
scanf ("%d", &num);
x = 2;
resto = num % x;
while ( (resto) && (x < num) ) {
x = x + 1; resto = num % x;
}
if (x == num)
printf ("El nmero es primo");
else
printf ("El nmero NO es primo");
}

Uno de los lenguajes de programacin de mayor difusin y uso para el desarrollo


de aplicaciones software es el lenguaje C. En el Cuadro 3.2 se presenta la historia de
este lenguaje, mientras que en el Cuadro 3.3 se recogen sus principales caractersticas.
El lenguaje C fue desarrollado en 1972 por Dennis Ritchie en los laboratorios Bell
Telephone. Es el resultado de dos lenguajes anteriores: el BCPL y el B. La idea
inicial era crear un lenguaje de propsito general que facilitara la programacin y la
realizacin de muchas tareas anteriormente reservadas al lenguaje ensamblador. De
hecho, la primera gran tarea que se realiz en lenguaje C, en el ao 1973, es la
reescritura del ncleo del sistema operativo UNIX (escrito originalmente en
ensamblador) para una DEC PDP-11.
En 1978 Brian Kernighan y Dennis Ritchie publican una descripcin definitiva
del lenguaje, que se denomina frecuentemente "K&R C". Tras dicha publicacin, en
el mbito profesional se comienza a promover su uso, antes las muchas
caractersticas interesantes del C.
A mediados de los 80 su uso y popularidad se extendieron: se escriben
compiladores C para todo tipo de ordenadores y se comienzan a desarrollar grandes
aplicaciones comerciales en C. Incluso se reescriben en C aplicaciones que
originalmente fueron creadas en otros lenguajes para aprovechar su eficiencia y
portabilidad.
La evolucin de los ordenadores y la creciente popularidad del C dio lugar a la
aparicin de varias implementaciones con un alto grado de incompatibilidad entre
ellas. Para resolver este problema el Instituto Nacional Americano de Estndares
(ANSI) establece en 1989 una definicin no ambigua del lenguaje C, independiente
de la mquina. As, qued definido el estndar ANSI para el lenguaje C (ANSI C).
Prcticamente todos los compiladores actuales adoptan el ANSI C. Algunos
proporcionan, adems, caractersticas adicionales propias.
En la dcada de los ochenta, Bjarne Stroustrup desarrollo en los laboratorios Bell
otro lenguaje de programacin llamado C++. Todas las caractersticas del C estn
disponibles en C++, pero incorpora nuevos fundamentos base para la programacin
orientada a objetos.
Cuadro 3.2. Breve historia del lenguaje C

82

Programacin en C

El lenguaje C es un lenguaje de alto nivel que se puede caracterizar por:


De propsito general: puede ser utilizado para el desarrollo desde sistemas
operativos hasta programas cientficos, programas de aplicacin (hojas de clculo,
procesadores de texto...) o programas de educacin y juegos. Otros lenguajes, sin
embargo, se caracterizan por ser utilizados slo en determinadas reas, como por
ejemplo el FORTRAN para el clculo cientfico, el BASIC como soporte para
enseanza, el COBOL para gestin...
Transportabilidad o portabilidad: Los programas escritos en ANSI C pueden ser
llevados a otro tipo de ordenador y con mnimos cambios funcionarn.
Compacto: El C ANSI slo tiene unas 32 palabras reservadas, aunque adems,
algunos compiladores pueden proporcionar palabras reservadas adicionales propias.
Conciso: Los programas suelen tener mucho menos cdigo que otros lenguajes de
alto nivel, permitiendo as a los compiladores generar ejecutables ms pequeos.
Estructurado: los programas pueden organizarse en mdulos donde cada uno de
ellos realice una funcin definida y concreta. Posee, adems, sentencias de
programacin estructurada: for, while, do-while.
De nivel medio: tiene una sintaxis semejante al ingls, por lo que posee
caractersticas de los lenguajes de alto nivel, pero mantiene tambin muchas de las
caractersticas de los lenguajes de bajo nivel prximos al ensamblador.
Flexible: se puede crear fcilmente en C cdigo que transcribe un algoritmo.
Eficiente: los compiladores generan programas ejecutables rpidos y no muy
grandes.
Su principal desventaja es que debido a la versatilidad del C, su cdigo puede ser escrito
con tal brevedad y concisin que se vuelve casi ilegible (crptico).
Cuadro 3.3. Caractersticas del lenguaje C

3.3.4. Compilacin y enlazado


El objetivo de esta fase es traducir el programa fuente escrito en un lenguaje
(generalmente de alto nivel) a cdigo ejecutable (cdigo mquina).
#include <stdio.h>
void main(void) {
printf(Hola Mundo);
}

Programa Fuente
Proceso de Traduccin
Anlisis lexicogrfico
Anlisis sintctico
Anlisis semntico
Generacin de cdigo
Optimizacin

001010101110
110101010100
001010101010

Programa objeto

Figura 3.13. Proceso de traduccin

El proceso de traduccin, que de forma genrica se muestra en la Figura 3.13, es


llevado a cabo por programas traductores, que traducen programas escritos en
lenguajes de programacin de alto nivel a lenguaje mquina. Un traductor es, por

Diseo de Programas. Programacin Estructurada

83

tanto, un programa que recibe como entrada un texto en un lenguaje concreto y


produce, como salida, un texto en lenguaje mquina equivalente.
3.3.4.1. Compiladores
Un compilador traduce todo un programa fuente, escrito en un lenguaje de alto nivel,
a un programa objeto en cdigo mquina. El programa fuente puede estar contenido
en uno o varios ficheros, y el programa objeto puede almacenarse como archivo en
memoria masiva para ser procesado posteriormente, sin necesidad de volver a realizar
la traduccin. Un compilador slo traduce el programa, no lo ejecuta.
Posterior a la compilacin, siempre es necesario un proceso de enlazado (que ser
realizado por un enlazador o link editor). En este proceso se vincula el cdigo objeto
generado por la compilacin, con otro cdigo objeto necesario para la ejecucin del
programa que se est generando (desarrollado por el propio u otros programadores, o
proporcionado por la herramienta de desarrollo del lenguaje utilizado).
La compilacin y el enlazado son dos procesos diferentes, aunque en la mayora de
las ocasiones se suela englobar a ambos bajo el nombre de compilacin, debido a
que es ste es el proceso que consume mayor cantidad de recursos.
Una vez traducido el programa, su ejecucin es independiente del compilador, as,
por ejemplo, cualquier interaccin con el usuario slo estar controlada por el sistema
operativo, como se muestra en la Figura 3.14.
Se requiere un compilador diferente para cada lenguaje de programacin y para
cada arquitectura, pudiendo existir diferentes compiladores para un mismo lenguaje y
una misma arquitectura.
Programa
Fuente

Programa
Fuente

Compilador

Compilador

Sistema Operativo
Hardware

Ejecutable

Usuario

Sistema Operativo
Hardware

Figura 3.14. La ejecucin de un programa compilado es independiente del compilador

La compilacin propiamente dicha, consta de dos etapas fundamentales: la fase de


anlisis del programa fuente y la fase de sntesis del programa objeto. Cada una de
estas fases conlleva la realizacin de varias actividades. As, el anlisis del programa
fuente implica la realizacin de un anlisis del lxico, de la sintaxis y de la semntica.
Por su parte, la sntesis del programa objeto conduce a la generacin de cdigo y a su
optimizacin, como se puede apreciar en la Figura 3.15.
Anlisis lexicogrfico o morfolgico: Consiste en descomponer el
programa fuente en sus elementos constituyentes o smbolos (tokens). Los
smbolos son caracteres o secuencias de caracteres con significado propio en
el lenguaje. Como resultado del anlisis lexicogrfico se obtiene una
representacin del programa formada por la descripcin de smbolos en
tablas, y una secuencia de smbolos junto con la referencia a la ubicacin del
smbolo en la tabla.

84

Programacin en C

Programa fuente

Anlisis

Anlisis lexicogrfico
Anlisis sintctico

Sntesis

Anlisis semntico

Tablas de
smbolos

Generacin de cdigo
Optimizacin
Programa objeto

Figura 3.15. Fases en la traduccin de un programa fuente a un programa objeto por un


compilador

Anlisis sintctico: Consiste en la verificacin de la correcta construccin


de las expresiones e instrucciones. La sintaxis de un lenguaje de
programacin especifica cmo deben escribirse los programas, mediante un
conjunto de reglas de sintaxis o gramtica del lenguaje. Se pueden presentar
dos tipos de situaciones diferentes:
Errores: Se producen porque no se supera alguno de los anlisis
anteriores. El proceso no contina, y el propio compilador indica el
lugar del programa y el tipo de error detectado. Algunos
compiladores detienen el proceso al primer error detectado, otros
generan un fichero o listado con todos los errores encontrados. Si
existen errores, deber modificarse de nuevo el programa fuente y
volver a compilarlo6.
Advertencias o warnings: Son indicaciones sobre algn aspecto
concreto de la traduccin del cdigo fuente que podra ser
susceptible de producir un error, sin que necesariamente lo sea. En
otras palabras, nos est advirtiendo Cuidado! Esto puede ser un
error, revsalo.
o Son informativos. No provocan la interrupcin del proceso
de compilacin. El compilador indica el lugar del
programa y el tipo de warning detectado.
o Si existen warnings, es conveniente revisarlos y evitarlos.

Muchas veces al corregir ciertos errores de compilacin, el nmero de ellos que se genera en
una segunda compilacin aumenta en lugar de disminuir. Esto es debido a que ciertos errores
primeramente detectados enmascaraban otros que salen en posteriores compilaciones.

Diseo de Programas. Programacin Estructurada

85

Anlisis semntico: La semntica de un lenguaje de programacin es el


significado dado a las distintas construcciones sintcticas. En la traduccin
se busca la generacin de un cdigo en lenguaje mquina con el mismo
significado que el cdigo fuente. Durante el anlisis semntico se pueden
producir errores cuando se detectan construcciones sin un significado
concreto. Por ejemplo, la asignacin de una cadena de caracteres a una
variable numrica.
Generacin y optimizacin de cdigo: Se crea un archivo con el programa
objeto (lenguaje mquina). Este programa objeto no es directamente
ejecutable, ya que tan slo contiene el cdigo mquina correspondiente al
programa fuente compilado. En algunas ocasiones se utiliza un lenguaje
intermedio para optimizar el cdigo.
Despus de la compilacin de todos los ficheros fuentes en que se ha dividido el
programa, debe utilizarse un enlazador para obtener finalmente el programa
ejecutable.
En general, en un programa fuente se suele invocar a mdulos que son
proporcionados por la misma herramienta de desarrollo o por otros desarrolladores, y
que resuelven problemas concretos. El cdigo mquina correspondiente a estos
mdulos debe ser vinculado con los ficheros objeto generados por la compilacin, de
manera que el resultado final sea un nico fichero ejecutable. Esta tarea es la que
realiza el enlazador.
En la Figura 3.16 se presenta de forma esquemtica las etapas por las que se pasa
para obtener un programa ejecutable a partir de un programa escrito en varios ficheros
fuente.

Figura 3.16. Generacin del ejecutable desde el programa fuente

86

Programacin en C

3.3.4.2. Intrpretes
Un intrprete hace que un programa fuente escrito en un lenguaje de alto nivel vaya
traducindose y ejecutndose sentencia a sentencia. Toma una sentencia del programa
fuente cada vez (ver Figura 3.17), la analiza e interpreta, ejecutndola
inmediatamente. Por lo tanto, no se crea un fichero objeto que se pueda almacenar en
disco para posteriores ejecuciones. Esto conduce a que la ejecucin del programa est
supervisada siempre por el intrprete, como se puede apreciar en la Figura 3.18.

Figura 3.17. Proceso de traduccin y ejecucin mediante un intrprete


Usuario

Programa
Fuente
Intrprete
Sistema Operativo
Hardware

Figura 3.18. Ejecucin de un programa con un intrprete

No detectan errores en la escritura del programa hasta que no intentan ejecutar la


instruccin mal escrita. Si se localiza un error en ejecucin, sta se interrumpe, y
dependiendo del intrprete, se podr corregir el error y continuar a partir de la
sentencia en cuestin.
Los programas interpretados son ms lentos que los compilados, por tener que unir
el proceso de traduccin al de ejecucin en cada sentencia, adems de contar con
menos optimizaciones (las optimizaciones se hacen dentro del contexto de cada
sentencia, como lo que el cdigo ser siempre menos optimizado que el generado por
un compilador).
Se utilizan menos que los compiladores. Un ejemplo clsico de intrprete son las
primeras versiones de BASIC.

3.3.5. Verificacin y depuracin


El objetivo de esta fase es verificar el correcto funcionamiento del programa creado.
Que un programa no tenga errores de compilacin no indica que vaya a funcionar
correctamente. Para verificar el correcto funcionamiento de un programa se debe
ejecutar el mismo con una amplia gama de datos de prueba o juego de datos de

Diseo de Programas. Programacin Estructurada

87

prueba, compuesto por valores de entrada normales, extremos, incorrectos, con salida
y resultado conocido..., que comprueban el funcionamiento de los aspectos esenciales
del programa.

Figura 3.19. Tipos de errores

Hay dos tipos de errores que se producen en tiempo de ejecucin (bugs):


Errores de ejecucin: El programa se para (aborta) repentinamente
sin llegar al final. Algunos errores tpicos de ejecucin estn motivados
por: intentos de dividir por cero, races cuadradas de nmeros negativos,
intentos de apertura de archivos inexistentes y acceso a perifricos no
conectados... La solucin suele venir por la inclusin de ms cdigo en
el programa que realice ciertas comprobaciones antes de intentar realizar
la operacin que produce el error.
Errores lgicos: el programa se ejecuta hasta el final pero aportando
unos resultados incorrectos. Errores a veces muy difciles de detectar.
Las principales causas suelen ser un mal diseo de la lgica del
algoritmo, o bien errores en su codificacin. La solucin viene por la
revisin del algoritmo y en su caso, si es necesario, su rediseo.

88

Programacin en C

Para depurar un programa realizado con un compilador se necesita ejecutar dicho


programa bajo el control de un programa especial denominado depurador o
debugger7.
Un depurador es una herramienta software que suelen incorporar todos los
entornos de desarrollo, y que es capaz de cargar y ejecutar de forma controlada
nuestro programa. Permite la ejecucin paso a paso (instruccin a instruccin),
evaluacin de expresiones, examen de contenido de variables, registros de la UCP...
Generalmente, cuando se realiza la compilacin, se debe indicar al compilador,
mediante la correspondiente opcin, que debe generar cdigo para ejecutar con
depurador.
La depuracin implica:
La modificacin del programa fuente e incluso a veces del algoritmo
correspondiente.
La posterior compilacin.
La nueva verificacin de la desaparicin del error detectado y de que no
se han introducido otros en esta ltima modificacin.

3.3.6. Documentacin
El objetivo de esta fase es la creacin de los documentos necesarios para facilitar la
comprensin, mantenimiento, evolucin y uso del programa.
Existen dos tipos principales de documentacin: la documentacin tcnica para el
mantenimiento del programa y la documentacin para el usuario final del programa.
3.3.6.1. Documentacin tcnica: Interna y externa
La documentacin externa se realiza en ficheros distintos al programa fuente, y debe
incluir:
El anlisis y diseo de los algoritmos.
Diagramas de flujo y pseudocdigo.
Especificacin de los formatos de los datos de entrada y salida del
programa.
Explicacin de cualquier frmula o clculo y expresin compleja del
programa.
Todas las consideraciones que se consideren oportunas que deba saber
cualquier programador que necesite modificar posteriormente el
programa.
La documentacin interna se incluye en el cdigo del programa fuente, usando
tanto comentarios (ver Tabla 3.3) como identificadores significativos.
Respecto a los comentarios en un programa se puede afirmar que:
Su objetivo es ayudar a la comprensin del cdigo.
7

La traduccin estricta del ingls de trmino debug es desinfectar o matar chinches. En los
primeros tiempos de la informtica, una polilla colocada sobre un contacto electromecnico del
ordenador Mark II ocasion un error en el programa que ejecutaba esta mquina. A partir de
entonces, se asocia el trmino debug al hecho de corregir errores en un programa.

Diseo de Programas. Programacin Estructurada

89

No generan cdigo ejecutable cuando el programa es compilado.


El compilador los ignora.
Su nico objetivo es que los programas sean ms fciles de leer y
entender.
Deben ser significativos, y no decir algo obvio.
Smbolos de comentarios
REM al comienzo de la lnea
Basic
{ } o (* *) (multilnea)
Pascal
* en la columna 7 de la lnea
Cobol
/* */ (multilnea)
C
/* */ o // al comienzo de lnea
C++
/* */ o // al comienzo de lnea o /** */ comentario de documentacin
Java
Pseudocdigo Cualquiera de los anteriores (no es estndar)
Tabla 3.3. Formato de los comentarios en diferentes lenguajes de programacin

Es habitual incluir en cada fichero fuente una cabecera entre comentarios que
especifican bsicamente los siguientes datos:
Nombre y finalidad del programa.
Fecha de creacin.
Nombre del analista y del programador.
Descripcin bsica.
Limitaciones y dependencias para su ejecucin.
A continuacin se presenta un ejemplo de cabecera.
/*
* Nombre
* Conversor de Celsius a Fahrenheit
* Sinopsis
*
Programa que genera una tabla de equivalencias entre
*
grados Celsius y Fahrenheit.
* Descripcin de la ejecucin del programa
*
Desde la lnea de rdenes C2F.
*
No admite ningn parmetro de entrada.
* Directivas de compilacin
*
Ninguna.
* Lista de cdigos de salida
*
Ninguno.
* Dependencias funcionales
*
El programa completo se encuentra en el fichero C2F.C.
* Lista de situaciones crticas
*
No se ha detectado ninguna
* Organizacin
*
Universidad de Salamanca
* Analista
*
Francisco Jos Garca
* Programador
*
Ivn lvarez
* Fecha
*
26 - 10 - 2000
* Versin
*
1.5
* Lista de versiones
*
* Referencia a las fuentes consultadas
*/

Por su parte, un identificador se puede definir como una combinacin de caracteres


alfabticos y dgitos que se utiliza para poner nombre a los distintos componentes del

90

Programacin en C

programa (variables, constantes, funciones, archivos...). No es lo mismo leer el


siguiente fragmento de cdigo:
i = (c * t * fi ) /36500;
a = (b * al) / 2;

que leer este otro:


intereses = (capital * tiempo * factor_interes) / 36500;
area = (base * altura) / 2;

Una buena documentacin interna cobra todava ms importancia cuando los


programas son complejos y tienen gran cantidad de lneas, con lo que es muy difcil
su seguimiento, incluso para el propio programador una vez transcurrido un tiempo
desde que lo codific.
Es muy normal, que una vez transcurrido un tiempo desde que se termin el
programa, que deban incluirse modificaciones en el mismo lo que se denomina
mantenimiento del programa. Despus de cada cambio la documentacin debe ser
actualizada. Es una prctica habitual llevar un control de las distintas versiones de un
programa, as como indicar dentro del mismo el nombre de los programadores que
alguna vez lo han modificado.
3.3.6.2. Documentacin para el usuario final del programa
Debe confeccionarse una documentacin para la persona que va a trabajar con dicho
programa. En ella deber especificarse claramente:
La finalidad del programa.
Los datos de entrada al programa y sus tipos.
La obligatoriedad o no de los mismos.
Los datos de salida del programa y sus tipos.
Las restricciones de uso del programa.
En caso de usar ficheros de entrada y/o salida, debe especificarse el
formato de los mismos.
Los mensajes de error que puede emitir el programa (ante datos
incoherentes de entrada, por ejemplo), aclarando el motivo de los mismos
y la accin a realizar en su caso.

3.4. Otros paradigmas de desarrollo


Las tcnicas de programacin estructuradas han sido y son fundamentales en el
desarrollo de productos software. No obstante, ante un incremento en la complejidad
de los programas de ordenador, debido fundamentalmente a una mayor demanda de
software y a unas mayores exigencias de sus usuarios en cuanto a funcionalidad y
facilidad de manejo, se hace necesario buscar soluciones de desarrollo que permitan
afrontar ms adecuadamente esa complejidad inherente al software actual.
En este sentido cabe destacar especialmente las tcnicas de desarrollo visuales,
orientadas a objetos y basadas en componentes.

Diseo de Programas. Programacin Estructurada

91

3.4.1. Orientacin a Objetos


La orientacin a objetos forma parte de la Ingeniera del Software como un paradigma
de desarrollo con su propio marco conceptual que involucra terminologa, mtodos,
modelos, procedimientos, tcnicas, prcticas y procesos.
Los mtodos de la orientacin a objetos han sido reconocidos en el mbito de las
tecnologas de la informacin, como la mejor filosofa para abordar la reutilizacin y
la extensibilidad del software.
En una primera aproximacin se puede decir que el trmino orientado a objetos
significa que el software se organiza como una coleccin de objetos discretos que
contienen tanto estructuras de datos como un comportamiento. Esto se opone
frontalmente a la programacin convencional, donde las estructuras de datos y la
funcionalidad slo se relacionan dbilmente.
Tradicionalmente se asocia la orientacin a objetos con la programacin orientada
a objetos, es decir centrando la atencin en los lenguajes de programacin. Cierto es
que los lenguajes de programacin orientados a objetos son tiles para eliminar
algunas restricciones propias de los lenguajes de programacin tradicionales. Sin
embargo, enfatizar la fase de codificacin supone un retroceso de la Ingeniera del
Software al priorizar los mecanismos de implementacin frente al proceso de
pensamiento subyacente al cual sirven de base [Rumbaugh et al., 1991]. As, este
paradigma se extiende a todas las fases del ciclo de vida con un modelo comn
subyacente a todas estas fases: el modelo objeto.
Se entender, entonces, por desarrollo orientado a objetos la forma de pensar
acerca del software basndose en abstracciones del mundo real; donde la palabra
desarrollo hace alusin directa al bloque inicial de fases del ciclo de vida del
software: anlisis, diseo e implementacin.
Aparecen as los conceptos de Programacin Orientada a Objetos (POO),
Diseo Orientado a Objetos (DOO) y Anlisis Orientado a Objetos (AOO).
La orientacin a objetos gira en torno a dos conceptos fundamentales: el objeto y la
clase. Ambos son dos conceptos estrechamente relacionados, pero que no deben
confundirse nunca [Holland et al., 1997].
Un objeto se puede definir como una entidad conceptual que es identificable, tiene
caractersticas que comporten un estado interno y tiene unas operaciones que pueden
cambiar el estado del sistema local, y que tambin pueden solicitar operaciones de
objetos relacionados [Champeaux et al., 1993].
Por su parte una clase sirve como una plantilla para la creacin de objetos,
especificando un comportamiento a todas sus instancias. Se puede definir como la
descripcin de un grupo de objetos con propiedades similares, comportamientos
comunes, interrelaciones comunes y semntica comn [Rumbaugh et al., 1991].
Los lenguajes de programacin orientados a objetos tienen su gnesis a finales de
la dcada de los sesenta con Simula 67 [Dahl et al., 1970]. Actualmente, existen
numerosos representantes que han surgido o han evolucionado hacia la tecnologa de
objetos. Dos de los lenguajes ms representativos son C++, desarrollado por Bjarne
Stroustrup en los AT&T Bell Laboratories a principios de la dcada de los ochenta
[Stroustrup, 1997], y Java [SUN, 2005], un lenguaje desarrollado por la empresa
SUN Microsystem que ha revolucionado el mundo de los lenguajes de programacin
en la segunda mitad de la dcada de los noventa.

92

Programacin en C

Documentos pblicos

Al igual que sucede en el paradigma estructurado, la tecnologa de objetos debe dar


cobertura a todo el ciclo de desarrollo de los sistemas software, es decir, al anlisis, al
diseo y a la implementacin; aunque se debe resaltar que estas fases se solapan y
presentan unas formas ms difuminadas que en el desarrollo estructurado.
Cuando a mediados de la dcada de los ochenta resurge la orientacin a objetos,
comienzan a surgir multitud de propuestas metodolgicas con una orientacin
objetual, slo en el perodo comprendido entre 1989 y 1994 el nmero de lenguajes de
modelado orientados a objetos pasa de diez a ms de cincuenta. Precisamente esta
diversidad de mtodos y notaciones ha sido una de las mayores barreras con que se
encontraron las empresas y los departamentos de informtica para la adopcin de la
orientacin a objetos, conocindose a este fenmeno como la guerra de los mtodos
[Garca y Pardo, 1998].
Ante esta situacin de diversidad, en la parte final de la dcada de los noventa se
empiezan a escuchar los primeros rumores sobre la necesidad de una unificacin y
una estandarizacin. El catalizador principal de esta unificacin ha sido sin lugar la
aparicin en escena del lenguaje de modelado UML (Unified Modeling Language) de
Rational Software Corporation, que fue adoptado el 17 de noviembre de 1997 como
lenguaje de modelado estndar por el OMG (Object Management Group) en su
versin 1.1 [Rational et al., 1997], siendo a fecha de hoy la versin 1.5 de este
lenguaje de modelado la que se encuentra en vigor [OMG, 2003], por ms que la
versin 2.0 est prxima a estar finalizada (http://www.uml.org/#UML2.0). En la
Figura 3.20 se muestra la evolucin de este lenguaje de modelado desde su gnesis a
la versin actual.
Marzo de 2003

OMG UML 1.5

Septiembre de 2001

OMG UML 1.4

Junio de 1999
Modificaciones editoriales
Julio de 1998

OMG UML 1.3

Aceptacin de UML 1.1 como


estndar por OMG
17 de Noviembre 1997
Publicacin de UML 1.1
Septiembre 1997
Publicacin de UML 1.0
Enero 1997
Junio 96 y Octubre 1996
OOPSLA95

OMG UML 1.2


OMG UML 1.1

UML 1.0
UML 0.9 & 0.91

Mtodo Unificado 0.8


Booch94

Estandarizacin

UML 1.1

Unificacin
Colaboradores y
expertos

OMT-2

Fragmentacin
Otros mtodos Booch91

OMT-1

OOSE

Figura 3.20. Evolucin de UML

Diseo de Programas. Programacin Estructurada

93

3.4.2. Desarrollo basado en componentes


El desarrollo basado en componentes aparece como una evolucin de la tecnologa de
objetos, buscando conseguir los beneficios de calidad, reutilizacin y productividad
que prometan las soluciones basadas en objetos.
En el desarrollo basado en componentes o CBD (Component Based Development)
las aplicaciones son construidas ensamblando componentes como cajas negras de
software, provenientes de diferentes fuentes; de forma que los mismos componentes
pueden ser escritos en diferentes lenguajes de programacin y ejecutarse en diferentes
plataformas. La parte de la Ingeniera del Software que se encarga de cuidar por el
desarrollo coherente de aplicaciones de calidad mediante la integracin de
componentes se conoce como CBSE (Component-Based Software Engineering)
[Kozaczynski y Booch, 1998].
Adems, la gran difusin de los ordenadores en nuestra sociedad, junto con el
abaratamiento y el aumento de eficiencia del hardware y las comunicaciones, han
ocasionado el establecimiento de una infraestructura excepcionalmente propicia para
el desarrollo de aplicaciones distribuidas en sistemas abiertos. El gran auge que tienen
los modelos de componentes COM [Microsoft, 1995], JavaBeans [Hamilton, 1997] y
CORBA (CCM) [Sevilla, 2000] y sus plataformas asociadas son claros ejemplos.
Ms difcil resulta dar una definicin internacionalmente aceptada de lo qu se
entiende por componente, al tener este concepto diferentes significados o acepciones
segn quien lo emplee. Una definicin interesante, por la complecin que presenta, es
la que aparece en [Sparling, 2000] donde un componente se define como un paquete
de servicios software independientemente implementado, neutro con respecto al
lenguaje, distribuido en un contenedor encapsulado y reemplazable, accedido
mediante una o ms interfaces pblicas. Aunque un componente puede tener la
habilidad de modificar una base de datos, no debe mantener informacin de estado.
Un componente no est restringido por la plataforma ni est restringido a una
aplicacin.
Los objetos y los componentes son conceptos diferentes aunque se vean
combinados por las herramientas de desarrollo actuales. Tpicamente, un componente
cobra vida a travs de un conjunto de objetos y, por tanto, normalmente contiene una
o ms clases o prototipos de objetos inmutables. Adicionalmente, el componente
puede contener un conjunto de objetos que capturan un estado inicial por defecto u
otros recursos del componente. Sin embargo, no es obligatorio que un componente
contenga exclusivamente clases o alguna clase siquiera. Un componente puede
contener procedimientos o funciones tradicionales, o estar realizado enteramente bajo
otro paradigma diferente al paradigma orientado a objetos [Szyperski, 2000].

3.4.3. Programacin visual


Las tcnicas de programacin visual se han arraigado profundamente en muchos de
los entornos de desarrollo actuales. Esta tecnologa utiliza el poder de expresin de las
representaciones grficas para especificar la funcionalidad de los programas a
construir.

94

Programacin en C

Los entornos de desarrollo visuales ofrecen un mecanismo para que el ordenador


entienda las representaciones grficas y transforme las interacciones con el usuario a
travs de dispositivos como el ratn en componentes de cdigo fuente. Adems, estos
entornos establecen el entorno adecuado tanto para la ejecucin del cdigo como para
su prueba y depuracin.
La programacin visual est muy ligada a la utilizacin de componentes, y al CBD
por tanto, de manera que los elementos visuales representan componentes que ofrecen
una variada funcionalidad, como acceso a base de datos, generacin de informes,
tratamiento de imgenes..., y que son integrados y configurados para formar parte del
programa final mediante operaciones grficas, tales como seleccionar o arrastrar y
soltar (drag & drop).
Una herramienta de programacin visual posibilita que los desarrolladores
especifiquen los programas de forma interactiva y grfica. Todos los elementos de un
programa tienen una representacin grfica basada en metforas e iconos. Las
relaciones entre estos componentes se establecen de forma grfica tambin.
Los entornos de desarrollo visual no son herramientas para mostrar visualmente los
programas. En las herramientas de visualizacin de programas, stos se construyen
con tcnicas tradicionales, de forma que las herramientas se limitan a presentar una
vista grfica de ellos, para ilustrar aspectos de su ejecucin y depuracin.
Las herramientas de programacin actuales presentan una serie de caractersticas
que denotan su potencia, a saber:
Tienen facilidad de uso.
Dan soporte para lenguajes de script que dan ms flexibilidad y potencia a
la herramienta de desarrollo. Estos lenguajes de script pueden verse como
lenguajes pegamento que permiten combinar ms eficientemente los
componentes visuales.
Permiten el desarrollo de aplicaciones robustas, con un alto grado de
productividad; convirtindose as en entornos de desarrollo completos que
van ms all de ser meras herramientas para la construccin de interfaces
grficas de usuario.
Ofrecen capacidad para la reutilizacin de aplicaciones existentes.
Soportan el desarrollo basado en componentes.
Cumplen los estndares marcados por las organizaciones internacionales.
Soportan la construccin de aplicaciones cliente/servidor.
Soportan el trabajo cooperativo y la gestin de la configuracin del
software.
Ejemplos de entornos visuales de desarrollo son Microsoft Visual Studio .NET, la
familia de productos IBM Visual Age o Borland Builder.

3.5. Resumen
En este captulo se ha realizado una presentacin de los conceptos bsicos que se
necesitan manejar en el desarrollo de programas de ordenador, haciendo especial
hincapi en las denominadas tcnicas estructuradas de programacin.

Diseo de Programas. Programacin Estructurada

95

Se ha introducido el concepto de algoritmo, definindolo como un procedimiento


no ambiguo para la solucin de un problema. Un algoritmo no es un programa
software, que expresa un medio para la representacin de un algoritmo mediante un
lenguaje de programacin para que un ordenador pueda procesar el mtodo de
solucin que se encuentra embebido en el programa.
El estilo de programacin en el que se ha pretendido iniciar al lector, es el de la
programacin estructurada, aunque existen otros paradigmas de desarrollo ms
avanzados como puede ser el paradigma orientado a objetos. La programacin
estructurada toma como principal caracterstica contar con mdulos diseados de
forma descendente y cuya funcionalidad se expresa exclusivamente con las tres
estructuras bsicas de control: secuencia, seleccin y repeticin.
A lo largo de este captulo se ha intentado transmitir el hecho de que la realizacin
de un programa de ordenador, con independencia de su tamao y complejidad, no es
una mera actividad de codificacin en un lenguaje de programacin concreto, sino
que tiene un ciclo de vida que abarca su definicin, desarrollo y mantenimiento.
Dentro del enfoque introductorio a las tcnicas de programacin estructurada, se han
ido presentando las fases por las que se pasa en la creacin de un programa: el
anlisis que sirve para definir qu se pretende conseguir con el programa, el diseo
donde se comienza a vislumbrar el camino de solucin que se va a implementar,
centrando todos los esfuerzos en el diseo de los algoritmos, la codificacin en el
lenguaje de programacin escogido, la compilacin para traducir el programa fuente
realizado, normalmente, en un lenguaje de alto nivel a un programa objeto, la
verificacin y depuracin del programa para eliminar los errores de ejecucin y
lgicos que pudieran haberse cometido y la documentacin tanto tcnica como de
usuario que permita el mantenimiento, evolucin y uso del programa.

3.6. Lecturas complementarias


Para profundizar en algunos de los aspectos tratados, o mencionados, en el presente
captulo se recomienda la consulta de alguna de las siguientes fuentes bibliogrficas.
Para adentrarse en el concepto de ciclo de vida del software, en la descripcin de
sus fases y en paradigmas concretos se recomienda la consulta de dos textos clsicos
en la docencia de la Ingeniera del Software: Ingeniera del Software: Un Enfoque
Prctico de Roger S. Pressman (5 Edicin, McGraw-Hill, 2002) y Anlisis y
Diseo Detallado de Aplicaciones Informticas de Gestin de Mario G. Piattini
Velthuis et al. (Ra-ma, 1996).
Para completar las nociones introducidas sobre la fase de diseo de algoritmos se
recomienda la lectura del captulo dos Estructuras de Datos y Algoritmos del libro de
Alberto Prieto, Antonio Lloris y Juan Carlos Torres Introduccin a la Informtica
(3 Edicin, McGraw-Hill, 2002) y el captulo tres de Fundamentos de
Programacin de Jos Lpez Herranz y Enrique Quero Catalinas (Paraninfo, 1998).
Un complemento al apartado de compilacin se puede encontrar en el captulo diez
del libro de Alberto Prieto et al. Si se quiere consultar un texto ms especfico sobre
compiladores se recomienda la lectura del libro del dragn: Compiladores.

96

Programacin en C

Principios, Tcnicas y Herramientas de Alfred V. Aho, Ravi Sethi y Jeffrey D.


Ullman (Addison-Wesley Iberoamericana, 1990).
Ejemplos de formatos para la documentacin tcnica y de usuario se pueden
encontrar en el captulo ocho del libro de Jos Lpez Herranz y Enrique Quero
Catalinas, anteriormente mencionado.
Un libro muy adecuado para iniciarse en el diseo y anlisis, aunque sencillo, de
algoritmos, y el estudio de tipos abstractos de datos (TAD) bsicos, es Estructuras
de datos. Algoritmos, abstraccin y objetos de Luis Joyanes e Ignacio Zahonero
(McGraw-Hill, 1998). Adems cuenta con un buen nmero de ejemplos y ejercicios.
Como complemento se pueden utilizar otros dos ttulos que se centran en distitntos
aspectos de programacin: Programacin estructurada en C de J. L. Antonakos y
K. C. Mansfield Jr. (Prentice-Hall, 1997) y Fundamentos de programacin, 2 ed.,
de Luis Joyanes (McGraw-Hill, 1996).
Para introducirse en la Orientacin a Objetos en general se recomienda la segunda
edicin del libro de Bertrand Meyer Object Oriented Software Construction
(Prentice Hall, 1997). Otras referencias de inters son Object Oriented Analysis and
Design with Applications de Grady Booch (2nd Edition. The Benjamin/Cummings
Publishing Company, 1994); Object-Oriented Modeling and Design de James
Rumbaugh et al. (Prentice-Hall, 1991); o An Introduction to Object-Oriented
Programming de Timothy Budd (Addison-Wesley, 1991)8.
En cuanto a las referencias bsicas de UML las constituyen los libros escritos por
los autores principales del mismo The Unified Modeling Language User Guide de
Grady Booch et al. (Object Technology Series. Addison-Wesley, 1999) y The
Unified Modeling Language Reference Manual de James Rumbaugh et al. (Object
Technology Series. Addison-Wesley, 1999)9.
En el sitio web The Component-based Development Headquarters
(http://www.cbd-hq.com/) se pueden encontrar numerosos artculos introductorios
sobre el desarrollo basado en componentes. Otro artculo introductorio a este tema
puede ser Component Based Development de A. C. Wills, accesible en Internet en
la direccin http://www.trireme.com. De los mltiples libros que sobre el desarrollo
basado en componentes se pueden encontrar se van a destacar dos: la segunda edicin
del libro de Clemens Szyperski et al. Component Software: Beyond Object-Oriented
Programming (Addison-Wesley/ACM, 2002), una de las referencias obligadas en
este campo, y el libro de D. F DSouza y A. C. Wills Objects, Components and
Frameworks with UML. The Catalysis Approach (Addison-Wesley, 1999) que
presenta un enfoque metodolgico en el que incluir todas las nuevas tendencias de
desarrollo que evolucionan a partir del paradigma orientado a objetos.
8

Los libros presentados en este prrafo estn disponibles traducidos al espaol: Meyer, B.
Construccin de Software Orientado a Objetos. 2 Edicin. Prentice Hall, 1999; Booch, G.
Anlisis y Diseo Orientado a Objetos con Aplicaciones. 2 Edicin. Addison-Wesley/Diaz
de Santos, 1996; Rumbaugh, J., Blaha, M., Premerlani, W., Eddy, F., y Lorensen, W.
Modelado y Diseo Orientados a Objetos. Metodologa OMT. Prentice Hall, 2 reimpresin,
1998; Budd, T. Programacin Orientada a Objetos. Addison-Wesley Iberoamericana, 1994.
9 Los libros presentados en este prrafo estn disponibles traducidos al espaol: Booch, G.,
Rumbaugh, J. y Jacobson, I. El Lenguaje Unificado de Modelado. Addison Wesley, 1999;
Rumbaugh, J., Jacobson, I. y Booch, G. El Lenguaje Unificado de Modelado. Manual de
Referencia. Addison-Wesley. 2000.

Diseo de Programas. Programacin Estructurada

97

3.7. Cuestiones y ejercicios


1.
2.
3.

4.
5.
6.
7.
8.
9.
10.
11.
12.
13.

14.
15.
16.
17.
18.
19.

Define algoritmo y programa. Cules son sus diferencias?


En qu consiste el mtodo de diseo descendente en la creacin de
algoritmos? Qu otro nombre recibe tambin?
Explica qu es:
Un programa fuente.
Un programa objeto.
Un programa ejecutable.
Cul de ellos es el ms importante, puesto que los dems se pueden
obtener de l?
Explica el propsito de un compilador. Puede utilizarse cualquier compilador
para compilar cualquier programa?
Enuncia las fases de la compilacin, indicando brevemente qu se realiza en
cada una de ellas. Qu tipos de errores puede tener un programa fuente al ser
compilado? Cmo se resolver cada uno de ellos?
Realiza un diagrama de flujo que represente como se llega a generar un
programa ejecutable desde su correspondiente programa fuente.
Qu tipos de errores pueden detectarse en un programa al ser verificado y
depurado? Cmo se resolver cada uno de ellos?
Qu significa verificar un algoritmo? Cmo suele hacerse?
En qu fase de la creacin de programas se usan tanto el diagrama de flujo como
el pseudocdigo.
Indica dos ventajas de los diagramas de flujo respecto al pseudocdigo y dos
ventajas de pseudocdigo respecto a los diagramas de flujo.
Define lenguaje de alto nivel, lenguaje ensamblador y lenguaje mquina.
Enuncia algunos lenguajes de alto nivel que conozcas, indicando su orientacin
(educativo, gestin, cientfico, general...).
Marca la respuesta correcta (puede haber varias):
Un programa escrito en lenguaje ensamblador es:
Un programa fuente.
Un programa ejecutable.
Un programa en binario.
Un programa mquina.
Un programa objeto.
Explica el propsito de un editor. Puede utilizarse cualquier editor para
codificar cualquier programa? En qu fase de la creacin de programa se utiliza
un editor?
Cules son las diferencias entre un intrprete y un compilador? Cul de ellos
es ms eficiente?
Cules son las diferencias entre un ensamblador y un compilador?
Explica el propsito de un programa enlazador o linker.
Explica qu significa la portabilidad en un lenguaje de programacin.
Realizar el anlisis y diseo (con diagrama de flujo y pseudocdigo) de los
algoritmos que resuelvan los siguientes problemas:

98

Programacin en C

Dada la longitud del radio, obtener el rea del crculo, longitud de la


circunferencia y volumen de la esfera que determina.
Clculo del salario semanal de los empleados de una empresa, sabiendo
que ste se calcula en base a las horas semanales trabajadas y a un precio
especificado por hora (ambos sern datos de entrada). Si el trabajador
sobrepasa las 40 horas semanales, las horas extraordinarias se pagarn a
razn de 1,7 veces la hora ordinaria.
Calcular el factorial de un nmero que se leer de teclado.
Determinar la suma de los 100 primeros nmeros impares.

3.8. Referencias
[AECC, 1986] Asociacin Espaola para la Calidad. Glosario de Trminos de Calidad e
Ingeniera del Software. AECC, 1986.
[Champeaux et al., 1993] Champeaux, D., Lea, D. y Faure, P. Object-Oriented System
Development. Addison Wesley. 1993.
[Dahl et al., 1970] Dahl, O., Myrhaug, B. y Nygaard, K. (Simula 67) Common Base
Language. Norsk Regnesentral (Norwegian Computing Center), Publication N. S22, Oslo. October 1970.
[Garca y Pardo, 1998] Garca Pealvo, F. J. y Pardo Aguilar, C. UML 1.1. Un Lenguaje
de Modelado Estndar para los Mtodos de ADOO. Revista Profesional para
Programadores (RPP), Editorial Amrica-Ibrica, V(1):57-61. Enero, 1998.
[Goodman y Hedetniemi, 1977] Goodman, S. E. y Hedetniemi, S. T. Introduction to the
Design and Analysis of Algorithms. McGraw-Hill. 1977.
[Hamilton, 1997] Hamilton, G. (Ed.). JavaBeans Specification 1.01. Sun Microsystems.
http://java.sun.com/products/javabeans/docs/spec.html. [ltima vez visitado,
27/7/2005]. 1997.
[Holland et al., 1997] Holland, S., Griffiths, R. y Woodman, M. Avoiding Object
Misconceptions. In Proceedings of the twenty-eighth SIGCSE technical symposium
on Computer Science Education (SIGCSE97). (Feb. 27-Mar. 1, 1997, San Jose, CA
USA). Pages 131-134. 1997.
[Kozaczynski y Booch, 1998] Kozaczynski, W. y Booch, G. Component-Based Software
Engineering. IEEE Software, 15(5):34-36. 1998.
[Microsoft, 1995] Microsoft. The Component Object Model Specification. Version 0.9.
October 1995.
[OMG, 2003] OMG. OMG Unified Modeling Language Specification Version 1.5. Object
Management Group Inc. Document formal/03-03-01. http://www.omg.org/cgibin/doc?formal/03-03-01. [ltima vez visitado, 27/7/2005]. March 2003.
[Rational et al., 1997] Rational Software Corporation, Microsoft, Hewlett-Packard,
Oracle, Sterling Software, MCI Systemhouse, Unisys, ICON Computing
IntelliCorp, i-Logix, IBM, ObjecTime. Platinum Technology, Ptech, Taskon,
Reich Technologies y Softeam. UML Proposal to the Object Management. In
Response to the OA&D Task Forces RFP-1. UML 1.1 Referece Set 1.1. 1
September 1997.
[Rumbaugh et al., 1991] Rumbaugh, J., Blaha, M., Premerlani, W., Eddy, F. y Lorensen,
W. Object-Oriented Modeling and Design. Prentice-Hall, 1991.
[Scacchi, 1987] Scacchi, W. Models of Software Evolution: Life Cycle and Process. SEI
Curriculum Module SEI-CM-10-1.0, Pittsburgh (EEUU), Software Engineering
Institute (Carnegie Mellon University). October 1987.

Diseo de Programas. Programacin Estructurada

99

[Sevilla,

2000] Sevilla, D. A brief tutorial on CORBA and CCM.


http://www.ditec.um.es/~dsevilla/ccm/#CCMtutorial.
[ltima
vez
visitado,
27/7/2005]. 2000.
[Sparling, 2000] Sparling, M. Lessons Learned through Six Years of Componet-Based
Development. Communications of the ACM, 43(10):47-53. October 2000.
[Stroustrup, 1997] Stroustrup, B. The C++ Programming Language. 3rd Edition, Addison
Wesley, 1997.
[SUN, 2005] SUN Microsystems. The Java Tutorial. A Practical Guide for Programmers.
http://java.sun.com/docs/books/tutorial/index.html. [ltima vez visitado, 27/7/2005]
April 2005.
[Szyperski, 2000] Szyperski, C. Components versus Objects. ObjectiveView, Issue #5.
http://www.ratio.co.uk. 2000.
[Wirth, 1971] Wirth, N. Program Development by Stepwise Refinement. Communication of
the ACM, 14(4):221-227. April 1971.

4. Elementos bsicos de un lenguaje de programacin


En este captulo se presentan los elementos bsicos para la construccin de
instrucciones simples. Entre stos se encuentra el conjunto de caracteres, los
identificadores y las palabras reservadas, los tipos de datos, las constantes y las
variables. Se ver como combinar estos elementos para formar componentes ms
grandes de programas.
Parte de este material se presenta de forma detallada y puede resultar difcil de
asimilar, sobre todo para programadores con poca experiencia. Hay que sealar que el
propsito de este captulo es presentar ciertos conceptos bsicos y algunas
definiciones necesarias para los conceptos que se tratan en los siguientes temas. Por
tanto, cuando lea este captulo por primera vez puede ser suficiente el adquirir una
cierta familiaridad con los conceptos que en l se presentan. Se conseguir una
comprensin ms profunda de estos elementos tras las repetidas referencias a este
captulo que se encuentran en los siguientes.
Este captulo se ha organizado en diez secciones. En la primera seccin se
introduce el significado y la forma de utilizacin de los identificadores y de las
palabras reservadas. La seccin 2 presenta la problemtica de los datos, mientras la
seccin 3 hace un repaso por los tipos de datos bsicos presentes en la mayora de los
lenguajes de programacin. En la cuarta seccin se definen los conceptos de variable
y de constante. La seccin 5 se dedica a la presentacin de los tipos bsicos
soportados por el lenguaje C, mientras que la seccin 6 hace lo propio con las cadenas
de caracteres en este lenguaje de programacin. La sptima seccin introduce las
constantes simblicas en el lenguaje C y la octava seccin explica cmo introducir
comentarios en un programa C. La seccin novena presenta la estructura bsica de un
programa C, realizndose el primer programa en este lenguaje. Finalmente, la dcima
seccin presenta una lista de cuestiones y ejercicios de repaso.

4.1. Identificadores y palabras reservadas


Para escribir un programa se usa un conjunto de caracteres:
Las letras maysculas.
Las letras minsculas.
Los 10 dgitos decimales.
Ciertos caracteres especiales (ver Tabla 4.1).
+
!
<
:

?
>
;

*
^
(
.

/
"
)
,

=
'
[
_

%
&
#
~
\
|
]
{
}
espacio blanco

Tabla 4.1. Caracteres especiales que permiten casi todos los lenguajes de alto nivel

Se denomina componente lxico o token al elemento ms bsico reconocido por el


compilador. Est formado por un carcter simple o grupo de caracteres.
- 101 -

102

Programacin en C

Existen seis clases de componentes sintcticos o tokens en el vocabulario del


lenguaje C: palabras reservadas, identificadores, constantes, cadenas de caracteres,
operadores y separadores. Los separadores uno o varios espacios en blanco,
tabuladores, caracteres de nueva lnea, y tambin los comentarios escritos por el
programador se emplean para separar los dems tokens; por lo dems son ignorados
por el compilador. El compilador descompone el texto fuente o programa en cada uno
de sus tokens, y a partir de esta descomposicin genera el cdigo objeto
correspondiente.

Ejemplo en C

En C es la palabra main, o la llave izquierda ({) o el signo de suma


(+).

4.1.1. Palabras reservadas


Son componentes lxicos predefinidos por el lenguaje y que tienen un significado
especial para el compilador. No pueden ser usadas ms que para la finalidad que les
ha dado el propio lenguaje. En particular no pueden utilizarse como nombres de
variables y/o funciones.

Apunte de C

El C es un lenguaje muy conciso, con muchas menos palabras reservadas -slo


tiene 32-, que otros lenguajes. Todas las palabras reservadas del C se escriben
en minsculas.
auto
default
float
register
struct
volatile

break
do
for
return
switch
while

case
double
goto
short
typedef

char
else
if
signed
union

const
enum
int
sizeof
unsigned

continue
extern
long
static
void

Tabla 4.2. 32 palabras reservadas del ANSI C1

4.1.2. Identificador
Nombre que el programador puede elegir libremente para referenciar a distintos
elementos dentro del programa como datos, funciones...
Existen unas normas generales para su empleo comunes a la mayora de todos los
lenguajes de programacin:
Puede estar formado por letras, dgitos y el carcter guin2.
Debe comenzar siempre por una letra o el carcter subrayado.
1

Algunos compiladores de C pueden reconocer otras palabras reservadas. Para obtener una
lista completa de las mismas para un compilador determinado se debe consultar su manual de
referencia.
2Unos lenguajes de programacin slo admiten el guin bajo ( _) y otros slo el guin medio (-).

Elementos bsicos de un lenguaje de programacin

103

No se permite ningn otro tipo de caracteres (espacios, puntos...).


Su longitud no est limitada, aunque el compilador slo los distinguir por los N
primeros caracteres (donde valores tpicos para N son 32, 64 128 dependiendo del
compilador).
EJEMPLO

Identificadores vlidos:
x
y12
Nombre area

suma_1
_temperatura
indicador_de_error
TABLA

Identificadores no vlidos:
4num
%iva
indicador de error
EJEMPLO

Los identificadores suma_de_valores y suma_de_variaciones son


vlidos gramaticalmente, sin embargo, los compiladores muy antiguos puede
que no fueran capaces de distinguirlos ya que ambos tienen las 8 primeras
letras iguales. De esta forma, el programa considerara como iguales a ambos
identificadores.
Los compiladores actuales son capaces de distinguir las 31 primeras letras de
los identificadores, incluso las 63 primeras letras 3.
Si se est redactando un programa para calcular el valor futuro de una
inversin:
Los identificadores valor y valor_futuro son nombres simblicos
apropiados (son significativos).
Sin embargo, los identificadores v y vf seran demasiado cortos, ya que
no queda claro el significado de lo que representan.
Por otro lado, un identificador como
futuro_valor_de_la_inversion_inicial
no sera adecuado por ser demasiado largo.
Informacin a representar
Puntos acumulados
Total vendido a final de mes
Saldo a fin de perodo
Nombre empleado

Buen Identificador
puntos_acum
tot_ventas_mes
saldo_final
nom_empl

Mal Identificador
pac
total_vendido_a_final_de_mes
saldo
var1

Tabla 4.3. Ejemplos de identificadores adecuado y no adecuados

Buen estilo de programacin


El nombre asignado a un identificador debe tener relacin con la informacin que
representa. En caso de emplear abreviaturas, deben ser significativas. En general
es muy aconsejable elegir los nombres de los identificadores de forma que
permitan conocer a simple vista qu tipo de informacin representan, utilizando
para ello tantos caracteres como sean necesarios. Esto simplifica enormemente la

3 ANSI

C distingue los 31 primeros caracteres de los identificadores.

104

Programacin en C

tarea de programacin y, sobre todo, de correccin y mantenimiento de los


programas.

Apunte de C

En C puede pueden utilizarse tanto las maysculas como las minsculas para
escribir un identificador.
Pero C es un lenguaje sensible a las maysculas y minsculas: para C no son
lo mismo las letras maysculas que las minsculas.
Es decir, los siguientes son cuatro identificadores vlidos pero distintos:
saldo
Saldo
SALDO
SalDo

Dado que todas las palabras reservadas de C se escriben en minsculas, podra


utilizarse una palabra reservada escrita en maysculas como identificador. Esto
no suele hacerse normalmente porque provoca confusin a la hora de leer el
programa y se considera propio de un estilo de programacin pobre y malo.

4.2. Los datos


Un dato es toda aquella informacin caracterstica de una entidad (objeto, ente) y que
es susceptible de tratamiento en un programa informtico. Pueden representar desde
una edad (dato numrico sin decimales), el saldo de una cuenta (numrico con
decimales), el nombre de una persona (cadena de caracteres), la letra de la direccin
de un piso (carcter)...
El primer objetivo de un programa informtico es el manejo de estos datos, de
forma que las acciones de las instrucciones ejecutables de los programas se reflejan en
que los datos de entrada se manipulan en el programa, y despus de las etapas
intermedias, se generan datos de salida, como se muestra en la Figura 4.1.

Figura 4.1. Datos de entrada y de salida

Los datos se clasifican de manera que los programas sean capaces de realizar los
tratamientos necesarios de forma determinada. La forma de clasificar los datos
constituye la estructura de datos dentro de un lenguaje de programacin.
Un dato dentro de un programa se caracteriza por llevar asociado: un
identificador, un tipo y un valor.
Identificador. Nombre para referenciar al dato dentro del programa.
Tipo. Los tipos de datos que admite un lenguaje de programacin estn
clasificados. El tipo de un dato determina el rango de valores que puede

Elementos bsicos de un lenguaje de programacin

105

tomar el dato y su ocupacin en memoria, durante la ejecucin del


programa
Valor. Ser un elemento determinado del rango de valores permitidos
por el tipo de dato definido.
EJEMPLO

Identificador
puntos_acum
talla
nombre_empleado

Tipo
Valor
entero (numrico sin decimales) 123
1,75
real (numrico con decimales)
JUAN PREZ SERRANO
cadena de caracteres

Existen muchas formas de clasificar los tipos de datos de un lenguaje. En la Tabla


4.4 se muestra una de las ms comunes.
En el siguiente apartado se describen los tipos de datos bsicos comunes a todos
los lenguajes. El resto de los tipos ser descrito en el momento en que surja la
necesidad de utilizarlos.
Entero
Numricos
Real
Bsicos
Carcter
Lgico
Puntero
Derivados
Registro
Heterogneos
Enumeracin
Estticos
Tabla
Homogneos
Cadena de caracteres
Estructurados
Lista
Internos
Pila
Lineales
Cola
Dinmicos
rbol
No lineales
Grafo
Estructurados
Fichero
externos
Tabla 4.4. Clasificacin de los tipos de datos

4.3. Descripcin de los tipos de datos bsicos


4.3.1. Tipo entero
Representa nmeros enteros con o sin signo, que estarn compuestos por los dgitos
del 0 al 9, pudiendo ser precedidos por los signos + -.
Existen varias formas de representacin interna de los datos numricos enteros.

106

Programacin en C

Para enteros sin signo:


Binario puro: El nmero entero completo se convierte a binario,
completando con ceros a la longitud de palabra del ordenador
(normalmente 2 bytes 4 bytes).
45
00000000 00101101
789
00000011 00010101
Nmeros enteros sin signo representados en binario puro en 16 bits.
Decimal codificado en binario (BCD): Cada dgito decimal se
convierte a binario en 4 bits.
45
0000 0000
0100 0101
789
0000 0111
1000 1001
Nmeros enteros sin signo representados en BCD en 16 bits
El formato de representacin de nmeros enteros sin signo ms usado es el binario
puro, y el rango de representacin depender del nmero de bytes utilizados, como se
muestra en la Tabla 4.5.
Nmero de bytes Valores posibles de Menor entero Mayor entero
representar
representable representable
1
2
4
n

byte (8 bits)
bytes (16 bits)
bytes (32 bits)
bits

28 = 256
216 = 65536
232 = 4294967296
2n

0
0
0
0

255
65535
4294967295
2n -1

Tabla 4.5. Relacin entre bytes utilizados e intervalos de representacin en enteros sin signo

Para enteros con signo:


Signo-magnitud: El bit situado ms a la izquierda contiene el signo
(0 para + y 1 para -). El resto de los bits contiene el mdulo o valor
del nmero representado en binario puro.
+45
00000000 00101101
-789
10000011 00010101
Nmeros enteros representados en MS en 16 bits.
Complemento a 1 (C-1): El bit situado ms a la izquierda contiene el
signo, igual que en el formato signo-magnitud. Los nmeros positivos
se representan igual que en el formato signo-magnitud. Para obtener
la representacin de un nmero negativo, se representa el mdulo
(valor sin signo) de ste en signo-magnitud y se cambian los ceros por
unos y los unos por ceros.
+45
00000000 00101101
-789
11111100 11101010
Nmeros enteros representados en C-1 en 16 bits.
Complemento a 2 (C-2): El bit situado ms a la izquierda contiene el
signo, igual que en signo-magnitud. Los nmeros positivos se
representan igual que en signo-magnitud. La representacin de los
nmeros negativos se obtiene realizando el complemento a 1 y
sumndole al resultado de la operacin anterior un 1.

Elementos bsicos de un lenguaje de programacin

107

+45
00000000 00101101
-789
11111100 11101011
Nmeros enteros representados en C-2 en 16 bits.
Exceso a 2n-1: No se emplea ningn bit para el signo y el valor se
corresponde con la representacin en binario puro del nmero ms el
exceso.
32768 + +45 = 32813
10000000 00101101
32768 + -789 = 31979
01111100 11101011
Nmeros enteros representados en exceso 2n-1. El exceso para 16
bits es 216-1 = 32768.
Decimal desempaquetado: Cada dgito ocupa un octeto, teniendo un
cuarteto de zona (el cuarteto de la izquierda, que para todos los
dgitos menos para el ltimo contiene 1111) y un cuarteto de
contenido (el de la derecha) donde se halla el dgito decimal
representado en binario puro. El ltimo dgito del nmero contiene en
el cuarteto de zona el signo (1100 para + y 1101 para -).
+2015
-2015

zona dgito zona dgito zona dgito signo dgito


1111 0010 1111 0000 1111 0001 1100 0101
1111 0010 1111 0000 1111 0001 1101 0101

Nmeros enteros representados en decimal desempaquetado en 32


bits.
Decimal empaquetado: Cada dgito ocupa un cuarteto, y al ltimo
dgito se le aade un cuarteto por la derecha con el signo (1100 para
+ y 1101 para -). Se completa a un nmero par de cuartetos,
aadiendo cuartetos 0000 por la izquierda si es necesario.
+2015
-2015

relleno relleno relleno dgito dgito dgito dgito signo


0000 0000 0000 0010 0000 0001 0101 1100
0000 0000 0000 0010 0000 0001 0101 1101

Nmeros enteros representados en decimal empaquetado en 32 bits.


El formato de representacin de nmeros enteros ms usado es el C-2, y el rango
de representacin depender del nmero de bytes utilizados, cuyas capacidades de
representacin se muestran en la Tabla 4.6.
Nmero de bytes
Valores posibles de
Menor entero
Mayor entero
representar
representable
representable
1
2
4
n

byte (8 bits)
bytes (16 bits)
bytes (32 bits)
bits

28 = 256
216 = 65536
232 = 4294967296
2n

-128
-32768
-2147483648
-2n-1

+127
+32767
+2147483647
-2n-1 - 1

Tabla 4.6. Capacidad de representacin del Complemento a 2

Cuando a una variable entera se le asigna en tiempo de ejecucin un valor que


queda fuera del rango permitido (situacin de overflow o valor excesivo), de ordinario
el programa no se interrumpe, por lo que puede producirse un error lgico en el
programa de consecuencias imprevisibles.

108

Programacin en C

4.3.2. Tipo real


Representa nmeros que tengan parte decimal (por ejemplo 150,25, que puede ser
el saldo de una cuenta en euros, o 1,85, que puede ser la talla de una persona en
metros) o nmeros sin decimales muy grandes, o muy pequeos si son negativos, que
no pueden ser representados en el rango de los nmeros enteros (por ejemplo 5974
x 1021, que es la masa de la tierra en Kg.).
El formato de representacin de los nmeros reales se basa en la notacin
exponencial de un nmero real (tambin llamada notacin cientfica o notacin en
coma o punto flotante), donde todo nmero N se puede expresar como N = M BC.
Donde:
M es la mantisa, es un nmero real.
B es la base. Cuando se trata de la base decimal o base 10 se
representa por la letra E mayscula e minscula.
C es la caracterstica o exponente. Es un nmero entero con su signo.
Base:
Signo de la mantisa:
Mantisa:
Caracterstica:

2 (no hace falta almacenarla)


1 bit (0 para +, 1 para -)
binario puro
exceso a 2n-1

Simple precision (32 bits)


Signo
Caracterstica
Mantisa

Doble precision (64 bits)

1 bit (posicin 32)


8 bits (posiciones 24 a 31)
23 bits (posiciones 1 a 23)

1 bit (posicin 64)


11 bits (posiciones 53 a 63)
52 bits (posiciones 1 a 52)

Figura 4.2. Norma IEEE 754


EJEMPLO
3443,46
-0,075
25000000

=
=
=

0,344346 x 104
-0,75 x 10-1
0,25 x 108

=
=
=

0,344346E4
-0,75E-1
0,25E8

La representacin interna segn la norma IEEE 7544 es la que se muestra en la


Figura 4.2.
4

Hasta la dcada de los 80 cada fabricante de ordenadores utilizaba un sistema propio para la
representacin de los nmeros reales. El IEEE 754 es un estndar de aritmtica en coma

Elementos bsicos de un lenguaje de programacin

109

4.3.3 Tipo carcter


El tipo carcter es el conjunto finito y ordenado de los caracteres que el ordenador
reconoce. Un dato tipo carcter contiene un nico carcter.
Un dato tipo carcter se expresa encerrando el carcter entre comillas dobles o
simples, dependiendo del lenguaje.

Apunte de C

En C los datos tipo carcter se expresan encerrando el carcter entre comillas


simples.
'a'
'H'
'1'
'8'
'+'
'('

La representacin interna depende del cdigo utilizado. Los cdigos ms


empleados son los que utilizan 8 bits para la representacin (en total 256 posibles
caracteres), como por ejemplo el cdigo ASCII (American Standard Code for
Information Interchange) y el cdigo EBCDIC (Extended Binary Code Decimal
Interchange Code).
Cualquiera de estos cdigos asigna un nmero entero entre 0 y 255 a cada
carcter representable en el ordenador.
Es posible hacer referencia a un carcter tambin por ese nmero.
El formato de representacin de caracteres ms usado durante mucho tiempo fue el
ASCII, aunque actualmente se utilizan otros sistemas de representacin que extienden
significativamente el cdigo ASCII, que puede verse en la Figura 4.3.

Figura 4.3. Tabla de cdigos ASCII utilizada para el sistema operativo MS DOS

Dentro de la tabla ASCII no todos los caracteres tienen el mismo tratamiento:


Del 0 al 31 y el 127 son los caracteres especiales de control.
Ejemplos de stos son los que determinan el salto de pgina, el
flotante que cubre tanto la representacin de los operandos como la forma de realizar las
operaciones.

110

Programacin en C

retorno de carro, salto de lnea... Algunos al ser impresos,


dependiendo del procesador o editor de texto, dan lugar a smbolos
grficos como caras, signos musicales...
Del 32 al 126 son caracteres imprimibles bien determinados. Se
encuentra el alfabeto bsico, nmeros, smbolos matemticos
bsicos...
Del 128 al 255 definen caracteres o smbolos grficos por parte de
los vendedores independientemente (OEM). El MS DOS los emplea
para dar soporte simblico a los caracteres particulares de los
diversos idiomas5.
Se puede garantizar que los caracteres del 0 al 127 son constantes en todos los
conjuntos ASCII. Se advierte que en la escritura de texto, algunos valores, tales como
10, 13, 26..., no sern presentados en pantalla, provocando acciones especiales como
retorno de carro, nueva lnea, fin de fichero...
Una pregunta muy habitual en las primeras sesiones de trabajo en Windows es:
Qu pasa con lo que he escrito? Si se abre con el editor de textos EDIT del intrprete
de rdenes no se leen bien las letras acentuadas ni las 'ees' que se hayan colocado
con el NOTEPAD (o bloc de notas) de Windows, y viceversa.
La respuesta es muy sencilla. El intrprete de rdenes basado en MS-DOS trabaja
con un conjunto ASCII denominado OEM. En l se fijan los caracteres determinados
para los cdigos 128 a 255. Incluso hay variaciones segn la pgina de cdigos que
se especifique en el sistema:
437: caracteres de EEUU grabada en la ROM.
850: caracteres de Europa Occidental
852: caracteres de Europa del Este (MS-DOS 5.0)
860: caracteres portugueses.
861: caracteres islandeses.
...
En rigor el conjunto ASCII-OEM no es estndar, aunque la extensin del sistema
operativo MS DOS haya hecho que haya sido uno de los ms utilizados. Esto supone
que pueda causar problemas porque Windows no trabaja con l (como tampoco lo
hace el System X en los ordenadores Apple, por citar otro caso).
El conjunto bsico de Windows se denomina ANSI y s es un estndar empleado
por mltiples plataformas. Es ste el que se debe tener como referencia a la hora de
programar en Windows.
Los conjuntos de caracteres OEM y ANSI disponen del mismo nmero de
caracteres, pero no coinciden los de cdigo superior a 127.
Es importante observar que la tabla ASCII asocia cdigos numricos consecutivos
a las letras maysculas y minsculas, ordenadas alfabticamente. Esto simplifica
ciertas operaciones de ordenacin alfabtica de nombres.
Igualmente, la diferencia numrica entre una letra minscula y su correspondiente
mayscula es siempre la misma: 32. Siempre, por tanto, se puede obtener a partir de

En este grupo se encuentran por ejemplo las vocales acentuadas, la letra para el castellano, o
la para el francs.

Elementos bsicos de un lenguaje de programacin

111

una letra mayscula su correspondiente minscula, o viceversa, sumando/restando a


su cdigo ASCII este valor.

Truco
En un editor o procesador de texto se puede obtener un carcter de la tabla ASCI,
utilizando el teclado numrico del ordenador, tecleando Alt+<cdigo ASCII>.
As, por ejemplo Alt 65 producir una A en pantalla.

4.3.4 Tipo cadena de caracteres6


Un dato tipo cadena de caracteres se expresa encerrando el conjunto de caracteres
entre comillas dobles o simples, dependiendo del lenguaje.
La longitud mxima permitida para una cadena de caracteres depende del lenguaje
de programacin. Por ejemplo, para Pascal son 255 caracteres, mientras que para
Cobol o C no hay limitacin.

Apunte de C

En C los datos tipo cadena de caracteres se expresan encerrando el conjunto de


caracteres entre comillas dobles.
"Calle del Candil 24"
"7845638K"
"Entrada de dato 1 incorrecta."

4.3.5. Tipo lgico


El tipo lgico se emplea para representar dos valores opuestos: verdadero falso.
Un dato lgico tan slo puede tomar uno de esos dos valores opuestos,
caracterstica que utilizan muchos lenguajes (aunque no todos, vase el caso de Java)
para representarlo internamente como un entero que puede tomar dos valores: 0
(falso) y 1 (verdadero).

Apunte de

El lenguaje C no soporta explcitamente el tipo de dato lgico. El concepto de


falso se toma del valor numrico cero y el de verdadero de cualquier valor
numrico distinto de cero.

4.4. Constantes y variables


4.4.1. Constantes
Representan en un programa datos cuyo valor no variar durante la ejecucin del
mismo. Otra definicin de constante puede ser aquel tipo de informacin numrica o
6

En algunos lenguajes como COBOL o Pascal se incluye como tipo de dato bsico el tipo
alfanumrico, que no es ms que una cadena de caracteres. En otros como C se considera este
tipo de dato como estructurado, al ser tratado como una tabla unidimensional de caracteres.

112

Programacin en C

alfanumrica que no puede cambiar ms que con una nueva compilacin del
programa.
Existen tres tipos de constantes: numricas, que sern valores numricos, enteros
o de coma flotante (reales), constantes carcter, que ser cualquier carcter
individual encerrado entre comillas7, y constantes cadenas de caracteres, que ser
cualquier conjunto de caracteres alfanumricos encerrados entre comillas 8.
Las constantes pueden usarse de dos formas:
Constantes literales. Escribiendo explcitamente el valor embebido
dentro de las propias sentencias del programa.
Ejemplo en pseudocdigo:
ptos_totales partidos_ganados * 3 + partidos_empatados * 1
reparto ganacias_totales / 6
precio_final precio + (precio * 6.0) / 100

3, 1, 6, 6.0 y 100 son constantes literales.


Constantes definidas o constantes con nombre. Al principio del
programa se utiliza un identificador para definir la constante en
memoria, asignndole un tipo y un valor que permanecer inalterable
durante todo el programa. Posteriormente se utiliza este identificador en
las sentencias del programa.
Ejemplo en pseudocdigo:
const entero PUNTOS_VICTORIA 3
const entero MIEMBROS_GRUPO 6
const real FACTOR_SUELDO 3
const entero PUNTOS_EMPATE 1
const real IVA 6
. . .
ptos_totales partidos_ganados * PUNTOS_VICTORIA +
partidos_empatados * PUNTOS_EMPATE
reparto ganacias_totales / MIEMBROS_GRUPO
precio_final precio + (precio * IVA) / 100

Esta segunda forma de utilizar las constantes tiene varias ventajas:


Facilita la legibilidad del programa. Es difcil de reconocer el significado
de una constante literal.
Facilita la modificacin del programa. Es suficiente con modificar el
valor de la constante en la definicin, sin necesidad de leer todo el
programa buscando el valor que se desea modificar y comprobando su
significado para si es el que se quiere cambiar.
EJEMPLO

Se muestran, en pseudocdigo, las dos formas de uso de constantes en un


programa.
Esta primera es utilizar constantes con nombre. Estas se declaran al
comienzo del programa indicando su nombre, tipo y valor.
const real
const real
7

ASIGNACION_PERSONAL 500000;
ASIGNACION_HIJO
100000;

Comillas dobles o simples, dependiendo del lenguaje. En particular en el lenguaje C sern


comillas simples.
8 Comillas dobles o simples, dependiendo del lenguaje. En particular en el lenguaje C sern
dobles comillas.

Elementos bsicos de un lenguaje de programacin

113

. . .
ingreso_imponible sueldo_bruto
- ASIGNACION_PERSONAL
- (numero_hijos * ASIGNACION_HIJO);

Esta segunda forma incluye las constantes como literales numricos dentro
del propio cdigo del programa.
ingreso_imponible sueldo_bruto - 500000 (numero_hijos * 100000);

Se advierte claramente que una de las ventajas de las constantes con nombre frente
a las constantes literales es en el caso de las primeras, las sentencias de cdigo quedan
autodocumentadas.

4.4.2. Variables
Representacin en un programa de un dato cuyo valor puede variar durante la
ejecucin del mismo, bien porque es un dato de entrada, un dato intermedio calculado
o un dato de salida.
Las variables deben definirse al comienzo del programa, indicando el nombre
que el programador le asigna (identificador) y su tipo. As, un ejemplo en
pseudocdigo puede ser:
real precio, precio_final
entero puntos_totales

Definir una variable significa reservar el suficiente espacio en memoria para


almacenar un valor del tipo declarado. Solamente con la definicin no se tiene el valor
deseado en memoria, sino que la variable tomar el valor que tengan las posiciones de
memoria en el momento de ser reservada, es decir, un valor impredecible y aleatorio
(basura).
La forma de asignar el valor deseado a una variable dentro del programa puede
hacerse de las siguientes formas:
Por iniciacin en su definicin.
entero puntos_totales 0

Por iniciacin en el cuerpo del programa.


puntos_totales 10

Por lectura desde un perifrico de entrada (teclado, disco...).


leer (precio)

Por asignacin del resultado de un clculo.


precio_final precio + (precio * IVA) / 100

Antes de utilizar una variable para realizar algn clculo (por ejemplo precio en
la anterior expresin), deber habrsele asignado un valor vlido.
Una variable slo podr tomar un valor dentro del rango de la definicin de su tipo.
Si se intenta asignar un valor de otro tipo pueden producirse truncamientos o
desbordamientos en el caso de variables numricas, o incluso un error de compilacin
o ejecucin, en el caso de variables de otros tipos9.

Esto depender del compilador. Incluso puede que el compilador no avise y simplemente el
programa produzca resultados incorrectos.

114

Programacin en C

Las variables que alberguen los resultados para la salida del programa, debern
mostrarse en pantalla, grabar su valor en disco, llevarlas a una impresora...

Apunte de

escribir (precio_final)

Aunque no es obligatorio, en C los nombres de las constantes suelen escribirse en


maysculas y el nombre de las variables en minsculas.

4.5. Tipos de datos bsicos en C


Los tipos de datos que contempla el lenguaje C se pueden clasificar como se muestra
en la Tabla 4.7.
En C no existe el tipo de dato lgico (llamado tambin boolean en algunos
lenguajes). El concepto de falso se toma del valor numrico cero y el de verdadero de
cualquier valor numrico distinto de cero.
Tipos de datos
Nombre en C
int
Entero
float, double
Numricos
Real
Bsicos
char
Carcter
Derivado

Estticos
Estructurados
Internos
Dinmicos

Estructurados
Externos
Definidos por el usuario

Vaco
Puntero
Heterogneos Registro
Enumeracin
Homogneos Tabla
Cadena caracteres
Lista
Lineales
Pila
Cola
No lineales
rbol
Grafo
Fichero

void

struct, union
enum

FILE
typedef

Tabla 4.7. Clasificacin de los tipos de datos del lenguaje C

Se describen a continuacin los tipos de datos bsicos de C (Tabla 4.8). El resto de


los tipos sern descritos en el momento en que surja la necesidad de utilizarlos.

Elementos bsicos de un lenguaje de programacin

Identificador
de tipo
void

Significado

Tamao
(bytes)

Rango de valores
(cdigo de 16 bits)

char

nulo o ninguno
carcter

0
1

-128 a 127

int

entero

-32.768 a 32.767

float

real
(simple precisin)
real doble
(doble precisin)

1.0e+38 a 1.0e-38
precisin ~ 7 dgitos
1.0e+308 a 1.0e-308
precisin ~15 dgitos

double

115

Constantes
relacionadas
CHAR_MIN
CHAR_MAX
INT_MIN
INT_MAX
FLT_MIN
FLT_MAX
DBL_MIN
DBL_MAX

Tabla 4.8. Tipos de datos bsicos del lenguaje C

Algunos tipos admiten los modificadores de tipo, lo que hace variar tanto su
tamao del almacenamiento en memoria como el rango de valores representables.
Los modificadores soportados en el lenguaje C son:
short
unsigned

long
signed

El modificador signed no se suele utilizar porque por defecto es el que se asume


si no se indica unsigned. En los casos concretos de short int y de long
int pueden abreviarse respectivamente por short y long respectivamente.
En la Tabla 4.9 se recogen los tipos que admiten modificadores, junto con sus
caractersticas, en el lenguaje C.
Identificador de
Significado
Tamao
Rango de valores
Constantes
tipo
(bytes)
(cdigo de 16 bits) relacionadas
unsigned
char
short int

carcter

0 a 255

UCHAR_MAX

entero corto

-32.768 a 32.767

long int

entero largo

unsigned int

entero sin signo


entero corto sin signo

2
2

-2.147.483.648
a 2.147.483.647
0 a 65.535
0 a 65.535

SHRT_MIN
SHRT_MAX
LONG_MIN
LONG_MAX

entero largo sin signo

0 a 4.294.967.295

real doble largo


(doble precisin largo)

10

unsigned
short int
unsigned
long int
long double

UINT_MAX
USHRT_MAX
ULONG_MAX

10e+4932 a 10e-4932 LDBL_MIN


precisin ~15 dgitos LDBL_MAX

Tabla 4.9. Tipos con modificadores en el lenguaje C

La Tabla 4.10 muestra la relacin entre los tipos short, int y long en el
lenguaje C. De todas formas, estas especificaciones pueden variar de un compilador
de C a otro, pero lo que garantiza el ANSI C es que el rango de int no es nunca
menor que el de short, ni mayor que el de long.

116

Programacin en C

Ordenadores con
longitud de palabra
2 bytes (16 bits)

Ocupacin y rango
tipo short
2 bytes
-32.768 a 32.767

Ocupacin y rango tipo


Ocupacin y
int
rango tipo long
2 bytes
4 bytes
-32.768 a 32.767
-2.147.483.648 a
2.147.483.647
4 bytes (32 bits)
2 bytes
4 bytes
4 bytes
-32.768 a 32.767
-2.147.483.648 a
-2.147.483.648 a
2.147.483.647
2.147.483.647
Tabla 4.10. Relacin entre los tipos short, int y long en el lenguaje C

4.5.1. Tipo entero (int)


Representa nmeros enteros con signo. Para definir variables en C se antepone la
palabra reservada del tipo al identificador de la variable, int en este caso concreto.
En el caso de las constantes se antepone a todo ello la palabra reservada const.
EJEMPLOS
int num, conmutador, indicador;
int contador = 0;
const int FACTOR_BLOQUE = 512;

Las constantes literales de tipo int pueden escribirse en tres sistemas numricos:
Decimal (base 10), utilizando los dgitos 0 1 2 3 4 5 6 7 8 9.
Octal (base 8), utilizando los dgitos 0 1 2 3 4 5 6 7.
Hexadecimal (base 16), utilizando los dgitos 0 1 2 3 4 5 6 7 8 9 y las
letras A B C D E F.
Las constantes literales de tipo int expresadas en notacin decimal tienen que
cumplir las siguientes restricciones:
Puede estar formada por una combinacin de dgitos del 0 al 9.
Si tiene dos o ms dgitos, el primero nunca puede ser cero.
Si es negativo debe incluir el signo menos (-) por delante.
El rango de valores admitidos para representaciones de16 bits es
desde -32768 a 32767.
EJEMPLOS

Ejemplos de constantes literales tipo int correctas expresadas en decimal:


0
1
743
5280 -15450
9999
Ejemplos de constantes literales tipo int incorrectas expresadas en decimal:
12,245
carcter ilegal (,).
0900
el primer dgito no puede ser cero.
42345
fuera de rango en 16 bits (vlida para 32 bits).
Las constantes literales de tipo int expresadas en notacin octal tienen que
cumplir las siguientes restricciones:
Puede estar formada por una combinacin de dgitos del 0 al 7.
El primer dgito debe ser obligatoriamente un 0.
El rango de valores admitidos para representaciones 16 bits es de 0 a
077777 (0 a 32767) y de 0100000 a 0177777 (-32768 a -1).

Elementos bsicos de un lenguaje de programacin

117

EJEMPLOS

Ejemplos de constantes literales tipo int correctas expresadas en octal:


0
0

01
1

010
8

0743
483

07777
4095

Valor en decimal
Ejemplos de constantes literales tipo int incorrectas expresadas en octal:
743
no comienza por 0.
05684
dgito ilegal (8).
0777.77
carcter ilegal (.).
0277777
98303, fuera de rango en 16 bits (vlida para 32 bits).
Las constantes literales de tipo int expresadas en notacin hexadecimal tienen
que cumplir las siguientes restricciones:
Pueden estar formada por una combinacin de dgitos del 0 al 9 y letras
de la A a la F, tanto maysculas como minsculas.
Deben comenzar por 0X 0x.
El rango de valores admitidos para 16 bits es de 0 a 0X7FFF (0 a
32767) y de 0X8000 a 0XFFFF (-32768 a -1).
EJEMPLOS

Ejemplos de constantes literales tipo int correctas expresadas en hexadecimal:


0X

0x1

0x7fff

0X3A98

Valor en decimal
0
1
32767
5000
Ejemplos de constantes literales tipo int incorrectas expresadas en hexadecimal:
0x12.34
carcter ilegal (.).
0BE3
no comienza por 0X.
0x12AG
carcter ilegal (G).
0XFFFFF
1048575, fuera de rango en 16 bits (vlida para 32 bits).

4.5.2. Tipo entero corto (short int o simplemente short)


Se emplea para representar nmero enteros pequeos con signo, de forma que
normalmente ocupan menos espacio en memoria que un int. El rango de valores
admitidos para 16 bits es desde 32768 a +32767.
Para definir variables se antepone la palabra reservada short al identificador de
la variable. En el caso de las constantes se antepone la palabra reservada const a
todo ello.
EJEMPLOS
short numero, conmutador_b = 1;
const short NUM_P = 127;

No existen constantes literales de tipo short int de forma explcita. Toda


constante int que no sobrepase el rango del tipo short int podr utilizarse como
tal.

4.5.3. Tipo entero largo (long int o simplemente long)


Para representar nmeros enteros mayores que los permitidos por un tipo int.

118

Programacin en C

Para definir variables se antepone la palabra reservada long al identificador de la


variable. En el caso de las constantes se antepone la palabra reservada const a todo
ello.
EJEMPLOS
long numero;
const long NUM_G = 127L;

Las constantes literales de tipo long se expresan aadiendo una letra L mayscula
o minscula al final de la misma. Los rangos de valores admitidos para
representaciones de 16 bits son:
Constantes enteras largas decimales: 2147483648L a 1L y 0 a
2147483647L.
Constantes enteras largas octales: 0 a 017777777777L y
080000000000L a 037777777777L.
Constantes enteras largas hexadecimales: 0 a 0X7FFFFFFFL y
0X80000000L a 0XFFFFFFFFL.

4.5.4. Tipo entero sin signo (unsigned int)


Se emplea para representar nmeros enteros sin signo. Para definir variables se
anteponen las palabras reservadas unsigned int al identificador de la variable.
En el caso de las constantes se antepone la palabra reservada const a todo ello.
EJEMPLOS
unsigned int num1, num2;
const unsigned int KC = 128U;

Las constantes literales de tipo unsigned int tienen que cumplir las siguientes
restricciones:
Se identifican aadiendo una letra U mayscula o minscula al final de
la misma.
Sus valores no pueden ser negativos.
Su valor mximo es aproximadamente el doble que una constante entera
con signo.
Los rangos de valores admitidos para representaciones de 16 bits son:
o Constantes enteras sin signo decimales: 0 a 65535U.
o Constantes enteras sin signo octales: 0 a 0177777U.
o Constantes enteras sin signo hexadecimales: 0 a 0XFFFFU.

4.5.5. Tipo entero corto sin signo (unsigned short)


Se emplea para representar nmeros enteros cortos sin signo. Para definir variables se
anteponen las palabras reservadas unsigned short al identificador de la
variable. En el caso de las constantes se antepone la palabra reservada const a todo
ello.

Elementos bsicos de un lenguaje de programacin

119

EJEMPLOS
unsigned short num1, num2;
const unsigned short KB = 128U;

No existen constantes de tipo unsigned short de forma explcita. Toda


constante unsigned int que no sobrepase el rango del tipo unsigned short
podr utilizarse como tal.

4.5.6. Tipo entero largo sin signo (unsigned long)


Se emplea para representar nmero enteros largos sin signo. Para definir variables se
anteponen las palabras reservadas unsigned long al identificador de la variable.
En el caso de las constantes se antepone la palabra reservada const a todo ello.
EJEMPLOS
unsigned long num1, num2;
const unsigned long LC = 128UL;

Las constantes literales de tipo unsigned long tienen que cumplir las
siguientes restricciones:
Se identifican aadiendo las letras UL maysculas o minsculas al final
de la misma.
Sus valores no pueden ser negativos.
Su valor mximo es aproximadamente el doble que una constante entera
larga con signo.
Los rangos de valores admitidos para representaciones de 16 bits son:
o Constantes enteras largas sin signo decimales: 0 a
4294967295UL.
o Constantes enteras largas sin signo octales: 0 a
037777777777UL.
o Constantes enteras largas sin signo hexadecimales: 0 a
0XFFFFFFFFUL.
EJEMPLO

Distintos tipos de constantes enteras (algunas de ellas incorrectas) en cdigo


de 16 bits:
5000U
Entero sin signo en decimal (ocupa 2 bytes).
5000L
Entero largo en decimal (ocupa 4 bytes).
123456789L Entero largo en decimal (ocupa 4 bytes).
123456789UL Entero largo sin signo en decimal (ocupa 4 bytes).
75678U
Incorrecto. Valor fuera de rango.
75678UL
Entero largo sin signo en decimal (ocupa 4 bytes).
42596
Incorrecto. Valor fuera de rango.
42596U
Entero sin signo en decimal (ocupa 2 bytes).
07756U
Entero sin signo en octal (ocupa 2 bytes).
0XFFFFFUL
Entero largo sin signo en hexadecimal (ocupa 4 bytes).

120

Programacin en C

4.5.7. Tipos reales (float, double, long double)


Se emplean para representar nmero reales (con decimales).
EJEMPLO

Ejemplos de constantes literales reales correctas, la notacin E+n es


equivalente a x 10n, y E-n a x 10-n:
1.0
0.

0.2

1.66657E+3

2E-2

0.006e-4

0.00074 .1253

Ejemplos de constantes literales reales incorrectas:


1
debe contener un punto decimal y/o un exponente.
1,000
carcter ilegal (,).
2e+10.3
el exponente debe ser entero.
Si existe exponente, su efecto es desplazar el punto decimal tantas posiciones a la
derecha como indica el exponente. Si el exponente es negativo el desplazamiento es
hacia la izquierda.
Si el nmero no incluye punto decimal, se supone que ste se encuentra a la
derecha del ltimo dgito.
EJEMPLO

La cantidad 3x105 puede expresarse de todas las siguientes formas.


300000.
3E5
.3E6
0.3e630E4
3000000.0E-1

3E+5
3e+5
3.0e5
30.0E+4
300E+3 0.003E+8

Para definir variables reales se antepone la palabra reservada adecuada (float,


double o long double) al identificador de la variable. En el caso de las
constantes se antepone la palabra reservada const a todo ello.
EJEMPLO
float num1;
double num2;
long double num3;
const float N1 = 1.234F;
const double N2 = 123E3;
const long double N3 = 12E-2L

Las constantes literales reales tienen que cumplir las siguientes restricciones:
En cdigo de 16 bits, las constantes reales (en coma flotante) se
representan en C como cantidades de doble precisin (tipo
double, 8 bytes).
Para representar una constante en coma flotante en simple precisin
(tipo float, 4 bytes), se aade una letra F mayscula o minscula
al final de la constante.
Para representar una constante en coma flotante larga (tipo long
double, 10 bytes), se aade una letra L mayscula o minscula al
final de la constante.
EJEMPLO

Distintos tipos de constantes literales en coma flotante:


3E+5
constante double (ocupa 8 bytes en memoria).

Elementos bsicos de un lenguaje de programacin

121

constante float (ocupa 4 bytes en memoria).


constante long double (ocupa 10 bytes en memoria).

3E+5F
3E+5L

4.5.8. Tipo carcter (char, unsigned char)


Se emplean para representar un carcter perteneciente a un determinado cdigo
utilizado por el ordenador (normalmente el ASCII en 8 bits).
Para definir variables de tipo carcter se antepone la palabra reservada adecuada
(char o unsigned char) al identificador de la variable. En el caso de las
constantes se antepone la palabra reservada const a todo ello.
EJEMPLO
char letra;
unsigned char respuesta;
const char LETRA2 = 'A';
const unsigned char LT1 = '}';

Para el uso de las constantes literales de tipo carcter es conveniente conocer los
siguientes hechos:
Una constante tipo char o unsigned char se representar como un
solo carcter encerrado entre comillas simples.
'A'

'3'

'c'

'?'

' ' (espacio en blanco)

Internamente el tipo char almacena en 8 bits el nmero entero, que se


corresponde con el cdigo utilizado (tabla ASCII), mientras que
externamente se puede mostrar como un carcter.
El tipo unsigned char expresa que el valor almacenado es un
nmero sin signo. Ser un nmero con signo si el tipo es char (ver
Tabla 4.11).
Carcter
Combinacin de Valor numrico como Valor numrico
unsigned char
bits
como char
\0
A
B

00000000
0
. . .
. . .
01000001
65
01000010
66
. . .
. . .
01111111
127
10000000
128
10000001
129
. . .
. . .
11111110
254
11111111
255
Tabla 4.11. Tipos carcter con y sin signo

0
65
66
127
-128
-127
-2
-1

Truco
Con las constantes carcter se puede realizar operaciones aritmticas, pues C
internamente operar con el cdigo numrico correspondiente a dicho carcter
(ver tabla ASCII - Figura 4.3).

122

Programacin en C

EJEMPLOS

'C'+'4' se corresponder con 67+52=119 que es el carcter


'C'+4 se corresponder con 67+4=71 que es el carcter

'w'
'G'

Las secuencias de escape se pueden representar como constantes literales de tipo


carcter. El objetivo de las secuencia de escape es la representacin de caracteres no
imprimibles de la tabla ASCII (de 0 al 31 y el 127), as como la barra hacia atrs (\),
la comilla simple (') y la comilla doble (").
Las constantes literales que representan secuencias de escape cumplen las
siguientes restricciones:
Comienza por una barra hacia atrs y va seguida de uno o varios
caracteres.
Es un slo carcter de la tabla ASCII, aunque la forma de representarla
sea con dos o ms caracteres.
Un carcter ASCII cualquiera puede expresarse como una secuencia de
escape utilizando el valor octal o el valor hexadecimal de su cdigo.
En la Tabla 4.12 se muestran las principales secuencias de escape y su
representacin como constantes literales de caracteres en el lenguaje C.
Carcter
Nulo
Sonido (pitido)
Retroceso
Tabulador horizontal
Tabulador vertical
Nueva lnea (avance de lnea y principio de la siguiente)
Avance de pgina
Retroceso de carro
Comilla doble (")
Comilla simple (')
Signo de interrogacin (?)
Barra hacia atrs (\)
Carcter ASCII cuyo cdigo es el valor hexadecimal dd
Carcter ASCII cuyo cdigo es el valor octal ooo
Tabla 4.12. Principales secuencias de escape

Secuencia
de escape
\0
\a
\b
\t
\v
\n
\f
\r
\"
\'
\?
\\
\xdd
\ooo

Valor
ASCII
0
7
8
9
11
10
12
13
34
39
63
92

EJEMPLOS

La letra A, que es el cdigo 65 en la tabla ASCII, puede expresarse como:


'A'
constante carcter.
'\101' secuencia de escape en octal (65 en octal es 101).
'\x41' secuencia de escape en hexadecimal (65 en hexadecimal es 41).
(la x siempre en minscula).
El cdigo 9 de la tabla ASCII (tabulador) no tiene carcter grfico que lo
represente. Se debe representar por su secuencia de escape, en alguno de sus
formatos:
'\t'
secuencia de escape tabulador.
'\011' secuencia de escape en octal (9 en octal es 11).
(La secuencia en octal siempre en tres dgitos).

Elementos bsicos de un lenguaje de programacin

123

'\x09' secuencia de escape en hexadecimal (9 en hexadecimal es 9).

(La secuencia en hexadecimal siempre x ms dos caracteres).


Si se desea utilizar la comilla simple como constante carcter, no se puede encerrar
entre comillas simples (''') para representarlo. La forma de hacerlo es utilizando su
secuencia de escape, en alguno de sus formatos, esto es:
'\''
secuencia de escape comilla simple.
'\047' secuencia de escape en octal (39 en octal es 47).
'\x27' secuencia de escape en hexadecimal (39 en hexadecimal es 27).

4.5.9. Tipo de dato vaco (void)


Se define con la palabra reservada void que significa que el dato es nulo, por lo que
no ocupa espacio en memoria y, por tanto, su tamao en bytes es 0.
Se usa principalmente para:
Especificar que una funcin no retorna de forma explcita ningn valor.
Especificar que una funcin no utiliza parmetros.
Definir un puntero genrico (no se conoce el tipo de dato de la variable
que hay que apuntar).
EJEMPLOS
void main (void)
int verificar_error (void)
void escribir_puntos (int p)
void *apunt;

4.6. Tipo cadena de caracteres en C


Una cadena de caracteres es nmero de caracteres consecutivos (incluso ninguno)
encerrado entre unos delimitadores determinados, que en el lenguaje C (as como en
otros muchos lenguajes) son las comillas dobles.
EJEMPLOS
"verde"
"La respuesta correcta es:"
"923-123456"
"Pablo Milans"
"" (cadena de caracteres vaca o nula)
"Lnea 1\nLnea 2\nLnea 3"

Puede observarse que cualquier carcter de la tabla ASCII puede pertenecer a la


cadena de caracteres (espacio, caracteres no imprimibles representados por su
correspondiente secuencia de escape...) y, adems, dos caracteres comillas dobles
seguidos, sin ningn carcter entre ellos estn representando una cadena de caracteres
vaca (nula).
Una cadena de caracteres se almacena en memoria carcter a carcter
consecutivamente. Cada carcter se almacena en un byte que recoge su cdigo ASCII,
adems se aade un carcter nulo (cdigo ASCII 0 \0) al final de la misma. Este
carcter no es necesario representarlo como parte de la cadena, y no aparece cuando

124

Programacin en C

se visualiza sta en pantalla, aunque s se almacena en memoria como ltimo carcter


de la misma.
EJEMPLO

La constante cadena de caracteres MI CASA ocupa en memoria 8 bytes,


los 7 caracteres de la cadena ms el carcter nulo final. Se almacena en
memoria de la siguiente forma:
Posicin de memoria
Carcter
Valor ASCII
01001101
01001001
00100000
01000011
01000001
01010011
01000001
00000000

'M'
'I'
' '
'C'
'A'
'S'
'A'
'\0'

77
73
32
67
65
83
65
0

Para definir variables de tipo cadena, stas se definen como vectores de caracteres,
esto es, anteponiendo la palabra reservada char al identificador de la variable, y
despus entre corchetes la longitud mxima de la cadena. En el caso de las constantes
se antepone la palabra reservada const a todo ello.
La definicin de variables de tipo cadena tiene algunas excepciones a la regla
general que se muestran y se explican en los siguientes ejemplos.
EJEMPLOS
char mensaje[30];

El carcter nulo final con que termina toda cadena de caracteres no es


necesario representarlo como parte de la cadena, y no aparece cuando se
visualiza sta en pantalla, aunque s es necesario reservar un carcter para l.
Por ello esta sentencia est definiendo una variable cadena de caracteres de
longitud 30, es decir, puede albergar una cadena de entre 0 y 29 caracteres,
ms el carcter nulo final.
char saludo[80] = "Bienvenido al programa";

En este otro caso se est definiendo una cadena para albergar hasta 79
caracteres ms el carcter nulo final. Adems de definirla se inicia a un valor.
Con la iniciacin slo se emplearn 23 de los 80 caracteres de la cadena
saludo.
const char CAD[20]= "Fin de proceso OK";
En este ejemplo se est definiendo una constante cadena para albergar hasta
20 caracteres ms el carcter nulo final. Slo se emplean 18 de los 20
caracteres de la cadena CAD.
const char LETRAS[] = "CALIFORNIA";
En este caso puede observarse que en la definicin de esta constante cadena
no se ha incluido entre los corchetes el nmero de elementos (caracteres) de
dicha cadena. No es necesario porque al existir a la derecha la asignacin del
valor inicial para la cadena, el compilador calcula y reserva automticamente

Elementos bsicos de un lenguaje de programacin

125

el espacio necesario para guardar en dicha cadena el valor con que se inicia,
en este caso 11 bytes (10 caracteres ms el carcter nulo al final).

Buen estilo de programacin


Si se definen variables o constantes cadenas que se van a iniciar, siempre es ms
cmodo y seguro no especificar el tamao y que el compilador lo calcule
automticamente.
Puede accederse a cada uno de los caracteres individuales de la cadena
especificando el nombre de la cadena y entre corchetes un ndice que indique el
nmero de carcter. Este ndice comienza en 0 para el primer carcter y termina en
n-1 para el ensimo carcter de la cadena.
En el caso de la constante cadena LETRAS, anteriormente definida en uno de los
ejemplos, se podr acceder a cada uno de sus caracteres componentes de la siguiente
manera:
Nmero de elemento

ndice

Elemento
de la cadena

1
2
3
4
5
6
7
8
9
10
11

0
1
2
3
4
5
6
7
8
9
10

LETRAS[0]
LETRAS[1]
LETRAS[2]
LETRAS[3]
LETRAS[4]
LETRAS[5]
LETRAS[6]
LETRAS[7]
LETRAS[8]
LETRAS[9]
LETRAS[10]

'C'
'A'
'L'
'I'
'F'
'O'
'R'
'N'
'I'
'A'
'\0'

No es lo mismo la constante carcter 'M' que la constante cadena de caracteres


"M". De hecho sta ltima constante de cadena ocupa 2 bytes en memoria (recordar
el carcter nulo al final).
La constante 'M' tiene un valor entero asociado, el 77. La constante cadena de
caracteres "M", aunque conste de un nico carcter, no tiene un valor entero
equivalente, ya que de hecho consta de dos caracteres.
Carcter

Valor ASCII

11010100

'M'

77

carcter 'M'

11010100
00000000

'M'
'\0'

77
0

cadena "M"

126

Programacin en C

Buen estilo de programacin


Normalmente cada constante o variable se define en una nica lnea,
acompaando a cada definicin de un comentario que explique el propsito o uso
que se le dar a la variable dentro del programa.
int dias;
int dias_demora;
float saldo;
float saldo_inicial;
float saldo_final;
char mensaje[80];

/*
/*
/*
/*
/*
/*

Var.auxiliar clculo periodos a liquidar


Das en que no se cobrarn intereses
Saldo actual de la cuenta
Saldo inicial periodo especificado
Saldo final incluyendo intereses devengados
Mensaje por pantalla

*/
*/
*/
*/
*/
*/

4.7. Constantes simblicas en C


Es un nombre que sustituye a una secuencia de caracteres, de forma que la secuencia
de caracteres puede representar cualquier tipo de constante.
Permite que aparezca un nombre en lugar de una constante literal en una
instruccin del programa, realizando el preprocesador el reemplazo de cada aparicin
del nombre por la correspondiente secuencia de caracteres, antes de comenzar la
compilacin.
El preprocesador es un componente caracterstico del lenguaje C que no existe en
otros lenguajes de programacin. El preprocesador acta sobre el programa fuente
antes de que empiece la compilacin propiamente dicha, para realizar ciertas
operaciones. Entre ellas est, por ejemplo, el sustituir cada aparicin del nombre de
las constantes simblicas dentro del cdigo fuente del programa por el valor definido
en la cabecera de ste. El preprocesador realiza otras muchas funciones que se irn
viendo a medida que se vaya profundizando sobre el lenguaje C. Lo importante es
recordar que acta siempre por delante del compilador (de ah su nombre)
facilitando la tarea de ste y la del programador.
La definicin de las constantes simblicas se hace normalmente al principio del
programa, con la directiva #define.
#define nombre
texto
Donde:
nombre representa un nombre simblico, que se suele escribir en
maysculas
texto representa la secuencia de caracteres asociada al nombre
simblico.
En el momento de comenzar la compilacin, el preprocesador recorre el cdigo
fuente en una primera pasada, y en todos los sitios donde encuentre nombre,
sustituir ste por texto, para que despus se pueda compilar el programa.
Puede observarse que texto no termina en punto y coma, ya que no es una
verdadera instruccin de C, sino que es una directiva para el compilador. Si texto
terminase en punto y coma, este punto y coma se tratara como si fuese parte de la
secuencia de caracteres constante.
Las constantes simblicas contribuyen al desarrollo de programas claros y
ordenados. Es una forma fcil y cmoda de parametrizar un programa, es decir, es

Elementos bsicos de un lenguaje de programacin

127

mucho ms fcil cambiar el valor de una constante simblica y volver a compilar que
repasarse un programa entero buscando donde aparece una secuencia de caracteres
concreta que hay que modificar.
Una diferencia entre las constantes simblicas y las constantes declaradas es que en
las primeras no existe reserva de posiciones de memoria.
EJEMPLO

Un programa contiene las siguientes definiciones de constantes simblicas:


#define
#define
#define
#define

INTERES 0.23
PI 3.141593
FALSE
1
NOMBRE "Juan Antonio"

Supngase que el programa contiene la siguiente instruccin:

area = PI * radio * radio;

Durante el proceso de compilacin, cada aparicin de una constante


simblica se sustituye por su secuencia de caracteres correspondiente, por lo
que la anterior instruccin, el compilador la transformar, antes de
compilarla, en:
area = 3.141593 * radio * radio;

Si se hubiera definido de forma errnea la constante simblica como:


#define PI 3.141593;

La instruccin del clculo del rea se transformara entonces en:


area = 3.141593; * radio * radio;

Lo que provocar un error de compilacin por ser una sentencia claramente


incorrecta en C.
EJEMPLO

La sustitucin de una constante simblica se efecta en cualquier parte del


programa, excepto cuando el nombre de la constante se encuentra dentro de
una cadena de caracteres.
#define CONSTANTE 0.23
. . .
printf("CONSTANTE = %f", c);

En este caso la instruccin printf() no se ver afectada por la definicin


de la constante simblica, ya que el trmino "CONSTANTE = %f" es
una constante cadena de caracteres.
Sin embargo, si se hubiera escrito la sentencia printf() de la forma:
printf("CONSTANTE = %f", CONSTANTE);

El compilador transformara durante el proceso de compilacin la anterior


sentencia en:
printf("CONSTANTE = %f", 0.23);

A continuacin se presenta un esquema de utilizacin de los tres tipos de


constantes posibles en el lenguaje C.
CONSTANTES DECLARADAS
#include <stdio.h>
void main(void)
const int
const float
const float

{
FACTOR_IVA = 12;
ASIGNACION_PERSONAL = 500000;
ASIGNACION_HIJO
= 100000;

128

Programacin en C
. . .
ingreso_imponible = sueldo_bruto
- ASIGNACION_PERSONAL
- (numero_hijos * ASIGNACION_HIJO);
iva = ( precio / 100 ) * FACTOR_IVA;
. . .
}

CONSTANTES LITERALES
#include <stdio.h>
void main(void) {
. . .
ingreso_imponible = sueldo_bruto
- 500000
- (numero_hijos * 100000);
iva = ( precio / 100 ) * 12;
. . .
}

CONSTANTES SIMBOLICAS (#define)


#include <stdio.h>
#define FACTOR_IVA 12
#define ASIGNACION_PERSONAL
#define ASIGNACION_HIJO

500000
100000

void main(void) {
. . .
ingreso_imponible = sueldo_bruto
- ASIGNACION_PERSONAL
- (numero_hijos * ASIGNACION_HIJO);
iva = ( precio / 100 ) * FACTOR_IVA;
. . .
}

4.8. Comentarios en el lenguaje C


Todos los lenguajes de programacin permiten que el programador introduzca
comentarios en los ficheros fuente que contienen el cdigo de su programa. La misin
de los comentarios es servir de explicacin o de aclaracin sobre cmo est hecho el
programa, de forma que pueda ser entendido por una persona diferente (o por el
propio programador algn tiempo despus). El compilador, o el intrprete segn el
caso, ignora por completo los comentarios.
En el lenguaje C, los caracteres (/*) se emplean para iniciar un comentario
introducido entre el cdigo del programa; el comentario termina con los caracteres
(*/). No se puede introducir un comentario dentro de otro. Todo texto introducido
entre los smbolos de comienzo (/*) y final (*/) de comentario son siempre
ignorados por el compilador.

Elementos bsicos de un lenguaje de programacin

EJEMPLO
valor_1 = valor_2;

129

/* Se iguala en este momento


el valor_1 a valor_2 para poder
conservar el valor de este */

4.9. Estructura de un programa en C


Un programa escrito en el lenguaje de programacin C siempre est formado por una
o ms funciones, una de las cuales, obligatoriamente, debe ser la que representa al
programa principal y se designa por la palabra reservada main.
De forma general (ya se volver sobre el tema ms adelante) una funcin es un
grupo de instrucciones que realizan una o ms acciones.
Adems, un programa C contiene una serie de directivas de inclusin #include
que permiten aadir en el mismo archivos de cabecera que a su vez constarn de
funciones y datos predefinidos en su interior.
El esqueleto de programa C ms sencillo es el que slo consta de la funcin main,
como se muestra a continuacin.
#include <archivo.h>
Archivo de cabecera
#define ...

Macros del procesador

int main ( )
{
...
}

Cabecera de la funcin
Nombre de la funcin

Sentencias
La funcin main() representa el punto de entrada al programa, siendo su
estructura es:
int main() {
/* Sentencias */
}
El conjunto de sentencias incluidas entre llaves se denomina bloque. Un programa
slo puede tener una nica funcin main().
A continuacin, se presenta el que suele ser el primer programa que se hace en
cualquier lenguaje de programacin: el hola mundo.
/* Programa HolaMundo.c */
#include <stdio.h> /* Archivo cabecera para funciones de E/S bsica*/
int main () {
/* Funcin main */
printf(HOLA MUNDO); /* Saca por pantalla el mensaje */
return 1;
}

4.10. Cuestiones y ejercicios


1.

Qu se entiende por palabra reservada en un lenguaje de programacin?


Enumera algunas palabras reservadas del lenguaje C.

130

2.
3.

4.

Programacin en C

Cules son las reglas bsicas a tener en cuenta a la hora de crear el


programador sus propios identificadores?
Cules de los siguientes identificadores son vlidos en C, y cules no? Para
los no validos indica por qu. Realizar un pequeo programa definiendo
variables con estos nombres para ver cul da por vlido el compilador y cul
no.
CincuentaYCinco
77j
variable-contador
primera

6.

program
valor?
variable

IMPUESTOS

impuestos

En definitiva, es el lenguaje C sensible a las maysculas y a las minsculas?


Qu es una variable en un programa? Explica qu significa declarar una
variable o una constante en un programa. Por qu es necesario declarar las
variables y constantes en un programa?
Indicar que tipo de constante es y cunto ocupar en memoria. Si alguna de
ellas es incorrecta, explicar por qu:
12,57
0743
0XFFFF
0XfffffUL

7.

main
$volcado
j77

Dados los siguientes identificadores en C, representan a la misma variable o


a variables distintas?
Impuestos

5.

si
Nombre_fichero
InDiCe
_impuesto

0758
5000L
42345
777777L

75678u
2.E-01L
4.5e4F
3E5

0777777L
0x3A
12458698UL

0B12
010
077.77

Definir la cantidad 4589 como constante:


entera
entera sin signo
entera larga
entera larga sin signo
entera en octal
entera larga sin signo en hexadecimal
entera sin signo en hexadecimal
entera larga en octal
real
real doble
real doble larga

8.
9.
10.
11.

Cmo se almacena en memoria un tipo char? Cuntos valores posibles


puede tener el tipo char?
Principalmente, para qu se usa una secuencia de escape?
Representa el carcter 'Q' y el tabulador horizontal a travs de sus
secuencias de escape, expresadas de todas las formas posibles.
Escribe las sentencias printf()orrespondientes para obtener en salida los
siguientes literales:
Francisco Narvez "Kiko"
Desea continuar?
Dino's
Santiago Martnez /El Viti/

12.

13.
14.

Define qu se entiende por una cadena de caracteres. Cunto ocupa en


memoria? Con qu carcter termina en memoria toda cadena de caracteres
en C? Cul es el nmero de elemento del primer carcter de cualquier
cadena en C? Dibuja la distribucin en memoria de la cadena char
NOMBRE = "VICENTE";
Es equivalente en C escribir "b" que 'b'? Por qu? Qu representa cada
caso?
Si se quiere declarar una variable cadena de caracteres para poder albergar
cadenas de hasta 8 caracteres, qu longitud deber darse a dicha cadena en
la declaracin? Cmo se hara dicha declaracin?

Elementos bsicos de un lenguaje de programacin

15.

Indicar qu hacen las siguientes sentencias:


- char nombre[26];
- printf(nombre);

16.

17.

18.

19.

- scanf("%s", nombre);
- printf("%c", nombre[0]);

Si se tiene declarada una cadena de 80 caracteres como char


mensaje[80];
- Qu hace la siguiente sentencia: printf("%c", mensaje[7]);
- Cmo podra verse cul es la tercera letra de dicho mensaje? Y la quinta?
Cmo se declarara en un programa:
Una constante llamada letra de tipo carcter, con valor 'F'.
Una variable llamada digito de tipo entero corto, iniciada a valor 23.
Una variable llamada nombre para poder albergar nombres de hasta 25
letras.
Una variable llamada impuestos para poder albergar cantidades
numricas con decimales.
Indica qu tipo de variables habra que utilizar para usar en un programa los
siguientes datos:
Saldos de cuentas en euros (con dos decimales).
Edad de una persona.
Talla de una persona.
Distancias en aos-luz entre 2 puntos de la galaxia.
Nmero de piezas en stock en una fabrica (nunca superior a 30.000
piezas).
Presupuestos del Estado en euros.
Importe en euros (sin decimales) de un artculo (nunca superior a
33.000 euros).
Nmero de alumnos matriculados en una asignatura de la Universidad.
Escribir una definicin apropiada para cada una de las siguientes constantes
simblicas, de forma que puedan aparecer en un programa en C.
Constante
Texto
FACTOR
ERROR
BEGIN
NOMBRE
EOLN

20.

131

-18
0.0001
{
"Adrin"
'\n'

Un programa en C desea utilizar las siguientes constantes:


3
puntos por victoria.
1
puntos por empate.
0
puntos por derrota.
12.5
IVA.
100000 factor mximo.
Indica cmo se utilizaran en el programa como constantes simblicas y
como constantes declaradas con nombre.

5. Expresiones y operadores
El proceso bsico de transformacin de los datos de entrada en datos de salida que
hace cualquier programa lleva asociado un conjunto de operaciones de diversa
complejidad. En este captulo se va a abordar cmo los lenguajes de programacin en
general, y en concreto C, hacen uso de los operadores para construir las expresiones
que dan soporte a dichos procesos de transformacin, conjugando para ello conceptos
como los de prioridad y asociatividad.
As, este captulo se ha organizado en siete secciones. En las dos primeras se
introducen los conceptos de operador y expresin respectivamente. La seccin tres se
dedica a repasar las caractersticas de los operadores ms frecuentemente utilizados en
el lenguaje C. La cuarta seccin estudia las reglas de prioridad de los operadores. La
quinta seccin aborda el tema de las funciones intrnsecas, mientras que la sexta
seccin concreta este tema para el lenguaje C. Finalmente, la sptima seccin presenta
una lista de cuestiones y ejercicios de repaso.

5.1. Operadores
Son tems (smbolos formados por uno o ms caracteres) que actan sobre una, dos o
ms variables para realizar una determinada operacin (un clculo predeterminado),
produciendo siempre un determinado resultado.
Algunos requieren dos operandos (binarios), mientras que otros actan slo sobre
un operando (unarios o monarios), incluso los hay que actan sobre tres operandos
(ternarios).
Normalmente, los operadores permiten expresiones1 como operandos, aunque
existen tambin los que slo permiten variables como operandos.
Hay operadores que slo pueden actuar con operandos de un tipo de dato
determinado, produciendo un resultado siempre de un tipo de dato establecido. Sin
embargo, hay otros operadores que pueden actuar sobre operandos de diversos tipos
de datos. En estos casos, es comn que el operador pueda originar un resultado donde
su tipo de dato depende del tipo de dato de los operandos. Esto se denomina
sobrecarga de operadores.
Cada lenguaje posee un conjunto determinado de operadores, algunos de stos son
comunes a casi todos los lenguajes, e incluso se representan por el mismo smbolo,
como se puede ver en la Tabla 5.1.

5.2. Expresiones
Las expresiones son combinaciones de constantes y variables relacionadas a travs de
operadores y/o nombres de funciones, donde adems tambin pueden incluirse
parntesis.
1

Una expresin es una combinacin de constantes y variables relacionadas a travs de


operadores y/o nombres de funciones.
- 133 -

134

Programacin en C

Smbolo
Cobol
C

Clasificacin

Operador

Asignacin
Aritmticos

:=
=
=
=
asignacin
suma
+
+
+
+
resta
multiplicacin
*
*
*
*
/
/
/
/
divisin
div
\
/
/
divisin entera
mod
mod
mdulo o resto divisin
%
potenciacin
^
igual que
=
=
=
==
!=
distinto
<>
<>
not =
mayor
>
>
>
>
menor
<
<
<
<
mayor o igual
>=
>=
>=
not <
menor o igual
<=
<=
<=
not >
not
not
not
!
negacin
and
and
and
conjuncin (Y)
&&
or
or
or
disyuncin (O)
||
Tabla 5.1. Ejemplos de operadores en diferentes lenguajes

Relacionales

Lgicos

Pascal

Basic

Pseudocdigo

+
*
/
div
mod
elevado a
=
<>
>
<
>=
<=
not no
and y
or o

Las expresiones siguen la misma filosofa que en la notacin matemtica


tradicional, como en los siguientes ejemplos.

b* a 5

a
b 3

*c

f
3.1

Una expresin siempre toma un valor, que se determina tomando los valores de las
variables y constantes implicadas y ejecutando las operaciones indicadas. El tipo de
dato del resultado depende tanto del operador o funcin utilizada como del tipo de los
operandos implicados.
En ocasiones, la introduccin de parntesis y espacios en blanco mejora la
legibilidad de la expresin a la hora de expresarla en un lenguaje de programacin,
aunque, en el caso de los parntesis, pueda llegar a ser innecesario en algunos casos.
EJEMPLO

Si a vale 3, b vale 6 y c vale 7.2, entonces


a + b
toma el valor 9
4 + c

toma el valor 11.2

Para recoger el valor o resultado de una expresin y almacenarlo en una variable se


utiliza el operador asignacin. As, en la Tabla 5.2 se muestra el operador de
asignacin utilizado por diversos lenguajes de programacin.
Smbolos del operador de asignacin
=
Basic
Pascal
:=
Cobol
=
C
=
Java
=
(flecha hacia la izquierda)
Pseudocdigo
Tabla 5.2. Operadores de asignacin en diversos lenguajes de programacin

Expresiones y operadores

135

As, por ejemplo, la siguiente sentencia, escrita en pseudocdigo, asigna el


resultado de la expresin escrita a la derecha del operador de asignacin a la
variable escrita a la izquierda del mismo. La variable solucion tomar el valor
de la suma de a ms b.
Solucion

a + b
Variable
operador de asignacin
expresin
El operando de la derecha del operador asignacin puede ser una variable simple o
una expresin, pero el operando de la izquierda debe ser siempre una variable.

5.3. Operadores en C
En esta seccin se va a hacer un repaso por los operadores que ofrece el lenguaje C,
explicando sus caractersticas y sintaxis.

5.3.1. Operador de asignacin


El operador asignacin en C se representa por el smbolo =, y significa que se asigna
el valor de la expresin situada a su derecha a la variable situada a su izquierda, como
se puede apreciar en la Tabla 5.3.
Operador
=

Operacin
Tipo del resultado
Asigna el valor del operando de su derecha Se acomoda a la variable
(expresin) al operando (variable) de su que recibe el valor
izquierda, sustituyendo al valor que tuviera sta
Tabla 5.3. Operador de asignacin en C

Si los dos operandos (derecha e izquierda) son de distinto tipo el valor de la


expresin de la derecha se convertir automticamente al tipo de la variable de la
izquierda.
Esta conversin automtica de tipos puede conllevar una alteracin del dato que se
est asignando, as sucede que:
Un valor real puede ser truncado si se asigna a una variable entera.
Un valor double o long double puede ser redondeado si se
asigna a una variable de tipo float.
Una cantidad entera puede ser alterada (se pierden los bits ms
significativos) si es asignada a una variable entera ms corta en
precisin.
EJEMPLO

Supngase la variable entera i (de tipo int), a continuacin se muestran


distintas expresiones de asignacin, as como el valor con el que quedar la
variable i.
Expresin
i = 3.3;
i = 2.6 + 0.3;
i = 7.8 15.2;

Resultado
3
2
-7

136

Programacin en C

La expresin asignacin (por ejemplo a = b) en C devuelve el valor que se est


asignando (b en el anterior ejemplo), adems, como el operador de asignacin en C es
asociativo por la derecha, es posible la realizacin asignaciones mltiples, las cuales
se efectan de derecha a izquierda.
Por ejemplo en la siguiente expresin de asignacin mltiple, todos los
identificadores tomarn el valor que tenga el identificador identifi4.
identifi1 = identifi2 = identifi3 = identifi4;

As, la expresin anterior equivaldra a:


identifi1 = (identifi2 = (identifi3 = identifi4));
EJEMPLO

Supngase que todas las variables que se referencian son enteras.


Expresin
j = i = 3;
k = j = i = -5;
m = n = 2*k;

Resultado
i y j con valor 3
i, j, k con valor -5
si k vale -5, m y n con valor -10

EJEMPLO

Puede asignarse a una variable de tipo entero el valor de una constante o de


una expresin de tipo carcter. El valor recibido por la variable entera
depende del cdigo que se est utilizando (por ejemplo la tabla ASCII).
Carcter
'x'
'0'
Expresin
Resultado
i = 'x';
i vale 120
i = 'x' '0'; i vale 72

Cdigo ASCII
120
48
Expresin
i = '0';
i = '0' 'x';

Resultado
i vale 48
i vale -72

EJEMPLO

Es muy frecuente encontrarse en un programa expresiones como la siguiente:


i = i + 1;
La expresin anterior ocasiona que el valor de la variable i se incremente en
una unidad. Por qu? Primero se evala la expresin de la derecha del
operador de asignacin (sumar 1 a la variable i), y el resultado se asignar a
la propia variable i.
Del mismo modo la expresin i=i+10; ocasiona que el valor de la variable
i se incremente en 10 unidades, mientras que la expresin i=i*3;
ocasiona que el valor de la variable i se triplique.

5.3.2. Operadores aritmticos


Sirven para realizar operaciones aritmticas bsicas. En la Tabla 5.4 se presentan los
operadores binarios aritmticos propios del lenguaje C.
La divisin de variables de tipo entero trunca el resultado, esto es, no deja
decimales caso (*1*) en la Tabla 5.4. Por otra parte, el operador mdulo (%) exige la
utilizacin de nmeros enteros caso (*2*) en la Tabla 5.4.
En lo que respecta al signo del resultado final de la subexpresin afectada por un
operador aritmtico se tiene que para los operadores +, -, *, /, el signo del resultado

Expresiones y operadores

137

viene determinado por las reglas del lgebra, mientras que para el operador mdulo
(%), el signo del resultado es el del primer operando, esto es, el signo del dividendo.
Operador
+
*
/
%

Tipo del
resultado
suma
Numricos
numrico
resta
Numricos
numrico
producto
Numricos
numrico
divisin
Los dos enteros y el segundo no cero
entero (*1*)
alguno de ellos real y el segundo no cero
real
mdulo o resto Los dos enteros y el segundo no cero (*2*) entero
Tabla 5.4. Operadores binarios aritmticos en C
Operacin

Tipo de los operandos

EJEMPLO

Supngase a y b variables enteras, con valores 10 y 3 respectivamente.


Expresin
a + b
a - b
b - a
a * b
a / b
b / a
a % b
b % a

Resultado
13
7
-7
30
3
0
1
3

EJEMPLO

Supngase v1 y v2 variables reales, con valores 12.5 y 2.0


respectivamente.
Expresin
v1 + v2
v1 * v2
v1 % v2

Resultado
14.5
25.0
(No permitido)

Expresin
v1 - v2
v1 / v2

Resultado
10.5
6.25

EJEMPLO

Con el operador divisin (/) y mdulo (%):


El signo del cociente entero siempre se deduce de la regla algebraica
de los signos.
El signo del resto es siempre el del primer operando (dividendo).
Siempre se verificar la regla de la divisin:
Expresin
7 / 2
-7 / 2
7 / -2
-7 / -2

dividendo = divisor * cociente + resto


D = d * (D/d) + (D%d)
Resultado
Expresin
Resultado
3
7 % 2
1
-3
-7 % 2
-1
-3
7 % -2
1
3
-7 % -2
-1

EJEMPLO

Los operadores aritmticos pueden ser usados sobre variables y constantes de


tipo carcter. En ese caso el valor utilizado para la expresin depende del

138

Programacin en C

cdigo que se est utilizando (por ejemplo la tabla ASCII). Supngase las
siguientes variables tipo carcter:
Identificador de variable
c1
c2
c3
c4
Expresin
Resultado
c4
102
c3 15
50
c1 + c2 + 5
169
c1 + c2 + '5' 217

Valor
'P'
'T'
'A'
'5'
'f'

Cdigo ASCII
80
84
65
53
102
Expresin
Resultado
c1 + c2
164
2 * c3
130
c3 / 2
32

EJEMPLO

Observando la tabla ASCII, se puede apreciar que la diferencia numrica


entre una letra minscula y su correspondiente mayscula es siempre la
misma: 32. De esta forma, por tanto, puede obtenerse a partir de una letra
mayscula su correspondiente minscula, o viceversa, sumando o restando a
su cdigo ASCII este valor.
Suponiendo minus, mayus de tipo char, y tomando el resto de variables
del ejemplo anterior:
minus=c2+32;
minus=c2+('a'-'A');
mayus=c4-32;
mayus=c4+('a'-'A');

// minus tomar el valor 't'

// minus tomar el valor 't'


// mayus tomar el valor 'F'
// mayus tomar el valor 'F'

En el lenguaje C existen tambin operadores aritmticos unarios o monarios. stos


actan sobre un solo operando para producir un nuevo valor. En la Tabla 5.5 se
presentan los operadores unarios que ofrece el lenguaje C.
Operador
-

Ejemplo
-x

++x
++

x++

--x
--

x--

Significado
Operador signo menos.
Cambia el signo del operando.
Operador incremento. El operando debe ser una variable.
Pre-incremento: Incrementa x, es decir, hace x=x+1
antes de cualquier operacin con la variable x.
Post-incremento: Incrementa x, es decir, hace x=x+1
despus de cualquier operacin con la variable x.
Operador decremento. El operando debe ser una variable.
Pre-decremento: Decrementa x, es decir, hace x=x-1
antes de cualquier operacin con la variable x.
Post-decremento: Decrementa x, es decir, hace x=x-1
despus de cualquier operacin con la variable x.
Tabla 5.5. Operadores unarios en C

EJEMPLO

Supngase i una variable entera con valor 5.


La expresin ++i es equivalente a i = i + 1, y hace que i pase a valer 6.

Expresiones y operadores

139

La expresin --i es equivalente a i = i - 1, y hace de i (partiendo de


su valor original 5) pase a valer 4.
EJEMPLO

La diferencia entre los operadores incremento/decremento como prefijo y


como sufijo, estriba en el momento en que se efecta la operacin aritmtica.
Si el operador precede al operando (++i -i), el valor del operando se
modificar antes de que se contine con la operacin donde est incluido
el operando i.
Sin embargo, si el operador sigue al operando (i++ i--), primero se
completa la operacin donde est incluido el operando i y luego se
incrementa o decrementa el operando i.
Supngase un programa que incluye las sentencias de asignacin en el orden
siguiente (los valores con que quedan cada una de esas variables se escriben
a la derecha de la expresin):
i
a
b
c

=
=
=
=

1;
i;
++i;
2*(++i);

valor
valor
valor
valor
valor

de i
de a
de b
de c
final de i

1
1
2
6
3

Sin embargo, si se cambia el operador pre incremento por el post incremento,


el valor de cada una de las variables ser:
i
a
b
c

=
=
=
=

1;
i;
i++;
2*(i++);

valor
valor
valor
valor
valor

de i
de a
de b
de c
final de i

1
1
1
4
3

5.3.2.1. Conversiones de tipo implcitas


Cuando en una expresin aritmtica se mezclan constantes y variables de diferentes
tipos, los operandos pueden sufrir una conversin automtica de tipo antes de que la
expresin alcance su valor final. Es decir, en ejecucin se hace una conversin
temporal hacia arriba o promocin, hacia el mayor de los tipos de los operandos,
devolvindose el resultado en ese tipo.
El objetivo de este mecanismo es no perder informacin, de manera que en
general se expresar el resultado con la mayor precisin posible, pero siempre de
forma consistente con los tipos de datos de los operandos.
Las reglas de promocin son las siguientes:
1. Si uno de los operandos es long double, el otro ser convertido a
long double y el resultado ser long double.
2. En otro caso, si uno de los operandos es double, el otro ser
convertido a double y el resultado ser double.
3. En otro caso, si uno de los operandos es float, el otro ser convertido
a float y el resultado ser float.
4. En otro caso, si uno de los operandos es unsigned long, el otro ser
convertido a unsigned long y el resultado ser unsigned long.

140

Programacin en C

5.

En otro caso, si uno de los operandos es long y el otro unsigned


int, entonces:
a. Si el operando unsigned int se puede convertir a long, el
operando unsigned int ser convertido y el resultado ser
long.
b. En otro caso, ambos operandos sern convertidos a unsigned
long y el resultado ser unsigned long.
6. En otro caso, si uno de los operandos es long, el otro ser convertido a
long y el resultado ser long.
7. En otro caso, si uno de los operandos es unsigned int, el otro ser
convertido a unsigned int y el resultado ser unsigned int.
8. Si no se puede aplicar ninguna de las condiciones anteriores, entonces
ambos operandos sern convertidos a int (si es necesario) y el
resultado ser int.
Nota: algunos compiladores C convierten automticamente todos los operandos
float a double y todos los char y short int a int.
EJEMPLO

Especificar el valor y tipo de dato del resultado de las siguientes expresiones,


suponiendo los siguientes tipos de datos de los operandos:
int
i = 7, j=3;
float f = 5.5;
char c = 'w'; /* Cdigo ASCII de w es 119 */
long int x = 34000;
Expresin
Resultado
Tipo del resultado
i + f
12.5
float
i + c
126
int
i + c + 0.0 126.0
double (0.0 double)
i * 1
7
int
i * 1.0F
7.0
float (1.0F float)
(c * i) + (j / i) (x / i) + (2.3 f)

int

int

long

int

long

double
Resultado 4027.2 de tipo double

double

Expresiones y operadores

141

Otra clase de conversin implcita tiene lugar cuando el resultado de una expresin
es asignado a una variable, pues dicho resultado se convierte al tipo de la variable.
En este caso, sta puede ser de menor rango que la expresin, por lo que en esta
conversin puede perderse informacin y ser peligrosa.
EJEMPLO

Cul es el resultado de las siguientes expresiones? Indicar si hay prdida de


informacin o promocin de la misma.
int a =

3/5

0.75/2;

0 (int)

0.375 (double)

Se produce promocin de tipos en la expresin. Su valor es 0.375


double.
Se produce prdida de informacin al asignar el resultado de la
expresin a la variable a que es de tipo int. La variable a toma el
valor 0.

float b =

50/2

25 (int)

7%3;

1 (int)

Al asignar el resultado de la expresin (26 int) a la variable


float b, se convierte ste al tipo de la variable. La variable
float b tomar el valor 26.0.

int c =

1.75F;

(float)

Se produce prdida de informacin al asignar el resultado de la


expresin (float) a la variable int c. La variable int c tomar
el valor 1.

int c =

1.75e9F;

(1750000000 float)

Se produce overflow2 al asignar el resultado de la expresin


(float) a la variable int c. La variable int c quedar cargada
con un valor impredecible.
char d =

160/2 + 5;

80 (int) + 5 (int)

Al asignar el resultado de la expresin (int) a la variable char d,


se convierte ste al tipo de la variable. En este caso el tipo de la
variable es de menor rango que el de la expresin, pero dado que el
valor de la expresin (85) no sobrepasa el rango de valores
permitidos por la variable, este valor se almacenar en la variable.
La variable char d tomar el valor 85 ('U').

5.3.3. Operadores de asignacin compuestos


C proporciona cinco operadores de asignacin adicionales, que actan como una
notacin abreviada para expresiones utilizadas con frecuencia. Representan una forma
2

Cuando a una variable entera se le asigna en tiempo de ejecucin un valor que queda fuera del
rango permitido (situacin de overflow o valor excesivo), de ordinario el programa no se
interrumpe, por lo que puede producirse un error lgico en el programa de consecuencias
imprevisibles. La variable quedar cargada con un valor impredecible.

142

Programacin en C

ms compacta y abreviada de escribir expresiones que involucran a operadores


aritmticos.
Por ejemplo, si se quiere incrementar en dos unidades el contenido de la variable
entera i, se podra hacer con la expresin i = i +2;. Sin embargo, esto mismo se
puede llevar a cabo utilizando el operador de asignacin compuesto += de la siguiente
manera: i += 1;. En la Tabla 5.6 se pueden apreciar los cinco operadores de
asignacin compuestos proporcionados por C.
Operador
+=
-=
*=
/=
%=

Ejemplo
Significado equivalente
de uso
x += y
x = x + y
x += 1
x = x + 1
x -= y
x = x - y
x -= 1
x = x - 1
x *= y
x = x * y
x *= 3
x = x * 3
x /= y
x = x / y
x /= 10
x = x / 10
x %= y
x = x % y
x %= 5
x = x % 5
Tabla 5.6. Operadores de asignacin compuestos en C

5.3.4. Operadores relacionales


Los operadores relacionales comprueban una relacin entre dos operandos,
retornando el valor 0 si es falsa la relacin evaluada, y el valor 1 si es cierta.
Estos operadores se utilizan en expresiones de la forma:
expresin1 operador_relacional expresin2

Donde expresin1 y expresin2 son dos expresiones vlidas en C y


operador_relacional es uno de los operadores que aparecen en la Tabla 5.7.
Operador
= =
!=
<
>
<=
>=

Comparacin que
Tipo de datos que puede
realiza
comparar
igual
numricos, carcter, punteros
distinto
numricos, carcter, punteros
menor que
numricos, carcter, punteros
mayor que
numricos, carcter, punteros
menor o igual que
numricos, carcter, punteros
mayor o igual que
numricos, carcter, punteros
Tabla 5.7. Operadores relacionales en C

Tipo del
resultado
int
int
int
int
int
int

EJEMPLO

Supngase la siguiente declaracin de variables.


int
i = 1, j = 2, k = 3;
float f = 5.5;
char c = 'w';

Se representan diversas expresiones lgicas y el resultado que proporcionan:

Expresiones y operadores

Expresin
i < j
(i + j) >= k
(j + k) > (i + 5)
k != 3
j == 2
f > 5
(i + f) > 10
c != 'p'
k >= 10 * (i + f)

Interpretacin
cierto
cierto
falso
falso
cierto
cierto
falso
cierto
falso

143

Resultado
1
1
0
0
1
1
0
1
0

5.3.5. Operadores lgicos


Actan sobre operandos que son a su vez expresiones lgicas, permitiendo combinar
expresiones lgicas individuales para formar otras condiciones lgicas ms
complicadas3.
Como ya se ha comentado en alguna otra ocasin, en C no existe el tipo lgico. En
una expresin lgica se considera el valor 0 o nulo como falso (false). El resto de
valores como verdadero (true).
As pues, los operadores lgicos devuelven el valor 0 si es falso el resultado de
la operacin lgica evaluada, y el valor 1 si es cierto el resultado de la operacin
lgica evaluada.
Operador
!

&&
||

Operacin que realiza


NOT. Negacin lgica
Cambia el cierto por el falso y viceversa
Acta sobre un nico operando
AND lgico.
cierto slo si los dos operandos son ciertos
OR lgico
cierto si cualquiera de los operandos es cierto
Tabla 5.8. Operadores lgicos en C

Tipo del resultado


int
int
int

En la Tabla 5.8 se pueden observar los operadores lgicos que ofrece el lenguaje C,
mientras que a continuacin se muestran las tablas de verdad de dichos operadores.

EJEMPLO
3

De esta forma permiten comprobar si se cumplen simultneamente varias condiciones.

144

Programacin en C

Supngase la siguiente declaracin de variables.


int
i = 7;
float f = 5.5;
char c = 'w';
Se representan diversas expresiones lgicas y el resultado que proporcionan:
Expresin
(i >= 6) && (c == 'w')
(i < 6) || (c == 'p')
(f < 11) && (i > 100)
(c != 'z') || ( (i+f) > 20 )
!(i > 1)
!( (i >= 6) && (c == 'w') )
!( (i < 6) || (c == 'p') )
!i
(equivale a i==0)

Interpretacin
cierto
falso
falso
cierto
falso
falso
cierto
falso

Resultado
1
0
0
1
0
0
1
0

Los compiladores tratan de optimizar en ejecucin la evaluacin de las


expresiones, lo cual puede tener a veces efectos no deseados. Por ejemplo, para que el
resultado de la siguiente expresin sea verdadero:
subexpresion1 && subexpresion2
Ambas subexpresiones deben ser verdaderas; si se evala subexpresion1 y es
falsa, ya no hace falta evaluar subexpresion2, y de hecho no se evala.
Algo parecido ocurre con la expresin:
subexpresion3 || subexpresion4
Si subexpresion3 es verdadera, ya no hace falta evaluar subexpresion4, y
de hecho no se evala, ya que el resultado de la expresin ser verdadero.
Una de las caractersticas ms importantes (y en ocasiones ms difciles de
manejar) del lenguaje C es su flexibilidad para combinar expresiones y operadores de
distintos tipos en una expresin que podramos llamar general. Recurdese que el
resultado de una expresin lgica o una expresin relacional es siempre un valor
numrico (1 si cierto 0 si falso). Esto permite que cualquier expresin lgica o
relacional pueda aparecer como subexpresin en una expresin aritmtica.
Recprocamente, cualquier valor numrico puede ser considerado como un valor
lgico vlido en una expresin lgica (cierto si es distinto de 0 y falso si es igual a 0).
Esto permite que cualquier expresin aritmtica pueda aparecer como subexpresin
de una expresin lgica.
EJEMPLO

Sean i, j, k tres variables de tipo int, con valores iniciales 0, 10 y 100


respectivamente. Para cada una de las siguientes expresiones se va a realizar
el rbol de evaluacin de sus subexpresiones, indicando los resultados
parciales y el resultado final.
(i != k) && (j * i)

k == j || (k - i*j)

Expresiones y operadores

145

EJEMPLO

Sean a, b, c y d variables de tipo int, con valores iniciales 0, 10, 20 y 30


respectivamente. Para la siguiente expresin, se va a realizar el rbol de
evaluacin de sus subexpresiones, indicando los resultados parciales y el
resultado final.
(c != d) + (!b !a*2.0)

5.3.6. Operadores de tratamiento de bits


El lenguaje de programacin C es considerado muchas veces como un lenguaje de
nivel intermedio por su facilidad para manejar cadenas de bits directamente,
ofreciendo para ello diversos operadores que manejan bits a bajo nivel.
Los operadores de tratamiento de bits (bitwise) ejecutan operaciones lgicas sobre
cada uno de los bits de los operandos. Cada operador de esta categora realiza una
operacin lgica bit a bit sobre datos internos. Estos operadores slo se aplican a
variables y a constantes char, int, unsigned y long, pero nunca a datos en
coma flotante.
Tipo de los
operandos
Complemento a uno (unario)
entero
AND a nivel de bit
entero
OR a nivel de bit
entero
OR exclusivo a nivel de bit
entero
desplazamiento de bits a la izquierda
entero
desplazamiento de bits a la derecha
entero
Tabla 5.9. Operadores de tratamiento de bits en C

Operador

Tipo del
resultado
entero
entero
entero
entero
entero
entero

Operacin que realiza

~
&
|
^
<<
>>

En la Tabla 5.9 se presentan los operadores de tratamiento de bits que soporta el


lenguaje C, y a continuacin se presentan las tablas de verdad para los operadores &,
|, ^ y ~.
A
0
0
1
1

&
&
&
&
&

B
0
1
0
1

=
=
=
=
=

=
=
=
=
=

C
0
0
0
1

A
0
0
1
1

|
|
|
|
|

B
0
1
0
1

=
=
=
=
=

=
=
=
=
=

C
0
1
1
1

A
0
0
1
1

^
^
^
^
^

B
0
1
0
1

=
=
=
=
=

=
=
=
=
=

C
0
1
1
0

A
1
0

~A
0
1

Todos los operadores de la Tabla 5.9, salvo el operador de complemento a uno (~)
son binarios, y por lo tanto necesitan dos operandos.

146

Programacin en C

El relleno de bits para los operadores desplazamiento, que depende de la mquina,


normalmente se rellenan a ceros.
EJEMPLO

Supngase la siguiente declaracin de variables:


int

i = 12, j = 140, k = 12698;

Expresin
~i

Resultado
-13

Comprobacin
12 en binario en 2 bytes
complemento a 1

00000000
11111111

00001100
11110011

j & k

136

140 en binario en 2 bytes


12698 en binario en 2 bytes
AND bit a bit

00000000
00110001
00000000

10001100
10011010
10001000

j | k

12702

140 en binario en 2 bytes


12698 en binario en 2 bytes
OR bit a bit

00000000
00110001
00110001

10001100
10011010
10011110

j ^ k

12566

140 en binario en 2 bytes


00000000 10001100
12698 en binario en 2 bytes
00110001 10011010
OR exclusivo bit a bit
00110001 00010110
OR exclusivo: 1 slo si uno de los dos bit es 1; 0 si los 2 bits son 0 1

j << 2

560

140 en binario en 2 bytes


00000000 10001100
Desplazar dos posiciones hacia la izq. 00000010 00110000
(se rellenan los bits por la derecha con 0)

k << 3

-29488

12698 en binario en 2 bytes


00110001 10011010
Desplazar tres posiciones hacia la izq. 10001100 11010000
(se rellenan los bits por la derecha con 0)

k >> 1

6349

12698 en binario en 2 bytes


00110001 10011010
Desplazar 1 posicin hacia la derecha 00011000 11001101
(se rellenan los bits por la izquierda con 0)

5.3.7. Operador tamao


Este operador es unitario y produce un resultado que es el tamao en bytes del dato o
tipo de dato que toma como operando. Como la mayora de los tipos de datos
requieren diferentes cantidades de almacenamiento interno en ordenadores diferentes,
este operador permite la consistencia de programas en diferentes tipos de
ordenadores. El uso de este operador queda explicitado en la Tabla 5.10.
Operador

Operacin que realiza

Tipo del
operando

Tipo del
resultado

Proporciona el tamao4 (nmero de

sizeof( ) bytes) que ocupa en memoria el

cualquiera
operando
Tabla 5.10. Operador tamao (sizeof) en C

entero

Hay que recordar que el tamao de una variable depende del compilador y del tipo de
ordenador utilizado.

Expresiones y operadores

147

El formado de este operador es:


sizeof (nombre de variable)
sizeof (tipo_dato)
sizeof (expresin)

Si su operando en una variable, pueden no utilizarse los parntesis.


EJEMPLO

Supngase las siguientes declaraciones de variables:


int i = -12;
float a = 12.34;
char c1 = 'G';
Expresin
sizeof(float)
sizeof a
sizeof c1
sizeof (2*i + 3)

Resultado
4
4
1
2

Este operador tiene como utilidad:


Permite calcular la ocupacin en memoria de una variable
compuesta, sin necesidad de sumar las longitudes de cada uno de
sus componentes.
EJEMPLO
struct datos {
int num;
char nom[40];
float abono;
} socio;
Expresin
Resultado
sizeof socio
46

La informacin que proporciona este operador es til cuando se


debe transferir el programa a un nuevo ordenador o a un nuevo
compilador de C5.
Para asignacin dinmica de memoria (reservar durante la ejecucin
del programa espacio para variables).

5.3.8. Operador de conversin explcita (casting)


En C existe la posibilidad de realizar conversiones explcitas de tipos (llamadas
casting en terminologa inglesa).
El casting es, por tanto, una conversin de tipo realizada explcitamente por el
programador que consiste en obligar a una variable o expresin a ser temporalmente
de un determinado tipo mientras se evala en una expresin.
Se realiza utilizando el operador cast, que ser un nombre de tipo encerrado entre
parntesis que preceder a la variable o expresin, como se ilustra en la Tabla 5.11.
Operador
(especificador de tipo)

Operacin que realiza


Tipo del operando
conversin de tipos
cualquiera
Tabla 5.11. Operador de casting en C

Es decir, producir cdigo portable.

Tipo del resultado


el indicado

148

Programacin en C

Este operador es un operador unitario, donde el tipo de dato del operando no vara
en su definicin, nicamente se convierte temporalmente para evaluar la expresin en
la que se utiliza el operador de molde.
EJEMPLO

Supngase la siguiente declaracin:


float f = 11;
La expresin f % 4 no es vlida, porque el operando f es real en vez de
entero. Sin embargo, la expresin (int) f % 4 hace que el operando se
transforme en entero y, por tanto, la expresin ya es vlida, obtenindose
como resultado el valor 3 como entero.
EJEMPLO

La expresin f = (9/5) * c + 32;, que realiza la conversin a grados


Fahrenheit de una temperatura en grados Centgrados, proporcionar un
valor incorrecto y no esperado en C, pues 9/5, al ser los dos operandos
enteros, proporcionar como resultado el valor 1 y no el 1.8 deseado.
Posibles formas de escribir esta expresin para que proporcione el resultado
esperado son:
f = (9.0/5) * c + 32;
f = (9/5.0) * c + 32;
f = ((float)9/5) * c + 32;

Hay que ser cuidadoso, pues el operador cast acta sobre la variable ms prxima.
As, si se tiene la declaracin:
int x = 11;
Y se tiene la expresin:
(float) x/2
sta convierte x a float, por lo que 2 es considerado como float y, por tanto,
la divisin se realiza entre reales, dando un resultado de tipo float que ser 5.5.
Si por el contrario se tiene la expresin:
(float) (x/2)
Primero se realiza la divisin entera obtenindose un resultado entero sin
decimales (truncado), y posteriormente este resultado es convertido a float, pero se
han perdido los decimales. El resultado ser 5, es decir, no se ha hecho nada!
EJEMPLO

Supngase el siguiente fragmento en C:


long producto;
int a= 3000, b =1500;
...
producto = a * b;

Dado que tanto la variable a como la b son de tipo int, no existe


promocin automtica de tipos, y el resultado de la expresin a*b tendr
tipo int. Pero se producir overflow dado que 3000 * 1500 es
4500000 que excede el rango del tipo int, por lo que el resultado de la
propia expresin ya quedar truncado y por tanto errneo.

Expresiones y operadores

149

De este modo, al asignar la variable producto con el resultado de la


expresin, aunque se haya declarado de tipo long, se producir prdida de
informacin, y el valor que se cargar en ella ser impredecible y errneo.
La solucin ser utilizar el operador cast para obligar a que el resultado de la
expresin sea de tipo long, as:
producto = (long) a * b;

producto = a * (long) b;
Pero ser incorrecto producto = (long) (a * b); por los motivos
expuestos anteriormente.

5.3.9. Operador coma


Se utiliza para separar dos expresiones dentro de una expresin total, evalundose de
izquierda a derecha, y siendo el resultado de la ltima expresin de la derecha el de la
expresin total.
En la Tabla 5.12 se resume la utilizacin de este operador.
Operador
Operacin que realiza
,
Separa expresiones dentro de una
expresin total

Tipo de los operandos Tipo del resultado


Cualquiera
El de la ltima
expresin de la
derecha
Tabla 5.12. Operador coma en C

El operador coma realizar una secuencia de operaciones. No debe confundirse con


la coma que se utiliza para separar los parmetros de llamada a una funcin o con la
coma que se utiliza en una definicin mltiple de variables del mismo tipo.
Este operador se emplea esencialmente en dos casos:
Para realizar una asignacin de una expresin, que previamente requiere
otra expresin.
EJEMPLO

La expresin:
a = (b = 5, c = 3, b+c);
Se puede escribir como:
b = 5;
c = 3;
a = b+c;
Por lo que en la variable a se almacenar el valor 8.
Para realizar varias operaciones dentro de la condicin de un bucle for.
EJEMPLO
for (i= 0, k = 15; i < k; k--, i++) {

Equivale a escribir:

i= 0;
for (k = 15; i < k; k--) {
...
i++;
}

...

150

Programacin en C

5.3.10. Operador condicional


Es un operador ternario que utiliza dos smbolos: interrogacin (?) y dos puntos (:).
Devuelve un resultado que cuyo valor depende de la condicin comprobada, es decir,
se utiliza para realizar una operacin alternativa mediante una condicin.
En la Tabla 5.13 se resume el significado de este operador ternario.
Operador
?:

Operacin que realiza


Bifurcacin condicional

Tipo de los
operandos
cualquiera

Tipo del resultado


El de la ltima expresin
que corresponda a cierto

Tabla 5.13. Operador condicional en C

El formato de este operador es:


expresin_1
?
expresin_v
:
expresin_f ;
La operacin se realiza de la siguiente forma:
Se evala expresin_1
Si el resultado es cierto (cualquier valor distinto de cero) se
evala expresin_v y su resultado se toma como resultado de la
expresin total.
Por el contrario si el resultado de expresin_1 es falso (valor cero
o nulo), se evala expresin_f y su resultado se toma como
resultado de la expresin total.
EJEMPLO

x = (a > b) ? a : b;
Asigna a x el mayor entre a y b
y = (f < g) ? f : g;
Asigna a y el menor entre f y g
indicador = (i < 0) ? 0 : 100;
Si i es negativo, se le asignar a indicador el valor 0. Si
fuese cero o positivo, se le asignar el valor 100.

5.4. Reglas de prioridad de los operadores


El resultado de una expresin depende del orden en que se ejecuten las operaciones
(se evalen los operadores). El siguiente ejemplo ilustra claramente la importancia del
orden.
Supngase la expresin: 36 / 2 + 4. Si primero se realiza la divisin el
resultado ser 22, pero si primero la suma el resultado ser 6.
Con el objeto de que el resultado de una expresin quede claro e inequvoco, es
necesario definir unas reglas que establezcan el orden con que se evalan los
operadores en un lenguaje de programacin.
Existen en todo lenguaje de programacin reglas que determinan el orden de
evaluacin de sus operadores: la precedencia o prioridad y la asociatividad. De
todas formas, el orden de evaluacin de los operadores puede ser alterado a voluntad
por el programador utilizando parntesis, ya que siempre primero se realizan las
operaciones encerradas entre parntesis.

Expresiones y operadores

151

Las tres reglas de evaluacin de los operadores, en cualquier lenguaje de


programacin, son las siguientes:
1. Parntesis. Todas las expresiones entre parntesis se evalan primero.
Las subexpresiones con parntesis se evalan de dentro hacia fuera (el
parntesis ms interno primero).
2.

Nivel de prioridad. Los operadores en la misma subexpresin se


evalan de acuerdo a su nivel de prioridad dado por la tabla de
clasificacin propia del lenguaje. Los operadores con mayor prioridad se
evalan antes que los de menor prioridad.

3.

A igual nivel de prioridad, se aplica la asociatividad. Los operadores


en una misma subexpresin con igual nivel de prioridad se evalan de
izquierda a derecha o de derecha a izquierda, segn la asociatividad del
grupo de operadores.

Todo lenguaje de programacin dispone de una clasificacin jerrquica de todos


los operadores segn su prioridad y asociatividad. Esta clasificacin es semejante de
un lenguaje a otro, pero no exactamente igual.
La Tabla 5.14 muestra los distintos operadores de C agrupados por nivel de
prioridad. Dentro de cada grupo todos los operadores tienen el mismo nivel de
prioridad, por lo que su orden de evaluacin viene marcado por su asociatividad.
Existe una tabla anloga, aunque no tiene por qu ser exactamente igual para cada
lenguaje de programacin.
EJEMPLO

Supngase la siguiente declaracin de variables:


int
i = 7;
float f = 5.5;
char c = 'w'; /* Cdigo ASCII de w es 119 */
Expresin
Resultado
Comentario
i + f <= 10
falso
+ antes que <=
i >= 6 && c == 'w'
cierto
>= y == antes que &&
4 + 36 / 2
22
/ antes que +
(4 + 36) / 2
20
parntesis primero
5 + 36 / 6 % 4 - 2
5
por este orden: /, %, +, -

Buen estilo de programacin


Aunque no sea estrictamente necesario, se considera un buen estilo de
programacin colocar parntesis para ver ms claro el orden de evaluacin:
5 + ((36 / 6) % 4) - 2
EJEMPLO

Supngase a, b y c variables enteras. En la siguiente instruccin de


asignacin aparecen operadores de seis grupos de precedencia distintos.
c += (a > 0 && a <= 10) ? ++a : a/b;
En el momento de ejecutarse la instruccin se comenzar por la evaluacin
de la subexpresin entre parntesis, esto es:
(a > 0 && a <= 10)
Si esta expresin es cierta, se evala la expresin ++a. Si no es as, se evala
la expresin a/b. Finalmente, se efecta la operacin de asignacin (+=),

152

Programacin en C

haciendo que se incremente la variable c en el valor de la expresin


condicional.
Si a=1, b=2, c=3 entonces (a > 0 && a <= 10) valdr 1 (cierto),
por lo que se evaluar ++a, tomando la expresin condicional total el valor
de 2. A c se le asignar el valor 5 (c = 3 + 2).
Si a=50, b=10, c=20 entonces (a > 0 && a <= 10) valdr 0
(falso), por lo que se evaluar a/b, tomando la expresin condicional total
el valor de 5. A c se le asignar el valor 25 (c = 20 + 5).
PRIORIDAD
Ms alta

Sentido de
crecimiento de la
prioridad

Ms baja

OPERADORES
()
parntesis y llamada a funcin
[]
subndice de una tabla
.
miembro de una estructura
->
puntero a miembro de una estructura
!
no lgico
~
complemento a uno a nivel de bit
signo menos
++
incremento
-decremento
&
direccin de una variable
*
valor almacenado para punteros
(tipo)
molde o <<cast>>
sizeof
tamao
* /
%
multiplicacin, divisin, mdulo o resto
+ suma, resta
<<
desplazamiento a la derecha
>>
desplazamiento a la izquierda
< <= > >=
menor que, menor o igual que, mayor que,
mayor o igual que
==
igualdad
!=
desigualdad
&
and a nivel de bit
^
or exclusivo a nivel de bit
|
or a nivel de bit
&&
and lgico
||
or lgico
? :
condicional
= += -= *= /= %= &= ^= |= <<= >>=
asignacin
,
coma

Tabla 5.14. Tabla de prioridad de los operadores en el lenguaje C

ASOCIATIVIDAD
Izq.
Der.

Izq.

Der.

Izq.

Der.

Izq.

Der.

Izq.

Der.

Izq.

Der.

Izq.

Der.

Izq.
Izq.
Izq.
Izq.
Izq.

Der.
Der.
Der.
Der.
Der.
Izq.
Der.
Izq.
Der.

Izq.

Der.

Expresiones y operadores

153

5.5. Funciones de intrnsecas o de biblioteca


Todo lenguaje de programacin para realizar clculos predeterminados dispone,
adems de operadores, de las llamadas funciones intrnsecas o de biblioteca, las
cuales estn ya incorporadas en el compilador para realizar una serie de tareas y
clculos especficos de uso frecuente, por ejemplo:
Funciones para efectuar las operaciones estndar de entrada/salida: mostrar
un dato o un literal en pantalla, leer un dato de teclado, escribir en un
archivo...
Funciones para efectuar operaciones sobre caracteres: conversin de
maysculas a minsculas, determinar si un carcter es una letra mayscula o
minscula...
Funciones que realizan operaciones con cadenas de caracteres: copiar una
cadena en otra, concatenar cadenas...
Funciones que realizan clculos matemticos: evaluacin de las funciones
trigonomtricas, logartmica y exponencial, clculo de valores absolutos,
races cuadradas...
...
Las funciones intrnsecas son segmentos independientes de cdigo mquina que el
compilador proporciona agrupadas, segn su propsito, en distintos ficheros objeto
(bibliotecas de funciones), para realizar un clculo, operacin o tarea especfica ya
resuelta por el propio compilador.
Normalmente, en todos los lenguajes, antes de utilizar una funcin intrnseca se
debe referenciar dentro del programa fuente al fichero objeto o biblioteca de
funciones donde se encuentra (el nombre de la biblioteca en la que se encuentra la
funcin correspondiente lo proporciona la ayuda o manual del compilador). Por
ejemplo:
USES unidad;
en Pascal
#include <archivo_cabecera>
en C
Toda funcin intrnseca tomar ninguno, uno o ms valores de entrada
(argumentos) y proporciona un valor de salida (resultado). El resultado es el valor
que toma la funcin para los argumentos de entrada.
Cada lenguaje posee un conjunto determinado de funciones intrnsecas (catlogo o
biblioteca de funciones), algunas de stas comunes a casi todos los lenguajes, y
muchas de ellas coincidentes hasta en el nombre.
Las ideas son las mismas que en el concepto matemtico tradicional. Recordando
la nomenclatura matemtica se tiene que:

Donde:
f es el nombre de la funcin, la cual slo tiene un argumento, de
tipo real. El resultado que proporcionar tambin ser real.
x es el parmetro formal que representa el argumento en la
definicin de la funcin f.

154

Programacin en C

Para que f proporcione un resultado deber invocrsela con un


valor actual o real que sustituya a x. Por ejemplo, con x = 3. En
este caso el valor 3 ser el parmetro real o actual.
El resultado de la funcin para x = 3 ser 0.3 y se escribe como
f (3) = 0.3.
Una funcin puede tener varios argumentos, pero siempre proporcionar un nico
resultado.

f (3, 4) = - 0.04
Para poder utilizar una funcin intrnseca en un lenguaje de programacin se debe
conocer la interfaz (o prototipo) de la funcin, es decir:
El nombre de la funcin.
El nmero y tipo de sus argumentos.
El tipo del resultado que proporciona.
Dependiendo del lenguaje, la sintaxis que se utilizar para indicar la interfaz de una
funcin intrnseca ser parecida a una de estas dos:
nombre_funcin (parmetro1 tipo1, parmetro2 tipo2, ...): tipo_resultado

tipo_resultado nombre_funcin (tipo1 parmetro1, tipo2 parmetro2, ...)

EJEMPLO

Supngase que se necesita obtener en la variable resultado, la raz


cuadrada de un nmero contenido en la variable num dentro del programa.
En lugar de programar nosotros dicho clculo, se acude al catlogo de
funciones que proporciona el compilador y ste, adems proporcionar el
nombre de la biblioteca donde se encuentra, indicar como interfaz o
prototipo algo parecido a lo siguiente:
Intrprete Basic:
sqr (x real): real
Compilador Pascal:
sqrt (x real): real
Compilador C:
double sqrt(double x)
Est indicando que:
El nombre de la funcin: sqr en Basic y sqrt en Pascal y C.
El nmero y tipo de sus argumentos: uno, ser un nmero real.
El tipo del resultado que proporciona: real.
x es el parmetro formal que representa el argumento en la
definicin de la funcin.
Por tanto, la llamada en el programa fuente a la funcin, recogiendo el valor
devuelto en la variable resultado, ser:
resultado = sqr (num)
en Basic
resultado := sqrt (num);
en Pascal
resultado = sqrt (num);
en C
Donde num ser el parmetro actual o real en esta llamada a la funcin.

Expresiones y operadores

155

5.6. Funciones intrnsecas bsicas en C


Funcin
abs(x)
labs(x)

Prototipo

fabs(x)
ceil(x)

int abs(int x);


long int labs(long int
x);
double fabs(double x);
double ceil(double x);

floor(x)

double floor(double x);

fmod(x,y)

double fmod(double x,
double y);

exp(x)

double exp(double x);

log(x)
pow10(x)
log10(x)
pow(x,y)

double
double
double
double
double
double
double
double

sqrt(x)
hypot(x,y)

log(double x);
pow10(int x);
log10(double x);
pow(double x,
y);
sqrt(double x);
hypot(double x,
y);

sin(x)

double sin(double x);

cos(x)

double cos(double x);

tan(x)

double tan(double x);

asin(x)

double asin(double x);

acos(x)

double acos(double x);

atan(x)

double atan(double x);

atan2(x,y)

double atan2(double x,
double y);
double sinh(double x);
double cosh(double x);
double tanh(double x);
int atoi(const char
*s);

sinh(x)
cosh(x)
tanh(x)
atoi(s)

atol(s)

long int atol(const


char *s);

atof(s)

double atof(const char


*s);

Descripcin

Archivo
cabecera

Valor absoluto de x.
Valor absoluto de x.

math.h
math.h

Valor absoluto de x.
Redondea x al entero superior ms
prximo.
Redondea x al entero inferior ms
prximo (trunca x).
Resto de la divisin x / y, con el
mismo signo que x.
Exponencial de x: e elevado a x,
donde e=2.7182818...
Logaritmo neperiano de x
Potencia x de 10: 10 elevado a x.
Logaritmo decimal de x.
Potencia: x elevado a y.

math.h
math.h

Raz cuadrada de x.
Calcula la hipotenusa de un
tringulo rectngulo, dados sus
catetos x, y.
Seno del ngulo x expresado en
radianes.
Coseno del ngulo x expresado en
radianes.
Tangente del ngulo x expresado
en radianes.
Proporciona el ngulo en radianes
cuyo seno es x.
Proporciona el ngulo en radianes
cuyo coseno es x.
Proporciona el ngulo en radianes
cuya tangente es x.
Proporciona el ngulo en radianes
cuya tangente es x/y.
Seno hiperblico de x.
Coseno hiperblico de x.
Tangente hiperblica de x.
Convierte una cadena s en un
entero. Devuelve 0 si la conversin
no es posible.
Convierte una cadena s en un
entero largo. Devuelve 0 si la
conversin no es posible.
Convierte una cadena s en un real.
Devuelve 0 si la conversin no es
posible.

math.h
math.h

math.h
math.h
math.h
math.h
math.h
math.h
math.h

math.h
math.h
math.h
math.h
math.h
math.h
math.h
math.h
math.h
math.h
stdlib.h

stdlib.h

stdlib.h

156

Programacin en C

Funcin

Prototipo

toascii(c)
tolower(c)
toupper(c)
frexp(x,n)

ldexp(m,n)

modf(x,d)

Descripcin

Convierte el valor del argumento a


ASCII.
int tolower(int c);
Convierte una letra a minscula.
int toupper(int c);
Convierte una letra a mayscula.
double frexp(double x,
Proporciona de un nmero real x
int *n)
su mantisa (m) y su exponente (n),
donde x = m * 2n y
0,5 <= m < 1.
Devuelve la mantisa, y el
exponente lo almacena en la
variable entera referenciada por el
puntero n.
double ldexp(double m,
Dada una mantisa (m) y un
int n)
exponente (n), proporciona el
nmero real correspondiente, es
decir, calcula m * 2n.
double modf(double x,
Proporciona de un nmero real x
double *d)
su parte entera y su parte decimal.
Devuelve la parte entera, y la parte
decimal la almacena en la variable
referenciada por el puntero d.
Tabla 5.15 Principales funciones intrnsecas del lenguaje C
int toascii(int c);

Archivo
cabecera
ctype.h
ctype.h
ctype.h
math.h

math.h

math.h

Existen tambin funciones bsicas para el manejo de cadenas de caracteres, las


cuales se vern en el tema especfico de tratamiento de cadenas de caracteres.
En C no es obligatorio recoger el valor que devuelve una funcin. Si no se hace,
simplemente ste se perder.
Una funcin puede formar parte de cualquier expresin que incluya operadores u
otras funciones.
EJEMPLO

Dada la siguiente ecuacin:

ln b

b2

4ac

e 2a

Donde a, b y c son variables reales, en C podra escribirse como:

x = (log(b) sqrt(fabs(b*b 4*a*c))) / exp(2*a);

5.6.1. Archivos cabecera en C


Para utilizar una funcin de biblioteca en C es necesario incluir su prototipo en la
parte principal del programa. Esta informacin se encuentra dentro de ciertos archivos
(archivos de cabecera) que proporciona el compilador.
La inclusin de la informacin se realiza con la instruccin al compilador
include.
#include <nombre_de_archivo_cabecera>

Expresiones y operadores

157

El preprocesador incluir la informacin de los archivos cabecera en el programa


fuente antes de proceder a la compilacin y la generacin del ejecutable.
Los nombres de estos archivos son especiales de cada compilador. Nombres
comnmente usados son math.h, stdlib.h, ctype.h, stdio.h...
EJEMPLO

Se esquematiza a continuacin un programa en C para calcular el mdulo de


un vector tridimensional de componentes (x, y z), utilizando la frmula:

mdulo

x2

y2

z2

#include <stdio.h>6
#include <math.h>
double x, y, z, modulo;
/* Aqu se codificarn las sentencias
para leer de teclado los valores de las
coordenadas del vector x, y y z */
modulo = sqrt(x * x + y * y + z * z);
/* Aqu se codificarn las sentencias
para escribir en pantalla el valor de la
variable modulo calculada */

EJEMPLO

Se presentan diversas funciones intrnsecas y el resultado que proporcionan


de acuerdo al parmetro de entrada.
char minus, mayus, letra= 'a';
int num1=-57, num2;
double realalto, realbajo;
float numreal = 5.7;
double resto, raiz;

Llamada a la funcin

Resultado

Archivo cabecera

mayus=toupper(letra);
minus=tolower('B');
mum2=abs(num1);
realalto=ceil(numreal);
realbajo=floor(5.7);
resto=fmod(7,2);
resto=fmod(25.63,3);
raiz=sqrt(abs(num1));

mayus='A'
minus='b'
num1=57
realalto=6.0
realbajo=5.0
resto=1
resto=1.63
raiz=7.5498

#include
#include
#include
#include
#include
#include
#include
#include

<ctype.h>
<ctype.h>
<math.h>
<math.h>
<math.h>
<math.h>
<math.h>
<math.h>

La biblioteca estndar. Con objeto de mantener el lenguaje lo ms sencillo posible, muchas


sentencias que existen en otros lenguajes, no tienen su contrapartida en C. Por ejemplo, en C no
hay sentencias para entrada y salida de datos. Es evidente, sin embargo, que sta es una
funcionalidad que hay que cubrir de alguna manera. El lenguaje C lo hace por medio de
funciones preprogramadas que proporciona el propio compilador. Estas funciones estn
agrupadas en un conjunto de bibliotecas de cdigo objeto, que constituyen la llamada biblioteca
estndar del lenguaje.

158

Programacin en C

5.7. Cuestiones y ejercicios


1.

Tienen sentido las siguientes sentencias en C? Por qu?

2.

Cmo se puede cambiar el valor de una expresin a un tipo de datos


diferente? Cmo se llama a esto?
Qu se entiende por precedencia de operadores? Cules son las
precedencias relativas de los operadores aritmticos, relacionales y lgicos?
Describir los operadores relacionales en C. Qu tipo de operandos admiten?
Qu tipo de resultado devuelven?
Codificar en C cada una de las siguientes ecuaciones:

a - b = c;

3.
4.
5.

ma

cos eno 2T
x1 y 2
9
C 32
5

W
T
6.

3 - c = d + 4;

ax 2

tan

Z
B

x1 x 2
x1 x 2

bx c
Z

x2

y2

Dados los siguientes valores para las variables x, y, z, evaluar las


expresiones y responder si el resultado es verdadero o falso.
x=0.01, y=0, z=1.1
(a) (x && y) || (x && z)
(b) (x || ! y) && (! x || z)
(c) x || y && z
(d) !(x || y) && z

7.

Las siguientes expresiones tienen sentido, pero no son vlidas de acuerdo


con las reglas sintcticas del lenguaje C. Rescribirlas como expresiones
lgicas vlidas en este lenguaje.
(a)
(c)
(d)
(e)
(f)

x > y >= z
x, y y z son mayores que cero
x es diferente a y y a z
x igual a y y tambin igual a z
x es menor o igual que 500 y mltiplo de 5, pero
distinto de 100
(g) x igual a y pero distinto de z, y adems z mayor que
15 o distinto de 7.

8.

Escribir el resultado de las siguientes expresiones:


a)
b)
c)
d)

9.

5 + 3) / 2 *
35 > 47 && 9
9 == 15 || 8
8 * 2 < 5 ||

3) / 2 - (int) 28.75 / 4 + 29 % 3 * 4
== 9 || 35 != 3 + 2 && 3 >= 3
!= 5 && 7 > 4
7 + 2 > 9) && 8 - 5 < 18

Si se ha definido como int la variable resul, cul ser su valor despus


de ejecutarse la asignacin siguiente?
resul = 'C' - (float) 5 / 2 + 3.5 + 0.4

Expresiones y operadores

10.

159

Cul ser el resultado de las siguientes expresiones? Indica si habr prdida


de informacin o promocin de la misma.
int a = 3 / 5 + 0.75 / 2;
float b = 50 / 2 + 7 % 3;
int c = 1.75E9;
char d = 160 /2 + 5;

11.
12.
13.
14.
15.
16.

Qu se entiende por asociatividad? Cul es la asociatividad de los


operadores aritmticos, relacionales y lgicos?
Cmo se puede determinar el nmero de bytes que ocupa cada tipo de dato
en un compilador de C?
Describir los operadores lgicos en C. Qu tipo de operandos admiten?
Qu tipo de resultado devuelven?
Describir las dos formas diferentes de utilizar los operadores monarios de
incremento y decremento. Cul es la diferencia entre ellas?
Qu es un archivo de cabecera? Para qu vale? Cmo se referencia en un
programa?
Cul es el resultado de la evaluacin en C de la siguiente expresin?
1 + 2 * 7 / 3 + 1

Colocar adecuadamente los parntesis a la expresin anterior para que el


resultado sea:
a) 8

b) 5

c) 7

d) 4

17.

Cul es el resultado de la evaluacin en C de la siguiente expresin?

18.

Con: a=43, b=12, c=7, d=0. Podras colocando adecuadamente


parntesis modificar el resultado?
Con: a=0, b=8, c=8, d=1. Podras colocando adecuadamente
parntesis modificar el resultado?
Qu diferencia existe entre las siguientes sentencias de asignacin?

! a && (b > c) || d

a = b
a = 7

19.

21.

a = 'b'
a = '7'

Qu resultado dar la evaluacin de las siguientes expresiones en C?


13
13
13
-13
-13

20.

y
y

/ 0
/ 4
/ -4
/ 4
/ -4

13
13
13
-13
-13

% 0
% 4
% -4
% 4
% -4

Prestar atencin al signo del resultado.


Escribir un programa en C que las evale y presente el resultado.
Emparejar la expresin lgica de la columna de la izquierda con la
correspondiente de la columna de la derecha que defina la misma condicin.
(a)
(b)
(c)
(d)
(e)

(x
(x
(x
(x
(x

< y) && (y < z)


> y) && (y >= z)
!= y) || (y == z)
== y) || (y <= z)
== y) && (y == z)

(1)
(2)
(3)
(4)
(5)

! (x != y) && (y == z)
! ((x <= y) || (y < z))
((y < z) || (y == z))) || (x == y)
!(x >= y) && !(y >= z)
! ((x == y) && (y != z))

Prueban las dos siguientes expresiones la misma condicin?


a) x != y

b) (x || y) && !(x && y)

160

22.

Programacin en C

Dados los siguientes valores de i, j, k y m, aadir los parntesis que sean


necesarios a las expresiones para que se evalen todas dando valor cierto.
i=10, j=19, k=1, m= 0
(a) i == j || k
(b) i >= j || i <= j && k
(c) !k || k
(d) ! m && m

23.

Podras modificar ahora los parntesis para que el resultado fuera falso?
Cada una de las siguientes expresiones utiliza una funcin de biblioteca.
Identificar el propsito de cada una de ellas e indicar cul es su archivo de
cabecera.
abs(i - 2 * j)
toupper(d)
floor(x + y)
log(x)
fmod(x,y)
pow(x - y, 3.0)

fabs(x + y)
ceil(x)
exp(x)
sqrt(pow(x,2) + pow (y,2))
tolower(65)
sin(x - y)

6. Entrada y salida de datos por pantalla


Un programa de ordenador es un conjunto de instrucciones rdenes dadas a la
mquina que producirn la ejecucin de una determinada tarea.
Un programa ser til si es posible que en cada ejecucin pueda trabajar con una
coleccin distinta de datos de entrada, con los cuales realizar los clculos y
algoritmos codificados, aportndonos los resultados correspondientes.
Dispositivos habituales por los que introducir datos a un programa son: teclado,
unidades de disco... Dispositivos habituales en los que presentar los resultados son:
pantalla, impresora, unidades de disco...
En este captulo se estudiar la entrada de datos por teclado y la salida por pantalla,
postergando la entrada y salida mediante ficheros en disco para un captulo posterior.
As, este captulo se ha organizado en tres secciones. En la primera de ellas se
introducen los conceptos genricos de las operaciones de entrada y salida. La seccin
dos presenta las funciones bsicas de entrada y salida que se ofrecen en el lenguaje C.
Finalmente, la tercera seccin presenta una lista de cuestiones y ejercicios de repaso.

6.1. Operaciones de entrada/salida


Las operaciones de entrada de datos en un programa se conocen como operaciones de
lectura (read). Los datos se introducen al programa mediante dispositivos como
teclado, disco... Los datos de entrada se almacenan en variables, de forma que pueden
ser utilizados en expresiones, argumentos en funciones..., es decir, manipulados.
En la escritura de algoritmos las acciones de lectura se representan como:
leer (dispositivo, lista de variables de entrada)
Si no se especifica explcitamente el dispositivo de entrada, normalmente se asume
por defecto el teclado.
Por su parte, las operaciones de salida de datos en un programa s conocen como
operaciones de escritura (write). La informacin de salida se presenta en dispositivos
como pantalla, impresora, disco...
En la escritura de algoritmos las acciones de escritura se representan como:
escribir (dispositivo, lista de expresiones y variables
de salida)
Si no se especifica explcitamente el dispositivo de escritura, normalmente se
asume por defecto la pantalla.
EJEMPLO

La sentencia:
leer (cantidad_inicial, porcentaje, duracion)
Representa en pseudocdigo la lectura de teclado de tres valores de entrada
que se asignarn a las variables cantidad_inicial, porcentaje y
duracion.
El siguiente fragmento de cdigo:
suma_total 25.67
escribir ("El resultado es ", suma_total)
- 161 -

162

Programacin en C

Representa en pseudocdigo la salida a pantalla del mensaje El


resultado es seguido del valor que tenga la variable suma_total. Es
decir:
El resultado es 25.67
Cada lenguaje de programacin dispone de sus propias funciones y/o
procedimientos de entrada/salida (ver Tabla 6.1), que aunque bsicamente realizan
acciones comunes, sus caractersticas y limitaciones suelen ser diferentes.
Nombre funcin o procedimiento
Pascal
Basic
Cobol
C
Pseudocdigo
read
input
read
scanf
leer
Entrada
write
print
display
printf
escribir
Salida
Tabla 6.1. Funciones de E/S bsicas en diferentes lenguajes de programacin
Operacin

6.2. Funciones de entrada/salida en C


Con objeto de mantener el lenguaje lo ms sencillo posible, muchas sentencias que
existen en otros lenguajes no tienen su contrapartida en el lenguaje C. Por ejemplo, en
C no hay sentencias para la entrada y la salida de datos. Es evidente, sin embargo, que
sta es una funcionalidad que hay que cubrir de alguna manera. El lenguaje C lo hace
por medio de funciones preprogramadas que proporciona el propio compilador. Estas
funciones estn agrupadas en un conjunto de bibliotecas de cdigo objeto, que
constituyen la llamada biblioteca estndar del lenguaje. En el caso concreto de las
funciones de entrada y salida, su archivo de cabecera es stdio.h.
A continuacin se van a presentar las funciones bsicas que ofrece el lenguaje C
para soportar las operaciones de entrada y salida.

6.2.1. Funcin puts()


La funcin puts() sirve para mostrar una cadena de caracteres por pantalla. En la
Tabla 6.2 se resumen los datos fundamentales necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

int puts (char *s)


stdio.h
Variable/constante cadena de caracteres a imprimir en pantalla
Un valor no negativo o EOF si error
Imprime un mensaje en pantalla
Tabla 6.2. Funcin puts()

Los mensajes deben de ser cadenas de caracteres sin datos variables, aunque
pueden incluir espacios en blanco.
Esta funcin tiene un nico parmetro (literal o variable cadena de caracteres), que
es el mensaje a imprimir en pantalla. Al mostrar el mensaje por pantalla genera
automticamente un salto de lnea al final del texto impreso en pantalla.
EJEMPLO

Entrada y salida de datos por pantalla

163

El siguiente fragmento de cdigo en C:


char titulo[] = "CONVERSION DE GRADOS CENTIGRADOS A FAHRENHEIT";
char subrayado[] = "-------------------------------------------";
...
puts(titulo);
puts(subrayado);
puts("");
puts("El programa convertir la temperatura introducida");
puts("en grados Centgrados a grados Fahrenheit.");

Genera en pantalla la siguiente salida:

CONVERSION DE GRADOS CENTIGRADOS A FAHRENHEIT


--------------------------------------------El programa convertir la temperatura introducida
en grados Centgrados a grados Fahrenheit.

6.2.2. Funcin gets()


La funcin gets() sirve para introducir una cadena de caracteres por el teclado. En
la Tabla 6.3 se resumen los datos fundamentales necesarios para el uso de esta
funcin.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

char *gets (char *s)


stdio.h
Variable cadena de caracteres a leer de teclado
La propia cadena leda o EOF si se produce un error
Lee una cadena de caracteres de teclado
Tabla 6.3. Funcin gets()

La funcin gets() tiene un nico parmetro (variable cadena de caracteres),


donde se almacenar el texto que se lee desde el teclado.
La cadena de caracteres que se teclee puede incluir espacios en blanco, pero debe
terminar en un carcter de fin de lnea (intro).
EJEMPLO

El siguiente programa en C pide el nombre del usuario, presentando


posteriormente un saludo por la pantalla.
#include <stdio.h>
void main(void) {
char nombre[80];
puts("Introduzca su nombre:");
gets(nombre);
puts("");
puts("Hola");
puts(nombre);
puts("Su ordenador personal le saluda.");
}

La ejecucin del mismo produce la siguiente secuencia de entradas/salidas:

164

Programacin en C

Introduzca su nombre:
Juan Prez <intro>
(entrada del usuario)
Hola
Juan Prez
Su ordenador personal le saluda.

6.2.3. Funcin putchar()


La funcin putchar() sirve para presentar un carcter por la pantalla. En la Tabla
6.4 se resumen los datos fundamentales necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

int putchar (int ch)


stdio.h
Variable carcter a imprimir en pantalla.
El propio carcter del parmetro o EOF si se produce un error
Imprime un carcter en pantalla
Tabla 6.4. Funcin putchar()

La funcin putchar() tiene un nico parmetro (carcter a imprimir en


pantalla). Este carcter puede ser una secuencia de escape.
EJEMPLO

Se presentan diversos grupos de sentencias en C y el resultado que


producirn en su salida:
Sentencias
putchar('a');
putchar('\n');
putchar('c');
putchar('d');
Sentencias
putchar('A');
putchar(65);
putchar('\101');
putchar('\x41');
Sentencias
putchar('A');
putchar('\t');
putchar('C');
putchar('D');
Sentencias
char letra = 'p';
...
putchar(letra);
putchar('e');
putchar(letra);
putchar('e');
putchar('\'');
putchar('s');

Salida en pantalla
a
cd
Salida en pantalla
AAAA

Salida en pantalla
A
CD

Salida en pantalla
pepe's

Entrada y salida de datos por pantalla

165

6.2.4. Funciones getch() y getche()


Ambas funciones sirven para introducir un carcter por el teclado, aunque no se
incluyen en el estndar de este lenguaje. En la Tabla 6.5 se resumen los datos
fundamentales necesarios para el uso de estas funciones.
Prototipos:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

int getch (void)


int getche (void)
conio.h (No es estndar ANSI)
Ninguno
El carcter ledo de teclado
Leer un carcter de teclado
Tabla 6.5. Funciones getch() y getche()

Ambas funciones se usan para leer un carcter de teclado sin necesidad de pulsar
<intro> despus de pulsar el carcter. En el momento en que se pulse un carcter
en el teclado, ste es devuelto por las funciones y se contina con la siguiente
instruccin.
No tienen argumentos, pero requieren siempre un par de parntesis vacos detrs
del nombre de la funcin.
La forma de recoger el valor ledo de teclado ser realizar una asignacin a una
variable de tipo carcter:
variable_caracter = getch();
variable_caracter = getche();

Si no se realiza esta asignacin, simplemente el valor ledo de teclado se pierde (no


se recoge en ninguna variable):
getch();
getche();

La diferencia entre ambas funciones es que con getche() el carcter tecleado


aparece en pantalla, mientras que con getch() el carcter no aparece.
EJEMPLO

Las dos siguientes sentencias en un programa pararan la ejecucin de ste


hasta que el usuario pulse cualquier tecla.
puts("Pulse una tecla para continuar.");
getch();

Las siguientes sentencias piden la pulsacin de una tecla y posteriormente


muestran dicha tecla en pantalla.
char c;
puts("Pulse una tecla.");
c = getch();
puts("La tecla pulsada es");
putchar(c);

Lo que se vera en pantalla sera:


Pulse una tecla
La tecla pulsada es
B
(Suponiendo que se hubiera pulsado la tecla B)

6.2.5. Funcin getchar()


La funcin getchar() sirve para introducir un carcter por el teclado. En la Tabla

166

Programacin en C

6.6 se resumen los datos fundamentales necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

int getchar (void)


stdio.h
Ninguno
El carcter ledo de teclado
Leer un carcter de teclado
Tabla 6.6. Funcin getchar()

La funcin getchar() permite la entrada de caracteres, uno a uno, desde


cualquier dispositivo de entrada (discos, teclado...).
Se puede utilizar esta funcin para leer una cadena de caracteres introducida desde
el teclado, recibindola carcter a carcter mediante un bucle. El <intro>, que
marca el final de la cadena tecleada, tambin ser recibido y deber ser tratado
convenientemente.
Esta funcin no tiene argumentos, pero requiere siempre un par de parntesis
vacos detrs del nombre de la funcin.
La forma de recoger el valor ledo de teclado ser realizar una asignacin a una
variable de tipo carcter.
variable_caracter = getchar();
Si no se realiza esta asignacin, simplemente el valor ledo de teclado se pierde (no
se recoge en ninguna variable).
getchar();
EJEMPLO

Sea el siguiente programa escrito en lenguaje C:


#include <stdio.h>
#define EOL '\n'
void main (void) {
int cont=0;
char letra;
printf("Introduzca una cadena de caracteres: ");
while ( (letra = getchar() ) != EOL) {
putchar(letra);
cont++;
}
printf("\nLa cadena introducida tiene %d
caracteres.\n", cont);
}

Si el usuario teclea:
LAS TARJETAS DOMINARAN EL COMERCIO ELECTRONICO<intro>

A travs de la funcin getchar() se reciben, de uno en uno, los caracteres


tecleados, incluido el carcter <intro>. Segn se van recibiendo se van
contando e imprimiendo en pantalla. Al recibir el carcter <intro> se da
por finalizada la cadena tecleada.

6.2.6. Funcin printf()


La funcin printf() sirve para presentar datos por pantalla con un formato
asociado. En la Tabla 6.7 se resumen los datos fundamentales necesarios para el uso
de esta funcin.

Entrada y salida de datos por pantalla

Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

167

int printf(const char *formato, arg1,arg2,...,argn)


stdio.h

Puede recibir un nmero cualquiera de argumentos, de cualquier tipo y


formato
El nmero de caracteres escritos o EOF si error
Escribir literales y datos variables en pantalla
Tabla 6.7. Funcin printf()

El prototipo de la funcin printf() es ms complejo que el de las funciones


vistas hasta el momento. Para comprenderlo mejor se va a explicar cada uno de los
elementos que lo componen. As:
formato: hace referencia a una cadena de caracteres (cadena de
control) que contiene informacin sobre el formato de la salida.
arg1, arg2, ..., argn: son los argumentos que representan los datos de
salida. Los argumentos pueden ser constantes, variables simples,
cadenas de caracteres o expresiones ms complejas que involucran
operadores y llamadas a funciones.
La cadena de control puede constar de hasta tres tipos de informacin:
Texto, que ser mostrado en salida literalmente como ha sido escrito.
Secuencias de escape, que sern interpretadas convenientemente en la
salida.
Especificadores de formato, que son ciertos caracteres precedidos del
carcter tanto por ciento (%):
o Debe haber tantos como argumentos.
o Se corresponden por posicin con los argumentos (primero con
primero, segundo con segundo...).
o Representan la conversin que se realizar sobre el argumento
correspondiente antes de mostrarlo en la salida. El
especificador de formato debe coincidir con el tipo de dato que
se quiere imprimir, o ser compatible siguiendo las reglas de
conversin de tipos.
En la Tabla 6.8 se muestran distintos especificadores de formato para la funcin
printf().Dependiendo del compilador de C, los especificadores de formato de los
tipos short, long, double y long double pueden ser otros.
EJEMPLO

Sea el siguiente programa escrito en C:


#include <stdio.h>
#include <math.h>
void main (void) {
int num1 = 120, num2 = 130;
int resultado;
resultado = num1 + num2;
printf("La suma de %d y %d es %d\n", num1, num2,resultado);
printf("Raz cuadrada de su suma: %f\n", sqrt(num1+num2));
}

Presenta la siguiente salida por pantalla:

La suma de 120 y 130 es 250


Raz cuadrada de su suma: 15.811388

168

Programacin en C

Algunas modificaciones en los especificadores de formato y salida


correspondiente pueden ser:
printf("Raz cuadrada de su suma: %e\n", sqrt(num1+num2));
Raz cuadrada de su suma: 1.581139e+01
printf("Raz cuadrada de su suma: %E\n", sqrt(num1+num2));
Raz cuadrada de su suma: 1.581139E+01

Tipo del
argumento

Especificadores de formato
%d

%i

Entero con signo en decimal


Entero sin signo en octal, sin el cero
inicial
Entero sin signo en hexadecimal en
minsculas, sin el prefijo 0x
Entero sin signo en hexadecimal en
maysculas, sin el prefijo 0X

%o
int

%x
%X
%h

short

%hd
%hx
%hX

%ho

long
%ld
unsigned int
unsigned
short
unsigned
long

%lo

%lx

%lu

float
%G

long double
carcter
cadena
puntero

%lf

Se aade prefijo h para formatos octal y


hexadecimal
%lX Se aade prefijo l

%u
%hu

%f
%e
%E
%g

double

Comentario

Real con signo


Real con signo usando notacin e
Real con signo usando notacin E
Real con signo que escoge
automticamente entre el formato e f,
segn sea el tamao del nmero. No se
visualizan ni los ceros finales ni el punto
decimal si no es necesario
Real con signo que escoge
automticamente entre el formato E F,
segn sea el tamao del nmero. No se
visualizan ni los ceros finales ni el punto
decimal si no es necesario
%lg Se aade prefijo l

%le
%lE
%lG
%Lf %Le %LE %Lg
%LG
%c
%s
%p

Se aade prefijo L

Imprime la direccin contenida en el


puntero
%%
Imprime el smbolo %
Tabla 6.8. Especificadores de formato para la funcin printf()

Entrada y salida de datos por pantalla

169

EJEMPLO

Sean las siguientes definiciones de variables:


char concepto[]= "cremallera";
int num_partida=1235;
float coste=0.05;

Se presenta a continuacin diversas llamadas a la funcin printf() y su


correspondiente salida en pantalla.
Sentencia printf():
printf("%s %d %f", concepto, num_partida, coste);
Salida en pantalla:
cremallera 1235 0.050000
Sentencia printf():
printf("%s%d%f", concepto, num_partida, coste);
Salida en pantalla:
cremallera12350.050000
Sentencia printf():
printf("%s\t%d\n%g", concepto, num_partida, coste);
Salida en pantalla:
cremallera
1235
0.05
EJEMPLO

Sea las siguientes definiciones de variables:


char letra= 'A';
float num= 123.456;

Se muestra la salida de la misma variable en distintos formatos:


Sentencia printf():
printf("%c, %d, %o, %x", letra, letra, letra, letra);
Salida en pantalla:
A, 65, 101, 41
---------------------------------Sentencia printf():
printf("%f, %e, %E, %g", num, num, num, num);
Salida en pantalla:
123.456000, 1.234560e+02, 1.234560E+02, 123.456
EJEMPLO

En este ejemplo se va a mostrar la diferencia de comportamiento entra las


funciones printf() y puts(). Mientras que la funcin puts() realiza
un salto de lnea implcito al final de la cadena de caracteres que imprime en
pantalla, la funcin printf() no lo hace a no ser que se coloque la
secuencia de escape \n en su cadena de control de forma explcita. As, sea
el siguiente fragmento de un programa escrito en lenguaje C:
char nombre[80];
puts("Introduzca su nombre: ");
gets(nombre);
puts("");
puts("Hola");
puts(nombre);
puts(", tu ordenador personal te saluda.");

170

Programacin en C

La salida por pantalla sera la siguiente:


Introduzca su nombre:
PEDRO PEREZ
Hola
PEDRO PEREZ
, tu ordenador personal te saluda.

Si se hubiera utilizado la funcin printf():

char nombre[80];
printf("Introduzca su nombre: ");
gets(nombre);
printf("\nHola %s, tu ordenador personal te saluda.",nombre);

La salida por pantalla sera la siguiente:


Introduzca su nombre: PEDRO PEREZ
Hola PEDRO PEREZ, tu ordenador personal te saluda.
EJEMPLO

En el siguiente ejemplo slo se necesita imprimir texto en pantalla, por lo


que basta con considerar la cadena de control.
printf("Con diez caones por banda, \nviento en popa a toda vela\n");

El resultado sern dos lneas con las dos primeras estrofas de la famosa
poesa de Jos de Espronceda.
Con diez caones por banda,
viento en popa a toda vela

No es posible partir cadena de control en varias lneas con caracteres


<intro>, por lo que en este ejemplo, para aadir ms estrofas, una

forma sencilla, clara y ordenada puede ser la siguiente:

printf("%s\n%s\n%s\n%s\n",
"Con diez caones por banda,",
"viento en popa a toda vela,",
"no cruza el mar sino vuela,",
"un velero bergantn.");
EJEMPLO

Se muestran a continuacin varias sentencias printf() con secuencias de


escape en su cadena de control, as como el resultado en salida de las
mismas.
printf("\nAvanzo tres lneas \n\n\ny escribo.\n");
printf("

4\n");

printf("1234567890123456789012345678901234567890\n");
printf("Compra\tVenta\tTotal\n");
printf("1.100\t345\t755\n");
printf("1.520\t786\t734\n");
printf("ABCDEFG\b12345 \n");
printf("RSTUVWXYZ\r12345\n");

Entrada y salida de datos por pantalla

171

printf("\"El Quijote de la \'Mancha\' de Cervantes\" \n");


printf("\\Madrid\\ \n");
printf("El signo de porcentaje es %%.\n");
printf("\x41 \n"); /*Cdigo ASCII 65 es A. 41 en hexa

*/

printf("\x4b \n"); /*Cdigo ASCII 75 es K. 4b en hexa


printf("\x4B \n"); /*Cdigo ASCII 75 es K. 4B en hexa

*/
*/

printf("\51 \n");

*/

/*Cdigo ASCII 41 es ). 51 en octal

printf("\145 \n"); /*Cdigo ASCII 101 es e. 145 en octal */


printf("\a\a");
/*Suena pitido 2 veces
*/

Siendo el resultado en pantalla:


Avanzo tres lneas

y escribo.
1
2
3
4
1234567890123456789012345678901234567890
Compra Venta
Total
1.100
345
755
1.520
786
734
ABCDEF12345
12345WZYZ
"El Quijote de la 'Mancha' de Cervantes"
\Madrid\
El signo de porcentaje es %.
A
K
K
)
e
(En este momento sonar el pitido 2 veces)

6.2.6.1. Detalles adicionales de la funcin printf()


La funcin printf() incorpora algunos modificadores especialmente interesantes
para controlar el formato de los datos presentados en pantalla. A continuacin se va a
hacer un repaso de los casos ms significativos.
Especificacin de longitud mnima de campo en salida
La funcin printf() permite especificar cul va a ser la longitud mnima reservada
para mostrar un campo de datos en pantalla.
Para ello, se antepone al carcter de conversin del especificador de formato un
nmero entero.
Si el nmero de caracteres del dato es menor que la longitud de campo
especificada, el dato ser precedido por tantos espacios en blanco como sean
necesarios para que se consiga la longitud de campo especificada.

172

Programacin en C

Por el contrario, si el nmero de caracteres del dato es mayor que la longitud de


campo especificada, el dato ser visualizado completo en pantalla. Es decir, nunca se
pierde informacin.
EJEMPLO

A continuacin se muestran diferentes salidas en pantalla segn sea el


especificador de longitud mnima de campo.
int i = 12345;
. . .
printf("%d\n", i);
printf("%8d\n", i);
printf("%2d\n", i);
printf("%12d\n", i);
Salida en pantalla:
12345
12345
12345
12345

char cadena[] = "BLANCO Y VERDE";


. . .
printf("%s\n", cadena);
printf("%3s\n", cadena);
printf("%20s\n", cadena);
Salida en pantalla:
BLANCO Y VERDE
BLANCO Y VERDE
BLANCO Y VERDE

En datos reales, el especificador de longitud mnima de campo incluye el total de


caracteres del nmero a imprimir (parte entera, ms decimales, ms una posicin para
el punto decimal).
EJEMPLO

A continuacin se muestran diferentes salidas en pantalla segn sea el


especificador de longitud mnima de campo.
float f = 1.45;
. . .
printf("%g\n", f);
printf("%8g\n", f);
printf("%2g\n", f);

printf("%12g\n", f);
Salida en pantalla:
1.45

1.45

1.45
1.45

Especificacin de la precisin
Para las cantidades numricas reales la precisin significa el nmero de decimales a
mostrar. Para ello se antepone al carcter de conversin del especificador de formato
un punto y un nmero entero que representar el nmero de decimales a mostrar. El
nmero real se redondear si se le debe recortar, o se le aadirn ceros si se le debe
aumentar el nmero de decimales reales que tiene la cantidad para ajustarla a la
precisin indicada. Normalmente, si se especifica precisin se suele especificar
tambin el ancho de campo mnimo.
Para cantidades numricas enteras la precisin indica el mnimo nmero de cifras a
mostrar. Para ello se completar, si es necesario, con ceros por la izquierda el nmero
a mostrar.
Para cadenas de caracteres la precisin especifica el mximo nmero de caracteres
a mostrar.

Entrada y salida de datos por pantalla

173

EJEMPLO

Obsrvese la salida en pantalla dependiendo del especificador de precisin:


float f = 123.567;
. . .
printf("%f\n", f);
printf("%.5f\n", f);
printf("%.2f\n", f);
printf("%12.4f\n", f); /* Ancho de campo y precisin
*/
printf("%8.0f\n", f); /* Ancho de campo y 0 decimales */

Salida en pantalla:

123.567000
123.56700
123.57
123.5670
124

EJEMPLO

Obsrvese el comportamiento de la especificacin de la precisin con


cadenas de caracteres.
char mensaje = "Precisin con cadenas";
. . .
printf("%s\n", mensaje);
printf("%.9s\n", mensaje);
printf("%7.5s\n", mensaje);

Salida en pantalla:
Precisin con cadenas
Precisin
Preci
EJEMPLO

Obsrvese el comportamiento de la especificacin de la precisin con


variables int o long.
printf("%.8d\n", 12);
printf("%.6ld\n", 167L);
printf("%.3d\n", 31243);

Salida en pantalla:
00000012
000167
31243

Especificacin variable de la longitud mnima de campo y precisin


Puede ajustarse en ejecucin, de forma variable, el tamao mnimo y la precisin de
un campo, de forma que en cada ejecucin puedan ser distintos.
En el especificador de formato se introduce un carcter asterisco (*), en el lugar
del especificador de longitud mnima y precisin, informndole a la funcin
printf() de la longitud mnima y de la precisin mediante los parmetros
apropiados.

174

Programacin en C

EJEMPLO

Sea el siguiente fragmento de cdigo escrito en lenguaje C:


unsigned int lo, pr;
float numf = 123.456;
int numd = 145;
. . .
printf("Longitud mnima?: ");
scanf("%u", &lo);
printf("Nmero de decimales?: ");
scanf("%u", &pr);
printf("Salida 1:%*.*f\n", lo, pr, numf);
printf("Salida 2:%.*f\n", pr-1, numf);
printf("Salida 3:%*f\n", lo, numf);
printf("Salida 4:%10.*f\n", pr, numf);
printf("Salida 5:%*d\n", 5, numd);

Si la entrada al programa fuera:

Longitud mnima?: 15
Nmero de decimales?: 2

La salida en pantalla sera:


Salida
Salida
Salida
Salida
Salida

1:
123.46
2:123.5
3:
123.456000
4:
123.46
5: 145

Especificacin de relleno con dgito 0 para justificaciones a la derecha


Por defecto las cantidades numricas, cuando se especifica una longitud mnima de
campo en salida, se justifican a la derecha y se rellenan con espacios en blanco las
posiciones sobrantes a la izquierda.
Si se desea que en lugar de blancos se rellenen las posiciones sobrantes por la
izquierda con el dgito 0, se puede especificar colocando un 0 por delante de los
dgitos que indican la longitud mnima de campo.
EJEMPLO

Obsrvese el especificador %012.4f, que indica:


Longitud mnima total del dato 12 posiciones.
Nmero de decimales 4.
Rellenar por la izquierda con 0 las posiciones sobrantes.
float f = 123.456;
int
i = 78;
. . .
printf("%12.4f\n", f);
printf("%012.4f\n", f);
printf("%8d\n", i);
printf("%08d\n", i); //equivalente a printf("%.8d\n", i);

Salida en pantalla:

123.4560
0000123.4560
78
00000078

Entrada y salida de datos por pantalla

175

Especificacin de justificacin a la izquierda


Por defecto la justificacin en salida al especificar longitud mnima de campo es a la
derecha, rellenando con espacios en blanco por la izquierda las posiciones sobrantes.
Si se desea que la justificacin sea a la izquierda se coloca un signo menos (-)
delante de los dgitos que indican la longitud mnima de campo.
EJEMPLO

Obsrvese el resultado diferente de justificar a la derecha (por defecto) o a la


izquierda:
float f1 = 123.456;
float f2 = 23.4;
float f3 = 1333.456;
float f4 = 345.6788;
char mensaje = "Depende. De qu depende?";
printf("**%12.4f",
printf("**%12.4f",
printf("**%12.4f",
printf("**%12.4f",
printf("**\n");

f1);
f2);
f3);
f4);

printf("**%-12.4f",
printf("**%-12.4f",
printf("**%-12.4f",
printf("**%-12.4f",
printf("**\n");

f1);
f2);
f3);
f4);

printf("**%12.4s", mensaje);
printf("**%-12.4s", mensaje);
printf("**%.4s", mensaje);
printf("**\n");

Salida en pantalla:
**
123.4560**
23.4000**
1333.4561**
345.6788**
**123.4560
**23.4000
**1333.4561
**345.6788
**
**
Depe**Depe
**Depe**

Todos los especificadores comentados pueden combinarse entre s, de manera que


el formato general de un especificador de formato en la funcin printf() es:
Sintaxis:
%-0ancho.prec< l, h >C
Donde:
%
0
ancho
.
prec
< l, h>
C

Indicador de formato.
Signo - (indica ajuste a la izquierda).
Dgito 0 (si justificacin derecha, rellena con 0 los
caracteres en blanco de la izquierda).
Anchura mnima total del campo.
Un punto decimal.
Precisin.
Si enteros, l para long y h para short.
Carcter de conversin del especificador de formato.

EJEMPLO

Obsrvese la salida de las siguientes sentencias printf():

176

Programacin en C
int dd = 7, mm = 9, aa = 1997;
int hh = 6, mi = 9, ss = 56;

Salida en pantalla
printf("%02d/%02d/%4d\n", dd, mm, aa);
07/09/1997
printf("%d-%d-%d\n", dd, mm, aa);
7-9-1997
printf("%4d%02d%02d\n", aa, mm, dd);
19970907
printf("%d del %d de %d\n", dd, mm, aa);
7 del 9 de 1997
printf("%d del %d de %d.%d\n", dd, mm, aa/1000, aa%1000); 7 del 9 de 1.997
printf("%02d:%02d:%02d\n", hh, mi, ss);
06:09:56
printf(">>%10o<<\n", 12);
>>
14<<
printf(">>%120X<<\n", 12);
>>
C<<
printf(">>%70x<<\n", 12);
>>
c<<

6.2.7. Funcin scanf()


La funcin scanf() se puede considerar de alguna manera como la inversa de la
funcin printf(), pues sirve para introducir datos desde el teclado con un formato
asociado. En la Tabla 6.9 se resumen los datos fundamentales necesarios para el uso
de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

int scanf (const char *formato, arg1,arg2,...,argn)


stdio.h

Una cadena de caracteres de control de formato y una lista de


punteros a variables de cualquier tipo y formato
El nmero de campos (datos) ledos con xito
Leer de teclado variables de cualquier tipo
Tabla 6.9. Funcin scanf()

Como ya se ha explicado, la funcin scanf() se usa para introducir datos a un


programa procedentes del teclado, esto es, permite leer cualquier cantidad y tipo de
dato, ya sean numricos, caracteres individuales o cadenas de caracteres. Adems,
cabe destacar que lleva implcito un retorno de carro en pantalla a la introduccin del
ltimo dato.
El prototipo de la funcin scanf() es muy parecido al de la funcin printf().
Para comprenderlo mejor se va a explicar cada uno de los elementos que lo
componen. As:
formato: hace referencia a una cadena de caracteres (cadena de
control) que contiene informacin sobre el formato de los datos que se
van a introducir mediante el teclado.
arg1, arg2, ..., argn: son los argumentos que representan los datos de
entrada. Los argumentos slo pueden ser variables simples o cadenas de
caracteres, ya que es donde se almacenarn los datos introducidos desde
el teclado.
o En realidad los argumentos son punteros, por lo que deben ir
siempre precedidos del carcter &, salvo que sean cadenas de
caracteres1.
1

En la funcin scanf() los argumentos que siguen a la cadena de control deben ser pasados
por referencia, ya que la funcin los lee y tiene que trasmitirlos al mdulo que la ha llamado.
Para ello, dichos parmetros deben estar constituidos por las direcciones de las variables en las
que hay que depositar los datos (operador indireccin & precediendo al nombre de la variable),

Entrada y salida de datos por pantalla

177

La cadena de control puede constar de:


Especificadores de formato, que son ciertos caracteres precedidos del
carcter tanto por ciento (%):
o Debe haber tantos como argumentos.
o Se corresponden por posicin con los argumentos (primero con
primero, segundo con segundo...).
o Representan el formato en que se leern los datos de entrada
correspondientes.
Caracteres espacio.
Otros caracteres no espacios, para entradas con formato. En este caso
dichos caracteres se utilizan para tratar de detectar la presencia de
caracteres idnticos en la entrada por teclado.
En la Tabla 6.10 se muestran distintos especificadores de formato para la funcin
scanf().Dependiendo del compilador de C, los especificadores de formato de los
tipos short, long, double y long double pueden ser otros. Por ejemplo,
algunas versiones de C permiten el uso de maysculas como caracteres de conversin
para enteros largos (con o sin signo).
%D
long int
Tipo del Argumento

int

%o
%x

short
long
unsigned int
unsigned short
unsigned long
float
double
long double
carcter
cadena

Especificadores de
formato
%d
%i

%h
%hd
%ho
%hx
%ld
%lo
%lx
%u
%hu
%lu
%f %e %g
%lf
%le
%lg
%Lf %Le %Lg
%c
%s
%[...]

Comentario
Entero con signo en decimal
Entero en decimal, octal (si comienza por 0)
hexadecimal (si comienza por 0x 0X)
Entero en octal, sin el cero inicial
Entero en hexadecimal, sin el prefijo 0x
0X
Se aade prefijo h para octal y hexadecimal
Se aade prefijo l

Real con signo


Se aade prefijo l
Se aade prefijo L

Cadena de caracteres de la que slo se leern


los caracteres que se especifiquen entre
corchetes
Tabla 6.10. Especificadores de formato para la funcin scanf()

En la entrada de datos con scanf(), un dato es diferenciado del siguiente porque


en medio de ellos se teclea uno o varios de los siguientes caracteres: carcter espacio,
y no por las propias variables. Una excepcin son las cadenas de caracteres, cuyo nombres es
ya de por s una direccin (un puntero), y por tanto no debe ir precedido por el operador
indireccin & en la llamada.

178

Programacin en C

carcter tabulador o carcter de fin de lnea. Estos tres caracteres reciben el nombre
genrico de caracteres separadores.
EJEMPLO

Observar los especificadores de formato utilizados en las siguientes


sentencias scanf() y las formas en que es posible separar los datos de
entrada al teclearlos:
char concepto[80];
int
num_partida;
float coste;

Sentencia scanf():

scanf("%s %d %f", concepto, &num_partida, &coste);

Al escribir los datos de entrada stos podrn ser separados por:


Espacios en blanco.
cremallera

1235

0.050000

Caracteres de tabulacin.
cremallera

1235

0.050000

Caracteres de fin de lnea (<intro>).


cremallera
1235
0.050000

Una combinacin de las anteriores.


cremallera
0.050000

1235

En aquellos casos en los que la cadena de control de scanf() est constituida por
caracteres que no sean caracteres de separacin, ni tampoco un especificador de
formato, dichos caracteres sern detectados en la lnea de entrada y descartados. Es
una forma de leer datos con un determinado formato.
EJEMPLO

Como ejemplo de entrada de datos con un determinado formato se presentan


las siguientes sentencias scanf():
scanf("%d:%d", &hh, &mm);
o Espera que la entrada de datos tenga el siguiente formato:
12:34.
o El primer entero (12) ser asignado a la primera variable
(hh). Posteriormente se detectarn los dos puntos y sern
descartados y, por ltimo, el segundo entero (34) ser
asignado a la segunda variable (mm).
scanf("%d,%d,%d", &dia, &mes, &anio);
o Espera que la entrada de datos tenga el siguiente formato:
6,10,1999.
scanf("%d-%d-%d", &d1, &d2, &d3);
o Espera que la entrada de datos tenga el siguiente formato:
6-10-1999.
o Si se desearn introducir nmeros negativos: -7--54-128.

Entrada y salida de datos por pantalla

179

Si la entrada no coincidiera con los caracteres de la cadena de control, se terminar


de forma prematura la ejecucin de scanf(), quedando, tal vez, algunas variables
con valores indeterminados no deseados:
EJEMPLO

Sea la siguiente sentencia scanf():

scanf("%d:%d", &hh, &mm);

Si se teclea la siguiente entrada:


12, 34

El valor 12 se asignar a la variable hh, pero la variable mm contendr un


valor indeterminado.
Si con el especificador de formato [...] para cadenas de caracteres, se indica entre
los corchetes varios caracteres, la lectura con scanf() terminar en el momento en
que se encuentre un carcter no comprendido entre los corchetes, es decir, la funcin
scanf() considerar como carcter separador todo carcter no incluido entre los
corchetes.
EJEMPLO

Supngase el siguiente fragmento de un programa escrito en lenguaje C:


char concepto[80];
scanf("%[ABCD]", concepto);

Una entrada como la siguiente:


ACDEBFH

Har que la variable concepto se cargue slo con el valor ACD, ya que la E
no se encuentra entre los corchetes.
Al leer con scanf() una cadena de caracteres, como el carcter espacio es un
carcter separador, las cadenas que se tecleen no podrn incluir espacios en blanco.
Existen dos posibles alternativas para solucionar este problema.
1. Emplear la funcin gets(), que es la solucin ms frecuentemente
utilizada.
2. Utilizar scanf() con el especificador de formato [..]. Dentro de los
corchetes se debe incluir el carcter acento circunflejo (^), que provoca
que los caracteres que se especifiquen posteriormente sean considerados
de forma opuesta, es decir, se leern caracteres del dispositivo de entrada
mientras no se encuentren dentro de los corchetes.

EJEMPLO
Sea el siguiente fragmente de un programa escrito en lenguaje C:
char concepto[80];
scanf("%[^\n]", concepto);

Entonces una entrada como:


CIUDAD

DE

MLAGA<intro>

Har que toda la cadena tecleada, incluidos los espacios en blanco, ser
cargada en la variable concepto, ya que la entrada estndar terminar slo
cuando encuentre un carcter fin de lnea (<intro>).

180

Programacin en C

Buen estilo de programacin


Aunque la funcin scanf() permite la introduccin de varios datos en la misma
lnea de entrada, esto no es una tcnica habitual en programacin. Un hbito de
buen estilo de programacin es realizar la lectura de los datos de entrada de uno en
uno y, adems, siempre acompaando a cada lectura de datos de un rtulo por
delante, que especifique claramente al usuario el dato que est introduciendo.
Es decir, si hay que leer tres datos, lo ms correcto ser realizarlo de la siguiente
forma:
int
num_partida;
float coste;
. . .
printf("Introduzca el concepto (mx. 79 caracteres): ");
gets(concepto);
printf("Introduzca el nmero de unidades iniciales: ");
scanf("%d", &num_partida);
printf("Introduzca el coste por unidad: ");
scanf("%f", &coste);

De forma que la ejecucin de este cdigo producir el siguiente efecto en pantalla:


Introduzca el concepto (mx. 79 caracteres): CREMALLERA
Introduzca el nmero de unidades iniciales: 1234
Introduzca el coste por unidad: 0.05

6.2.7.1. Detalles adicionales de la funcin scanf()


La funcin scanf(), al igual que sucede con la funcin printf(), incorpora
algunos modificadores especialmente interesantes para controlar el formato de los
datos que se introducen por teclado. A continuacin se va a hacer un repaso de los
casos ms significativos.
Especificacin de longitud mxima de campo en entrada
En la lnea de entrada los caracteres consecutivos, que no sean espacios, tabuladores o
fin de lnea, componen el dato de entrada. Cualquiera de los tres tipos de caracteres
anteriormente comentados sirve para separar en la lnea de entrada un dato del
siguiente.
Es posible delimitar el nmero mximo de caracteres para un campo de entrada.
Para ello se introduce en la cadena de control un entero, que indica la longitud de ese
campo, entre el carcter porcentaje (%) y el carcter de especificador de formato.
El dato de entrada podr estar compuesto por menos o hasta los mismos caracteres
que especifica esta longitud mxima de campo. Pero si el nmero de caracteres reales
del campo de entrada supera a la longitud mxima de campo definida, no se leern los
caracteres ms all de esta longitud, quedando el resto para ser recibidos e
interpretados (tal vez de forma incorrecta) como componentes del siguiente dato.
EJEMPLO

Se muestra una estructura esquemtica de un programa en C.


int a, c, b;
. . .
scanf("%3d %3d %3d", &a, &b, &c);

Entrada y salida de datos por pantalla

181

Cuando el programa se ejecuta, espera a que se introduzcan tres cantidades


enteras por teclado. Se muestran varios ejemplos de datos tecleados y el
valor que llegarn a cada una de las tres variables en memoria.
Valores tecleados
1 2 3
123 456 789
789456256
1234 5678 9

Valor de a
1
123
789
123

Valor de b
2
456
456
4

Valor de c
3
789
256
3 caracteres para cada dato
567
8 y 9 son ignorados

EJEMPLO

Se muestra una estructura esquemtica de un programa en C.


int i;
float x;
char c;
. . .
scanf("%3d %5f %c", &i, &x, &c);

Se muestra un ejemplo de datos tecleados y el valor que llegarn a cada una


de las tres variables en memoria.
Valores tecleados
10 256.875 T

Valor de i
10

Valor de x
256.8

Valor de c
7
5 y T son ignorados

Especificacin de supresin de asignacin en entrada


Es posible saltarse un dato ledo en entrada sin que ste sea asignado a ninguna
variable. Para ello, se pospone un asterisco (*) al signo % dentro de la cadena de
control.
Hay que tener cuidado con la lectura de variables de tipo char con el tipo de
conversin %c, ya que en estos casos el carcter separador es interpretado como un
carcter ms, que es recibido en la variable de tipo char. Para saltarse este carcter
se utiliza el especificador de supresin de asignacin. El comportamiento de la
funcin scanf() para leer un carcter se muestra en la Figura 6.1.

Figura 6.1. Comportamiento de la funcin scanf() en la lectura de caracteres

182

Programacin en C

EJEMPLO

Sea el siguiente fragmento de cdigo C:


char concepto[20];
int numero;
int coste;
. . .
scanf ("%s %*d %d", concepto, &numero, &coste);

Si los datos de entrada son:


cremallera

123

25

Se producen las siguientes asignaciones en memoria a las variables


especificadas:
concepto = cremallera
numero = 25
coste = valor indefinido
La cantidad 123 ser leda entre los otros dos datos, aunque no ser asignada
a ninguna variable.
EJEMPLO

Sea el siguiente fragmento de cdigo C:


char c1, c2, c3, c4;
float r;
printf("Introduzca r:");
scanf("%f", &r);
printf("r=%f\n", r);
printf("Introduzca c1:");
scanf("%c", &c1);
printf("c1=%c %d\n", c1, c1);
printf("Introduzca c2:");
scanf("%c", &c2);
printf("c2=%c %d\n", c2, c2);
printf("Introduzca c3:");
scanf("%c", &c3);
printf("c3=%c %d\n", c3, c3);
printf("Introduzca c4:");
scanf("%c", &c4);
printf("c4=%c %d\n", c4, c4);

La ejecucin del programa causar el siguiente efecto en pantalla:


Introduzca r: 12.3<intro>
r=12.300000
Introduzca c1: c1=
, 10
Introduzca c2: A<intro>
c2=A, 65
Introduzca c3: c3=
, 10
Introduzca c4: B<intro>
c4=B, 66

Lo que ocurre es que el carcter <intro> (cdigo ASCII 10) es recogido


por la entrada de datos y asignado como carcter a la siguiente peticin de

Entrada y salida de datos por pantalla

183

espera de datos (en este caso la lectura de la variable c1 no espera a que se


introduzcan ms datos porque dispone del carcter <intro> para asignar a
dicha variable). Lo mismo ocurre con la lectura de la variable c3.
Se puede evitar este comportamiento, la mayora de las veces no deseado,
utilizando el especificador de supresin de asignacin, tal y como se muestra
a continuacin:
char c1, c2, c3, c4;
float r;
printf("Introduzca r:");
scanf("%f", &r);
printf("r=%f\n", r);
printf("Introduzca c1:");
scanf("%*c%c", &c1);
printf("c1:%c %d\n", c1, c1);
printf("Introduzca c2:");
scanf("%*c%c", &c2);
printf("c2:%c %d\n", c2, c2);
printf("Introduzca c3:");
scanf("%*c%c", &c3);
printf("c3:%c %d\n", c3, c3);
printf("Introduzca c4:");
scanf("%*c%c", &c4);
printf("c4:%c %d\n", c4, c4);

La ejecucin del programa causar el siguiente efecto:


Introduzca r: 12.3<intro>
r=12.300000
Introduzca c1: A<intro>
c1=A, 65
Introduzca c2: B<intro>
c2=B, 66
Introduzca c3: C<intro>
c3=C, 67
Introduzca c4: D<intro>
c4=D, 68

Los caracteres <intro> son recibidos pero no son asignados a ninguna


variable, debido al especificador de supresin de asignacin utilizado en las
lecturas.
Otras formas posibles de solucionar el problema anterior son:
1. Haber escrito la funcin scanf() con espacios en blanco dentro
de la cadena de control. Cuando se encuentran dentro de la cadena
de control caracteres no reconocidos, se espera que aparezcan estos
caracteres en los datos de entrada, en cuyo caso se leen pero no se
asignan a ningn identificador. En este caso el blanco dentro de la
cadena de control est representando cualquier carcter separador.
...
scanf("
...
scanf("
...
scanf("
...
scanf("

%c", &c1);
%c", &c2);
%c", &c3);
%c", &c4);

184

Programacin en C

2.

Utilizar la funcin fflush() en su formato fflush(stdin).


Esta funcin limpia el buffer de teclado. Su archivo de cabecera es
stdio.h.
...
fflush(stdin);
scanf("%c", &c1);
...
fflush(stdin);
scanf("%c", &c2);
...
fflush(stdin);
scanf("%c", &c3);
...
fflush(stdin);
scanf("%c", &c4);

Este mismo problema descrito con el carcter <intro> puede presentarse al


utilizar las funciones de lectura de un carcter (getchar(), getch(),
getche()). En estos casos, si es necesario, puede utilizarse la funcin
fflush() en su formato fflush(stdin) para limpiar el buffer de
teclado antes de proceder a la lectura del carcter.

6.3. Cuestiones y ejercicios


1.

Qu es un especificador de formato para la funcin printf()? Indicar


cul es el especificador de formato adecuado para:
char
int
unsigned long
double
short en octal

2.
3.
4.
5.
6.

short
unsigned int
float
long double
int en octal

unsigned short
long
una cadena de caracteres
int en hexadecimal

Cul es la diferencia entre las conversiones tipo f, tipo e y tipo g cuando se


estn presentando datos mediante printf()?
Comparar las funciones gets() y puts() respectivamente con las
funciones scanf() y printf(). Indicar diferencias, similitudes, ventajas
y desventajas.
Qu se entiende por precisin en un dato de salida? A qu tipo de datos se
le aplica? Se puede aplicar la precisin a un dato cadena de caracteres?
Qu ocurre si un dato numrico real tiene ms decimales que los
especificados en el formato de salida? Y si tuviera menos?
Un programa en C contiene las siguientes declaraciones:
int vi, vj;
short vs;
float vf;
doble vdx;
long double vldx;

long vix;
unsigned vu;
long int vlix;
char vc;

Escribir una funcin scanf() para cada uno de los siguientes grupos de
variables. Supngase que los enteros se leern como cantidades decimales.

Entrada y salida de datos por pantalla

vi, vj, vf, vdx


vldx, vs, vix

7.

185

vi, vix, vj, vc, vu


vlix, vs, vc, vldx

De acuerdo a las cadenas de control de las siguientes sentencias scanf(),


determinar el tipo de las variables a leer.
scanf("%12ld %5hd %15lf %15le", &a, &b, &c, &d);
scanf("%10lx %6ho %5hu %14lu", &e, &f, &g, &h);
scanf("%s %hd %15f %15e", &i, &j, &k, &m);
scanf("%8d %*d %12lf %Le", &q, &r, &s);

8.

9.
10.

11.
12.

13.

14.

Escribir para cada una de las siguientes cuestiones una sentencia printf()
que la resuelva:
Cul es el cdigo ASCII de la letra A?
Cul es el cdigo ASCII de la letra Z?
Cul es la letra que corresponde al cdigo ASCII 70?
Cul es la letra que corresponde al cdigo ASCII 36?
Cmo se llama el archivo cabecera de las funciones de entrada/salida
estndar en la mayora de las versiones de C? Cmo se incluye este archivo
en un programa?
Qu se entiende por especificar la longitud de campo mnima en salida con
printf()? Y por especificar la longitud de campo mxima en entrada con
scanf()?
Cmo se puede suprimir la asignacin de un dato de entrada mediante
scanf() a un argumento?
Realizar la declaracin de una variable cadena de 80 caracteres a la que se va
a denominar mensaje, y una variable entera a la que se va a denominar
numero.
Cmo se leera de teclado con scanf() dicha cadena?
Cmo se leera de teclado con scanf() dicho entero?
Qu diferencia se puede observar entre las dos anteriores lecturas?
Cmo se leera de teclado con gets() dicha cadena?
Cmo se leera de teclado con gets() dicho entero?
Cmo se escribira en pantalla con printf() dicha cadena?
Cmo se escribira en pantalla con printf() dicho entero?
Cmo se escribira en pantalla con puts() dicha cadena?
Cmo se escribira en pantalla con puts() dicho entero?
Qu ocurre si el dato de salida tiene ms caracteres que la especificada por
la longitud de campo mnima en printf()? Y si la entrada tiene ms
caracteres que la especificada por la longitud de campo mxima en
scanf()?
Cmo se especificara en una cadena de control de scanf() que se desean
leer argumentos:
enteros cortos
enteros largos sin signo

15.

enteros cortos sin signo enteros largos


reales de doble precisin reales largos de doble precisin

Un programa en C contiene las siguientes declaraciones:


int i, j, k;

Escribir de forma adecuada una funcin scanf() que permita introducir los
valores numricos de i, j, k asumiendo que:

186

16.

17.

18.

Programacin en C

Los valores de i, j, k son enteros decimales y cada uno no


excede de seis caracteres.
El valor de i es un entero decimal, j un entero octal y k un entero
hexadecimal, y cada cantidad no excede de 8 caracteres.
Los valores de i y j son enteros hexadecimales y k un entero octal.
Cada cantidad tiene 7 o menos caracteres.
Escribir las parejas de sentencias C adecuadas para presentar en pantalla un
mensaje de peticin de datos y posteriormente realizar la lectura de teclado
de los siguientes datos referentes al usuario que est ejecutando el programa.
Escribir posteriormente las sentencias adecuadas para mostrar los datos
introducidos, antecedidos todos ellos por el texto conveniente.
Su nombre y dos apellidos (dato de tipo cadena de caracteres, que
puede incluir espacios).
La letra de su NIF (dato de tipo char).
Su DNI (dato de tipo unsigned long).
Su edad actual (dato de tipo char).
Su peso (dato de tipo float). Mostrar en salida con 3 decimales.
Su estatura (dato de tipo double). Mostrar en salida con 2
decimales.
Su ciudad de nacimiento (dato de tipo cadena de caracteres).
Se tienen declaradas tres variables llamadas:
int hh=7, mm=48, ss=6;
stas representan las horas (hh), minutos (mm) y segundos (ss) de un
instante de tiempo. Escribir las sentencias printf() necesarias para
mostrar dicho instante de tiempo en los siguientes formatos:
07:48:06
/07/48/06/
\07\48\06\
(07:48:06)

07h 48m 06s


Son las 7 horas, 48 minutos y 6 segundos
0007-0048-0006

Se tienen declaradas tres variables llamadas:


int dd=12, mm=6, aa=1999;

stas representan el da (dd), mes (mm) y ao (aa) de una fecha. Escribir las
sentencias printf() necesarias para mostrar dicha fecha en los siguientes
formatos:
12-06-1999
//12//06//99/
19990612
12 del 6 de 1999

19.

12/06/99
990612
12 del 6 de 1.999

Un programa en C contiene las siguientes declaraciones:


char texto[80] = "Programar en C es enormemente creativo";

Escribir sentencias printf() para visualizar el contenido de dicha variable


de las siguientes formas:
Todo en una lnea.
Slo los 8 primeros caracteres.
Los 9 primeros caracteres, precedidos de 5 espacios en blanco.
Los 10 primeros caracteres, seguidos de 4 asteriscos.

Entrada y salida de datos por pantalla

20.

187

Un programa en C contiene las siguientes declaraciones:


float a = 2.5, bb = 0.0005, c= 3000.;

Mostrar la salida resultante de cada una de las siguientes sentencias


printf():
printf("%f %f %f\n, a, b, c);
printf("%3f %3f %3f\n, a, b, c);
printf("%8f %8f %8f\n, a, b, c);
printf("%8.4f %8.4f %8.4f\n, a, b, c);
printf("%8.3f %8.3f %8.3f\n, a, b, c);
printf("%e %e %e\n, a, b, c);
printf("%3e %3e %3e\n, a, b, c);
printf("%12e %12e %12e\n, a, b, c);
printf("%12.4e %12.4e %12.4e\n, a, b, c);
printf("%8.2e %8.2e %8.2e\n, a, b, c);
printf("%-8f %-8f %-8f\n, a, b, c);
printf("%08f %08f %08f\n, a, b, c);
printf("%g %g %g\n, a, b, c);

7. Sentencias de control
En los ejemplos de los programas que se han visto hasta ahora, las sentencias se
ejecutan una nica vez y en el mismo orden en que aparecen en el programa, es decir,
cada una a continuacin de la anterior escrita en el programa fuente, empezando por
la primera y terminando por la ltima.
Los programas que se suelen realizar en la prctica no son tan sencillos, ya que los
que se han visto no incluyen ningn tipo de elemento de control lgico. En
particular, en estos programas no aparecen comprobaciones de condiciones que sean
ciertas o falsas ni aparece la ejecucin repetida de grupos individuales de sentencias
de forma selectiva.
Muchos programas requieren que se efecte una comprobacin lgica en algn
punto concreto de los mismos y a continuacin realizar alguna accin que depender
del resultado de la prueba lgica realizada. Esto se conoce como ejecucin
condicional. Existe tambin una clase especial de ejecucin condicional, llamada
seleccin, en la que se selecciona un grupo de sentencias entre varios grupos
disponibles.
Adems, los programas pueden requerir que un grupo de sentencias se ejecute
repetidamente, hasta que se satisfaga alguna condicin lgica. Esto se conoce con el
nombre de bucle.
Las sentencias de control son aqullas que permiten variar o alterar la secuencia
normal de ejecucin de un programa. Prcticamente la totalidad de lenguajes de
programacin de alto nivel soportan tres tipos de sentencias de control:
Instrucciones condicionales o alternativas. Controlan la ejecucin de
un grupo u otro de sentencias de un programa en funcin de que se
cumpla o no una condicin lgica previamente establecida. Tambin
conocidas como bifurcaciones.
Instrucciones de salto. Alteran o rompen la secuencia normal de
ejecucin de un programa, perdindose toda posibilidad controlada de
retornar la ejecucin del programa al punto de llamada, a no ser que se
vuelva a utilizar otra sentencia de salto.
Instrucciones repetitivas. Hacen posible que un grupo de sentencias se
ejecute ms de una vez, de forma consecutiva, hasta que se verifique
cierta condicin lgica. Tambin conocidas como bucles.
As, este captulo se ha organizado en siete secciones. Las tres primeras secciones
introducen las sentencias condicionales, de salto y repetitivas, respectivamente. Las
siguientes tres secciones hacen lo propio pero desde el punto de vista del lenguaje de
programacin C. Finalmente, la sptima y ltima seccin presenta una lista de
cuestiones y ejercicios de repaso.

7.1. Sentencias condicionales o alternativas


Existen las sentencias alternativas simples y dobles, las cuales permiten la ejecucin
de un grupo u otro de instrucciones de un programa en funcin de que se cumpla o no
- 189 -

190

Programacin en C

una condicin previamente establecida.


Una alternativa simple evala una condicin, ejecutndose un grupo de sentencias
si el resultado es verdadero, y no ejecutndose este grupo de sentencias si el
resultado es falso, tal y como se ilustra en la Figura 7.1.
Una alternativa mltiple evala una condicin, ejecutndose un grupo de
sentencias si el resultado es verdadero, y ejecutndose otro grupo alternativo de
sentencias si el resultado es falso. Siempre, por tanto, entre dos bloques alternativos
de sentencias, se ejecutar uno de ellos, tal y como se puede ver en la Figura 7.2.

Pseudocdigo de la alternativa simple


Si condicin
sentencia 1
sentencia 2
...
sentencia n
FinSi

Figura 7.1. Alternativa simple


Pseudocdigo de la alternativa doble
Si condicin
sentencia v1
sentencia v2
...
sentencia vn
Si no
sentencia f1
sentencia f2
...
sentencia fm
FinSi
Figura 7.2. Alternativa doble
EJEMPLO

Sea un sistema de ecuaciones lineales como el siguiente:


aX + bY = c
dX + eY = f

El cual puede resolverse mediante las frmulas:


af cd
cd bf
Y
X
ae bd
ae bd
Disear un algoritmo que pida el conjunto de coeficientes (a, b, c, d,
e, f) y calcule las coordenadas X e Y de la solucin.

Sentencias de control

191

algoritmo sistema_dos_ecuaciones_lineales
variables
real a, b, c, d, e, f /* Coeficientes */
real x, y
/* Solucin
*/
real denominador
inicio
leer (a, b, c, d, e, f)
denominador a*e b*d
Si denominador = 0
escribir("El sistema no tiene solucin)
Si no
x (c*e b*f) / denominador
y (a*f c*d) / denominador
escribir(x, y)
FinSi
fin

Existe tambin la sentencia alternativa mltiple (o selector), donde en lugar de


una condicin, se evala una expresin con mltiples pero finitos resultados,
ejecutndose en funcin del resultado de la expresin, un grupo de sentencias entre
mltiples posibles, tal y como se expresa en la Figura 7.3.

Pseudocdigo de la alternativa mltiple


Segn_valor expresin hacer
Valor_1:
bloque de sentencias 1
Valor_2:
bloque de sentencias 2
...
Valor_n:
bloque de sentencias n
Si no:
bloque de sentencia x
FinSegn_valor
Figura 7.3. Alternativa mltiple
EJEMPLO

Disear un algoritmo que pida dos nmeros enteros y un tipo de operacin a


realizar con ellos. Los tipos de operacin vendrn representados por un
carcter. Los caracteres vlidos admitidos son:
S s para suma.

192

Programacin en C

R r para resta.
P p para producto.
D d para divisin.
En funcin del tipo de operacin que se teclee se deber realizar la operacin
correspondiente. Si se recibiera un tipo de operacin distinto a los anteriores,
deber darse un mensaje de error.
algoritmo aritmtica_bsica
variables
real num1, num2, solucion
caracter operacion
inicio
leer (num1)
leer (num2)
leer (operacion)
Segn_valor operacion hacer
'S', 's':
solucion num1 + num2
escribir (solucion)
'R', 'r':
solucion num1 - num2
escribir (solucion)
'P', 'p':
solucion num1 * num2
escribir (solucion)
'D', 'd':
solucion num1 / num2
escribir (solucion)
Sino:
escribir (Tipo de operacin incorrecto)
FinSegn_valor
fin

7.1.1. Sentencias alternativas anidadas


La sentencia de seleccin Si-Si no implica la seleccin de una de dos alternativas.
Es posible utilizar esta sentencia para disear estructuras que seleccionen entre ms
de dos alternativas. Por ejemplo, tanto la rama Si como la Si no pueden contener a
su vez otra sentencia Si-Si no, y as sucesivamente un nmero indeterminado de
veces.
Si condicin1
bloque de sentencias 1v
Si no
Si condicin2
bloque de sentencias 2v
Si no
Si condicin3
bloque de sentencias 3v
Si no
bloque de sentencias 3f
FinSi
FinSi
FinSi

Las sentencias Si-Si no interiores a otras se denominan anidadas. Como las


estructuras Si-Si no anidadas pueden volverse bastante complejas, para que el

Sentencias de control

193

algoritmo sea claro es preciso utilizar sangra en las instrucciones, de modo que se
haga coincidir en la misma columna las palabras reservadas Si, Si no y FinSi
correspondientes.
EJEMPLO

El siguiente segmento en pseudocdigo determina cul de tres variables


numricas enteras (n1, n2 y n3), con valores distintos, es la que contiene el
valor mayor.
Si ( (n1 = n2) or (n1 = n3) or (n2 = n3) )
escribir("Los tres nmeros NO son distintos");
Si no
Si (n1 > n2)
Si (n1 > n3)
Puede verse en este ejemplo la conveniencia
mayor n1
de realizar sangra a las sentencias, para
Si no
facilitar la lectura del algoritmo.
mayor n3
FinSi
Si no
Si (n2 > n3)
mayor n2
Si no
mayor n3
FinSi
FinSi
escribir ("El nmero mayor es: ", mayor)
FinSi

El diagrama de flujo correspondiente a este ejemplo se muestra en la Figura


7.4.
(n1 = n2) or (n1 = n3) or (n2 = n3)

falso

verdadero
verdadero

verdadero

falso

n1 > n2

falso

Los nmeros NO
son distintos

verdadero

n1 > n3

mayor

n1

falso
n2 > n3

mayor

n3

mayor

n2

mayor

n3

El nmero
mayor es mayor

Figura 7.4. Diagrama de flujo correspondiente al ejemplo de localizar el mayor de tres


nmeros

7.2. Sentencias de salto


Se utilizan para realizar un salto (transferir el control) a un punto del programa donde

194

Programacin en C

seguir la ejecucin del mismo, pero perdiendo toda posibilidad de retornar, de forma
controlada, la ejecucin del programa al punto de llamada.
No se aconseja su uso, pues crea un cdigo difcil de leer y mantener. Su uso est
muy restringido, por no decir prohibido, en la programacin estructurada.
Su forma general es:
ir_a <etiqueta>
Donde <etiqueta> es un identificador que se utiliza para rotular la sentencia
dentro del programa a la que se transfiere el control.
La sentencia de destino debe estar precedida por este identificador:
<etiqueta:> sentencia
Las sentencias de salto pueden clasificarse de la siguiente manera:
Salto condicional. Alteran la secuencia de ejecucin de un programa
slo y exclusivamente en el caso de que una condicin especfica sea
cierta.
Salto incondicional. Alteran la secuencia de ejecucin de un programa
siempre, pues no van acompaadas de una condicin que limite en
determinadas ocasiones la realizacin del salto a otra parte del programa.

Buen estilo de programacin


La mayora de los lenguajes de propsito general poseen la sentencia ir_a
(goto), aunque actualmente todas las tcnicas de programacin instan a evitar su
utilizacin. El uso de goto tiende a que se disperse la lgica subyacente por todo
el programa, mientras que el uso de elementos estructurados requiere que todo el
programa se escriba de forma ordenada y secuencial.
La utilizacin de la sentencia goto se debe evitar siempre dentro de un programa
estructurado. Su uso es seal de programadores con pocos recursos.

7.3. Sentencias repetitivas


Tambin se denominan bucles, ciclos o lazos. Este tipo de instrucciones de control
hace que mientras se verifique una condicin, un segmento de algoritmo o programa
se repita consecutivamente.

Figura 7.5. Esquema general de un bucle

Sentencias de control

195

El cuerpo del bucle son las sentencias que se repiten. Cada repeticin del cuerpo
del bucle se denomina iteracin.
En cada iteracin se evala la expresin de control del bucle (condicin), que
determina si se contina realizando otra iteracin o bien se sale definitivamente del
bucle.
En la Figura 7.5 se presenta el esquema general de un bucle.
Se pueden distinguir tres tipos de bucles, dependiendo de que la condicin de
control del bucle se evale antes o despus de cada iteracin, y de que se pueda fijar
de antemano o no el nmero de iteraciones a realizar.

7.3.1. Bucle mientras


Se utiliza cuando se desea que un bloque de sentencias se ejecute MIENTRAS una
condicin sea cierta. El esquema de este bucle se puede apreciar en la Figura 7.6.

Pseudocdigo del bucle mientras


Mientras condicin
sentencia 1
sentencia 2
...
sentencia n
FinMientras

Figura 7.6. Bucle mientras

El bucle mientras se caracteriza porque:


Permite repetir un bloque de instrucciones de 0 a n veces.
La condicin se evala antes de ejecutar el bucle, por lo que si la
condicin es falsa a la entrada del bucle, ste no se ejecutar ninguna
vez.
Mientras la condicin sea cierta, el bucle se repetir. Se debe incluir
dentro del cuerpo del bucle alguna sentencia que, en determinadas
condiciones, llegue a hacer falsa la condicin del bucle para poder salir
del mismo, ya que de lo contrario el bucle no terminara nunca (bucle
infinito).
EJEMPLO

El siguiente pseudocdigo permite obtener la suma de una coleccin de


nmeros enteros positivos que se introducirn por teclado. La introduccin
de nmeros terminar cuando se teclee un valor negativo.
algoritmo suma_numeros
variables
entero suma
entero numero

196

Programacin en C

inicio
suma 0
leer (numero)
Mientras (numero >= 0)
suma suma + numero
leer (numero)
FinMientras
escribir(suma)
fin

7.3.2. Bucle repetir mientras


El bucle repetir mientras es un bucle similar al bucle mientras, con la
diferencia de que la condicin se evala despus de ejecutar el cuerpo del bucle. El
esquema de este bucle se puede apreciar en la Figura 7.7.

Pseudocdigo del bucle repetir mientras


Repetir
sentencia 1
sentencia 2
...
sentencia n
Mientras condicin

Figura 7.7. Bucle repetir mientras

El bucle repetir mientras se caracteriza porque:


Permite repetir un bloque de instrucciones de 1 a n veces.
La condicin se evala despus de ejecutar el bucle, por lo que
independientemente de cmo sea la condicin, ste se ejecutar siempre
al menos una vez.
Mientras la condicin sea cierta, el bucle se repetir. Se debe incluir
dentro del cuerpo del bucle alguna sentencia que, en determinadas
condiciones, llegue a hacer falsa la condicin del bucle para poder salir
del mismo, ya que de lo contrario el bucle no terminara nunca (bucle
infinito).
EJEMPLO

Crecimiento de una poblacin.


Una colonia de animales tiene una poblacin inicial de 100 criaturas. Si al
ao la poblacin crece un 20%, en cuntos aos se duplicar la poblacin
inicial?
Matemticamente se podra resolver el problema realizando la siguiente
tabla, donde se desprecian los decimales en los clculos.

Sentencias de control

Ao
1
2
3
4

Pobl.inicial
100
120
144
172

Crecimiento
20
24
28
34

197

Pobl.final
120
144
172
206

Generalizando este problema, el siguiente pseudocdigo permite determinar,


dada una poblacin inicial y un factor de crecimiento anual, el nmero de
aos que tardar dicha poblacin en duplicarse. Adems, genera una tabla de
crecimiento anual como la anterior.
algoritmo duplicar_poblacin
variables
real
poblacion, fcrecim
real
pobl_ini_ao, pobl_fin_ao, limite
entero cont_aos
inicio
leer (poblacion, fcrecim)
cont_aos 0
pobl_fin_ao poblacion
limite 2 * poblacion
Repetir
cont_aos cont_aos + 1
pobl_ini_ao pobl_fin_ao
crecimiento (pobl_ini_ao * fcrecim) / 100
pobl_fin_ao pobl_ini_ao + crecimiento
escribir (cont_aos, pobl_ini_ao,
crecimiento, pobl_fin_ao)
Mientras (pobl_fin_ao < limite)
escribir(cont_aos)
fin

7.3.3. Bucle desde


Se utiliza cuando se desea que un bucle se ejecute repetidamente un nmero
determinado de veces que se conoce por anticipado.

Pseudocdigo del bucle desde


variables
entero v
Desde v vi hasta vf con incremento n
sentencia 1
sentencia 2
...
sentencia m
FinDesde

Figura 7.8. Bucle desde

198

Programacin en C

Las principales caractersticas del bucle desde son:


Comienza siempre realizando la asignacin del valor inicial a la variable
de control o ndice del bucle. Siempre la primera iteracin del bucle se
realiza con la variable ndice iniciada con el valor inicial.
Las acciones especificadas en el cuerpo del bucle se ejecutarn (a menos
que el valor final sea menor que el inicial) y al finalizar el cuerpo del
bucle la variable ndice se incrementa automticamente en el valor
establecido (hay lenguajes que no permiten establecer valores para el
incremento distintos a la unidad).
Si el nuevo valor de la variable ndice no excede del valor final, se
ejecutarn de nuevo las sentencias que forman el cuerpo del bucle y se
proceder a un nuevo incremento de la variable ndice. Todo esto se
repetir hasta que el valor de la variable ndice supere al valor final
establecido.
En algunos lenguajes existe la posibilidad de utilizar bucles desde con
decremento en cada iteracin. En este caso se debe establecer un valor
inicial para la variable ndice mayor que el valor final.
Segn se observa en el diagrama de flujo del bucle desde (Figura 7.8),
ste no es ms que una forma compacta de escribir un bucle
mientras, como se puede apreciar en el pseudocdigo que se muestra
a continuacin.
Pseudocdigos equivalentes del bucle desde y del bucle mientras
variables
entero v
Desde v vi hasta vf con inc n
sentencia 1
sentencia 2
...
sentencia m
FinDesde

variables
entero v
v vi
Mientras v <= vf
sentencia 1
sentencia 1
...
sentencia m
v v + n
FinMientras

EJEMPLO

El siguiente pseudocdigo permite imprimir en pantalla la tabla de


multiplicar (hasta el 20) del nmero entero que se introduzca por teclado.
algoritmo tabla_multiplicar
constantes
entero MAX 20
variables
entero i, num
inicio
leer(num)
Desde i 1 hasta MAX con incremento 1
escribir(i, " x ", num, " = ", i*num)
FinDesde
fin

Es decir, si se introduce por teclado, por ejemplo, el nmero 23, el resultado


del algoritmo ser escribir en pantalla la siguiente tabla:

Sentencias de control

199

1 x 23 = 23
2 x 23 = 46
3 x 23 = 69
...
19 x 23 = 437
20 x 23 = 460

7.3.4. Terminacin de los bucles mientras y repetir mientras


Existen varios mtodos para controlar y provocar la terminacin de un bucle.
7.3.4.1. Bucles controlados por contadores
Las iteraciones del bucle se controlarn por una variable contador. Un contador es
una variable cuyo valor se incrementa o decrementa en una cantidad constante
(normalmente 1) en cada iteracin del bucle.
En este tipo de bucles se conoce a priori el nmero de iteraciones a realizar.
EJEMPLO

Se va a escribir un algoritmo en pseudocdigo que permita leer N nmeros


enteros desde el teclado y obtener su suma. Se utilizar un bucle mientras
controlado por un contador al que se va denominar cuantos. Se muestran a
continuacin dos versiones para este algoritmo.
Versin 1:
Versin 2:
algoritmo suma_numeros
constantes
entero N 10
variables
entero num, cuantos
entero suma 0
inicio
cuantos 1
Mientras (cuantos <= N)
leer(num)
suma suma + num
cuantos cuantos + 1
FinMientras
escribir(suma)
fin

algoritmo suma_numeros2
constantes
entero N 10
variables
entero num, cuantos
entero suma 0
inicio
cuantos 0
Mientras (cuantos < N)
leer(num)
suma suma + num
cuantos cuantos + 1
FinMientras
escribir(suma)
fin

En la primera versin del algoritmo, la variable contador cuantos que


controla el bucle, se ha iniciado a 1, por lo que indica en el algoritmo el
siguiente nmero a leer.
En la segunda versin, variable contador cuantos se ha iniciado a 0, por lo
que indicar cuantos nmeros se llevan ledos.

200

Programacin en C

Normalmente las variables contador se inician a 0 a 1, dependiendo del diseo


del programa. En funcin del valor de inicio y de final se deber escoger el operador
relacional adecuado.
Estos bucles mientras controlados por contador suelen escribirse normalmente
de forma ms compacta utilizando el bucle desde, ya que la variable de control o
ndice de un bucle desde es en realidad un contador. As, por ejemplo, el algoritmo
diseado en el ejemplo anterior podra rescribirse como sigue utilizando un bucle
desde.
algoritmo suma_numeros3
constantes
entero N 10
variables
entero num, cuantos
entero suma 0
inicio
Desde cuantos 1 hasta N con incremento 1
leer(num)
suma suma + num
FinDesde
escribir(suma)
fin

7.3.4.2. Bucles controlados por acumuladores


Un acumulador o totalizador es una variable cuya misin es almacenar cantidades
variables resultantes de operaciones aritmticas sucesivas, lo que permite obtener el
total acumulado de dichas cantidades. Realiza funciones similares a un contador, con
la diferencia de que el incremento o decremento es variable.
En este tipo de bucles no se conoce a priori el nmero de iteraciones a realizar.
EJEMPLO

Se escribe un algoritmo en pseudocdigo que permita obtener a partir de qu


trmino la suma de la serie: 2 + 5 + 8 + 11 + 14 + ..., supera un
valor lmite introducido por teclado. Se va a utilizar un bucle mientras
controlado por un acumulador que se denota por suma.
algoritmo limite_suma
variables
entero numterm
entero termino, suma, limite
inicio
leer(limite)
numterm 1
termino 2
suma 2
Mientras (suma < limite)
termino termino + 3
suma suma + termino
numterm numterm + 1
FinMientras
escribir(numterm)
fin

Sentencias de control

201

7.3.4.3. Bucles controlados por un valor centinela


Si un algoritmo est leyendo una lista de valores (de la que no se conoce su longitud)
con un bucle mientras, un mecanismo tpico para determinar el final de la lista y,
por tanto, el final del bucle, es terminar la lista con un valor centinela.
Un valor centinela es un valor especial, que se utiliza para sealar el final de una
lista de datos. El valor debe ser cuidadosamente elegido para que no pueda ser
confundido con un dato perteneciente a la propia lista.
Por ejemplo, supngase que se tienen unas calificaciones de unos tests (cada
calificacin comprendida entre 0 y 100); un valor centinela vlido para la lista puede
ser el 999, ya que nunca ser una calificacin vlida. En realidad, dado que la lista
del ejemplo es de nmeros positivos, un valor centinela correcto podra ser cualquier
nmero negativo
En este tipo de bucles no se conoce a priori el nmero de iteraciones a realizar.

EJEMPLO
Se va a escribir un algoritmo en pseudocdigo que permita calcular el valor
medio de una coleccin de nmeros enteros positivos que se introducirn por
teclado.
La introduccin de nmeros terminar cuando se teclee un nmero negativo.
Se va a utilizar un bucle mientras controlado por centinela, donde el valor
centinela ser cualquier nmero negativo.
algoritmo media_lista_enteros_positivos
variables
entero numero, cuantos 0, suma 0
real
media
inicio
leer(numero)
Mientras (numero >= 0)
cuantos cuantos + 1
suma suma + numero
leer(numero)
FinMientras
Si cuantos > 0
media suma / cuantos
Si no
media 0
FinSi
escribir (media)
fin

7.3.4.4. Bucles controlados por interruptores, banderas o flags


Un interruptor, bandera o flag es una variable que se utiliza para conservar el
estado verdadero o falso de una condicin. Su nombre proviene de su similitud con un
interruptor (encendido/apagado), o con una bandera (arriba/abajo). Normalmente se
utilizan para recordar en un lugar del programa la ocurrencia o no de un proceso
acaecido con antelacin.
El valor del flag debe iniciarse antes de entrar en el bucle y debe poder cambiarse
dentro del bucle para poder en un momento determinado salir del mismo.

202

Programacin en C

En este tipo de bucles no se conoce a priori el nmero de iteraciones a realizar.


EJEMPLO

Se va a escribir un algoritmo en pseudocdigo que permita calcular cuntos


nmeros componen una coleccin de enteros, que se introducir por teclado,
as como el valor de su suma. La introduccin de nmeros terminar cuando
su suma supere el valor 15000 o bien cuando se hayan introducido 25
nmeros.
Se va a emplear un bucle repetir mientras controlado por un flag
llamado terminar.
algoritmo suma_lista
constantes
entero MAXNUM 25
real
MAXSUMA 15000
variables
entero numero, cuantos 0
entero suma 0
lgica terminar /* flag: cierto falso */
inicio
terminar falso
Repetir
leer(numero)
cuantos cuantos + 1
suma suma + numero
Si (cuantos = MAXNUM or suma > MAXSUMA)
terminar cierto
FinSi
Mientras (not terminar)
escribir (cuantos)
escribir (suma)
fin

7.3.5. Bucles anidados


En un algoritmo los bucles pueden ser anidados o independientes. Los bucles son
anidados cuando estn dispuestos de tal modo que unos son interiores a otros, es
decir, dentro del cuerpo de un bucle existe a su vez otro bucle. El bucle interior se
denomina anidado.
Los bucles son independientes cuando son externos los unos a los otros.
En la Figura 7.9 se observa que el algoritmo 1 tiene tres bucles
independientes, mientras que el algoritmo 2 tiene el bucle G anidado al bucle F y
ste, a su vez, anidado al D. El bucle E es independiente del F, pero tambin est
anidado al bucle D. Sin embargo, el diseo del algoritmo 3 no es correcto porque
se tienen bucles cruzados, y siempre un bucle debe ser independiente de otro o bien
encontrarse completamente dentro del primero.
En el caso de existir bucles anidados, stos cumplen las siguientes caractersticas:
El bucle interno y externo pueden ser generados con distinta sentencia
repetitiva.
El bucle interno debe de encontrarse completamente dentro del externo.

Sentencias de control

203

Normalmente cada bucle es controlado por condiciones de salida


diferentes.
Por cada iteracin del bucle externo, el bucle interno se ejecuta
completamente.

Figura 7.9. Bucles independientes vs bucles anidados


EJEMPLO

Se escribe un algoritmo en pseudocdigo que permita obtener todas las


combinaciones de horas y minutos de un da. Se utilizan dos instrucciones
desde anidadas.
variables
entero horas, minutos
Desde horas 0 hasta 23 con incremento 1
Desde minutos 0 hasta 59 con incremento 1
escribir (horas, ":", minutos)
FinDesde
FinDesde
Salida en pantalla:
0:0
0:1
0:2
...
0:58
0:59
1:0
1:1
...
1:58
1:59
2:0
2:1
...
23:57
23:58
23:59

Se imprimen un total de 24 x 60 = 1440 lneas.

204

Programacin en C

EJEMPLO
Realizar un algoritmo que dado un intervalo de nmeros enteros positivos determine
cuantos nmeros primos hay dentro del mismo, y cul es la suma de dichos nmeros
primos.
Anlisis: Un nmero n es primo si slo tiene como divisores a l mismo y la unidad.
Por ejemplo 1, 2, 3, 5, 7 y 11 son primos, mientras que 4, 6, 8, 9 y 10
no lo son.
Mediante un bucle externo se recorre el intervalo, determinando en cada iteracin el
nmero a investigar.
Mediante el bucle interno se va dividiendo dicho nmero por 2, 3, 4...,
terminando bien cuando alguna de estas divisiones d resto cero, en cuyo caso se
habr encontrado un divisor de dicho nmero, por lo que NO es primo, o bien cuando
se llegue a dividir el nmero n a investigar por l mismo, en cuyo caso por no haber
encontrado ningn divisor, el nmero SI es primo.
Se puede mejorar el rendimiento del algoritmo buscando divisores del nmero n slo
en el intervalo [2, n ] , pues puede demostrarse que si en ese intervalo no existen
divisores del nmero n, tampoco existirn en el intervalo [

n , n 1] .

algoritmo primos
constantes
entero LIMINF 150
entero LIMSUP 2500
variables
entero numero, divisor, cuantos 0, suma 0
real limite
lgica primo /* flag: cierto falso */
inicio
Desde numero LIMINF hasta LIMSUP con incremento 1
primo cierto
divisor 2
limite raizcuadrada(numero)
Mientras (primo and divisor <= limite)
Si (numero mod divisor) = 0
primo falso
FinSi
divisor divisor + 1
FinMientras
Si (primo)
cuantos cuantos + 1
suma suma + numero
FinSi
FinDesde
escribir (cuantos)
escribir (suma)
fin

7.4. Sentencias condicionales o alternativas en C


Antes de comenzar a describir las estructuras de control en C se debe recordar que:
En C, no existe el tipo de dato lgico o booleano.
La evaluacin de expresiones relaciones y lgicas originan:

Sentencias de control

205

o Un valor numrico 0 para falso.


o Un valor numrico 1 para verdadero.
En expresiones lgicas o evaluacin de condiciones:
o Un valor numrico 0 es tomado como falso.
o Cualquier valor numrico distinto de 0 es tomado como verdadero.
La definicin de interruptores, banderas o flags se realiza normalmente
utilizando una variable entera que se inicia a 0 para falso y a un valor
distinto de 0 (normalmente 1) para verdadero.
En el caso concreto de las sentencias alternativas o condicionales, el lenguaje C
soporta tanto las sentencias alternativas simples y dobles, como las alternativas
mltiples.
El soporte de la construccin alternativa simple en el lenguaje C se consigue
mediante la sentencia if.

Figura 7.10. Alternativa simple en C, sentencia if

La semntica de esta sentencia se refleja en la Figura 7.10, mientras que su sintaxis


se muestra a continuacin:
if (condicin) {
sentencia_1;
sentencia_2;
...
sentencia_n;
}

Detalles relevantes a tener en cuenta sobre la sintaxis de esta sentencia son:


La condicin debe incluirse entre parntesis.
Si la condicin es verdadera (distinta de cero) se ejecuta el bloque
de sentencias encerrado entre llaves (as queda delimitado el
principio y fin del bloque).
Si la condicin es falsa (igual a cero) no se ejecuta el bloque de
sentencias.
Si el bloque de sentencias consta de una sola sentencia, pueden
omitirse las llaves, como se muestra a continuacin:
if (condicin)
sentencia;
EJEMPLO

Si el saldo de la cuenta es negativo, se visualiza un mensaje en pantalla, y se


le coloca el crdito a cero. En caso de ser positivo o cero, no se hace nada.

206

Programacin en C

En cualquiera de los dos casos, se contina con la ejecucin de la siguiente


sentencia del programa.
if (saldo < 0){
printf("Cuenta %ld en nmeros rojos.\n", n_cuenta");
credito = 0;
}

El soporte de la construccin alternativa doble en el lenguaje C se consigue


mediante la construccin if-else.

Figura 7.11. Alternativa doble en C, construccin if-else

La semntica de esta construccin se refleja en la Figura 7.11, mientras que su


sintaxis se muestra a continuacin:
if (condicin) {
sentencia_v1;
...
sentencia_vn;
}
else {
sentencia_f1;
...
sentencia_fm;
}

Detalles relevantes a tener en cuenta sobre la sintaxis de esta construccin son:


La condicin debe incluirse entre parntesis.
Si la condicin es verdadera (distinta de cero) se ejecuta el primer
bloque de sentencias encerrado entre llaves (el asociado a la clusula
if).
Si la condicin es falsa (igual a cero) se ejecuta el segundo bloque de
sentencias encerrado entre llaves (el asociado a la clusula else).
En cualquier caso, siempre se ejecuta uno y slo uno de los dos bloques.
Si algn bloque de sentencias consta de una sola sentencia, se pueden
omitir las llaves.
EJEMPLO

Se va a codificar en C el ejemplo de la resolucin del sistema de ecuaciones


lineales, ya explicado en pseudocdigo anteriormente en la Seccin 1.

Sentencias de control

207

#include <stdio.h>
void main (void) {
float a, b, c, d, e, f; /* Coeficientes */
float x, y;
/* Solucin
*/
float denominador;
/* Presentacin
*/
printf("Resolucin del sistema de ecuaciones lineales\n");
printf("
aX + bY = c
\n");
printf("
dX + eY = f
\n");
/* Peticin de datos */
printf("\nIntroduzca los coeficientes\n");
printf("Coeficiente a?: "); scanf("%f", &a);
printf("Coeficiente b?: "); scanf("%f", &b);
printf("Coeficiente c?: "); scanf("%f", &c);
printf("Coeficiente d?: "); scanf("%f", &d);
printf("Coeficiente e?: "); scanf("%f", &e);
printf("Coeficiente f?: "); scanf("%f", &f);
/* Clculos y presentacin de resultados */
printf("\n\nEl sistema\n);
printf("
%gX + %gY = %g
\n", a, b, c);
printf("
%gX + %gY = %g
\n", d, e, f);
denominador = a*e b*d;
if (denominador == 0)
printf("\nNo tiene solucin.);
else {
x = (c*e b*f) / denominador;
y = (a*f c*d) / denominador;
printf("\nTiene por soluciones:\n);
printf("X = %g\n, x);
printf("Y = %g\n, y);
}
}

Las sentencias if-else se pueden anidar. Tanto la clusula if como la clusula


else pueden contener a su vez otra construccin if-else, y as sucesivamente un
nmero cualquiera de veces.
Como la estructura resultante puede ser bastante compleja, deben utilizarse
sangras para que sea fcil la identificacin de a qu if pertenece cada else.
EJEMPLO

Se resumen una estructura if-else anidada que puede utilizarse en un


programa que resuelve una ecuacin de segundo grado.
dd = b*b-4*a*c;
if (dd > 0) {
printf("Tiene dos races reales distintas:\n");
...
}
else
if (dd == 0) {
printf("Tiene una raz real doble:\n");
...
}
else {
printf("Tiene dos races complejas conjugadas:\n");
...
}

208

Programacin en C

EJEMPLO

Se va a codificar en C el ejemplo de la seleccin del mayor de tres nmeros


enteros, ya solucionado en pseudocdigo anteriormente en la Seccin 1.
#include <stdio.h>
void main (void) {
int n1, n2, n3, mayor;
/* Presentacin
*/
printf("Clculo del mayor de tres nmeros enteros distintos\n");
printf("---------------------------------------------------\n");
/* Peticin de datos */
printf("\nIntroduzca tres nmeros enteros distintos\n");
printf("Primer nmero?: "); scanf("%d", &n1);
printf("Segundo nmero?: "); scanf("%d", &n2);
printf("Tercer nmero?: "); scanf("%d", &n3);
/* Clculos y presentacin de resultados */
if ( (n1 == n2)||(n1 == n3)||(n2 == n3) ) {
printf("\nLos tres nmeros NO son distintos.\n");
printf("Vuelva a introducirlos.\n");
}
else {
if (n1 > n2)
if (n1 > n3)
Puede verse en este ejemplo la
mayor = n1;
conveniencia de realizar sangra a las
else
sentencias, para facilitar la lectura de la
mayor = n3;
estructura if-else anidada.
else
if (n2 > n3)
mayor = n2;
else
mayor = n3;
printf("El mayor es el %d\n", mayor);
}
}

El soporte de la construccin alternativa mltiple en el lenguaje C se consigue


mediante la sentencia switch.

Figura 7.12. Alternativa mltiple en C, sentencia switch

Sentencias de control

209

La semntica de esta construccin se refleja en la Figura 7.12, mientras que su


sintaxis se muestra a continuacin:
switch (expresin) {
case constante_1:
bloque de sentencias 1
break;
...
case constante_n:
bloque de sentencias n
break;
default:
bloque de sentencias x
}

Detalles relevantes a tener en cuenta sobre la sintaxis de esta construccin son:


Permite seleccionar una entre mltiples alternativas en funcin resultado
de una expresin entera o carcter. Normalmente la expresin se reduce
a una nica variable, de forma que lo que se est haciendo es investigar
el contenido de la misma.
Cuando la sentencia switch es ejecutada, se comprueba cada caso
(case), empezando por el primero y descendiendo, para ver si el valor
de la expresin es igual al de alguna constante. Si coinciden se
ejecutarn las sentencias correspondientes hasta que se encuentre una
sentencia break.
Una constante slo puede aparecer en un nico case.
La clusula default es opcional. Permite ejecutar una sentencia si no
se ha seleccionado ninguno de los casos. Si no existe, y el valor de la
expresin no coincide con ninguna constante, simplemente no se hace
nada.
Las clusulas break son tambin opcionales. Si no existen se
ejecutarn las sentencias especificadas asociadas al caso correspondiente
y todas las sentencias asociadas a casos posteriores hasta encontrar una
sentencia break.
En realidad cada caso no es ms que una etiqueta que marca el comienzo
de ejecucin de un conjunto de sentencias, pero no el final, por lo que
debe forzarse la salida del switch mediante una sentencia break.
EJEMPLO

Se va a implementar en C el segundo ejemplo presentado en la seccin 1.


#include <stdio.h>
void main (void) {
float num1, num2;
float solucion;
char opcion;
/* Presentacin
*/
printf("Aritmtica Bsica\n");
printf("=================\n\n");
printf("Introduzca dos nmeros reales y el tipo de operacin\n");
printf("a realizar S)uma R)esta P)roducto D)ivisin\n\n");
/* Peticin de datos */
printf("Primer nmero?: "); scanf("%f", &num1);
printf("Segundo nmero?: "); scanf("%f", &num2);

210

Programacin en C

printf("Tipo operacin (S/R/P/D)?: "); scanf("%*c%c", &opcion);


/* Clculos y presentacin de resultados */
switch (opcion) {
case 'S':
case 's':
solucion = num1 + num2;
printf("\n%g + %g = %g\n", num1, num2, solucion);
break;
case 'R':
case 'r':
solucion = num1 - num2;
printf("\n%g - %g = %g\n", num1, num2, solucion);
break;
case 'P':
case 'p':
solucion = num1 * num2;
printf("\n%g * %g = %g\n", num1, num2, solucion);
break;
case 'D':
case 'd':
solucion = num1 / num2;
printf("\n%g / %g = %g\n", num1, num2, solucion);
break;
default:
printf("\nTipo de operacin incorrecta\n");
} /* Fin de switch (opcion) */
}

En la prctica la sentencia switch se puede concebir como una alternativa al uso


de sentencias if-else anidadas que comprueben igualdades. En tales
circunstancias, utilizar la sentencia switch es por norma general ms conveniente.
EJEMPLO

Supngase que indicador es una variable de tipo int.


Construccin switch:
Estructura if-else equivalente:
switch (indicador) {
case 0:
y = sqrt(x);
break;
case 1:
y = x;
break;
case 2:
case 3:
y = 2 * (x-1);
break;
default:
y = 0;
}

if (indicador == 0)
y = sqrt(x);
else if (indicador == 1)
y = x;
else
if ((indicador==2)||
(indicador==3))
y = 2*(x-1);
else
y = 0;

EJEMPLO

Supngase que opcion es una variable de tipo char.

Construccin switch:

Estructura if-else equivalente:

switch (opcion) {
case 'A':
case 'a':
area = b * a;
perimetro = 2*(b+a);
break;
case 'B':
case 'b':
area = 3.14159*r*r;
perimetro= 2*3.1416*r;
break;
default:
printf("No valido");
area = 0;
perimetro = 0;
}

if ((opcion == 'A') ||
(opcion == 'a'))
{ area = b * a;
perimetro = 2*(b+a);
}
else if ((opcion == 'B') ||
(opcion == 'b')) {
area = 3.14159*r*r;
perimetro= 2*3.1416*r;
}
else
{ printf("No valido");
area = 0;
perimetro = 0;
}

7.4.1. El operador condicional ? :


Es un operador que equivale a una sentencia if-else de alternativa doble con
sentencias nicas.
(expresin_1) ? expresin_v : expresin_f;
La operacin se realiza de la siguiente forma: Se evala expresin_1, si el
resultado es cierto (cualquier valor distinto de cero) se evala expresin_v y su
resultado se toma como resultado de la expresin total. Por el contrario, si el resultado
de expresin_1 es falso (valor cero o nulo), se evala expresin_f y su
resultado se toma como resultado de la expresin total.
EJEMPLO

La expresin:
x = ( (a > b) ? a : b );

Asigna a x el mayor valor entre a y b. Adems, la sentencia if-else


equivalente es la siguiente:
if (a > b)
x = a;
else
x = b;
EJEMPLO

La expresin:
indicador = (i < 0) ? 0 : 100;

Si i es negativo, se le asignar a indicador el valor 0. Si i fuese 0 o


positivo, se le asignar el valor 100.
Se puede ver que como el operador condicional tiene una prioridad superior
al operador asignacin, pueden suprimirse los parntesis que encierran a la
expresin de la derecha. La sentencia if-else equivalente es la siguiente:
if (i < 0) indicador = 0;
else
indicador = 100;

- 211 -

212

Programacin en C

EJEMPLO

Sea la sentencia:
eleccion ? printf("No cero.\n") : printf("Cero.\n");

Si eleccion es distinto de 0 (verdadero), se ejecuta el primer printf,


presentando el mensaje No cero. Si eleccion es 0 (falso), se ejecuta el
segundo printf, presentando en mensaje Cero.
La sentencia if-else equivalente es la siguiente:
if (eleccion) printf("No cero.\n");
else
printf("Cero.\n");

7.5. Sentencia de salto en C


Se utiliza para alterar la secuencia de ejecucin normal del programa, transfirindose
el control a otra parte de l.
En C la sentencia de salto incondicional es goto. En su forma general, la sintaxis
de la sentencia goto se escribe:
goto etiqueta;
Donde etiqueta es un identificador que se utiliza para rotular la sentencia a la
que se transfiere el control.
Se puede transferir el control a cualquier otra sentencia dentro de la funcin actual.
La sentencia por la que se contina debe encontrarse etiquetada, y la etiqueta debe ir
seguida de dos puntos (:), de la siguiente forma:
etiqueta: sentencia;
La etiqueta debe ser nica dentro de la funcin actual, es decir, dos sentencias
dentro de la misma funcin no pueden tener la misma etiqueta.

Buen estilo de programacin


La sentencia goto no es una sentencia muy prestigiada en el mundo de la
programacin C, pues disminuye la claridad y legibilidad del cdigo. Fue
introducida en el lenguaje por motivos de compatibilidad con antiguos hbitos de
programacin, y siempre puede ser sustituida por otras construcciones ms claras
y estructuradas, ya que los elementos estructurados del lenguaje C permiten
realizar todo tipo de operaciones sin recurrir a la sentencia goto. El uso de goto
tiende a que se disperse la lgica subyacente por todo el programa, mientras que
los elementos estructurados de C requieren que todo el programa se escriba de
forma ordenada y secuencial.
Por esta razn, la utilizacin de la sentencia goto se debe evitar, como norma
general, dentro de los programas en C.
EJEMPLO

Se va a codificar en lenguaje C un segmento de programa que permita


obtener todas las combinaciones de horas y minutos de un da. Van a
utilizarse instrucciones de salto (goto).

Sentencias de control

213

int hh, mm;


hh = 0;
...
bucle_hh:
if (hh > 23)
goto salida;
else
{
mm = 0;
bucle_mm: if (mm > 59)
goto inc_hh;
else
{ printf("%02d:%02d\n", hh, mm);
mm++;
goto bucle_mm;
}
inc_hh:
hh++;
goto bucle_hh;
}
salida:

Este mismo ejemplo se ver codificado ms adelante utilizando sentencias


repetitivas propias de la programacin estructurada.

7.6. Sentencias repetitivas en C


El lenguaje C presenta diferentes estructuras repetitivas.

7.6.1. El bucle mientras en C


El bucle mientras en el lenguaje C se soporta mediante la sentencia while. La
semntica de esta sentencia se refleja en la Figura 7.13, mientras que su sintaxis se
muestra a continuacin:
while (condicin) {
sentencia_1;
...
sentencia_n;
}

Figura 7.13. Bucle while

214

Programacin en C

EJEMPLO

Sea el siguiente fragmento escrito en lenguaje C:


tiempo = 1;
while (tiempo <=3) {
printf("Valor de tiempo = %d \n", tiempo);
tiempo++;
}

La salida por pantalla que se corresponde con el fragmento anterior es:


Valor de tiempo = 1
Valor de tiempo = 2
Valor de tiempo = 3

Este fragmento se puede escribir de forma ms concisa de la siguiente forma:


tiempo = 1;
while (tiempo <=3)
printf("Valor de tiempo = %d \n", tiempo++);

O tambin de la siguiente forma:

tiempo = 0;
while (tiempo++ < 3)
printf("Valor de tiempo = %d \n", tiempo);

EJEMPLO
El siguiente programa permite obtener la suma, la media y el nmero de
elementos que componen una coleccin de nmeros enteros positivos que se
introducirn por teclado. La introduccin de nmeros terminar cuando se
teclee un valor negativo (bucle controlado por centinela).
#include <stdio.h>
void main (void) {
int numero; /* Cada uno de los nmeros de la coleccin */
long
suma;
float
media;
unsigned int cuantos;
/* Presentacin
*/
printf("Suma de una lista de nmeros enteros positivos \n");
printf("==============================================
\n\n");
printf("Introduzca una lista de nmeros enteros positivos y
\n");
printf("el programa calcular su suma. \n");
printf("Para finalizar la introduccin de nmeros teclee
\n");
printf("cualquier valor negativo. \n\n");
/* Peticin de datos y clculos */
suma = 0;
cuantos = 0;
printf("Introduzca nmero?: ");
scanf("%d", &numero);
while (numero >= 0) {
suma += numero;
cuantos++;
printf("Introduzca nmero?: ");
scanf("%d", &numero);
}
/* Presentacin de resultados */
if (cuantos > 0) {
media = (float) suma / cuantos;
printf("\n");

Sentencias de control

215

printf("Dimensin de la lista..: %u\n", cuantos);


printf("Suma de la lista.......: %ld\n", suma);
printf("Media de la lista......: %g\n", media);
}
else
printf("No se ha introducido ningn dato.\n");

EJEMPLO

El siguiente programa convierte una cadena de caracteres a maysculas.


#include <stdio.h>
#include <ctype.h>
#define NULO '\0'
void main (void) {
char cadena[120];
int cont;
/* Presentacin y peticin de datos */
printf("Conversin de una cadena de caracteres a maysculas \n");
printf("=================================================== \n\n");
printf("Introduzca una cadena de caracteres y el programa la \n");
printf("convertir a maysculas. \n");
printf("\n?: ");
gets(cadena);
/* Clculos */
cont = 0;
while (cadena[cont] != NULO) {
cadena[cont] = toupper(cadena[cont]);
cont++;
}
/* Resultados */
printf(">: ");
puts(cadena);
}

Un ejemplo de ejecucin del programa puede ser:

?: Tu mirada me recuerda el Fulgor de la Aurora...


>: TU MIRADA ME RECUERDA EL FULGOR DE LA AURORA...

Normalmente, el bucle ms apropiado para recorrer una cadena de caracteres y


realizar un tratamiento individual a cada carcter, es el bucle while. Se comienza
con el ndice 0 para el primer carcter de la cadena y se recorre sta (incrementando
el ndice) hasta encontrar el carcter '\0' que marca su final. A continuacin se
muestra un esqueleto de cdigo C que ilustra esta circunstancia:
#define NULO '\0'
int cont;
cont = 0;
while (cadena[cont] != NULO) {
sentencias de tratamiento
}

cont++;

EJEMPLO

En este ejemplo se muestra un segmento de cdigo que sustituye en la


cadena de caracteres mensaje, todas las veces que aparece el carcter
c_ini por el carcter c_ult.

216

Programacin en C
#define NULO '\0'
int cont;
cont = 0;
while (mensaje[cont] != NULO) {
if (mensaje[cont] == c_ini)
mensaje[cont] = c_ult;
cont++;
}

EJEMPLO

Se muestra un segmento de cdigo C que calcula la longitud de la cadena de


caracteres mensaje.
#define NULO '\0'
int long;
long = 0;
while (mensaje[long] != NULO)
long++;

Hay ocasiones en las que al usuario se le pide que teclee una cadena de caracteres,
y sta puede ser leda carcter a carcter, ya que en algunos casos, segn el algoritmo
a desarrollar, es ms conveniente recibir la cadena de esta forma. Para ello se utiliza
un bucle while y la funcin getchar().
EJEMPLO

El siguiente segmento de cdigo lee de teclado el nombre y apellidos de una


persona en el formato NOMBRE*APELLIDO1*APELLIDO2, e internamente
almacena el NOMBRE en la cadena nomb, APELLIDO1 en la cadena ape1 y
APELLIDO2 en la cadena ape2.
Obsrvese que como se est cargando carcter a carcter cada cadena, y
cmo debe colocarse siempre el carcter nulo final ('\0').
#define NULO
'\0'
#define SEPARADOR '*'
#define FINLINEA '\n'
...
int cont;
char nomb[50];
char ape1[50];
char ape2[50];
...
printf("Introduzca nombre en formato
NOMBRE*APELLIDO1*APELLIDO2 \n");
cont = 0;
nomb[cont] = getchar();
while (nomb[cont] != SEPARADOR) {
cont++;
nomb[cont] = getchar();
}
nomb[cont] = NULO;
cont = 0;
ape1[cont] = getchar();
while (ape1[cont] != SEPARADOR) {
cont++;

Sentencias de control

217

ape1[cont] = getchar();
}
ape1[cont] = NULO;
cont = 0;
ape2[cont] = getchar();
while (ape2[cont] != FINLINEA) {
cont++;
ape2[cont] = getchar();
}
ape2[cont] = NULO;

7.6.2. El bucle repetir mientras en C


El bucle repetir mientras en el lenguaje C se soporta mediante la construccin
do while. La semntica de esta sentencia se refleja en la Figura 7.14, mientras que
su sintaxis se muestra a continuacin:
do {
sentencia_1;
...
sentencia_n;
} while (condicin);

Figura 7.14. Bucle do while


EJEMPLO

El siguiente fragmento escrito en lenguaje C utiliza un bucle do while,


siendo equivalente al cdigo mostrado anteriormente con un bucle while.
tiempo = 1;
do {
printf("Valor de tiempo = %d \n", tiempo);
tiempo++;
} while (tiempo <= 3);

La salida por pantalla que se corresponde con el fragmento anterior es:


Valor de tiempo = 1
Valor de tiempo = 2
Valor de tiempo = 3

218

Programacin en C

Este fragmento se puede escribir de forma ms concisa de la siguiente forma:


tiempo = 1;
do
printf("Valor de tiempo = %d \n", tiempo++);
while (tiempo <= 3);

O tambin de la siguiente forma:


tiempo = 1;
do
printf("Valor de tiempo = %d \n", tiempo);
while (tiempo++ < 3);

EJEMPLO

Clculo del capital acumulado a lo largo de varios aos. Dado un inters y un


capital inicial sobre el que se aplica, se determina el ao en el que el capital
total duplica a la inversin inicial. Se presenta, adems, el capital total al
final de cada ao.
#include <stdio.h>
void main (void) {
float capital, capital_inicial, limite;
int
anio;
float tasa_interes, intereses;
/* Presentacin */
printf("Clculo del capital acumulado a lo largo de varios aos\n");
printf("=======================================================\n");
printf("Dado un inters y un capital inicial sobre el que se
\n");
printf("aplica, se determina el ao en que el capital total
\n");
printf("duplica a la inversin inicial. \n");
/* Peticin de datos */
printf("Capital inicial?: "); scanf("%f", &capital_inicial);
printf("Inters?: ");
scanf("%f", &tasa_interes);
/* Clculos y resultados */
capital = capital_inicial;
anio = 0;
limte = 2 * capital_inicial;
do {
anio++;
intereses = (tasa_interes * capital) / 100;
capital = capital + intereses;
printf("Ao %2d. Capital %10.2f\n", anio, capital);
} while (capital < limite);
}

A continuacin se presenta un ejemplo de la ejecucin del programa:


Capital inicial?: 1000000
Inters?: 6.4
Ao 1. Capital 1064000.00
Ao 2. Capital 1132096.00
Ao 3. Capital 1204550.12
Ao 4. Capital 1281641.38
Ao 5. Capital 1363666.38
Ao 6. Capital 1450941.00
Ao 7. Capital 1543801.25
Ao 8. Capital 1642604.50
Ao 9. Capital 1747731.25
Ao 10. Capital 1859586.00
Ao 11. Capital 1978599.50
Ao 12. Capital 2105229.75

Sentencias de control

219

EJEMPLO

Cuando se quiere crear un programa que despus de presentar los resultados


en pantalla, en lugar de terminar, solicite al usuario si desea una nueva
ejecucin, suele utilizarse la estructura do while, tal como muestra este
ejemplo.
Este programa determina si un ao, que se introduce por teclado, es o no
bisiesto. Un ao es bisiesto si es mltiplo de 4 (por ejemplo 1984). Sin
embargo, los aos mltiplos de 100 slo son bisiestos cuando a su vez son
mltiplos de 400 (el ao 2000 es bisiesto, el 1800 no lo es). Todo esto se
resume en la siguiente tabla:
Mltiplo 4
(n%4==0)
No
Si
Si
Si

Mltiplo 100
(n%100==0)
No
Si
Si

Mltiplo 400
(n%400==0)
Si
No

Bisiesto
No
Si
Si
No

#include <stdio.h>
#include <ctype.h>
void main (void) {
int anio;
int bisiesto;
char terminar;
/* Presentacin */
printf("Aos bisiestos\n");
printf("==============\n");
printf("\nDetermina si el ao que se indica es bisiesto o no.\n\n");
do {
/* Peticin de datos.
Se repite hasta que se introduzca un ao correcto */
do {
printf("\nAo?: ");
scanf("%d", &anio);
} while (anio <= 0);
/* Clculos */
if (anio % 4)
bisiesto = 0;
else if (anio % 100)
bisiesto = 1;
else if (anio % 400)
bisiesto = 0;
else bisiesto = 1;
/* Resultados */
if (bisiesto)
printf("%d es bisiesto.", anio);
else printf("%d NO es bisiesto.", anio);
printf("\n\nTerminar (S/N)?: "); scanf("%*c%c", &terminar);
} while (toupper(terminar) != 'S');
}

EJEMPLO

Se codifica en C el algoritmo, ya visto anteriormente, que permite calcular


cuantos nmeros componen una coleccin de enteros, que se introducir por
teclado, as como el valor de su suma. La introduccin de nmeros terminar
cuando su suma supere el valor 15000 o bien cuando se hayan introducido

220

Programacin en C

25 nmeros. Se va a emplear un bucle do while controlado por un flag


llamado terminar.
#include <stdio.h>
#define FALSO
0
#define CIERTO
1
#define MAXNUM 25
#define MAXSUMA 15000
void main (void) {
int
numero;
long suma=0;
int
cuantos=0;
int
terminar; /* flag */
/* Presentacin */
printf("Listas de nmeros enteros \n");
printf("========================= \n");
printf("\nDetermina cuantos nmeros componen una coleccin de \n");
printf("enteros que se introduce por teclado, as como el valor\n");
printf("de su suma.\n");
printf("\La introduccin de nmeros terminar cuando:\n");
printf("
su suma supere el valor %d\n", MAXSUMA);
printf("
se introduzcan %d nmeros\n\n", MAXNUM);
/* Peticin de datos y clculos */
terminar = FALSO;
do {
printf("Introduzca nmero?: ");
scanf("%d", &numero);
cuantos++;
suma += numero;
if (cuantos >= MAXNUM || suma > MAXSUMA)
terminar = CIERTO;
} while (!terminar);
/* Presentacin de resultados */
if (cuantos > 1) {
printf("\Se han introducido %d nmeros\n", cuantos);
printf("Su suma vale %ld\n", suma);
}
else
printf("\Se han introducido slo el nmero %ld\n", suma);
}

7.6.3. El bucle desde en C


El bucle desde en el lenguaje C se soporta mediante la sentencia for. La semntica
de esta sentencia se refleja en la Figura 7.15, mientras que su sintaxis se muestra a
continuacin:
for (iniciacin; condicin; incremento) {
sentencia_1;
...
sentencia_n;
}

En el lenguaje C, el bucle for ofrece una gran potencia y una enorme flexibilidad,
pues su formato no es tan rgido como el que, de forma genrica, se ha descrito
anteriormente para el bucle desde, comn a la mayora de los lenguajes de
programacin de alto nivel.

Sentencias de control

221

Figura 7.15. Bucle for

El bucle for en el lenguaje C puede dividirse en cuatro partes:


1. Iniciacin. Es la sentencia que se ejecutar una nica vez antes de
evaluar la condicin que controla el bucle. Pueden incluirse en la
iniciacin varias sentencias C separadas por el operador coma (,).
2. Condicin de salida del bucle. El bucle se repetir mientras esta
condicin sea cierta (distinta de 0).
3. Incremento. Es la sentencia que se ejecutar, en cada iteracin, despus
de la ltima sentencia del cuerpo del bucle. Pueden incluirse en el
incremento varias sentencias C separadas por el operador coma (,).
4. Cuerpo del bucle. Grupo de sentencias que se repetirn en cada
iteracin.
La potencia y flexibilidad del bucle for en C, respecto a la implementacin del
bucle desde en otros lenguajes de programacin, viene dada porque las sentencias
incluidas tanto en la iniciacin como en el incremento, pueden ser cualquier expresin
vlida en C, y no necesariamente una sentencia de iniciacin para la primera, y una de
incremento o decremento para la segunda.
En el lenguaje C, tanto los bucles while y do-while como el bucle for, se
pueden utilizar para realizar repeticiones donde no se conozca a priori el nmero de
iteraciones a realizar por el bucle. Sin embargo, debido a los elementos incorporados
en la sentencia for, sta es particularmente adecuada para bucles donde se conozca a
priori el nmero de iteraciones (bucles controlados por contador). Como regla
prctica, los bucles while y do-while se utilizan generalmente cuando no se
conoce a priori el nmero de iteraciones, y el bucle for para implementar bucles
controlados por contador.
EJEMPLO

Como se ha venido haciendo con el bucle while y como el bucle do while, se


va a construir como primer ejemplo un sencillo bucle que muestre un simple
contador.
for (tiempo = 1; tiempo <=3; tiempo++)
printf("Valor de tiempo = %d \n", tiempo);

222

Programacin en C

De forma que la salida en pantalla asociada al fragmento anterior sera:


Valor de tiempo = 1
Valor de tiempo = 2
Valor de tiempo = 3

En este ejemplo se puede detectar:


1. La iniciacin, que es una expresin para dar valor inicial a la
variable de control del bucle (variable contador), valor con el que la
variable de control realiza la primera iteracin.
tiempo = 1

2.

La condicin de salida del bucle, incluye una expresin relacional


sobre la variable de control, de forma que el bucle se repetir
mientras esta condicin sea cierta (distinta de cero).

3.

El incremento puede ser una expresin monaria o de asignacin,


que incrementa la variable de control justo despus de ejecutarse la
ltima sentencia del cuerpo del bucle.

tiempo <=3

tiempo++

La iniciacin, condicin e incremento del bucle for son opcionales, no es


imprescindible que existan, pero siempre debe colocarse el punto y coma (;) de
separacin.
Si la condicin de salida no existe se considera como permanentemente verdadera
dicha condicin, es decir, se tendra un bucle infinito.
La iniciacin y el incremento pueden constar de varias sentencias, separadas por el
operador coma, pero slo puede existir una condicin de salida del bucle.
EJEMPLO

El siguiente bucle admite una cadena por teclado y muestra por pantalla los
caracteres que la constituyen junto con su correspondiente cdigo ASCII.
for (; (valor = getchar() ) != '\n';) printf("%c - %d\n", valor, valor);

Si la entrada de datos es:


ABC<intro>

La salida sera:
A - 65
B - 66
C - 67

Adems, el bucle while equivalente es:


while ((valor = getchar()) != '\n') printf("%c - %d\n", valor, valor);

EJEMPLO

El siguiente bucle for presenta varias expresiones de iniciacin y de


incremento.
for (cn=0, vi=2; (cn <= 2) || (vi < 15); cn++, vi+=3)
printf("%d, %d\n", cn, vi);

Al fragmento de cdigo anterior le corresponde la siguiente salida:


0,
1,
2,
3,
4,

2
5
8
11
14

Sentencias de control

223

7.6.4. Bucles anidados en C


Se presentan a continuacin distintos ejemplos de bucles anidados en C.
EJEMPLO

Se va a implementar en C el algoritmo en el cual se obtenan todas las


combinaciones de horas y minutos de un da. Se emplearn dos sentencias
for anidadas.
int horas, minutos;
...
for (horas=0; horas<=23; horas++)
for (minutos=0; minutos<=59; minutos++)
printf("%02d:%02d\n", horas, minutos);

Salida en pantalla:
00:00
00:01
00:02
...
00:58
00:59
01:00
01:01
...
01:58
01:59
02:00
02:01
...
23:57
23:58
23:59

Se imprimen 24 x 60 = 1440.
EJEMPLO

Se va a implementar en C un programa que presente la siguiente salida en


pantalla:
ZYXWVUTSRQPONMLKJIHGFEDCBA
YXWVUTSRQPONMLKJIHGFEDCBA
XWVUTSRQPONMLKJIHGFEDCBA
WVUTSRQPONMLKJIHGFEDCBA
VUTSRQPONMLKJIHGFEDCBA
UTSRQPONMLKJIHGFEDCBA
TSRQPONMLKJIHGFEDCBA
SRQPONMLKJIHGFEDCBA
RQPONMLKJIHGFEDCBA
QPONMLKJIHGFEDCBA
PONMLKJIHGFEDCBA
ONMLKJIHGFEDCBA
NMLKJIHGFEDCBA
MLKJIHGFEDCBA
LKJIHGFEDCBA
KJIHGFEDCBA
JIHGFEDCBA
IHGFEDCBA
HGFEDCBA
GFEDCBA
FEDCBA
EDCBA
DCBA
CBA
BA
A

224

Programacin en C
#include <stdio.h>
void main(void) {
char linterior, lexterior;
for (lexterior= 'Z'; lexterior >='A'; lexterior--) {
for (linterior=lexterior; linterior >='A'; linterior--)
printf("%c", linterior);
printf("\n");
}
}

EJEMPLO

Se va a implementar en C un programa que presente la siguiente salida en


pantalla:
0
101
21012
3210123
432101234
54321012345
6543210123456
765432101234567
87654321012345678
9876543210123456789
87654321012345678
765432101234567
6543210123456
54321012345
432101234
3210123
21012
101
0

Para ello se deben tener en cuenta los siguientes aspectos:


La figura se genera lnea a lnea.
Para generar cada lnea de la figura participan tres bucles.
La Figura 7.16 indica qu parte de la lnea realiza cada bucle.

Figura 7.16 Generacin de la figura


#include <stdio.h>
#define BLANCO '\040'
void main (void) {

Sentencias de control

225

int i, j, k, b;
for (i=0; i <= 9; i++) {
for (b=1; b <= 9-i; b++)
printf("%c", BLANCO);
for (k=i; k >= 1; k--)
printf("%d", k);
for (j=0; j <= i; j++)
printf("%d", j);
printf ("\n");
}
for (i=8; i >= 0; i--) {
for (b=1; b <= 9-i; b++)
printf("%c", BLANCO);
for (k=i; k >= 1; k--)
printf("%d", k);
for (j=0; j <= i; j++)
printf("%d", j);
printf ("\n");
}
}

/*bucle 1*/
/*bucle 2*/
/*bucle 3*/

/*bucle 4*/
/*bucle 5*/
/*bucle 6*/

EJEMPLO

Se va a codificar en C un programa que determina, dado un intervalo de


nmeros enteros positivos, cuntos nmeros primos hay dentro del mismo, y
cul es la suma de dichos primos.
#include <stdio.h>
#include <math.h>
#define FALSO
0
#define CIERTO 1
#define LIMINF 150L
#define LIMSUP 2500L
void main (void) {
long numero, divisor, suma=0, cuantos=0;
float limite;
int primo; /* flag */
/* Presentacin */
printf("Clculo de nmeros primos \n");
printf("========================= \n");
printf("\nEl programa determina cuantos nmeros primos existen \n");
printf("entre %ld y %ld, as como su suma.\n", LIMINF, LIMSUP);
for (numero = LIMINF; numero <= LIMSUP; numero++) {
primo = CIERTO;
divisor = 2;
limite = sqrt(numero);
while (primo && divisor <= limite) {
if (numero % divisor == 0) /* comprueba si resto cero */
primo = FALSO;
divisor++;
}
if (primo) {
cuantos++;
suma+= numero;
}
}
printf("\n\nEntre %ld y %ld hay %ld nmeros primos.\n",
LIMINF, LIMSUP, cuantos);
printf("Su suma vale %ld.\n", suma);
}

226

Programacin en C

7.6.5. La sentencia break dentro de un bucle


La sentencia break se utiliza, adems de para salir de una sentencia switch, para
terminar anticipadamente la ejecucin de un bucle for, while o do while.
Provoca que el bucle desde el que se invoca termine inmediatamente.
Si es invocado dentro de un bucle anidado, termina solamente el bucle desde el que
ha sido invocado, pero no el bucle externo a ste.
Esta sentencia proporciona una forma conveniente de terminar un bucle cuando se
detecta un error o alguna otra condicin irregular.
EJEMPLO

Sea el siguiente fragmento de cdigo escrito en lenguaje C:


for (externo=0; externo <= 6; externo++) {
interno = 3;
while (interno <= 8) {
printf("externo:%d,
interno:%d\n",
interno);
interno++;
if (interno == 6)
break;
}
}

externo,

Salida en pantalla:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:

0,
0,
0,
1,
1,
1,
2,
2,
2,
3,
3,
3,
4,
4,
4,
5,
5,
5,
6,
6,
6,

interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:

3
4
5
3
4
5
3
4
5
3
4
5
3
4
5
3
4
5
3
4
5

7.6.6. La sentencia continue


Es muy similar a la sentencia break con la salvedad de que continue se salta el
resto del cdigo asociado al cuerpo bucle, pero retorna a la condicin del bucle, y si
sta es cierta, ejecuta la siguiente iteracin. Se recuerda que break rompe el bucle
definitivamente.

Sentencias de control

227

EJEMPLO

Sea el siguiente fragmento de cdigo escrito en lenguaje C:


for (externo=1; externo <= 10; externo++) {
if ((externo % 2) == 0)
continue;
interno = 3;
while (interno <= 5) {
printf("externo: %d", externo);
printf("interno: %d\n", interno);
interno++;
}
}

Salida en pantalla:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:
externo:

1,
1,
1,
3,
3,
3,
5,
5,
5,
7,
7,
7,
9,
9,
9,

interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:
interno:

3
4
5
3
4
5
3
4
5
3
4
5
3
4
5

7.7. Cuestiones y ejercicios


1.
2.

3.

Cundo interpreta el lenguaje C el valor verdadero o falso en las sentencias


alternativas y condiciones de control de los bucles?
La sentencia if:
Cul es su propsito?
Entre cuntas bifurcaciones de cdigo permite escoger una sentencia
if simple?
Anidando sentencias if, entre cuntas bifurcaciones podr escogerse
como mximo?
En una sentencia if-else, en funcin de qu valor de la expresin
que controla la sentencia if se decide ejecutar un bloque de sentencias
u otro?
Las siguientes expresiones tienen sentido, pero no son vlidas de acuerdo
con las reglas sintcticas del lenguaje C. Rescribirlas como expresiones
lgicas vlidas para ser utilizadas como expresin de control de una
sentencia if.
a) x mayor que 0.
b) z menor o igual a y.

228

4.

5.

6.

Programacin en C

c) x diferente a z.
d) x menor o igual a 0.
e) y igual a 0.
f) x deber ser mayor que z o menor que 0 y, adems, distinto de y.
g) a distinto de b y distinto de c, y b tambin distinto de c.
h) a distinto de 10 o distinto de 24.
i) z debe estar comprendido entre -5 y 15.
j) y no puede estar comprendido entre 5 y 25.
k) a, b y c deben tener el mismo valor.
Construir en C una sentencia if que imprima en pantalla el mensaje ES
UNA MAYUSCULA, si el valor del carcter letra es una letra mayscula;
imprima el mensaje ES UNA MINUSCULA si el valor del carcter letra
es una letra minscula, y el mensaje NO ES UNA LETRA si el valor del
carcter letra no es una letra. (Atencin con la letra , la letra , y las
vocales acentuadas. Comprobar en la tabla ASCII en qu posicin est).
La Sentencia switch:
Cul es su propsito?
Entre cuntas bifurcaciones de cdigo permite escoger como mximo?
El siguiente fragmento de cdigo escrito en C visualiza por pantalla todos los
mltiplos de 13 comprendidos entre 13 y 10000 (ambos lmites incluidos).
Se ha utilizado una estructura while para realizar el bucle. Realizar el
mismo algoritmo utilizando una estructura do-while y una estructura for.
multiplo = 13;
while (multiplo <= 10000) {
printf("%d", multiplo);
multiplo = multiplo + 13;
}

7.

8.
9.

Escribir un segmento de cdigo en C (atencin, no se pide un programa


entero) que presente los 100 primeros nmeros naturales en pantalla (en
orden CRECIENTE de 1 a 100) y, adems, calcule y presente su suma.
Debern escribirse tres versiones de este cdigo (con for, con while y con
do-while). Las tres versiones debern presentar salidas idnticas.
Modificar ahora los tres segmentos de cdigo anteriores para que la
presentacin de los nmeros sea en orden DECRECIENTE (de 100 a 1).
Indicar cuntas iteraciones realizan los siguientes bucles for y el valor que
la variable de control tendr en cada iteracin. Si alguno de ellos est mal
escrito indicar el error. Atencin: el que un bucle for no realice ninguna
iteracin no significa que est mal escrito.
for
for
for
for
for
for
for
for
for

(i=134; i<=135; i++)


(k='F'; k<='d'; k++)
(i=100; i<=97; i++)
(n=1.5; n<=3; n++)
(x='k'; x<=''; x++)
(j=0; j<=0; j++)
(k=0; k<=0; k--)
(h=1; h<=1.5; h+=0.1)
(m=7; m<=1; m++)

Sentencias de control

for
for
for
for
for
for
for

10.

(c='f'; c<='a'; c--)


(f=1; f<=0; f++)
(f=1; f<=0; f--)
(i=-10; i<=3; i++)
(f=-3; f<=1; f--)
(p="ABC"; p<="ABF"; p++)
(d='b'; d<='ch'; d++)

Indicar cuntas veces se ejecutarn los siguientes bucles. Dibujar cul ser el
resultado de su ejecucin en pantalla.
a) i = 1;
while (i < 10) {
i++;
printf("%d", i);
printf("*");
}
c) i = 1;
do {
printf("%d", i);
i = i * 2;
printf("*\n");
} while (i > 0);

11.

229

b) i = 10;
do {
printf("%d\n", i);
i += 3;
printf("*");
} while (i != 20);
d) for(i = -1; i >= -20; i--) {
x = 0;
while (x < 10) {
printf("%d", x);
x = x + 2;
}
}

e) i= 0;
j= 2;
printf("j
i\n");
for(i = 1; i <= 3; i++) {
printf("-\n");
for(j = 12; j >= 10, j--)
printf("%d %d\n", j, i);
}

Los siguientes fragmentos de cdigo representan unas estructuras de


sentencias if anidadas. Implementarlas en forma de estructura switch.
Anidar estructuras switch si es necesario. No debe utilizarse ni una sola
sentencia if.
a) x = 40;
y = 10;
scanf("%c", &ch);
if ((ch != 'P') && (ch != 'p'))
if ((ch == 'S') || (ch == 's'))
y--;
else if ((ch == 'B') || (ch == 'b'))
y++;
else if ((ch == 'D') || (ch == 'd'))
x++;
else if ((ch == 'I') || (ch == 'i'))
x--;
else printf("carcter incorrecto");
printf ("valor de x = %d, valor de y = %d\n", x, y);
b) scanf("%d", &num);
if ((num == 11000) || (num == 11500))
serie = 1;
else if ((num == 12000) || (num == 12500))
serie = 2;
else if ((num == 13000) || (num == 13500))
serie = 3;
else serie = 0;

230

12.

Programacin en C

La siguiente estructura if anidada tiene cinco posibles ramificaciones (que


imprimirn cinco mensajes distintos en pantalla), que pueden tomarse
dependiendo de los valores ledos en las variables a, b y c. Para probar la
ejecucin correcta de la estructura, se necesitan cinco conjuntos de datos,
cada conjunto produciendo una bifurcacin diferente. Escribir la lista de los
cinco conjuntos de datos para la prueba.
scanf("%d", &a);
scanf("%d", &b);
scanf("%d", &c);
if (a = b)
if (b = c)
printf("Mensaje 1.\n");
else printf("Mensaje 2.\n");
else
if (b = c)
printf("Mensaje 3.\n");
else if (a = c)
printf("Mensaje 4.\n");
else printf("Mensaje 5.\n");

13.
14.

15.

16.

17.
18.

Qu se entiende por bucle? Y por iteracin? Y por cuerpo del bucle? Y


por variable de control del bucle?
Responde a las siguientes preguntas referentes al bucle while:
Cul es su propsito?
Cundo se evala la expresin lgica?
Cundo finalizar la ejecucin del bucle?
Cul es el nmero mnimo de veces que se ejecutar el cuerpo del
bucle?
Responde a las siguientes preguntas referentes al bucle do while:
Cul es su propsito?
Cundo se evala la expresin lgica?
Cundo finalizar la ejecucin del bucle?
Cul es el nmero mnimo de veces que se ejecutar el cuerpo del
bucle?
Responde a las siguientes preguntas referentes al bucle for:
Cul es su propsito?
Cundo se evala la expresin lgica?
Cundo finalizar la ejecucin del bucle?
Cul es el nmero mnimo de veces que se ejecutar el cuerpo del
bucle?
Compara las respuestas anteriores para ver en que se parecen y se diferencian
los tres bucles.
Qu imprime en pantalla el siguiente bucle? (numero es una variable de
tipo int).
numero = 1;
while (numero < 11) {
numero = numero + 1;
printf("%d", numero);
}

Sentencias de control

19.

231

Cambiando simplemente el orden de las sentencias (sin modificar ninguna de


ellas) hacer que el bucle imprima los nmeros del 1 al 10.
Cuando se ejecute el siguiente cdigo, cuntas iteraciones del bucle se
ejecutarn?
numero = 2;
hecho = 0;
while (!hecho) {
numero = numero * 2;
if (numero > 64)
hecho = 1;
}

20.
21.

22.

23.

24.

Cul es el papel de la variable de control en la sentencia for? De qu tipo


puede ser la variable de control?
Escribir un segmento de cdigo en C (atencin, no se pide un programa
entero) que imprima en pantalla la secuencia de todas las combinaciones de
hora y minutos de un da, comenzando en 00:00 y terminando en 23:59.
Deben escribirse tres versiones de ste cdigo (con for, con while y con
do-while). Las tres versiones debern presentar salidas idnticas.
Escribir un segmento de cdigo en C (nota: no se pide un programa entero)
que calcule el valor de la suma de los nmeros impares comprendidos entre
2000 y 4500. Deben escribirse tres versiones de ste cdigo (con for, con
while y con do-while). Las tres versiones debern presentar salidas
idnticas.
Se dispone de una variable llamada selector de tipo char. Construir una
estructura if-else anidada de forma que:
Si el valor de selector es una letra mayscula, sume 1 a un contador
llamado total.
Si el valor de selector es una letra minscula, salvo la 'a' o la 'o',
sume un 2 al contador total.
Si el valor de selector es la letra 'a' o la letra 'o', no debe hacerse
nada.
Si el valor de selector es un asterisco (*), o un signo ms (+), sume
3 al contador total.
En cualquier otro caso, debe sumar -1 a total e imprimir en pantalla
un mensaje que diga "se decrementa el valor total".
Simplificar el siguiente segmento de programa de forma que se necesiten
menos comparaciones. Ayuda: utilizar operadores lgicos y sentencias ifelse.
if

(edad > 18)


if (edad < 100)
if (edad > 64)
printf("seguridad social");
else printf("exento");
else printf ("centenario");
if (edad <= 18)
if edad > 0
printf ("menor de edad");
else printf ("imposible");

232

25.
26.
27.
28.
29.

Programacin en C

Cul es la finalidad de la sentencia break? Dentro de qu sentencias de


control puede incluirse la sentencia break?
Supngase que una sentencia break se encuentra incluida en la ms interna
de una serie de sentencias de control anidadas. Qu ocurre cuando se
ejecuta la sentencia break?
Realizar un algoritmo en pseudocdigo que presente en pantalla todos los
nmeros mltiplos de 7 11 comprendidos entre 50 y 225 y, adems,
indique la suma de sus cuadrados.
Realizar un algoritmo en pseudocdigo que presente en pantalla el factorial
de un nmero que se introduzca por teclado.
El siguiente segmento de cdigo se ha escrito utilizando una estructura
switch. Rescribirlo utilizando para ello una estructura de sentencias ifelse anidadas (usando los menos anidamientos posibles) que realice
exactamente las mismas acciones:
switch
case
case
case
case

30.

31.

(num) {
2:
3:
5:
7: printf("Nmero primo entre 1 y 10.");
break;
case 11:
case 13:
case 17:
case 19: printf("Nmero primo entre 11 y 20.");
break;
case 23:
case 29: printf("Nmero primo entre 21 y 30.");
break;
default: printf("El nmero no est entre los primeros primos.");
}

Realizar un segmento de cdigo en C que presente en pantalla en orden


descendente todas las letras maysculas del alfabeto, y posteriormente, todas
las letras minsculas en orden ascendente. (No es necesario tratar la ch y la
en ninguno de los dos casos).
Realizar un algoritmo en pseudocdigo que calcule la suma de los 50
primeros trminos de la serie:
2 + 6 + 10 + 14 + 18 + ...

32.

Realizar un algoritmo en pseudocdigo que calcule la suma, hasta el trmino


N introducido por teclado, de la serie:

33.

Realizar un programa que calcule el valor de N para el cual la suma de la


serie siguiente supere el valor S que se introduzca por teclado.

2/3 + 3/5 + 4/7 + 5/9 + ... + (N+1)/(2N+1)

3/2 + 6/5 + 11/10 + ... + (N2+2)/(N2+1)

8. Estructuras estticas de datos


En los captulos anteriores se ha introducido el concepto de dato atmico que
representa valores de tipo simple, como un nmero entero, real o un carcter. En
muchas situaciones se necesita, sin embargo, procesar una coleccin de valores que
estn relacionados entre s, como por ejemplo: una lista de calificaciones, una serie de
temperaturas medidas a lo largo de un da... El procesamiento de tales conjuntos de
datos, utilizando datos simples, puede ser extremadamente difcil y por ello la
mayora de los lenguajes de programacin incluyen caractersticas de estructuras
estticas de datos.
Las estructuras estticas de datos ms bsicas que soportan la mayora de los
lenguajes de programacin son las matrices (tambin llamadas en algunos textos
formaciones o arrays).
Una matriz es una coleccin de variables, todas ellas del mismo tipo de datos
(caracteres, enteros, nmeros en coma flotante...), a las que se hace referencia
mediante un nombre comn. Estas variables pueden ser manejadas bien globalmente,
utilizando el nombre de la matriz, o bien individualmente, utilizando el nombre de la
matriz seguido de uno o ms subndices que representan la posicin relativa de la
variable individual dentro de la matriz.
Las matrices siempre deben ser declararlas al comienzo del mdulo del programa
en el que va a ser utilizadas. Pueden declararse tambin matrices de constantes.
As, este captulo se ha organizado en cuatro secciones. La primera de ellas
introduce los conceptos bsicos que se manejan a la hora de trabajar con estructuras
matriciales. La seccin 2 se dedica a las operaciones bsicas con matrices: definicin,
iniciacin y acceso. La tercera seccin presenta las caractersticas de las matrices en
C, centrndose en las estructuras unidimensionales (vectores) y en las
bidimensionales (tablas). Finalmente, la cuarta seccin presenta una lista de
cuestiones y ejercicios de repaso.

8.1. Conceptos bsicos y terminologa


A la hora de trabajar con matrices, independientemente del lenguaje de programacin
que se vaya a utilizar, se maneja un conjunto de trminos, los cuales conviene definir
de forma precisa antes de profundizar en su manejo.
Se denomina matriz a la estructura de datos constituida por un nmero fijo de
variables (o constantes), todas ellas del mismo tipo de datos y ubicadas en direcciones
de memoria fsicamente contiguas, las cuales colectivamente son referenciadas
mediante un nico nombre.
De acuerdo con la definicin anterior, se denomina elemento de la matriz a cada
una de las variables (o constantes) que forman parte de la matriz.
El nombre de la matriz es el identificador utilizado para denominar a toda la
matriz, es decir, para referenciar de forma global a todos los elementos que la forman.
Se conoce como tipo base de la matriz al tipo de datos comn a todos los
elementos de la matriz (entero, real, carcter...).
- 233 -

234

Programacin en C

Se llama ndices de la matriz a cada uno de los valores numricos enteros a travs
de los cuales se puede acceder directa e individualmente a los elementos de una
matriz. Cada uno de estos ndices marca la situacin relativa de cada elemento dentro
de la matriz. Debe ser expresado mediante una constante, una variable o una
expresin de tipo entero.
La dimensin de la matriz viene determinada por el nmero de ndices que son
necesarios para acceder a cualquiera de sus elementos, tal y como se muestra en la
Tabla 8.1.
Nmero
de ndices
1
2
3
4 o ms

Denominacin

Llamada tambin

Matriz unidimensional
Vector o lista esttica
Matriz bidimensional
Tabla
Matriz tridimensional
Cubo
Matriz multidimensional
Tabla 8.1. Dimensiones de una matriz

Los elementos de una matriz son variables (o constantes) que, tratadas


individualmente, tienen las mismas caractersticas y propiedades que cualquier
variable (o constante) simple del tipo base de la matriz. Para acceder a un elemento en
particular es suficiente indicar el nombre de la matriz seguido del ndice o de los
ndices correspondientes.
Por ejemplo, en un vector x de n elementos, los elementos individuales se
referencian por:
x(1), x(2), x(3), ..., x(n)
Tal que, por ejemplo, una posible ocurrencia del vector x podra ser:
3
x(1)

7
x(2)

11
x(3)

23
x(n-1)

31
x(n)

Donde x(i) referencia al elemento que se encuentra en la posicin i del vector x.

Apunte de C

Algunos lenguajes de programacin, como el C, utilizan:


El valor 0 para la primera posicin del ndice en una estructura
matricial.
Corchetes
en lugar de parntesis ( ) para encerrar los ndices.
En este caso, en un vector x de n elementos, los elementos individuales se
referencian por:
x 0 , x 1 , x 2 , ..., x n-1
Tal que, por ejemplo, una posible ocurrencia del vector x podra ser:
3
x[0]

7
x[1]

11
x[2]

23
x[n-2]

31
x[n-1]

Donde x[i] referencia al elemento que se encuentra en la posicin i+1 del


vector x.
En el caso de una tabla de n x m elementos, los elementos individuales de la tabla
se referencian por:
Elementos de la primera fila: y(1,1), y(1,2), y(1,3), ..., y(1,m)

Estructuras estticas de datos

235

Elementos de la segunda fila: y(2,1), y(2,2), y(2,3), ..., y(2,m)


...
Elementos de la n-sima fina: y(n,1), y(n,2), y(n,3), ..., y(n,m)
Columnas
1

Filas

1
2
3

m-1

y(i,j)

n-1
n

Donde, y(i,j) referencia al elemento que se encuentra en la fila i, columna j de


la tabla y.

Apunte de C

Algunos lenguajes de programacin, como el lenguaje C, utilizan la notacin:


nombre_matriz i j en lugar de nombre_matriz(i,j)
En este caso, para una tabla de n x m elementos, los elementos individuales de
la tabla se referencian por:
Elementos de la primera fila: y 0 0 , y 0 1 , ..., y 0 m-2 , y 0 m-1
Elementos de la segunda fila: y 1 0 , y 1 1 , ..., y 1 m-2 , y 1 m-1
...
Elementos de la n-sima fina: y n-1 0 , y n-1 1 , ..., y n-1 m-2 ,
y n-1 m-1
Columnas
0

Filas

0
1
2

m-2

m-1

y[i][j]

n-2
n-1

Donde, y[i][j] referencia al elemento que se encuentra en la fila i+1,


columna j+1 de la tabla y.
Otro concepto importante a la hora de trabajar con matrices es el de tamao de la
matriz. Existen dos tamaos a tener en cuenta en una matriz:
Tamao fsico: Indica el nmero de elementos que componen la matriz.
Se fija en declaracin.
Tamao efectivo: Representa el tamao que se utiliza en una ejecucin
en particular. Vara en cada ejecucin.

236

Programacin en C

EJEMPLOS

Para un vector de 10 elementos, el tamao fsico es de 10 elementos,


mientras que el tamao efectivo depender de cada ejecucin concreta, esto
es, en cada ejecucin concreta del algoritmo podr utilizarse la totalidad de
elementos o menos.
x(1)

x(2)

x(3)

x(4)

x(5)

x(6)

x(7)

x(8)

x(9)

x(10)

Para el caso de una tabla de 6 filas x 8 columnas, el tamao fsico de 48


elementos, mientras que su tamao efectivo depender de cada ejecucin
concreta del algoritmo, donde podr utilizarse la totalidad de elementos o
menos.
1

1
2
3
4
5
6

La ocupacin en memoria de la matriz supone una cantidad de memoria igual a


la ocupacin de un elemento individual por tamao fsico de la matriz (nmero de
elementos de la matriz).
Por ejemplo1, para un vector de 10 elementos ocupar 10 bytes si es de caracteres,
20 bytes si es de enteros, 40 bytes si es de reales y 80 bytes si es de nmeros reales
dobles. En el caso de una tabla de 6 filas y 8 columnas ocupar 48 bytes si es de
caracteres, 96 bytes si es de enteros y 192 bytes si es de reales y 384 bytes si es de
nmeros reales dobles.

8.2. Operaciones bsicas con matrices


Existen un nmero reducido de operaciones bsicas que se pueden realizar con las
estructuras matriciales. stas son la definicin, la iniciacin en la definicin y el
recorrido secuencial.

8.2.1. Definicin
Se debe indicar el tipo base de los elementos de la matriz y su tamao.

Suponiendo que se necesita un byte para almacenar una variable de tipo carcter, dos bytes
para un entero, cuatro bytes para variables de tipo real y ocho bytes para el real doble.

Estructuras estticas de datos

237

EJEMPLOS

La siguiente lnea de pseudocdigo define un vector de enteros de 6


elementos llamado num. Se representa tambin grficamente la variable
num.
num

num(1)

matriz de enteros num(6)


num(2)
num(3)
num(4)

num(5)

num(6)

La siguiente lnea de pseudocdigo define una tabla de reales de 3 filas y 4


columnas llamada tbl. Se representa tambin grficamente la variable tbl.
matriz de reales tbl(3,4)
1
2
3
4

1
2
3

tbl(2,3)

8.2.2. Iniciacin en la definicin


Se asignan valores a cada uno de los elementos individuales de la matriz en el mismo
momento de ser definida.
EJEMPLOS

La siguiente lnea de pseudocdigo define un vector de enteros de seis


elementos llamado num, y asigna valor a cada uno de sus seis elementos. Se
representa tambin grficamente la variable num y el valor que toman sus
elementos.
num

matriz de enteros num(6) = (2, 4, 6, 8, 10, 12)


num(1) num(2) num(3) num(4) num(5) num(6)
2
4
6
8
10
12

La siguiente lnea de pseudocdigo define una tabla de reales de 3 filas y 4


columnas llamada tbl, y asigna valor a cada una de sus 12 elementos. La
asignacin de valores iniciales en una tabla se realiza por filas. Se representa
tambin grficamente la variable tbl y el valor que toman sus elementos.
matriz de reales tbl(3,4) =
tbl
1
2
3

1
1.1
21
31.2

2
1.2
22
32

3
1.3
23
33

(1.1, 1.2, 1.3, 14,


21,
22,
23,
24,
31.2, 32, 33, 34.5)
4
14
24
34.5

8.2.3. Acceso secuencial a la matriz (recorrido)


Tanto para realizar la carga de datos en una matriz (provenientes bien de la lectura de
datos de un dispositivo de entrada o de la asignacin de valores determinados dentro
del algoritmo) como para mostrar los datos de una matriz (escritura de datos en un

238

Programacin en C

dispositivo de salida), normalmente no puede hacerse en una nica sentencia, sino que
deber realizarse individualmente sobre los elementos de la misma, recorriendo toda
la matriz.
Estas operaciones de recorrido de la matriz se realizan utilizando estructuras
repetitivas, cuyas variables de control (por ejemplo i, f, c), se utilizarn como
subndices de la matriz (por ejemplo num(i) o tbl(f,c) en los ejemplos
anteriores). El incremento de la variable de control del bucle producir el tratamiento
sucesivo de los elementos de la matriz.
Debern utilizarse tantas estructuras repetitivas anidadas como ndices tenga la
matriz (una para vectores y dos para tablas).
EJEMPLOS

Se utilizarn las definiciones del vector num y la tabla tbl de los ejemplos
anteriores.
Vectores
Recorrido ascendente de los elementos de un vector para lectura.
Se recorre el vector desde el elemento de ndice menor hasta el de ndice
mayor.
Bucle Desde

Bucle Mientras

Bucle Repetir Mientras

Desde i1 hasta 6 con inc=1


leer(num(i))
FinDesde

i1
Mientras i<=6
leer(num(i))
ii+1
FinMientras

i1
Repetir
leer(num(i))
ii+1
Mientras i<=6

Recorrido ascendente de los elementos de un vector para asignacin de


datos.
Se recorre el vector desde el elemento de ndice menor hasta el de ndice
mayor, cargando el vector con los valores adecuados (en el ejemplo con
los seis primeros nmeros naturales impares).
Bucle Desde

Bucle Mientras

Bucle Repetir Mientras

Desde i1 hasta 6 con inc=1


num(i) (2*i)-1
FinDesde

i1
Mientras i<=6
num(i)(2*i)-1
ii+1
FinMientras

i1
Repetir
num(i)(2*i)-1
ii+1
Mientras i<=6

Recorrido descendente de los elementos de un vector para escritura.


Se recorre el vector desde el elemento de ndice mayor hasta el de ndice
menor.
Bucle Desde

Bucle Mientras

Bucle Repetir Mientras

Desde i6 hasta 1 con inc=-1


escribir(num(i))
FinDesde

i6
Mientras i>=1
escribir(num(i))
ii-1
FinMientras

i6
Repetir
escribir(num(i))
ii-1
Mientras i>=1

Tablas
Recorrido por filas de los elementos de una tabla para lectura.

Estructuras estticas de datos

239

Se recorre la tabla desde la primera fila a la ltima y, dentro de cada fila,


desde la primera columna a la ltima.
Bucles Desde
Desde f1 hasta 3 con inc=1
Desde c1 hasta 4 con inc=1
leer(tbl(f,c))
FinDesde
FinDesde

Bucles Mientras

Bucles Repetir Mientras

f1
f1
Mientras f<=3
Repetir
c1
c1
Mientras c<=4
Repetir
leer(tbl(f,c))
leer(tbl(f,c))
cc+1
cc+1
FinMientras
Mientras c<=4
ff+1
ff+1
FinMientras
Mientras f<=3

Recorrido por columnas de los elementos de una tabla para escritura.


Se recorre la tabla desde la primera columna a la ltima y, dentro de cada
columna, desde la primera fila a la ltima.
De esta forma se muestra en pantalla la matriz traspuesta de tbl.
Bucles Desde

Bucles Mientras

Bucles Repetir
Mientras

Desde c1 hasta 4 con inc=1


Desde f1 hasta 3 con inc=1
escribir(tbl(f,c))
FinDesde
escribir (lf)
FinDesde

c1
Mientras c<=4
f1
Mientras f<=3
escribir(tbl(f,c))
ff+1
FinMientras
escribir(lf)
cc+1
FinMientras

c1
Repetir
f1
Repetir
escribir(tbl(f,c))
ff+1
Mientras f<=3
escribir(lf)
cc+1
Mientras c<=4

lf en el pseudocdigo anterior significa nueva lnea (carcter cdigo


ASCII 10). Por tanto, escribir(lf) significa pasar a siguiente lnea
en pantalla.

8.3. Caractersticas de las matrices en C


A continuacin se van a estudiar las caractersticas de las estructuras matriciales en el
lenguaje de programacin C, concretamente en el apartado 8.3.1 se presentan los
vectores y en el apartado 8.3.2 las tablas, aunque primeramente se introducen sus
caractersticas generales.
En C todas las matrices estn formadas por posiciones contiguas de memoria,
correspondiendo el primer elemento a la posicin de memoria ms baja. La primera
posicin de cualquier ndice se referencia por 0.
Las matrices en C no tienen comprobacin de lmites. Si la matriz tiene N
elementos, es posible seguir escribiendo valores por encima del ndice N-1, sin que
por ello se produzca error de compilacin. Lo que seguramente producir esta
actuacin sern o bien errores de ejecucin o bien errores lgicos en el programa.
Es responsabilidad del programador asegurarse que no se violan los lmites de una
matriz.

240

Programacin en C

8.3.1. Matrices unidimensionales (vectores) en C


Son estructuras de datos lineales o unidimensionales, es decir, para acceder a un
elemento es suficiente indicar el nombre del vector seguido de un nico ndice.
Elemento
vector x

12
x0

15

23

x1

x2

-7
x3

9
x[4]

x4

x[3]

Primer elemento
del vector

ltimo elemento
del vector

x[2]
x[1]
x[0]

Direcciones

FFF5
FFF4
FFF3
FFF2
FFF1
FFF0
FFEF
FFEE
FFED
FFEC

9
-7
23
15
12

Figura 8.1. Representacin de un vector de cinco elementos

En la Figura 8.1 se representa grficamente un vector de cinco elementos de tipo


int, as como su almacenamiento en la memoria del ordenador, suponiendo dos
bytes para almacenar un entero (int).
Para definir un vector en el lenguaje C se utiliza la siguiente sintaxis:
tipo_bsico

nombre_vector tamao ;

Donde el tamao del vector indica el nmero de elementos que lo componen.


EJEMPLOS
int x 100 ;
float val_medios 25 ;

/* Vector llamado x de 100 enteros


*/
/* Vector llamado val_medios de 25 float */

El acceso a los elementos de un vector se realiza a travs de su nombre seguido del


ndice (posicin relativa que ocupa dicho elemento dentro de la lista) entre corchetes.
En el lenguaje C el primer ndice de un vector es el 0 (en otros lenguajes es el 1).
EJEMPLO

El valor del ndice para acceso a los elementos del vector x ir de 0 a 99, y
para acceso a los elementos del vector val_medios ir de 0 a 24:
x0
primer elemento
x1
segundo elemento
...
x 98 penltimo elemento
x 99 ltimo elemento

val_medios 0
val_medios 1

primer elemento
segundo elemento

...
val_medios 23
penltimo elemento
val_medios 24
ltimo elemento

La carga de datos en un vector puede hacerse por iniciacin o por asignacin.


En la iniciacin se asignan los valores en la declaracin del vector, de acuerdo a la
siguiente sintaxis:
tipo_bsico

nombre_vector tamao = {valor1, valor2, ...};

Especificar en la iniciacin ms valores que el tamao del vector produce un error


de compilacin. Especificar menos valores que el tamao del vector no produce error,
pero los ltimos elementos del vector se inician a 0.

Estructuras estticas de datos

241

A diferencia de las variables simples, un vector slo puede ser iniciado en el


momento de la declaracin, nunca dentro de las sentencias ejecutables del programa.
EJEMPLOS
int vector 5 = {1, 3, 45, 34, 211};
float lnum 4 = {3.21, 2, 7.333, 4.001};

EJEMPLOS
int vt 5 = {1, 3, 45, 34, 211, 79};
Incorrecto. Se especifican ms valores para iniciacin que el tamao del vector.
int vt 5 = {1, 3, 45};
Correcto. Los elementos vt 3 y vt 4 se inician a 0.
int vt 5 ;
Correcto, pero no se inician a ningn valor los elementos del vector.
char letras 5 = {'A', 'B', 'C'};
Correcto. Los elementos letras 3 y letras 4 se inician a valor ASCII 0 ('\0').

EJEMPLOS

Iniciacin de vectores de caracteres.


char
char
char
char

letras 4
pal 5
pal2 5
pal3 5

pal, pal2 y pal3


letras

=
=
=
=

{'H', 'O', 'L', 'A'};


{'H', 'O', 'L', 'A', '\0'};
"HOLA";
{'H', 'O', 'L', 'A'};

podrn ser usados como una cadena de caracteres,


pues incluyen el carcter '\0' final.
no podr ser usada como cadena de caracteres, ser
simplemente un vector de caracteres, pues le falta el
'\0' final.

Normalmente es conveniente definir el tamao de un vector en trminos de una


constante simblica, en lugar de utilizar un literal entero para ello. De esta forma, esta
constante puede ser utilizada en otros puntos del programa en lugar del valor literal.
Esto hace ms fcil modificar un programa que utiliza un vector, ya que todas las
referencias al tamao mximo del vector (por ejemplo en bucles), sern alteradas
cambiando simplemente el valor de la constante simblica.
EJEMPLO
#define TAMANO 100
#define DIM
5
...
float lista TAMANO ;
int vector DIM = {1, 32, 415, 0, -25};

El tamao del vector no es necesario especificarlo explcitamente cuando ste se


inicia en la definicin, ya que su tamao es calculado automticamente por el
compilador de acuerdo al nmero de valores incluidos dentro de la iniciacin.
EJEMPLOS
int digitos = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
Declara un vector de 10 elementos.
char color = "ROJO";
Declara un vector de 5 elementos, pues incluye el '\0' final.

242

Programacin en C

char color = {'R', 'O', 'J', 'O'};


Declara un vector de 4 elementos tipo char ya que no incluye el '\0' final.

Para cargar de valores un vector mediante asignacin, se asigna de manera


individual a cada elemento del vector un valor.
Normalmente, para cargar un vector en tiempo de ejecucin se recorre ste
secuencialmente desde el primer elemento hasta el ltimo, asignando a cada elemento
un valor determinado.
EJEMPLO
#define DIM 4
...
int vector DIM ;
...
vector 0 = 35;
scanf("%d", &vector 3 );

/*Asignacin directa*/
/*Lectura de teclado*/

EJEMPLO
#define DIM 7
...
int vector DIM ;
int i;

Se carga en cada elemento del vector


el valor de su ndice.
for (i=0; i < DIM; i++)
vector i = i;

Se carga el vector leyendo


los valores desde teclado.
for (i=0; i < DIM; i++)
scanf("%d", &vector i );

Observar el bucle for para recorrer un vector de DIM elementos: for


(i=0; i<DIM; i++), comienza con valor del ndice a 0 (i=0)
recorriendo de uno en uno los elementos del vector (i++) y terminando con
el valor del ndice a DIM-1 (i < DIM).
EJEMPLO

Realizar un programa que presente en pantalla la media aritmtica de n


nmeros reales que se introduzcan por teclado. Previamente el programa
debe pedir el valor de n, es decir, cuntos nmeros se van a introducir.
En este programa se va a trabajar con una coleccin de nmeros que se
introducirn de teclado, pero no es necesario almacenarlos en memoria
porque segn se va leyendo el nmero se va procesando.
Esquema del programa
int cuantos;
float media;
float numero;
int j;

/*
/*
/*
/*

Nmeros a introducir
Media aritmtica a calcular
Nmero que se lee de teclado
Contador para for

printf("Cuntos nmeros se introducirn?:");


scanf("%d", &cuantos);
media = 0;
for(j = 1; j <= cuantos; j++) {
printf("Introduzca nmero %d:", j);
scanf("%f", &numero);
media = media + numero;
}
media = media / cuantos;
printf("Media: %g", media);

*/
*/
*/
*/

Estructuras estticas de datos

243

EJEMPLO

Realizar un programa que presente en pantalla la media aritmtica de n


nmeros reales que se introduzcan por teclado, y las desviaciones de cada
uno de ellos respecto de la media, mediante la frmula:

desviacini

nmeroi

media

Previamente el programa pedir el valor de n, es decir, cuntos nmeros se


van a introducir, tomando 100 como valor mximo permitido para n.
Comentarios previos
La desviacin de cada nmero introducido no podr calcularse hasta no tener
calculada la media, y para calcular la media es necesario que se hayan
introducido todos los nmeros, por lo que en este programa se requiere
almacenar en memoria los nmeros que se van introduciendo, para una vez
finalizada la introduccin de los mismos y calculada la media, hallar la
desviacin de cada uno de ellos. Por lo tanto, es necesario utilizar un vector
para almacenar los nmeros segn se van introduciendo.
Normalmente, cuando en un programa se necesita utilizar un vector, se
dimensiona ste, en su declaracin, a un valor lo suficiente grande como para
soportar todas las posibles ejecuciones del programa, pero en cada ejecucin
slo se utiliza el nmero de elementos necesarios del mismo
As se definen los siguientes elementos de datos:
Constantes:
DIM
=
tamao fsico del vector (se fija
declaracin)
Variables:
vx
=
vector declarado de longitud DIM
diml
=
nmero de elementos utilizados de vx
en cada ejecucin (tamao efectivo)
j
=
ndice para recorrer el vector vx
vx
[0]
[1]
[2]

diml

DIM

[DIM-2]
[DIM-1]

Esquema del programa


#define DIM 100
float vx DIM ;
int diml;
int j;
float media;
/* Se lee de teclado el nmero mximos de elementos a
introducir: longitud efectiva del vector */

en

244

Programacin en C
do {
printf("Nmeros a introducir (mx.%d)?: ", DIM);
scanf("%d", &diml);
} while ((diml <=0) || (diml > DIM));
/* Se carga el vector de nmeros y calculamos la media */
media = 0;
printf("\n");
for (j = 0; j < diml; j++) {
printf("Nmero %d?: ", j+1);
scanf("%f", &vx[j]);
media = media + vx[j];
}
media = media / diml;
printf("\n");
printf("Media: %g\n", media);
/* Se calcula y se muestra la desviacin de cada nmero respecto
de la media */
printf("Nmero
Valor Desviacin\n");
printf("------ -------- ----------\n");
for (j = 0; j < diml; j++)
printf("%5d %9.3f %9.3f\n", j+1, vx[j], vx[j]-media);

Ejemplo de ejecucin
Nmeros a introducir (mx.100)?: 6
Nmero
Nmero
Nmero
Nmero
Nmero
Nmero

1?:
2?:
3?:
4?:
5?:
6?:

34.56
-12.3
56
4
0.89
5.66

Media: 14.8017
Nmero
Valor Desviacin
------ -------- ---------1
34.560
19.758
2
-12.300
-27.102
3
56.000
41.198
4
4.000
-10.802
5
0.890
-13.912
6
5.660
-9.142

EJEMPLO

Realizar un programa que presente en pantalla el mximo y el mnimo de una


coleccin de n nmeros que se introducirn por teclado. Debe indicarse
igualmente cuntas veces aparece tanto el valor mximo como el mnimo.
Adems, posteriormente, el programa debe presentar la diferencia de cada
nmero introducido respecto al mximo y al mnimo calculados.
Previamente, el programa pedir el valor de n, es decir, cuntos nmeros se
van a introducir, tomando 100 como valor mximo permitido para n.
Comentario previo
El mximo de una coleccin debe ser un elemento de dicha coleccin. Por
ello se comienza tomando como mximo el primer valor de la coleccin.
Posteriormente, se compara este valor mximo, de uno en uno, con el resto
de elementos de la coleccin, de forma que si se encuentra algn elemento
mayor que el mximo hasta ese momento, se tomar como nuevo mximo.

Estructuras estticas de datos

El modo de actuar a la hora de calcular el mnimo es similar.


Esquema del programa
#define DIM 100
float vx[DIM];
int diml;
int j;
float maximo, minimo; /* Mismo tipo que elementos de vx*/
int veces_max, veces_min;
/* Se lee de teclado el nmero mximos de elementos a
introducir: longitud efectiva del vector */
do {
printf("Nmeros a introducir (mx.%d)?: ", DIM);
scanf("%d", &diml);
} while ((diml <=0) || (diml > DIM));
/* Se inicia el vector de nmeros */
for (j = 0; j < diml; j++) {
printf("Nmero %d?: ", j+1);
scanf("%f", &vx[j]);
}
/* Se toma como mximo y mnimo el primer elemento */
maximo = vx[0];
minimo = vx[0];
veces_max = 1;
veces_min = 1;
/* Se recorre la lista calculando a la vez mximo y mnimo*/
for (j = 1; j < diml; j++) {
if (vx[j] == maximo)
veces_max++;
else if (vx[j] > maximo) {
maximo = vx[j];
veces_max = 1;
}
if (vx[j] == minimo)
veces_min++;
else if (vx[j] < minimo) {
minimo = vx[j];
veces_min = 1;
}
}
/* Se presentan el mximo y mnimo encontrados */
printf("Mximo %7g (veces repetido %d)\n", maximo,veces_max);
printf("Mnimo %7g (veces repetido %d)\n", minimo,veces_min);
/* Diferencia de cada nmero respecto al mximo y mnimo */
printf("Nmero
Valor Dif.maximo Dif.mnimo\n");
printf("------ -------- ---------- ----------\n");
for (j = 0; j < diml; j++)
printf("%5d %9.3f %9.3f %9.3f\n",
j+1, vx[j], maximo-vx[j], vx[j]-minimo);

Ejemplo de ejecucin
Nmeros a
Nmero 1?:
Nmero 2?:
Nmero 3?:
Nmero 4?:
Nmero 5?:
Nmero 6?:

introducir (mx.100)?: 6
12.5
8.45
15.8
4
15.8
5.66

245

246

Programacin en C

Mximo
15.8 (veces repetido 2)
Mnimo
4 (veces repetido 1)
Nmero
Valor Dif.maximo Dif.mnimo
------ -------- ---------- ---------1
12.500
3.300
8.500
2
8.450
7.350
4.450
3
15.800
0.000
11.800
4
4.000
11.800
0.000
5
15.800
0.000
11.800
6
5.660
10.140
1.660

EJEMPLO

Realizar un programa que lea n nmeros que se introducirn por teclado, no


permitiendo que se introduzcan nmeros repetidos. Previamente, el
programa pedir el valor de n, es decir, cuntos nmeros se van a introducir,
tomando 100 como valor mximo permitido para n. Posteriormente, el
programa presentar la lista de nmeros introducida en la pantalla.
Esquema del programa
Se van leyendo los nmeros desde el teclado y cargndose en un vector.
Salvo el primero, cada nmero que se carga en el vector debe compararse
con todos los anteriores, para comprobar si est repetido.
Se describe a continuacin al algoritmo que resuelve el problema:
leer(vx[0])
Desde j 1 hasta diml-1 con incremento 1
Repetir
leer (lv[j])
repetido falso
k (j - 1)
Mientras ((k >=0) and repetido = falso)
Si (vx[j] = vx[k])
repetido cierto
k k - 1
FinMientras
Mientras (repetido = cierto)
FinDesde

A continuacin se presenta el esquema del programa C que implementa el


algoritmo anterior.
#define DIM
#define CIERTO
#define FALSO
float
int
int
int

vx DIM ;
diml;
j, k;
repetido;

100
1
0

/* flag: cierto falso */

/* Se lee de teclado el nmero de elementos a introducir */


do {
printf("Nmeros a introducir (mx.%d)?:", DIM);
scanf("%d", &diml);
} while ((diml <=0) || (diml > DIM));
/* Se inicia el vector sin permitir nmeros repetidos */
printf("Nmero %d?: ", 1);
scanf("%f", &vx[0]);

Estructuras estticas de datos

247

for (j = 1; j < diml; j++)


do {
printf("Nmero %d?: ", j+1);
scanf("%f", &vx[j]);
repetido = FALSO;
k = j - 1;
while ( (k >= 0) && !repetido ) {
if (vx[j] == vx[k])
repetido = CIERTO;
k--;
}
if (repetido)
printf("Ya est repetido.\n");
} while (repetido);
/* Se muestra el vector en pantalla */
for (j = 0; j < diml; j++)
printf("%3d: %g\n", j+1, vx[j]);

8.3.2. Matrices bidimensionales (tablas) en C


Las tablas son estructuras de datos planas (de dos dimensiones), es decir, para
acceder a un elemento es necesario indicar el nombre de la tabla seguido de dos
ndices. El primero marcar la fila y el segundo la columna del elemento dentro de la
tabla.
En la Figura 8.2 se representa grficamente una tabla de 4 filas y 3 columnas de
elementos tipo int, y su almacenamiento en la memoria del ordenador (de nuevo se
suponen dos bytes para almacenar elementos del tipo int).
Elemento
tabla tb

Columnas
tb[3][2]

Filas

[1]

[2]

10

11

12

20

21

22

30

31

32

Primer elemento de la tabla


tb[0][0]

tb[3][1]
tb[3][0]
tb[2][2]
tb[2][1]

ltimo elemento de la tabla


tb[3][2]

Direcciones

FFF5
FFF4
FFF3
FFF2
FFF1
FFF0
FFEF
FFEE
FFED
FFEC

32
31

fila 3

30
22
21

...

...
10

tb[1][0]

FFE5
FFE4

tb[0][2]

FFE3
FFE2
FFE1
FFE0

tb[0][1]

tb[0][0]

FFDF
FFDE

fila 2

fila 1
Direcciones
de memoria
crecientes
fila 0

Figura 8.2. Representacin de una tabla de 4x3 posiciones

Para definir una tabla en C se utiliza la siguiente sintaxis:


tipo_bsico

Donde:

nombre_tabla filas columnas ;

248

Programacin en C

filas indica el tamao de la primera dimensin de la tabla, es decir, el


nmero de filas.
columnas indica el tamao de la segunda dimensin de la tabla, es
decir, el nmero de columnas.
EJEMPLOS
int tbx 12 3 ;
/* Tabla llamada tbx de 12 filas y 3 columnas: 36 enteros */
char pagina 24 80 ;
/* Tabla llamada pagina de 24 filas y 80 columnas: 1920 caracteres */

El acceso a los elementos de una tabla se realiza a travs de su nombre seguido de


dos ndices, cada uno de ellos entre corchetes, los cuales indican la posicin relativa
de la fila y de la columna del elemento dentro de la tabla.
Se recuerda que en C el primer ndice, tanto de las filas como de las columnas, es
el 0 (en otros lenguajes es el 1).
EJEMPLO

Sea float med 20 35 ;


Los ndices para acceso a los elementos de la tabla med irn desde 0 a 19
para las filas, y desde 0 a 34 para las columnas. En total 20 x 35 = 700
elementos.
med 0 0
med 0 1
...
med 19 33
med 19 34

primer elemento: fila 0, columna 0


elemento fila 0, columna 1
elemento fila 19, columna 33
ltimo elemento: fila 19, columna 34

La carga de datos en una tabla puede hacerse por iniciacin o por asignacin
En la iniciacin se asignan los valores en la declaracin de la tabla, de acuerdo a la
siguiente sintaxis:
tipo_bsico

nombre_tabla filas columnas = {valor1, valor2, ...};

La iniciacin de la matriz a partir de la lista de valores que se especifica entre


llaves se realiza por filas.
A diferencia de las variables simples, una tabla slo puede ser iniciada en el
momento de la declaracin, nunca dentro de las sentencias ejecutables del programa.
Especificar en la iniciacin ms valores que el tamao de la tabla produce un error
de compilacin. Especificar menos valores que el tamao de la tabla no produce error,
los ltimos elementos de la tabla se inician a 0.
EJEMPLOS
int tb 3 5 = {63, 56, 21, 3, 5,
10, 7, 3, 9, 1,
454, 231, 4, 8, 6};
float tnum 4 2 = {3.2, 0.5, 1, 3, 5.9, 7, 9, 8};

tb
0
1
2

0
63
10
454

1
t 56
b 7
231

2
21
3
4

3
3
9
8

4
5
1
6

tnum
0
1
2
3

0
3.2
1
5.9
9

1
0.5
3
7
8

Estructuras estticas de datos

249

EJEMPLOS
int tb1 2 3 = {1, 3, 45, 34, 2, -11, 79};
Incorrecto. Se especifican ms valores para iniciacin que el tamao de la tabla.
int tb2 2 4 = {1, 3, 45, 6, 12, -2};
Correcto. Los elementos tb 1 2 y tb 1 3 se inician a 0.
int tb3 3 6 ;
Correcto, pero no se inician a ningn valor los elementos de la tabla.

Normalmente es conveniente definir el tamao de una tabla en trminos de dos


constantes simblicas, en lugar de dos literales enteros. De esta forma, estas
constantes pueden ser utilizadas en otros puntos del programa en lugar de los valores
literales. Esto hace ms fcil modificar un programa que utiliza una tabla, ya que
todas las referencias al tamao mximo de la tabla (por ejemplo en bucles), sern
alteradas cambiando simplemente el valor de las constantes simblicas.
EJEMPLO
#define FILAS
#define COLUMNAS
...

3
5

float tpb FILAS COLUMNAS ;


int tba FILAS COLUMNAS = {1, 32, 415};

El nmero de filas de la tabla no es necesario especificarlo explcitamente cuando


se inicia la tabla en su definicin, ya que el tamao es calculado automticamente por
el compilador de acuerdo al nmero de valores incluidos dentro de la iniciacin.
EJEMPLO
int dgt 3 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; /*(3 filas)*/
float x 4 = {-1.5, -1, -5, 0, 0.5, 1, 1, 5}; /*(2 filas)*/

Para declarar e iniciar una tabla de cadenas de caracteres, se puede realizar como
se muestra en el siguiente ejemplo.
EJEMPLO
char nombres 4 7 = {"JUAN", "PEDRO", "PEPE", "DAVID"};

O bien:

0
1
2
3

char nombres
0
1
J
U
P
E
P
E
D
A

7 = {"JUAN",
2
A
D
P
V

"PEDRO",
3
N
R
E
I

"PEPE", "DAVID"};
4
5
6
\0
O
\0
\0
D
\0

Donde:
nombres 3 2
es el carcter 'V'
Pero la tabla puede ser tratada como un vector de cadenas de caracteres,
utilizando nicamente el primer ndice de la misma.
nombres 3
es la cadena "DAVID"

250

Programacin en C

Por tanto pueden utilizarse sentencias para leer y escribir individualmente


una de las cadenas de la tabla:
printf("%s", nombres 3 );
scanf("%s", nombres 3 );

Para cargar de valores una tabla mediante asignacin, se asigna un valor de manera
individual a cada elemento de la tabla.
Normalmente, para cargar una tabla en tiempo de ejecucin se recorre sta
secuencialmente, desde el primer elemento hasta el ltimo, asignando a cada elemento
un valor determinado.
EJEMPLO
#define FILAS
3
#define COLUMNAS 4
...
int matriz_a FILAS COLUMNAS ;
...
matriz_a 2 3 = -14;
Asignacin directa
scanf("%d", &matriz_a 2 2 ); Lectura de teclado

EJEMPLO

Se inicia la tabla cargando en cada elemento el valor de la suma de sus


ndices.
#define FILAS
7
#define COLUMNAS 5
int tba FILAS COLUMNAS ;
int f,c;

a) Recorrindola por filas


c
f

for (f=0; f < FILAS; f++)


for (c=0; c < COLUMNAS; c++)
tba f c = f+c;

b) Recorrindola por columnas


c
f

for (c=0; c < COLUMNAS; c++)


for (f=0; f < FILAS; f++)
tba f c = f+c;

Estructuras estticas de datos

EJEMPLO

Se inicia la tabla introduciendo por teclado los valores oportunos.


#define FILAS
7
#define COLUMNAS 5
int tba FILAS COLUMNAS ;
int f,c;

a) Recorrindola por filas


for (f=0; f < FILAS; f++)
for (c=0; c < COLUMNAS; c++) {
printf("Elemento %d,%d: ", f+1, c+1);
scanf("%d", &tba f c );
}

Peticin de datos en pantalla

Matriz a introducir
1
69
93
115
304
615
1001

7
70
95
123
405
617
1205

9
73
99
156
408
709
1300

25
81
101
198
459
800
1525

Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
...
Elemento
Elemento
Elemento

34
90
112
203
512
909
1709

Almacenamiento en memoria
c
1
69
93
115
304
615
1001

7
70
95
123
405
617
1205

9
73
99
156
408
709
1300

25
81
101
198
459
800
1525

34
90
112
203
512
909
1709

b) Recorrindola por columnas


for (c=0; c < COLUMNAS; c++) {
for (f=0; f < FILAS; f++)
printf("%6d", tba f c );
printf("\n");
}

c
f

Almacenamiento en memoria
1
69
93
115
304
615
1001

7
70
95
123
405
617
1205

9
73
99
156
408
709
1300

25
81
101
198
459
800
1525

34
90
112
203
512
909
1709

1,1:
1,2:
1,3:
1,4:
1,5:
2,1:
2,2:
2,3:

1
7
9
25
34
69
70
73

7,3: 1300
7,4: 1525
7,5: 1709

251

252

Programacin en C

Presentacin en pantalla
1
7
9
25
34

69
70
73
81
90

93
95
99
101
112

115
123
156
198
203

304
405
408
459
512

615
617
709
800
909

1001
1205
1300
1525
1709

EJEMPLO

Realizar un programa que pida por pantalla una matriz de nmeros enteros
de 5 filas por 3 columnas. Debe presentarse posteriormente en la pantalla la
suma de cada fila, el mximo de cada columna y la matriz traspuesta a la
introducida.
Esquema del programa
#define FILAS
5
#define COLUMNAS 3
int mx FILAS COLUMNAS ; /* Matriz de nmeros enteros */
float lsum FILAS ;
/* Vector suma de cada fila */
int lmax COLUMNAS ;
/* Vector mximo de cada columna */
int f, c;
/* ndices para recorrido matriz */

lsum

FILAS

COLUMNAS

lmax
/* Se introduce desde teclado la matriz por filas */
for (f = 0; f < FILAS; f++)
for (c = 0; c < COLUMNAS; c++) {
printf("Elemento (%d,%d)?: ", f+1, c+1);
scanf("%d", &mx[f][c]);
}
/* Se calcula la suma de cada fila */
for (f = 0; f < FILAS; f++) {
lsum[f] = 0;
for (c = 0; c < COLUMNAS; c++)
lsum[f] = lsum[f] + mx[f][c];
}
/* Se calcula el mximo de cada columna */
for (c = 0; c < COLUMNAS; c++) {
lmax[c] = mx[0][c];
for (f = 1; f < FILAS; f++)
if (mx[f][c] > lmax[c])
lmax[c] = mx[f][c];
}
/* Se presenta la matriz introducida y la suma de cada fila */
printf("\nMatriz introducida: \n");
for (f = 0; f < FILAS; f++) {
for (c = 0; c < COLUMNAS; c++)
printf("%8d", mx[f][c]);

Estructuras estticas de datos

253

printf("
Suma fila %d: ", f+1);
printf("%8g\n", lsum[f]);

}
/* Se presenta el mximo de cada columna */
printf("\nMximo de cada columna: \n");
for (c = 0; c < COLUMNAS; c++)
printf("%8d", lmax[c]);
/* Se presenta la tabla traspuesta */
printf("\nMatriz traspuesta: \n");
for (c = 0; c < COLUMNAS; c++) {
for (f = 0; f < FILAS; f++)
printf("%8d", mx[f][c]);
printf("\n");
}

Ejemplo de ejecucin del programa


Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento
Elemento

(1,1)?:
(1,2)?:
(1,3)?:
(2,1)?:
(2,2)?:
(2,3)?:
(3,1)?:
(3,2)?:
(3,3)?:
(4,1)?:
(4,2)?:
(4,3)?:
(5,1)?:
(5,2)?:
(5,3)?:

2
5
7
51
4
7
8
69
50
45
3
95
14
5
96

Matriz introducida:
2
5
51
4
8
69
45
3
14
5

7
7
50
95
96

Suma
Suma
Suma
Suma
Suma

fila
fila
fila
fila
fila

1:
2:
3:
4:
5:

14
62
127
143
115

Mximo de cada columna:


51
69
96
Matriz traspuesta:
2
51
5
4
7
7

8
69
50

45
3
95

14
5
96

EJEMPLO
Realizar un programa que permita introducir por pantalla una matriz cuadrada de
nmeros enteros. Posteriormente se presentar su traza y se indicar si la matriz
introducida es simtrica. El programa deber permitir trabajar con matrices de hasta
10 x 10 elementos.

Se recuerda que:
La traza de una matriz cuadrada es la suma de los elementos de su
diagonal principal.
La diagonal principal de una matriz cuadrada la forman todos los
elementos aij donde i=j

254

Programacin en C

Una matriz cuadrada es simtrica si coincide con su traspuesta, es


decir, aij=aji para toda pareja de ndices i, j.
Comentarios previos
Normalmente, cuando en un programa se necesita utilizar una tabla, se
dimensiona sta en su declaracin a un valor suficiente grande para todas las
posibles ejecuciones del programa, pero en cada ejecucin slo se utiliza el
nmero de elementos necesarios de la misma.
As, se define:
Constantes
FILAS
COLUMNAS
Variables
tb =
fil =
col =
f, c =

=
=

nmero de filas en declaracin de la tabla.


nmero de columnas en declaracin de la tabla.

tabla declarada de FILAS x COLUMNAS.


nmero de filas de tb utilizadas en cada ejecucin.
nmero de columnas de tb utilizadas en cada ejecucin.
ndices para recorrer las filas y columnas de tb respectivamente.
COLUMNAS

tb

col

fil
FILAS

Esquema del programa


#define FILAS
10
#define COLUMNAS FILAS /* matriz cuadrada */
#define CIERTO
1
#define FALSO
0
int
tb FILAS COLUMNAS ;
/* matriz de nmeros
enteros */
int
fil, col;
/* filas y columnas
efectivas */
int
f, c;
/*
ndices
para
recorrido matriz */
long
traza;
/* Valor de la traza a calcular */
int
simetrica;
/* Indicador (flag) de simtrica */
/* Se lee de teclado el tamao de la matriz a introducir, es
decir,
el tamao efectivo de la matriz */
do {
printf("Nmero de filas de la matriz (mx.%d)?:",FILAS);
scanf("%d", &fil);
} while ((fil <=0) || (fil > FILAS));

Estructuras estticas de datos

255

/* La matriz es cuadrada */
col = fil;
/* Se introduce desde teclado la matriz por filas */
for (f = 0; f < fil; f++)
for (c = 0; c < col; c++) {
printf("Elemento (%d,%d)?: ", f+1, c+1);
scanf("%d", &tb[f][c]);
}
/* Se calcula la traza de la matriz */
traza = 0;
for (f = 0; f < fil; f++)
traza = traza + tb[f][f];
/* Se comprueba si la matriz es simtrica...
*/
/* ----- con bucle for ------------------------- */
simetrica = CIERTO;
for (f = 0; f < fil; f++)
for (c = 0; c < col; c++)
if (tb[f][c] != tb[c][f])
simetrica = FALSO;
printf("\nLa traza vale %ld\n", traza);
if (simetrica)
printf("La matriz es simtrica\n");
else
printf("La matriz NO es simtrica\n");

La comprobacin de si la matriz es o no simtrica se puede hacer ms


eficientemente de la siguiente forma:
/* - ms optimizado, con bucle for - */
simetrica = CIERTO;
for (f = 0; (f < fil) && simetrica; f++)
for (c = 0; (c < col) && simetrica; c++)
if (tb[f][c] != tb[c][f])
simetrica = FALSO;

O bien utilizando un bucle while:

/* - o con bucle while - */


simetrica = CIERTO;
f = 0;
while ((f < fil) && simetrica) {
c = 0;
while ((c < col) && simetrica)
if (tb[f][c] != tb[c][f])
simetrica = FALSO;
else c++;
f++;
}

Ejemplo de ejecucin
-18
2
3
4

2
1
5
7

3
5
24
3

4
7
3
-9

Tabla tb

256

Programacin en C
Nmero de filas
Elemento (1,1)?:
Elemento (1,2)?:
Elemento (1,3)?:
Elemento (1,4)?:
Elemento (2,1)?:
Elemento (2,2)?:
Elemento (2,3)?:
Elemento (2,4)?:
Elemento (3,1)?:
Elemento (3,2)?:
Elemento (3,3)?:
Elemento (3,4)?:
Elemento (4,1)?:
Elemento (4,2)?:
Elemento (4,3)?:
Elemento (4,4)?:

de la matriz (max.10)?: 4
-18
2
3
4
2
1
5
7
3
5
24
3
4
7
3
-9

La traza vale 2
La matriz es simtrica

EJEMPLO
Realizar un programa que multiplique dos matrices de nmeros reales.
Deber permitirse trabajar con matrices de hasta 25 x 25 elementos.
Se recuerda que:
Para que dos matrices puedan multiplicarse se exige que el nmero
de columnas de la primera coincida con el nmero de filas de la
segunda.
La matriz producto tendr tantas filas como la primera y tantas
columnas como la segunda.
a11 a12 a13
p11 p12 p13 p14 p15
b11 b12 b13 b14 b15
a21 a22 a23
p21 p22 p23 p24 p25
b21 b22 b23 b24 b25
a31 a32 a33
p31 p32 p33 p34 p35
b31 b32 b33 b34 b35
a41 a42 a43
p41 p42 p43 p44 p45
Matriz A (4f x 3c)

Donde:

Matriz B (3f x 5c)

Matriz Producto (4f x 5c)

p11 a11b11 a12b21 a13b31


p12 a11b12 a12b22 a13b32
p45

a41b15

a42b25

a43b35

pij

ai1b1 j

ai 2b2 j

ai 3b3 j

Es decir:

ainbnj

Donde n es el nmero de columnas de la matriz A, que coincide con el


nmero de filas de la matriz B.

Estructuras estticas de datos

257

Esquema del programa


#define FILAS
25
#define COLUMNAS 25
float tba FILAS COLUMNAS ; /* matriz A
*/
float tbb FILAS COLUMNAS ; /* matriz B
*/
float tbp FILAS COLUMNAS ; /* matriz Producto
*/
int fil_a, col_a; /* filas, columnas efectivas matriz A */
int fil_b, col_b; /* filas, columnas efectivas matriz B */
int fil_p, col_p; /* filas, columnas efectivas matriz Producto */
int f, c;
/* ndices para recorrido matriz
*/
int j; /* Se lee del nmero de filas de la primera matriz (A) -- */
do {
printf("Nmero filas matriz A (mx.%d)?:", FILAS);
scanf("%d", &fil_a);
} while ((fil_a <=0) || (fil_a > FILAS));
/* Se lee el nmero de columnas de la primera matriz (A) --------- */
do {
printf("Nmero columnas matriz A (mx.%d)?:", COLUMNAS);
scanf("%d", &col_a);
} while ((col_a <=0) || (col_a > COLUMNAS));
/* filas segunda = columnas primera ----------------------------- */
fil_b = col_a;
/* Se lee el nmero de columnas de la segunda matriz (B) --------- */
do {
printf("Nmero columnas matriz B (mx.%d)?:", COLUMNAS);
scanf("%d", &col_b);
} while ((col_b <=0) || (col_b > COLUMNAS));
/* Se introduce desde teclado la primera matriz (A) por filas ---- */
printf("Introduzca la matriz A:\n");
for (f = 0; f < fil_a; f++)
for (c = 0; c < col_a; c++) {
printf("Elemento (%d,%d)?: ", f+1, c+1);
scanf("%f", &tba[f][c]);
}
/* Se introduce desde teclado la segunda matriz (B) por filas ---- */
printf("Introduzca la matriz B:\n");
for (f = 0; f < fil_b; f++)
for (c = 0; c < col_b; c++) {
printf("Elemento (%d,%d)?: ", f+1, c+1);
scanf("%f", &tbb[f][c]);
}
/* ------------------------------------------------Dimensin de la matriz producto:
Nmero de filas de la de la primera matriz.
Nmero de columnas de la segunda matriz.
------------------------------------------------- */
fil_p = fil_a;
col_p = col_b;
/* Se calcula la matriz producto --------------------------------- */
for (f = 0; f < fil_p; f++)
for (c = 0; c < col_p; c++) {
tbp[f][c] = 0;
for (j = 0; j < col_a; j++)
tbp[f][c] = tbp[f][c] + (tba[f][j] * tbb[j][c]);
}
/* Se presenta la matriz producto -------------------------------- */
printf("\MATRIZ PRODUCTO: \n");
for (f = 0; f < fil_p; f++) {
for (c = 0; c < col_p; c++)

258

Programacin en C

printf("%8.2f", tbp[f][c]);
printf("\n");

8.4. Cuestiones y ejercicios


1.

2.

Responde a las siguientes cuestiones, pero antes recuerda que en una matriz
sus elementos individuales se comportan como variables del tipo base de la
matriz.
En un vector de N elementos, cul es el ndice del primer elemento? Y
el del ltimo elemento?
Cmo se declarara un vector de enteros llamado vector1, para
almacenar 100 enteros?
Qu hace la siguiente sentencia: vector1[3] = 27;?
Cmo se almacenara en la primera posicin del vector el valor -15?
Cmo se almacenara en la ltima posicin del vector el valor 54?
Qu
hace
la
siguiente
sentencia:
scanf("%d",
&vector1[75]);?
Cmo se mostrara en pantalla el valor contenido en la quinta posicin
del vector?
Indicar qu hacen las siguientes sentencias:
- int edades[20];
- float notas[100];
- char nombre[26];

3.

4.
5.
6.

7.

scanf("%d", &edades[2]);
notas[99] = 7.5;
printf(nombre);
printf("%c", nombre[0]);

Define qu se entiende por una cadena de caracteres. Es lo mismo que un


vector de caracteres? Cunto ocupa en memoria? Con qu carcter termina
en memoria toda cadena de caracteres en C? Cul es el ndice del primer
carcter de cualquier cadena en C? Dibuja la distribucin en memoria de la
cadena definida como char NOMBRE[] = "VICENTE";
Es equivalente en el lenguaje C escribir "A" que 'A'? Por qu? Qu
representa cada uno de ellos?
Si se quiere declarar una variable cadena de caracteres para poder albergar
cadenas de hasta 8 caracteres, qu longitud deber darse a dicha cadena en
la declaracin? Cmo se hara dicha declaracin?
Si se tiene declarada una cadena de 80 caracteres como: char
mensaje[80];
Qu hace la sentencia printf("%c", mensaje[7]);?
Cmo podra verse cul es la tercera letra de dicho mensaje?
Y la quinta?
Cmo se declarara en un programa...
Una constante llamada letra de tipo carcter, con valor 'F'.
Una variable llamada dgito de tipo entero corto, iniciada a valor 23.

Estructuras estticas de datos

8.

9.

10.

Una variable llamada nombre para poder almacenar nombres de hasta


25 letras.
Una variable llamada impuestos para poder almacenar cantidades
numricas con decimales.
Declarar las siguientes variables:
Vector de 25 reales llamado lista1.
Vector de 7 enteros llamado la.
Vector de 25 caracteres llamado vletras.
Vector de 20 enteros largos llamado lista_largos.
Segn las declaraciones del ejercicio 8, escribir la sentencia necesaria para...
Asignar al segundo real de lista1 el valor 23.56.
Asignar al primer entero de la el valor 12.
Mostrar en pantalla el contenido del ltimo entero largo de
lista_largos.
Mostrar en pantalla toda la cadena de caracteres incluida en vletras.
Mostrar en pantalla el ltimo real del vector lista1.
A continuacin se muestran varias declaraciones de vectores. Se desea saber:
Cul es la dimensin de cada uno de ellos (nmero de elementos que
permiten almacenar)?
Qu valores podrn almacenar los elementos de ellos?
Cunto ocupa en memoria cada uno de ellos?
a)
b)
c)
d)
e)

11.
12.
13.
14.
15.
16.

259

float
char
int
long
double

valores[100 ;
lista[21 ;
vector[10 ;
v3[200 ;
lista77[200 ;

Construir las sentencias necesarias para:


a) Leer de teclado y cargar de valores todos los elementos de
valores.
b) Mostrar en pantalla todos los elementos de lista.
c) Mostrar en pantalla la suma de todos los elementos de vector.
d) Mostrar en pantalla cuantas veces aparece el valor 0 en v3.
e) Mostrar en pantalla el valor mnimo de todos los almacenados en
lista77.
Sugiere un modo prctico e intuitivo de imaginar una matriz unidimensional,
bidimensional y tridimensional.
Qu se entiende por elementos de una matriz? Qu tipos de datos pueden
usarse como elementos de una matriz?
Qu se entiende por ndice (o subndice) de una matriz?
Cmo puede accederse a un elemento individual de una matriz?
Explica por qu la mayora de las aplicaciones con matrices requieren bucles
anidados Cul es el propsito de la utilizacin de estos bucles?
Qu ventaja tiene definir el tamao de una matriz en trminos de
constante/s simblica/s en vez de usar literales numricos?

260

17.

Programacin en C

Cmo se escriben los valores iniciales en una definicin de un vector?


Deben iniciarse todo el vector? Y en una tabla?

9. Punteros
Los punteros son variables que contienen una direccin de memoria que,
normalmente, representa la posicin de otra variable en memoria. La correcta
comprensin y uso de los punteros en C es fundamental para obtener los mximos
beneficios en este lenguaje, especialmente por el uso que se hace de ellos, por
ejemplo, para el paso de argumentos por referencia en una funcin, para la asignacin
dinmica de memoria o para el soporte de estructuras de datos avanzadas.
No obstante, y aunque los punteros son una de las herramientas de programacin
ms poderosas del lenguaje C, los punteros son una caracterstica peligrosa que
pueden provocar fallos diversos en el sistema, fallos que pueden ser muy difciles de
localizar.
En este captulo se van a estudiar los fundamentos de los punteros, para lo cual ste
se ha divido en cinco secciones. La primera explica cmo se almacenan las variables
en la memoria de un ordenador, introduciendo el concepto de direccin de memoria.
La segunda seccin introduce los punteros desde un punto de vista general, mientras
que la seccin tercera lo hace desde el punto particular del lenguaje C. La cuarta
seccin explica la estrecha relacin que existe entre los punteros y las matrices en el
lenguaje C. Finalmente, la quinta seccin presenta una lista de cuestiones y ejercicios
de repaso.

9.1. Almacenamiento de las variables en memoria


Dentro de la memoria de un ordenador, cada dato almacenado ocupa uno o ms bytes
contiguos de memoria. El nmero de bytes requeridos para almacenar un dato
depende de su tipo. Por ejemplo, para un compilador que genera cdigo de 16 bits, un
carcter se almacena normalmente en un byte, un entero en dos bytes, un real cuatro
bytes...
Supngase que d es una variable que representa un determinado dato. El
compilador asignar automticamente celdas adyacentes de memoria (bytes) para
almacenar ese dato.
Las celdas adyacentes dentro de la memoria del ordenador estn numeradas
consecutivamente, desde el principio hasta el final del rea de memoria. Normalmente
se sigue el convenido de utilizar el sistema de numeracin hexadecimal para designar
las direcciones de celdas de memoria.
EJEMPLO

Supngase la siguiente declaracin de variables en un lenguaje de alto nivel:


carcter
entero
real
real doble

c
i
f
d

'q'
2581
123.456
1.56

- 261 -

262

Programacin en C

El compilador1 automticamente asignar "celdas de memoria" (bytes) para


estos datos, de la siguiente forma:
nombre
variable

tamao

direccin
comienzo

1 byte

FFF4

2 bytes

FFF2

q
2581

Direcciones
de memoria
crecientes

123.456
f

4 bytes

FFEE

1.56

8 bytes

FFE6

Se representa en la figura la direccin


de comienzo en memoria de cada
variable.

En un programa, cuando se desea almacenar un valor en la memoria del ordenador,


se asigna ste al nombre de una variable, y el ordenador enva este valor a las
direcciones de memoria asignadas para esa variable.
En la mayora de lenguajes de programacin de alto nivel es posible acceder al
contenido de una variable utilizando su nombre, como por ejemplo se ha estado
haciendo hasta ahora.
escribir (c)
d i + (f/2)
O bien utilizando su direccin de comienzo en memoria, como se ver ms tarde.

9.1.1. Cmo obtener la direccin de una variable en memoria?


La direccin de memoria de una variable es un entero sin signo. La mayora de
lenguajes de programacin disponen de un operador direccin, que proporciona la
direccin de comienzo de una variable en memoria.
1

Suponiendo que el compilador necesita un byte para almacenar una variable de tipo carcter,
dos bytes para un entero, cuatro bytes para variables de tipo real y ocho bytes para el real doble.
Esta suposicin se realizar para todos los ejemplos planteados en pseudocdigo en este
captulo.

Punteros

263

Genricamente en pseudocdigo a este operador se va a representar como:


direccin(nombre_variable)

9.1.2. Dnde almacenar la direccin de una variable?


La mayora de lenguajes de programacin disponen un tipo de dato especial
denominado puntero.
Las variables de tipo puntero2 almacenan, en lugar del valor de un dato, una
direccin de memoria, que suele ser la direccin de comienzo en memoria de otra
variable, por lo que se suele decir que el puntero apunta a dicha variable.
Definicin de variables
puntero a real punt
...
real f

Nombre
variable

punt

Direccin
comienzo
FFE6

FFF4

Asignacin de valores
f 123.456
punt direccin(f)

Direcciones
de memoria
crecientes

123.4
f

FFE6

56

9.2. Punteros
Un puntero es un tipo de dato soportado por la mayora de los lenguajes de
programacin. Las variables declaradas de tipo puntero se utilizan para contener la
direccin de comienzo en memoria de otra variable.

9.2.1. Definicin de variables punteros


La variable apuntada por un puntero puede ser de cualquier tipo de dato, pero al
definir un tipo puntero se debe indicar el tipo de dato de las variables a las que va a
apuntar.
Puntero a tipo_de_dato
nombre_puntero
La razn es que los distintos tipos de datos requieren diferentes cantidades de
memoria para almacenar sus variables. Un puntero slo almacena la direccin de
comienzo de la variable, pero a travs de la declaracin del puntero, el compilador
conocer, a partir de sta direccin, cuantas ms forman la variable a la que apunta el
puntero.
2

Como los punteros son a su vez variables, estn almacenados en algn lugar de la memoria y
tienen su propia direccin.

264

Programacin en C

Dado que un puntero slo almacena la direccin de comienzo de la variable,


independientemente del tipo de variable a la que apunta, un puntero ocupa en
memoria el espacio suficiente para almacenar una direccin de memoria, es decir, un
dato entero3.

9.2.2. Iniciacin de variables punteros


Como cualquier otra variable, inicialmente una variable puntero no contendr ningn
valor vlido, es decir, su valor ser indeterminado. Para no confundir ste valor
indeterminado con ninguna direccin de memoria vlida, se puede iniciar el puntero a
valor nulo. Este valor indicar que el puntero todava no apunta a ninguna direccin
de memoria vlida.
nombre_puntero NULO
Grficamente suele representarse como se muestra en la Figura 9.1.

nombre_puntero

NULO

Figura 9.1. Representacin grfica del puntero nulo

9.2.3. Acceso a variables a travs de punteros


9.2.3.1. Asignacin de la direccin de memoria de una variable a un puntero
La forma de obtener cual es la direccin de comienzo en memoria de una variable, es
utilizar el operador direccin4, el cual devuelve la direccin de comienzo en
memoria de la variable sobre la que opera.
Para que un puntero apunte a una variable, es necesario asignar la direccin de
dicha variable al puntero.
nombre_puntero direccin (nombre_variable)
El tipo de dato en la definicin del puntero debe coincidir con el de la variable
apuntada.

Suponiendo que el compilador necesita dos bytes para almacenar una variable de tipo entero,
una variable puntero ocupar dos bytes en memoria, independientemente del tipo de variable a
la que apunte.
4 Los literales y las expresiones no tienen direccin, por lo que no se les puede aplicar el
operador direccin. Tampoco puede modificarse la direccin de una variable.

Punteros

265

EJEMPLO
/* -- Declaracin de variables -- */
puntero a entero pntc
puntero a real pntr
...
real nreal
entero cant
...
cant 15 /* Se asigna a cant un valor */
pntc direccin(cant) /* El puntero pntc apunta a la variable cant */
...
nreal 166.176
/* Se asigna a nreal un valor */
pntr direccin(nreal) /* El puntero pntr apunta a la variable nreal */

nombre
variable

direccin
comienzo

pntc

FFF4

pntr

FFF2

FFAA
FFAC

Direcciones de
memoria
crecientes

166.176
nreal

FFAC

cant

FFAA

15

9.2.3.2. Indireccin
Se entiende por indireccin la forma de referenciar el valor de una variable a travs de
un puntero que apunta a dicha variable. Para realizar la indireccin se utiliza el
operador indireccin, que permite acceder a la variable cuya direccin est contenida
en dicho puntero.
El operador indireccin suele representarse en pseudocdigo como
nombre_puntero.
Con el nombre de la variable puntero (nombre_puntero), se accede a la
direccin de comienzo de la variable referenciada o apuntada. Mientras que con el
nombre del puntero, aplicando el operador indireccin (nombre_puntero), se
accede al valor de la variable referenciada o apuntada por el puntero.
EJEMPLO
/* -- Declaraciones -- */
entero a 40, b 0
puntero a entero pnum
...

266

Programacin en C

Nombre
variable

Direccin
comienzo
Direcciones de
memoria
crecientes

40
a

FFDA

FFD8

pnum

FFEC

FFDA

pnum direccin(a)
escribir("a = ", a, " b = ", b )
escribir("La variable apuntada por pnum contiene ", pnum)
Salida en pantalla:
a = 40 b = 0
La variable apuntada por pnum contiene 40
Nombre
variable

Direccin
comienzo

FFDA

FFD8

pnum

FFEC

Direcciones
de memoria
crecientes

pnum 145
145
0

FFDA

escribir("a = ", a, " b = ", b )


escribir("La variable apuntada por pnum contiene ", pnum)
Salida en pantalla:
a = 145 b = 0
La variable apuntada por pnum contiene 145

Punteros

Nombre
variable

Direccin
comienzo

FFDA

267

b pnum

FFD8

pnum

FFEC

145
Direcciones de
memoria
crecientes

145

FFDA

escribir("a = ", a, " b = ", b )


escribir("La variable apuntada por pnum contiene ", pnum)
Salida en pantalla:
a = 145 b = 145
La variable apuntada por pnum contiene 145

9.2.4. Operaciones bsicas con punteros


9.2.4.1. Asignacin de punteros
Es posible asignar el valor de un puntero a otro puntero, con la condicin de que sean
punteros del mismo tipo. Cuando esta operacin se efecta, los dos punteros estarn
apuntando a la misma variable, pues contendrn la misma direccin de memoria.

EJEMPLO
/* -- Declaraciones -- */
Nombre
entero a 5, b, c
variable
...
puntero a entero pt1 direccin(a)
/* pt1 apunta a la variable a */
puntero a entero pt2 NULO
a
/* pt2 no apunta a ninguna variable */

Direccin
comienzo

FFEA

FFE8

FFE6

pt1

FFBC

pt2

FFBA

FFEA
NULO
Direcciones de
memoria
crecientes

268

Programacin en C

pt2 pt1
/*pt2 se carga con la direccin de memoria contenida en pt1
pt2 y pt1 apuntan a la variable a */

b pt1

/*se carga en b el valor de la variable apuntada por pt1 (variable a) */

c pt2

/*se carga en c el valor de la variable apuntada por pt2 (variable a) */

Nombre
variable

Direccin
comienzo

FFEA

FFE8

FFE6

pt1

FFBC

pt2

FFBA

5
5
5

FFEA
FFEA

Direcciones de
memoria
crecientes

9.2.4.2. Comparacin para igualdad/desigualdad


Es posible comparar dos punteros para comprobar si contienen la misma direccin de
memoria, es decir, si apuntan a la misma variable.
EJEMPLO
/* -- Declaraciones -- */
entero a 5
entero b 7
...
puntero a entero pt1
puntero a entero pt2
puntero a entero pt3
...
pt1 direccin(b)
pt2 direccin(a)
pt3 pt2

Nombre
variable

Direccin
comienzo

FFEA

FFE8

pt1

FFB4

pt2

FFB2

pt3

FFB0

pt3 y pt2 apuntan a la misma variable, por lo que:


pt2 = pt3
es verdadero.
pt2 <> pt3
es falso.
pt1 y pt2 apuntan a distintas variables, por lo que:
pt1 = pt2
es falso.
pt1 <> pt2
es verdadero.

5
7

FFE8
FFEA
FFEA

Direcciones de
memoria
crecientes

Punteros

269

9.3. Punteros en C
Los punteros tienen tres usos fundamentales en C: soporte para uno de los
mecanismos de comunicacin con las funciones; capacidad de sustitucin de las
matrices; y soporte para las rutinas de asignacin dinmica de memoria.

9.3.1. Definicin de variables punteros


En el lenguaje C se utiliza el operador monario * que afecta directamente al nombre
de la variable a la cual precede, esto es:
tipo_de_dato
*nombre_puntero;
EJEMPLOS
int
*apunt;
float
*pnum, *pprec, *pimp;
char *cad, *cad2;

No hay que cometer, en la definicin de varios punteros, el error de utilizar un


nico operador * para amparar todos los nombres de puntero que le siguen.
As, por ejemplo, en la siguiente definicin, la primera variable pnum es un
puntero a entero y la segunda variable pnum2 es un entero (no es un puntero).
int *pnum, pnum2;
El tipo de dato empleado en la definicin del puntero debe ser del mismo tipo de
dato que el de las posibles variables a las que dicho puntero puede apuntar. En caso
contrario puede haber prdida de informacin sin que el compilador emita un mensaje
de error.
En caso de que el tipo de dato sea void, se obtiene una definicin de puntero de
tipo genrico, de tal forma que el tipo de dato implcito ser el de la variable cuya
direccin se le asigne.
Las variables de tipo puntero ocupan 2 bytes en memoria, independientemente del
tipo de dato al que puedan apuntar.

9.3.2. Iniciacin de variables punteros


Cuando se define un puntero, su contenido o valor no est definido (al igual que
cuando se define cualquier otro tipo de variable). Se suele iniciar el puntero a un valor
nulo (NULL) para indicar que dicho puntero no se la ha asignado todava una posicin
de memoria vlida.
EJEMPLOS
int
float
char

*apunt = NULL;
*pnum = NULL;
*cad = NULL;

270

Programacin en C

9.3.3. Iniciacin de variables punteros


9.3.3.1. Asignacin de la direccin de memoria de una variable a un puntero
La forma de obtener cul es la direccin de comienzo en memoria de una variable, es
utilizar el operador monario &, precediendo al nombre de la variable.
Por tanto, de una variable se puede utilizar el valor que contiene (con
nombre_variable) o utilizar la direccin de memoria donde est definida (con
&nombre_variable).
Para que un puntero apunte a una variable, es necesario asignar la direccin de
dicha variable al puntero.
El tipo de dato del puntero debe coincidir con el tipo de dato de la variable
apuntada, excepto en le caso de puntero genrico (tipo void).
Las constantes literales y expresiones no tienen direccin, por lo que no es posible
aplicarles el operador (&).
La asignacin de la direccin puede hacerse en dos momentos:
1. En la definicin del puntero.
float numero;
float *pnum = &numero;

2.

Despus de haber definido el puntero, en el cuerpo del programa.


float numero;
float *pnum;
...
pnum = &numero;
La direccin de memoria de una variable es un entero sin signo. Puede visualizarse
en hexadecimal utilizando el especificador de formato %p con la funcin printf().
EJEMPLO
printf("Direccin de la variable numero: %p",&numero);
printf("Direccin contenida en el puntero pnum: %p",pnum);

Un puntero es una verdadera variable, por lo tanto puede cambiar de valor, es


decir, despus de tener asignada una direccin a un puntero, durante la ejecucin del
programa se le puede volver a asignar la direccin de otra variable, apuntando por
tanto a esa nueva variable y abandonando la anterior. Debe mantenerse la condicin
de que el tipo de dato de la variable que se va a apuntar sea del mismo tipo que el del
puntero (excepto en un puntero del tipo void).
EJEMPLO
#include <stdio.h>
void main (void) {
float nreal;
/* Define una variable esttica float en memoria */
int
nume = 2581; /* Tiene una variable esttica int en memoria */
int
*ptint = &nume; /* Define una variable esttica puntero a int en
memoria y la inicia con la direccin de
memoria de la variable nume */
float *pt;
/* Define una variable esttica puntero a float en memoria */
nreal = 166.386; /* Se utiliza el nombre de la variable para
asignarle un valor */
pt = &nreal;
/* Se utiliza el &nreal para indicar la direccin
de la variable nreal */
printf("MAPA DE MEMORIA\n\n");

Punteros

printf("Variable
printf("-------printf("nreal
printf("nume
printf("ptint
printf("pt

Direccin
-----------%p (%u)
%p (%u)
%p (%u)
%p (%u)

Salida en pantalla:
MAPA DE MEMORIA
Variable
-------nreal
nume
ptint
pt

Direccin
-----------FFF2 (65522)
FFF0 (65520)
FFEE (65518)
FFEC (65516)

Valor\n");
--------\n");
%g\n", &nreal, &nreal, nreal);
%d\n", &nume, &nume, nume);
%p\n", &ptint, &ptint, ptint);
%p\n", &pt, &pt, pt);

Nombre
variable

Valor
------166.386
2581
FFF0
FFF2

271

Direccin
comienzo

166.386

nreal

FFF2

nume

FFF0

ptint

FFEE

pt

FFEC

2581
FFF0
FFF2
Direcciones de
memoria
crecientes

9.3.3.2. Indireccin
Se entiende por indireccin la forma de referenciar el valor de una variable a travs de
un puntero que apunta a dicha variable. Para realizar la indireccin se utiliza el
operador monario asterisco (*) que cuando precede al nombre del puntero
correspondiente permite acceder a la variable cuya direccin est contenida en dicho
puntero.
EJEMPLO
/* -- Declaraciones -- */
int a = 40;
Nombre
variable
int b = 0;
...
int *pnum;
...

Direccin
comienzo

FFDA

40

FFD8

pnum

FFEC

Direcciones de
memoria
crecientes

FFDA

pnum = &a;
printf("a = %d ", a);
printf("b = %d\n", b);
printf("La variable apuntada por pnum contiene %d\n", *pnum);
Salida en pantalla:
a = 40
b = 0
La variable apuntada por pnum contiene 40

272

Programacin en C

*pnum = 145;

Nombre
variable

Direccin
comienzo

FFDA

145

FFD8

pnum

FFEC

Direcciones de
memoria
crecientes

FFDA

printf("a = %d ", a);


printf("b = %d\n", b);
printf("La variable apuntada por pnum contiene %d\n", *pnum);
Salida en pantalla:
a = 145
b = 0
La variable apuntada por pnum contiene 145
b = *pnum;

Nombre
variable

Direccin
comienzo

FFDA

145

FFD8

145

pnum

FFEC

Direcciones de
memoria
crecientes

FFDA

printf("a = %d ", a);


printf("b = %d\n", b);
printf("La variable apuntada por pnum contiene %d\n", *pnum);
Salida en pantalla:
a = 145
b = 145
La variable apuntada por pnum contiene 145

Se vuelve a recalcar el hecho de que los punteros nunca deben dejarse sin iniciar,
para lo cual se presentar el siguiente contraejemplo.
CONTRAEJEMPLO
int *ptr;
*ptr = 10;

ptr est apuntando a cualquier parte (cdigo, datos), pudiendo producirse


un error en ejecucin, puesto que en compilacin esto no da error, ni siquiera
una advertencia.

Punteros

273

9.3.4. Operaciones con punteros


9.3.4.1. Asignacin de punteros
Es posible asignar un puntero a otro puntero, con la condicin de que sean punteros
del mismo tipo. Cuando esta operacin se efecta, los dos punteros estarn apuntando
a la misma variable, pues contendrn la misma direccin de memoria.
EJEMPLO
/* -- Declaraciones -- */
int a = 5, b, c;
...
int *pt1 = &a;
/* pt1 apunta a la variable a
int *pt2 = NULL;
/* pt2 no apunta a ninguna variable
Nombre
variable

Direccin
comienzo

FFEA

FFE8

FFE6

pt1

FFBC

pt2

FFBA

*/
*/

FFEA
NULL
Direcciones de
memoria
crecientes

pt2 = pt1;
/*pt2 se carga con la direccin de memoria contenida en pt1 */
/*pt2 y pt1 apuntan a la variable a */
b = *pt1;
/*se carga en b el valor de la variable apuntada por pt1 (variable a) */
c = *pt2;
/*se carga en c el valor de la variable apuntada por pt2 (variable a) */
Nombre
variable

Direccin
comienzo

FFEA

FFE8

FFE6

pt1

FFBC

pt2

FFBA

5
5
5

FFEA
FFEA
Direcciones de
memoria
crecientes

274

Programacin en C

9.3.4.2. Comparacin de punteros


Es posible comparar dos punteros para comprobar si contienen la misma direccin de
memoria, es decir si apuntan a la misma variable.
EJEMPLO
/* -- Declaraciones -- */
int a = 5;
int b = 7;
...
int *pt1;
int *pt2;
int *pt3;
...
pt1 = &b;
pt2 = &a;
pt3 = pt2;

Nombre
variable

Direccin
comienzo

FFEA

FFE8

pt1

FFB4

pt2

FFB2

pt3

FFB0

FFE8

Direcciones de
memoria
crecientes

FFEA
FFEA

pt3 y pt2 apuntan a la misma variable, por lo que:


pt2 == pt3
es verdadero.
pt2 != pt3
es falso.
pt1 y pt2 apuntan a distintas variables, por lo que:
pt1 == pt2
es falso.
pt1 != pt2
es verdadero.

Tambin es posible utilizar los operadores relacionales >, >=, <, <= con dos
punteros para comparar las posiciones relativas en memoria que ocupan las variables
apuntadas por ellos.
EJEMPLO

De acuerdo al ejemplo anterior pt1 < pt3 es verdadero, pues la variable


apuntada por pt1 (variable b en FFE8) se encuentra en posiciones de
memoria menores que la apuntada por pt3 (variable a en FFEA).
9.3.4.3. Aritmtica de punteros
Como ya se ha visto, los punteros son variables un tanto especiales, ya que guardan
informacin no slo de la direccin a la que apuntan, sino tambin del tipo de variable
almacenado en esa direccin. Esto implica que el lenguaje C permitir dos
operaciones aritmticas con punteros: adicin y sustraccin de enteros a punteros, y la
sustraccin de dos punteros, pero no va a permitir las operaciones que no tienen
sentido con direcciones de variables como multiplicar o dividir punteros, sumar dos
punteros, aplicar desplazamientos de bits y operadores mscara a punteros, sumar ni
restar datos del tipo float o double de un puntero...
a) Sumar o restar un entero a un puntero
Si se suma o se resta un entero a un puntero, se produce un incremento o decremento,
respectivamente, de la direccin contenida en dicho puntero (se produce un avance
por posiciones de memoria segn el tipo de dato apuntado). El nmero de posiciones

Punteros

275

de memoria incrementadas o decrementadas depender del nmero entero empleado y


del tipo de dato al que apunta el puntero, tal y como se muestra en la siguiente
expresin:
puntero + n = direccin + ( n * sizeof(tipo_puntero) )

En cdigo de 16 bits se tendrn los avances que se muestran en la Tabla 9.1.


Tipo de dato apuntado
Avance en bytes
char
n
bytes
int
2n
bytes
long
4n
bytes
float
4n
bytes
double
8n
bytes
long double
10n
bytes
Tabla 9.1. Avance en posiciones de memoria en compiladores de 16 bits
EJEMPLOS
int a[4]={1000, 100, 10, 1};
...
int *pte = &a[0];
printf("*pte: %d\n",
pte++;
printf("*pte: %d\n",
pte = pte + 2;
printf("*pte: %d\n",
pte = pte - 3;
printf("*pte: %d\n",

*pte);
*pte);
*pte);

Nombre
variable

Direccin
comienzo

a[3]

FFEA

a[2]

FFE8

a[1]

FFE6

a[0]

FFE4

1
10
100
1000

*pte);
pte

FFBA

FFE4

Direcciones de
memoria
crecientes

Salida en pantalla:
*pte:
*pte:
*pte:
*pte:

1000
100
1
1000

char cd[4]={'D','C','B','A'};
...
char *ptc = &cd[3];
int i;
for (i = 1; i <= 4; i++) {
printf("%c\n", *ptc);
ptc--;
}
/* O bien */
for (i = 1; i <= 4; i++)
printf("%c\n", *ptc--);

Nombre
variable

Direcciones de
memoria
crecientes

Direccin
comienzo

cd[3]
cd[2]
cd[1]
cd[0]

FFD5
FFD4
FFD3
FFD2

A
B
C
D

ptc

FFB2

FFD5

Salida en pantalla:
A
B
C
D

Debe prestarse especial atencin a la combinacin de los operadores *, ++ y -sobre el mismo puntero. Todos ellos tienen la misma prioridad y su asociatividad es
de derecha a izquierda, por tanto:

276

Programacin en C

*++apunt

++*apunt
*apunt++
*(apunt++)
(*apunt)++

Primero se incrementa la direccin contenida en apunt y


posteriormente se obtiene el valor apuntado por la nueva
direccin de apunt.
Se incrementa la variable apuntada por apunt.
Primero se obtiene el valor apuntado por apunt y
posteriormente se incrementa la direccin contenida en
apunt.
Mismo comportamiento que el caso anterior.
Se incrementa el valor apuntado por apunt.

EJEMPLO
int a[4]={1000,100,10,1};
...
int *pte = &a[0];

Nombre
variable

Direccin
comienzo

a[3]

FFEA

a[2]

FFE8

a[1]

FFE6

a[0]

FFE4

pte

FFBA

printf("Valor: %d\n", *++pte);

1
10
100
1000

Direcciones
de memoria
crecientes

FFE4

Salida en pantalla:
Valor: 100

Nombre
variable

Direccin
comienzo

a[3]

FFEA

Primero se incrementa la
direccin contenida en pte y
posteriormente se obtiene el
valor apuntado por la nueva
direccin de pte.

a[2]

FFE8

a[1]

FFE6

a[0]

FFE4

pte

FFBA

1
10
100
1000

Direcciones
de memoria
crecientes

FFE6

++*pte;

Se incrementa la variable
apuntada por pte.

Nombre
variable

Direccin
comienzo

a[3]

FFEA

a[2]

FFE8

a[1]

FFE6

a[0]

FFE4

pte

FFBA

printf("Valor: %d\n", *pte++);

1
10
101
1000

FFE6

Direcciones
de memoria
crecientes

Punteros

Salida en pantalla:

Nombre
variable

Direccin
comienzo

a[3]

FFEA

a[2]

FFE8

a[1]

FFE6

277

Valor: 101

Primero se obtiene el valor


apuntado por pte y
posteriormente se incrementa la
direccin contenida en pte.

(*pte)++;

Se incrementa el valor
apuntado por pte.

a[0]

FFE4

pte

FFBA

Nombre
variable

Direccin
comienzo

a[3]

FFEA

a[2]

FFE8

a[1]

FFE6

a[0]

FFE4

pte

FFBA

1
10
101
1000

Direcciones
de memoria
crecientes

FFE8

1
11
101
1000

Direcciones
de memoria
crecientes

FFE8

b) Resta de dos punteros


Es una operacin que se realiza entre dos punteros del mismo tipo, para conocer el
nmero de elementos (del tipo de los punteros), que separan las direcciones apuntadas
por los punteros.
Dicho de otra forma, el resultado de la resta de dos punteros es la distancia entre
las direcciones apuntadas por ellos, no en bytes, sino en datos del tipo de los punteros.
Se suele utilizar con estructuras de datos lineales como vectores. Si ambos
punteros apuntan a elementos distintos de un mismo vector, entonces el resultado es
el mismo que restar los ndices de los elementos a los que se apunta.
EJEMPLO

Sea el siguiente fragmento de cdigo C:


float lista 10 ;
float *pt1 = &lista 2 ;
float *pt2 = &lista 7 ;
int d;
d = pt2 - pt1;

As, d tomar el valor 5, el nmero de elementos que separan a ambos


punteros.

9.4. Punteros y matrices en C


En el lenguaje C existe una relacin muy estrecha entre matrices y punteros. Como

278

Programacin en C

punto de partida el nombre de la matriz es un puntero que contiene la direccin


del primer elemento de dicha matriz. En esencia el nombre de la estructura
matricial es, por tanto, una constante puntero a ese primer elemento, y por ello no se
pueden realizar operaciones aritmticas que modifiquen la direccin contenida en l.
En este apartado se va discutir en profundidad la relacin existente entre los
punteros y las matrices en este lenguaje, y este estudio se va orientar desde la
perspectiva de los vectores y de las tablas.

9.4.1. Punteros y vectores


El nombre del vector (sin ndice ni corchetes) es una constante puntero que contiene
la direccin del primer elemento del vector.
Esto quiere decir que si, por ejemplo, vector es el nombre de un vector de
elementos de un tipo soportado en el lenguaje C, el propio nombre (vector en este
caso) contiene la direccin del primer elemento, es decir, &vector[0].
Debido a que el nombre de un vector es una constante puntero a su primer
elemento:
Dos vectores no pueden copiarse directamente uno en otro, sino que hay que
copiarlos elemento a elemento.
Dos vectores no pueden compararse directamente para ver si son iguales
(contienen los mismos valores en los elementos de posiciones equivalentes),
sino que hay que compararlos elemento a elemento.
Adems, de la misma forma que se puede acceder a elementos de un vector
mediante un puntero (el nombre del vector), se pueden poner ndices a los punteros
que apuntan a vectores.
EJEMPLO

En este ejemplo se tiene un vector llamado lista que contiene cuatro


nmeros enteros.
int lista 4 = {1000, 100, 10, 1};

Segn los explicado anteriormente, el puntero lista contiene la direccin


del primer elemento, es decir, &lista 0 .
Adems, por ejemplo, una operacin como lista++ es una operacin
incorrecta, pues lista es una constante.
Nombre

Observar la siguiente diferencia:


*(lista+1) obtiene el valor del 2do elemento de lista: 100
*lista+1
incrementa en 1 el contenido del 1er elemento
de lista: de 1000 a 1001

Direccin
comienzo

lista
FFEE

Son equivalentes...

lista[3]

FFF4

lista[2]

FFF2

lista[1]
lista[0]

FFF0
FFEE

1
10
100
1000

Direcciones
de memoria
crecientes

Es decir, se tienen las siguientes equivalencias:

lista[3]

*(lista+3)

lista[2]

*(lista+2)

lista[1]

*(lista+1)

lista[0]

*lista

Punteros

Son equivalentes...
lista[0] *lista
lista[1] *(lista+1)
lista[2] *(lista+2)
lista[3] *(lista+3)

Valor
1000
100
10
1

Son equivalentes...
&lista[0] lista
&lista[1] lista+1
&lista[2] lista+2
&lista[3] lista+3

279

Valor
FFEE
FFF0
FFF2
FFF4

En general: lista[i] es equivalente a *(lista+i).


EJEMPLO

Copia de vectores.
#define DIM 120
int vector1[DIM], i, vector2[DIM];
for (i=0; i<DIM; i++)
/* Se inicia el vector1 */
vector1[i]=i);
/* Se quiere copiar vector1 en vector2 */
/* vector2 = vector1 ---- ILEGAL !!!! */
for (i=0; i<DIM; i++)
vector2[i] = vector1[i];

O bien utilizando notacin de punteros:


for (i=0; i<DIM; i++)
*(vector2+i) = *(vector1+i);

EJEMPLO

Comparacin de vectores.
#define DIM 120
int vector1[DIM], i, vector2[DIM];
int iguales;
for (i=0; i < DIM; i++)
/* Lectura del vector1 de teclado*/
scanf("%d", &vector1 i );
for (i=0; i < DIM; i++)
/* Lectura del vector2 de teclado*/
scanf("%d", &vector2 i );
/* Se quiere comparar si son iguales vector1 y vector2 */
/* if (vector1 == vector2) ---- INCORRECTO !!!!
iguales = 1;
else iguales = 0;
*/
iguales = 1;
i = 0;
do
if (vector1[i] != vector2[i])
iguales = 0;
else i++;
while (i < DIM && iguales);

O bien utilizando notacin de punteros:


iguales = 1;
i = 0;
do
if (*(vector1+i) != *(vector2+i))
iguales = 0;
else i++;
while (i < DIM && iguales);

EJEMPLO

Dadas las siguientes declaraciones:


char
char
char

mensaje [80] = "ESTO ES UN MENSAJE";


aviso
[80];
aviso_inv[80];

280

Programacin en C

Se necesita copiar el valor de mensaje en la cadena aviso. Para ello, se


recuerda que intentar realizar aviso = mensaje; es ilegal, puesto que
aviso es una constante puntero y, por tanto, no puede modificarse su valor.
Existen dos posibles soluciones:
1. Utilizar la funcin strcpy() definida en string.h.
2. Que el programador directamente realice el tratamiento de mensaje
y aviso como vectores de caracteres, manejndolos carcter a
carcter.
Se presenta a continuacin un esquema del programa que copia en la cadena
aviso la cadena mensaje, y en la cadena aviso_inv la cadena
mensaje invertida, manejando carcter a carcter las tres cadenas.
Se copia en aviso la cadena mensaje.
#define NULO '\0'
int i;
i = 0;
while ( mensaje[i] != NULO)
aviso[i++] = mensaje[i];
aviso[i] = NULO;

La versin equivalente realizada con punteros sera:


#define NULO '\0'
int i;
i = 0;
while ( *(mensaje+i) != NULO)
*(aviso + i++) = *(mensaje+i);
*(aviso+i) = NULO;

Se copia en aviso_inv la cadena mensaje invertida.


#define NULO '\0'
int i;
int k;
/* Se busca el '\0' de la cadena mensaje */
k = 0;
while (mensaje[k] != NULO) k++;
/* Posicionamiento en el carcter anterior al '\0' */
k--;
/* Se recorre hacia atrs la cadena mensaje y hacia
delante aviso_inv */
i=0;
while (k >= 0) {
aviso_inv[i] = mensaje[k];
k--;
i++;
}
/* Se carga en aviso_inv y el '\0' final. La variable i
ya apunta a la siguiente posicin de aviso_inv */
aviso_inv[i] = NULO;

La versin equivalente realizada con punteros sera:

#define NULO '\0'


int i;
char *pt;
/* Se busca el '\0' de la cadena mensaje */
pt = mensaje;
while (*pt != NULO) pt++;
/* Posicionamiento en el carcter anterior al '\0' */
pt--;
/* Se recorre hacia atrs la cadena mensaje y hacia
delante aviso_inv */

Punteros

281

i=0;
while (pt != mensaje) {
*(aviso_inv+i) = *pt;
pt--;
i++;
}
/* Se carga el primer carcter de mensaje en aviso_inv y
el '\0' al final de la cadena aviso_inv */
*(aviso_inv+i) = *pt;
i++;
*(aviso_inv+i) = NULO;

Finalmente, aviso y aviso_inv quedarn cargadas con "ESTO ES UN


MENSAJE" y "EJASNEM NU SE OTSE" respectivamente.
Segn se observa en este ejemplo, la diferencia entre el uso del nombre de la
matriz como puntero (mensaje) y el uso de un puntero definido
especficamente para contener la direccin de la matriz (pt), es que la
primera es una constante y siempre debe apuntar a la misma direccin de
memoria (posicin del primer elemento de la matriz), mientras que la
segunda es una variable que puede cambiar su contenido para contener la
direccin de cualquiera de los elemento de la matriz. Incluso se puede volver
a utilizar la variable puntero para apuntar a otra matriz, siempre que sea del
mismo tipo de dato que el definido para el puntero.
EJEMPLO

A continuacin se presenta un ejemplo del uso de punteros con notacin


indexada, como si se tratase de un vector.
int cad[10];
int *p; /* Definicin de un puntero a entero */
int t;
p = cad;
cad[t] es equivalente a *(cad + t)
p[t]
es equivalente a *(p + t)

9.4.2. Punteros y tablas


Para comprender mejora la relacin entre punteros y tablas en el lenguaje C, se podra
pensar en una tabla de f filas y c columnas como un vector de f elementos, donde
cada elemento es a su vez un vector de c elementos.
Sea la siguiente tabla:
int mat 4 3 ={0, 1, 2, 10, 11, 12, 20, 21, 22, 30, 31, 32};

El nombre de la tabla (sin ndices ni corchetes) ser por tanto una constante
puntero que contiene la direccin del primer elemento, es decir, la direccin de la
primera fila de la tabla.
En la Figura 9.2 se muestra una posible representacin en memoria de la tabla
mat. En este caso concreto, mat es un puntero que contiene la direccin de la
primera fila de la tabla.
Si se obtiene su contenido, *mat, se tendr el elemento al que apunta, es decir, la
primera fila de la tabla (mat[0]). Pero *mat (o mat[0]) es a su vez el nombre de

282

Programacin en C

un vector de 3 elementos, por lo que es un puntero al primer elemento de la primera


fila.
Si se obtiene nuevamente su contenido, **mat, se tiene el primer elemento de la
primera fila, esto es, mat[0][0].
Si *mat (o mat[0]) es un puntero al primer elemento de la primera fila,
incrementndolo en una unidad, *mat+1, se obtiene un puntero al segundo elemento
de la primera fila, y posteriormente obteniendo su contenido, *(*mat+1), se obtiene
el segundo elemento de la primera fila, es decir, mat[0][1].
Como mat es un puntero a la primera fila de la tabla, incrementndolo en una
unidad mat+1, se obtiene un puntero que contiene la direccin de la segunda fila de
la tabla. Si se obtiene su contenido, *(mat+1), se tendr el elemento al que apunta,
es decir, la segunda fila de la tabla (mat[1]). Pero *(mat+1) (o mat[1]) es a su
vez el nombre de un vector de 3 elementos, por lo que es un puntero al primer
elemento de la segunda fila. Si se obtiene nuevamente su contenido, **(mat+1), se
tiene el primer elemento de la segunda fila, es decir, mat[1][0].
Por razonamiento anlogos se llega a que *(*(mat+1)+1) ser el segundo
elemento de la segunda fila, es decir, mat[1][1].
Elemento
tabla mat

mat[3][2]
mat[3][1]
mat[3][0]
mat[2][2]
mat[2][1]
mat[2][0]
mat[1][2]
mat[1][1]
mat[1][0]
mat[0][2]
mat
FFDE

mat[0][1]
mat[0][0]

Direcciones

FFF5
FFF4
FFF3
FFF2
FFF1
FFF0
FFEF
FFEE
FFED
FFEC
FFEB
FFEA
FFE9
FFE8
FFE7
FFE6
FFE5
FFE4
FFE3
FFE2
FFE1
FFE0
FFDF
FFDE

32
31

Fila 4

30
20
21

Direcciones
de memoria
Fila 3 crecientes

20
12
11

Fila 2

10
2
1

Fila 1

Figura 9.2. Representacin de una tabla en memoria

Segn lo expuesto se tienen las siguientes equivalencias:

Punteros

283

Son equivalentes...
*mat
*(mat+1)
*(mat+2)
*(mat+3)

mat[0]
mat[1]
mat[2]
mat[3]

fila
fila
fila
fila

[0]
[1]
[2]
[3]

de
de
de
de

Son equivalentes...
**mat
*(*mat+1)
*(*mat+2)
**(mat+1)
*(*(mat+1)+1)
*(*(mat+1)+2)

mat[0][0]
mat[0][1]
mat[0][2]
mat[1][0]
mat[1][1]
mat[1][2]

mat
mat
mat
mat

puntero
puntero
puntero
puntero

Valor
0
1
2
10
11
12

al
al
al
al

primer
primer
primer
primer

elemento
elemento
elemento
elemento

de
de
de
de

fila
fila
fila
fila

[0]
[1]
[2]
[3]

Son equivalentes...
**(mat+2)
*(*(mat+2)+1)
*(*(mat+2)+2)
**(mat+3)
*(*(mat+3)+1)
*(*(mat+3)+2)

mat[2][0]
mat[2][1]
mat[2][2]
mat[3][0]
mat[3][1]
mat[3][2]

de
de
de
de

mat
mat
mat
mat

Valor
20
21
22
30
31
32

En general se puede decir que mat[f][c] es equivalente a *(*(mat+f)+c).


La Figura 9.3 muestra las direcciones de las filas dentro de la tabla mat, mientras
que la Figura 9.4 hace lo propio con los elementos de cada fila.

mat[2][0]
mat[1][2]
mat[1][1]
mat[1][0]
mat[0][2]
mat[0][1]
mat[0][0]

FFEB
FFEA
FFE9
FFE8
FFE7
FFE6
FFE5
FFE4
FFE3
FFE2
FFE1
FFE0
FFDF
FFDE

20

mat+2
Puntero al tercer elemento
(fila) de la tabla (a la fila [2])

12
11
10

mat+1
Puntero al segundo elemento
(fila) de la tabla (a la fila [1])

2
1
0

mat
Puntero al primer elemento
(fila) de la tabla (a la fila [0])

Figura 9.3. Direcciones de las filas dentro de una tabla

mat[2][0]
mat[1][2]
mat[1][1]
mat[1][0]
mat[0][2]
mat[0][1]
mat[0][0]

FFEB
FFEA
FFE9
FFE8
FFE7
FFE6
FFE5
FFE4
FFE3
FFE2
FFE1
FFE0
FFDF
FFDE

20
12
11
10

*(mat+2)

Puntero al primer elemento de la


tercera fila (fila [2]) de la tabla.

*(mat+1)+2

Puntero al tercer elemento de la


segunda fila (fila [1]) de la tabla.

*(mat+1)+1

Puntero al segundo elemento de la


segunda fila (fila [1]) de la tabla.

*(mat+1)

Puntero al primer elemento de la


segunda fila (fila [1]) de la tabla.

*mat+2

Puntero al tercer elemento de la


primera fila (fila [0]) de la tabla.
Puntero al segundo elemento de la
primera fila (fila [0]) de la tabla.

2
1

*mat+1

*mat

Puntero al primer elemento de la


primera fila (fila [0]) de la tabla.

Figura 9.4. Direcciones de los elementos de cada fila dentro de una tabla

La Figura 9.5 muestra otro tipo de esquema para poder comprender la relacin
entre punteros y tablas descrita anteriormente. Como se puede apreciar en ella el
nombre de la tabla (mat) es un puntero al primer elemento de un vector de punteros,

284

Programacin en C

mat[], cuyos elementos contienen las direcciones del primer elemento de cada fila
de la tabla (el nombre mat por tanto es un puntero a puntero).
Segn describe por tanto la Figura 9.5:
*mat (o mat[0]), es un puntero a la primera fila de la tabla.
**mat (o mat[0][0]), es el primer elemento de la primera fila de la
tabla.
*(*mat+1) (o mat[0][1]), es el segundo elemento de la primera fila de
la tabla.
...
*(mat+1) (o mat[1]), es un puntero a la segunda fila de la tabla.
**(mat+1) (o mat[1][0]), es el primer elemento de la segunda fila de
la tabla.
*(*(mat+1)+1) (o mat[1][1]), es el segundo elemento de la segunda
fila de la tabla.
...

Figura 9.5. Tabla vista como un vector de punteros

Al igual que suceda con los vectores, el nombre de una tabla es una constante
puntero a su primera fila, por tanto:
Dos tablas no pueden copiarse directamente una en otra, sino que hay
que copiarlas elemento a elemento.
Dos tablas no pueden compararse directamente para ver si son iguales
(contienen los mismos valores en los elementos de posiciones
equivalentes), sino que hay que compararlas elemento a elemento.
EJEMPLO

Copia de tablas.
#define FIL 12
#define COL 24
int tabla1[FIL][COL], tabla2[FIL][COL];
int f, c;
...
/* Se quiere copiar tabla1 en tabla2 */
/* tabla2 = tabla1 ---- ILEGAL !!!! */
for (f=0; f < FIL; f++)

Punteros

285

for (c=0; c < COL; c++)


tabla2[f][c] = tabla1[f][c];

Utilizando notacin de punteros.


for (f=0; f<FIL; f++)
for (c=0; c<COL; c++)
*(*(tabla2+f)+c) = *(*(tabla2+f)+c);

EJEMPLO

Comparacin de tablas.
#define FIL 12
#define COL 24
int tabla1[FIL][COL], tabla2[FIL][COL];
int f, c;
int iguales;
...
/* Se quiere comparar si son iguales tabla1 y tabla2 */
/* if (tabla1 == tabla2) ---- INCORRECTO !!!!
iguales = 1;
else iguales = 0;
*/
iguales = 1;
f = 0;
do {
c = 0;
do {
if (tabla1[f][c] != tabla2[f][c])
iguales = 0;
else c++;
} while (c < COL && iguales);
f++;
} while (i < DIM && iguales);

En general la aritmtica de punteros es mucho ms rpida que el uso de ndices,


sobre todo si se est realizando un acceso secuencial a la matriz. En el caso de un
acceso aleatorio ser igual de rpido.
De la misma forma que se puede acceder a elementos de una tabla mediante un
puntero (el nombre de la tabla), se puede poner ndices a los punteros que apuntan a
tablas, tal y como se muestra a continuacin.
int mat[10][20];
int (*pt)[20]; /* Definicin de un puntero pt que apunta a
filas (vectores) de 20 enteros */
int f,c;
pt = mat;
mat[f][c]
pt[f][c]

es equivalente a
es equivalente a

*(*(mat+f)+c)
*(*(pt+f)+c)

9.5. Cuestiones y ejercicios


1.
2.
3.
4.

Qu se entiende por la direccin de una celda de memoria? Cmo se


numeran normalmente estas direcciones?
Cmo se declara una variable puntero? Cul es la finalidad del tipo de dato
incluido en la declaracin?
Cuntos bytes son necesarios para almacenar un puntero?
Cmo se puede determinar la direccin de una variable?

286

5.
6.
7.
8.
9.
10.
11.

Programacin en C

Puede utilizarse el operador direccin aplicndose a una expresin como


2*(u + v)? Explicarlo.
Si una variable, por ejemplo float, ocupa 4 bytes, que direccin se
obtiene con el operador direccin?
Qu significa que la variable puntero pv apunte a la variable v?
Cul es la finalidad del operador indireccin? A qu tipo de operandos
puede aplicarse?
Cmo se accedera a la variable v utilizando el puntero pv?
De qu manera puede incluirse la asignacin de un valor inicial en la
declaracin de una variable puntero?
Escribir una declaracin apropiada para cada una de las siguientes
situaciones:
Declarar dos punteros que apunten a enteros.
Declarar un puntero a una cantidad de coma flotante y otro a una en doble
precisin.
Declarar un puntero a carcter.

12.

Un programa C contiene las siguientes instrucciones:


char u, v
char *pu,
...
*pv = v +
u = *pv +
pu = &u;

= 'A';
*pv = &v;
1;
1;

Supngase que la direccin de comienzo de la variable v en memoria es


FF86 y la de la variable u es FF87, entonces:
Qu valor es representado por &v?
Qu valor es asignado a pv?
Que valor es representado por *pv?
Qu valor es asignado a u?
Que valor es representado por &u?
Qu valor es asignado a pu?
Qu valor es representado por *pu?

13.

14.
15.
16.
17.

Qu se entiende por asignacin dinmica de memoria? Qu funciones de


biblioteca permiten asignar memoria dinmicamente? Cmo se especifica el
tamao del bloque de memoria? Qu clase de informacin es devuelta por
las funciones?
Supngase que una cantidad entera es sumada o restada a un puntero. Cmo
ser interpretada la suma o diferencia? Puede sumarse una cantidad real a
un puntero?
Bajo qu condiciones puede una variable puntero ser restada de otra?
Cmo se interpreta esta diferencia?
Bajo qu condiciones pueden compararse dos punteros? Para qu es til
esta comparacin?
Teniendo en cuenta las siguientes definiciones:
float num1 = 1.5, num2 = 2.5, num3 = 3.5, num4 = 4.5;
float numreal = 52.75;
float *p;

Punteros

287

Supngase que cada cantidad en coma flotante ocupa 4 bytes en memoria, y


que las direcciones de comienzo en memoria de las variables son las
siguientes:
variable
num1
num2
num3
num4
numreal

direccin
FFEC
FFE8
FFE4
FFE0
FFDC

Determinar las direcciones de memoria contenidas en el puntero p y el valor


de la variable a la que apunta en los siguientes apartados:

18.

a) p = &numreal;
b) p++;
c) p = &num1;
d) p = &num3;

e) p++;
f) p = &num2;
g) p--;

Un programa en C contiene las siguientes instrucciones:


int i, j = 25;
int *pi, *pj = &j;
...
*pj = j + 5;
i = *pj + 5;
pi = pj;
*pi = i + j;

Supngase que cada cantidad entera ocupa 2 bytes en memoria. Si j


comienza en la direccin de memoria FF9C e i en la FF9E, entonces:
a) Qu valor es representado por &i?
b) Qu valor es representado por &j?
c) Qu valor es asignado a pj?
d) Qu valor es asignado a *pj?
e) Qu valor es asignado a i?
f) Qu valor es representado por pi?
g) Qu valor final es asignado a *pi?
h) Qu valor es representado por la expresin (pi + 2)?
i) Qu valor es representado por la expresin (*pi + 2)?
j) Qu valor es representado por la expresin *(pi + 2)?

19.

Un programa en C contiene las siguientes instrucciones:


float a = 0.001, b = 0.003;
float c , *pa, *pb;
...
pa = &a;
*pa = 2 * a;
pb = &b;
c = 3 * (*pb - *pa);

Supngase que cada cantidad en coma flotante ocupa 4 bytes en memoria. Si


c comienza en la direccin de memoria 1130, b en la 1134 y a en la
1138, entonces:
a) Qu valor es asignado a &a?
b) Qu valor es asignado a &b?
c) Qu valor es asignado a &c?
d) Qu valor es asignado a pa?
e) Qu valor es representado por *pa?
f) Qu valor es representado por &(*pa)?

288

Programacin en C

g) Qu valor es asignado a pb?


h) Qu valor es representado por *pb?
i) Qu valor es asignado a c?

20.
21.
22.
23.
24.
25.

Cul es la relacin entre el nombre de una matriz y los punteros? En


particular, que representa el nombre de una matriz unidimensional o vector?
Y el de una matriz bididimensional o tabla?
Puede introducirse una matriz completa en el ordenador con una nica
sentencia de lectura scanf?
Puede escribirse una matriz completa en pantalla con una nica sentencia de
escritura printf?
Pueden asignarse colectivamente todos los elementos de una matriz a los
elementos correspondientes de otra matriz con una sola sentencia de
asignacin?
Pueden compararse para igualdad/desigualdad en una nica sentencia dos
matrices?
Un programa C contiene la siguientes declaracin:
float x[8] = {10, 20, 30, 40, 50, 60, 70, 80};

Cul es el significado de x?
Cul es el significado de (x+2)?
Cul es el valor de *x?
Cul es el valor de (*x + 2)?
Cul es el valor de *(x + 2)?
Cul es el significado de x+7?
Cul es el valor de *(x + 7)?
Cul es el valor de (*x + 7)?
Cul es el valor de *(x + 7) + 7?

26.

Un programa C contiene la siguientes declaracin:


float tabla[2][3] = {1.1, 1.2, 1.3 2.1, 2.2, 2.3};
Cul es el significado de tabla?
Cul es el significado de tabla + 1?
Cul es el significado de *tabla?
Cul es el significado de *(tabla + 1)?
Cul es el significado de *tabla + 2?
Cul es el significado de (*(tabla + 1) + 2)?
Cul es el valor de **tabla?
Cul es el valor de *(*(tabla + 1)+1)?
Cul es el valor de *(*tabla + 1)?
Cul es el valor de **(tabla + 1)?
Cul es el valor de *(*tabla + 1) + 1?
Cul es el valor de *(*tabla + 2)?
Cul es el valor de *(*(tabla + 1)+2) + 7?
Cul es el valor de --**tabla?

27.

Declarar una variable cadena de caracteres para poder almacenar hasta 79


caracteres.
Escribir la sentencia necesaria para cargar dicha variable desde teclado.
Escribir las sentencias de cdigo necesarias para contabilizar el nmero de
vocales (tanto maysculas como minsculas).

10. Cadenas de caracteres en C


Aunque ya se ha visto en captulos anteriores cmo se define y se maneja una cadena
de caracteres en el lenguaje C, se hace en este captulo una recopilacin de conceptos
ya expuestos sobre cadenas de caracteres, y se presentan las principales funciones de
biblioteca que este lenguaje dispone para el manejo de las aqullas.
Este captulo se ha divido en cuatro secciones. La primera explica el concepto de
cadena de forma genrica. La seccin siguiente presenta cmo se puede hacer el
tratamiento de una cadena de caracteres accediendo a sus componentes atmicos
directamente. La tercera seccin hace un repaso por las principales funciones que la
biblioteca estndar del lenguaje C aporta para el manejo de funciones, y que en su
mayora se encuentran definidas en el fichero de cabecera string.h. Por ltimo, la
cuarta presenta una lista de cuestiones y ejercicios.

10.1. Concepto de cadena de caracteres en C


En algunos lenguajes como COBOL o Pascal se incluye como tipo de dato bsico el
tipo cadena alfanumrica o cadena de caracteres, el cual se utiliza para almacenar y
manipular texto.
En el lenguaje C, sin embargo, no existe un tipo especial de dato para el manejo de
cadenas. Una cadena de caracteres se definir en C como un vector de tipo char.
La nica diferencia con los vectores numricos, es que C inserta automticamente
un carcter nulo (cdigo ASCII 0, \0) al final de la misma. Este carcter no es
necesario representarlo como parte de la cadena, y no aparece cuando se visualiza sta
en pantalla, aunque s es almacenado automticamente en memoria como ltimo
carcter de la misma y, por tanto, en la declaracin de la cadena deber reservarse
espacio para el mismo.
Todas las cadenas en C obligatoriamente necesitan tener el carcter nulo al final de
la misma, ya que las funciones que manejan las cadenas de caracteres como entidades
completas, necesitan este carcter nulo como indicador de terminacin de la cadena.

10.1.1. Definicin
Para definir una cadena en C se utiliza la siguiente sintaxis:
char nombre_cadena tamao+1 ;
Donde tamao representa la longitud mxima de la cadena que es posible utilizar,
ya que como el lenguaje C inserta automticamente un carcter nulo ('\0') al final
de toda cadena de caracteres, en la definicin de una variable cadena, se debe prever
una posicin para este carcter.
EJEMPLO

Sea la siguiente definicin de una cadena:


char mensaje 81 ;
- 289 -

290

Programacin en C

mensaje ser una variable cadena de caracteres que podr almacenar


cadenas de caracteres de longitud mxima 80, ya que un carcter deber ser
ocupado siempre por el '\0' final.

10.1.2. Iniciacin
Se asignan el valor de la cadena en la declaracin de la misma. Como cualquier
vector, una variable cadena de caracteres slo podr ser iniciada en el momento de la
declaracin, nunca dentro de las sentencias ejecutables del programa.
La iniciacin puede hacerse de formas diferentes:
char cadena[] = "HOLA";
Aunque no se indica entre corchetes la longitud de la cadena, el
compilador reserva automticamente el nmero correcto de posiciones
de memoria para almacenar cada elemento de la cadena, incluyendo el
carcter nulo final. En este caso se reservan cinco posiciones de
memoria, como se muestra en el siguiente esquema.
Direcciones
de memoria

Direcciones
de memoria
crecientes

FFA6
FFA5
FFA4
FFA3
FFA2

Cdigo
ASCII

'\0'
'A'
'L'
'O'
'H'

0
65
76
79
72

Obsrvese que:
o Los literales cadena de caracteres se encierran entre dobles
comillas.
"verde"
"La respuesta correcta es:"
"923-123456"
"Pablo Milans"
"" (cadena de caracteres vaca o nula)
"Lnea 1\nLnea 2\nLnea 3"

Cualquier carcter de la tabla ASCII puede pertenecer a la


cadena de caracteres (espacio, caracteres no imprimibles
representados por su correspondiente secuencia de escape...).
o Dos caracteres comillas dobles seguidos, sin ningn carcter
entre ellos estn representando una cadena de caracteres vaca
(nula).
char cadena[] = {'H', 'O', 'L', 'A', '\0'};
Hace exactamente lo mismo que la sentencia anterior, pero con una
sintaxis ms oscura.
o

Cadenas de caracteres en C

291

char cadena[5] = "HOLA";


Se especifica la longitud mxima de la cadena entre los corchetes. Debe
tenerse siempre presente que el carcter nulo es obligatorio y, por tanto,
debe reservarse espacio suficiente para l.
char cadena[80] = "HOLA";
Es frecuente especificar una longitud de cadena mayor de la necesaria,
lo que da la opcin de aadir ms caracteres, o asignar cadenas de
mayor longitud a la variable. Las posiciones extras se inician con el
carcter nulo ('\0'), como en cualquier vector en el que no se inician
sus ltimos elementos.
char cadena[3] = "HOLA";
Especificar en la iniciacin ms valores que el tamao de la variable
cadena produce un error de compilacin.
char cadena[4] = "HOLA";
No reservar espacio para el carcter nulo final no debiera producir
ningn error en compilacin (atencin con algunos compiladores), pero
en memoria no se introduce el carcter nulo final, por lo que la anterior
variable no podr ser utilizada como una cadena de caracteres (las
funciones que manejan cadenas de caracteres como entidades completas
necesitan el carcter nulo para marcar el final de la cadena), sino
simplemente como un vector de caracteres, en el cual los caracteres que
forman parte del mismo slo podrn ser procesados individualmente.
Direcciones
de memoria

Direcciones
de memoria
crecientes

FFA5
FFA4
FFA3
FFA2

Cdigo
ASCII

'A'
'L'
'O'
'H'

65
76
79
72

10.1.3. Lectura y escritura de cadenas de caracteres


En captulos anteriores ya se han visto las funciones de que dispone el lenguaje C para
la lectura de cadenas de caracteres de teclado y para su la escritura en pantalla. No
obstante, se va a realizar en este momento un breve resumen de lo que ya se conoce.
Para introducir cadenas desde el teclado puede utilizarse la funcin scanf(),
cuyo archivo de cabecera es stdio.h, con el especificador de formato %s (cadenas),
aunque este especificador de formato tiene el inconveniente de que utiliza el carcter
espacio, el carcter tabulador y el carcter fin de lnea (intro) para dar por

292

Programacin en C

terminada la entrada del dato, por lo que no se podr cargar desde teclado cadenas que
incluyan el carcter espacio.
Para dar entrada por scanf() a cadenas de caracteres que incluyan caracteres
espacio como parte de los datos, se debe utilizar como especificador de formato
%[^\n], que indica que slo los caracteres incluidos entre corchetes sean
considerados como terminacin de dato, en este caso slo el carcter fin de lnea
(intro o \n).
EJEMPLO

Si se define la variable char nombre[80] para realizar la entrada desde


teclado de nombres de persona con una sentencia como:
scanf("%s", nombre);

No se podr cargar el valor ANTONIO JOSE, quedando la variable nombre


cargada simplemente con la palabra ANTONIO.
La sentencia scanf() a utilizar sera:
scanf("%[^\n]", nombre);

Existe otra funcin de la biblioteca estndar de C que permite introducir cadenas


desde el teclado. Esta funcin es gets(), y su archivo de cabecera es stdio.h.
La funcin gets() no es una funcin de entrada tan verstil como scanf(), ya
que slo est preparada para leer cadenas, pero tiene la ventaja de que utiliza slo el
carcter fin de lnea (intro) para dar por terminada la entrada del dato, permitiendo
la introduccin de espacios en blanco y tabuladores dentro de la cadena.
La sintaxis de gets() es: gets(vector_caracteres);
EJEMPLO
#include <stdio.h>
void main() {
char nombre[80];
printf("Escribe tu nombre: ");
gets(nombre);
printf("\nHola %s", nombre);
}

Para realizar la salida en pantalla de una cadena de caracteres, adems de poder


utilizar la funcin printf(), cuyo archivo de cabecera es stdio.h, con el
especificador de formato %s (cadenas), existe una funcin de salida de propsito
particular especializada en cadenas, llamada puts(), cuyo archivo de cabecera es
stdio.h.
A diferencia de printf(), puts() slo puede imprimir una cadena a la vez, no
teniendo capacidad de formatear una cadena antes de imprimirla. Adems, genera en
pantalla automticamente un salto de lnea al final de la cadena.
La sintaxis de puts() es: puts(cadena);
EJEMPLO
#include <stdio.h>
void main() {
char nombre[80];
printf("Escribe tu nombre: ");
gets(nombre);

Cadenas de caracteres en C

293

printf("\nHola ");
puts(nombre);
}

10.2. Acceso individual a los caracteres de una cadena


La forma ms sencilla de resolver muchos problemas con cadenas de caracteres es
procesar individualmente los caracteres que la componen, en lugar de tratarla como
una entidad completa. Dado que una cadena de caracteres no es ms que un vector de
caracteres, donde su final viene marcado por el carcter nulo (\0), se puede
acceder directamente e individualmente (o de forma secuencial) a cada uno de los
caracteres que la componen.
Cuando se recorre secuencialmente un vector siempre se hace hasta su dimensin
efectiva (nmero de elementos almacenados en l), la cual, normalmente, estar
almacenada en una variable numrica entera. Cuando se recorre secuencialmente una
cadena de caracteres debe hacerse hasta encontrar su carcter nulo, que marca el final
de la misma.
En consecuencia, cuando se est formando una cadena de caracteres insertando en
ella de forma individual sus caracteres, siempre se debe insertar el carcter nulo al
final de la misma.
EJEMPLO

Realizar un programa que lea en tres variables cadena, el nombre y los dos
apellidos de una persona y, posteriormente, concatene en una cuarta variable
cadena las tres anteriores, separando cada una de ellas por el carcter
asterisco (*).
#include <stdio.h>
#define N 30
#define SEPARADOR '*'
#define NULO '\0'
void main (void) {
int j, i;
char nomb[N];
char ape1[N];
char ape2[N];
char iden_persona[3*N];
printf("Introduzca el nombre: ");
gets(nomb);
printf("Introduzca el primer apellido: "); gets(ape1);
printf("Introduzca el segundo apellido: "); gets(ape2);
i = 0;
/* Se recorre carcter a carcter la cadena nomb, llevando de
uno en uno los caracteres a la cadena iden_persona.
Se termina cuando se localice el carcter nulo final de la
cadena nomb */
j = 0;
while (nomb[j] != NULO) {
iden_persona[i] = nomb[j];
j++;
i++;
}

294

Programacin en C
/* Se coloca el carcter separador a iden_persona */
iden_persona[i] = SEPARADOR;
i++;
/* Se recorre carcter a carcter la cadena ape1, llevando de
uno en uno los caracteres a la cadena iden_persona.
Se termina cuando se localice el carcter nulo final de la
cadena ape1 */
j = 0;
while (ape1[j] != NULO) {
iden_persona[i] = ape1[j];
j++;
i++;
}
iden_persona[i] = SEPARADOR;
i++;
/* Se recorre carcter a carcter la cadena ape2, llevando de
uno en uno los caracteres a la cadena iden_persona. Se
termina cuando se localiza el carcter nulo final de la
cadena ape2 */
j = 0;
while (ape2[j] != NULO) {
iden_persona[i] = ape2[j];
j++;
i++;
}
/* Se coloca el carcter nulo final a iden_persona */
iden_persona[i] = NULO;
printf("Identificacin persona: %s\n", iden_persona);
}

El siguiente es un ejemplo de ejecucin del programa (en negrita las entradas


del usuario):
Introduzca el nombre: JUAN JOSE
Introduzca el primer apellido: GARCIA
Introduzca el segundo apellido: DE LA CUESTA
Identificacin persona: JUAN JOSE*GARCIA*DE LA CUESTA

10.3. Funciones para el manejo de cadenas de caracteres


Los problemas en los que se requiere que las cadenas de caracteres se procesen como
entidades completas, suelen simplificarse considerablemente utilizando funciones de
biblioteca proporcionadas por el propio lenguaje C, la mayora de ellas definidas en el
archivo cabecera string.h.
Se recuerda, por otro lado, que una cadena de caracteres en C no es ms que un
vector de caracteres, por ello su nombre sin corchetes representa un puntero al primer
carcter de la cadena. As en C, operaciones que en otros lenguajes se resuelven a
travs de un operador, como por ejemplo la asignacin de una constante a una
variable cadena o la comparacin de dos variables cadenas para comprobar si
contienen el mismo valor, se encuentran tambin implementadas a travs de funciones
de biblioteca.

Cadenas de caracteres en C

295

EJEMPLO

Supngase que nombre1 y nombre2 son dos variables cadena de


caracteres en C. Entonces:
nombre1 = "JUAN";
Es incorrecto pues nombre1 es un puntero.
if (nombre1 == nombre2)...
Compara si los dos punteros nombre1 y nombre2 apuntan a
la misma posicin de memoria, pero no compara si las dos
cadenas contienen el mismo valor.
Para resolver las dos anteriores operaciones se debe recurrir a la funcin de
biblioteca correspondiente.
Se presentan en la Tabla 10.1 diversas funciones de biblioteca en C orientadas al
manejo de cadenas de caracteres, as como de caracteres individuales.
Archivo
Funcin
Descripcin
cabecera
strlen()
strcpy()
strncpy()
strdup()
strcat()
strncat()
strcmp()
strncmp()
strcmpi()
strncmpi()
strchr()
strrchr()
strstr()
strpbrk()
strspn()
strcspn()
strupr()
strlwr()
strset()
strrev()
strtok()
atoi()
atol()
atof()
isalnum()
isalpha()
isascii()
iscntrl()
islower()
isupper()
isdigit()
ispunct()
isspace()
isxdigit()

Longitud de la cadena de caracteres


Copia una cadena en otra
Copia n caracteres de una cadena en otra
Duplica una cadena
Concatenacin de cadenas
Concatenacin de n caracteres
Comparacin de cadenas
Comparacin de n caracteres
Comparacin de cadenas, sin distinguir maysculas de minsculas
Comparacin de n caracteres, sin distinguir maysculas de minsculas
Bsqueda de un carcter dentro de una cadena
Bsqueda de un carcter dentro de una cadena desde el final
Bsqueda de una subcadena dentro de una cadena
Bsqueda de cualquier carcter de una subcadena en una cadena
Devuelve el nmero de caracteres de una cadena incluidos en una cadena patrn
Devuelve el ndice del primer carcter de una cadena incluido en una cadena patrn
Convierte una cadena de caracteres a maysculas
Convierte una cadena de caracteres a minsculas
Llena una cadena con un determinado carcter
Invierte una cadena de caracteres
Permite dividir una cadena en subcadenas
Convierte una cadena de caracteres a entero (int)
Convierte una cadena de caracteres a entero largo (long)
Convierte una cadena de caracteres a real (double)
Retorna cierto si el argumento es un carcter alfanumrico
Retorna cierto si el argumento es un carcter alfabtico
Retorna cierto si el argumento es un carcter entre el cdigo ASCII 0 y el 126
Retorna cierto si el argumento es un carcter entre el cdigo 0 y el 31 o es el 127
Retorna cierto si el argumento es un carcter alfabtico minsculas
Retorna cierto si el argumento es un carcter alfabtico maysculas
Retorna cierto si el argumento es un carcter numrico
Retorna cierto si el argumento es un carcter de puntuacin
Retorna cierto si el argumento es un carcter espacio, tabulador, avance lnea...
Retorna cierto si el argumento es un carcter hexadecimal, es decir, 0 - 9, a - f, A - F

Tabla 10.1. Funciones de cadena y carcter

A continuacin se detallan las funciones de cadena ms relevantes.

string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
string.h
stdlib.h
stdlib.h
stdlib.h
ctype.h
ctype.h
ctype.h
ctype.h
ctype.h
ctype.h
ctype.h
ctype.h
ctype.h
ctype.h

296

Programacin en C

10.3.1. Funcin strlen()


Calcula la longitud de una cadena de caracteres. La longitud de una cadena de
caracteres es un entero sin signo que indica el nmero de caracteres que forman parte
de la cadena, excluyendo el carcter nulo final. El prototipo de esta funcin es:
unsigned strlen(char *src);
Como no se tiene en cuenta el carcter nulo final, la memoria real necesaria para
albergar la cadena es 1+strlen(scr).
EJEMPLO

Este programa cuenta la longitud de varias cadenas de caracteres.


#include <stdio.h>
#include <string.h>
void main(void) {
char msg[] = "Bienvenidos al sistema.";
char vca[] = "";
printf("\"%s\" contiene %d caracteres\n", msg, strlen(msg));
printf("\"%s\" contiene %d caracteres\n", vca, strlen(vca));

}
Salida por pantalla:

"Bienvenidos al sistema." contiene 23 caracteres


"" contiene 0 caracteres

10.3.2. Funciones strcpy(), strncpy() y strdup()


La funcin strcpy() se utiliza para copiar una cadena en otra. El contenido de la
cadena que recibe como segundo argumento, incluyendo el carcter nulo final, se
copia en la cadena que recibe como primer argumento. En definitiva, esta funcin
viene a sustituir al operador asignacin, el cual no se puede utilizar en C con cadenas
de caracteres.
La funcin strncpy(), copia sobre la cadena destino slo los n primeros
caracteres de la cadena origen, pero sin colocar el carcter nulo al final de la cadena
destino.
Los prototipos de estas funciones son:
char *strcpy(char *dest, char *src);
char *strncpy(char *dest, char *src, unsigned n);
EJEMPLO

Este programa copia el contenido de una cadena en otra. Se observa tambin


el comportamiento de la funcin strncpy(), despus de la cual puede
interesar o no colocar el carcter nulo ('\0') a la cadena destino.
#include <stdio.h>
#include <string.h>
#define N 4
void main(void) {
char string[18]
char str1[]
char str2[]
char str3[]

=
=
=
=

"ESTA ES MI CADENA";
"abcdef";
"123456";
"VWXYZ";

Cadenas de caracteres en C
printf("Al principio.
printf("string: %s\n", string);

297

");

strcpy(string, str1); /* Sustituye a string = str1 */


printf("Despus de copiar str1.
");
printf("string: %s\n", string);
strncpy(string, str2, N);
printf("Despus de copiar %d caracteres de str2. ", N);
printf("string: %s\n", string);

strncpy(string, str3, N-2);


string[N-2] = '\0';
printf("Despus de copiar %d caracteres de str3. ", N-2);
printf("string: %s\n", string);

Salida por pantalla:


Al principio.
Despus de copiar str1.
Despus de copiar 4 caracteres de str2.
Despus de copiar 2 caracteres de str3.

string:
string:
string:
string:

ESTA ES MI CADENA
abcdef
1234ef
VW

La funcin strdup(), duplica la cadena argumento en una zona de memoria por


ella reservada y retorna un puntero a dicha zona. El prototipo de esta funcin es:
char *strdup (char *src)
EJEMPLO
Ntese que el retorno de la funcin debe ser siempre asignado a un dato puntero.
#include <string.h>
void main(void) {
char *p;
char q[] = "Duplicacin de strings";
p = strdup(q);
..................
}

10.3.3. Funciones strcat() y strncat()


La funcin strcat() se utiliza para concatenar dos cadenas de caracteres.
Cuando se concatenan dos cadenas, los contenidos de la segunda cadena (segundo
argumento de la funcin) se copian al final de la primera (primer argumento de la
funcin). Es importante que la primera cadena tenga definida una longitud suficiente
para almacenar la concatenacin de ambas.
Si no se necesita concatenar la segunda cadena completa, se puede usar la funcin
strncat(), que concatena a la primera cadena slo los n primeros caracteres de la
segunda.
Los prototipos de estas funciones son:
char *strcat(char *dest, char *src);
char *strncat(char *dest, char *src, unsigned n);
EJEMPLO

Este programa forma una frase concatenando tres cadenas. De la ltima slo
concatena tantos caracteres como posiciones queden disponibles en la cadena
destino.

298

Programacin en C
#include <stdio.h>
#include <string.h>
#define MAXLON 55
void main(void) {
char cad1[30];
char mensaje[MAXLON+1]="Hola ";
char cad2[] = ". Bienvenido al sistema.";
int lon;
printf("\nIntroduzca su nombre: "); gets(cad1);
strcat(mensaje, cad1);

lon = strlen(mensaje)+strlen(cad2);
if (lon < MAXLON)
strcat(mensaje, cad2);
else
strncat(mensaje, cad2, MAXLON-strlen(mensaje));
puts(mensaje);

Se muestran dos ejemplos de ejecucin de este programa:


Introduzca su nombre: Jos Carlos
Hola Jos Carlos. Bienvenido al sistema.
Introduzca su nombre: BLAS ALBERTO JOSE PIO GABRIEL
Hola BLAS ALBERTO JOSE PIO GABRIEL. Bienvenido al siste

La concatenacin de varias cadenas puede anidarse, de la siguiente manera:


strcat(strcat(cadx, cadw), cadz);
Donde a la cadena cadx se le agrega a continuacin la cadena cadw, y luego la
cadena cadz. Por supuesto, la cadena cadx tiene que tener la suficiente longitud
como para albergar adems a las otras dos cadenas.

10.3.4. Funciones strcmp() y strncmp()


Las funciones strcmp() y strncmp() se utilizan para comparar cadenas de
caracteres. Estas funciones vienen a sustituir al operador de relacin igualdad, el cual
no se puede utilizar en C con cadenas de caracteres.
La comparacin se hace carcter a carcter. Devuelven un valor entero sin signo
que depende del resultado de la comparacin:
0 si las cadenas son idnticas (iguales caracteres e igual longitud).
>0 si la primera cadena precede alfabticamente a la segunda.
<0 si la segunda cadena precede alfabticamente a la primera.
La diferencia entre strcmp() y strncmp() es que esta segunda funcin slo
compara los primeros n caracteres de ambas cadenas.
Algunos compiladores incluyen las funciones strcmpi() y strncmpi(),
equivalentes a las anteriores pero que no distinguen entre maysculas y minsculas.
Los prototipos de estas funciones son:
int strcmp(char *s1, char*s2);
int strncmp(char *s1, char *s2, unsigned n);

Cadenas de caracteres en C

299

EJEMPLO

Este programa calcula el nmero de caracteres iniciales que dos cadenas


tienen iguales.
#include <stdio.h>
#include <string.h>
#define MAXLON 80
void main() {
char cad1[MAXLON], cad2[MAXLON];
int i, lon;
printf("Introduce una cadena: ");
gets(cad1);
printf("Introduce una segunda cadena: "); gets(cad2);
if (!strcmp(cad1, cad2))
printf("Las cadenas son iguales\n");
else {
lon = strlen(cad1);
if (lon > strlen(cad2)) lon = strlen(cad2);
i=1;
while (!strncmp(cad1, cad2, i)) i++;
printf("Las cadenas tienen los %d primeros ", i-1);
printf("caracteres iguales\n");
}

Salida por pantalla:


Introduce una cadena: Hola Mundo
Introduce una segunda cadena: Hola MUNDO
Las cadenas tienen los 6 primeros caracteres iguales

10.3.5. Funciones strchr () y strrchr ()


Las funciones strchr() y strrchr(), buscan un carcter especfico (segundo
argumento que reciben) dentro de una cadena de caracteres (primer argumento).
Ambas funciones devuelven un puntero a la posicin del carcter dentro de la
cadena, si lo han encontrado, o un puntero nulo en caso contrario.
La diferencia entre ambas es que la funcin strchr() devuelve la primera
ocurrencia del carcter dentro de la cadena, mientras que la funcin strrchr()
devuelve la ltima ocurrencia del carcter dentro de la cadena.
Los prototipos de estas funciones son:
char *strchr(char *scr, char c);
char *strrchr(char *scr, char c);

EJEMPLO
Este programa localiza la posicin inicial y final de un carcter dentro de una
cadena.
#include <stdio.h>
#include <string.h>
#define MAXLON 80
void main() {
char cad[MAXLON], c, *ptr;
int i;
printf("Introduce una cadena: ");

gets(cad);

300

Programacin en C
printf("Introduce un carcter: ");

scanf("%c", &c);

ptr = strchr(cad, c);


if (ptr) {
printf("El primer \"%c\" es el carcter n %d\n",
*ptr, ptr-cad+1);
printf("El ltimo \"%c\" es el carcter n %d\n",
*(strrchr(cad,c)), strrchr(cad,c)-cad+1);
i=0;
while(ptr) {
i++;
ptr = strchr(++ptr, c);
}
printf("Hay %d \"%c\" en la cadena %s\n", i, c, cad);
}
else
printf("El carcter \"%c\" no est en la cadena %s\n",
c, cad);
}

Salida por pantalla:


Introduce
Introduce
El primer
El ltimo

una cadena: Hola, Don Jos!


un carcter: o
"o" es el carcter n 3
"o" es el carcter n 13

10.3.6. Funcin strstr()


La funcin strstr() busca la primera ocurrencia de una subcadena (segundo
argumento de la funcin) dentro de una cadena (primer argumento de la funcin).
Si se encuentra la subcadena, la funcin strstr() devuelve un puntero al
principio de la subcadena, en caso contrario devuelve un puntero nulo. El prototipo de
esta funcin es:
char *strstr(char *cad, char *subcad);
EJEMPLO

Este programa busca cuantas veces aparece una subcadena dentro de una
cadena.
#include <stdio.h>
#include <string.h>
#define MAXLON 80
void main() {
char cad[MAXLON], palabra[80] , *ptr;
int lon, contador=0;
printf("Introduce una cadena: ");
printf("Introduce una palabra: ");
lon=strlen(palabra);
ptr=cad;
do {
ptr=strstr(ptr, palabra);
if (ptr) {
contador++;
ptr += lon;
}
} while (ptr);

gets(cad);
gets(palabra);

Cadenas de caracteres en C

301

printf("La palabra \"%s\" aparece %d veces\n",


palabra, contador);

Se muestran varios ejemplos de ejecucin de este programa:


Introduce una cadena: El casado se caso en su casa de campo
Introduce una palabra: ca
La palabra "ca" aparece 4 veces
Introduce una cadena: Le robaron el perro a Roque
Introduce una palabra: ro
La palabra "ro" aparece 3 veces
Introduce una cadena: LE ROBARON EL PERRO A ROQUE
Introduce una palabra: ro
La palabra "ro" aparece 0 veces

10.3.7. Funcin strpbrk()


La funcin strpbrk() busca la primera aparicin de cualquier carcter de una
subcadena dentro de una cadena.
Esta funcin es til cuando es necesario saber si una cadena de caracteres contiene
un carcter determinado o un conjunto de caracteres concretos. El prototipo de esta
funcin es:
char *strpbrk(char *cad, char *subcad);
EJEMPLO

Este programa busca en la cadena que se introduzca por teclado los signos de
puntuacin y los sustituye por espacios.
#include <stdio.h>
#include <string.h>
#define MAXLON 80
#define ESPACIO ' '
void main() {
char cad[MAXLON], *ptr;
printf("Introduzca una cadena con signos de puntuacin:\n");
gets(cad);
ptr = cad;
while (ptr) {
ptr = strpbrk(ptr, ".,!;'\"?-");
if (ptr)
*ptr = ESPACIO;
}
puts(cad);
}

Salida por pantalla:


Introduzca una cadena con signos de puntuacin:
Hola!, soy yo. Cmo ests?. -Bien, gracias.
Hola
soy yo
Cmo ests
Bien gracias

10.3.8. Funciones strspn() y strscpn()


La funcin strspn() devuelve el nmero de caracteres, comenzando por la parte

302

Programacin en C

izquierda de la cadena s1 que coinciden con cualquier carcter de la cadena patrn


s2. La bsqueda termina cuando se localiza en s1 un carcter no incluido en la
cadena patrn s2, o bien cuando se llega al final de la cadena s1. Su prototipo es:
size_t strspn(char *s1, char *s2);
EJEMPLO
#include <stdio.h>
#include <string.h>
#include <alloc.h>
void main(void) {
char *cadena1 =
char *cadena2 =
char *cadena3 =
char *cadena4 =
int length;

"1234567890";
"123DC8";
"1234567";
"12345678";

length = strspn(cadena1, cadena2);


printf("El carcter donde cadena1 difiere de cadena2 \n");
printf("est en la posicin %d\n", length);
length = strspn(cadena3, cadena4);
printf("El carcter donde cadena3 difiere de cadena4 \n");
printf("est en la posicin %d\n", length);
}

Salida por pantalla:


El carcter donde cadena1 difiere de cadena2
est en la posicin 3
El carcter donde cadena3 difiere de cadena4
est en la posicin 7

La funcin strcspn() localiza el ndice del primer carcter de la cadena s1 que


est incluido en el conjunto de caracteres especificado en la cadena patrn s2. Su
prototipo es:
size_t strcspn(char *s1, char *s2);
EJEMPLO
#include <stdio.h>
#include <string.h>
#include <alloc.h>
void main(void) {
char *cadena1 = "1234567890";
char *cadena2 = "747DC8";
int length;
length = strcspn(cadena1, cadena2);
printf("ndice del primer carcter de cadena2 ");
printf("incluido en cadena1: %d\n", length);
}

Salida por pantalla:


ndice del primer carcter de cadena2 incluido en cadena1: 3

10.3.9. Funciones strupr() y strlwr()


La funcin strupr() convierte toda una cadena de caracteres a maysculas,
mientras que la funcin strlwr() convierte toda una cadena de caracteres a
minsculas.

Cadenas de caracteres en C

303

Los prototipos de estas funciones son:


char *strupr(char *s);
char *strlwr(char *s);
EJEMPLO
#include <stdio.h>
#include <string.h>
void main(void) {
char string[] = "-Hola!, Cmo ests?. -Bien, gracias.";
printf("string
: %s\n", string);
strupr(string);
printf("En maysculas: %s\n", string);
strlwr(string);
printf("En minsculas: %s\n", string);
}

Salida por pantalla:


string
: -Hola!, Cmo ests?. -Bien, gracias.
En maysculas: -HOLA!, CMO ESTS?. -BIEN, GRACIAS.
En minsculas: -hola!, cmo ests?. -bien, gracias.

10.3.10. Funcin strset()


La funcin strset() carga en todos los caracteres de la cadena que recibe como
primer argumento, el carcter que se le indique como segundo argumento. El
prototipo de esta funcin es:
char *strset(char *s, int ch);
EJEMPLO
#include <stdio.h>
#include <string.h>
void main(void) {
char string[] = "123456789";
char symbol = 'c';
printf("Antes de strset() : %s\n", string);
strset(string, symbol);
printf("Despus de strset(): %s\n", string);
}

Salida por pantalla:


Antes de strset() : 123456789
Despus de strset(): ccccccccc

10.3.11. Funciones atoi(), atol() y atof()


Las funciones atoi(), atol() y atof() convierten una cadena de caracteres a
un entero, entero largo y a un real doble, respectivamente. Todas ellas se encuentran
definidas en el fichero cabecera stdlib.h.
En cualquiera de las tres funciones, el primer carcter de la cadena no permitido
para la conversin, hace finalizar sta. Adems, cualquiera de las tres funciones,
devolvern valor cero si la conversin no ha sido posible.

304

Programacin en C

10.3.12. Funcin strtok()


La funcin strtok() permite dividir una cadena en subcadenas, basndose en un
conjunto especificado de caracteres de separacin incluidos en una segunda cadena
patron. Se encuentra definida en el fichero cabecera string.h. El prototipo de
esta funcin es:
char *strtok(char *s1, char *s2);
strtok() trata la cadena s2 como el conjunto de caracteres que se utilizan de
separadores de los caracteres de la cadena s1. Busca dentro de s1 un carcter
incluido en s2, si lo encuentra, modifica s1 reemplazando el carcter encontrado por
el carcter nulo (\0, fin de cadena). Por tanto, ha partido la cadena s1 en dos,
s11, de la cual adems devuelve su direccin, y s12.
Si se vuelve a invocar a la funcin con NULL en lugar del primer parmetro,
continua buscando en la subcadena s12 una nueva aparicin de algn carcter de s2,
actuando de la misma forma descrita anteriormente. Cuando no encuentra en la
cadena de bsqueda ningn carcter de la subcadena s2, se retorna la direccin de la
propia cadena de bsqueda, pero en la siguiente llamada, al haber llegado al final de
la cadena de bsqueda, se retornar NULL.
EJEMPLO
#include <string.h>
#include <stdio.h>
void main(void) {
char input[] = "JUAN-CARLOS*BENITO*GARCIA";
char separador[] = "-*";
char *ptr = input;

printf("Cadena original: %s\n", ptr);


printf("Se divide la cadena original en subcadenas\n");
ptr = strtok(input, separador);
while (ptr) {
printf("%s\n", ptr);
ptr = strtok(NULL, separador);
}

Salida por pantalla:


Cadena original: JUAN-CARLOS*BENITO*GARCIA
Se divide la cadena original en subcadenas
JUAN
CARLOS
BENITO
GARCIA

10.3.13. Funcin strrev()


La funcin strrev() invierte el orden de los caracteres de la cadena especificada
en el argumento, y devuelve un puntero a la cadena resultante. Se encuentra definida
en el fichero cabecera string.h. Su prototipo es:
char *strrev (char *s);

Cadenas de caracteres en C

305

EJEMPLO
#include <string.h>
#include <stdio.h>
void main(void) {
char *forward = "string";
char *cadena1 = "Hola Mundo";
char *cadena2;
printf("Antes
de strrev(): %s\n", forward);
strrev(forward);
printf("Despus de strrev(): %s\n", forward);

printf("Antes
de strrev(): %s\n", cadena1);
cadena2 = strrev(cadena1);
printf("Despus de strrev(), cadena1: %s\n", cadena1);
printf("Despus de strrev(), cadena2: %s\n", cadena2);

Salida por pantalla:


Antes
Despus
Antes
Despus
Despus

de
de
de
de
de

strrev():
strrev():
strrev():
strrev(),
strrev(),

string
gnirts
Hola Mundo
cadena1: odnuM aloH
cadena2: odnuM aloH

10.3.14. Funciones is...()


Estas funciones retornarn un valor cierto (distinto de 0) si el carcter del argumento
est dentro de la categora fijada para la comparacin, y falso (0) en caso contrario.
Todas estas funciones se encuentran definidas en el fichero cabecera ctype.h.
Funcin
Retorna CIERTO si el argumento c es:
isalnum(c)
isalpha(c)
isascii(c)
iscntrl(c)

Alfanumrico (letras nmeros)


Alfabtico, mayscula minscula
Si su valor est entre el cdigo ASCII 0 y 126
Si es un carcter de control cuyo cdigo ASCII est comprendido entre 0 y
31, o si es el cdigo de delete 127
islower(c) Si es un carcter alfabtico minscula
isupper(c) Si es un carcter alfabtico mayscula
isdigit(c) Si es un dgito (entre 0 y 9)
ispunct(c) Si es un carcter de puntuacin
isspace(c) Si es el carcter espacio, tabulador, avance de lnea, retorno de carro...
isxdigit(c) Si es cdigo correspondiente a un nmero hexadecimal, es decir entre 0 - 9
A-Fa-f
Tabla 10.2. Categoras funciones is...( )

Los prototipos de las funciones as categoras para cada funcin son las siguientes:
int isalnum (int c);
int isalpha (int c);
int isascii (int c);
int iscntrl (int c);
int isdigit (int c);

306

Programacin en C

int islower (int c);


int isupper (int c);
int ispunct (int c);
int isspace (int c);
int isxdigit (int c);
Las categoras para cada funcin se muestran en la Tabla 10.2.

10.4. Cuestiones y ejercicios1


1.

Teniendo en cuenta el siguiente segmento de cdigo, indicar los errores y la


forma de corregirlos.
char *cad1 = "Buena hora";
char *cad2 = cad1;
char cad3[] = "Hora feliz";
char* cad4;
cad4 = cad3;
cad4 = "Enhorabuena";

2.
3.

Qu diferencia
existe
entre
scanf("%s",
cadena)
gets(cadena)? En qu casos ser mejor utilizar una u otra?
Qu diferencias y analogas hay entre las siguientes variables?

char **prt1;
char *prt2[55];
char *prt3[14][18];

4.

5.

6.
7.

Qu diferencias y analogas hay entre las siguientes variables? Cul de


ellas puede utilizarse como cadena de caracteres y cul slo como un vector
de caracteres?
char mensaje[21] = "Bienvenido al sistema";
char aviso[22]
= "Bienvenido al sistema";
Por qu dimensionar una cadena de caracteres con un tamao mayor del
necesario? Por ejemplo, por qu definir char mensaje[80] =
"Hola"; en lugar de char mensaje[] = "Hola";?
Si se pierde el carcter nulo del final de una cadena de caracteres, qu har
la funcin strlen()? Y la puncin puts()?
Implementa una funcin que convierta un carcter que sea un dgito
numrico a su valor numrico correspondiente, y que responda al siguiente
pseudocsigo.
funcion numero (carcter c): entero
inicio
segn sea c
'0': retorno 0
'1': retorno 1

En varios ejercicios propuestos en este captulo se solicita la implementacin de una funcin.


Si el lector todava no ha tenido contacto con las funciones, podr fcilmente adaptar el
enunciado de los ejercicios para implementar al menos un fragmento de cdigo que realice el
algoritmo solicitado, o bien previamente consultar el Captulo 11 dedicado ntegramente a las
funciones en C.

Cadenas de caracteres en C

'2': retorno
'3': retorno
'4': retorno
'5': retorno
'6': retorno
'7': retorno
'8': retorno
'9': retorno
fin segn sea
fin numero

8.

307

2
3
4
5
6
7
8
9

Implementar una funcin que convierta un dgito numrico a su valor


carcter ASCII correspondiente, y que responda al siguiente pseudocsigo.
funcion caract (entero n): carcter
inicio
Segn sea C
0: retorno '0'
1: retorno '1'
2: retorno '2'
3: retorno '3'
4: retorno '4'
5: retorno '5'
6: retorno '6'
7: retorno '7'
8: retorno '8'
9: retorno '9'
fin segn sea
fin caract

9.

Implementar una funcin que calcule la longitud de una cadena de


caracteres, y que responda al siguiente pseudocsigo.
funcion longitud (cadena c): entero
var
l: entero
inicio
l <- 0
Mientras c[l] <> '\0'
l <- l+1
fin Mientras
Retorno l
fin longitud

10.

Implementar una funcin que convierta una cadena de caracteres compuesta


por dgitos numricos, a su valor numrico correspondiente, y que responda
al siguiente pseudocsigo.
funcion valor (cadena c): entero
var
cif, num, i: entero
inicio
cif <- longitud (c)
Si cif > 0
entonces num <- 0
Desde i=0 hasta cif-1
num <- num + numero(c[i]*10^(cif-i-1)
fin Desde

308

Programacin en C

retorno (num)
Sino retorno (-1)
fin si
fin valor

11.

Implementar una funcin que responda al siguiente prototipo y que


reemplace en la cadena del argumento cad todas las apariciones del carcter
cb por el carcter cr.

12.

Implementar una funcin que responda al siguiente prototipo y que elimine


los espacios por la izquierda de la cadena del argumento, generando una
cadena por tanto de longitud menor a la recibida, o como mucho igual, en
caso de que la cadena recibida no contenga espacios por la izquierda.

13.

Implementar una funcin que responda al siguiente prototipo y que convierta


todas las primeras letras de las palabras que forman la cadena del argumento
a maysculas.

14.

Implementa una funcin que responda al siguiente prototipo y que compare


dos cadenas y devuelve 1 si son iguales y 0 si no lo son.

void reemplazar (char *cad, char cb, char cr)

void recortarizq (char *cad)

void nombrepropio (char *cad)

int cadenasiguales (char *cad1, char *cad2, int metodo)

15.

El argumento metodo indica el mtodo de comparacin. Los posibles


valores sern:
0. la comparacin de cadenas diferenciar maysculas de minsculas
con lo que, por ejemplo, una M mayscula no coincidir con una m
minscula.
1. la comparacin de cadenas no diferenciar maysculas de minsculas
con lo que, por ejemplo, una M mayscula coincidir con una m
minscula.
Cualquier otro valor se comportar como 0.
Implementar una funcin que responda al siguiente prototipo y que devuelva
editada la fecha que se le pasa como argumento en formato AAAAMMDD.
void editafecha (char *fecha, long f, int tipo)

El tipo de edicin depende del parmetro tipo. Por ejemplo, si f recibe el


valor 19960412...
tipo 0. Salida: 12/04/96
tipo 1. Salida: 12/04/1996
tipo 2. Salida: 12/04/1.996
tipo 3. Salida: 12-04-96
tipo 4. Salida: 12-04-1996
tipo 5. Salida: 12-04-1.996
tipo 6. Salida: 12-Abr-96
tipo 7. Salida: 12 de Abril de 1.996
tipo 8. Salida: 12, Abril 1.996
Cualquier otro valor de tipo no especificado en esta relacin se comportar
como el caso 0.

11. Funciones en C
Las aplicaciones informticas que habitualmente se utilizan, incluso en el mbito de la
informtica personal, suelen contener decenas y an cientos de miles de lneas de
cdigo fuente. A medida que los programas se van desarrollando y aumentan de
tamao, se convertiran rpidamente en sistemas poco manejables si no fuera por la
modularizacin, que es el proceso consistente en dividir un programa muy grande en
una serie de mdulos mucho ms pequeos y manejables.
A estos mdulos se les suele denominar de distintas formas (subprogramas,
subrutinas, procedimientos, funciones...) segn los distintos lenguajes. El lenguaje C
utiliza el concepto de funcin.
Sea cual sea la nomenclatura, la idea es siempre la misma: dividir un programa
grande en un conjunto de subprogramas o funciones ms pequeas que son llamadas
por el programa principal; stas a su vez llaman a otras funciones ms especficas y
as sucesivamente.
La divisin de un programa en unidades ms pequeas o funciones presenta, entre
otras, las ventajas siguientes:
Modularizacin. Cada funcin tiene un propsito nico e identificable, de
modo que no tiene un nmero de lneas excesivo y siempre se mantiene dentro
de un tamao manejable.
Claridad lgica de los programas. Como resultado de la divisin del
programa en varios mdulos (funciones) concisos ms pequeos, donde cada
mdulo representa alguna parte bien definida del problema global.
Independencia de datos y ocultamiento de informacin. Es muy frecuente
que al hacer una modificacin en un programa para aadir funcionalidad o
corregir un error, se introduzcan nuevos errores en partes del programa que
antes funcionaban correctamente. Una funcin es capaz de mantener una gran
independencia con el resto del programa, manteniendo sus propios datos y no
permitiendo ninguna posibilidad de acceso a su informacin por parte de otros
componentes del programa.
Cdigo no redundante. Evita la necesidad de repetir las mismas instrucciones
de forma redundante en distintas partes del programa. stas se incluirn en una
funcin y se llamar a la misma cuando sea necesario. Incluso podr ser
utilizada por otros programas.
Ahorro en tiempo de desarrollo. Cada funcin puede desarrollarse y
depurarse por separado, de forma independiente por programadores diferentes.
Creacin de bibliotecas personalizadas. Permite la creacin de bibliotecas
de funciones a medida del programador, pudiendo as reutilizar cdigo ya
probado y verificado en el desarrollo de nuevos programas.
En este captulo se van a estudiar los fundamentos de las funciones segn la
perspectiva del lenguaje de programacin C, para lo cual ste se ha divido en nueve
secciones. La primera explica cmo se utilizan las funciones, distinguiendo tres fases
en su empleo: declaracin, definicin y llamada. La segunda seccin explica cmo el
uso de funciones obliga a distinguir varios tipos de variables. La tercera seccin se
centra en explicar los argumentos de una funcin, diferencindose los argumentos que
- 309 -

310

Programacin en C

espera una funcin (parmetros formales) de los que se le pasan en el momento de


hacer una llamada a dicha funcin (parmetros reales). La siguiente seccin introduce
un problema derivado del mal uso de las funciones y que por tanto hay que evitar: los
efectos laterales. La seccin quinta se dedica a un aspecto capital en el uso de las
funciones en cualquier lenguaje, y por tanto en C, esto es, cmo se produce el paso de
parmetros a una funcin. En la sexta seccin se repasa un concepto ntimamente
relacionado con el uso de las funciones: la recursividad. La sptima seccin estudia la
posibilidad que ofrece C para tener punteros a funciones. En la siguiente seccin, est
dedicada a una funcin nica que est presente en todo programa en C: la funcin
main() o funcin principal; en concreto, se considera el uso de argumentos en sta.
Finalmente, la novena seccin presenta una lista de cuestiones y ejercicios de repaso.

11.1. Empleo de funciones


Una funcin de C es un mdulo o segmento independiente de cdigo fuente,
diseado para realizar una tarea especfica. Toda funcin lleva asociado un
identificador o nombre que se utiliza para referirse a la misma desde el resto del
programa.
Una funcin puede utilizarse en distintos puntos de un programa, envindole unos
parmetros de entrada (cuando estos son necesarios), para que realice su tarea
especfica y en la mayora de los casos tambin proporcione un resultado.
El lenguaje C se fundamenta en gran medida en el uso de funciones, como se
puede inferir de las siguientes propiedades del mismo:
Todo programa en C incluye al menos una funcin, la funcin principal
main().
Un programa C hace uso de funciones de biblioteca (aportada por el propio
compilador) para realizar un cierto nmero de clculos u operaciones de uso
comn, como por ejemplo printf(), scanf(), sqrt()...
El lenguaje C permite al programador construir sus propias funciones para
modularizar el programa y realizar tareas no resueltas por las funciones de
biblioteca.
En el empleo de funciones se diferencian tres conceptos importantes: la definicin
de la funcin, la declaracin de la funcin y la llamada a la funcin.

11.1.1. Definicin de las funciones


La definicin de una funcin no es ms que el conjunto de sentencias necesarias para
que la funcin pueda realizar su tarea cuando sea llamada.
La definicin de la funcin, adems del cdigo que realiza la tarea concreta,
incluye la especificacin del tipo del valor de retorno de la funcin y de cada uno de
los argumentos.
Una funcin se define una sola vez en cualquier punto del programa, pero nunca
dentro de otra funcin, ni siquiera dentro de la funcin main().

Funciones en C

311

Si un programa incluye varias funciones, sus definiciones pueden aparecer en


cualquier orden, pero han de ser independientes unas de otras.
El formato genrico para la definicin de una funcin se puede apreciar en la
Figura 11.1.
Cabecera
de la funcin

tipo_r

nombre

(tipo1 par1, ..., tipon parn)

{
declaracin de las variables locales;
Cuerpo de la funcin

Definicin
de la funcin

Figura 11.1. Formato genrico empleado en la definicin de funciones en C

La cabecera de la funcin incluye la siguiente informacin:


tipo_r: Es el tipo de dato del valor de retorno de la funcin. Para funciones
que no devuelven ningn resultado, simplemente ejecutan una accin, este tipo
de dato ser void. Si se omite este tipo, el compilador asume que el valor
retornado por la funcin es de tipo int.
nombre: Es el identificador elegido por el programador para dar nombre a la
funcin.
tipon parn: Es el tipo y nombre de cada parmetro formal (tambin
llamado argumento) de la funcin, los cuales representan los nombres de los
elementos que se transfieren a la funcin desde la parte del programa que hace
la llamada.
La declaracin de los parmetros formales es una lista de tipos y nombres de
variables separadas por comas.
int f(int x, int y, float z)

correcto

int f(int x,y, float z)

incorrecto
Si la funcin no requiere parmetros, se puede colocar entre los parntesis el
tipo de dato void.
Los nombres de los argumentos no pueden coincidir entre s, ni tampoco con
el nombre de las variables o constantes definidas dentro de la funcin.
El cuerpo de la funcin incluye la siguiente informacin:
Declaraciones locales: Es el conjunto de definiciones y declaraciones de datos
que la funcin necesita utilizar para la realizacin de la tarea especfica. Tanto
los parmetros o argumentos de la cabecera de la funcin, como para las
variables y constantes que se definen dentro de la funcin, son locales: no son
reconocidos fuera de la funcin (este concepto de localidad se explica en la
seccin 11.2). No podrn ser utilizados ms que por las sentencias o
instrucciones incluidas en el cuerpo de la funcin.

312

Programacin en C

Cuerpo: Es el conjunto de sentencias que se ejecutan cada vez que la funcin


es llamada y que realizan la tarea especfica para la que la funcin ha sido
diseada. En otras palabras: lo que hace la funcin.
11.1.1.1. Cmo devuelve una funcin el resultado calculado
Existen dos formas de finalizar la ejecucin de una funcin y volver al cdigo que
realiz la llamada:
1. Ejecutando la ltima sentencia de la funcin, es decir, llegando al final de la
funcin, representado por la llave de cierre (}).
2. Mediante la sentencia return. Esta sentencia, utilizada sin ningn valor
asociado, fuerza la salida de la funcin y la vuelta al cdigo que realiz la
llamada, sin necesidad de llegar al final del cdigo de la funcin.
La sentencia return tambin se usa para poder devolver desde la funcin un
determinado valor. En ese caso la palabra return1 debe ir seguido de una constante,
variable o expresin que ser evaluada, y su valor resultante es el que ser devuelto al
mdulo que realiz la llamada como valor de retorno. Evidentemente el tipo del dato
que se devuelve con return debe coincidir con el tipo de la funcin en su
definicin.
Una funcin slo puede devolver un nico valor. Dentro de una funcin pueden
existir varias sentencia return, pero en cada llamada a la funcin slo se ejecutar
una de ellas (ya que ste ser el punto de salida), devolviendo nicamente un valor.
En principio puede parecer muy limitante el hecho de devolver un nico valor.
Adems, con la sentencia return no se puede devolver un vector o una matriz,
aunque s un puntero a un vector o a una matriz. Sin embargo, el valor de retorno s
puede ser una estructura, que a su vez puede contener vectores o matrices como
elementos miembros.
EJEMPLO

Sea la definicin de la funcin mayus():


char mayus (char cin) {
/* Convierte a maysculas el carcter recibido */
char csal;
if (cin >= 'a' && cin <= 'z')
csal = cin - 32 ;
else
if (cin == '')
csal = ''
else
csal = cin;
return (csal);
}

La cabecera de la funcin es:


char mayus (char cin)

El tipo de retorno de la funcin es:


char
1

Opcionalmente, se pueden utilizar parntesis para englobar a la expresin que sigue a


return.

Funciones en C

313

La lista de parmetros formales est formado por un solo representante:


char cin

Las declaraciones locales son:


char csal;

El cuerpo de la funcin es:


if (cin >= 'a'
csal = cin
else
if (cin ==
csal =
else
csal =
return (csal);

&& cin <= 'z')


- 32 ;
'')
''
cin;

EJEMPLO

Otra forma de definir la funcin del ltimo ejemplo, equivalente a la anterior


en cuanto al valor devuelto, es la siguiente. Obsrvese que se utilizan tres
sentencias return, pero en cualquier caso slo se ejecutar una de ellas.
char mayus (char cin) {
/* Convierte a maysculas el carcter recibido */
if (cin >= 'a' && cin <= 'z')
return (cin 32);
else
if (cin == '')
return ('')
else
return (cin);
}

11.1.2. Declaracin de las funciones


La declaracin o prototipo de la funcin es una sentencia que es igual a la cabecera
de la definicin de la funcin.
El prototipo de una funcin en C tiene la siguiente forma:
tipo_r nombre (tipo1 par1, ... , tipon parn);
No es imprescindible escribir el nombre de los parmetros, slo el tipo, por lo que
normalmente nunca se hace, quedando as el prototipo como sigue:
tipo_r nombre (tipo1, ... , tipon);
Al igual que ocurre con las variables, toda funcin debe ser declarada
explcitamente antes de ser invocada dentro del programa. Esta declaracin se hace
mediante el prototipo de la funcin, que normalmente se coloca al principio del
programa (despus de las directivas del preprocesador: #define, #include...), o
en un archivo de cabecera2.

En muchos casos, particularmente en programas grandes, con muchas funciones repartidas en


mltiples ficheros fuente, se puede crear un fichero de cabecera (con extensin .h) con todos
los prototipos de las funciones utilizadas en un programa, e incluirlo con una directiva
#include en todos los ficheros donde se utilicen dichas funciones.

314

Programacin en C

El uso de prototipos en el lenguaje C fue introducido por el estndar ANSI. El


compilador, basndose en el prototipo de una funcin, realiza dos tareas primordiales
cuando encuentra la llamada a una funcin, antes de encontrar su definicin:
1. Identifica el tipo que devuelve la funcin.
2. Identifica el tipo y nmero de argumentos que emplea la funcin.
Si la definicin de la funcin se hace en el programa antes de la llamada a la
misma, no es necesario declarar la funcin, ya que la propia definicin sirve de
declaracin.
EJEMPLO

El prototipo de la funcin mayus(), vista en un ejemplo anterior, es:


char mayus (char cin);

O simplemente
char mayus (char);
EJEMPLO

Ejemplos de prototipos legales pueden ser:


void presentacion(void);
float calc_valor (int x, float y);
float calc_valor (int, float);
float calc_media (float sum, int elem);
float calc_media (float, int);
EJEMPLO

El siguiente prototipo es ilegal porque no se puede utilizar un nico


especificador de tipo para varios parmetros.
int fun (float x, y);

11.1.3. Llamada a una funcin


La llamada a una funcin se realiza incluyendo en una sentencia o expresin de un
mdulo el nombre de la funcin seguido de la lista de parmetros actuales o reales,
separados por comas y entre parntesis.
En la llamada a una funcin, los parntesis despus del nombre de la funcin son
obligatorios. Si la funcin est definida sin parmetros, la llamada a la misma se har
con el nombre de la funcin seguido de un par de parntesis vacos. Adems, el
nmero de parmetros actuales en la llamada debe coincidir el con nmero de los
parmetros formales en la definicin y en la declaracin.
Estos parmetros actuales o reales son los datos que, desde el lugar de invocacin,
se envan a la funcin para que sta realice su proceso.
Si la funcin no devuelve nada (el tipo es void), la llamada a la funcin
comprende nicamente el nombre de la misma y la lista de parmetros actuales. Pero
si la funcin retorna un valor, la llamada a la misma debera estar incluida en una
expresin que recoja el valor retornado (siempre y cuando interese recoger dicho
valor de retorno, aunque no es obligatorio).
EJEMPLO

Llamadas a las funciones declaradas en los ejemplos anteriores.


letra_mayuscula = mayus ('m');

Funciones en C

315

printf("La letra en maysculas es %c\n", mayus (letra));


valor_final = calc_valor (7, numf);
media = calc_media (suma_total, numero_elementos);
presentacion();
EJEMPLO

Programa que utiliza funciones creadas por el programador. En este


programa es necesario especificar los prototipos de las funciones porque la
llamada a las funciones se encuentra antes que su definicin.
#include <stdio.h>
/* ---- Prototipos ----------- */
void presentacion (void);
float area_circulo (float);
float volumen_esfera (float);
void main(void) {
float radio;
float area_c;
float volumen_e;
presentacion();
printf("\nIntroduzca el valor del radio: ");
scanf ("%f", &radio);
area_c = area_circulo (radio);
volumen_e = volumen_esfera (radio);
printf("\nArea del circulo %g\n", area_c);
printf("\Volumen de la esfera %g\n", volumen_e);
printf("\n\n");
}
void presentacion (void) {
/* Realiza la presentacin del programa
printf("\nSe calcularn el rea del crculo ");
printf("y el volumen de la esfera\n");
printf("cuyo radio se pedir por teclado.\n");
printf("\n\n");
}
float area_circulo (float rd) {
/* Calcula el rea del circulo cuyo radio se obtiene
/* como parmetro
*/
const float PI = 3.14159;
float resultado;
resultado = PI * rd * rd;
return (resultado);
}

*/

*/

float volumen_esfera (float rd) {


/* Calcula el volumen de la esfera cuyo radio se obtiene */
/* como parmetro
*/
const float PI = 3.14159;
return ((4.0/3.0) * PI * rd * rd * rd);
}

316

Programacin en C

11.2. Tipos de variables segn su lugar de definicin


Dependiendo del lugar en el que se definan las variables en un programa C, se
distingue entre variables globales, variables locales y parmetros formales. La
diferencia entre estos tipos est tanto en el mbito o alcance de la variables como en
tiempo de almacenamiento.
Se entiende por mbito o alcance de una variable la parte de cdigo del programa
donde la variable es conocida y por tanto accesible. Por su parte, el tiempo de
almacenamiento de una variable es el tiempo de vida o duracin en memoria de la
variable durante la ejecucin del programa.
Las variables locales son aqullas que se definen dentro de una funcin, ya sea la
funcin main() o cualquiera otra definida por el programador.
Se podra generalizar ms an para entender que las variables locales son las que se
declaran dentro de un bloque, entendindose por bloque el cdigo que est encerrado
entre dos llaves. Segn esto, el mbito de una variable local es la funcin (o bloque)
dentro de la que est definida, mientras que su tiempo de almacenamiento abarca
desde el comienzo hasta el final de la ejecucin de la funcin (o bloque) en la que se
ha definido. Es decir, la variable local se crea en la pila al entrar en la funcin (o
bloque) y se destruye al finalizar la funcin (o bloque).
Las variables globales son aqullas que se definen al principio del programa fuera
de cualquier funcin, o de cualquier bloque de cdigo.
Segn esto se puede pensar que, en principio, el mbito de una variable global es
todo el programa, mientras que su tiempo de almacenamiento abarca desde su
definicin hasta el final de la ejecucin del programa. Es decir, la variable global se
crea al declararla y se destruye al terminar la ejecucin del mismo. No obstante, se
debe tener en cuenta que si el programa consta de varios ficheros fuente, habr que
indicar el hecho de que una variable sea global a todos ellos, utilizando para ello un
especificador de clase de almacenamiento adecuado, como se mostrar
posteriormente.
Las variables globales deben evitarse porque toda funcin que utilice una variable
global pierde generalidad y transportabilidad, ya que dependera de la existencia de
esa variable global.
Los parmetros formales son las variables locales que sirven de enlace entre la
funcin que llama y la funcin llamada. Se definen en la cabecera de definicin de
una funcin. Su mbito es la funcin dentro de la que est definida (igual que las
variables locales), mientras que su tiempo de almacenamiento abarca desde el
comienzo hasta el final de la ejecucin de la funcin en la que se han definido.
Los parmetros formales se comportan a todos los efectos como otra variable local.
EJEMPLO

A continuacin se esquematiza un programa con dos funciones (main() y


funcionA()), mostrndose el mbito para las variables y funciones.

Funciones en C

Figura 11.2. mbito para variables y funciones


EJEMPLO

Dado el siguiente programa:


#include <stdio.h>
int x, y;
void f1(void);
int f2(int, int);
void f1(void) {
int x1, y1;
x1 = 101;
y1 = 102;
printf("\nFuncin f1.
} /* fin funcin f1 */
int f2(int a, int b) {
int res;
res = a + b;
printf("\nFuncin f2.
res);
return (res);
} /* fin funcin f2 */

x1=%d, y1=%d", x1, y1);

a=%d, b=%d, res=%d", a, b,

void main(void) {/* Funcin principal */


int suma;
x = 1;
y = 2;
printf("\nFuncin main. x=%d, y=%d", x, y);
f1();
suma = f2(x, y);
printf("\nVolvemos a la funcin main. );
printf("x=%d, y=%d, suma=%d", x, y, suma);
}

317

318

Programacin en C

De acuerdo a la definicin de variables y funciones en el mismo, se pueden


hacer las siguientes afirmaciones:
1. x e y son variables globales, accesibles desde todas las partes del
programa, es decir, desde las funciones f1(), f2() y main().
2. Las variables x1 e y1 son locales a la funcin f1(), y slo puede
ser utilizada dentro de ella. No pueden por tanto utilizarse desde
sentencias incluidas en la funcin f2() ni en la funcin main().
3. Las variables a y b son locales a la funcin f2(), por lo que slo
pueden ser utilizadas dentro de esta funcin. No pueden por tanto
utilizarse desde sentencias incluidas en la funcin f1() ni en la
funcin main().
4. La sentencia x = a sera vlida en la funcin f2().
5. La sentencia b = x1 no sera vlida en la funcin f1(), pues b es
una variable local de la funcin f2(), por lo que no es posible
utilizarla desde la funcin f1().
6. Las variables a y b no son visibles en la funcin main().
7. La sentencia y = y1 en la funcin f1() asignara el valor de y1
a la variable global y, quedando modificada la variable global y
desde dentro de la funcin f1().
8. Desde la funcin main() nunca se podr acceder al valor de las
variables a, b y res de la funcin f2().
9. La variable suma slo puede utilizarse dentro de la funcin
main(), por ser local a sta, pero no desde las funciones f1() y
f2().
10. Son vlidas las siguientes sentencias dentro de la funcin
main()?
x = a;
NO, a es local a f2().
y = x;
SI, ambas son variables globales.
suma = x1; NO, x1 es local a f1().
11. Son vlidas las siguientes sentencias dentro de la funcin f1()?
x = a;
NO, a es local a f2().
y1 = x;
SI, y1 es local a f1() y x es global.
suma = x1; NO, suma es local a main().
12. Son vlidas las siguientes sentencias dentro de la funcin f2()?
x = a;
SI, a es local a f2() y x es
global.
suma = a + b;
NO, suma es local a main().
res = x1;
NO, x1 es local a f1().

11.2.1. Especificadores de clase de almacenamiento


Los especificadores de clase de almacenamiento indican al compilador cmo debe
almacenar la variable que le sigue. En el lenguaje C se pueden especificar variables de
cuatro tipos de almacenamiento:

Funciones en C

319

Externa.
Registro.
Esttica.
Automtica.
El especificador precede al resto de la declaracin de la variable, siendo su formato
general:
especificador tipo identificador;
11.2.1.1. Variables externas
El especificador extern permite que todos los archivos que forman un programa C
conozcan las variables globales requeridas por el programa. Esto es as porque las
variables globales slo se pueden declarar una vez; as si se declaran dos variables
globales con el mismo nombre en el mismo archivo, el compilador informar del
error. El mismo tipo de problema ocurrira si se declaran todas las variables globales
en cada archivo. En este caso el compilador no dara ningn mensaje de error en
tiempo de compilacin, pero el problema surgira en el momento del enlazado, y el
enlazador emitira un mensaje porque no sabra que variable usar. La solucin a este
problema es definir todas las variables globales en un archivo y usar declaraciones
extern en el resto de los archivos.
As el especificador extern permite que el compilador conozca los tipos y
nombres de las variables globales sin crear un nuevo almacenamiento para ellas, de
forma que cuando el linker enlaza los mdulos se resuelven todas las referencias a las
variables externas.
EJEMPLO

El siguiente esqueleto de programa C cuenta con dos archivos, en el primero


se encuentran las definiciones de las variables globales, mientras que en el
segundo dichas variables aparecen como declaraciones extern.
Archivo 1
Archivo 2
int i;
float x;
void main () {
...
}
void f1() {
printf(%d %f, i, x);
}

extern int i;
extern float x;
void f2() {
i = (int) x;
}

11.2.1.2. Variables registro


El especificador register solicita al compilador que mantenga el valor de la
variable declarada de forma que se permita el acceso ms rpido a la misma. Para
enteros y caracteres esto normalmente significa colocarla en un registro de la Unidad
Central de Proceso en lugar de en memoria. El compilador puede ignorar la peticin
por completo.
Slo se puede aplicar este especificador a variables locales y parmetros formales
de una funcin.

320

Programacin en C

EJEMPLO

El siguiente ejemplo presenta una funcin que calcula la potencia entera,


declarando un parmetro formal y una variable local como register.
int potencia (int mantisa, register int exponente) {
register int tmp = 1;
for ( ; exponente>1 ; exponente--) tmp *= mantisa;
return tmp;
}

11.2.1.3. Variables estticas


Las variables declaradas como estticas mediante el especificador static son
variables permanentes. Difieren de las variables globales y locales que no son
conocidas fuera de su archivo o funcin respectivamente. Adems, las variables
estticas mantienen sus valores entre llamadas.
Las variables estticas tienen efectos diferentes si son locales que si son globales.
Cuando se aplica el especificador static a una variable local, el compilador crea
una almacenamiento permanente para ella, de forma similar a cuando hace lo propio
para una variable global. La diferencia fundamental entre una variable local esttica y
una variable global es que la variable local esttica slo se va a conocer en el bloque
en el que est declarada. Adems, la variable local esttica retiene el valor entre
llamadas de funciones.
Si las variables estticas no se inician, la primera vez que se invoca a la funcin se
inician implcitamente a 0. Si las variables estticas se inician, la iniciacin slo se
produce en la primera llamada a la funcin.
EJEMPLO

Sea el siguiente programa C:


#include <stdio.h>
void f();
void main() {
int i;
for (i=0;i<10;i++)
f();
}
void f() {
static int j;
printf ("%d ", j);
j++;
}

La salida en pantalla es:


0 1 2 3 4 5 6 7 8 9

EJEMPLO

Sea el siguiente programa C:


#include <stdio.h>
void f();
void main() {
int i;
for (i=0;i<10;i++)
f();
}

Funciones en C

321

void f() {
static int j=10;
printf ("%d ", j);
j++;
}

La salida en pantalla es:


10 11 12 13 14 15 16 17 18 19

Cuando se aplica el especificador static a una variable global, se est indicando


al compilador que cree una variable global conocida nicamente en el archivo en el
que se declara la variable global esttica.
Esto significa que, aunque la variable es global, las rutinas de otros archivos no la
reconocern ni alterarn su contenido directamente.
11.2.1.4. Variables automticas
Por defecto, son todas las variables en una funcin cuando no se utiliza ningn
especificador de clase de almacenamiento. Si se desea hacer explcitamente se puede
utilizar la palabra reservada auto.

11.3. Parmetros formales y parmetros reales


Los parmetros formales representan los nombres de los datos que se transfieren a la
funcin desde la parte del programa que la invoca. Sirven para indicar las
instrucciones del algoritmo que realiza la funcin, es decir, qu har con los datos que
le lleguen cuando sea llamada.
Cuando desde una parte del programa se hace la llamada a la funcin, se le deben
pasar unos valores reales para que la funcin trabaje con ellos, son los parmetros
reales. El valor de estos parmetros reales en la llamada se copia sobre los parmetros
formales de la funcin (uno a uno, en orden), y la funcin se ejecuta con un valor
concreto y definido en sus parmetros formales.
sum = suma_impares (num1, num2);
long suma_impares (int lim_a, int lim_b) {
...
El nmero de los parmetros reales debe coincidir con el nmero de los parmetros
formales. Adems, se cumple que los parmetros formales de distintas funciones
pueden tener el mismo nombre, ya que por ser variables locales, cada funcin slo
puede acceder a los suyos.
EJEMPLO

Se presenta una funcin para calcular la suma de todos los nmeros enteros
impares comprendidos en un intervalo. La funcin recibe dos parmetros de
tipo int que marcan respectivamente el inicio y final del intervalo, y
devuelve un long con la suma calculada.

322

Programacin en C

Definicin de la funcin: La funcin se define utilizando los parmetros


formales para representar los elementos que se transfieren a la funcin desde
la parte del programa que hace la llamada. En el ejemplo lim_a y lim_b.
long suma_impares (int lim_a, int lim_b) {
int i;
long suma = 0;
for (i=lim_a; i <= lim_b, i++)
if (i % 2)
suma += i;
return (suma);
}

Llamada de la funcin: A la funcin de la invoca con los parmetros reales,


valores que se transfieren a la funcin para que sta realice su tarea
especfica. Ejemplos de llamadas a la funcin pueden ser:
sum = suma_impares (num1, num2);
sum = suma_impares (37897, 123456);

Cuando el programa que llama encuentra el nombre de la funcin, evala los


parmetros reales contenidos en la llamada y pasa copias de dichos valores a la
funcin (la cual los recibe sobre sus parmetros formales) junto con el control de la
ejecucin. Si es necesario se realizar una conversin del tipo de los parmetros reales
al de los parmetros formales de la funcin.
Posteriormente, se ejecuta el cdigo correspondiente a la funcin hasta que se
llegue a una sentencia return o al final del cuerpo de la funcin, y entonces se
devuelve el control al programa que realiz la llamada, junto con el valor de retorno
que proporcione la sentencia return dentro de la funcin (si es que existe sta),
convirtindolo previamente al tipo de retorno especificado en el prototipo de la
funcin, si fuera necesario.
La llamada a una funcin puede hacerse de muchas formas (independientemente de
que sea intrnseca al lenguaje o definida por el programador), segn sea la clase de
tarea que realice la funcin.
Si su papel fundamental es calcular un valor de retorno a partir de uno o ms
argumentos, podr ser llamada incluyendo su nombre y entre parntesis los
parmetros reales, recibiendo el valor de retorno el mdulo que la ha invocado
en una variable. El siguiente ejemplo llama a la funcin intrnseca para el
clculo de seno de un ngulo.
seno_a = sin(alpha);
Pero tambin es posible que sea llamada incluyndola en una expresin
aritmtica o de otro tipo. En este caso, la llamada a la funcin hace el papel de
un operando ms de la expresin:
a = d * sin(alpha) / 2.0;
En otros casos, no existir valor de retorno y la llamada a la funcin se har
incluyendo en el programa una sentencia que contenga solamente el nombre
de la funcin, siempre seguido por los argumentos reales entre parntesis y
terminando con un carcter (;). Por ejemplo, la siguiente sentencia llama la
funcin intrnseca de terminacin anormal del programa. Obsrvese que en
este caso no hay valor de retorno, ya que la funcin no realiza ningn clculo.
abort();
Todo programa C, por pequeo que sea, tiene al menos una funcin: main().La

Funciones en C

323

funcin main() marca el punto de comienzo de la ejecucin del programa, por lo


que tambin se la suele llamar programa principal.
A todos los efectos, la funcin main() tiene las mismas limitaciones y
caractersticas que cualquier funcin que defina el programador. Por ejemplo, las
variables y constantes declaradas en el cuerpo de main(), al ser locales a sta, no
son accesibles desde otras funciones, incluso aunque stas estn definidas dentro del
mismo fichero fuente.
EJEMPLO

A continuacin, se presenta el esquema de ejecucin de un programa C que


usa funciones declaradas por el usuario.
#include <stdio.h>
/* ---- Prototipos ----------- */
void presentacion (void);
float area_circulo (float);
float volumen_esfera (float);

No existen variables globales

void main(void) {
float radio;
A float area_c;
float volumen_e;

Variables locales a main()

presentacion();

Retorno al punto
de llamada

void presentacion (void) {


printf("\nSe calcularn el rea del crculo ");
printf("y el volumen de la esfera\n");
printf("cuyo radio se pedir por teclado.\n");
printf("\n\n");
}

printf("\nIntroduzca el valor del radio: ");


scanf ("%f", &radio);

area_c = area_circulo ( radio );

Retorno al
punto de llamada

float area_circulo (float rd) {


const float PI = 3.14159;
float resultado;

Variables y
constantes locales

resultado = PI * rd * rd;
return (resultado);
}

volumen_e = volumen_esfera (radio);

Retorno al punto
de llamada

float volumen_esfera (float rd) {


const float PI = 3.14159;

G
}

return ( (4.0/3.0) * PI
* rd * rd * rd );

printf("\nArea del circulo %g\n", area_c);


printf("\Volumen de la esfera %g\n", volumen_e);

printf("\n\n");
}

324

Programacin en C

Se describen los principales puntos del programa anterior:


A. Funcin main(), es el punto de inicio de la ejecucin del
programa.
B. Llamada a funcin presentacion(), sin parmetros. Se ejecuta
el cuerpo de la funcin.
C. Al finalizar la ejecucin de la funcin presentacion() se
retorna al punto de llamada, ejecutndose la siguiente sentencia de
la funcin main(). No se recoge ningn valor retornado por la
funcin, ya que sta no devuelve ningn resultado.
D. Llamada a la funcin area_circulo(), pasndole como
parmetro real la variable radio. El valor de esta variable es
cargado en el parmetro formal rd y se ejecuta el cuerpo de la
funcin.
E. Al finalizar la funcin area_circulo() retorna un valor float
que se recoge en la variable area_c, y se contina con la siguiente
sentencia de la funcin main().
F. Llamada a la funcin volumen_esfera(), pasndole como
parmetro real la variable radio. El valor de esta variable es
cargado en el parmetro formal rd y se ejecuta el cuerpo de la
funcin.
G. Al finalizar la funcin volumen_esfera() retorna un valor
float que se recoge en la variable volumen_e, y se contina con
la siguiente sentencia de la funcin main().
H. Final de la funcin main() y punto de finalizacin del programa.

11.4. Efectos laterales en funciones


Una funcin toma los valores de los parmetros que recibe y devuelve un nico valor
de retorno. Es decir, de forma general, la comunicacin entre el mdulo que invoca y
la funcin invocada ser a travs de la interfaz declarada en la cabecera de la funcin:
los parmetros de entrada y el valor de retorno de la funcin.
Sin embargo, una funcin puede cambiar el valor de una variable global y ejecutar
instrucciones de entrada/salida (escribir un mensaje en pantalla, leer el valor de una
variable global desde teclado...). Estas operaciones se conocen como efectos
laterales. Sern acciones realizadas dentro de la funcin que repercuten en otros
mdulos del programa, y que no son encaminadas a travs de la interfaz, tal y como
se muestra en la Figura 11.3.
Los efectos laterales estn considerados, normalmente, como una mala tcnica de
programacin. En las funciones que defina el programador, normalmente:
Nunca debern hacerse referencia a variables globales del programa, de
forma que las variables que necesite conocer la funcin del mdulo que la
invoca debern ser pasadas como parmetros, y el resto ser declaradas locales
a la propia funcin.
Nunca debern modificarse variables globales del programa.

Funciones en C

325

Figura 11.3. Efectos laterales

Las funciones creadas sin efectos laterales se comporta como cajas negras 3
totalmente exportables a otros programas, con slo conocer el rea de interfaz: los
parmetros con que hay que llamarlas y el tipo de dato del valor de retorno.
El mdulo que invoca slo espera de una funcin que le resuelva una tarea y, si es
el caso, que le proporcione un resultado que deber ser recogido por el propio mdulo
que origin la llamada.
Desde el punto de vista de la propia funcin, sta no es ms que un conjunto de
instrucciones que se ejecutarn cuando la funcin sea llamada, y reciba sobre sus
parmetros formales los valores que se le han pasado desde el mdulo que la
invoc. La funcin devolver el valor resultado de su clculo a travs del valor de
retorno, el cual deber ser recogido por el mdulo que hizo la llamada.
Todas las funciones que se codifiquen de acuerdo a las anteriores normas podrn
engrosar una biblioteca propia de funciones del programador, que completarn a las
intrnsecas del lenguaje, de forma que con el paso del tiempo programar ser mucho
ms fcil, rpido y seguro, pues se estar utilizando cdigo ya probado con un
comportamiento conocido.
Por tanto, la interfaz es la forma natural y directa de comunicacin entre una
funcin y el mdulo que la llama. Es la forma que tiene la funcin de recibir del
exterior informacin con la que realizar su tarea especfica, para la que ha sido
diseada, y de devolver el valor de retorno calculado.
EJEMPLO

Programa realizado slo con variables globales. Implica un malsimo estilo


de programacin. La funcin no es fcilmente exportable a otros programas.

Mdulos independientes de los que el programa principal no conoce su contenido, slo lo que
hacen. Estos mdulos independientes pueden ser probados y depurados por s solos, lo que hace
mucho ms fcil acotar e identificar posibles errores de funcionamiento de los programas.

326

Programacin en C

#include <stdio.h>
void calcular_factorial (void); /* Prototipos */
int numero;
long factorial;
int i;

Variables globales a todo el programa.


Estn declaradas fuera de toda funcin.

void main(void) {
printf("\nIntroduzca un nmero entero para calcular su
factorial: ");
scanf ("%d", &numero);
calcular_factorial();
printf("\nEl factorial de %d es %ld\n, numero,
factorial);
}
void calcular_factorial (void) {
factorial = 1;
for (i=1; i<=numero; i++)
factorial *= i;
}

La funcin no recibe parmetros ni


devuelve ningn resultado ya que
utiliza directamente las variables
globales.

El mismo programa utilizando variables locales y parmetros en la llamada a


la funcin. La funcin es exportable a otros programas.
#include <stdio.h>
/* Prototipos */
long calcular_factorial (int);
void main(void) {
int numero;
long factorial;
printf("\nIntroduzca un nmero entero para calcular su
factorial: ");
scanf ("%d", &numero);
factorial = calcular_factorial (numero);
printf("\nEl factorial de %d es %ld\n, numero,
factorial);
}
long calcular_factorial (int num) {
int i;
long resultado = 1;
for (i=1; i<=num; i++)
resultado *= i;
return(resultado);
}

11.5. Tipos de paso de parmetros a una funcin


Los parmetros reales son variables de enlace entre el mdulo que invoca a una
funcin y la funcin llamada.

Funciones en C

327

Los parmetros formales de una funcin son variables locales a la misma, los
cuales, al ser llamada la funcin, se crean y reciben el valor de los parmetros reales
de la llamada, y se destruirn al salir de la misma.
El paso de estos parmetros a una funcin puede hacerse de dos formas: mediante
paso por valor o mediante paso por referencia.

11.5.1. Paso por valor


Al realizarse la llamada a la funcin, los valores de los parmetros reales (variables
del mdulo que invoca a la funcin), se copian sobre los parmetros formales
(variables locales a la funcin llamada). Dado que los parmetros reales y los
formales son distintas variables en memoria, las modificaciones que se hagan sobre
los parmetros formales no afectan al valor de los parmetros reales.
EJEMPLO

Supngase el siguiente esquema de programa en C.


long suma_num(int, int);
void main(void) {
int a, b;
a = 23;
b = 125;
printf("\nLa suma vale: %ld", suma_num(a, b));
}
long suma_num(int x, int y) {
return (x + y);
}
Nombre

Direccin

FFF4

FFF2

FFD8

125

FFD6

23

Direcciones de
memoria
crecientes

23
125

Las variables x e y se ubican en posiciones de


memoria diferentes a donde se ubican las
variables a y b.
Cualquier modificacin de x o de y no afectar
a a y b.
Se crean en memoria en el momento
en que comienza la ejecucin de la
funcin suma_num(), y se copia en
ellos el valor de los parmetros reales
de la llamada.

11.5.2. Paso por referencia


Al realizarse la llamada a la funcin, los parmetros formales que deben ser de tipo
puntero, reciben la direccin de los parmetros reales y, por tanto, se puede acceder
y modificar el valor de los parmetros reales desde dentro de la funcin, ya que se
conoce cul es su direccin.
Utilizando el paso de parmetros por referencia puede conseguirse que de forma
implcita una funcin devuelva ms de un valor a la funcin que llama.

328

Programacin en C

EJEMPLO

Supngase el siguiente esquema de programa en C.


void main(void) {
int a, b;
a = 23;
b = 125;
intercambia(&a, &b);
printf("a: %d, b: %d\n", a, b);
}
void intercambia (int *x, int *y) {
int temp;
temp = *x;
*x = *y;
*y = temp;
}
Nombre

Direccin

FFF4

FFF2

Direcciones
de memoria
crecientes

23
125

FFD8

FFFF2

FFD6

FFF4

Las variables x, y son punteros a


los parmetros reales a y b.
Cualquier modificacin de *x o
de *y afectar a a y b.
Se crean en memoria en el momento en
que comienza la ejecucin de la
funcin intercambia(), y se
cargan con la direccin de los
parmetros reales. Por tanto, a travs de
estos punteros podemos acceder y a
modificar el valor de los parmetros
reales utilizados en la llamada a la
funcin.

11.5.3. Paso por referencia


Cualquier variable simple puede pasarse como parmetro a una funcin por valor o
por referencia.
Se utiliza paso por valor cuando la funcin usa slo los parmetros como
entrada, es decir, utiliza el valor recibido en ellos pero sin tener necesidad de
modificarlo. Estas funciones, si devuelven un resultado, lo harn a travs de la
sentencia return dentro de la funcin.
EJEMPLO

Funcin que devuelve 1 si el nmero tipo long recibido es primo y 0 en


caso de no serlo.
#include <math.h>
int primo(long n) {
int es_primo = 1;
long divisor = 2;
float limite;
n = labs(n); /* Valor absoluto de n, por si n negativo.
Aunque dentro de la funcin se modifique n,

Funciones en C

329

no se ver afectado el parmetro real


de la llamada */
limite = sqrt(n);
while (es_primo && divisor <= limite) {
if (n % divisor == 0) /* comprueba si resto cero */
es_primo = 0;
divisor++;
}
return (es_primo);
}

Un ejemplo de llamada a esta funcin puede ser el siguiente:


void main(void) {
long numero;
...
scanf("%ld", &numero);
...
if (primo(numero))
printf("El nmero %ld es primo.\n", numero);
else printf("El nmero %ld NO es primo.\n", numero);
...
}

Se utiliza paso por referencia cuando la funcin usa los parmetros como
entrada/salida, es decir, accede desde los parmetros formales (que son punteros) a
los parmetros reales y, adems de utilizar el valor de stos, modifica finalmente el
valor de los mismos. Tambin se utiliza el paso por referencia cuando se quiere
construir una funcin que devuelva ms de un resultado.
EJEMPLO

Se escribe una funcin que recibir tres parmetros enteros: hh, mm e


incrmm. Donde hh es un entero que representa las horas, mm es entero que
representa los minutos e incrmm es un nmero entero que representa los
minutos que hay que aadir al tiempo de arranque pasado a travs de los
otros dos parmetros. El nuevo tiempo resultante se devolver a travs de los
propios parmetros hh y mm.
void sumartiempo (int *hh, int *mm, int incrmm) {
int
tmp;
*mm += incrmm; /* Incremento de *mm en los minutos incrmm */
/* Si *mm supera el 60, se debe incrementar las horas (*hh) */
tmp = *mm / 60;
*mm = *mm % 60;
*hh += tmp;
}

Esta funcin utiliza los valores de los parmetros reales recibidos y los
devuelve modificados convenientemente.
Un ejemplo de llamada se presenta en el siguiente esqueleto de programa.
int horas = 5;
int minutos = 57;
int incremento = 148;
...
printf("Tiempo inicial: %2d:2d\n", horas, minutos);

330

Programacin en C

printf("Incremento %2d minutos\n", incremento);


sumartiempo(&horas, &minutos, incremento);
...
printf("Tiempo final: %2d:2d\n", horas, minutos);

Un ejemplo de la ejecucin del mismo sera:


Tiempo inicial: 5:57
Incremento 148 minutos
Tiempo final: 8:25
EJEMPLO

Funcin que transforma coordenadas polares a cartesianas. Esta funcin


recibe dos parmetros mediante paso por valor (radio y angulo) y
utilizando otros dos parmetros pasados por referencia, devuelve dos
resultados (coordenada x y coordenada y), de acuerdo al esquema mostrado
en

Figura 11.4. Relacin entre coordenadas polares y coordenadas cartesianas


void polares_a_cartesianas(float rd, float ang, float *x, float *y) {
#include <math.h>
*x = rd * cos(ang);
*y = rd * sin(ang);
}

Un ejemplo de llamada se presenta en el siguiente esqueleto de programa.

float radio;
float angulo;
float ordenada, abcisa;
...
scanf("%f", &radio);
scanf("%f", &angulo);
...
polares_a_cartesianas (radio, angulo, &abcisa, &ordenada);

Los parmetros reales radio y angulo deben tener un valor definido


cuando se llama a la funcin. Los otros dos parmetros reales abcisa y
ordenada, sern cargados por la funcin como resultado al clculo
realizado.
Un vector debe pasarse a una funcin siempre por referencia, utilizando el nombre
del vector, que es un puntero al primer elemento. Por tanto, el parmetro formal de la
funcin debe ser un puntero al tipo de dato del vector, para poder recoger su
direccin.
Adems, la funcin que manipule el vector deber conocer su dimensin, por lo
que se pasar sta en un segundo parmetro, en este caso normalmente por valor.

Funciones en C

331

EJEMPLO

Se definen dos funciones, la funcin cargar_lista() que carga un


vector (pasado por referencia) y la funcin calcular_maximo() que
calcula y devuelve el valor mximo de un vector (pasado por referencia).
Ambas funciones deben recibir la longitud del vector para poder saber
cuntos elementos deben tratar.
#include <stdio.h>
#define DIM 100
/* Prototipos de las funciones */
Al definir el parmetro formal es
void cargar_lista(float *, int);
float calcular_maximo (float [], int); equivalente float *la (puntero
a float) y float la[] (el

void main (void) {


compilador lo transforma en un
puntero a float).
float lista DIM ;
int diml;
float maximo; /* Mismo tipo que elementos del vector */
/* Se lee de teclado el nmero mximos de elementos a
introducir: longitud efectiva del vector */
do {
printf("Cuntos nmeros a introducir (mx. %d)?:", DIM);
scanf("%d", &diml);
} while ((diml <=0) || (diml > DIM));
/* Se carga el vector de nmeros */
cargar_lista(lista, diml);
/* Se calcula el valor mximo de los elementos del vector */
maximo = calcular_maximo(lista, diml);
/* Se presenta el mximo calculado */
printf("Mximo de lista: %g\n", maximo);
}
void cargar_lista(float *la, int dm) {
int j;
for (j = 0; j < dm; j++) {
printf("Elemento %d: ", j+1);
scanf("%f", &la[j]);
}
}
float calcular_maximo (float la[], int dm) {
float max = la[0];
int j;
for (j = 1; j < dm; j++)
if (la[j] > max)
max = la[j];
return (max);
}

Una cadena de caracteres debe pasarse a una funcin siempre por referencia,
utilizando el nombre de la cadena, que es un puntero al primer carcter. Por tanto, el
parmetro formal de la funcin debe ser un puntero de tipo char, para poder recoger
su direccin.
No hace falta pasar a la funcin la dimensin de la cadena, pues la funcin la
puede obtener localizando el '\0' final de la cadena.

332

Programacin en C

EJEMPLO

La funcin cadena_minusculas() recibe una cadena de caracteres (por


referencia) que retorna convertida toda ella a minsculas.
#include <stdio.h>
#define DIM 80
/* Prototipos de las funciones */
void cadena_minusculas(char *);
void minus(char *);

Al definir el parmetro formal es


equivalente escribir char *cad
(puntero a carcter) y char
cad[] (el compilador lo transforma
en un puntero a carcter).

void main (void) {


char cadena DIM ;
printf("Introduzca una frase:");
gets(cadena);
/* Se convierte la cadena a minsculas */
cadena_minusculas(cadena);
printf("La cadena en minsculas es %s\n", cadena);
}
void cadena_minusculas(char *cad) {
int j = 0;
while (cad[j] != '\0') {
minus(&cad[j]);
j++;
}
}
void minus(char *letra) {
if (*letra == '')
*letra = '';
else
if ((*letra >= 'A') && (*letra <= 'Z'))
*letra = (*letra + 32);
}

Una tabla4 debe pasarse a una funcin siempre por referencia, utilizando el nombre
de la tabla, que es un puntero a la primera fila de la tabla. Por tanto, el parmetro
formal de la funcin debe ser un puntero a filas de la tabla, para poder recoger su
direccin.
Adems la funcin que manipule la tabla deber conocer el nmero de filas de la
tabla, por lo que se le pasar siempre en un segundo parmetro, en este caso
normalmente por valor.
El nmero de columnas de la tabla se est pasando a la funcin de forma implcita
en la definicin del parmetro formal puntero a filas.
EJEMPLO
El siguiente ejemplo implementa dos funciones que reciben un entero y una matriz de
enteros, la primera calcula la traza de la matriz, funcin traza(), y la segunda que
presenta en pantalla la matriz que recibe, funcin presenta_matriz().

Una tabla puede considerarse como un vector de filas, donde cada fila es a su vez un vector de
dimensin el nmero de columnas de la tabla.

Funciones en C

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

333

Al definir el parmetro formal es equivalente


escribir int (*ma)[COL] (puntero a filas de
dimensin COL) y int ma[][COL] (el
compilador lo transforma en un puntero a filas de
dimensin COL )

/* Prototipos */
float traza (int, int (*)[COL]);
void presenta_matriz (int, int [][COL]);

void main(void) {
int mat[FIL][COL] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int i,j;
presenta_matriz(FIL, mat);
printf("Traza: %g\n", traza(FIL, mat));
}
float traza (int filas, int (*ma)[COL]) {
int f;
float tr = 0;
for (f=0; f<=filas-1; f++)
tr = tr + ma[f][f];
return (tr);
}
void presenta_matriz (int filas, int ma[][COL]) {
int f,c;
for (f = 0; f <= filas-1; f++) {
for (c = 0; c <= COL-1; c++)
printf("%5d", ma[f][c]);
printf("\n");
}
}

11.6. Recursividad
11.6.1. Conceptos bsicos
El concepto de recursividad est presente en la mayora de los lenguajes de
programacin. Consiste en el hecho de que una funcin haga referencia a s misma
dentro de su definicin.
Un programa que hace uso de la recursividad contiene una funcin que se llama a
s misma de forma repetida, hasta que se satisface alguna determinada condicin de
salida. Este mecanismo es muy til cuando se requieren clculos en los que cada
accin se determina segn el resultado anterior. sta es por tanto una valiosa forma de
abordar muchos problemas iterativos.
Para que una funcin recursiva sea vlida, la referencia a s misma debe ser de
alguna forma ms sencilla que el caso considerado. En trminos de ejecucin, la
recursividad supone que una invocacin al subprograma recursivo genere una o ms
invocaciones al propio subprograma. La cadena de invocaciones terminar cuando
alguna no genere nuevas invocaciones. Cuando estas llamadas terminales acaban su
ejecucin, devuelven el control a la llamada anterior, que a su vez, har lo mismo,

334

Programacin en C

hasta que todas las cadenas de invocacin, incluida la propia llamada inicial,
terminan.
Para que se pueda resolver un problema mediante recursividad, se deben satisfacer
dos condiciones: en primer lugar, el problema se debe escribir de forma recursiva y
segundo, la sentencia del problema debe incluir una condicin de fin.
11.6.1.1. Algoritmo Recursivo
Un algoritmo recursivo es aqul que, en parte, est formado por s mismo o se define
como funcin de si mismo. Tiene como principal ventaja la posibilidad de definir un
conjunto infinito de objetos mediante una proposicin finita, ya que un conjunto
infinito de clculos puede describirse mediante un programa recursivo finito, sin que
contenga repeticiones explcitas. Siendo una situacin idnea aqulla donde el
problema por resolver, la funcin por calcular o la estructura de datos por procesar ya
estuvieran definidos en trminos recursivos.
EJEMPLO

Considrese la estructura de cualquier diccionario: las definiciones de las


palabras contienen a su vez otras palabras. Cuando se busca el significado de
una palabra, es posible que no se entiendan todas las palabras que forman la
definicin, lo que obliga a consultar su significado.
Como el diccionario es finito, llegar a un momento en el que se entendern
todas las palabras de la definicin.
La forma de consulta, por tanto, se resume en el siguiente procedimiento: si
se conoce el significado de una palabra, queda resuelto el problema; si no, se
encuentra el significado de la definicin buscando de forma recursiva las
palabras que no se entienden. Este procedimiento terminar si el diccionario
est bien definido.
EJEMPLO

Un ejemplo clsico de recursividad aparece al considerar la definicin de un


nmero natural bajo esta ptica:
El nmero 0 es natural.
El Nmero n es natural si n-1 lo es.
Otro ejemplo, Clculo del factorial de un nmero:
Factorial (n) = n! = n * (n-1)! = n * (n-1) * (n-2)! = n * (n-1) * (n-2)* ...* 1
Con 1! = 1 y 0! Definido como 1.
En ambos casos la accin deseada se expresa en trminos del resultado
anterior, que se supone conocido y se proporciona una condicin de fin para
el proceso recursivo.

11.6.2. Tipos de recursividad


Hasta ahora se ha tratado el concepto de recursividad, sin distinguir ningn caso
particular. Sin embargo, segn se establezca la definicin de la misma se pueden
considerar varios tipos: recursividad simple, recursividad mltiple, recursividad
anidada y recursividad cruzada o anidada.

Funciones en C

335

11.6.2.1. Recursividad simple


Es la que se desprende de lo explicado hasta este momento: aqulla en cuya
definicin slo aparece una llamada recursiva. Se puede transformar con facilidad en
un algoritmo iterativo.
11.6.2.2. Recursividad mltiple
Se da cuando en una funcin hay ms de una llamada a s misma dentro del cuerpo de
la misma, lo que hace ms difcil convertir el algoritmo a su equivalente iterativo. Por
ejemplo el clculo de la sucesin de Fibonacci.
EJEMPLO
long int fib(long int num){
if (num <= 1)
return (1);
else
return (fib(num - 1) + fib (num - 2));
}

11.6.2.3. Recursividad anidada


Este tipo de recursividad se caracteriza porque algunos de los parmetros que se pasan
a la funcin en la llamada recursiva son a su vez llamadas a la propia funcin. Se
considera por ejemplo la funcin de Ackerman.
EJEMPLO
long int ack(long int n, long int m){
if (n == 0)
return (m + 1);
else
if (m == 0)
return ack(ack(n - 1), 1));
else
return ack(ack(n - 1), ack(n, m - 1)));
}

11.6.2.4. Recursividad cruzada o indirecta


En realidad, ste es el tipo de recursividad ms diferente a los vistos hasta ahora 5. Se
trata de aquellos casos en los que una funcin provoca una llamada a s misma de
forma indirecta, a travs de otras funciones. Se ve por ejemplo si una funcin es Par o
Impar.
EJEMPLO
int par(long int np){
if (np == 0)
return (1);
5

Algunos autores slo distinguen entre recursividad directa, que englobara a todos los dems
tipos de recursividad enumerados, y la recursividad indirecta.

336

Programacin en C

else
return (impar(np - 1));
}
int impar(long int ni){
if (ni == 0)
return (0);
else
return (par(ni - 1));
}

11.6.3. Diseos recursivos vs. diseos iterativos


Los lenguajes de programacin deben proporcionar alguna manera de describir
aquellas partes que han realizarse de forma repetida (funciones, un conjunto de
sentencias) a lo largo de la ejecucin de un programa. Ambos, diseos recursivos y
diseos iterativos, se utilizan con este propsito. A pesar de que ambos diseos tienen
el mismo poder expresivo, a la recursividad se le ha prestado mucha menos atencin
que al diseo iterativo, que tiene dedicada una abundante literatura. Esto se debe en
parte a que la propia historia de la programacin ha estado muy unida a la estructura
de los computadores, al ser la iteracin el mecanismo de bajo nivel que estos
suministran, es aceptada de forma ms natural por los programadores para describir
los clculos.
Esta situacin es, cuando menos, curiosa si se tiene en cuenta que las definiciones
recursivas se utilizan desde mucho antes que los ordenadores y sus programas. stas
constituyen, en muchos casos, un mtodo ms natural para describir funciones y tipos
de datos. La propia definicin recursiva del factorial de un nmero o la de tipos de
datos como listas o rboles, son ejemplos de ello.
Como ya se ha dicho, la recursividad tiene la misma potencia expresiva que la
iteracin, lo que permite describir los mismos clculos y las mismas funciones que
sus correspondientes versiones iterativas. Adems, su uso genera programas ms
compactos, por lo que se considera una herramienta fundamental para el diseo de
programas y para el razonamiento formal sobre su correccin. Por otro lado, se
considera menor el esfuerzo cuando se trabaja en un diseo recursivo que en uno
iterativo.
Otra caracterstica, como se ver ms adelante, de los programas recursivos es que
pueden ser transformados en sus correspondientes versiones iterativas sin que merme
su correccin o eficiencia.
Es importante conocer las diferencias entre los diseos recursivos e iterativos que
surgen cuando se da solucin a un determinado problema.
A continuacin, en la Figura 11.5 se muestran de forma esquematizada estas
diferencias. Para ello, se toma como punto de partida un problema P, que ser resuelto
de forma iterativa y de forma recursiva. La solucin del problema se expresa segn
unos datos D.
Diseo Recursivo:
Diseo Iterativo:

Funciones en C

Es posible resolver P sobre los datos D


suponiendo que P ya est resuelto para un
conjunto de datos D, que supone un
subconjunto de D y es ms sencillo en
algn sentido.
Se supone que D y D son del mismo tipo
y D ms pequeo que D. Se precisan
menos elementos que en el caso iterativo,
ya que en un primer nivel de anlisis, hay
involucrado un solo problema P y un solo
tipo de datos.

337

La solucin para P se obtiene mediante


el planteamiento de un problema
distinto y ms sencillo p, en trmino de
unos datos distintos y ms pequeos d.
Se plantea un subproblema ms sencillo
para resolver P, aplicado a datos ms
pequeos un cierto nmero de veces.
Resuelto p y aplicado sobre datos de
tipo d un cierto nmero n de veces, se
resuelve el problema original P sobre
los datos D.

Es posible resolver P sobre los datos D


suponiendo que P ya est resuelto para D.
Se obtiene, por tanto, un problema ms
sencillo y de igual naturaleza.
Figura 11.5. Comparacin entre diseo iterativo y diseo recursivo
EJEMPLO

Diseo Recursivo:
Calcular la potencia n-sima de un nmero, es decir, se trata de
calcular an.
P = an
D = (a, n).
La aplicacin al problema de la exponenciacin es simple:
suponiendo que ya se sabe calcular an-1, resolver el problema
original an resulta inmediato.
Calcular an es P, mientras que an-1 es D. Resolver el problema
original es evidente:
an = a * a n-1
Ntese que el par (a, n-1) es del mismo tipo que (a, n), pero
ms sencillo.
Se trata de encontrar una relacin de recurrencia para solucionar el
problema sobre datos cada vez ms pequeos:
D>D>D>...>Dt (obtenindose una sucesin finita).
Diseo Iterativo:
Se llega a unos datos lo suficientemente sencillos, Dt para que P sea
resuelto directamente. Siendo Dt un caso trivial.
El problema p consiste en multiplicar dos nmeros x y a.
Repitindose el problema p exactamente n veces. En total hay
involucrados dos problemas distintos, P y p, y dos tipos de datos, D
y d en general distintos.

338

Programacin en C

a * a * ... * a (n veces)
Se tienen un conjunto de instrucciones que se repiten un nmero de
veces.
El fundamento de un programa recursivo es un reflejo de siguiente anlisis: habr
una o ms instrucciones condicionales dedicadas a separar los tratamientos
correspondientes al caso trivial o casos triviales de los correspondientes al caso o
casos no triviales. Los primeros tienen el aspecto de un programa convencional
mientras que, en los segundos, se pueden distinguir las siguientes partes:
Primero se calcular D, el sucesor de D. Se debe aadir que se produce una
descomposicin recursiva de los datos D para obtener los datos ms sencillos
D. A veces se dice que D es un subproblema de D.
A continuacin, se produce una llamada recursiva para obtener la solucin a
un subproblema D.
Finalmente, se opera sobre los resultados obtenidos para D a fin de calcular
la solucin para D.
En general, la versin iterativa ser siempre menos legible y modificable, la
ganancia en eficiencia debe estar suficientemente justificada.

11.6.4. Criterios a tener en cuenta en la eleccin de una versin recursiva


o la iterativa
A continuacin se exponen algunos criterios que ayudan en el momento de decantarse
por un diseo recursivo o un diseo iterativo.
Generalmente, si la versin iterativa del algoritmo tiene una forma simple,
ser la opcin preferible.
La herramienta fundamental de anlisis de algoritmos recursivos es el rbol
de recursividad. La altura de dicho rbol da una medida del nmero de
llamadas recursivas, lo que equivale al consumo de recursos.
Anlisis del rbol de recursividad.
1) Si el rbol presenta una nica rama se debe elegir la versin iterativa.
2) Si el rbol de recursividad presenta muchas ramas pero se repiten
operaciones se debe elegir la versin iterativa.
3) Si el rbol de recursividad presenta muchas ramas sin repetir operaciones
y es poco profundo se debe elegir la versin recursiva.
En general siempre es cierto que la recursividad se puede reemplazar por
iteracin y el uso de una pila.
Pueden existir otras razones para transformar una versin recursiva en su
correspondiente iterativa:
1) Que el lenguaje de programacin disponible no soporte recursividad.
2) Que no se quiera pagar el coste adicional en tiempo del mecanismo de
llamadas a procedimientos y traspaso de parmetros, ni el coste adicional
de memoria que lleva implcito la implementacin de la recursividad.

Funciones en C

339

11.6.5. Pasos para emplear la recursividad


Como se ha mencionado la herramienta principal para construir un algoritmo
recursivo es la funcin, donde se distinguen como mnimo dos partes:
Caso trivial, base o de fin de recursividad: es un caso donde el problema
puede resolverse sin tener que hacer uso de una nueva llamada a s mismo.
Evita la continuacin indefinida de las partes recursivas.
Parte puramente recursiva, que relaciona el resultado del algoritmo con
resultados de casos ms simples. Se hacen nuevas llamadas a la funcin,
pero estn ms prximas al caso base. Para ello se recomienda:
o Examinar varios casos sencillos y buscar el caso base, calcular un
resultado y devolverlo.
o Buscar un mtodo que funcione de forma general. Cuando la
funcin recibe un caso ms complejo que el base, lo divide en dos
partes conceptuales:
Una parte que sabe cmo resolver.
Una parte que no sabe cmo resolver, pero que es muy
parecida al problema original aunque ms pequea o
sencilla lo que implica una llamada recursiva o paso de
recursividad.
El paso de recursividad debe devolver el resultado que se combina con la parte que
se supo resolver para formar un resultado que, a su vez, ser devuelto (return en C)
a la llamada original
Se debe encontrar una regla de finalizacin del algoritmo y verificar que,
efectivamente, se detiene, es decir, que la recursividad tiene fin. Lo que se podra
implementar generalmente como:
si condicin entonces
llamar_recursivamente
sino
no_hacer_nada o devolver_valor

Este punto es particularmente importante, ya que, al igual que en los bucles, se


pueden producir situaciones de llamadas infinitas. Se debe demostrar la terminacin
de las llamadas recursivas.

11.6.6. La pila de recursividad


Desde el punto de vista lgico, la memoria del ordenador se puede dividir en los
siguientes segmentos:
Segmento de cdigo: Parte de la memoria donde se guardan las
instrucciones del programa en cdigo mquina.
Segmento de datos: Destinado a almacenar las variables estticas.
Montculo: Parte de la memoria destinada a las variables dinmicas.
Pila del programa: Destinada a las variables locales y parmetros de la
funcin que est siendo ejecutada.
A continuacin se examina la forma en que se trabaja con la memoria para el caso
general de cualquier funcin y para el caso de una funcin recursiva.

340

Programacin en C

En la llamada a una funcin:


Se reserva espacio en la pila para los parmetros de la funcin y sus variables locales.
Se guarda en la pila la direccin de la lnea de cdigo desde donde se ha llamado a la
funcin. Se almacenan los parmetros de la funcin y sus valores en la pila. Al
terminar la funcin, se libera la memoria asignada en la pila y se vuelve a la
instruccin actual.
En la llamada a una funcin recursiva:
Cada llamada genera un nuevo ejemplar de la funcin con sus correspondientes
objetos locales. La funcin se ejecutar normalmente hasta la llamada a s misma. En
ese momento se crean en la pila nuevos parmetros y variables locales. El nuevo
ejemplar de la funcin comienza a ejecutarse. Se crean ms copias hasta llegar a los
casos bases, donde se resuelve directamente el valor, y se va saliendo liberando
memoria hasta llegar a la primera llamada (ltima en cerrarse).
Es importante notar que cada vez que una funcin se activa de forma recursiva, se
crea un nuevo conjunto de objetos locales en la pila del programa.
Cada conjunto de objetos locales tienen el mismo nombre que en la llamada
anterior, pero sus valores son especficos de la llamada y se distinguen por ser los de
creacin ms reciente. Pueden dar lugar a un desbordamiento de pila, pues se sita en
memoria una copia completa de la funcin.
El entorno de programacin de C soporta recursividad mediante el uso prudente de
una pila de ejecucin. Cuando un programa llama a una funcin detrs de otra, la pila,
en tiempo de ejecucin, crece. Cuando una funcin termina, la pila se hace ms
pequea, tambin en tiempo de ejecucin. As, la pila se gestiona automticamente
por el programador en tiempo de ejecucin. Por ello, es crucial que se escriba el
cdigo de tal manera que la recursividad pare en algn nivel. De otro modo, la pila
crecer tanto en tiempo de ejecucin, que se agotar el espacio en memoria y el
programa terminar con un error.
sta es una desventaja de los programas recursivos, aunque se generan programas
ms compactos donde el esfuerzo de razonamiento y mantenimiento es menor (ms
legibles y modificables). Por otro lado, con los iterativos se generan programas ms
eficientes que consumen menos tiempo y memoria.
A continuacin se analizan algunos ejemplos clsicos de algoritmos, que, si bien se
han utilizado histricamente para explicar el concepto de recursividad en los
manuales de programacin, constituyen claros ejemplos de situaciones en las que no
se debe emplear recursividad.
EJEMPLO

Clculo del factorial de un nmero.


Como se sabe, calcular el factorial de un nmero consiste en multiplicar un
nmero dado por cada uno de sus nmeros inmediatos anteriores, es decir:
n! = n * (n-1) * (n-2) * (n-3) * ... * 1. Hacer esto sin
recursividad es sencillo; hacerlo con recursividad es an ms sencillo.
Por ejemplo: resultado = factorial (5)
Como se puede apreciar en la Figura 11.6, factorial es una funcin que
se llama a s misma. En la figura se muestra cmo se va produciendo la
secuencia de llamadas recursivas (pasos 1 a 5) y la devolucin de resultados
encadenados (pasos 6 al 10).

Funciones en C

341

Figura 11.6. Secuencia de ejecucin de una funcin recursiva

En concreto, la funcin se llamar a s misma de manera continua, hasta que


el valor del parmetro que reciba sea igual o menor que uno. sta es, por
tanto, la condicin que har que finalice la recursividad y, por tanto, se
devuelva el valor final.
As, al finalizar el proceso recursivo de la funcin, se almacenar 120 en la
variable resultado.
Se muestra a continuacin cmo se ejecuta la funcin en detalle:
Cuando se llama la funcin por primera vez, que sera la llamada
factorial(5), sta recibe el parmetro real 5. Dentro, se verifica si
el valor de n (parmetro formal) es menor o igual que 1. Como en este
caso es 5, no lo es, y, por tanto, su valor se multiplicar por el resultante
de llamarse a s misma, pero con el valor n 1 (paso 1), con lo que
factorial ahora se ejecutar con un valor de 4 en el parmetro (paso
2).
Este comportamiento se repetir hasta que factorial reciba un
parmetro con un valor igual o menor que 1. Ante esto, factorial ya
no volver a llamarse a s misma, devolver 1 (paso 6). As, una vez
finalizada la ejecucin de esta ltima copia de la funcin, el control
vuelve a la lnea que hizo la llamada inmediatamente anterior (paso 6),
que utilizar el resultado recibido.
El proceso se repite sucesivas veces hasta llegar a la llamada original
(pasos 7 a 10). As, el encadenamiento de operaciones, que se lleva
desde dentro hacia afuera, traera consigo la siguiente secuencia: 1 * 1
* 2 * 3 * 4 * 5, y el resultado, 120, se asignar a la variable
resultado.
Como se ha mencionado, al finalizar la ejecucin de cada una de las
instancias de la funcin, el resultado se devuelve a la lnea que hizo la
llamada. Esto se consigue porque se lleva un control de la pila de llamadas.
En sta se establece el orden en que se han hecho las llamadas a las distintas

342

Programacin en C

funciones, lo que incluye a las llamadas recursivas. Este hecho es importante;


siempre se debe tener en cuenta la cantidad de veces que pueda llevarse a
cabo la recursividad, aunque normalmente los lmites en la pila son
suficientes para ejecutar cualquier proceso.
Cuando se ejecuta un programa recursivo, las llamadas recursivas no se
ejecutan inmediatamente. Lo que ocurre es que se van colocando en la pila
hasta que se encuentra la condicin de terminacin. Entonces se ejecutan las
llamadas a la funcin en orden inverso a como se generaron, como si se
fueran sacando de la pila. Cuando se evala el factorial recursivamente, las
llamadas a la funcin se generarn en el siguiente orden:
n! = n * (n - 1)!
(n -1)! = (n - 1)! * (n - 2)!
(n - 2)! = (n - 2)! * (n-3)!
...
5! = 5 * 4 * 3 * 2 * 1

Los valores reales se devolvern en orden inverso, es decir:


1! =
2! =
3! =
4! =
...
n! =

1
2 * 1 = 2
3 * 2 * 1 = 6
4 * 3 * 2 * 1 =24
n * (n - 1)! = ...

Si una funcin recursiva tiene variables locales, se crearn un conjunto


diferente de variables locales durante cada llamada. Los nombres de las
variables locales sern, por supuesto, siempre los mismos: los declarados en
la funcin. Sin embargo, las variables representarn un conjunto diferente de
valores cada vez que se ejecute la funcin. Cada conjunto de valores se
almacenar en la pila, as se podr disponer de ellas cuando el proceso
recursivo comience las ejecuciones en sentido inverso, es decir, cuando las
llamadas a la funcin se saquen de la pila y se ejecuten.
La implementacin del ejemplo anterior consiste en identificar:
Caso base: 1! = 1.
Mtodo general: n * (n 1)!.
Donde existe una parte conocida: n y otra parte desconocida pero similar al
problema original (llamada recursiva o paso de recursividad): ? (n1)!.
El cdigo C de la funcin factorial sera el siguiente:
long int factorial(long int n){
if (n <= 1)
return (1);
else
return (n * factorial(n - 1));
}

Si se analiza el rbol de recursividad para este ejemplo se ve que el mismo


presenta una nica rama, por lo que ser preferible la versin iterativa. Al
leer el rbol en direccin ascendente y no en direccin descendente, se
obtiene el programa iterativo partiendo del recursivo.
Cuando el rbol se reduce a una cadena, la transformacin de la recursividad
en iteracin resulta generalmente fcil, ahorrar tiempo y espacio a la vez.
sta sera la versin iterativa:

Funciones en C

343

long int factorial(long int n){


long int resultado = 1;
long int i;
for (i = 2; i <= n; i++)
resultado = i * resultado;
return (resultado);
}
EJEMPLO

La sucesin de Fibonacci.
Como ya se vio ejemplificando la recursividad mltiple, la sucesin de
Fibonacci se corresponde con la siguiente definicin:
fib(1) = 1; fib(0) = 0
fib(n) = fib(n - 1) + fib(n - 2) si n >= 2

A continuacin se muestra el rbol de recursividad (Figura 11.7) para el caso


fib(5).

Figura 11.7. rbol de recursividad para la funcin fibonacci

Como se puede observar, este algoritmo es muy ineficiente porque recalcula


muchas veces los mismos valores. Analizando el rbol, para el clculo de
fib(5) se necesitan los valores de fib(4) y fib(3), pero fib(4)
tambin requiere el clculo de fib(3). De esta forma fib(3) se calcula
dos veces, fib(2) se calcula tres veces y fib(1) cinco veces.
Con la versin iterativa se evita calcular una y otra vez los mismos valores y
se consigue que los tiempos de ejecucin sean mejores.
Al leer el rbol en direccin ascendente y no en direccin descendente, se
obtiene el programa iterativo partiendo del recursivo:
long int fib(long int n){
long int a, b, c
if (n == 0)
return (0);
if (n == 1)
return (1);
a = 0;
b = 1;

344

Programacin en C

for (i
c =
a =
b =
}
return

= 2; i <= n; i++){
a + b;
b;
c;
(c);

Se han estudiado dos ejemplos en los que el uso de la recursividad es pernicioso. A


continuacin se va a estudiar un ejemplo de algoritmo en el que s se debe emplear
recursividad.
EJEMPLO

Las Torres de Hanoi.


En el momento de la creacin del mundo, los sacerdotes recibieron una
plataforma de bronce sobre la cual haba tres agujas de diamante (Figura
11.8). En la primera aguja estaban apilados 64 discos de oro, cada uno
ligeramente menor que el que est debajo de l. A los sacerdotes se les
encomend la tarea de pasarlos todos de la primera aguja a la tercera, con
dos condiciones:
Slo puede moverse un disco a la vez
Ningn disco podr ponerse encima de otro ms pequeo
Se dijo a los sacerdotes que, cuando hubieran terminado de mover los 64
discos, llegara el fin del mundo.
El problema, por tanto, consiste en realizar un programa que d una lista de
instrucciones a los sacerdotes. Se trata de un problema clsico que se
resuelve con la tcnica divide y vencers.
El algoritmo debe consistir en mover 63 discos de la aguja 1 a la 2,
utilizando la aguja 3 temporalmente; mover el disco 64 de la aguja 1 a la 3;
mover los 63 discos de la aguja 2 a la 3, utilizando la aguja 1 temporalmente.

Figura 11.8. Torres de Hanoi

Para entender el problema, se considera primero el caso de tener que mover


slo tres discos, es decir n = 3 (ver Figura 11.9).
Desde el punto de vista de la recursividad, la primera llamada tiene dos
partes conceptuales:
Caso base. Mover el disco 3 de la aguja 1 a la 3 (lo que se consigue
en el paso 4).
Paso de recursividad. Mover dos (n - 1) discos: el 1 y el 2, de la
aguja 1 a la 2, utilizando la 3 como auxiliar (pasos 1 a 3), y de la
aguja 2 a la 3 utilizando la 1 como auxiliar (pasos 5 a 7).

Funciones en C

345

Figura 11.9. Torres de Hanoi con tres discos

Cada una de estas llamadas desencadena varias operaciones y llamadas. As,


la siguiente llamada (mover los discos 1 y 2 de la aguja 1 a la 2), tiene a su
vez dos partes conceptuales:
Caso base. Mover el disco 2 de la aguja 1 a la 2 (que se hace en el
paso 2)
Paso de recursividad. Mover un (n - 1) disco: el 1 de la aguja 1 a la
3 utilizando la aguja 2 si fuera necesario (paso 1), y de la aguja 3 a
la 2, utilizando (paso 3).
La otra llamada recursiva (mover los discos 1 y 2 de la aguja 2 a la 3) se
hara de forma anloga.
En este caso, con slo tres disco, las terceras llamadas recursivas ya reciben
como problema mover un nico disco (caso base).
El rbol de recursividad (ver Figura 11.10) muestra la sucesin de llamadas
recursivas y las correspondientes resultados; se observa que es rbol
balanceado y que no repite operaciones en sus ramas. Es por tanto un caso en
el que el uso de la recursividad es aconsejable.
Si se generaliza este estudio de la recursividad en las torres de Hanoi para un
nmero de discos n, se obtienen las siguientes conclusiones:
La primera llamada se realiza con un nmero de discos igual a n.
Cada llamada recursiva reduce en 1 el nmero de discos.
Si se excluyen las llamadas con nmero de discos igual 0, que no
hacen nada, se tendr una profundidad de recursividad de n.
Exceptuando las hojas, cada vrtice produce dos llamadas
recursivas, y por ello el nmero de vrtices en cada nivel es
exactamente el doble al nmero de vrtices del nivel anterior.

346

Programacin en C

Figura 11.10. rbol de recursividad para las torres de Hanoi

Haciendo un anlisis ms detallado de rbol de recursividad se pude calcular


cuntas instrucciones se necesitan para mover n discos.
Una instruccin se imprime por cada vrtice en el rbol, salvo las
hojas.
El nmero de vrtices no hojas: 1+2+4+...+2n-1=2n 1.
En el caso de los monjes n = 64, por tanto se tiene que:
264-1=1019
Si se supone que aproximadamente pasan 3.2x107 segundos en un
ao, y asumiendo que cada instruccin pudiera hacerse a una
velocidad de una por segundo, la tarea total tardara unos 5x1011
aos.

Funciones en C

347

El cdigo en C para la funcin recursiva que permitiera dar por escrito las
instrucciones a los monjes se muestra a continuacin. La llamada inicial sera
mueve(64, 1, 3, 2):
/* a: aguja origen
b: aguja destino
c: aguja temporal
*/
void mueve(int n, int a, int b, int c){
if (n > 0){
mueve (n - 1, a, c, b);
printf(Mueve un disco de %d a %d, a, b);
mueve(n - 1, c, b, a);
}
}

Como se ha mostrado, la recursividad es una herramienta potente para resolver


mltiples problemas. Es ms, todo programa iterativo puede realizarse empleando
expresiones recursivas y viceversa.

11.6.7. Cundo utilizar la recursividad


Para empezar, algunos lenguajes de programacin no admiten el uso de recursividad,
como por ejemplo el Ensamblador o el Fortran. Es obvio que en ese caso se requerir
una solucin no recursiva (iterativa).
Cuando la solucin iterativa sea clara a simple vista no se debe emplear
recursividad. Sin embargo, en otros casos, obtener una solucin iterativa puede ser
mucho ms complicado que una solucin recursiva, y es entonces cuando se puede
plantear la duda de si merece la pena transformar la solucin recursiva en otra
iterativa, pues se reduce la claridad del programa.
Por otra parte, casi todos los algoritmos basados en los esquemas de vuelta atrs y
divide y vencers son recursivos, pues de alguna manera parece mucho ms natural
una solucin de este tipo. Aunque no lo parezca, es en general mucho ms sencillo
escribir un programa recursivo que su equivalente iterativo.
De todo lo estudiado cabe concluir que la recursividad puede involucrar un
compromiso entre simplicidad y prestaciones. Cada problema se debe juzgar
independientemente considerando sus caractersticas especficas. El utilizar
recursividad no es necesariamente la mejor aproximacin al problema, aunque la
definicin del mismo sea recursiva. Una implementacin no recursiva puede ser ms
eficiente, en cuanto a utilizacin de memoria y velocidad de ejecucin.

11.7. Punteros a funciones


Hasta ahora cuando se quera utilizar una funcin se utilizaban los mecanismos de
llamada y recepcin de resultados expuestos. En este apartado se va a explicar cmo

348

Programacin en C

definir punteros a funciones, lo que permitir establecer otra forma de llamar a una
funcin y de recoger sus resultados.
Es importante destacar que la definicin y declaracin de la funcin no se
modifican, se trata nicamente de un modo diferente de hacer uso de la misma. As,
un puntero a funcin almacena la direccin de memoria de una funcin.
La principal ventaja del uso de este tipo de variables es la simplificacin del
cdigo, sobre todo cuando se tienen que aplicar diferentes criterios sobre un conjunto
de elementos como son las estructuras.
La declaracin de un puntero a funcin tiene la siguiente forma:
tipo_r (*nombre)(tipo1 par1, ..., tipon parn);
Se debe resaltar que los parntesis que encierran al nombre de la variable son
indispensables, ya que tipo_r *nombre se corresponde con la definicin de una
funcin que devuelve como resultado un puntero al tipo de datos tipo_r.
EJEMPLOS
int (*accion)(int a, int b);
float (*beneficios)(int incial, int stock, float margen)

11.7.1. Uso
Al igual que con los punteros estudiados en el captulo 9, slo se le pueden asignar
direcciones de memoria de funciones que retornen el mismo tipo de dato, y que
tengan la misma lista de parmetros (lo que evita errores de indireccin). La
asignacin, lgicamente, consistir en igualar el nombre del puntero con el nombre de
la funcin a la que debe apuntar.
Al igual que los dems punteros, estos pueden cambiar de direccin cuantas veces
se necesite.
EJEMPLO
/* Definicin de funciones */
int sumar(int a, int b);
int restar(int a, int b);
int multiplicar(int a, int b);
int dividir(int a, int b);
/* Declaracin del puntero a funcin */
int (*accion)(int a, int b);
... /* resto de sentencias */
/* En este punto, el programa se requiere hacer una suma */
valor_a = 2;
valor_b = 3;
accion = sumar;
resultado = accion(valor_a, valor_b);
... /* resto de sentencias */
/* En este punto, el programa se requiere hacer un producto */
valor_a = 27;
valor_b = 10;
accion = multiplicar;
resultado = accion(valor_a, valor_b);
... /* resto de sentencias */

Funciones en C

349

Como se puede observar en el ejemplo anterior, lo que se hace, es ir a la direccin


de memoria y evaluar la funcin. Por esa razn, estas dos sentencias son equivalentes:
resultado = accion(valor_a, valor_b);
resultado = (*accion)(valor_a, valor_b);

Por otro lado, el lenguaje C tambin ha previsto la posibilidad de especificar


vectores de punteros a funciones. Se trata del caso en que una familia de funciones
devuelven resultados del mismo tipo y reciben parmetros del mismo tipo.
Posteriormente, las funciones que forman parte del vector se pueden manipular de
igual forma que se manipulan los elementos de un vector.
EJEMPLO
/* Definicin de un vector de punteros a funciones */
void (*normas[4])(int) = {
norma1,
norma2,
norma3,
norma4,
};
... /* resto de sentencias */
/* Uso de una funcin dependiendo del caso actual */
valor = (*normas[selector])(codigo);

11.7.2. Funciones como parmetros


ntimamente relacionado con los punteros a funciones est el concepto de funciones
que reciben a otras funciones como parmetros. Resulta de gran ayuda cuando se
necesita que un programa realice una tarea que no se puede conocer en el momento de
la codificacin del mismo.
Resulta ventajoso cuando se quiere desarrollar una funcin de propsito general,
pero hay que utilizarlo con exquisito cuidado, ya que los efectos slo se pueden
conocer en tiempo de ejecucin y stos no son siempre previsibles.
EJEMPLO
/* Declaracin de la funcin que recibe otra como parmetro*/
int regla(int codigo, int (*norma)(int));
/* Declaracin de las dems funciones */
int mcyt_2002(int cod);
int mcyt_2003(int cod);
... /* resto de sentencias */
/* Llamada a la funcin */
valor_1 = regla(3, mcyt_2002);
... /* resto de sentencias */
valor_2 = regla(90, mcyt_2003);
... /* resto de sentencias */
/* Implementacin de la funcin que recibe otra */
int regla(int codigo, int (*norma)(int)) {
return norma(codigo);
}

350

Programacin en C

11.8. Argumentos de la funcin main()


Como ya se ha explicado convenientemente en la seccin 11.3, las funciones pueden
recibir argumentos. Pues bien, esto incluye a la funcin ms importante de todo
programa en C: la funcin main(). En este caso los argumentos se reciben desde el
exterior, desde la lnea de comandos.
La forma de acceder a los argumentos se habilita a travs de dos parmetros
formales:
argc: es un contador. Su valor es igual al nmero de argumentos escritos en
la lnea de comandos, contando el nombre del programa, que es el primer
argumento.
argv: es un puntero a una lista de cadenas de caracteres que contiene los
argumentos, uno por cadena.
Como se desprende de los dos parmetros que recibe la funcin main, ser
necesario procesar la lista de argumentos, es decir, sabiendo el nmero de argumentos
que se han recibido en la lnea de comandos (argc), se irn seleccionando los
argumentos segn la posicin que ocupen (argv[indice]).
EJEMPLO
#include <stdio.h>
#include <stdlib.h>
/* argumentos

en la funcin principal*/

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


int i;
if (argc<2){
printf("\nNo ha introducido ningn argumento");
exit(1); /* fin */
}
else {
printf("\nEl nombre del programa = %s", argv[0]);
for (i = 1; i < argc; i++){
printf("\nArgumento %d = %s", i, argv[i]);
}
}
}

11.9. Cuestiones y ejercicios


1.
2.
3.
4.

Qu es una funcin? Cita tres ventajas de la utilizacin de funciones en un


programa.
Qu se entiende por llamar a una funcin? Desde qu partes de un
programa se puede llamar a una funcin?
Se puede llamar a la misma funcin desde distintos puntos del programa?
Qu es lo que informa al compilador sobre las funciones que sern definidas
en el programa?

Funciones en C

5.
6.
7.
8.
9.
10.
11.

12.
13.
14.
15.
16.
17.
18.
19.
20.

351

Qu son los parmetros de una funcin? Cul es su propsito? Qu otro


nombre reciben?
Qu son los parmetros formales? Qu son los parmetros reales? Qu
relacin existe entre ambos tipos de parmetros?
Pueden coincidir los nombres de los parmetros formales de una funcin
con los nombres de otras variables definidas en otras funciones? Por qu?
Pueden coincidir los nombres de las variables locales definidas dentro de
una funcin con los nombres de variables globales definidas en el programa?
Por qu?
Pueden coincidir los nombres de los parmetros formales de una funcin
con los nombres de otras variables locales definidas dentro de la misma
funcin? Por qu?
Cul es la finalidad de la sentencia return?
Se pueden incluir varias sentencias return en una misma funcin? Se
pueden incluir ms de una expresin en una sentencia return para que sta
retorne ms de un valor?
Qu relacin existe entre el tipo de dato de la funcin (el que aparece en la
definicin de la funcin como tipo que devolver la funcin) y el valor
devuelto por la sentencia return?
Qu nombre reciben las funciones que no devuelven ningn valor? Cul es
el tipo de estas funciones?
Es necesario incluir la sentencia return a una funcin que no devuelve
ningn valor?
Qu son los prototipos de las funciones? Cul es su propsito? Dnde se
colocan normalmente?
Qu dos formas de comunicacin o paso de parmetros a una funcin
existen? En qu se diferencian cada una de ellas?
Qu diferencia existe entre pasar un dato simple a una funcin o pasar una
matriz?
Cmo debe de pasarse un parmetro real a una funcin para dicha funcin
pueda alterar su valor? Qu se pasa a la funcin?
Cmo se interpreta el nombre de una matriz cuando aparece como
argumento de una funcin?
El siguiente programa se escribi con mal estilo. Se utilizan referencias a
variables globales en vez de parmetros. Reescribir el programa sin
referencias a variables globales dentro de las funciones, usando un buen
estilo de programacin.
#include <stdio.h>
int a, b, i;
char c;
void proceso1(void) {
int temp;
temp = a + b;
for (i=1, i<=a, i++)
printf("%c", c);
printf("\n");
for (i=1, i<=b, i++)
printf("%c", c);

352

Programacin en C
printf("\n");
for (i=1, i<=temp, i++)
printf("%c", c);
printf("\n");
temp = a; a = b; b = temp;

21.

}
void main(void) {
scanf("%d %d", &a, &b);
scanf("%d", &c);
printf("A=%d, B=%d, C=%c\n", a, b, c);
proceso1();
printf("A=%d, B=%d, C=%c\n", a, b, c);
}

Se dispone del siguiente programa:

#include <stdio.h>
/* Programa: MBITO DE VARIABLES.
Desarrollado por: Prcticas de programacin.
Descripcin: Muestra el comportamiento en cuanto al
mbito de existencia de las variables globales,
locales y parmetros de las funciones en C. */
int x, y;
void f1(void) {
int x1, y1;
x1 = 101;
y1 = 102;
printf("\nFuncin f1.
} /* fin funcin f1 */

x1=%d, y1=%d", x1, y1);

int f2(int a, int b) {


int res;
res = a + b;
printf("\nFuncin f2. a=%d,b=%d,res=%d",a,b,res);
return (res);
} /* fin funcin f2 */
void main(void) {
int suma;
x = 1;
y = 2;
printf("\nFuncin main. x=%d, y=%d", x, y);
f1();
suma = f2(x, y);
printf("\Se vuelve a la funcin main. x=%d, y=%d, suma=%d", x, y, suma);
}

De acuerdo a la definicin de variables y funciones del programa, razonar y


puntualizar si las siguientes afirmaciones son verdaderas o falsas.
x e y son variables globales, accesibles desde todas las partes del programa,
es decir, desde las funciones f1(), f2() y main().
La variable x1 es local a la funcin f1(), y slo puede ser utilizada dentro
de ella.
La variable y1 puede ser utilizada en la funcin main().
Las variables x e y no puede ser utilizada dentro de las funciones f1() y
f2(), sino simplemente dentro de la funcin main.
La variables a y b son locales a la funcin f2(), por lo que slo pueden
ser utilizadas dentro de esta funcin.
La sentencia x = a sera vlida en la funcin f2().

Funciones en C

353

La sentencia b = x1 sera vlida en la funcin f1().


Las variables a y b no son visibles en la funcin main().
La sentencia y = y1 en la funcin f1() asignara el valor de y1 a la
variable global y, quedando modificada y.
Desde la funcin f2() nunca se podr acceder al valor de la variable
global x.
Desde la funcin main() nunca se podr acceder al valor de la variable a
de la funcin f2().
La variable suma puede utilizarse tanto dentro de la funcin main()
como de las otras dos funciones f1() y f2().
Son vlidas las siguientes sentencias dentro de la funcin main()?
o x = a;
o y = x;
o suma = x1;
Son vlidas las siguientes sentencias dentro de la funcin f1()?
o x = a;
o y1 = x;
o suma = x1;
Son vlidas las siguientes sentencias dentro de la funcin f2()?
o x = a;
o suma = a + b;
o res = x1;

22.

Escribir el prototipo de la funcin, incluyendo las declaraciones de los


argumentos formales, para cada una de las funciones que se describen a
continuacin.
Una funcin llamada muestra(), que no recibe entrada, simplemente
genera y devuelve una cantidad entera.
Una funcin llamada raiz(), que acepta dos argumentos enteros y
devuelve un resultado en coma flotante.
Una funcin llamada convertir(), que recibe un carcter y devuelve el
mismo carcter modificado segn un cdigo de conversin.
Una funcin llamada modulo(), que recibe un entero largo y lo devuelve
modificado eliminando su signo.
Una funcin llamada iniciar(), que inicia con ceros una lista que
recibe como parmetro.
Una funcin llamada ajustar(), que recibe un entero y lo devuelve
convertido a su prximo impar inmediatamente superior.
Una funcin llamada traza(), que calcula y devuelve la traza de una
matriz cuadrada recibida como parmetro.
Una funcin llamada longitud(), que devuelve la longitud de la cadena
recibida como argumento.
Una funcin llamada func1(), que acepte un argumento que es un
puntero a entero y retorna un carcter.
Una funcin llamada fnx(), que acepte una cadena de caracteres y un
puntero a real de doble precisin. No retorna nada.
Una funcin llamada presentacion(), que no recibe ningn parmetro
ni devuelve nada en salida, limitndose a escribir varios mensajes de texto
en pantalla

23.

Qu es la recursividad? Qu ventajas tiene hacer uso de ella?

354

24.
25.
26.
27.

28.

29.

30.
31.

Programacin en C

Explicar por qu algunos programas pueden ser resueltos con o sin


recursividad.
Explicar lo que sucede cuando se ejecuta un programa que contiene llamadas
recursivas, en trminos de la informacin aadida o eliminada en la pila.
Si se programa recursivamente un proceso iterativo ser necesariamente
ms eficiente el programa que el correspondiente a una versin no recursiva?
Dado un vector constituido de nmeros enteros y que contiene N elementos
siendo N >= 1, escribir una funcin que devuelva la suma de todos los
elementos mayores que el ltimo elemento del vector.
Dado un vector constituido de nmeros enteros y que contiene N elementos
siendo N >= 1, escribir una funcin que devuelva cierto si la suma de la
primera mitad de los enteros del vector es igual a la suma de la segunda
mitad de los enteros del vector.
Dados dos vectores A y B de longitud n y m respectivamente, n >= m,
cuyos elementos estn ordenados y no se repiten, determinar si todos los
elementos de B estn contenidos en A. Recordar que los elementos estn
ordenados, de esta manera basta con realizar un nico recorrido sobre cada
vector.
Multiplicar dos nmeros con sumas sucesivas recursivas.
Qu hace este programa?
void cosa( char *cad, int i){
if( cad[i] != '\0' ) {
cosa(cad,i+1);
printf("%c", cad[i] );
}
}

32.

33.

34.

Supngase un almacn con un conjunto de productos sobre los que se lleva


un seguimiento a travs de los siguientes datos: cdigo, venta diaria,
cantidad almacenada y variacin histrica de precio. Se pide realizar un
programa que muestre la mayor venta, cantidad y variacin de este conjunto
de productos. Para optimizar el nmero de lneas de cdigo haga uso de
estructuras y de punteros a funciones.
De un modo similar al ejercicio anterior, realizar un programa en C que
almacene la informacin de personas recluidas en prisin. Cada preso tendr
las siguientes caractersticas: nmero (correlativo), das que ha permanecido
recluido (1 < das <= 5000), faltas o castigos del preso (0 <= faltas <= 15),
edad (15 <= edad < 65), nivel de conducta (0 <= conducta <= 100). Se piden
los siguientes datos: a) promedio de das que lleva preso cada recluso; b)
promedio de faltas o castigos; c) promedio de edad. c) nmero del interno
con menos edad. d) nmero del interno con peor nivel de conducta.
Realizar un programa que reciba a travs de la lnea de comandos una serie
de palabras y que produzca como resultado la concatenacin de toda ellas.

12. Tipos de datos en C definidos por el usuario


El lenguaje C permite crear cinco tipos diferentes de datos definidos por el usuario
(programador). El primero que se va a estudiar es la estructura, un tipo anlogo al
registro o record de otros lenguajes, que permite agrupar bajo un mismo nombre un
conjunto de datos de distintos tipos. Dentro de este tipo existe otro un tanto especial,
el campo de bits, que permite acceder fcilmente a un bit especfico dentro de una
palabra de memoria. El tercer tipo de dato que se va a estudiar es la unin, que define
una misma zona de memoria como dos o ms tipos de datos diferentes. El cuarto tipo
es la enumeracin, que no es ms que una lista de smbolos. Por ltimo est la
construccin typedef, que crea un nuevo nombre para un tipo ya existente.
Finalmente, se aade una ltima seccin donde se incluye una lista de cuestiones y
ejercicios de repaso.

12.1. Estructuras
Una estructura es una agrupacin de datos, que pueden ser de distintos tipos, a los que
globalmente puede hacerse referencia bajo un mismo nombre.
Los datos individuales que forman la estructura se denominan elementos o
miembros de la estructura.
Los miembros de una estructura normalmente estn relacionados de una forma
lgica desde el punto de vista de la informacin que tratan.

12.1.1. Declaracin de estructuras


Para poder emplear una estructura es necesario hacer una declaracin de la misma
mediante la palabra reservada struct, estableciendo as un patrn, plantilla o tipo
de la estructura que se va a emplear. La forma general de declarar el tipo de una
estructura es:

Donde:

struct nombre_tipo_estructura {
tipo1 miembro1;
tipo2 miembro2;

tipon miembron;
};

struct es una palabra reservada requerida.


nombre_tipo_estructura es un identificador para dar nombre a la
estructura declarada.
tipox miembrox son las declaraciones de los miembros individuales
de la estructura.

- 355 -

356

Programacin en C

Es necesaria la inclusin de un punto y coma (;) despus de cerrar las


llaves.
Los miembros individuales de la estructura pueden ser de cualquier tipo, incluso
punteros, matrices u otras estructuras.
Los nombres de los miembros dentro de una estructura particular deben ser
diferentes, pero pueden coincidir con el nombre de variables fuera de la estructura o
con los nombres de los miembros de otras estructuras.
La declaracin de un tipo de estructura no ocupa memoria hasta que no se declara
una variable de dicho tipo.
EJEMPLO

Supngase que se desea disear una estructura para relacionar los datos
correspondientes a un alumno de primer curso. Esta estructura, a la que se va
a llamar alumno, deber contener el nombre, la direccin, el nmero de
matrcula, el telfono y las notas de 10 asignaturas.
La declaracin del tipo de la estructura puede hacerse del siguiente modo:
struct alumno {
char nombre [51];
char direccion [81];
unsigned long num_matricula;
unsigned long telefono;
float notas[10];
};

A continuacin se muestra de forma grfica el tipo estructura alumno y sus


miembros.
Alumno (estructura)
nombre
direccion
num_matricula
telefono
notas

(miembro)
(miembro)
(miembro)
(miembro)
(miembro)

La declaracin de esta estructura1 crea un nuevo tipo de dato definido por


el programador (tipo estructura alumno), con cinco miembros, pero an
no hay ninguna variable definida en memoria de ese tipo. Es decir, con la
declaracin anterior slo se define el tipo de la estructura.

12.1.2. Definicin de variables estructura


Existen dos formas de definir variables de tipo estructura:
En la declaracin del tipo de la estructura, especificando los nombres de
las variables estructuras despus de la llave de cierre.

Las declaraciones de estructuras en un programa suelen hacerse de forma global para permitir
la definicin de las variables del tipo estructura en los diversos mdulos o funciones, sin tener
que volver a declarar la plantilla de la estructura en cada funcin.

Tipos de datos en C definidos por el usuario

357

struct nombre_tipo_estructura {
tipo1 miembro1;
tipo2 miembro2;
...
tipon miembron;
} nombrevariable;

Despus de que el tipo de la estructura ha sido declarado, pueden


definirse variables de ese tipo de estructura como si se tratase de un tipo
predefinido.
struct nombre_tipo_estructura nombrevariable;
EJEMPLO

Si se desean declarar dos variables estructuras alumno1, alumno2 a la vez


que se declara el tipo alumno, la forma de hacerlo sera la iguiente:
struct alumno {
char nombre [51];
char direccion [81];
unsigned long num_matricula;
unsigned long telefono;
float notas[10];
} alumno1, alumno2;

En este caso es posible omitir, si se desea, el nombre del tipo estructura, por
lo que tambin sera vlida una declaracin como la que sigue:
struct {
char nombre [51];
char direccion [81];
unsigned long num_matricula;
unsigned long telefono;
float notas[10];
} alumno1, alumno2;

Si se omite el nombre del tipo estructura, se dice que se est declarando


variables estructura sin etiqueta. En este caso no se puede volver a definir
otras variables de este tipo de estructura, pues no existe nombre para el tipo.
EJEMPLO

Si se desean declarar dos variables estructuras alumno1, alumno2 del tipo


alumno declarado en el ejemplo anterior, la forma de hacerlo sera la
siguiente:
struct alumno alumno1, alumno2;

Tanto alumno1 como alumno2 son variables estructuras, que podrn


almacenar un nombre de hasta 50 caracteres, una direccin de hasta 80
caracteres, un nmero de matrcula, un telfono y las notas de 10
asignaturas.

12.1.3. Estructuras anidadas


Una variable estructura puede ser definida como miembro de otra estructura. En tales
situaciones, la declaracin de la estructura interna debe aparecer antes que la
declaracin de la estructura externa.

358

Programacin en C

EJEMPLO

Un programa en C tiene las siguientes declaraciones de estructuras:


struct fecha {
int dia;
int mes;
int anio;
};
struct cuenta {
unsigned long numero;
char tipo;
char titular[51];
float saldo;
struct fecha fapertura;
};
struct cuenta nuevocliente, antiguocliente;

La segunda estructura (cuenta) contiene otra estructura (fecha) como


uno de sus miembros, por lo que la declaracin de la estructura fecha debe
preceder a la de la estructura cuenta.

12.1.4. Procesamiento de una estructura


Los miembros de una variable estructura se procesan generalmente de modo
individual, como entidades separadas. Por tanto, debe ser posible acceder a los
miembros individuales de una variable estructura.
12.1.4.1. Referencia a elementos individuales de una estructura
Un miembro de una variable estructura puede accederse a travs del operador
miembro de una estructura, que se presenta por un punto (.), precedido por el
nombre de la variable estructura y seguido por el nombre del miembro.
variable.miembro
Un miembro de una variable estructura referenciado de esta forma se comporta
como cualquier variable ordinaria del tipo declarado para ese miembro de la
estructura.
El operador miembro de una estructura (.) pertenece al grupo de mayor prioridad,
por lo que se evala primero que cualquier otro operador de menor prioridad. Por ello,
la expresin &variable.miembro es equivalente a &(variable.miembro),
por lo que la expresin anterior accede a la direccin de comienzo del miembro de la
variable estructura y no a la direccin de comienzo de la variable estructura.
Del mismo modo, la expresin ++variable.miembro es equivalente a
++(variable.miembro), por lo que el operador ++ se aplica sobre el miembro
de la variable estructura y no sobre la variable estructura completa.
El operador subndice de una tabla ([]), tiene la misma prioridad que el operador
miembro de una estructura (.) y su asociatividad es de izquierda a derecha. Por ello,
en el caso de vectores miembros de una estructura, como en el caso que se muestra a
continuacin, la expresin variable.miembro[i] es equivalente a

Tipos de datos en C definidos por el usuario

359

(variable.miembro)[i], por lo que se est accediendo a elemento i del vector


miembro de la variable estructura.
struct {
int miembro [N];
} variable;

Del mismo modo, en el caso de vectores de estructuras, como en el caso que se


muestra a continuacin, la expresin variable[k].miembro es equivalente a
(variable[k]).miembro, por lo que se est accediendo al miembro del
elemento k del vector de estructuras.
struct {
int miembro;
} variable[M];
EJEMPLO

Un programa en C contiene las siguientes declaraciones:


struct alumno {
char nombre [51];
char direccion [81];
unsigned long num_matricula;
unsigned long telefono;
float nota[10];
} nuevo_alumno;

Para asignar valor al miembro telefono de la variable nuevo_alumno:


nuevo_alumno.telefono = 923451268;

Para asignar valor al miembro nombre de la variable nuevo_alumno:


strcpy(nuevo_alumno.nombre, "Antonio Lpez Prez");

Para cargar desde teclado el miembro num_matricula de la variable


nuevo_alumno:
scanf("%lu", &nuevo_alumno.num_matricula);

Para cargar desde teclado la tercera nota de la variable nuevo_alumno:


scanf("%f", &nuevo_alumno.nota[2]);

Para mostrar en pantalla el miembro direccion de la variable


nuevo_alumno:
puts(nuevo_alumno.direccion);

O tambin:
printf ("%s", nuevo_alumno.direccion);

Para mostrar en pantalla la dcima nota de la variable nuevo_alumno:


printf("%f", nuevo_alumno.nota[9]);

12.1.4.2. Iniciacin de una estructura en su definicin


A los miembros de una variable estructura se les puede asignar valores iniciales en su
definicin. Los valores iniciales deben aparecer en el mismo orden en que sern
asignados a los correspondientes miembros de la variable estructura, encerrados entre
llaves y separados por comas.
EJEMPLO

Este ejemplo ilustra la iniciacin de variables estructuras.

360

Programacin en C

struct fecha {
int dia;
int mes;
int anio;
};
struct cuenta {
unsigned long numero;
char tipo;
char titular[51];
float saldo;
struct fecha fapertura;
};
struct cuenta nuevocliente = {30056231452, 'C',
"SANTIAGO DENIA", 365800, 12, 5, 1989};

La representacin grfica de la variable estructura nuevocliente y el


valor de sus miembros se muestra a continuacin:
nuevocliente
numero 30056231452
tipo
'C'
titular
"SANTIAGO DENIA"
saldo
365800
fapertura
dia
12
mes
5
anio
1985

12.1.5. Matrices de estructuras


Es posible declarar matrices de estructuras, es decir, matrices en las que cada
elemento sea una estructura.
Pueden asignarse valores iniciales en la declaracin de una matriz de estructuras,
como cualquier otra matriz.
EJEMPLO

Un programa en C contiene las siguientes declaraciones:


struct alumno {
char nombre [51];
char direccion [81];
unsigned long num_matricula;
unsigned long telefono;
float notas[10];
};
struct alumno clase_primero[100];

En este ejemplo, clase_primero es un vector de estructuras con espacio


para almacenar los datos de 100 alumnos. Cada elemento de
clase_primero es una estructura de tipo alumno (cada elemento de
clase_primero representa un registro de alumno individual).

Tipos de datos en C definidos por el usuario

361

EJEMPLO

Un programa en C contiene las siguientes declaraciones:


struct fecha {
int dia;
int mes;
int anio;
};
struct persona {
char nombre[81];
struct fecha fnacimiento;
} agenda[] = {"ANA", 30, 12, 1973,
"JOSE", 13, 5, 1966,
"ROSA", 4, 2, 1977,
"SARA", 29, 7, 1963,
"ANTONIO", 31, 3, 1974};

En este ejemplo, agenda es un vector de estructuras cuyo tamao est sin


especificar. Los valores iniciales, por tanto, definirn el tamao del vector.
Cada fila de los valores de iniciacin contiene 4 constantes. Estas constantes
representan los valores iniciales, esto es, un nombre, un da, un mes y un ao
para cada elemento del vector. Como hay 5 conjuntos de constantes (5 filas),
el vector contendr 5 elementos numerados del 0 al 4.
EJEMPLO

Un programa en C contiene las siguientes declaraciones:


#define ALUMNOS 45
#define NOTAS
5
struct fecha {
int dia;
int mes;
int anio;
};
struct alumno {
char nombre [81];
struct fecha fnac;
float nota[NOTAS];
};

/* Fecha de nacimiento */

struct alumno clase[ALUMNOS];


int i, k;

El siguiente fragmento de cdigo introducir desde teclado los datos para el


vector clase.
for (i=0; i<ALUMNOS; i++) {
printf("Nombre?: ");
gets(clase[i].nombre);
printf("Fecha de nacimiento?:\n");
printf("Da?: ");
scanf("%d", &clase[i].fnac.dia);
printf("Mes?: ");
scanf("%d", &clase[i].fnac.mes);
printf("Ao?: ");
scanf("%d", &clase[i].fnac.anio);

362

Programacin en C

for (k=0; k<NOTAS; k++) {


printf("Nota %d?: ", k+1);
scanf("%f", &clase[i].nota[k]);
}
}

Obsrvese que para referenciar elementos de una estructura anidada se


emplea sucesivamente el operador punto:
scanf("%d", &clase[i].fnac.dia);

El siguiente fragmento de cdigo muestra en pantalla los datos del vector


clase.
for (i=0; i<ALUMNOS; i++) {
printf("Nombre: %s\n", clase[i].nombre);
printf("Fecha nacimiento: %02d/%02d/%4d\n",
clase[i].fnac.dia,
clase[i].fnac.mes,
clase[i].fnac.anio);
for (k=0; k<NOTAS; k++)
printf("Nota
%d:
%.2f\n",
k+1,
clase[i].nota[k]);
}

Por ltimo, se presenta de forma grfica la representacin del vector de


estructuras clase y de los miembros de cada uno de sus elementos
individuales

clase[0]

clase[1]

PEDRO PEREZ
12
6
1970

clase[0].nombre

5.5

clase[0].nota
clase[1].nombre

7.3 4.9 5.2

MARIA GARCIA
31
3
1972
3.4 4.1

clase[44]

clase[0].fnac

clase[1].fnac
6.2

SUSANA RODRIGUEZ
8
7
1971
8

6.5

clase[0].fnac.dia
clase[0].fnac.mes
clase[0].fnac.anio

clase[1].fnac.dia
clase[1].fnac.mes
clase[1].fnac.anio

clase[1].nota
clase[1].nota[4]

5.5 7.9

clase[44].nota[2]

12.1.6. Punteros y estructuras


Se pueden declarar punteros a estructuras. La forma de hacerlo es igual que para
cualquier otro tipo de dato de C.
struct nombre_tipo_estructura *nombrepuntero;

Tipos de datos en C definidos por el usuario

363

Para apuntar con un puntero a una variable estructura, se utiliza el operador &
(operador direccin), igual que para cualquier otro tipo de dato de C.
struct nombre_tipo_estructura nombrevariable;
nombrepuntero = &nombrevariable;
En este momento el puntero nombrepuntero apunta a la variable estructura
nombrevariable, y esto permite una nueva forma de acceder a sus miembros,
utilizando el operador flecha (->), llamado operador puntero a miembro de una
estructura, y constituido con los smbolos (-) y (>).
Se utiliza de forma anloga a como se utiliza el operador punto (.), pero en lugar
de utilizar el nombre de la variable estructura se utiliza el nombre del puntero.
nombrepuntero->nombremiembro;
Tambin se puede utilizar el operador asterisco (*) pero es muy infrecuente:
(*nombrepuntero).nombremiembro;
En este segundo caso, obsrvese la necesidad de utiliza los parntesis, por la mayor
prioridad del operador (.) respecto al operador (*).

12.1.7. Operaciones con estructuras como unidades


A parte de las operaciones que ya se han visto con variables estructuras, esto es,
acceder individualmente a sus miembros (con los operadores . o ->), obtener su
direccin (con el operador &), e iniciarla en su declaracin; tambin es posible
realizar ciertas operaciones que acten sobre toda la estructura como una unidad,
concretamente:
Asignar una estructura completa a otra, como una unidad 2 (siempre que
ambas sean del mismo tipo), con el operador asignacin (=).
Pasar una estructura completa como argumento a una funcin 3 (tanto por
valor como por referencia), y devolver una estructura como valor de retorno
de una funcin.
Sin embargo, las estructuras no pueden ser comparadas.
EJEMPLO

Un programa en C contiene las siguientes declaraciones:


struct fecha {
int dia;
int mes;
int anio;
};

Esta caracterstica se incluye en el C estndar ANSI. En compiladores que no soportan C


ANSI no es posible asignar estructuras como unidades de informacin, por lo que no se pueden
pasar por valor estructuras a funciones, ni pueden ser devueltas por stas como valor de retorno.
Para ello, en esos compiladores se debe trabajar con punteros.
3 A la hora de pasar estructuras a funciones se ha de tener en cuenta que si sta es muy
complicada (con muchos miembros) se escribe y se borra mucha informacin en la pila, con la
consiguiente prdida de tiempo, por lo que se aconseja utilizar punteros en estos casos, es decir
pasarlas por referencia aunque no se vayan a modificar dentro de la funcin.

364

Programacin en C

struct alumno {
char nombre [81];
struct fecha fnac; /* Fecha de nacimiento */
float nota[NOTAS];
} anterioralumno, nuevoalumno;

Supngase que a todos los miembros de anterioralumno se le han


asignado valores individuales. En compiladores ANSI C es posible copiar
estos valores a nuevoalumno como una unidad, simplemente escribiendo:
nuevoalumno = anterioralumno;

12.2. Campos de bits


En algunas aplicaciones puede desearse trabajar con elementos de informacin que
consten de unos pocos bits (por ejemplo un indicador de un bit para una condicin
verdadera/falsa), un entero de 3 bits cuyo rango vaya de 0 a 7, un carcter ASCII de 7
bits). Varios datos de este tipo se pueden empaquetar en una sola palabra de memoria.
Para hacer esto la palabra se subdivide en campos de bits individuales.
Estos campos de bits se definen como miembros de una estructura, de forma que se
permite acceder a los bits contenidos en un byte o en una palabra de forma individual,
como cualquier otro miembro de una estructura.
As pues, un campo de bits no es ms que un tipo especial de miembro de una
estructura que define la longitud en bits que debe tener cada elemento.
En trminos generales la descomposicin de una palabra de memoria en distintos
campos de bits puede escribirse como:
struct nombre_tipo_estructura {
tipo1 miembro_campo_bits1:longitud1;
tipo2 miembro_campo_bits2:longitud2;
...
tipon miembro_campo_bitsn:longitudn;
};

Donde los miembros individuales (los campos de bits) tienen el mismo significado
que en una declaracin de estructura, pero ahora, cada declaracin de miembro debe
incluir una especificacin del tamao en bits del campo correspondiente. Para
hacerlo, el nombre del miembro debe ir seguido por dos puntos (:) y un entero sin
signo que indique el tamao del campo.
Un campo de bits debe ser declarado como int o unsigned int. Los campos
de bits de longitud 1 deben ser declarados como unsigned porque un nico bit no
puede tener signo.
Normalmente, los programas que incluyen campos de bits no son directamente
transportables entre mquinas. Esto es debido a que la asignacin de estos campos de
bits puede variar de un compilador a otro. Por ejemplo, algunos compiladores pueden
asignar los campos de bits de derecha a izquierda, mientras que otros los asignan de
izquierda a derecha (deber consultarse, por tanto, el manual del compilador
correspondiente). Se supone asignacin de derecha a izquierda en los ejemplos que se
muestren en este apartado.

Tipos de datos en C definidos por el usuario

365

No se pueden construir matrices de campos de bits y no tienen direcciones (pues la


menor unidad direccionable es el byte), por lo que no se puede utilizar el operador
direccin (&).
EJEMPLO

Un programa en C contiene las siguientes declaraciones:


struct muestra
unsigned a :
unsigned b :
unsigned c :
unsigned d :
};

{
1;
3;
2;
1;

struct muestra v;

La primera declaracin define una estructura que se subdivide en cuatro


campos de bits, llamados a, b, c y d. Estos campos tienen tamaos de 1 bit,
3 bits, 2 bits y 1 bit respectivamente. As, los campos de bits ocupan un total
de 7 bits dentro de una palabra en memoria. El resto de los bits dentro de la
palabra quedarn sin usar.
A continuacin se ilustra un boceto de los campos de bits dentro de la
palabra, suponiendo una palabra de 16 bits y asignacin de derecha a
izquierda.
15 14 13 12 11 10

sin uso

bit nmero

La segunda declaracin establece que v es una variable de estructura tipo


muestra. As, v.a es un campo dentro de v de tamao 1 bit.
Anlogamente v.b es un campo de 3 bits, y as sucesivamente.

12.3. Uniones
Las uniones, son similares a las estructuras, ya que contienen miembros cuyos tipos
de datos pueden ser diferentes. La diferencia de las uniones con las estructuras es que
los miembros que componen una unin comparten la misma rea de almacenamiento
dentro de la memoria, mientras que cada miembro de una estructura tiene asignada su
propia rea de almacenamiento.
Las uniones se usan para ahorrar memoria. Son tiles para aplicaciones que
involucren mltiples miembros donde no se necesita asignar valores a todos los
miembros simultneamente. Y son muy utilizadas cuando se necesita manejar
diferentes clases de datos en una nica rea de memoria.
La forma de declarar una unin es anloga a la de las estructuras. Tambin la
forma de acceder a los elementos, bien por nombre, bien mediante puntero.
EJEMPLO

Se supone que se desea acceder a un byte de dos formas distintas: una como
entidad de informacin (un carcter), y la otra bit a bit. Para ello se declara
un campo de bits y una unin de la siguiente forma:

366

Programacin en C

struct byte {
unsigned a:1;
unsigned b:1;
unsigned c:1;
unsigned d:1;
unsigned e:1;
unsigned f:1;
unsigned g:1;
unsigned h:1;
};
union caracter {
char car;
struct byte bit;
};
union caracter car_1;

La unin caracter almacena los miembros car y bit en el mismo byte


en memoria, por lo que puede accederse a la informacin de dos formas:
car_1.car;
car_1.bit.c;

/* Acceso al byte como un tipo char normal */


/* Acceso al tercer bit del byte */

EJEMPLO

Escribir un programa que pida una lnea de texto por teclado y visualice en
binario, segn el cdigo ASCII, los caracteres de la misma. Se utilizar una
unin que contenga un carcter y una estructura de campos de bits para
contener el octeto correspondiente a cada carcter de la lnea de texto.
#include <stdio.h>
struct octeto {
unsigned a : 1;
unsigned b : 1;
unsigned c : 1;
unsigned d : 1;
unsigned e : 1;
unsigned f : 1;
unsigned g : 1;
unsigned h : 1;
};
union bits {
char car;
struct octeto bit;
};
void visualizabit (union bits);
void main (void) {
char frase[81];
int i;
union bits codigo;
printf("Introduzca una frase (mx. 80 caracteres): ");
gets(frase);
i = 0;
printf("Carcter ASCII binario\n");
printf("-------- ----- -------\n");

Tipos de datos en C definidos por el usuario

367

while (frase[i] != '\0') {


codigo.car = frase[i];
printf("%4c %8d ", codigo.car, codigo.car);
visualizabit(codigo);
printf("\n");
i++;
}
}
void
if
if
if
if
if
if
if
if
}

visualizabit (union bits x)


(x.bit.h) printf("1"); else
(x.bit.g) printf("1"); else
(x.bit.f) printf("1"); else
(x.bit.e) printf("1"); else
(x.bit.d) printf("1"); else
(x.bit.c) printf("1"); else
(x.bit.b) printf("1"); else
(x.bit.a) printf("1"); else

{
printf("0");
printf("0");
printf("0");
printf("0");
printf("0");
printf("0");
printf("0");
printf("0");

12.4. Enumeraciones
Una enumeracin es un tipo de dato que consta de una lista de constantes enteras con
signo. Los nombres de estas constantes representan los valores legales que puede
tomar una variable que se defina de ese tipo.
La declaracin de la plantilla o tipo de una enumeracin se realiza con la palabra
reservada enum y la lista de constantes encerrada entre parntesis. Tambin se les
suele llamar miembros de la enumeracin a cada una de las constantes de la lista.
enum nombre_tipo_enumeracion {
constante1,
constante2,
...
constanten,
};

Los nombres de las constantes deben ser todos diferentes entre s, adems de
distintos a los nombres de otros identificadores cuyo mbito sea el mismo que el
mbito de la enumeracin.
La definicin de variables tipo enum se realiza de forma anloga a como se define
una variable estructura:
enum nombre_tipo_enumeracion variable_enumeracion;

O bien:
enum nombre_tipo_enumeracion {
constante1,
constante2,
...
constanten,
} variable_enumeracion;

O bien:

368

Programacin en C

enum {
constante1,
constante2,
...
constanten,
} variable_enumeracion;

Internamente los elementos que forman parte de una enumeracin son constantes
enteras con signo, cuyos valores son asignados automticamente por el compilador,
comenzando por cero para la primera constante, uno para la segunda, y as
sucesivamente.
EJEMPLO

Puede pensarse en una variable llamada diasemana que slo puede tomar
los siete siguientes valores: lunes, martes, miercoles, jueves,
viernes, sabado y domingo.
Se comienza por declarar un nuevo tipo enumeracin al que se va denominar
dia.
enum dia {lunes, martes, miercoles, jueves, viernes,
sabado, domingo };

La declaracin anterior crea un nuevo tipo de dato (tipo enum dia) cuyas
variables que se definan de dicho tipo slo podrn tomar uno de los siete
valores especificados.
Posteriormente la definicin de la variable diasemana se realiza de la
siguiente forma:
enum dia diasemana;

Internamente los elementos que forman parte de una enumeracin son


constantes tipo int con los siguientes valores:
lunes
martes
miercoles 2
jueves
viernes
sabado
domingo

0
1
3
4
5
6

Al declarar un tipo enumerado, se pueden redefinir los valores que el compilador


asigna automticamente, iniciando las constantes con los valores que se deseen,
recordando que cada constante no iniciada toma un valor superior en una unidad a la
anterior.
EJEMPLO

Un programa en C contiene las siguientes declaraciones:


enum monedas {peseta=1, duro=5, cincoduros=25, veinteduros=100};
enum mobiliario {silla, mesa, puerta=100, escalera, armario};

En esta segunda declaracin los valores que tomarn las constantes son:
silla
mesa
puerta
escalera
armario

0
1
100
101
102

Tipos de datos en C definidos por el usuario

369

12.4.1. Referencia a las variables enumeracin


Para dar valor a una variable enumeracin se utiliza el nombre de la constante
correspondiente.
EJEMPLO

Supngase la definicin de la variable enumerada color.


enum colores {rojo, amarillo=4, verde, azul};
enum colores color;

Los nicos operadores que pueden acompaar a los tipos enumerados son el
operador de asignacin y los de relacin.
EJEMPLO

Tomando la declaracin de la variable enumerada color:


Un ejemplo de asignacin de un valor podra ser:
color = verde;

Un ejemplo de una comparacin para la toma de una decisin


podra ser:
if (color == amarillo)

No es posible leer de teclado o de un fichero una variable enumerada y asignarle


un valor. Podra leerse un entero y asignrselo a la variable enumerada, aunque esto
no es muy comn.
Tampoco es posible escribir en la pantalla o en un fichero una variable enumerada.
Slo se podra mostrar el valor entero de una variable enumerada.
Si se necesitan visualizar las cadenas equivalentes a las constantes que se incluyen
en las enumeraciones, puede utilizarse una sentencia switch(), como se muestra en
el siguiente ejemplo.
EJEMPLO

En el siguiente fragmento de cdigo C se ilustra como mostrar en pantalla el


valor de una variable enumeracin.
enum arq (romanico, plateresco, gotico, neoclasico};
enum arq estilo;
...
switch (estilo) {
case romanico
: printf ("romanico");
break;
case plateresco : printf ("plateresco");
break;
case gotico
: printf ("gotico");
break;
case neoclasico : printf ("neoclasico");
break;
}

A menudo se puede incrementar la claridad lgica de un programa utilizando


variables enumeradas. Las variables de enumeracin son particularmente tiles como
indicadores, para indicar varias opciones para realizar un clculo, o para identificar

370

Programacin en C

condiciones que puedan haber surgido como resultado de clculos internos anteriores.
Desde esta perspectiva se aconseja el uso de variables de enumeracin dentro de un
programa complejo. No obstante, debe quedar claro que se pueden usar variables
enteras en lugar de variables de enumeracin. Por lo tanto, las variables de
enumeracin, a parte de facilitar la codificacin y la legibilidad de los programas, no
proporcionan nuevas capacidades.
EJEMPLO

Se muestran dos versiones de un mismo fragmento de cdigo C (con y sin


variables enumeradas), para apreciar la claridad lgica que aporta el uso de
tipos enumerados.
Versin 1: Sin tipos enumerados
char car_tecleado;
int car_tipo;
.......
getchar(car_tecleado);
.......
if (car_tecleado == '=')
car_tipo = 1; (* SIGNO IGUAL
*)
else
if (car_tecleado == '*' || car_tecleado == '/' ||
car_tecleado == '+' || car_tecleado == '-')
car_tipo = 2; (* OPER. ARITMETICOS
*)
else
if (car_tecleado == '>' || car_tecleado == '<')
car_tipo = 3; (* OPER. RELACIONAL
*)
else
if ( (car_tecleado >= 'A' && car_tecleado <= 'Z') ||
(car_tecleado == '') )
car_tipo = 4; (* LETRA MAYUSCULA
*)
else
if ( (car_tecleado >= 'a' && car_tecleado <= 'z') ||
(car_tecleado == '') )
car_tipo = 5; (* LETRA MINUSCULA
*)
else
if (car_tecleado >= '0' && car_tecleado <= '9')
car_tipo = 6; (* DIGITO
*)
else
if (car_tecleado >= 0 && car_tecleado <= 31)
car_tipo = 7; (* CARACTER DE CONTROL *)
else
car_tipo = 8; (* SIMBOLO
*)
...
printf("El caracter tecleado es ");
switch (car_tipo) {
case 1 : printf ("un signo de igual. \n");
case 2 : printf ("un operador aritmtico. \n");
case 3 : printf ("un operador relacional. \n");
case 4 : printf ("una letra en mayscula. \n");
case 5 : printf ("una letra en minscula. \n");
case 6 : printf ("un dgito. \n");
case 7 : printf ("un carcter de control. \n");
case 8 : printf ("un smbolo del teclado. \n");
}

Tipos de datos en C definidos por el usuario

371

Versin 2: Con tipos enumerados


char car_tecleado;
enum tipo_caracter (MAYUSCULA, MINUSCULA, DIGITO, SIMBOLO,
OPER_ARITMETICO, OPER_RELACIONAL, CONTROL,
SIGNO_IGUAL);
enum tipo_caracter car_tipo;
.......
getchar(car_tecleado);
.......
if (car_tecleado == '=')
car_tipo = SIGNO_IGUAL;
else
if (car_tecleado == '*' || car_tecleado == '/' ||
car_tecleado == '+' || car_tecleado == '-')
car_tipo = OPER_ARITMETICO;
else
if (car_tecleado == '>' || car_tecleado == '<')
car_tipo = OPER_RELACIONAL;
else
if ( (car_tecleado >= 'A' && car_tecleado <= 'Z') ||
(car_tecleado == '') )
car_tipo = MAYUSCULA;
else
if ( (car_tecleado >= 'a' && car_tecleado <= 'z') ||
(car_tecleado == '') )
car_tipo = MINUSCULA;
else
if (car_tecleado >= '0' && car_tecleado <= '9')
car_tipo = DIGITO;
else
if (car_tecleado >= 0 && car_tecleado <= 31)
car_tipo = CONTROL;
else
car_tipo = SIMBOLO;
...
printf("El caracter tecleado es ");
switch (car_tipo) {
case SIGNO_IGUAL
: printf ("un signo de igual. \n");
case OPER_ARITMETICO: printf ("un operador aritmtico. \n");
case OPER_RELACIONAL: printf ("un operador relacional. \n");
case MAYUSCULA
: printf ("una letra en mayscula. \n");
case MINUSCULA
: printf ("una letra en minscula. \n");
case DIGITO
: printf ("un dgito. \n");
case CONTROL
: printf ("un carcter de control. \n");
case SIMBOLO
: printf ("un smbolo del teclado. \n");
}

12.5. Construccin typedef


Esta construccin permite que el programador defina nuevos nombres o alias para los
tipos ya existentes. No se trata, por tanto, de la creacin de nuevos tipos de datos.
La forma general de esta construccin es:
typedef tipo nombre;

372

Programacin en C

Donde tipo es cualquier tipo de datos legal, y nombre el alias. El nuevo nombre
es una adicin a los tipos de existentes, no una sustitucin de stos.
Adems, es posible utilizar distintos nombres para un mismo tipo de dato,
pudindose usar la construccin typedef tambin para los tipos de datos complejos
y compuestos.
EJEMPLO

Se define un alias para el tipo de dato float.


typedef float real;

Con esta definicin no se ha creado un tipo nuevo sino que se emplea otro
nombre para un tipo de dato ya existente que sigue estando activo.
EJEMPLO

Sean las siguientes declaraciones en un programa C:


typedef long entero_largo;
typedef long entero_grande;

De esta forma en el programa se podr utilizar para definir una variable de


tipo long int llamada longitud, cualquiera de las siguientes
definiciones:
long longitud;
entero_largo longitud;
entero_grande longitud;
EJEMPLO

Sea un programa C con las siguientes declaraciones:


typedef struct tipo_usuario {
char nombre [40];
char login [16];
int id;
} usuario;
usuario lista [200];

El identificador usuario no representa en este caso a una variable, sino el


alias definido para el nuevo tipo de dato estructura struct
tipo_usuario.
Si no se hubiera utilizado typedef debera haberse definido la variable
lista de la siguiente forma:
struct tipo_usuario lista [200];

El uso de typedef proporciona ventajas en los siguientes casos:


1. Hacer ms portable el cdigo de una mquina a otra, pues si se manejan
tamaos distintos para un tipo de dato determinado ser suficiente
modificar la sentencia typedef para ajustarse al nuevo tamao.
2. Hacer que el tipo de dato sea ms explcito con respecto a su uso dentro
del programa.
EJEMPLO

Se recuerda que en sistemas con longitud de palabra de 2 bytes, el tipo

Tipos de datos en C definidos por el usuario

373

short y el tipo int ocupan 2 bytes, y el tipo long ocupa 4 bytes;


mientras que en sistemas con longitud de palabra de 4 bytes, el tipo short
ocupa 2 bytes, y los tipos int y long ocupan 4 bytes.
Por ello, si el programa a codificar es necesario que trabaje con enteros de 4
bytes, independientemente de la longitud de palabra del sistema, podra
utilizarse typedef para dar nombre a un nuevo tipo que llamaremos
entero:
Para un sistema de 16 bits: typedef long entero;
Para un sistema de 32 bits: typedef int entero;
As, para portar el programa entre diferentes sistemas es suficiente modificar
la sentencia typedef.
EJEMPLO

Puede utilizarse typedef para definir un tipo de dato importe para ser
utilizado con variables que representan importes, de la siguiente forma:
typedef float importe;

Posteriormente, las variables se declararn de este nuevo nombre:


importe precio;
importe total;

12.6. Cuestiones y ejercicios


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

Qu es una estructura? En qu se diferencia una estructura de una matriz?


Qu es un miembro? Cul es la relacin entre un miembro y una
estructura?
Describir la sintaxis para definir una estructura. Pueden ser iniciados los
miembros individuales dentro de una declaracin de un tipo estructura?
En qu se diferencian las declaraciones de variables estructura de las
declaraciones de tipos estructura?
Se puede definir como miembro de una estructura a su vez otra estructura?
En caso afirmativo, escribir un ejemplo.
Puede incluirse una matriz como miembro de una estructura? En caso
afirmativo, escribir un ejemplo.
Puede una matriz tener estructuras como elementos? En caso afirmativo,
escribir un ejemplo.
Cul es el mbito del nombre de un miembro?
Pueden coincidir los nombres de los miembros de una estructura con los
nombres de los miembros de otras estructuras?
Pueden coincidir los nombres de los miembros de una estructura con los
nombres de otras variables o constantes definidas dentro del mismo mdulo
del programa fuente?
Cmo puede accederse a un miembro en particular de una variable
estructura?
Si un miembro de una variable estructura es a su vez una estructura, cmo
se accede a los miembros de esta estructura interna?

374

13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.

28.

29.
30.
31.

Programacin en C

Cul es la prioridad del operador punto (.)? Cul es su asociatividad?


Cmo se usa el operador punto (.) para acceder a un miembro de un
elemento dentro de una matriz de estructuras?
Qu operaciones pueden aplicarse a estructuras completas?
Puede pasarse por valor una estructura completa a una funcin? Y si esta
estructura incluye como uno de sus miembros una matriz?
Puede devolverse como valor de retorno de una funcin una estructura
completa? Y si esta estructura incluye como uno de sus miembros una
matriz?
Cmo se puede determinar el tamao de una estructura completa? En qu
unidad de indica este tamao?
Cmo se declara una variable puntero a una estructura?
Puede obtenerse la direccin de un miembro particular de una estructura?
Cmo se puede acceder a un miembro individual de una estructura en
trminos de la variable puntero que direcciona toda la estructura?
Cul es la prioridad del operador ->? Cul es su asociatividad?
Supngase que una variable estructura contiene un miembro que es un
vector. Cmo pueden accederse a los elementos de este vector?
Qu es una unin? En qu se diferencia de una estructura?
Para qu tipo de aplicaciones son tiles las uniones?
Definir una estructura, llamada complejo, que conste de dos miembros
reales que recibirn los nombres de real e imaginario respectivamente.
Declarar una variable x de estructura complejo, y asignarle por iniciacin
los valores 1.3 y 2.5 a sus miembros x.real y x.imaginario
respectivamente.
Declarar una variable puntero, px, que apunte a estructuras complejo.
Asignarle la direccin de la variable estructura x y referenciar los miembros
de esta variable estructura en trminos de la variable puntero.
Declarar un vector, vc, de 100 elementos estructuras complejo.
Escribir las sentencias apropiadas para leer de teclado el complejo nmero
78 del vector vc.
Declarar una estructura llamada registro que contenga los siguientes
miembros:
Una cantidad entera llamada ganados.
Una cantidad entera llamada perdidos.
Una cantidad real llamada porcentaje.

32.

Declarar una estructura llamada equipo que contenga los siguientes


miembros:
Una cadena de caracteres de longitud 41 llamada nombre.
Una estructura llamada posicion, del tipo registro.

33.

Declarar una variable estructura llamada t de tipo equipo. Iniciar t con los
siguientes valores:
nombre: Osos de Chicago.
ganados: 4.
perdidos: 2.
porcentaje: 87.5.

Tipos de datos en C definidos por el usuario

34.
35.
36.
37.
38.
39.
40.
41.

42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.

53.
54.
55.
56.

375

Escribir una sentencia que muestre el tamao que ocupa la variable t.


Escribir un vector de 48 elementos llamado liga. Sus elementos sern
estructuras de tipo equipo.
Escribir las sentencias apropiadas para leer de teclado todo el vector liga.
Qu son los campos de bits?
Qu tipos de datos deben asociarse a los campos de bits?
En qu orden se colocan los campos de bits dentro de una palabra? Este
criterio es uniforme en todos los compiladores?
Puede obtenerse la direccin de comienzo en memoria de un campo de bits?
Escribir una declaracin de estructura para cada una de las siguientes
situaciones. Suponer una palabra entera de 16 bits.
Definir tres campos de bits llamados a, b y c con tamaos
respectivamente de 6, 4 y 6 bits (sin signo).
Declarar una variable v de tipo estructura con la composicin
definida en el punto anterior. Asignar los valores iniciales 3, 5 y 7
respectivamente a los tres campos de bits. Son suficientemente
grandes estos tres campos para acomodar estos tres valores?
Qu valores mximos permitirn almacenar cada uno de los
campos de bits anteriores?
Qu es una enumeracin? Cmo se define una enumeracin?
Qu normas hay que respetar a la hora de dar nombre a las constantes
enumeracin?
Cul es el valor entero del primer miembro de un tipo enumerado?
Mencionar las reglar para asignar valores numricos a las constantes de
enumeracin. Qu valores son asignados por omisin?
Pueden dos o ms constantes enumeracin tener el mismo valor numrico?
Pueden dos o ms constantes enumeracin con distinto valor numrico tener
el mismo nombre?
Puede una constante enumeracin tener el mismo nombre que una variable
del programa?
Qu ventajas tiene el uso de variables de enumeracin en un programa?
Puede leerse el valor de una variable de enumeracin de teclado?
Puede escribirse el valor de una variable de enumeracin en pantalla?
Declarar un tipo enumeracin llamado indicadores que tenga los
siguientes miembros: primero, segundo, tercero, cuarto y
quinto.
Qu valores numricos toma cada una de las constantes del tipo enumerado
indicadores?
Definir una variable de enumeracin llamada suceso, del tipo
indicadores anteriormente declarado.
Qu valores podr tomar la variable suceso en un programa?
Definir dos variables de enumeracin llamadas soprano y bajo. Las
constantes de la enumeracin deben ser las siguientes, con los siguientes
valores enteros:
do
re

1
2

376

Programacin en C

mi
fa
sol
la
si

57.

Declarar un tipo enumeracin llamado dinero, que tenga los siguientes


miembros, con los siguientes valores enteros para cada uno de ellos.
penique
niquel
dime
cuarto
medio
dolar

58.

3
4
5
6
7

1
15
10
25
50
100

59.

Definir una variable de tipo dinero, llamada moneda. Asignar el valor


inicial dime a moneda.
En la siguiente declaracin, determinar el valor inicial de cada miembro.

60.
61.

Cul es el propsito del uso de typedef en C?


Crea typedef un nuevo tipo de datos? Por qu?

enum brujula {norte = 2, sur, este = 1, oeste};

13. Estructuras externas de datos - Ficheros


Los datos estructurados como las matrices, o las propias estructuras (struct), se
denominan internas, ya que dichos datos se almacenan1 y procesan directamente en
la memoria central del ordenador, lo que implica una doble limitacin en cuanto al
tratamiento de la informacin:
1. Al ser muy limitado el tamao de la memoria central del ordenador, en
comparacin con el tamao de las memorias externas (discos magnticos,
cintas...), tambin queda limitado el tamao de la informacin con la que se
puede trabajar.
2. Todos los datos almacenados en la memoria central del ordenador tienen un
corto perodo de vida, que es el tiempo que dure la ejecucin del programa.
Cuando el programa finaliza, irremediablemente desaparecen.
Para superar estas limitaciones se utilizan dispositivos de almacenamiento
secundario (memorias externas o auxiliares) como discos magnticos o cintas, donde
el espacio disponible para almacenamiento de datos es mucho ms grande que el que
se dispone en la memoria central del ordenador y, adems, el tiempo de vida de los
datos es ilimitado (hasta que no sean borrados explcitamente).
Las estructuras de datos aplicadas a coleccin de datos en almacenamientos
secundarios se llaman archivos o ficheros.
En contraste con las estructuras de datos internas, el tamao de un fichero no viene
fijado por el propio programa, es decir, los ficheros no requieren tamaos
predeterminados, estando slo limitado su tamao por la propia capacidad de la
memoria externa del ordenador.
En este captulo se van a presentar las funciones de entrada y salida de informacin
que van a tener como origen/destino los datos de/a estas estructuras externas que son
los ficheros. Para ello, el captulo se ha dividido en once secciones, con la
organizacin que sigue: la primera seccin introduce el concepto o definicin de lo
qu se entiende por un fichero, completndose esta seccin con la terminologa bsica
asociada a estas estructuras de datos externas; la segunda seccin introduce y
diferencia los dispositivos fsicos secuenciales de los direccionables; las dos
siguientes secciones abordan los temas de la organizacin de los archivos y del acceso
a los archivos respectivamente; la quinta seccin introduce el concepto de archivo
desde el punto de vista del lenguaje C, mientras que en la sexta seccin se presentan
las principales operaciones que se pueden llevar a cabo con un archivo en C; la
sptima seccin se centra en las funciones especficas que aporta la biblioteca
estndar del lenguaje C para llevar a cabo las operaciones de entrada y salida con los
ficheros de texto. La octava y novena seccin describen respectivamente la escritura y
lectura de archivos de texto delimitados, tabulados y encolumnados. La dcima
seccin describe las funciones especficas de la biblioteca estndar del lenguaje C
para operaciones de entrada y salida con los ficheros binarios y, por ltimo, la seccin
undcima cierra el captulo con una coleccin de cuestiones y ejercicios.

Este tipo de almacenamiento se conoce como almacenamiento principal o primario.


- 377 -

378

Programacin en C

13.1. Nocin de archivo: estructura jerrquica


Antes de abordar el concepto de archivo, es importante clarificar las nociones de
campo y de registro.
Los caracteres se agrupan en campos de datos. Un campo representa un dato
elemental relativo a una entidad particular, como por ejemplo el nombre de una
persona, su edad, su nmero de identificacin dentro de su empresa, su fecha de
nacimiento...
Un campo est caracterizado por su tamao o longitud y su tipo de dato. Todo
campo tiene asociado un identificador a travs del cual se puede referenciar y
acceder a su contenido.
Los campos suelen dividirse con frecuencia en subcampos. Por ejemplo, un campo
que represente una fecha, podr subdividirse a su vez en tres subcampos que
representen el da, el mes y el ao.
Se entiende por registro lgico a una coleccin de informacin relativa a una
entidad particular. Un registro es una coleccin de campos lgicamente relacionados
y que pueden ser tratados como una unidad dentro de un programa.
Un ejemplo de registro puede ser la informacin relativa a un empleado de una
empresa, que constar de los campos nombre, direccin, fecha nacimiento, salario...
As, un fichero o archivo es una coleccin de registros lgicos relacionados entre
s con aspectos en comn y organizados con un propsito especfico.
Un fichero contendr informacin de un nmero determinado de entidades
particulares con aspectos comunes entre s. Por ejemplo, un fichero de una clase
escolar contiene un conjunto de registros, cada uno de los cuales representa la
informacin de un alumno. Otro ejemplo puede ser el fichero de datos personales de
los empleados de una empresa, en el que cada registro contendr la informacin de un
empleado.
El registro es considerado como una unidad de tratamiento dentro del fichero,
mientras que el campo es considerado como una unidad de tratamiento dentro de un
registro.

Figura 13.1. Relacin entre subcampos, campos y registros de un archivo

Estructuras externas de datos - Ficheros

379

En la Figura 13.1 se presenta la relacin existente entre los subcampos, campos y


registros de un archivo.
Una base de datos (BD) es una recopilacin de informacin que est relacionada
con un tema o con un propsito en particular. Esta informacin se mantiene en el
nmero justo de ficheros para evitar redundancias, de forma que diferentes
aplicaciones (programas) puedan trabajar con los ficheros, manteniendo stos su
independencia con respecto a las aplicaciones.
Las estructuras externas de datos se organizan de un modo jerrquico, de forma
que el nivel ms alto lo constituye la base de datos y el nivel ms bajo el carcter. Los
niveles intermedios son el archivo, el registro y el campo y el subcampo, tal y como
se muestra en la Figura 13.2.

Figura 13.2. Estructuras jerrquicas de datos

13.1.1. Conceptos y definiciones: terminologa


En relacin con el concepto de fichero se maneja una terminologa bsica que es
importante conocer para la mejor comprensin del trmino.
13.1.1.1. Clave primaria
Una clave primaria es un campo o combinacin de campos de datos cuyos valores
identifican unvocamente a un registro dentro del archivo. Esto implica que una clave
primaria tiene un valor nico y diferente para cada registro del archivo, de forma que
no puede haber nunca dos registros de un mismo archivo con el mismo valor para la
clave primaria.

380

Programacin en C

Como claves tpicas ms utilizadas estn los cdigos de identificacin, tales como,
nmero de matrcula de un alumno, nmero de empleado, nmero de cliente...
13.1.1.2. Registro fsico o bloque
Un registro fsico o bloque es la cantidad ms pequea de datos que pueden
transferirse en una operacin de entrada/salida entre la memoria central del ordenador
y sus dispositivos perifricos o viceversa.
13.1.1.3. Factor de bloqueo
El factor de bloque es el nmero de registros lgicos que puede contener un registro
fsico o bloque. Se pueden dar las situaciones que se recogen en la Tabla 13.1.
Relacin entre registro lgico y
Factor de bloqueo
Denominacin
bloque
Registro lgico > Registro fsico Factor de bloqueo < 1
Registros expandidos
Un registro lgico ocupa varios
bloques
Registro lgico = Registro fsico Factor de bloqueo = 1
Registros no bloqueados
Registro lgico < Registro fsico Factor de bloqueo > 1
Registros bloqueados
Un bloque contiene varios
registros lgicos
Tabla 13.1. Posibilidades del factor de bloqueo

La importancia del factor de bloqueo se puede apreciar mejor con un ejemplo.


Supngase que se tienen dos archivos. Uno de ellos tiene un factor de bloqueo de 1
(un registro lgico en cada bloque). El otro tiene un factor de bloqueo de 10 (10
registros lgicos en cada bloque). Si cada archivo contiene un milln de registros, el
segundo archivo requerir 900.000 operaciones de entrada/salida menos para leer
todos los registros. En el caso de ordenadores personales con un tiempo medio de
acceso de 10 milisegundos, el primer archivo empleara alrededor de 2,5 horas2 ms
para leer todos los registros del archivo.

Figura 13.3. Memoria intermedia o buffer en operaciones de entrada/salida de datos

Un factor de bloqueo mayor que 1 siempre mejora el rendimiento; entonces, por


qu no incluir todos los registros en un slo bloque? La razn reside en que las
operaciones de entrada/salida que se realizan por bloques se hacen a travs de un rea
de la memoria central del ordenador denominada memoria intermedia o buffer (ver
2

900.000x10ms = 9.000.000ms = 9.000s = 2,5 horas (ya que 1 hora son 3.600 segundos)

Estructuras externas de datos - Ficheros

381

Figura 13.3), y, por tanto, el aumento del bloque implicara el aumento de la memoria
intermedia y, por consiguiente, se reducira el tamao de la memoria central.
En el caso de computadoras personales, el bloque suele tener el tamao de un
sector del disco (512 bytes).
EJEMPLO

Supngase que se define un registro de nombre profesores con los


siguientes campos de datos:
Nombre de campo Tipo de dato del campo Tamao del campo
NOMBRE
APELLIDOS
DIRECCION
EDAD

Cadena (20)
Cadena (30)
Cadena (45)
Numrico entero

20 bytes
30 bytes
45 bytes
2 bytes

Tamao registro 97 bytes


Suponiendo que el tamao de bloque es de 210 bytes, cul es el factor de
bloqueo?

13.2. Soportes secuenciales y direccionables


El soporte es el medio fsico donde se almacenan los datos. Los tipos de soportes
utilizados en la gestin de archivos son: soportes secuenciales y soportes
direccionables.
Los soportes secuenciales son aqullos en los que los registros estn dispuestos
unos a continuacin de otros y para acceder a un determinado registro n es necesario
acceder (leer) los n-1 anteriores. Un ejemplo de soporte secuencial son las cintas
magnticas.
Los soportes direccionables se estructuran de modo que los registros pueden
localizarse directamente por su direccin y no se requiere acceder (leer) los registros
precedentes. Un ejemplo de soporte direccionable son los discos magnticos. Todo
soporte direccionable puede actuar tambin como soporte secuencial.
Los smbolos que se emplean en los diagramas de flujo para representar los
soportes direccionables y secuenciales se muestran en la Figura 13.4.

382

Programacin en C

Figura 13.4. Smbolos en los diagramas de flujo para los soportes direccionables y secuenciales

13.3. Organizacin de archivos


La organizacin de un archivo puede ser definida como la forma en que los datos son
estructurados y almacenados internamente en el fichero y sobre el soporte de
almacenamiento.
Existen tres tipos de organizaciones fundamentales:
1. Organizacin secuencial.
2. Organizacin directa o aleatoria (random).
3. Organizacin secuencial indexada (indexed).

13.3.1. Organizacin secuencial


Un archivo con organizacin secuencial es una sucesin de registros almacenados
consecutivamente sobre el soporte externo, de tal modo que para acceder a un registro
n dado es obligatorio acceder (leer) de uno en uno a todos los registros que le
preceden.
Los registros se graban consecutivamente cuando el archivo se crea, y se deben
acceder a ellos consecutivamente cuando se leen, tal y como se muestra en la Figura
13.5.
Principio de archivo

Registro 1
Registro 2

Registro n-1
Registro n

Fin de archivo

Registro final-1
Registro final
Figura 13.5. Organizacin secuencial

El orden lgico en que fueron grabados (escritos) los registros en el fichero es el


orden fsico de disposicin de los mismos en el soporte, y ste ser tambin el orden
de lectura de los mismos.
Todos los tipos de dispositivos de memoria auxiliar soportan la organizacin
secuencial.

Estructuras externas de datos - Ficheros

383

13.3.2. Organizacin directa


Un archivo est organizado de modo directo cuando el orden fsico de disposicin de
los registros en el soporte no se corresponde con el lgico en que fueron grabados.
Los datos se sitan en el archivo y se accede a ellos de forma directa mediante su
posicin, es decir, mediante el lugar relativo que ocupan.
Esta organizacin tiene la ventaja de que se pueden leer y escribir registros en
cualquier orden y posicin. Son muy rpidos de acceso a la informacin que
contienen.
La organizacin directa tiene el inconveniente de que es necesario programar la
relacin existente entre la clave primaria de un registro y la posicin que ocupa. El
acceso a los registros en modo directo implica la posible existencia de huecos libres
dentro del soporte, entre registros.
La correspondencia entre clave y direccin debe ser programada y la
determinacin de la relacin entre el registro y su posicin fsica se obtiene mediante
una funcin (funcin de conversin de claves o funcin hash).
Las condiciones para que un archivo sea de organizacin directa son:
Almacenamiento en un soporte direccionable.
Los registros deben contener un campo clave primaria.
Existencia de una correspondencia entre los posibles valores de la clave y
las direcciones disponibles sobre el soporte.
En la prctica el programador no gestiona directamente direcciones absolutas, sino
direcciones relativas respecto al principio del archivo. La manipulacin de direcciones
relativas permite disear el programa con independencia de la posicin absoluta del
archivo en el soporte.
El programador deber crear una relacin perfectamente definida (funcin hash)
entre la clave de cada registro y su posicin fsica dentro del dispositivo de
almacenamiento.

13.3.3. Organizacin secuencial indexada


Este tipo de organizacin surge con el propsito de subsanar los inconvenientes que
presenta las organizaciones secuencial y directa, pero aprovechando al mismo tiempo
las ventajas de ambas.
Este tipo de ficheros est formado por tres zonas o reas:
rea primaria o de datos: contiene los registros ordenados en forma
secuencial ascendente por el valor de su clave. Esta zona se encuentra
segmentada, de forma que cada segmento almacena un bloque de n
registros, todos ellos consecutivos y almacenados en posiciones de
memoria fsicamente contiguas.
rea o tabla de ndices: tiene la misma estructura y cualidades que un
fichero de organizacin secuencial (registros ordenados en forma
secuencial ascendente por el valor de su clave), pero con la caracterstica
de que los registros que la forman estn constituidos slo por dos campos.
El primer campo contiene la clave del ltimo registro de cada segmento

384

Programacin en C

mientras que el segundo contiene la direccin de entrada a cada uno de los


segmentos.
rea de excedentes u overflow: utilizada para las actualizaciones, dado
que albergar todos aquellos registros que no han tenido cabida en el rea
primaria, por tener sus claves valores intermedios a las de registros
previamente almacenados en dicha rea.
En este tipo de organizacin, el acceso a los registros se har previa consulta a la
tabla de ndices, para determinar el segmento donde se encuentra el registro buscado
dentro del fichero.
Las condiciones para que un archivo sea de organizacin secuencial indexada son:
Almacenamiento en un soporte direccionable.
Los registros deben contener un campo clave primaria.
Existencia de un ndice con cada una de las posiciones
direccionables del soporte, que almacena la direccin de la posicin
y el valor de la clave; en esencia, el ndice contiene la clave del
ltimo registro y la direccin de acceso al primer registro del
bloque.
En la Figura 13.6 se ilustra grficamente la organizacin secuencial indexada.

Figura 13.6. Organizacin secuencial indexada

Para consultar un registro en un fichero secuencial indexado, se debe realizar los


siguientes pasos:
1. Consultar el rea de ndices hasta localizar el segmento donde se
encuentra el registro que se quiere leer.
2. Una vez localizada la direccin de entrada al segmento, en el rea
primaria se accede directamente al primer registro de dicho segmento.

Estructuras externas de datos - Ficheros

385

3.

Se realiza una consulta del segmento hasta encontrar el registro deseado o


alcanzar el final del mismo.
4. En el supuesto de que el registro buscado no se halle en el rea primaria,
una vez recorrido todo el segmento se accede al rea de excedentes u
overflow, para as determinar la posible localizacin del registro en dicha
rea.
Los ficheros de organizacin secuencial indexada son eficaces tanto en accesos
directos a registros por clave como en lecturas secuenciales totales del archivo.
Si este tipo de ficheros sufre muchas altas de nuevos registros, es conveniente, para
mejorar el acceso a los mismos, generar de nuevo el rea de claves, para que el rea
de excedentes pase a formar parte del rea primaria.

13.4. Acceso a archivos


El acceso es el procedimiento necesario que se debe seguir para situarse sobre un
registro concreto con la intencin de realizar una operacin de lectura o escritura
sobre l. Los tipos de acceso son:
Secuencial: implica el acceso a un archivo segn el orden de almacenamiento
de sus registros, uno tras otro. Para acceder al registro n es necesario acceder
a los anteriores a ste. Este tipo de acceso puede ser utilizado tanto en
dispositivos secuenciales como direccionables.
Directo: implica el acceso a un registro determinado, sin que ello implique el
acceso a los registros anteriores. Este tipo de acceso slo es posible con
soportes direccionables.
Por ndice: permite acceder indirectamente a un registro previa consulta en
una tabla que contiene la clave y direccin relativa de cada uno de los
registros del fichero.

13.5. Ficheros en el lenguaje C: Una introduccin


El lenguaje C no tiene sentencias propias para realizar operaciones de entrada y
salida, como ya se vio anteriormente. En su lugar, proporciona una biblioteca estndar
de funciones de entrada y salida. Estas funciones se pueden clasificar en dos tipos:
Bajo nivel: Estas operaciones se realizan mediante una llamada al sistema
operativo y permiten obtener datos desde el disco y hacerlos disponibles al
programa. A este nivel, el dato es ledo desde el disco en un tamao regulado
por el sistema operativo. Este proceso de bajo nivel de entrada y salida no
aporta buffers ni ningn otro servicio, como formato. Todas las funciones de
entrada y salida son realizadas por dos funciones: read() y write().
Alto nivel: Son funciones que aportan los datos al programa segn los va
necesitando. Es conocido como el sistema de archivos ANSI o con formato.
A este nivel pertenecen las funciones de entrada y salida que se han estado
utilizado hasta ahora, por ejemplo, printf() o scanf().

386

Programacin en C

El sistema de entrada y salida de alto nivel de este lenguaje ofrece una interfaz
independiente del dispositivo, apoyado en la existencia de streams (flujos).
Sea cual sea el dispositivo fsico donde se quiere escribir o leer, el sistema de
ficheros ANSI lo transforma en un dispositivo lgico, llamado stream. El dispositivo
fsico es el archivo y el dispositivo lgico es el stream.
El programador siempre escribe en el stream. El sistema de entrada y salida es el
que se encarga de escribir en el archivo.
Existen dos tipos de streams: binarios y de texto.
Los streams binarios son una secuencia de bytes que mantienen una
correspondencia biunvoca con lo que aparece en el dispositivo fsico. No existe
traduccin de uno a otro.
Los archivos creados con streams binarios (llamados ficheros binarios) tendrn las
siguientes caractersticas:
Los datos son almacenados en disco en el mismo formato en que se
almacenan en la memoria central del ordenador. No existe conversin
alguna.
Slo pueden ser creados, visualizados y modificados por un programa que
se codifique particularmente para ellos.
Los streams de texto son una secuencia de caracteres. No tiene que haber
correspondencia entre los caracteres del stream y los que aparecen en el dispositivo.
Puede ser necesaria una traduccin de ciertos caracteres, dependiendo del entorno.
Los archivos creados con streams de texto (llamados ficheros de texto), tendrn las
siguientes caractersticas:
Estn constituidos por una secuencia indefinida de caracteres ASCII.
Puede existir una conversin o transformacin de datos de cmo son
almacenados en memoria y cmo son grabados en el fichero.
Pueden ser creados, visualizados y modificados por un editor de texto.
EJEMPLO

Supngase el valor 28563 almacenado en 2 bytes en memoria en una


variable tipo int. Su formato en la memoria central del ordenador ser el
siguiente:
01101111
10010011
Si este valor entero se almacena en un fichero de texto, ocupa 5 bytes, ya
que cada cifra se representar mediante el cdigo ASCII correspondiente.
00000010 00001000 00000101 00000110 00000011
Sin embargo, si se almacena en un fichero binario, ocupa 2 bytes, ya que se
almacena en el mismo formato que el utilizado en la memoria central.
01101111 10010011
El que se utilice uno u otro tipo de stream determina cmo deben manejarse
algunos detalles de la gestin de ficheros: cmo se almacenan los cambios de lnea,
cmo se indica final de fichero...
Es importante entender que cuando se habla de archivo, se est haciendo referencia
a un dispositivo fsico en general, que puede ser un archivo en disco o la consola. En
principio, todos los programas C tienen asociados los streams estndares: stdin,

Estructuras externas de datos - Ficheros

387

stdout y stderr, es decir, el canal de entrada estndar (teclado), el canal de salida


estndar (pantalla) y el canal de errores estndar (pantalla).

13.6. Operaciones con ficheros en el lenguaje C


El sistema de ficheros propio de ANSI C se compone de varias funciones que ofrecen
un protocolo de operaciones bsicas a realizar con archivos. Estas funciones requieren
que se incluya el archivo de cabecera stdio.h en cualquier programa en que se
vayan a utilizar.
Para poder realizar operaciones de escritura o lectura en un fichero, primero hay
que asociarle un stream, es decir, un rea de comunicacin entre fichero y el
programa. Esto se realiza con la operacin de apertura del fichero.
El rea de comunicacin entre el programa y el fichero est soportada en C por una
estructura que guarda informacin sobre el fichero, y que est definida como del tipo
struct FILE. De hecho, cuando se realiza una peticin de apertura del fichero, lo
que se recibe es un puntero a una estructura FILE particular.
La estructura FILE contiene informacin sobre el fichero utilizado, como es el
tamao actual y la localizacin de los buffers de datos. Esta estructura est declarada
en el fichero cabecera stdio.h.
Por tanto, un puntero a fichero es una variable de tipo puntero a struct FILE,
a travs de la cual se pueden realizar todas las operaciones de entrada/salida sobre el
fichero referenciado (apertura, lectura, escritura, cierre...).
El formato para la definicin del puntero a fichero es el siguiente:
FILE *nombrepuntero;

13.6.1. Apertura de un fichero


Antes de que se pueda leer o escribir en un fichero de disco, debe abrirse. Al abrir un
fichero se establece un entendimiento entre el programa y el sistema operativo sobre
el fichero al que se desea acceder y lo que se quiere hacer con l.
Al abrir un fichero se le indica al sistema cul es el nombre del fichero y si se
desea leer y/o escribir en l. Cada fichero abierto tiene su propia estructura FILE, con
un puntero a ella.
La funcin encargada de abrir un fichero es fopen(), esto es, de asociar un
stream al archivo. La funcin fopen() retorna un puntero de tipo FILE. Este
puntero identifica el archivo y se usa en la mayora del resto de funciones del sistema
de ficheros, por lo tanto no debe ser modificado mientras est apuntando a un fichero
abierto.
Como ya se ha dicho, un fichero debe ser abierto antes de realizar cualquier
operacin de lectura/escritura sobre l, pero del mismo modo todo fichero abierto
debe ser cerrado antes de finalizar el programa.
Los datos fundamentales necesarios para el uso de la funcin fopen() se
presentan en la Tabla 6.2.

388

Programacin en C

Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:
Finalidad:

FILE *fopen (char *nombrefichero, char *modo);


stdio.h
La cadena nombrefichero es el nombre externo del fichero, por
ejemplo C:\\TEMP\\DATOS.TXT3.
La cadena modo indica el modo en que va a ser abierto el fichero, es
decir, lectura, aadir datos, escritura...
Si puede abrirse retorna un puntero de tipo FILE.
Retorna el valor NULL si el fichero no puede abrirse.
Abre un fichero, permitiendo el acceso a la informacin en l
contenida. El acceso a dicha informacin vendr limitado por el modo
de apertura especificado.
Tabla 13.2. Funcin fopen()

El modo de apertura de un fichero permite las posibilidades que se recogen en la


Tabla 13.3.
Modo
Significado
de apertura
"r"
"w"
"a"
"r+"
"w+"
"a+"

Abre un fichero de texto para lectura. El fichero debe existir.


Crea un fichero de texto para escritura. Si el fichero existe se destruye la
informacin contenida en l. Si no existe lo crea.
Abre un fichero de texto para aadir datos al final de l. Si el fichero no
existe, se crea.
Abre un fichero de texto para lectura/escritura. El fichero debe existir.
Crea un fichero de texto para lectura/escritura. Si el fichero existe se
destruye la informacin contenida en l.
Abre un fichero de texto para leer/aadir datos al final de l. Si el fichero
no existe, se crea.
Tabla 13.3. Modos de apertura de un fichero

Para abrir ficheros binarios, se aade la letra b al final de la cadena modo de


apertura. Por ejemplo: "rb", "r+b", "a+b".
Si se produce un error durante la apertura del archivo, la funcin fopen()
devuelve un NULL, definido en el fichero cabecera stdio.h. Se debe utilizar este
hecho para asegurarse de una apertura correcta del archivo.
EJEMPLO

Se fp un puntero a FILE.
FILE *fp;

La siguiente sentencia abre un fichero llamado ejemplo.txt para


escritura.
fp = fopen("ejemplo.txt", "w");

Sin embargo, la sentencia de apertura del fichero se suele hacer


comprobando que el fichero se ha abierto correctamente, como se muestra a
continuacin:

A la hora de crear un fichero se deben respetar las normas, con respecto a los
nombres de los archivos, impuestas por el sistema operativo que se est ejecutando en
el ordenador, as como tener en cuenta la correcta utilizacin del carcter \ cuando
fuera necesario utilizarlo.
3

Estructuras externas de datos - Ficheros

389

if ( ( fp = fopen("ejemplo.txt", "w") ) == NULL ) {


printf(" No se puede abrir el fichero !!\n");
exit(-1);
}

13.6.2. Cierre de un fichero


Una vez se ha terminado de trabajar con el fichero, ste debe ser cerrado. Hay dos
formas de hacerlo, una es implcita, cuando tiene lugar una terminacin normal del
programa, ya que se cierran todos los streams. La otra es utilizando la funcin
fclose().
Cerrar un fichero tiene varios efectos, el primero de ellos es que el contenido del
buffer se escribe en el disco. Los buffers son invisibles al programador cuando se
utiliza la entrada/salida estndar. Sin embargo, su misin es muy importante para
lograr un sistema de entrada y salida eficiente.
Cuando una funcin tiene que escribir algo en un fichero, lo hace en el buffer (rea
de memoria dedicada a los intercambios entre dispositivos) en lugar de hacerlo
directamente en el disco. Cuando el buffer est lleno se vuelca su contenido al disco,
tal y como se ilustra en la Figura 13.7.

Programa

Los datos puestos en el disco


por el programa se almacenan en
el buffer

Fichero

El contenido del buffer se


coloca en el fichero cuando se
llena el buffer , se cierra el fichero
o se finaliza el programa

Figura 13.7. Buffer entre el programa y el fichero

Los datos fundamentales necesarios para el uso de la funcin fclose() se


presentan en la Tabla 13.4.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:

Finalidad:

int fclose (FILE *fp);


stdio.h
Puntero de tipo FILE correspondiente al fichero a cerrar.
Retorna el valor 0 si la operacin de cierre ha tenido xito, y EOF4
en caso de fracaso (generalmente fclose() slo falla cuando el
dispositivo donde est el fichero no est disponible o no queda
espacio en el dispositivo).
Cierra un fichero abierto con fopen().
Tabla 13.4. Funcin fclose()

EOF es una constante simblica definida en stdio.h con valor 1.

390

Programacin en C

13.6.3. Vaciado del buffer de lectura/escritura de un fichero


En cualquier momento se puede realizar una operacin de vaciado del buffer
(flushing) por medio de la funcin fflush().
Los datos fundamentales necesarios para el uso de la funcin fflush() se
presentan en la Tabla 13.5.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

int fflush (FILE *fp);


stdio.h
Puntero de tipo FILE correspondiente al fichero a vaciar su buffer. Si
es NULL se hace un flushing de todos los streams.
Retorna el valor 0 si la operacin de cierre ha tenido xito, y
cualquier otro valor en caso de fracaso.
Vaciado del buffer.
Tabla 13.5. Funcin fflush()

13.6.4. Comprobacin de final de fichero


Existe la funcin feof() para conocer cuando, en un proceso de lectura secuencial
del mismo, se ha alcanzado el final de fichero.
Los datos fundamentales necesarios para el uso de la funcin feof() se presentan
en la Tabla 13.6.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

int feof (FILE *fp);


stdio.h
Puntero de tipo FILE, correspondiente al fichero que se est
tratando.
Devuelve un valor distinto de 0 (true) si se ha alcanzado el final del
fichero. En otro caso devuelve 0 (false).
Facilita la deteccin del final del fichero.
Tabla 13.6. Funcin feof()

13.7. Ficheros de texto. Funciones de entrada/salida


Una vez que se tiene el archivo de texto abierto ya se pueden realizar operaciones de
lectura/escritura sobre el mismo. Las funciones a utilizar se pueden clasificar en:
Lectura/escritura de un carcter: fgetc() y fputc().
Lectura/escritura de cadenas de caracteres: fgets() y fputs().
Lectura/escritura con formato: fscanf() y fprintf().

13.7.1. Funcin para escribir un carcter en un fichero


La funcin fputc() sirve para escribir un carcter en un fichero de texto. En la
Tabla 13.7 se resumen los datos fundamentales necesarios para el uso de esta funcin.
Prototipo:

int fputc (int c, FILE *fp);

Estructuras externas de datos - Ficheros

Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

391

stdio.h
El argumento c es el carcter a escribir.
Puntero de tipo FILE correspondiente al fichero que se est tratando.
Retorna el carcter escrito si tiene xito. En caso de fracaso retorna
EOF.
Escribe un carcter en un stream.
Tabla 13.7. Funcin fputc()

EJEMPLO

El siguiente programa lee una lnea de texto del teclado y la almacena en


maysculas en un fichero.
#include <stdio.h>
#include <ctype.h>
void main(void) {
FILE *fp;
char c;
if ( (fp = fopen ("C:\\DATOS\\MUESTRA.TXT", "w")) == NULL )
printf("ERROR. No se puede crear el archivo indicado.\n");
else {
printf("Introduzca una frase: ");
while ( (c = getchar()) != '\n' )
fputc(toupper(c), fp);
fclose (fp);
}
}

Ejemplo de ejecucin:
Introduzca una frase: Nosotros, los Programadores de C

Y el archivo de texto C:\DATOS\MUESTRA.TXT contendr:


NOSOTROS, LOS PROGRAMADORES DE C

13.7.2. Funcin para leer un carcter de un fichero


La funcin fgetc() sirve para leer un carcter de un fichero de texto. En la Tabla
13.8 se resumen los datos fundamentales necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

int fgetc (FILE *fp);


stdio.h
Puntero de tipo FILE correspondiente al fichero que se est leyendo.
Retorna el carcter ledo si tiene xito. En caso de fracaso o cuando
se alcanza el final del fichero retorna EOF.
Devuelve un carcter de un stream.
Tabla 13.8. Funcin fgetc()

EJEMPLO

El archivo de texto creado en el anterior ejemplo podra leerse carcter a


carcter y mostrarse en pantalla de la siguiente forma.
#include <stdio.h>
void main(void) {
FILE *fp;
char c;
if ( (fp = fopen ("C:\\DATOS\\MUESTRA.TXT", "r")) == NULL )
printf("ERROR. No se puede abrir el archivo indicado.\n");
else {

392

Programacin en C

/* Lee carcter a carcter del fichero */


while ( (c = fgetc(fp)) != EOF )
putchar(c);
fclose (fp);

13.7.3. Funcin para escribir una cadena caracteres en un fichero


La funcin fputs() sirve para escribir una cadena de caracteres en un fichero de
texto. En la Tabla 13.9 se resumen los datos fundamentales necesarios para el uso de
esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:
Finalidad:

int fputs (char *cadena, FILE *fp);


stdio.h
El argumento cadena representa la cadena de caracteres a escribir.
El argumento fp es un puntero de tipo FILE correspondiente al
fichero que se est tratando.
Devuelve un entero no negativo si tiene xito y EOF en caso de
fracasar.
Escribe una cadena de caracteres en el stream asociado a fp.
Tabla 13.9. Funcin fputs()

EJEMPLO

El siguiente programa lee cadenas de caracteres de teclado y las esrcibe en


un fichero de texto.
#include <stdio.h>
#define FIN '#'
#define INTRO '\n'
#define N 200
void main(void) {
FILE *fp;
char cadena[N];
if ( (fp = fopen ("C:\\DATOS\\FRASES.TXT", "w")) == NULL )
printf("ERROR. No se puede abrir el archivo indicado.\n");
else {
printf("Introduzca cadenas de caracteres, y");
printf(" # para terminar.\n");
gets(cadena);
while (cadena[0]!= FIN) {
fputs (cadena, fp);
fputc(INTRO, fp); /* Retorno de carro en el fichero */
gets(cadena);
}
fclose (fp);
}
}

13.7.4. Funcin para leer una cadena caracteres de un fichero


La funcin fgets() sirve para leer una cadena de caracteres de un fichero de texto.
En la Tabla 13.10 se resumen los datos fundamentales necesarios para el uso de esta

Estructuras externas de datos - Ficheros

393

funcin.
Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:
Finalidad:

char *fgets (char *cadena, int n, FILE *fp);


stdio.h
El argumento cadena representa la cadena de caracteres a leer.
El argumento n es el nmero de caracteres a leer del fichero.
El argumento fp es un puntero de tipo FILE correspondiente al
fichero que se est tratando.
Devuelve la propia cadena leda si tiene xito. Si fracasa o cuando se
alcanza el final del fichero devuelve NULL.
Lee los siguientes n-1 caracteres del stream asociado a fp, y los
almacena en cadena.
Tabla 13.10. Funcin fgets()

Si encuentra un carcter de nueva lnea en el fichero, no lee ms caracteres,


almacenando, adems, dicho carcter en cadena. La cadena leda ser terminada con
el correspondiente carcter nulo.
EJEMPLO

El archivo de texto creado en el anterior ejemplo podra leerse lnea a lnea y


mostrarse en pantalla de la siguiente forma.
#include <stdio.h>
#define N 200
void main(void) {
FILE *fp;
char cadena[N];
if ( (fp = fopen ("C:\\DATOS\\FRASES.TXT", "r")) == NULL )
printf("ERROR. No se puede abrir el archivo indicado.\n");
else {
/* Lee lnea a lnea el fichero */
while ( fgets(cadena, N, fp) != NULL )
printf("%s", cadena);
fclose (fp);
}
}

13.7.5. Funcin para escribir datos formateados en un fichero


La funcin fprintf() sirve para escribir una cadena de caracteres con un formato
especfico en un fichero de texto. En la Tabla 13.11 se resumen los datos
fundamentales necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

int fprintf (FILE *fp, const char *formato,


arg1, arg2, ..., argn);
stdio.h
Puntero de tipo FILE correspondiente al fichero que se est tratando.
Cadena de control con la informacin sobre el formato de salida.
Argumentos que representan los datos de salida.
Retorna el nmero de caracteres escritos si tiene xito, y un nmero
negativo en caso de fracaso.
Convierte y escribe sobre el stream fp, bajo el control de la cadena
de caracteres formato.
Tabla 13.11. Funcin fprintf()

394

Programacin en C

Se comporta de forma idntica que printf(), pero con la diferencia de que


mientras que printf() permite dar formato a la informacin mostrada en pantalla,
fprintf() est destinada al tratamiento de ficheros.
La cadena de control puede constar de hasta tres tipos de informacin:
Texto, que se va a escribir en el stream.
Secuencias de escape, que sern interpretadas convenientemente en la
salida.
Especificadores de formato, similares a los de la funcin printf().
Al igual que en la funcin printf(), los especificadores de formato pueden
incluir modificadores que especifiquen la longitud mnima del campo, la precisin, el
relleno con dgito cero para justificaciones a la derecha y el ajuste a la izquierda.
EJEMPLO

El siguiente programa escribe en un fichero datos formateados en un fichero


de texto.
#include <stdio.h>
#define INTRO '\n'
void main(void) {
FILE *fp;
int entero;
char cadena[25];
float real;
int i;
if ( (fp = fopen ("C:\\DATOS\\LISTA.TXT", "w")) == NULL )
printf("ERROR. No se puede crear el archivo indicado.\n");
else {
for (i=1; i<=3; i++) {
printf("Nmero entero: "); scanf("%d", &entero);
printf("Cadena de caracteres: "); gets(cadena);
printf("Nmero real: "); scanf("%f", &real);
fprintf (fp, "%5d%25s%8.2f", entero, cadena, real);
fputc(INTRO, fp); /* Retorno de carro en el fichero */
}
fclose (fp);
}
}

Ejemplo de ejecucin:
Nmero
Cadena
Nmero
Nmero
Cadena
Nmero
Nmero
Cadena
Nmero

entero: 125
de caracteres: JUAN
real: -0.537
entero: -768
de caracteres: ANTONIO
real: 1234.567
entero: 1355
de caracteres: FERNANDEZ
real: 0.0067

Y el archivo de texto C:\DATOS\LISTA.TXT contendr:


125
-768
1355

JUAN
-0.53
ANTONIO 1234.57
FERNANDEZ
0.01

Estructuras externas de datos - Ficheros

395

13.7.6. Funcin para leer datos formateados de un fichero


La funcin fscanf() sirve para leer datos con un formato especfico de un fichero
de texto. En la Tabla 13.12 se resumen los datos fundamentales necesarios para el uso
de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

int fscanf (FILE *fp, const char *formato,


arg1, arg2, ..., argn);
stdio.h
Puntero de tipo FILE correspondiente al fichero que se est tratando.
Cadena de control con la informacin sobre el formato de entrada.
Argumentos que se desean cargar con la lectura.
Retorna el nmero de elementos ledos si tiene xito. Si fracasa o
cuando se alcanza el final del fichero, devuelve EOF.
Lee del stream fp, bajo el control de la cadena de caracteres
formato, y almacena los valores convertidos en las variables que
recibe como argumentos.
Tabla 13.12. Funcin fscanf()

Cada uno de los argumentos arg1, arg2, ..., argn, debe ser la direccin de
memoria de la variable donde se desea almacenar la informacin leda.
Se comporta de forma idntica que scanf(), pero con la diferencia de que
mientras que scanf() permite leer de teclado informacin formateada, fscanf()
est destinada a la lectura de ficheros de texto.
La cadena de control puede constar de:
Especificadores de formato, similares a los de la funcin scanf().
Caracteres separadores, son ignorados. Caracteres separadores son el
carcter espacio, tabulador, nueva lnea, retorno de carro, tabulador
vertical.
Caracteres ordinarios que no son espacios en blanco, dan lugar a que
se descarte un carcter igual del stream de entrada. Si no se encuentra, la
lectura concluir.
Al igual que en la funcin scanf(), los especificadores de formato pueden
incluir modificadores que especifiquen la longitud mxima del campo de entrada, o el
carcter asterisco (*) de supresin de asignacin en entrada.
EJEMPLO

El fichero generado en el ejemplo anterior puede leerse y presentarse en


pantalla de la siguiente forma.
#include <stdio.h>
void main(void) {
FILE *fp;
int entero;
char cadena[25];
float real;
if ((fp = fopen ("C:\\DATOS\\LISTA.TXT", "r")) == NULL)
printf("ERROR. No se puede abrir el archivo indicado.\n");
else {
while(fscanf(fp,"%d%s%f",&entero,cadena,&real) != EOF ) {
printf("Nmero entero: %d\n", entero);

396

Programacin en C
printf("Cadena: %s\n", cadena);
printf("Nmero real: %g\n", real);

}
fclose (fp);

Ejemplo de ejecucin:
Utilizando en entrada el archivo de texto C:\DATOS\LISTA.TXT
generado en el ejemplo anterior, la salida a pantalla del programa ser:
Nmero entero: 125
Cadena: JUAN
Nmero real: -0.53
Nmero entero: -768
Cadena: ANTONIO
Nmero real: 1234.57
Nmero entero: 1355
Cadena: FERNANDEZ
Nmero real: 0.01

13.8. Escritura en archivos de texto


Supngase que se dispone de un vector de estructuras en un programa, donde cada
estructura contiene los siguientes datos de una persona:
NOMBRE
PROVINCIA de nacimiento
EDAD
TALLA

Cadena de hasta 44 posiciones


Cadena de hasta 14 posiciones
Valor entero
Valor real

Donde el cdigo C que representara dicha estructura de datos sera el siguiente:


#define DIM 5
typedef struct {
char st_nombre [45];
char st_provincia [15];
int
st_edad;
float st_talla;
} persona;
persona lista[DIM] = {
"ANTONIO GARCIA PEREZ",
"PEDRO RUIZ PABLOS",
"MARIA SARMIENTOS BLANCO",
"FRANKLIN VAN VANTEN",
"EVA SANCHEZ SAN ROMAN",

"MADRID",
"AVILA",
"SORIA",
"CADIZ",
"BURGOS",

23,
22,
8,
25,
19,

1.7,
1.85,
1.04,
1.69,
1.8};

Se desea grabar los anteriores datos de personas en un fichero de texto, en una


lnea (registro) cada persona. Supngase que fp es el puntero a FILE para manejar el
fichero de texto a grabar. Entonces, se pueden grabar diferentes tipos de ficheros
como se detalla en los siguientes subapartados.

Estructuras externas de datos - Ficheros

397

13.8.1. Fichero de texto sin formato


El fragmento C para grabar un fichero de texto sin formato sera el siguiente:
for (i=0; i<DIM; i++)
fprintf(fp, "%s%s%d%.2f\n", lista[i].st_nombre,
lista[i].st_provincia,
lista[i].st_edad,
lista[i].st_talla);

La apariencia del fichero generado ser:

ANTONIO GARCIA PEREZMADRID231.70


PEDRO RUIZ PABLOSAVILA221.85
MARIA SARMIENTOS BLANCOSORIA81.04
FRANKLIN VAN VANTENCADIZ251.69
EVA SANCHEZ SAN ROMANBURGOS191.80

13.8.2. Fichero de texto delimitado


Dentro de la misma lnea (registro) cada dato (campo) se separa del siguiente por un
carcter delimitador. Este carcter deber ser escogido de forma que nunca pueda
formar parte de la informacin de un dato. Un carcter tpico usado como
delimitador es el asterisco (*). En este caso el fragmento C a utilizar para grabar un
fichero de texto delimitado por asteriscos ser:
for (i=0; i<DIM; i++)
fprintf(fp, "%s*%s*%d*%.2f\n", lista[i].st_nombre,
lista[i].st_provincia,
lista[i].st_edad,
lista[i].st_talla);

La apariencia del fichero generado ser:


ANTONIO GARCIA PEREZ*MADRID*23*1.70
PEDRO RUIZ PABLOS*AVILA*22*1.85
MARIA SARMIENTOS BLANCO*SORIA*8*1.04
FRANKLIN VAN VANTEN*CADIZ*25*1.69
EVA SANCHEZ SAN ROMAN*BURGOS*19*1.80

Otro carcter utilizado muy comnmente como delimitador es el tabulador


(carcter cdigo ASCII 9 o secuencia de escape \t). En este caso a los ficheros de
texto delimitados por tabuladores se les llama ficheros tabulados.
El fragmento C a utilizar para grabar un fichero de texto tabulado sera:
for (i=0; i<DIM; i++)
fprintf(fp, "%s\t%s\t%d\t%.2f\n",

La apariencia del fichero generado ser:

lista[i].st_nombre,
lista[i].st_provincia,
lista[i].st_edad,
lista[i].st_talla);

ANTONIO GARCIA PEREZ


MADRID 23
1.70
PEDRO RUIZ PABLOS
AVILA
22
1.85
MARIA SARMIENTOS BLANCO SORIA
8
1.04
FRANKLIN VAN VANTEN
CADIZ
25
1.69
EVA SANCHEZ SAN ROMAN
BURGOS 19
1.80
-------------------------------------------8
16
24
32
40
48

398

Programacin en C

13.8.3. Fichero de texto encolumnado


En este caso se reservan posiciones fijas para cada uno de los datos (campos) de la
lnea (registro) del fichero.
El fragmento C para grabar un fichero de texto encolumnado sera:
for (i=0; i<DIM; i++)
fprintf(fp, "%44s%14s%4d%5.2f\n", lista[i].st_nombre,
lista[i].st_provincia,
lista[i].st_edad,
lista[i].st_talla);

La apariencia del fichero generado ser:

ANTONIO GARCIA PEREZ


MADRID 23 1.70
PEDRO RUIZ PABLOS
AVILA 22 1.85
MARIA SARMIENTOS BLANCO
SORIA
8 1.04
FRANKLIN VAN VANTEN
CADIZ 25 1.69
EVA SANCHEZ SAN ROMAN
BURGOS 19 1.80
-----------------------------------------------------
44 posiciones
14 posic.
4
5

Los datos numricos se justifican, la mayora de las veces a la derecha, pero a


veces es conveniente justificar a la izquierda las cadenas de caracteres. En ese caso
deber utilizarse el especificador de justificacin a la izquierda, que no es ms que
un guin entre el carcter % y la longitud mnima del campo.
for (i=0; i<DIM; i++)
fprintf(fp, "%-44s%-14s%4d%5.2f\n",

La apariencia del fichero generado ser:

lista[i].st_nombre,
lista[i].st_provincia,
lista[i].st_edad,
lista[i].st_talla);

ANTONIO GARCIA PEREZ


MADRID
23 1.70
PEDRO RUIZ PABLOS
AVILA
22 1.85
MARIA SARMIENTOS BLANCO
SORIA
8 1.04
FRANKLIN VAN VANTEN
CADIZ
25 1.69
EVA SANCHEZ SAN ROMAN
BURGOS
19 1.80
-----------------------------------------------------
44 posiciones
14 posic.
4
5

13.9. Lectura de archivos de texto


13.9.1. Fichero de texto sin formato
Casi siempre ser imposible el tratamiento de los datos en ellos contenidos, pues
aparece el problema de no saber la posicin en la que acaba un dato (campo) y
comienza el siguiente.

13.9.2. Fichero de texto delimitado


Para poder tratar la informacin en ellos contenida debe de conocerse:
Cul es su carcter delimitador.

Estructuras externas de datos - Ficheros

399

El nmero de datos por lnea.


El tipo de cada dato.
Por ejemplo, para poder leer y tratar la informacin en un fichero delimitado como
el anteriormente generado se debera conocer la siguiente informacin:
Se trata de un fichero delimitado por asteriscos (*), con 4 datos
(campos) por lnea:
NOMBRE (alfanumrico)
PROVINCIA de nacimiento (alfanumrico)
EDAD (entero)
TALLA (real)
Un fragmento de cdigo para la lectura de los registros del fichero delimitado por
asteriscos generado anteriormente, y el clculo de la media de la edad y la talla,
podra ser el siguiente:
#include <stdlib.h>
...
int cuantos = 0;
int edad;
float talla;
float media_edad = 0, media_talla = 0;
char nombr[45];
char provin[15];
...
while (leer_delimitado(fp, nombr, provin, &edad, &talla)!= EOF) {
media_edad += edad;
media_talla += talla;
cuantos++;
}
media_edad /= cuantos;
media_talla /= cuantos;
...
int leer_delimitado(FILE *f, char
char
int
float
char x_edad[15];
char x_talla[15];

*pnom,
*pprov,
*pedad,
*ptalla) {

if ( fscanf(f, "%[^*]%*c%[^*]%*c%[^*]%*c%[^\n]%*c",
pnom, pprov, x_edad, x_talla) == EOF )
return (EOF);
else { *pedad = atoi(x_edad);
*ptalla = atof(x_talla);
return 0;
}
}

Ciertos puntos de inters sobre la funcin leer_delimitado() se comentan a


continuacin.
La secuencia de tres parejas de especificadores de formato %[^*]%*c, se
debe a que la lectura de una lnea del fichero se hace leyendo todas las
columnas como campos cadenas de caracteres, por tanto se usa el
especificar de formato %[^*] para indicar que la lectura de la cadena

400

Programacin en C

deber terminar al encontrar un carcter asterisco (*). Posteriormente


siempre hay que leer y desechar el propio carcter asterisco, lo cual se
hace con el especificador de formato %*c.
La ultima pareja de especificadores de formato %[^\n]%*c, se debe a
que el ltimo dato de la lnea del fichero ya no termina con el carcter
separador asterisco (*), sino con un retorno de carro (fin de lnea).
Igualmente siempre hay que leer y desechar el propio carcter fin de
lnea (para posicionarnos en la siguiente lnea del fichero), lo cual se hace
con el especificador de formato %*c.
Las funciones atoi() y atof() convierten una cadena de caracteres a
entero y a real respectivamente. De esta forma los datos numricos del
fichero son devueltos por la funcin de lectura como numricos. El
archivo de cabecera de ambas funciones es stdlib.h.
En el caso de los ficheros tabulados, se sabe de antemano que el carcter
delimitador es el tabulador, por lo que para leer los datos del fichero slo se necesita
conocer el nmero de datos por lnea y el tipo de cada dato.
Por ejemplo, para poder leer y tratar la informacin en un fichero tabulado, como
el anteriormente generado en la seccin 13.8.2, se debera conocer que:
Se trata de un fichero tabulado, con 4 datos (campos) por lnea:
NOMBRE (alfanumrico)
PROVINCIA de nacimiento (alfanumrico)
EDAD (entero)
TALLA (real)
Un fragmento de cdigo para la lectura de los registros del fichero tabulado
generado anteriormente, y el clculo de la media de la edad y la talla, podra ser el
siguiente:
int cuantos = 0;
int edad;
float talla;
float media_edad = 0, media_talla = 0;
char nombr[45];
char provin[15];
...
while (leer_tabulado(fp, nombr, provin, &edad, &talla) != EOF) {
media_edad += edad;
media_talla += tall;
cuantos++;
}
media_edad /= cuantos;
media_talla /= cuantos;
...
int leer_tabulado(FILE *f,

int
t_edad;
float t_talla;

char
char
int
float

*pnom,
*pprov,
*pedad,
*ptalla) {

Estructuras externas de datos - Ficheros

401

if ( fscanf(f, "%[^\t]%*c%[^\t]%d%f%*c",
pnom, pprov, &t_edad, &t_talla) == EOF )
return (EOF);
else {
*pedad = t_edad;
*ptalla = t_talla;
return 0;
}

Ciertos puntos de inters sobre la funcin leer_tabulado() se comentan a


continuacin.
Para cada lnea del fichero de texto, el primer campo es una cadena que se lee
con la pareja de especificadores de formato %[^\t]%*c, para indicar que la
lectura de la cadena deber terminar al encontrar un tabulador. Posteriormente
siempre hay que leer y desechar el propio carcter tabulador, lo cual se hace
con el especificador de formato %*c.
El segundo campo tambin es una cadena que se lee con el especificador de
formato %[^\t]. No es necesario el especificador de formato %*c posterior
ya que el siguiente campo a leer es numrico y el tabulador es uno de los tres
caracteres separadores estndar (espacio, tabulador o intro) en la lectura de
datos de C.
El tercer y cuarto campo al ser numricos y estar separados por tabuladores, se
leen con los especificadores de formato habituales %d y %f respectivamente.
Siempre hay que leer y desechar el propio carcter fin de lnea (para
posicionarnos en la siguiente lnea del fichero), lo cual se hace con el
especificador de formato %*c.

13.9.3 Fichero de texto encolumnado


Para poder tratar la informacin en ellos contenida se deben conocer los siguientes
datos:
El nmero de datos (columnas) por lnea.
El tipo de cada dato (columna).
El ancho de cada columna.
Para los datos cadenas de caracteres deber especificarse la justificacin
del mismo (derecha o izquierda).
Por ejemplo, para poder leer y tratar la informacin en un fichero encolumnado,
como el anteriormente generado en la seccin 13.8.3 se debera conocer que:
Se trata de un fichero encolumnado, con la siguiente distribucin
de columnas por lnea:
Descripcin del dato
Nombre
Provincia nacimiento
Edad
Talla

Columnas (longitud)
01 - 44 (44)
45 - 58 (14)
59 - 62 (04)
63 - 67 (05)

Tipo
Alfanumrico, justificado a la derecha
Alfanumrico, justificado a la derecha
Entero
Real

Un fragmento de cdigo para la lectura de los registros del fichero encolumnado


generado anteriormente, y el clculo de la media de la edad y la talla, podra ser el
siguiente:

402

Programacin en C
int cuantos = 0;
int edad;
float talla;
float media_edad = 0, media_talla = 0;
char nombr[45];
char provin[15];
...
while (leer_encolumnado(fp, nombr, provin, &edad, &talla)!=EOF) {
media_edad += edad;
media_talla += talla;
cuantos++;
}
media_edad /= cuantos;
media_talla /= cuantos;
...
int leer_encolumnado(FILE *f,

char
char
int
float

*pnom,
*pprov,
*pedad,
*ptalla) {

int
t_edad;
float t_talla;
if ( fscanf(f, "%44[^\t]%14[^\t]%4d%5f%*c",
pnom, pprov, &t_edad, &t_talla) == EOF )
return (EOF);
else {
recortarizq (pnom);
recortarizq (pprov);
*pedad = t_edad;
*ptalla = t_talla;
return 0;
}
}
void recortarizq (char *cad) {
/* Elimina los espacios por la izquierda de la cadena del
argumento, generando, por tanto, una cadena de longitud menor a la
recibida, o como mucho igual, en caso de que la cadena recibida no
contenga espacios por la izquierda */
const char ESPACIO = '\x20';
long k;
while (cad[0] == ESPACIO) {
k = 0;
while (cad[k] != '\0') {
cad[k] = cad[k+1];
k++;
}
}
}

Ciertos puntos de inters sobre la funcin leer_encolumnado() se comentan


a continuacin.
Para cada lnea del fichero de texto, el primer campo es una cadena que se
lee con el especificador de formato %44[^\t], para indicar que la
lectura de la cadena deber terminar al encontrar un tabulador o bien al
haber ledo 44 caracteres. En realidad como lo que se desea es leer 44
caracteres, entre corchetes se debera indicar un carcter delimitador que se

Estructuras externas de datos - Ficheros

403

sepa que no se va a encontrar entre los datos, por lo que otros


especificadores de formato vlidos habran sido, por ejemplo, %44[^\n]
o %44[^*] o %44[^@]. El segundo campo, tambin cadena de
caracteres, se lee con un especificador de formato similar.
El tercer campo es numrico y se lee con el especificador de formato %4d
para indicar que la lectura terminar al encontrar un carcter separador o
bien leer 4 caracteres. El cuarto campo se lee se forma similar.
Siempre hay que leer y desechar el propio carcter fin de lnea (para
posicionarse en la siguiente lnea del fichero), lo cual se hace con el
especificador de formato %*c.
En los ficheros encolumnados, los campos alfanumricos justificados a la
derecha (como es este caso), se leen con espacios por la izquierda. Por eso
se utiliza la funcin recortarizq() que elimina los espacios por la
izquierda en campos alfanumricos.
En caso de que los datos alfanumricos estuvieran justificados a la
izquierda, se leeran con espacios por derecha, por lo que se debera
utilizar una funcin similar a la descrita para eliminar los espacios por la
derecha.

13.10. Ficheros binarios


Algunas aplicaciones requieren el uso de archivos para almacenar bloques de datos,
donde cada bloque consiste en un nmero fijo de bytes contiguos. Cada bloque suele
representar una estructura compleja de datos. Por ejemplo, un archivo que conste de
varias estructuras del mismo tipo.
Para estas aplicaciones sera deseable leer o escribir el bloque entero del archivo en
lugar de leer o escribir separadamente los componentes individuales de cada bloque.
Las funciones de biblioteca fread() y fwrite(), referenciadas
frecuentemente como funciones de lectura y escritura sin formato, se usan en estas
ocasiones.

13.10.1. Funciones de lectura y escritura para ficheros binarios


La funcin fread() sirve para leer un bloque de datos de un fichero binario. En la
Tabla 13.13 se resumen los datos fundamentales necesarios para el uso de esta
funcin.
La funcin fread() lee, del stream asociado a fp, n elementos de tamao size
(en bytes) y los almacena en la direccin de memoria ptr. El puntero ptr est
declarado como de tipo genrico. La funcin fread() devuelve el nmero de
elementos realmente ledos, en caso de ser menor que n se debe utilizar las funciones
feof() y ferror() para determinar el estado del stream.
La funcin fwrite() sirve para escribir un bloque de datos de un fichero
binario. En la Tabla 13.14 se resumen los datos fundamentales necesarios para el uso
de esta funcin.

404

Programacin en C

La funcin fwrite() escribe n elementos de tamao size, que se encuentran


almacenados en la direccin ptr, en el stream asociado a fp. Devuelve el nmero de
elementos realmente escritos en el stream, indicando error en caso de ser menor que
n.
Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:
Finalidad:
Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:
Finalidad:

size_t fread(void *ptr, size_t size, size_t n,


FILE *fp);
stdio.h
El argumento ptr es un puntero al bloque de memoria donde se
almacena el bloque ledo del fichero.
El argumento size representa el tamao de los elementos a leer
dentro del bloque de datos.
El argumento n es el nmero de elementos a leer (el nmero de bytes
ledos es size*n).
El argumento fp es un puntero al stream binario.
Devuelve el nmero de objetos ledos. Si el valor de retorno es menor
que n, puede haber ocurrido un error de lectura.
Lectura de un bloque de datos desde el stream fp.
Tabla 13.13. Funcin fread()
size_t fwrite(const void *ptr, size_t size,
size_t n, FILE *fp);
stdio.h
El argumento ptr es un puntero al origen de los datos en memoria.
El argumento size representa el tamao de los elementos a escribir
dentro del bloque de datos.
El argumento n es el nmero de elementos a escribir.
El argumento fp es un puntero al stream binario.
Devuelve el nmero de elementos escritos. Si el valor de retorno es
menor que n, puede haber ocurrido un error de escritura.
Escritura de un bloque de datos a un stream fp.
Tabla 13.14. Funcin fwrite ()

13.10.2. Otras funciones para el manejo de ficheros binarios


El acceso directo o aleatorio a los datos de un archivo se hace mediante su posicin,
es decir, el lugar relativo que ocupan sus registros. Tiene la ventaja de que se pueden
leer y escribir registros en cualquier orden y posicin, y, por tanto, es muy rpido de
acceso a la informacin contenida en el archivo.
El principal inconveniente que tiene la organizacin directa es que es necesario
programar la relacin existente entre el contenido de un registro y la posicin que
ocupa.
Las funciones fseek() y ftell()consideran el archivo como una secuencia de
bytes. Segn se va leyendo o escribiendo registros en el archivo, el programa
mantiene a travs de un puntero la posicin actual (nmero de bytes existentes desde
el principio del archivo). Con la llamada a la funcin ftell() se obtiene el valor de
dicha posicin. La llamada a fseek() permite cambiar la posicin del puntero al
archivo, llevndolo a una direccin determinada.
A continuacin se exponen con ms detalle cada una de estas funciones.

Estructuras externas de datos - Ficheros

405

La funcin fseek() fija la posicin de acceso del stream con el objeto de realizar
operaciones de entrada y salida directa. En la Tabla 13.15 se resumen los datos
fundamentales necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:
Finalidad:

int fseek(FILE *fp, long offset, int origen);


stdio.h
El argumento fp es un puntero al stream binario.
El argumento offset indica el nmero de bytes a desplazarse a
partir del origen. El argumento origen es una macros con los
siguientes posibles valores:
SEEK_SET (0) comienzo del stream.
SEEK_CUR (1) posicin actual.
SEEK_END (2) fin del stream.
Devuelve 0 si ha tenido xito, distinto de 0 en caso de error.
Fija la posicin de acceso del stream asociado a fp. Los siguientes
accesos, de lectura o escritura, se realizarn a partir de esta nueva
posicin.
Tabla 13.15. Funcin fseek ()

La funcin ftell() retorna la posicin actual de acceso al stream. En la Tabla


13.16 se resumen los datos fundamentales necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:
Finalidad:

long ftell (FILE *fp);


stdio.h
El argumento fp es un puntero al stream binario.
Devuelve la posicin actual de acceso al stream fp, o -1L en caso
de error.
Retorna la posicin actual de acceso al stream.
Tabla 13.16. Funcin ftell ()

La funcin rewind() inicializa el indicador de posicin al principio del archivo


indicado en su argumento. En la Tabla 13.17 se resumen los datos fundamentales
necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:
Finalidad:

void rewind (FILE *fp);


stdio.h
El argumento fp es un puntero al stream binario.
Coloca el acceso al stream al comienzo del mismo, como si acabara
de ser abierto.
Tabla 13.17. Funcin rewind ()

La funcin fgetpos() almacena la posicin actual de acceso al stream en una


direccin de memoria. En la Tabla 13.18 se resumen los datos fundamentales
necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:
Finalidad:

int fgetpos(FILE *fp,fpos_t *ptr);


stdio.h
El argumento fp es un puntero al stream binario.
El argumento ptr es la direccin de memoria donde almacenar la
posicin actual de acceso al stream fp.
Devuelve el valor 0 si ha tenido xito, y un valor distinto de 0 en
caso de error.
Almacena la posicin actual de acceso al stream fp en la direccin
de memoria ptr, para posteriores llamadas a fsetpos().
Tabla 13.18. Funcin fgetpos()

406

Programacin en C

La funcin fsetpos() coloca el acceso al stream en la posicin devuelta por una


llamada anterior a fgetpos(). En la Tabla 13.19 se resumen los datos
fundamentales necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:
Finalidad:

int fsetpos(FILE *fp, const fpos_t *ptr);


stdio.h
El argumento fp es un puntero al stream binario.
El argumento ptr es la direccin de memoria donde est almacenada
la posicin actual de acceso al stream fp.
Devuelve el valor 0 si ha tenido xito, y un valor distinto de 0 en
caso de error.
Coloca el acceso al stream fp en la posicin devuelta por una
llamada a fgetpos() y que est almacenada en ptr.
Tabla 13.19. Funcin fsetpos()

13.10.3. Ejemplos de manejo de ficheros binarios


EJEMPLO

Almacenamiento y lectura de una tabla en un fichero binario.


#include <stdio.h>
#include <stdlib.h>
#define DIM 2
void
void
void
void

IntroduceTeclado(int m[][DIM], int lim);


GuardaFichero(int m[][DIM], char *nombre, int lim);
LeeFichero(int m[][DIM], char *nombre, int lim);
MuestraMatriz(int m[][DIM], int lim);

void main() {
int m1[DIM][DIM], m2[DIM][DIM];
char nombre[50];
/* Se introduce la matriz por teclado */
IntroduceTeclado(m1, DIM);
/* Se guardan los elementos de la matriz en un fichero binario */
puts("\nIntroduce el nombre del fichero.\n");
gets(nombre);
GuardaFichero(m1, nombre, DIM);
/* Se leen los datos de la matriz del fichero */
LeeFichero(m2, nombre, DIM);
/* Se presentan los datos por pantalla */
MuestraMatriz(m2, DIM);
}
void IntroduceTeclado(int m[][DIM], int lim) {
int j, i=0;
for(;i<lim;i++)
for(j=0;j<lim;j++) {
printf("\nElemento (%d, %d): ", i+1, j+1);
scanf("%d", &m[i][j]);
}
fflush(stdin);
}
void GuardaFichero(int m[][DIM], char *nombre, int lim) {
FILE *fich;
int j, i=0;
/* Se crea el fichero binario */

Estructuras externas de datos - Ficheros

407

if ( (fich=fopen(nombre, "w"))==NULL) {
printf("\nERROR AL CREAR EL FICHERO %s !!!!\n", nombre);
exit(-1);
}
/* Si no ha habido problemas se escriben los datos */
for(;i<lim;i++)
for(j=0;j<lim;j++)
fwrite(&m[i][j], sizeof(int), 1, fich);
/* Se cierra el fichero */
fclose(fich);
}
void LeeFichero(int m[][DIM], char*nombre, int lim) {
FILE *fich;
int j, i=0;
/* Se abre el fichero binario para lectura*/
if ((fich=fopen(nombre, "r"))==NULL) {
printf("\nERROR AL ABRIR EL FICHERO %s !!!!\n", nombre);
exit(-1);
}
/* Si no ha habido problemas se leen los datos */
for (;i<lim;i++)
for(j=0;j<lim;j++)
fread(&m[i][j], sizeof(int), 1, fich);
fclose(fich);
}
void MuestraMatriz(int m[][DIM], int lim) {
int j, i=0;
for (;i<lim;i++) {
for (j=0;j<lim;j++)
printf("%10d", m[i][j]);
printf("\n");
}
}

EJEMPLO

Volcado binario de un fichero. Cada lnea contiene 10 cdigos ASCII


seguidos de 10 caracteres.
#include <stdio.h>
#include <stdlib.h>
#define LON 10
#define FALSO 0
#define VERDADERO 1
void main() {
FILE *fich;
unsigned char cadena[LON+1];
char nombre[50];
int car, i, eof;
printf("\nIntroduce el nombre del fichero.\n"),
gets(nombre);
if ( (fich=fopen(nombre, "rb")) == NULL ) {
printf("\n\nERROR AL ABRIR EL FICHERO %s !!!!\n\n");
exit(-1);
}
eof = FALSO;
do {
for (i=0; i<LON; i++) {
if ( (car=getc(fich)) == EOF)
eof = VERDADERO;

408

Programacin en C
printf("%4x ", car);
if (car > 31)
*(cadena+i)=car;
else
*(cadena+i)='';
/* Carcter no imprimible */

}
*(cadena+i) = '\0';
printf("
%s\n", cadena);
} while (eof == FALSO);
fclose(fich);

EJEMPLO

Programa que realiza un mantenimiento de un almacn de productos.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define MAXPRO 100
struct Producto {
char cod[5];
int cantidad;
float precio;
};
struct Indice {
char cod[5];
unsigned int pos;
};
void CreaFicheroProducto (int);
void AbreFicheroProducto(int*, short unsigned int*, struct Indice *);
void NuevoProducto(int, short unsigned int*, struct Indice *);
void ListaProductos(int, short unsigned int, struct Indice *);
void OrdenaIndice(int, struct Indice*);
void main() {
int opc;
int flag=0;
short unsigned int np=0;
struct Indice indice[MAXPRO];
FILE *fich_idx;
randomize();
while (1) {
printf("\n1. Crear fichero de productos.");
printf("\n2. Cargar fichero de productos.");
printf("\n3. Aadir producto.");
printf("\n4. Listar productos.");
printf("\n5. Salir.\n\n");
printf("\nElige una opcin (1-5): ");
scanf("%d", &opc);
fflush(stdin);
switch(opc) {
case 1:
CreaFicheroProducto(flag);
break;
case 2:
AbreFicheroProducto(&flag, &np, indice);
break;
case 3:

Estructuras externas de datos - Ficheros

409

NuevoProducto(flag, &np, indice);


break;
case 4:
ListaProductos(flag, np, indice);
break;
case 5:
if (flag) {
if ( (fich_idx=fopen("produc.idx", "wb")) == NULL) {
printf("\n\nERROR al crear el fichero produc.idx.\n\n");
exit(-1);
}
fwrite(&np, sizeof(short unsigned int), 1, fich_idx);
fwrite(indice, sizeof(struct Indice), np, fich_idx);
fclose(fich_idx);
}
exit(1);
break;
default:
printf("\n\nERROR al introducir la opcin.\n\n");
};
}

void CreaFicheroProducto (int flag) {


FILE *fich, *fich_idx;
short unsigned int nproductos, j, i=0;
struct Producto p;
struct Indice idx[MAXPRO];
if (flag) {
printf("\nEl fichero de productos est cargado.\n");
printf("No puede crearse el fichero de productos.\n\n");
return;
}
/* Se crea el fichero produc.dat */
if ( (fich=fopen("produc.dat", "wb")) == NULL) {
printf("\n\nERROR al crear el fichero produc.dat.\n\n");
exit(-1);
}
/* Se crea el fichero produc.idx */
if ( (fich_idx=fopen("produc.idx", "wb")) == NULL) {
printf("\n\nERROR al crear el fichero produc.idx.\n\n");
exit(-1);
}
/* Se inicia de forma aleatoria */
do {
printf("Cuntos tipos de productos hay en el almacn?\n");
scanf("%d", &nproductos);
} while (nproductos >MAXPRO);
fwrite(&nproductos, sizeof(short unsigned int), 1, fich_idx);
for (;i<nproductos;i++) {
p.cantidad = random (100) + 1;
p.precio = (random (300)+1) / 100.0;
for (j=0; j<3; j++)
p.cod[j] = '0'+(i%10);
p.cod[3]= (char) (random(26)+65);
p.cod[4]='\0';
strcpy(idx[i].cod, p.cod);
idx[i].pos=i+1;
fwrite(&p, sizeof(struct Producto), 1, fich);
}

410

Programacin en C

OrdenaIndice(nproductos, idx);
fwrite(idx, sizeof(struct Indice), nproductos, fich_idx);
fclose(fich);
fclose(fich_idx);

void OrdenaIndice(int np, struct Indice *idx) {


int i, j;
struct Indice tmp;
for (i=0; i<np-1; i++)
for (j=i+1;j<np;j++)
if ( strcmp(idx[i].cod, idx[j].cod)>0 ) {
tmp=idx[i];
idx[i]=idx[j];
idx[j]=tmp;
}
}
void AbreFicheroProducto(int *flag, short unsigned int *np,
struct Indice *idx) {
FILE *fich, *fich_idx;
if (*flag) {
printf("\nEl fichero ya est cargado.\n\n");
return;
}
/* Se abre el fichero produc.dat */
if ( (fich=fopen("produc.dat", "rb")) == NULL) {
printf("\n\nERROR al abrir el fichero produc.dat.\n\n");
exit(-1);
}
/* Se abre el fichero produc.idx */
if ( (fich_idx=fopen("produc.idx", "rb")) == NULL) {
printf("\n\nERROR al abrir el fichero produc.idx.\n\n");
exit(-1);
}
*flag=1;
fread(np, sizeof(short unsigned int), 1, fich_idx);
fread(idx, sizeof(struct Indice), *np, fich_idx);
fclose(fich);
fclose(fich_idx);
}
void NuevoProducto(int flag, short unsigned int *np,
struct Indice *idx){
FILE *fich;
struct Producto p;
int i,j;
if (!flag) {
printf("\nEl fichero de productos no ha sido cargado.\n");
return;
}
if (*np==MAXPRO) {
printf("\nEl fichero de productos est lleno.\n");
return;
}
if ( (fich=fopen("produc.dat", "a")) == NULL) {
printf("\n\nERROR al abrir el fichero produc.dat.\n\n");
exit(-1);
}

Estructuras externas de datos - Ficheros

411

printf("\nIntroduce el cdigo del producto.\n");


gets(p.cod);
for (i=0; i<*np; i++) {
if (!strcmp(p.cod, idx[i].cod)) {
printf("\nCdigo existente.\n");
fclose(fich);
return;
}
}
printf("\nIntroduce la cantidad.\n");
scanf("%d", &p.cantidad);
printf("\nIntroduce el precio en euros.\n");
scanf("%f", &p.precio);
fflush(stdin);
fseek(fich, 0L, SEEK_END);
fwrite(&p, sizeof(struct Producto), 1, fich);
j=0;
while ( (strcmp(p.cod,idx[j].cod))>0 ) j++;
for (i=*np; i>j; i--)
idx[i]=idx[i-1];

strcpy(idx[j].cod, p.cod);
*np += 1;
idx[j].pos = *np;
fclose(fich);

void ListaProductos(int flag,short unsigned int np, struct Indice *idx) {


FILE *fich;
int i;
struct Producto p;
if (!flag) {
printf("\nEl fichero de productos no ha sido cargado.\n");
return;
}
/* Se abre el ficherp produc.dat */
if ( (fich=fopen("produc.dat", "rb")) == NULL) {
printf("\n\nERROR al abrir el fichero produc.dat.\n\n");
exit(-1);
}
printf("\nHay %d productos.\n\n", np);
for (i=0; i<np; i++) {
fseek(fich, sizeof(struct Producto)*(idx[i].pos-1),SEEK_SET);
fread(&p, sizeof(struct Producto), 1, fich);
printf("Producto: %s\n", p.cod);
printf("Cantidad: %d\n", p.cantidad);
printf("Precio unidad en euros: %5.2f\n\n", p.precio);
}
getch();
fclose(fich);
}

13.11. Cuestiones y ejercicios


1.

Cules son los tipos de ficheros soportados por el lenguaje ANSI C?

412

2.
3.
4.
5.

6.
7.
8.
9.

Programacin en C

Explica para qu se utiliza una funcin de hash en una organizacin directa


de un fichero.
Qu papel juega el puntero a FILE en las operaciones con ficheros en el
lenguaje C?
Cules son las diferencias entre las funciones printf() y fprintf()?
Se quiere guardar una matriz 3x3 en un fichero de texto, de forma que el
fichero resultado tenga el siguiente formato (donde la separacin entre los
nmeros de una lnea es el tabulador):
1
2
3
4
5
6
7
8
9
Realizar un programa que lea matrices MxN y NxO de ficheros de texto, las
multiplique, y almacene el resultado en otro fichero de texto.
Realizar un programa que pida un nombre de un fichero en disco y un
nmero entero positivo mayor que cero, y que presente en pantalla el byte
del archivo que se corresponde con el nmero introducido.
Realizar un programa que gestione un listn telefnico utilizando un fichero
binario.
Realizar un programa que lea matrices cuadradas de un fichero, calcule su
matriz transpuesta y su inversa, almacenando ambas matrices en sendos
ficheros. Hacer una versin que trabaje con ficheros de texto y otra que lo
haga con ficheros binarios.

14. Gestin de la memoria dinmica


En los captulos anteriores se ha hecho referencia a la memoria considerando la
asignacin esttica. Siempre que se asignaba una direccin de memoria vlida a un
puntero, ste se corresponda con una direccin de memoria de una variable definida
en el programa, estas variables se consideran estticas.
Las variables estticas son aqullas que deben ser declaradas por adelantado en
la seccin de declaraciones de un mdulo concreto, obligando a definir de antemano
su tamao, para que en tiempo de compilacin se reserve en memoria el suficiente
espacio para ellas.
Esto significa que, si bien sus valores pueden cambiar en tiempo de ejecucin,
desde el momento de su declaracin se les asigna unas posiciones de memoria fijas e
inalterables durante toda la ejecucin del programa.
Como no siempre es posible conocer con antelacin a la ejecucin cunta memoria
se debe reservar, se recurre en programacin a los punteros y a la asignacin dinmica
de memoria.
C permite una gestin dinmica de la memoria, es decir, solicitar memoria para
albergar el contenido de estructuras de datos cuyo tamao exacto no se conoce hasta
que se ha iniciado la ejecucin del programa.
La gestin de la memoria dinmica se considera un aspecto avanzado de
programacin, pues en lenguajes como C, toda la responsabilidad del correcto manejo
de la misma recae en el programador, siendo una fuente importante de errores
difciles de detectar y corregir.
Se estudian aqu dos formas de superar estas limitaciones de tamao que impone
C: mediante el uso de vectores cuyo tamao se fija en tiempo de ejecucin, y
mediante en empleo de listas enlazadas.
Ambas implementaciones se basan en el uso de punteros, y cada una de ellas
presenta ventajas e inconvenientes.
El presente captulo ha dividido en ocho secciones. La primera explica de forma
genrica la asignacin dinmica de memoria, mientras que la segunda hace lo propio
pero desde el punto de vista del lenguaje C, sobre todo enfatizando en el trabajo con
matrices dinmicas. La seccin tercera se introduce el concepto de estructuras de
datos dinmicas. La cuarta seccin estudia las listas simplemente enlazadas desde el
punto de vista de sus operaciones. En la seccin cinco se introduce la lista doblemente
enlazada. La seccin seis presenta otras estructuras dinmicas ms complejas (lista
circular, pilas y colas). La seccin sptima realiza un resumen del captulo, para
terminar en la seccin octava con una serie de ejercicios y cuestiones de repaso.

14.1. Asignacin dinmica de memoria


La memoria dinmica delimita una zona donde la reserva no se realiza definiendo
variables, sino por medio de funciones especficas de reserva y liberacin de
memoria, en cualquier momento durante la ejecucin. sta es una zona perteneciente
al programa que es distinta que la memoria de datos.
- 413 -

414

Programacin en C

El cdigo del programa compilado se sita en segmentos de memoria denominados


segmentos de cdigo. Los datos del programa, tales como variables globales, se sitan
en un rea denominada segmento de datos. Las variables locales y la informacin de
control del programa se sita en un rea denominada pila, existiendo slo mientras
estn activas las funciones en las que estn declaradas. La memoria restante se
denomina montculo (heap) o almacn libre. Cuando el programa solicita memoria
para una variable dinmica, se asigna el espacio de memoria deseado desde el
montculo. Para grandes modelos de datos, el almacn libre se refiere al rea de
memoria que existe dentro de la pila del programa que es toda la memoria que queda
libre despus de que se carga el programa.
La gestin de la memoria se realiza principalmente por medio de dos funciones,
una que permite reservar una parte de esa memoria para uso propio y la otra que
libera una zona previamente reservada para permitir futuras reservas.
Las variables dinmicas permiten reservar el espacio necesario en memoria en
tiempo de ejecucin, posibilitando posteriormente su liberacin, para as, en otra parte
del mismo programa, poder usar en otro momento la misma zona de memoria con un
contenido diferente.
Esta manera de utilizar la memoria se puede repetir tantas veces como sea
necesario durante la ejecucin de un programa, teniendo siempre la precaucin de ir
liberando aquellas porciones de memoria reservadas anteriormente y que ya no se van
a volver a utilizar.
Las variables creadas de esta forma se llaman dinmicas, porque se crean y
destruyen en tiempo de ejecucin.
La posibilidad de crear variables y asignar memoria a voluntad es una potente
herramienta. Por ejemplo, si se crea una variable esttica como una matriz, el nmero
de posiciones de memoria destinadas para su almacenamiento se fija en su
declaracin inicial, pero si se crea una variable dinmica, se podr aumentar o
disminuir el nmero de posiciones de memoria conforme se ejecute el programa.

14.1.1. Creacin de una variable dinmica


Para crear una variable dinmica no se reserva una posicin de memoria para
almacenar su contenido, sino que se reserva una posicin de memoria para almacenar
un puntero (es decir, se declara un puntero) que sealar a la posicin en que se
almacenar la variable dinmica.
Crear, por tanto, una variable dinmica es solicitar en ejecucin un bloque de
memoria libre, cuya direccin de comienzo se almacenar en un puntero, donde
posteriormente, a travs de ste puntero, se le asignarn valores.
Para realizar esta operacin se requiere de un procedimiento o funcin intrnseca,
que la prcticamente totalidad de lenguajes de alto nivel poseen, que reserve la
memoria libre necesaria y que devuelva la posicin de comienzo de la misma para
poder almacenarla en el puntero.
En pseudocdigo esta operacin se puede representar como:
nombre_puntero reservar(tam)

Gestin de la memoria dinmica

415

La llamada a esta funcin reserva una zona de memoria de tantas posiciones


(bytes) como se indique en su parmetro tam, devolviendo la direccin de comienzo
de la misma, que se carga en la variable nombre_puntero.

14.1.2. Destruccin de una variable dinmica


Consiste en hacer que la variable puntero deje de apuntar a la direccin de memoria
asignada dinmicamente, liberando el espacio de memoria solicitado anteriormente.
Para realizar esta operacin se requiere de un procedimiento o funcin intrnseca,
que la prcticamente totalidad de lenguajes de alto nivel poseen, que libere la zona de
memoria apuntada por el puntero.
En pseudocdigo esta operacin se puede representar como:
liberar(nombre_puntero)
Esta funcin o procedimiento normalmente slo puede utilizarse para liberar zonas
de memoria previamente reservadas por la funcin o procedimiento reservar.
Normalmente, despus de liberar la zona de memoria apuntada por un puntero se
inicia ste a nulo para indicar que ya no apunta a ninguna zona de memoria vlida.
nombre_puntero NULO
EJEMPLO

En este ejemplo se van a declarar dos variables puntero, una a entero y otra a
real. Se reserva espacio para crear dos variables dinmicas, cada una de ellas
apuntada por un puntero (tamao representa el operador que devuelve el
nmero de bytes que ocupa un tipo de dato).
Estas variables dinmicas no tienen nombre, y la nica forma de acceder a
ellas es a travs de los respectivos punteros que las apuntan.
Se utilizan para almacenar dos datos que se leen de teclado y posteriormente
se presenta la suma de los datos introducidos.
Antes de finalizar se libera el espacio de memoria reservado anteriormente.
/* -- Declaraciones -- */
puntero a entero pt1
puntero a real pt2
...

Nombre
variable

Direccin
comienzo

Direcciones
de memoria
libres
Direcciones de
memoria
crecientes
pt1

FFB4

pt2

FFB2

416

Programacin en C

pt1 reservar(tamao(entero))
pt2 reservar(tamao(real))
leer (pt1)
Nombre
leer (pt2)
variable
escribir (pt1 + pt2)
liberar (pt1)
liberar (pt2)

Direccin
comienzo

5.14
FFEA
FFE8

pt1

FFB4

pt2

FFB2

FFE8
FFEA

14.2. Asignacin dinmica de memoria en C


Las funciones de biblioteca que el ANSI C define para las operaciones de asignacin
dinmica de memoria se introducen en este subapartado.

14.2.1. Funcin calloc()


La funcin calloc() sirve para reservar un bloque de memoria, bloque que
quedara iniciado a 0. En la Tabla 14.1 se resumen los datos fundamentales necesarios
para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:

Finalidad:

void * calloc(int num, int tam);


stdlib.h
num: indica el nmero de elementos con la misma estructura
que ocuparn el bloque de memoria reservado.
tam: indica el tamao en bytes de cada uno de los elementos
que van a ocupar el bloque de memoria reservada.
Devuelve un puntero al primer byte del bloque de memoria
reservada, o un puntero NULL en el caso de no haberse podido
reservar el bloque de memoria solicitado. El puntero que devuelve
es del tipo void *.
Reserva un bloque de memoria para almacenar num elementos de
tam bytes cada uno de ellos. Todo el bloque de memoria queda
iniciado a 0.
Tabla 14.1. Funcin calloc()

Direcciones de
memoria
crecientes

Gestin de la memoria dinmica

417

La cantidad de memoria reservada viene determinada por el resultado que se


obtiene al multiplicar el nmero de elementos a almacenar en el bloque de memoria
por el tamao en bytes de cada uno de esos elementos, es decir, num * tam.
EJEMPLO

El siguiente ejemplo reserva memoria para una variable cadena.


char *c, B [121];
puts ( \n Introduzca una lnea de caracteres );
gets (B);
/*Se reserva memoria para el nmero de caracteres+1 */
c = ( char *) calloc ( strlen (B) + 1, sizeof (char));
strcpy (c, B);

EJEMPLO

Se quiere reservar memoria para cinco datos de tipo double.


#define N 5
double * pd;
pd = (double *) calloc (N,sizeof (double));

EJEMPLO

Se considera una secuencia de nmeros reales. Con una variable puntero a


float se procesa una lista de longitud variable, de modo que se pueda
ajustar a la cantidad de memoria necesaria para el nmero de valores durante
la ejecucin del programa.
# include <stdio.h>
# include <stdlib.h>
int main ( void ) {
float *pf = NULL;
int num, i;
do {
printf (\n Numero de elementos del vector: );
scanf(%d,&num);
}while ( num < 1);
/*asigna memoria: num*tamao bytes */
pf = ( float *)calloc(num, sizeof (float));
if (pf == NULL) {
puts ( Error en la asignacin de memoria);
return 1;
}
printf (\n Introduce %d valores, num);
for ( i = 0; i < num; i++)
scanf( %f , &pf[i] ); /*proceso del vector*/
free (pf);
return 0
}

14.2.2. Funcin malloc()


La funcin malloc() sirve para reservar un bloque de memoria. En la Tabla 14.2 se
resumen los datos fundamentales necesarios para el uso de esta funcin.
Esta funcin, al igual que la anterior, devuelve un puntero especial del tipo de
datos void *, lo que significa puntero a cualquier tipo de datos, o sea direccin
de memoria, permitiendo que se pueda reservar memoria para cualquier tipo base.

418

Programacin en C

Prototipo:
Archivo cabecera:
Argumentos:
Valor devuelto:

Finalidad:

void * malloc(int tam);


stdlib.h
tam: indica el tamao en bytes del bloque de memoria que
se desea reservar.
Devuelve un puntero al primer byte del bloque de memoria
reservada, o un puntero NULL en el caso de no haberse podido
reservar el bloque de memoria solicitado. El puntero que devuelve
es del tipo void *.
Reserva un bloque de memoria de tam bytes.
Tabla 14.2. Funcin malloc()

EJEMPLO

Reservar espacio para 100 nmeros reales.


float *BloqueMem;
BloqueMem = (float *) malloc(100 * sizeof (float));

EJEMPLO

La reserva de n caracteres puede declararse como sigue:


int n;
/* se hace la reserva de memoria de un tamao desconocido */
char *s;
scanf (%d, &n);
s = (char *) malloc (n * sizeof (char));

Tanto la funcin malloc()como calloc() pueden utilizarse para obtener


bloques de memoria libre en tiempo de ejecucin, slo cambia la forma de transmitir
el nmero de bytes de memoria requeridos.
En el primer caso la cantidad de memoria reservada ser de tam bytes, es decir, el
nmero de bytes que queremos reservar. En el segundo es el resultado que se obtiene
al multiplicar el nmero de elementos a almacenar en el bloque de memoria, por el
tamao en bytes de cada uno de esos elementos, es decir, num * tam.

14.2.3. Funcin realloc()


La funcin realloc() sirve para cambiar el tamao de un bloque de memoria. En
la Tabla 14.3 se resumen los datos fundamentales necesarios para el uso de esta
funcin.
Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:

Finalidad:

void *realloc(void *ptr, int nuevo_tamao);


stdlib.h
ptr: puntero que apunta al bloque de memoria reservado.
nuevo_tamao: valor en bytes que indica el nuevo tamao
del bloque de memoria apuntado por ptr y que puede ser
mayor o menor que el original.
Devuelve un puntero al primer byte del bloque de memoria
reservada, o un puntero NULL en el caso de no haberse podido
reservar el bloque de memoria solicitado. El puntero que devuelve
es del tipo void *
Cambia el tamao del bloque de memoria apuntada por ptr al
nuevo tamao indicado por nuevo_tamao.
Tabla 14.3. Funcin realloc()

Gestin de la memoria dinmica

419

EJEMPLO

Aqu el segundo argumento de realloc() es el tamao total que va tener


el bloque de memoria libre, si se pasa (0) como tamao se libera el bloque de
memoria al que est apuntando el puntero primer argumento, y la funcin
devuelve NULL. Se reserva memoria con calloc() y luego se libera con
realloc(). Se quiere reservar memoria para 10 datos de tipo double.
#define N 10
long *p1;
p1 = (long *) calloc (N,sizeof (long));
...
p1 = realloc (p1, 0);

EJEMPLO

Se reserva memoria para una cadena de caracteres y a continuacin, se


amplia por otra cadena ms larga. Se pueden usar cadenas de cualquier
longitud gracias a la memoria dinmica.
#include <stdio.h>
#include <stdio.h>
#include <string.h>
int main ( ) {
char *cadena;
int tam;
tam = (strlen( Primavera) + 1) * sizeof (char);
cadena = ( char *) malloc (tam);
strcpy( cadena, Primavera); puts(cadena);
/* Ampliar el bloque de memoria */
tam += (strlen(en Salamanca \n)+1)*sizeof(char);
cadena = ( char *) realloc (cadena, tam);
strcat (cadena, en Salamanca \n);
puts (cadena);
free ( cadena);
return 0;
}

En estas tres funciones es importante comprobar si se pudo obtener la memoria


solicitada, por lo que debe comprobarse si el puntero devuelto por ellas no es un
puntero nulo (NULL) antes de hacer uso del bloque de memoria reservado .

14.2.4. Funcin free()


La funcin free() sirve para liberar un bloque de memoria que previamente ha sido
reservado por las funciones calloc() o malloc(). En la Tabla 14.4 se resumen
los datos fundamentales necesarios para el uso de esta funcin.
Prototipo:
Archivo cabecera:
Argumentos:

Valor devuelto:
Finalidad:

void free(void *ptr);


stdlib.h
ptr: variable puntero que debe contener la direccin de
memoria del primer byte del bloque de memoria que se desea
liberar. En caso de que no sea un puntero previamente
reservado por malloc() o calloc().
Ninguno.
Libera el bloque de memoria apuntada por ptr y que previamente
ha sido asignado mediante malloc() o calloc().
Tabla 14.4. Funcin free()

420

Programacin en C

EJEMPLO

Se lee una lnea de caracteres, se reserva memoria para un buffer de tantos


caracteres como los ledos y se copia en el buffer la cadena.
#include <stdio.h>
#include <string.h>
void main ( ) {
char cad [121]; *ptr;
int lon;
puts ( \n Introduzca la lnea de texto \n );
gets (cad);
lon = strlen (cad);
ptr = ( char *) malloc (lon + 1) * sizeof (char));
/*copia cad a nueva area de memoria apuntada por ptr */
strcpy(ptr, cad);
printf (ptr = %s , ptr); /*cad est ahora en ptr */
free(ptr);
}

14.2.5. Creacin de matrices dinmicas


El nombre de una matriz es realmente un puntero constante que se asigna en tiempo
de compilacin.
float m[30];
/* m es un puntero constante a un bloque de 30 floats */
float * const p= (float *) malloc(30*sizeof (float));
/* m y p son punteros constantes a bloques de 30 nmeros reales */

Este tipo de ligadura es esttica, se llama as a la declaracin de m, debido a que


se asigna en tiempo de compilacin y el smbolo se enlaza a la memoria aunque nunca
se utilice durante la ejecucin del programa.
Por otra parte se llama ligadura dinmica, cuando se utiliza un puntero no
constante para posponer la asignacin de memoria hasta que el programa se est
ejecutando.
float *p= (float *) malloc(30*sizeof (float));

Una matriz que se declara de este modo se denomina Matriz Dinmica.


Se puede invocar la funcin malloc() para obtener memoria para una matriz
incluso aunque no se conozca con antelacin cunta memoria requieren los elementos
de la matriz.
14.2.5.1. Vectores dinmicos
Se conoce ya cmo definir vectores indicando su tamao en tiempo de compilacin:
#define TALLA 10
int a[TALLA];

Pero, y si no se sabe a priori cuntos elementos debe albergar el vector? Por lo


estudiado hasta el momento, se puede definir TALLA como el nmero ms grande de
elementos posible, el nmero de elementos para el peor de los casos. Pero, y si no se
puede determinar un nmero mximo de elementos? Aunque se pudiera, y si ste
fuera tan grande que, en la prctica, supusiera un despilfarro de memoria intolerable
para situaciones normales?

Gestin de la memoria dinmica

421

Volviendo al mencionado ejemplo de la agenda telefnica personal que, por si


acaso, reserva 100.000 entradas en un vector. Lo ms probable es que un usuario
convencional no gaste ms de un centenar. Se estara desperdiciando, pues, unas
99.900 celdas del vector, cada una de las cuales puede consistir en un centenar de
bytes. Si todas las aplicaciones del ordenador se disearan as, la memoria disponible
se agotara rpidamente.
Afortunadamente, se puede definir durante la ejecucin del programa vectores
cuyo tamao es exactamente el que el usuario necesita.
EJEMPLO
1 #include <stdlib.h>
2 #include <stdio.h>
3
4 int main(void )
5 {
6 int * a;
7 int talla, i;
8
9 printf("Nmero de elementos: "); scanf("%d ", &talla);
10 a = (int * )malloc( talla * sizeof (int ) );
11 for (i=0; i<talla; i++)
12
a[i] = i;
13 free(a);
14
15
16 return 0;
17 }

Se ha definido el vector a (lnea 6): como int *a, es decir, como puntero a
entero.
No se trata de un puntero a un entero, sino de un puntero a una secuencia de
enteros. Ambos conceptos son equivalentes en C, pues ambos son meras
direcciones de memoria.
La variable a es un vector dinmico de enteros, pues su memoria se obtiene
dinmicamente, esto es, en tiempo de ejecucin y segn convenga a las
necesidades. No se conoce an cuntos enteros sern apuntados por a, ya que
el valor de talla no se conocer hasta que se ejecute el programa y se lea
por teclado.
La lnea 10 reserva memoria para talla enteros y guarda en a la direccin
de memoria en la que empiezan esos enteros.
Si el usuario decide que talla valga, por ejemplo, 5, se reservar un total
de 20 bytes y la memoria quedar as tras ejecutar la lnea 10:

Es decir, se reserva suficiente memoria para albergar 5 enteros.


EJEMPLO

Seleccin de elementos de un vector dinmico. Se comienza por disear una


funcin que recibe un vector de enteros, selecciona aqullos cuyo valor es

422

Programacin en C

par y los devuelve en un nuevo vector cuya memoria se solicita


dinmicamente.
int * selecciona_pares(int a[], int talla) {
int i, j, numpares = 0;
int * pares;
/* Primero se ha de averiguar cuntos elementos pares hay */
for (i=0; i<talla; i++)
if (a[i] % 2 == 0)
numpares++;
/* Ahora se puede pedir memoria para ellos */
pares = (int *)malloc( numpares * sizeof (int ) );
/* Copiar los elementos pares en la zona de memoria solicitada */
j = 0;
for (i=0; i<talla; i++)
if (a[i] % 2 == 0)
pares[j++] = a[i];
return pares;
}

14.2.5.2. Matrices dinmicas


Se puede extender la idea de los vectores dinmicos a matrices dinmicas. Se va
analizar detenidamente el siguiente programa:
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(void)
5 {
6 double ** m = NULL;
7 int filas, columnas;
8
9 printf("Filas: "); scanf("%d ", &filas);
10 printf("Columnas: "); scanf("%d ", &columnas);
11
12 /* reserva de memoria */
13 m = (double **)malloc(filas * sizeof (double *));
14 for (i=0; i<filas; i++)
15 m[i] = (double *)malloc(columnas * sizeof (double ));
16
17 /* trabajo con m[i][j] */
18 ...
19
20 /* liberacin de memoria */
21 for (i=0; i<filas; i++)
22 free(m[i]);
23 free(m);
24
25
26 return 0;
27 }

Se comienza por la declaracin de la matriz (lnea 6). Es un puntero un poco


extrao: se declara como double **m. Dos asteriscos, no uno. Esto es porque se

Gestin de la memoria dinmica

423

trata de un puntero a un puntero de doubles o, equivalentemente, un vector dinmico


de vectores dinmicos de doubles. Las lneas 9 y 10 solicitan al usuario los valores de
filas y columnas.
En la lnea 13 aparece una peticin de memoria. Se solicita espacio para un nmero
filas de punteros a double. Supngase que filas vale 4. Tras esa peticin, se
tendra la siguiente asignacin de memoria para m:

El vector m es un vector dinmico cuyos elementos son punteros (del tipo


double*). De momento, estos punteros no apuntan a ninguna zona de memoria

reservada. De ello se encarga la lnea 15. Dicha lnea est en un bucle, as que se
ejecuta para m[0], m[1], m[2], ... El efecto es proporcionar un bloque de
memoria para cada celda de m. He aqu el efecto final:

Cmo se usa m ahora? Pues cmo cualquier matriz! Pero, qu ocurre cuando se
accede a m[1][2]. Analcese m[1][2] de izquierda a derecha. Primero se tiene m,
que es un puntero (tipo double**), o sea, un vector dinmico a elementos del
tipo double*. El elemento m[1] es el segundo componente de m, y de qu tipo es?
De tipo double*, un nuevo puntero o vector dinmico, pero a valores de tipo
double. Si es un vector dinmico, se puede indexar, as que es vlido escribir
m[1][2], y de qu tipo es esta expresin? De tipo double, como se muestra a
continuacin.
m es de tipo double **.
m[1] es de tipo double *.
m[1][2] es de tipo double.

Con cada indexacin, desaparece un asterisco del tipo de datos.

424

Programacin en C

Resta la liberacin de memoria. Obsrvese que hay una llamada a free() por cada
llamada a malloc() realizada con anterioridad (lneas 2023). Se ha de liberar cada
uno de los bloques reservados y se ha de empezar a hacerlo por los de segundo nivel,
es decir, por los de la forma m[i]. Si se comenzara liberando m, se cometera un
grave error: si se libera m antes que todos los m[i], se perdera el puntero que los
referencia y, en consecuencia, no podran ser liberarlos!
EJEMPLO

Se puede utilizar cualquier funcin de asignacin de memoria para reservar


espacios para objetos ms complejos, tales como estructuras y matrices en el
almacenamiento libre.
#include <stdib.h>
struct complejo { float x, y; };
void main () {
struct complejo* pz; /* puntero a estructura complejo */
int n;
scanf(%d, &n);
/* Asignar memoria para un array de tipo complejo */
pz = (struct complejo *) calloc (n, sizeof (struct complejo));
if (pz = = NULL) {
puts ( Error de asignacin de memoria);
exit (-1);
}
}

EJEMPLO

Se ver ahora un programa que multiplica matrices cuadradas utilizando


matrices bidimensionales creadas dinmicamente.
#include <stdio.h>
#include <stdlib.h>
float **crearMatriz(int filas, int columnas) {
float **m;
int j;
m = (float **) malloc(filas * sizeof(float *));
for (j = 0; j < filas; j++)
m[j] = (float *)malloc(columnas * sizeof(float));
return (m);
}
void destruirMatriz(float **m, int filas, int columnas) {
int j;
for (j = 0; j < filas; j++)
free(m[j]);
free(m);
return;
}
void leerMatriz(float **m, int filas, int columnas) {
int i, j;
float dato;
for (i = 0; i < filas; i++) {
for (j = 0; j < columnas; j++) {
printf("Elemento [%d,%d]: ", i,j);
scanf("%f", &dato);
m[i][j]=dato;
}

Gestin de la memoria dinmica

425

printf("\n");
}

void imprimirMatriz(float **m, int filas, int columnas) {


int i, j;
for (i = 0; i < filas; i++) {
for (j = 0; j < columnas; j++) {
printf("[ %e ] \t", m[i][j]);
}
printf("\n");
}
}
void multiplicarMatriz(float **A, float **B, float **C, int dimension) {
int i, j, k;
for(i = 0; i < dimension; i++) {
for(j = 0; j < dimension; j++) {
C[i][j] = 0.0;
for(k = 0; k < 2; k++) {
C[i][j] += A[i][k] * A[k][j];
}
}
}
}
int main(void) {
float **A, **B, **C;
int dimension;
/*
* Leer la dimensin de la matriz
*/
printf("Nmero de filas: ");
scanf("%d", &dimension);
/* Crear las tres matrices
*/
A = crearMatriz(dimension, dimension);
B = crearMatriz(dimension, dimension);
C = crearMatriz(dimension, dimension);
leerMatriz(A,dimension, dimension);
leerMatriz(B,dimension, dimension);
multiplicarMatriz(A, B, C, dimension);
imprimirMatriz(C, dimension, dimension);
destruirMatriz(A, dimension, dimension);
destruirMatriz(B, dimension, dimension);
destruirMatriz(C, dimension, dimension);
return (0);
}

EJEMPLO

El siguiente ejemplo tambin realiza una multiplicacin de matrices


bidimensionales, pero utiliza otras formas equivalentes de hacer la
declaracin de matrices, reserva y liberacin de memoria dinmica .
Como una matriz unidiemensional se puede representar en trminos de
punteros (el nombre de la matriz) y de un desplazamiento (el ndice), es
razonable esperar que las matrices multidimensionales puedan ser
representados tambin con una notacin equivalente de punteros.

426

Programacin en C

En efecto este es el caso. Por ejemplo, una matriz bidimensional es, en


realidad, una matriz unidimensional de elementos que son, a su vez, matrices
unidimensionales. Por tanto se puede definir una matriz bidimensional como
un puntero a una zona de memoria compuesta por un grupo contiguo de
matrices unidimensionales:
tipo-dato (*puntero)[TAM2];

De forma que se trata de un puntero que apuntar a los elementos de una


matriz bidimensional del tipo:
tipo-dato mat[TAM1][TAM2];

De forma general para matrices multidimensionales este concepto se puede


generalizar:
tipo-dato (*puntero)[TAM2][TAM3]...[TAMN];

De forma que se trata de un puntero que apuntar a los elementos de una


matriz multidimensional del tipo:
tipo-dato mat[TAM1][TAM2][TAM3]...[TAMN];

Debe recordarse que los parntesis deben estar presentes, sino sera sendas
matrices de punteros. ste es el caso de la declaracin int *pt1[N] que
representa una matriz de punteros, en estos casos debe ser de una dimensin
menor que la matriz multidimensional.
En trminos generales, una matriz bidimensional, que es este caso, se puede
definir como una matriz unidimensional de punteros escribiendo:
tipo-dato *puntero[TAM1];

En vez de la definicin convencional:


tipo-dato mat[TAM1][TAM2];

Obsrvese como la reserva y comprobacin de memoria para la primera


matriz se realiza mediante:
for (i=0;i<N;i++)
if (NULL==(pt1[i]=(int *) calloc(M,sizeof(int))))

En los que se reserva espacio para una matriz unidimensional de punteros a


enteros.
En la matriz que se declara como int(*pt2)[M], en vez de en la forma
tradicional int pt2[N][M], pt2 se define como un puntero a un grupo
contiguo de matrices unidimensionales de M elementos enteros.
La reserva para esa matriz se realiza mediante la sentencia:
if(NULL==(pt2=(int (*)[M])calloc(N,sizeof(int[M]))))

Que corresponde con el espacio necesario para almacenar las N filas por M
columnas.
La matriz resultante de la multiplicacin se declara igual que en los dos
primeros ejemplos estudiados con matrices dinmicas, ella representa una
matriz dinmica de matrices dinmicas (int **pt3). Notar que la reserva
y comprobacin de memoria se hace de forma similar a los ejemplos
estudiados, pero en este caso utilizando calloc().
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N
100
#define M
100
void main(void) {
int *pt1[N],(*pt2)[M],**pt3,i,j,k;

Gestin de la memoria dinmica

427

clock();
for(i=0;i<N;i++)
if(NULL==(pt1[i]=(int *)calloc(M,sizeof(int)))){
for(j=i-1;j>=0;j--) free(pt1[j]);
printf("\nError al reservar memoria 1...");
exit(0);
}
if(NULL==(pt2=(int (*)[M])calloc(N,sizeof(int[M])))) {
for(j=N-1;j>=0;j--) free(pt1[j]);
printf("\nError al reservar memoria 2...");
exit(0);
}
if(NULL==(pt3=(int **)calloc(N,sizeof(int **)))) {
for(j=N-1;j>=0;j--) free(pt1[j]);
free(pt2);
printf("\nError al reservar memoria 3...");
exit(0);
}
for(i=0;i<N;i++)
if(NULL==(pt3[i]=(int *)calloc(M,sizeof(int)))) {
for(j=N-1;j>=0;j--) free(pt1[j]);
free(pt2);
for(j=i-1;j>=0;j--) free(pt3[j]);
free(pt3);
printf("\nError al reservar memoria 4...");
exit(0);
}
srand((int)clock());
for(i=0;i<N;i++)
for(j=0;j<M;j++) {
pt1[i][j]=rand();
pt2[i][j]=rand();
}
for (i=0;i<N;i++)
for(j=0;j<M;j++) {
pt3[i][j]=0;
for(k=0;k<M;k++)
pt3[i][j]+=pt1[i][k]*pt2[k][j];
}
for(i=0;i<N;i++) {
for(j=0;j<M;j++)
printf("%d ",pt3[i][j]);
printf("\n");
}
for(i=0;i<N;i++) {
free(pt1[i]);
free(pt3[i]);
}
free(pt2);
free(pt3);
}

428

Programacin en C

14.3. Estructuras de datos dinmicas


Las estructuras de datos dinmicas crecen y se contraen a medida que se ejecuta el
programa, a diferencia de las estructuras de datos estticas (matrices, listas, vectores,
tablas y estructuras) en las que su tamao en memoria se establece durante la
compilacin y permanece inalterable. Dentro de esta clasificacin se encuentran
objetos tales como listas, colas, pilas y grafos. Dichos objetos, as como sus
operaciones, son tipos abstractos de datos.

14.3.1. Definicin de datos


Tipos de datos, tipos abstractos de datos y estructuras de datos son tres trminos
semejantes pero con significado diferente.
Los tipos de datos definen el conjunto de valores que puede tomar una variable,
son dependientes del lenguaje de programacin.
Los tipos abstractos de datos (TAD) son modelos matemticos, junto con las
operaciones definidas sobre dichos modelos. En ninguna parte de la definicin de un
TAD se hace mencin alguna a cmo se implementa el conjunto de operaciones, lo
que puede verse como una extensin del diseo modular, presentando propiedades
idnticas a los procedimientos:
Generalizacin de tipos bsicos o primitivos.
Encapsulado de cierto tipo de datos.
Adicionalmente, fuera de la seccin donde se trata un TAD, ste se
utiliza como un tipo bsico.
Un tipo de datos es abstracto cuando los programadores pueden trabajar con l,
conociendo slo sus operaciones, y utilizarlos exitosamente haciendo abstraccin de
su implementacin.
Por ejemplo, las listas, pilas y colas, pueden ser utilizadas sin conocer su
implementacin constituyendo un tipo abstracto de datos.
Otro ejemplo es el conjunto de nmeros enteros con las operaciones de unin,
interseccin y diferencia.
Generalmente, los algoritmos se implementan en funcin de los TAD, que se deben
representar en funcin de los tipos y operaciones bsicas del lenguaje de
programacin.
En programacin, la abstraccin es un mecanismo importante para poder disminuir
la complejidad de los problemas que se analizan. La idea bsica es que la
implementacin de esas operaciones se escribe una sola vez, y cualquier otra parte del
programa que necesite realizar una operacin sobre el TAD, puede hacerlo llamando a
la funcin apropiada. Si por alguna razn hay que cambiar el cdigo de la
implementacin, debe ser fcil modificando slo las rutinas que realizan las
operaciones del TAD, siendo transparente para el resto del programa.
Las operaciones a realizar por los TAD son aspectos dependientes del diseo del
programa y no se rigen por ninguna regla.
Cuando se implementa los TAD es necesario preocuparse de:

Gestin de la memoria dinmica

429

Elegir una representacin adecuada. Esta representacin consiste en un


conjunto de variable que almacenarn todos los datos que se requiere
memorizar para poder realizar las operaciones.
Implementar las operaciones que se podrn realizar con estas
estructuras.
Las estructuras de datos son conjuntos de variables, quiz de tipos distintos,
conectados entre s de diversas formas:
Celda, unidad bsica de una estructura de datos. Caja que puede
almacenar un valor de un tipo bsico o compuesto.
Las estructuras se crean:
o Dando nombres a agregados de celdas.
o Interpretando los valores de algunas celdas como representantes
de conexiones entre celdas (opcional).
Mecanismos de agregacin:
o Matriz, vector o arreglo (unidimensional).
o Estructura de registro.
o Archivo.
La estructura de datos de uso ms frecuente son las matrices. Otras estructuras muy
utilizadas en programacin, como las pilas y las colas, que se caracterizan porque
siguen un orden estricto para almacenar y recuperar sus elementos, se implementan
fcilmente por medio de matrices. Sin embargo, el resultado presenta serias
deficiencias, que se resuelven por medio de nuevas estructuras de datos: las listas
enlazadas.
Las listas enlazadas (ligadas o encadenadas) son una coleccin de elementos
(denominados nodos) dispuestos uno a continuacin de otro. Son estructuras muy
flexibles y con numerosas aplicaciones en el mundo de la programacin.
Se estudiarn en las prximas secciones las caractersticas e implementacin de las
listas enlazadas, as como algunos ejemplos de la utilizacin de pilas y colas con listas
enlazadas como un TAD a partir del tipo primario lista.

14.3.2. Fundamentos tericos


En captulos anteriores se han estudiado estructuras lineales de elementos
homogneos (listas, tablas, vectores) y se utilizaban matrices para implementar tales
estructuras. Esta tcnica obliga a fijar por adelantado el espacio a ocupar en memoria,
de modo que cuando se desea aadir un nuevo elemento, que rebase el tamao
prefijado por la matriz, no es posible realizar la operacin sin que se produzca un
error en tiempo de ejecucin. Ello se debe a que las matrices hacen un uso ineficiente
de la memoria.
EJEMPLO

El problema surge a la hora de hacer un programa al estilo de una agenda.


No se conoce a priori cuntos nombres se van a meter en la agenda, as que si
se usa una matriz para este programa se corre el riesgo de quedarse corto o
de pasarse. Si, por ejemplo, se crea una agenda con mil elementos (que
pueda contener mil nmeros) y se usan slo cien, se est desperdiciando una

430

Programacin en C

cantidad de memoria importante. Si por el contrario se opta por crear una


agenda con slo cien elementos para ahorrar memoria y realmente se
necesitan doscientos, no se podra cubrir el objetivo marcado. La mejor
solucin para este tipo de programas son las listas enlazadas. Con el uso de
punteros y la asignacin dinmica de variables, se pueden implementar listas,
de modo que la memoria fsica utilizada se corresponda con el nmero de
elementos de la tabla, lo que constituyen estructuras de datos dinmicas.
EJEMPLO

Un texto se puede representar con una lista de caracteres encadenados. As,


las operaciones de insercin y eliminacin son muy econmicas.
Si se representa el texto como una matriz, entonces las operaciones de
insercin y eliminacin son ms costosas porque obligan a reasignar muchos
elementos.
A

NULL

Principio
Fin

Las estructuras dinmicas estn basadas en la utilizacin de nodos de datos. Un


nodo, en su forma bsica, consta de dos partes:
Un rea de datos, donde est contenida la informacin y se puede
almacenar cualquier tipo de dato (enteros, reales, caracteres, cadenas de
caracteres, vectores). Es un valor de un tipo genrico (denominado
dato, TipoElemento, Info...).
Un puntero, que contiene al menos un puntero (direccin) que apunta a
otro nodo de informacin (denominado enlace o siguiente). La Figura
14.1 muestra la estructura de un nodo.
A

La flecha representa
estar apuntando a

Figura 14.1. Estructura de un nodo

Normalmente, un grupo de nodos se asocia con una estructura de datos: listas


enlazadas, pilas, colas y rboles binarios.

Gestin de la memoria dinmica

431

14.3.2.1. Lista enlazada


Consta de un nmero de elementos (nodos) dispuestos uno detrs de otro y cada
elemento tiene dos componentes (campos): un valor, que puede ser de cualquier tipo y
un puntero al siguiente elemento de la lista.
La representacin grfica ms extendida es aqulla que utiliza una caja
(rectngulLLo) con dos secciones en su interior. En la primera seccin se escribe el
primer elemento o valor del dato y en la segunda seccin, el enlace o puntero
mediante una flecha que sale de la caja y apunta al nodo siguiente. Como muestra la
Figura 14.2.
A

NULL

Principio
Fin
Figura 14.2. Lista enlazada representacin grfica tpica

Obsrvese que el campo de enlace del primer nodo apunta al segundo nodo, y el
campo de enlace del segundo apunta al tercero, y as sucesivamente. El campo de
enlace del quinto nodo contiene NULL. Esto indica que el quinto nodo constituye el
fin de la lista enlazada y no apunta a ningn otro. La Figura 14.3 muestra diferentes
representaciones grficas que se utilizan para dibujar el campo enlace del ltimo
nodo.
A

NULL

Figura 14.3. Diferentes representaciones grficas del nodo ltimo

14.3.2.2. Puntero nulo


La palabra NULL representa el puntero nulo, que es una constante especial de C. Se
puede utilizar el puntero nulo para cualquier valor de puntero que no apunte a ningn
sitio. Se utiliza normalmente en dos situaciones:
En el campo enlace o siguiente del nodo final de una lista enlazada.
Cuando una lista enlazada no tiene ningn nodo, se denomina lista vaca.
14.3.2.3. Punteros de cabecera y de cola
Cuando se construye y manipula una lista enlazada, se accede a ella a travs de uno o
ms punteros a nodos. El acceso ms frecuente es a travs del primer nodo de la lista
(puntero frente o cabeza), pero adems se puede utilizar una celda de
encabezamiento o nodo falso o ficticio, que no contiene ningn elemento y apunta a:
La celda que contiene el primer elemento vlido de la lista.
Puntero nulo, si la lista est vaca.
De esta manera se define una lista con nodo de cabecera a aqulla en la que el
primer nodo de la lista contendr en su campo dato, algn valor que lo diferencie de

432

Programacin en C

los dems nodos (*, -, +) hecho ste, que en muchos casos facilita el trabajo con
listas enlazadas. Un ejemplo de lista con nodo de cabecera se muestra en la Figura
14.4.
*

Pepe

Susy

NULL

Lul

Principio
Figura 14.4. Ejemplo de lista con nodo de cabecera

Para el caso de las listas sin nodo de cabecera, se usar la expresin primero para
referenciar al primer nodo de la lista, y primero->elemento y primero->siguiente para
hacer referencia al dato almacenado y al enlace al siguiente nodo respectivamente. En
el caso de las listas con nodo de cabecera el primero de la lista ser el que aparezca a
continuacin del mismo.
En ocasiones, se mantiene tambin un puntero al ltimo nodo de la lista (cola de la
lista), recibiendo el nombre de puntero cola.
Si la lista est vaca se asigna el puntero NULL a la cabeza y la cola, en caso
contrario para hacer cualquier recorrido de la lista, la posicin actual se referencia por
un puntero.
Pablo

Pepe

Susy

NULL

Lul

Ptr_cabeza
Ptr_cola
Figura 14.5. Punteros de cabeza y de cola

14.3.2.4. Clasificacin de las listas enlazadas


Las listas se pueden dividir en cuatro categoras:
Listas simplemente enlazadas: Cada nodo contienen un nico enlace que
conecta ese nodo al nodo siguiente o nodo sucesor. La lista es eficiente en
recorridos directos (adelante). Ejemplo de estas listas son las que se han
mostrado hasta el momento.
Listas doblemente enlazadas: Cada nodo contiene dos enlaces, uno a su
nodo predecesor y el otro a su nodo sucesor. La lista es eficiente tanto en
recorrido directo (adelante) como en recorrido inverso (atrs). En la Figura
14.6 se muestra un ejemplo de una lista doblemente ligada lineal que
almacena nmeros.
25

47

69

80

Figura 14.6. Lista doblemente enlazada

95

NULL

Gestin de la memoria dinmica

433

Lista circular simplemente enlazada: Una lista enlazada simplemente


en la que el ltimo elemento (cola) se enlaza al primer elemento (cabeza)
de tal modo que la lista puede ser recorrida de manera circular (en anillo).
25

47

69

80

95

Figura 14.7. Lista circular simplemente enlazada

Lista circular doblemente enlazada: Una lista doblemente enlazada


Finen
la que el ltimo elemento se enlaza al primer elemento y viceversa. Esta
lista se puede recorrer de modo circular (en anillo) tanto en direccin
directa como inversa.
25

47

69

80

95

Figura 14.8. Lista circular doblemente enlazada

Para cada uno de estos tipos de estructuras de lista, se puede elegir una
implementacin basada en matrices (asignacin esttica) o basada en punteros
(asignacin dinmica), que como ya se ha comentado difieren en el modo en que se
asigna la memoria para los datos de los elementos, cmo se enlazan juntos los
elementos y cmo se accede a dichos elementos.

14.4. Operaciones con listas simplemente enlazadas


Conceptualmente las listas enlazadas son similares a las definidas a travs de
matrices, pero a nivel de implementacin, el nivel de complejidad es mucho mayor.
Eso s, a cambio se gana en flexibilidad y se puede ofrecer versiones eficientes de
ciertas operaciones sobre listas de valores que implementadas con matrices dinmicas
seran muy costosas.
Las operaciones para gestionar los elementos contenidos en ellas, pueden
resumirse en:
Declaracin de los tipos nodo y puntero a nodo.
Inicializacin o creacin de la lista.
Comprobacin de si la lista est vaca.
Insercin de elementos en una lista.
Supresin de elementos en una lista.
Bsqueda de elementos de una lista.
Recorrido de una lista enlazada.
En esta seccin se mostrarn los algoritmos ms comunes para dichas operaciones
sobre listas enlazadas, los mismos pueden implementarse sin nodo de cabecera o con
nodo de cabecera.

434

Programacin en C

14.4.1. Declaracin de un nodo


Para definir un nodo de una lista enlazada se utilizan en la mayora de los lenguajes
de programacin un registro, que suelen tener las siguientes declaraciones bsicas:
tipo
_nodo = registro
elemento : tipo_elemento
siguiente : _nodo
fin
posicin = _nodo
LISTA = _nodo

En lenguaje C, se puede declarar un nuevo tipo de dato para un nodo mediante la


palabra reservada struct que contiene las dos partes citadas.
struct Nodo {
int dato;
struct Nodo* siguiente;
};

La declaracin anterior utiliza el tipo struct que permite agrupar campos de


diferentes tipos, el campo dato y el campo siguiente. Dado que los datos que se
pueden incluir en una lista suelen ser de cualquier tipo (entero, dobles, caracteres o
incluso cadenas), con el objetivo de que el tipo de dato de cada nodo, se pueda
cambiar con facilidad, se suele utilizar una sentencia typedef para declarar el
nombre de elemento como un sinnimo del tipo de datos de cada campo. El tipo
elemento se utiliza dentro de la estructura Nodo como se muestra a continuacin:
typedef double elemento;
struct Nodo {
elemento dato;
struct Nodo * siguiente;
};

Entonces si se necesita cambiar el tipo de datos en los nodos, slo tendr que
cambiar la sentencia de declaracin de tipos que afecta a elemento. Siempre que
una funcin necesite referirse al tipo del dato del nodo, puede utilizar el nombre
elemento.
Cada puntero a Nodo debe ser declarado como una variable puntero. Por ejemplo,
si se mantiene una lista enlazada con un puntero de cabecera y otro de cola, se deben
declarar dos variables puntero:
struct Nodo *ptr_cabeza;
struct Nodo *ptr_cola;

14.4.1.1. El operador - > de seleccin de miembro


Se considera un operador simple y se denomina operador de seleccin de miembro
o componente. De modo visual p->m recuerda a una flecha que apunta del puntero p
al objeto que contiene el miembro m.
Suponiendo que un programa ha de construir una lista enlazada y crear un puntero
de cabecera ptr_cabeza a un nodo Nodo, el operador * de indireccin aplicado a
una variable puntero representa el contenido del nodo apuntado por ptr_cabeza.
Es decir, *ptr_cabeza es un tipo de dato Nodo.

Gestin de la memoria dinmica

435

Al igual que con cualquier objeto, se puede acceder a los dos miembros de
*ptr_cabeza. La sentencia siguiente puede utilizarse para escribir los datos del
nodo cabecera.
printf (%lf, (*ptr_cabeza).dato);

Donde (*ptr_cabeza) es el miembro dato apuntado por ptr_cabeza.


Los parntesis son necesarios alrededor de la primera parte de la expresin
(*ptr_cabeza) ya que los operadores unitarios que aparecen a la derecha tienen
prioridad ms alta que los operadores unitarios que aparecen en el lado izquierdo (el
asterisco de indireccin). Sin los parntesis, el significado de ptr_cabeza
producira un error de sintaxis, al intentar evaluar ptr_cabeza.dato antes de la
indireccin.
p->m significa lo mismo que (* p).m
Utilizando el operador de seleccin -> se pueden imprimir los datos del primer
nodo de la lista:
printf (%lf, ptr_cabeza - > dato);

14.4.2. Construccin de una lista


Un algoritmo para la creacin de listas enlazadas debe seguir como mnimo los
siguientes pasos:
1. Declarar el tipo de dato y el puntero de cabeza o primero.
2. Asignar memoria para un elemento del tipo definido anteriormente
utilizando algunas de las funciones de asignacin de memoria (malloc(),
calloc(), realloc() en lenguaje C) y un casting para la conversin de
void* al tipo del puntero a nodo.
3. Crear iterativamente el primer elemento (cabeza) y los elementos sucesivos
de una lista simplemente enlazada.
Para ilustrar el proceso de creacin de una lista enlazada de forma lineal, se va a
crear una lista de elementos que almacena datos de tipo char, como muestra la
Figura 14.4.
A

NULL

Principio
Fin
Figura 14.4. Lista de caracteres

Para definir el nodo de la lista enlazada se utiliza la siguiente estructura:


struct lista {
char dato;
struct lista *siguiente;
} x, y, z, *p;

Donde x, y, z son variables de tipo struct lista y p es un puntero a datos de


tipo struct lista.

436

Programacin en C

Se comienza por el caso ms sencillo, suponiendo que los nodos ya estn creados y
que slo sera necesario hacer el encadenamiento de datos.
Se inicializan los nodos a un valor nulo (ver Figura 14.5) lo que implica que la lista
est vaca (no tiene elementos).
x.siguiente = y.siguiente = z.siguiente = NULL;
x
A

y
NULL

z
NULL

NULL

Figura 14.5. Nodos inicializados a nulo

En la estructura hay un campo para almacenar datos y otro para hacer conexiones.
La asignacin de valores se realiza con las siguientes instrucciones:
x.dato = 'A';
y.dato = 'B';
z.dato = 'C';

Si se desea encadenar cada elemento, habra que hacer:


x.siguiente = &y;
y.siguiente = &z;

El campo de enlace permite acceder al siguiente elemento hasta llegar a una marca
de final convenida.
x.siguiente -> dato es 'B'
x.siguiente -> siguiente -> dato es 'C'

Como sera muy poco til usar variables diferentes para manejar estructuras
dinmicas, se va a utilizar otra variante del algoritmo, considerando que:
El final de la lista se marca asignando el valor nulo al campo puntero del
ltimo elemento.
Se manejan dos punteros auxiliares para poder acceder al primer
elemento y al ltimo.
Se usa una variable genrica para representar los elementos de la lista.
Se considera ahora que un elemento de la lista se puede definir con la ayuda de la
estructura siguiente, que a diferencia del campo datos de la estructura anterior, que
reservaba espacio para un carcter, es ms general:
# define NULL 0
typedef char TIPO_DATOS;
struct lista {
TIPO_DATOS dato;
struct lista *siguiente;
};
typedef struct lista ELEMENTO;
typedef ELEMENTO *ENLACE;
ENLACE nodo, principio, fin;

El puntero principio y fin se inicializan a un valor nulo lo que implica que la lista
est vaca (no tiene elementos).
Se crean los nodos de forma dinmica (cada vez que se necesiten), asignndoles en
tiempo de ejecucin posiciones de memoria no utilizadas, para ello hay que reservar
tanta memoria como tamao tiene cada nodo, y asignar la direccin de la memoria
reservada al puntero nodo:
nodo = (ELEMENTO *) malloc( sizeof (ELEMENTO) ); (1)

Gestin de la memoria dinmica

437

Con el operador sizeof se obtiene el tamao de cada nodo de la lista, la funcin


malloc() devuelve el puntero genrico (void*), por lo que se convierte a
(ELEMENTO *) ahora se puede asignar un valor al campo dato:
nodo -> dato = 'A'; (2)
nodo -> siguiente = NULL; (3)
principio = fin = nodo; (4)
Todo lo anterior puede resumirse grficamente en la

Figura 14.6.
(1)

(2)

(3)

NULL

(4)
NULL

A
Principio

Fin
Figura 14.6. Definicin del primer elemento de la lista

Si ahora se desea aadir un nuevo elemento B a la lista, se llevan a cabo los


siguientes pasos, que se irn repitiendo para cada elemento que se vaya aadiendo.
nodo = (ELEMENTO *) malloc ( sizeof (ELEMENTO) );
nodo -> dato = 'B';
nodo -> siguiente = NULL; (5)
fin -> siguiente = nodo; (6)
fin = fin -> siguiente; (7)

Lo que se puede resumir grficamente en la Figura 14.7.


(5)
A

NULL

NULL

NULL

NULL

Principio
Fin

(6)

A
Principio
Fin
A

(7)

Principio
Figura 14.7. Aadir un nuevo nodo

Fin

438

Programacin en C

Para continuar estudiando el resto de las operaciones con listas enlazadas en primer
lugar se gestionar una lista directamente, desde un programa principal, centrando la
atencin en la realizacin de una serie de acciones que parten de un estado de la lista
y la dejan en otro estado diferente. Se ilustrar cada una de las acciones mostrando
con todo detalle qu ocurre paso a paso. Seguidamente se encapsular cada accin
elemental (aadir elemento, borrar elemento, etc.) en una funcin independiente.
Se va a crear una lista de enteros. Se comienza por definir el tipo de registro que
compone la lista:
struct Nodo {
int info;
struct Nodo * sig;
};

Un registro de tipo struct Nodo consta de dos elementos:


Un entero llamado info, que es la informacin que realmente importa.
Un puntero a un elemento struct Nodo.

14.4.3. Insercin de un elemento en una lista


El algoritmo empleado para aadir o insertar un elemento en una lista enlazada vara
dependiendo de la posicin en que se desea insertar el elemento. La posicin de
insercin puede ser:
1. En la cabeza (elemento primero de la lista).
2. En el final de la lista (elemento ltimo).
3. Antes de un elemento especificado.
4. Despus de un elemento especificado.
14.4.3.1. Insercin de un nuevo nodo en la cabeza de la lista
Aunque normalmente se insertan nuevos datos al final de una estructura de datos, es
ms fcil y ms eficiente insertar un elemento nuevo en la cabeza de una lista. El
proceso de insercin se puede resumir con el siguiente algoritmo:
1. Asignar un nuevo nodo a una variable puntero local que apunta al nuevo
nodo que se va a insertar en la lista.
2. Situar el nuevo elemento en el campo dato (info) del nuevo nodo.
3. Hacer que el campo enlace siguiente del nuevo nodo apunte a la cabeza
(primer nodo) de la lista original.
4. Hacer que cabeza (puntero cabeza) apunte al nuevo nodo que se ha
creado.
EJEMPLO

Se comienza creando el puntero cabeza, aqul en el que empieza la lista de


enteros:
int main(void ) {
struct Nodo * lista;
...

Gestin de la memoria dinmica

439

No es ms que un puntero a un elemento de tipo struct Nodo.


Inicialmente, la lista est vaca. Se ha de indicar explcitamente, as:
int main(void ) {
struct Nodo * lista = NULL;
...

Se tiene ahora esta situacin:


lista

O sea, lista no contiene nada, est vaca.


Se comienza aadiendo un nodo a la lista. El objetivo es pasar de la lista
anterior a esta otra:
8
lista

Para ello se crea el nuevo registro con malloc():

int main(void ) {
struct Nodo * lista = NULL;
if (lista != NULL)
lista = (Nodo *) malloc(sizeof (struct Nodo ));
...

Siendo el resultado:
lista

Ya se tiene el primer nodo de la lista, pero sus campos an no tienen los


valores que deben tener finalmente. Se ha representado grficamente dejando
el campo info en blanco y sin poner una flecha que salga del campo sig.
Por una parte, el campo info deber contener el valor 8 y, por otra, el
campo sig deber apuntar a NULL:
int main(void ) {
struct Nodo * lista = NULL;
lista = (Nodo *)malloc( sizeof (struct Nodo ));
if (lista != NULL)
{
lista->info = 8;
lista->sig = NULL;
...

He aqu el resultado:
8
lista

Ya se cuenta con una lista con un nico elemento. Se va a aadir un nuevo


nodo a la lista, uno que contenga el valor 3 y que se ubicar justo al principio
de la lista, delante del nodo que contiene el valor 8. O sea, se parte de la lista
anterior y se desea llegar a esta otra:
3
lista

440

Programacin en C

En primer lugar, se ha de crear un nuevo nodo al que deber apuntar lista.


El campo sig del nuevo nodo, por su parte, deber apuntar al nodo que
contiene el valor 8. Se comienza por la peticin de un nuevo nodo que, ya
que debe ser apuntado por lista, se puede pedir y rellenar as:
int main(void ) {
struct Nodo * lista = NULL;
...
lista = (Nodo *) malloc(sizeof (struct Nodo ));
if (lista != NULL)
{
lista->info = 3;
lista->sig = ???;
/* No se sabe cmo expresar esta asignacin */

...

Algo ha ido mal! Cmo se podra asignar a lista->sig la direccin del


siguiente nodo con valor 8? La situacin en la que se est se puede
representar as:
3
8
lista

Se ha perdido la referencia al nodo que contiene el valor 8 al usar la variable


lista para apuntar al nuevo nodo, as que no hay forma de acceder a l. Es
lo que se denomina una prdida de referencia, un grave error en el programa
que imposibilita seguir construyendo la lista. Si no se puede acceder a un
bloque de memoria que se ha pedido con malloc(), ni siquiera se podra
liberar posteriormente con free(). Cuando se produce una prdida de
referencia aparece una fuga de memoria: se pide memoria al ordenador que
no podr ser liberada cuando deje de ser necesaria. Se ha de estar siempre
atento para evitar prdidas de referencia. Es uno de los mayores peligros del
trabajo con memoria dinmica.
Cmo puede evitarse la prdida de referencia? Muy fcil, con un puntero
auxiliar.
1 int main(void )
2 {
3
struct Nodo * lista = NULL, * aux;
4
5
...
6
aux = lista;
7
lista = (Nodo *)malloc(sizeof (struct Nodo ));
8
if (lista != NULL)
9
{
10
lista->info = 3;
11
lista->sig = aux;
12 ...

Considrese el estado de la memoria justo despus de ejecutarse la lnea 6,


(aux = lista):

Gestin de la memoria dinmica

441

aux
8

lista

El efecto de la lnea 6 es que tanto aux como lista apuntan al mismo


registro. La asignacin de un puntero a otro hace que ambos apunten al
mismo elemento. Un puntero no es ms que una direccin de memoria, as
copiar un puntero en otro hace que ambos contengan la misma direccin de
memoria, es decir, que ambos apunten al mismo lugar.
El siguiente paso de la traza mostrara cmo quedara la memoria justo
despus de ejecutar la lnea 7, (lista=(Nodo*)malloc(sizeof(struct Nodo))):
aux
8
lista

La lnea 10, (lista->info = 3), asigna al campo info del nuevo nodo
(apuntado por lista) el valor 3:
aux
3

lista

La lista an no est completa, pero puede observarse que no se ha perdido la


referencia al ltimo fragmento de la lista. El puntero aux la mantiene. Queda
por ejecutar la lnea 11 (lista->sig = aux), que enlaza el campo sig
del primer nodo con el segundo nodo, el apuntado por aux. Tras ejecutarla
se tiene:
aux
3

lista

Y qu hace aux apuntando an a la lista? La verdad, da igual. Lo


importante es que los nodos que hay enlazados desde lista formen la lista
que se quiere construir.
No importa cmo quedan los punteros auxiliares, una vez que han jugado su
papel en la construccin de la lista, son superfluos. Se puede aadir una lnea
con aux = NULL al final del programa para que aux no quede apuntando a
un nodo de la lista, pero es innecesario.
Se va a continuacin a desarrollar un mdulo que permita manejar listas de enteros
y gestionar sus nodos utilizando funciones. En el fichero de cabecera lista.h se
declararan los tipos de datos bsicos:
struct Nodo {
int info;

442

Programacin en C

struct Nodo * sig;


};

Como ya se seal, este tipo de nodo slo alberga un nmero entero. Si se


necesitara una lista de elementos de tipo double se debera cambiar el tipo del valor
del campo info. Y si se quisiese una lista de personas, se podran aadir varios
campos a la estructura struct Nodo (uno para el nombre, otro para la edad, etc.) o
declarar info como de un tipo struct Persona definido ste previamente.
Una lista es un puntero a un struct Nodo, pero cuesta poco definir un nuevo tipo
para hacer referencia con mayor brevedad al tipo lista:
typedef struct Nodo * TipoLista;

Ahora, se puede declarar una lista como struct Nodo* o como TipoLista,
indistintamente. Por comodidad, se hace referencia al tipo de una lista con
TipoLista y al de un puntero a un nodo cualquiera con struct Nodo*, ambos
tipos son equivalentes.
Creacin de lista vaca
La primera funcin crear una lista vaca. El prototipo de la funcin, que se declara en
el fichero cabecera lista.h, es ste:
TipoLista lista_vacia(void );

La implementacin, que se proporciona en una unidad de compilacin lista.c,


resulta trivial:
#include <stdlib.h>
#include "lista.h "
TipoLista lista_vacia(void ) {
return NULL;
}

La forma de uso es muy sencilla:


#include <stdlib.h>
#include "lista.h "
int main(void ) {
TipoLista lista;
lista = lista_vacia();
return 0;
}
Ciertamente se podra haber hecho lista = NULL, sin ms, pero queda ms

elegante proporcionar funciones para cada una de las operaciones bsicas que ofrece
una lista, y crear una lista vaca es una operacin bsica.
Lista vaca?
Vendra bien disponer de una funcin que devuelva cierto o falso en funcin de si la
lista est vaca o no. El prototipo de la funcin es:
int es_lista_vacia(TipoLista lista);

Su implementacin es muy sencilla, incluida en lista.c:


int es_lista_vacia(TipoLista lista) {
return lista == NULL;
}

Gestin de la memoria dinmica

443

Insercin por cabeza


Ahora se va a crear una funcin que inserta un elemento en una lista por la cabeza, es
decir, haciendo que el nuevo nodo sea el primero de la lista. Antes, se ver cul es el
prototipo de la funcin:
TipoLista inserta_por_cabeza(TipoLista lista, int valor);

La forma de uso de la funcin en un programa llamado miprograma.c es:


#include "lista.h "
int main(void ) {
TipoLista lista;
lista = lista_vacia();
lista = inserta_por_cabeza(lista, 2);
lista = inserta_por_cabeza(lista, 8);
lista = inserta_por_cabeza(lista, 3);
...
return 0;
}

La implementacin de la funcin debe empezar pidiendo un nuevo nodo para el


nmero que se desee insertar, como en las anteriores funciones, su implementacin se
encuentra en lista.c.
TipoLista inserta_por_cabeza(TipoLista lista, int valor) {
struct Nodo* nuevo = (Nodo *)malloc(sizeof (struct Nodo ));
if (nuevo != NULL) {
nuevo->info = valor;
nuevo->sig = lista;
lista = nuevo;
return lista;
}
}

Impresin en pantalla
No resulta en absoluto difcil disear una funcin que muestre el contenido de una
lista en pantalla. El prototipo es ste:
void muestra_lista(TipoLista lista);

Una posible implementacin podra ser sta:


void muestra_lista(TipoLista lista) {
struct Nodo * aux;
for (aux = lista; aux != NULL; aux = aux->sig)
printf("%d\n ", aux->info);
}
EJEMPLO

Este programa muestra cmo crear una lista enlazada de enteros.


#include <stdio.h>
#include <stdlib.h>
typedef struct nodo {
int dato;
struct nodo *enlace;
} LISTA;
void mostrar_lista(LISTA *ptr);
void main() {
LISTA *primero = NULL;

444

Programacin en C

int elemento;
LISTA *q,*p;
do {
printf("\n Introduzca elemento: ");
scanf (" %d,&elemento);
if(elemento != 0)
p=(LISTA *) malloc(sizeof(LISTA));
if (primero == NULL)
primero=p;
else
q->enlace=p;
p->dato=elemento;
p->enlace=NULL;
q=p;
} while(elemento != 0);
printf("\nLa nueva lista enlazada es: ");
mostrar_lista(primero);
}
void mostrar_lista(LISTA *ptr) {
while(ptr != NULL) {
printf("%d",ptr->dato);
ptr = ptr->enlace;
}
printf("\n");
}

Ejemplo de ejecucin:
Introduzca elemento: 1
Introduzca elemento: 3
Introduzca elemento: 5
Introduzca elemento: 7
Introduzca elemento: 9
Introduzca elemento: 0
La nueva lista enlazada es: 13579
Presione otra tecla para continuar

14.4.3.2. Insercin de un nuevo nodo que no est en la cabeza de la lista


La insercin de un nuevo nodo no siempre se realiza al principio (cabeza) de la lista,
se puede insertar en el centro o al final de la lista.
El algoritmo de la nueva operacin insertar requiere las siguientes etapas:
1. Asignar el nuevo nodo apuntado por el puntero nuevo.
2. Situar el nuevo elemento en el campo dato (Info) del nuevo nodo.
3. Hacer que el campo enlace siguiente del nuevo nodo apunte al nodo que
va despus de la posicin donde se quiere insertar (o bien a NULL si no
hay ningn nodo despus de la nueva posicin).

Gestin de la memoria dinmica

445

4.

En la variable puntero anterior tener la direccin del nodo que est antes
de la posicin deseada para el nuevo nodo. Hacer que anterior->siguiente
apunte al nuevo nodo que se acaba de crear.
Si se desea insertar en una posicin exacta p lo que implica insertar antes.
Si la lista tiene nodo ficticio se puede insertar al comienzo de la lista sin
necesidad de alterar la variable posicin que la mantiene.
Si no tiene nodo ficticio:
Es preciso pasar por referencia la direccin de comienzo de la
lista.
Se debe comprobar si la posicin p coincide con el comienzo
de la lista para cambiar la direccin de comienzo.
En cualquier de los dos casos, al menos en principio, es necesario buscar el nodo
anterior al referenciado para poder insertar antes.
14.4.3.3. Insercin por la cola
Se tiene un nuevo objetivo. Se va intentar aadir un nuevo nodo al final de la lista. Es
decir, partiendo de la ltima lista:
3

lista

Se va a intentar obtener esta otra:


lista

La insercin al final de la lista resulta menos eficiente, si no se tiene un puntero al


ltimo elemento de la lista (puntero cola). Entonces se ha de seguir la traza desde la
cabeza de la lista hasta el ltimo nodo de la lista, para a continuacin realizar la
insercin.
Para empezar, debe pedirse un nuevo nodo, slo que esta vez no estar apuntado
por lista, sino por un puntero auxiliar. Despus, se acceder de algn modo al
campo sig del ltimo nodo de la lista (el que tiene valor 8) y se har que apunte al
nuevo nodo. Finalmente, se har que el nuevo nodo contenga el valor 2 y que tenga
como siguiente nodo a NULL.
1 int main(void )
2 {
3
struct Nodo * lista = NULL, * aux;
4
5
...
6
aux = (Nodo *)malloc( sizeof (struct Nodo ) );
7
if (aux != NULL)
8
{
9
lista->sig->sig = aux;
10
aux->info = 2;
11
aux->sig = NULL;
12
13 return 0;
14 }

446

Programacin en C

Se ver cmo queda la memoria paso a paso. Tras ejecutar la lnea 6 se tiene la
siguiente configuracin:
aux
3

lista

O sea, la lista que cuelga de lista sigue igual, pero ahora aux apunta a un nuevo
nodo. Si se pasa a estudiar la lnea 9 (lista->sig->sig = aux), si lista es un
puntero, y lista->sig es el campo sig del primer nodo, que es otro puntero,
entonces lista->sig->sig es el campo sig del segundo nodo, que es otro puntero.
Si a ese puntero se le asigna aux, el campo sig del segundo nodo apunta a donde
apunta aux.
aux
3

lista

Una vez ejecutadas las lneas 10 y 11, el trabajo estar completo:


2

aux

lista

Se ha obtenido la lista deseada


aux
3

lista

El proceso anterior puede resumirse de forma ms general:


1. Buscar el ltimo elemento con un bucle y mantenerlo referenciado con un
puntero auxiliar, dgase aux.
aux
3

lista
2.

Pedir un nodo nuevo y mantenerlo apuntado con otro puntero auxiliar, dgase
nuevo.
aux
3

lista

nuevo

3.

Escribir en el nodo apuntado por nuevo el nuevo dato y hacer que su campo
sig apunte a NULL.
aux
lista
nuevo

3
2

Gestin de la memoria dinmica

4.

447

Hacer que el nodo apuntado por aux tenga como siguiente nodo al nodo
apuntado por nuevo.
aux
3

lista

nuevo

Lo que es equivalente a este otro grfico en el que, sencillamente, se ha


reorganizado la disposicin de los diferentes elementos:
aux
lista

nuevo

Se va a modificar el ltimo programa para expresar esta idea:


int main(void ) {
struct Nodo * lista = NULL, * aux, * nuevo;
...
aux = lista;
while (aux->sig != NULL)
aux = aux->sig;
nuevo = (Nodo *) malloc(sizeof (struct Nodo));
if (nuevo != NULL) {
nuevo->info = 2;
nuevo->sig = NULL;
aux->sig = nuevo;
}
return 0;
}

Se va a disear ahora una funcin que inserte un nodo al final de una lista. Su
prototipo sera:
TipoLista inserta_por_cola(TipoLista lista, int valor);

Dicha funcin se dividir en dos etapas: una primera que localice al ltimo
elemento de la lista, y otra que crea el nuevo nodo y lo une a la lista.
La primera etapa sera:
TipoLista inserta_por_cola(TipoLista lista, int valor) {
struct Nodo * aux;
for (aux = lista; aux->sig != NULL; aux = aux->sig);
...
}

Se puede analizar paso a paso el bucle con un ejemplo. Imaginase que la lista que
contiene lista ya tiene tres nodos:

448

Programacin en C

lista

La primera iteracin del bucle hace que aux apunte al primer elemento de la lista:
aux
lista

Habr una nueva iteracin si aux->sig es distinto de NULL, es decir, si el nodo


apuntado por aux no es el ltimo de la lista, se itera haciendo aux = aux->sig, o
sea, se pasa a esta nueva situacin:
aux
lista

Sigue siendo cierto que aux->sig es distinto de NULL? S, entonces se avanza


aux un nodo ms a la derecha:
aux
lista

Ya se ha llegado al ltimo nodo de la lista. Se puede proceder con la segunda fase


de la insercin, esto es, pedir un nuevo nodo y enlazarlo desde el actual ltimo nodo.
Vendr bien un nuevo puntero auxiliar:
TipoLista inserta_por_cola(TipoLista lista, int valor) {
struct Nodo * aux, * nuevo;
nuevo = (Nodo *)malloc(sizeof (struct Nodo));
if (nuevo != NULL) {
nuevo->info = valor;
nuevo->sig = NULL;
}
if (lista == NULL)
lista = nuevo;
else {
for (aux = lista; aux->sig != NULL; aux = aux->sig);
aux->sig = nuevo;
}
return lista;
}

El efecto de la ejecucin de las nuevas lneas es el siguiente, considerando que se


aade el valor 10:
nuevo

10

aux
lista

Gestin de la memoria dinmica

449

Est claro que ha funcionado correctamente. Tal vez resulte de ayuda ver la misma
estructura reordenada as:
nuevo
aux
lista

10

EJEMPLO

Este programa muestra cmo crear una lista enlazada. El usuario


simplemente introduce caracteres uno a uno y stos se sitan en nuevos
nodos asociados a tal efecto. Obsrvese que el nodo se aade al final de la
lista. Cuando la lista se encuentra inicialmente vaca, este proceso requiere
una tercera etapa diferente, donde la variable LISTA se le asigna la direccin
del nuevo nodo.
#include <stdio.h>
#include <stdlib.h>
typedef struct nodo {
char dato;
struct nodo *enlace;
} LISTA;
void mostrar_lista(LISTA *ptr);
void insertar(LISTA **ptr, char elemento);
void main(){
LISTA *n1 = NULL;
char elemento;
do {
printf("\nIntroduzca elemento: ");
elemento = getchar();
if(elemento != '\r')
insertar(&n1, elemento);
} while(elemento != '\r');
printf("\nLa nueva lista enlazada es: ");
mostrar_lista(n1);
}
void mostrar_lista(LISTA *ptr) {
while(ptr != NULL) {
printf("%c",ptr->dato);
ptr = ptr->enlace;
}
printf("\n");
}
void insertar(LISTA **ptr, char elemento) {
LISTA *p1, *p2;

450

Programacin en C

p1 = *ptr;
if(p1 == NULL) {
p1 = (LISTA *) malloc(sizeof(LISTA));
if (p1 != NULL) {
p1->dato = elemento;
p1->enlace = NULL;
*ptr = p1;
}
}
else {
while(p1->enlace != NULL)
p1 = p1->enlace;
p2 = (LISTA *) malloc(sizeof(LISTA));
if (p2 != NULL) {
p2->dato = elemento;
p2->enlace = NULL;
p1->enlace = p2;
}
}
}

Ejemplo de ejecucin:
Introduzca
Introduzca
Introduzca
Introduzca

elemento: c
elemento: p
elemento: u
elemento:'\r'

La nueva lista enlazada es: cpu

EJEMPLO

El siguiente programa muestra cmo insertar la letra s al comienzo de una


lista enlazada compuesta por tres elementos cat, para dar como resultado
la lista enlazada scat, y cmo insertar al final de la lista la letra m
para dar lugar a la lista scatm.
#include <stdio.h>
#include <stdlib.h>

typedef struct nodo {


int dato;
struct nodo *enlace;
} LISTA;
void
void
void
void

insertar(LISTA **ptr, char elemento);


insertar_al_principio(LISTA **ptr, char item);
insertar_al_final(LISTA *ptr, char item);
mostrar_lista(LISTA *ptr);

void main() {
LISTA *primero = NULL;
char elemento;
do {
printf("\nIntroduzca elemento: ");
elemento = getchar();
if(elemento != '\t')
insertar(&primero, elemento);

Gestin de la memoria dinmica

} while(elemento != '\t');
printf("\nLa nueva lista enlazada es: ");
mostrar_lista(primero);
insertar_al_principio(&primero,'s');
printf("La nueva lista enlazada es: \n");
mostrar_lista(primero);
insertar_al_final(primero,'m');
printf("La nueva lista enlazada es: ");
mostrar_lista(primero);
}
void mostrar_lista(LISTA *ptr) {
while(ptr != NULL) {
printf("%c",ptr->dato);
ptr = ptr->enlace;
}
printf("\n");
}
void insertar_al_principio(LISTA **ptr, char item) {
LISTA *nuevo;
nuevo = (LISTA *)malloc(sizeof(LISTA));
if(nuevo != NULL) {
nuevo->dato = item;
nuevo->enlace = *ptr;
*ptr = nuevo;
}
}
void insertar_al_final(LISTA *ptr, char item) {
LISTA *nuevo;
while(ptr->enlace != NULL)
ptr = ptr->enlace;
nuevo = (LISTA *)malloc(sizeof(LISTA));
if(nuevo != NULL) {
ptr->enlace = nuevo;
nuevo->dato = item;
nuevo->enlace = NULL;
}
}
void insertar(LISTA **ptr, char elemento) {
LISTA *p1, *p2;
p1 = *ptr;
if(p1 == NULL) {
p1 = (LISTA *)malloc(sizeof(LISTA));
if (p1 != NULL) {
p1->dato = elemento;
p1->enlace = NULL;
*ptr = p1;
}
}
else {
while(p1->enlace != NULL)
p1 = p1->enlace;

451

452

Programacin en C

p2 = (LISTA *)malloc(sizeof(LISTA));
if(p2 != NULL) {
p2->dato = elemento;
p2->enlace = NULL;
p1->enlace = p2;
}
}
}

Ejemplo de ejecucin:
Introduzca elemento: c
Introduzca elemento: a
Introduzca elemento: t
Introduzca elemento:'\r'
La nueva lista enlazada es: cat
La nueva lista enlazada es: scat
La nueva lista enlazada es: scatm

14.4.3.4. Insercin en una posicin dada


Se va a disear una funcin que permita insertar un nodo en una posicin dada de la
lista. Se asume la siguiente numeracin de posiciones en una lista:
lista

3
0

8
1

O sea, insertar en la posicin 0 es insertar una nueva cabeza; en la posicin 1, un


nuevo segundo nodo, etc. Qu pasa si se quiere insertar un nodo en una posicin
mayor que la longitud de la lista? Pues que se insertar en la ltima posicin.
El prototipo de la funcin sera:
TipoLista inserta_en_posicion(TipoLista lista, int pos, int valor);

Y su implementacin:

TipoLista inserta_en_posicion(TipoLista lista, int pos, int valor) {


struct Nodo * aux, * atras, * nuevo;
int i;
nuevo = (struct Nodo*) malloc(sizeof (struct Nodo ));
if (nuevo != NULL) {
nuevo->info = valor;
for (i = 0, aux = lista, atras = NULL;
i < pos && aux != NULL;
i++, atras = aux, aux = aux->sig);
nuevo->sig = aux;
if (atras == NULL)
lista = nuevo;
else
atras->sig = nuevo;
}
return lista;

Gestin de la memoria dinmica

453

14.4.3.5. Insercin ordenada


Las listas que se han manejado hasta el momento estn desordenadas, es decir, sus
nodos estn dispuestos en un orden arbitrario. Es posible mantener listas ordenadas si
las inserciones se realizan utilizando siempre una funcin que respete el orden.
La funcin que se va a desarrollar, por ejemplo, inserta un elemento en una lista
ordenada de menor a mayor, de modo que la lista resultante sea tambin una lista
ordenada de menor a mayor.
TipoLista inserta_en_orden(TipoLista lista, int valor) {
struct Nodo * aux, * atras, * nuevo;
nuevo = (Nodo * )malloc(sizeof (struct Nodo ));
if (nuevo != NULL) {
nuevo->info = valor;
for (atras = NULL, aux = lista; aux != NULL; atras = aux, aux = aux->sig)
if (valor <= aux->info) {
/* Aqu se inserta el nodo entre atras y aux */
nuevo->sig = aux;
if (atras == NULL)
lista = nuevo;
else
atras->sig = nuevo;
/* Y como ya est insertado, se acaba */
return lista;
}
/* Si se llega aqu, es que nuevo va al final de la lista */
nuevo->sig = NULL;
if (atras == NULL)
lista = nuevo;
else
atras->sig = nuevo;
return lista;
}
return lista;
}

14.4.4. Supresin de un nodo en la lista


Esta operacin supone enlazar el nodo anterior con el nodo siguiente al que se desea
eliminar y liberar la memoria que ocupa. El algoritmo para eliminar un nodo que
contiene un dato requiere en general las siguientes etapas:
Bsqueda del nodo que contiene el dato. Se ha de tener la direccin del
nodo a eliminar.
El puntero siguiente del nodo anterior ha de apuntar al siguiente del
nodo a eliminar.
En caso de que el nodo a eliminar sea el primero, cabeza, se modifica
cabeza para que se tenga la direccin del nodo siguiente.
Por ltimo se libera la memoria ocupada por el nodo.
Se plantean escenarios anlogos a los que aparecen en la insercin de nodos en una
posicin p:

454

Programacin en C

Si p hace referencia al nodo anterior al que contiene el elemento a


suprimir:
o Con nodo ficticio, funciona de la misma manera que ocurre con
la insercin.
o Sin nodo ficticio, no se puede suprimir el primer elemento de la
lista sin alterar la direccin de comienzo de la lista.
Si p hace referencia al nodo al que realmente se desea eliminar.
o Con nodo ficticio, se puede eliminar cualquier valor de la lista.
o Sin nodo ficticio, se debe pasar por referencia la variable
encabezamiento o raz y prever la eliminacin de dicho nodo
(primer elemento de la lista).
o En ambos casos aparece el mismo problema de consumo de
tiempo si se realiza un recorrido de la lista para buscar el nodo
anterior.
EJEMPLO

Se muestra cmo eliminar un elemento del comienzo de la lista.


Lista original
lista

d'

d'

Lista nueva
lista

Vuelve al
almacenamiento libre

La direccin a la que apunta la variable lista pasa a apuntar a la direccin


indicada por el campo de enlace del primer nodo de la lista. Esto hace que la
variable lista apunte en este momento al segundo nodo. A continuacin se
libera la memoria reservada para el primer nodo, para que pueda ser utilizada
en un futuro. Si no se libera la memoria, sta seguir estando asignada, pero
debido a que la variable lista ha sido cambiada no habr forma de acceder
al primer nodo.
Esto hace que el primer nodo se convierta en basura. Cuando se descartan
muchos nodos de esta forma, sin liberar la memoria utilizada por ellos, es
posible que en un momento determinado no haya memoria disponible para
nuevos nodos, debiendo utilizarse una funcin de recoleccin de basura
(garbage collector)1 para recuperar todos los nodos perdidos.
1

El entorno de ejecucin de Java tiene un recolector de basura que peridicamente libera la


memoria ocupada por los objetos que no se van a necesitar ms. El recolector de basura de Java
es un barredor de marcas que escanea dinmicamente la memoria de Java buscando objetos,
marcando aqullos que han sido referenciados. Despus de investigar todos los posibles paths
de los objetos, los que no estn marcados (esto es, no han sido referenciados) se les conoce
como basura y son eliminados. El colector de basura funciona en un thread (hilo) de baja
prioridad.

Gestin de la memoria dinmica

455

Se va a aprender ahora a borrar elementos de una lista. Se comienza por ver cmo
eliminar el primer elemento de una lista. El objetivo es, partir de esta lista:
lista

Para llegar a esta otra:


2

lista

Como lo que se desea es que lista pase a apuntar al segundo elemento de la


lista, se podra disear una aproximacin directa modificando el valor de lista:
int main(void ) {
struct Nodo * lista = NULL, * aux, * nuevo;
...
lista = lista->sig;
return 0;
}

El efecto obtenido por esa accin es ste:


lista

Efectivamente, se ha conseguido que la lista apuntada por lista sea lo que se


pretenda, pero se ha perdido la referencia a un nodo (el que hasta ahora era el
primero) y ya no se puede liberar. Se ha provocado una fuga de memoria. Para liberar
un bloque de memoria se ha de llamar a free() con el puntero que apunta a la
direccin en la que empieza el bloque. Como el bloque est apuntado por lista,
podra pensarse que la solucin es trivial y que bastara con llamar a free() antes de
modificar lista:
1 int main(void )
2 {
3
struct Nodo * lista = NULL, * aux, * nuevo;
4
5
...
6
free(lista);
7
lista = lista->sig;
8
9
return 0;
10 }
Pero, claro, no iba a resultar tan sencillo. La lnea 7, ( lista=lista->sig), no

puede ejecutarse! Tan pronto como se ha ejecutado la lnea 6, se tiene otra fuga de
memoria:
lista

O sea, se ha liberado correctamente el primer nodo, pero ahora se ha perdido la


referencia al resto de nodos y el valor de lista->sig est indefinido. Cmo se
puede arreglar esto? Si no se libera memoria, hay una fuga, y si se libera se pierde la
referencia al resto de la lista. La solucin es sencilla, debe guardarse una referencia al
resto de la lista con un puntero auxiliar cuando an se est a tiempo.

456

Programacin en C

1 int main(void )
2 {
3 struct Nodo * lista = NULL, * aux, * nuevo;
4
5 ...
6 aux = lista->sig;
7 free(lista);
8 lista = aux;
9
10 return 0;
11 }

Se va a ver paso a paso qu hacen las ltimas tres lneas del programa.
La asignacin aux = lista->sig introduce una referencia al segundo nodo:
aux
lista

Al ejecutar free(lista), se pasa a esta otra situacin:


aux
lista

No hay problema. Se conoce dnde est el resto de la lista, cuelga de aux. As se


llega al resultado deseado con la asignacin lista = aux:
aux
lista

A continuacin se vern algunas funciones que permiten borra elementos de la


lista.
14.4.4.1. Borrado de la cabeza
La funcin que se va a disear recibe una lista y devuelve esa misma lista sin el nodo
que ocupaba inicialmente la posicin de cabeza. El prototipo sera el siguiente:
TipoLista borra_cabeza(TipoLista lista);

Y su implementacin:
TipoLista borra_cabeza(TipoLista lista) {
struct Nodo * aux;
if (lista != NULL) {
aux = lista->sig;
free(lista);
lista = aux;
}
return lista;
}

Gestin de la memoria dinmica

457

14.4.4.2. Borrado de la cola


Se va a disear ahora una funcin que elimine el ltimo elemento de una lista. He
aqu su prototipo:
TipoLista borra_cola(TipoLista lista);

Nuevamente, se divide el trabajo en dos fases:


1. Localizar el ltimo nodo de la lista para liberar la memoria que ocupa.
2. Hacer que el hasta ahora penltimo nodo tenga como valor de su campo
sig a NULL.
TipoLista borra_cola(TipoLista lista) {
struct Nodo * aux, * atras;
if (lista != NULL) {
for (atras = NULL, aux = lista;
aux->sig != NULL;
atras = aux, aux = aux->sig);
free(aux);
if (atras == NULL)
lista = NULL;
else
atras->sig = NULL;
}
return lista;
}

Si se sigue el cdigo de la funcin presentada, puede verse paso a paso, qu hace


ahora el bucle. En la primera iteracin se tiene que:
atrs

aux
3

lista

Y en la segunda iteracin:
aux

atrs

lista

Y en la tercera:
aux

atrs
3

lista

No importa cun larga sea la lista; el puntero atras siempre va un paso por detrs
del puntero aux. En el ejemplo presentado ya se ha llegado al final de la lista, as que
ahora se puede liberar el nodo apuntado por aux:
atrs
lista

aux
8

458

Programacin en C

Tras ejecutar la sentencia atras->sig = NULL, se tiene que:


atrs
lista

aux
8

Esta funcin ser satisfactoria incluso si la lista est compuesta por un solo
elemento.
EJEMPLO

El siguiente programa muestra cmo eliminar un elemento del comienzo de


la lista enlazada y cmo borrar tambin un elemento del final de la lista.
El borrado de un nodo final de la lista enlazada requiere una bsqueda para
encontrar el ltimo nodo de la lista. Es importante utilizar dos punteros
durante el proceso de borrado. Es insuficiente un solo puntero al ltimo
nodo, puesto que el campo de enlace del penltimo nodo debe cambiarse
para apuntar a NULL.
#include <stdio.h>
#include <stdlib.h>

typedef struct nodo {


int dato;
struct nodo *enlace;
} LISTA;
void eliminar_por_el_principio(LISTA **ptr);
void eliminar_por_el_final(LISTA **ptr);
void mostrar_lista(LISTA *ptr);
void main() {
LISTA *primero = NULL;
int elemento;
LISTA *q,*p;
do {
printf("\nIntroduzca elemento: ");
scanf("%d",&elemento);
if(elemento != 0) {
p=(LISTA *)malloc(sizeof(LISTA));
if(primero == NULL)
primero=p;
else
q->enlace=p;
p->dato=elemento;
p->enlace=NULL;
q=p;
}
} while(elemento != 0);
printf("\nLa nueva lista enlazada es: ");
mostrar_lista(primero);
eliminar_por_el_principio(&primero);
printf("La nueva lista enlazada es: ");
mostrar_lista(primero);
eliminar_por_el_final(&primero);

Gestin de la memoria dinmica

459

printf("La nueva lista enlazada es: ");


mostrar_lista(primero);
}
void mostrar_lista(LISTA *ptr) {
while(ptr != NULL) {
printf("%d",ptr->dato);
ptr = ptr->enlace;
}
printf("\n");
}
void eliminar_por_el_principio(LISTA **ptr) {
LISTA *p;
p = *ptr;
if(p != NULL) {
p = p->enlace;
free(*ptr);
}
*ptr = p;
}
void eliminar_por_el_final(LISTA **ptr) {
LISTA *p1, *p2;
p1 = *ptr;
if(p1 != NULL) {
if(p1->enlace == NULL) {
free(*ptr);
*ptr = NULL;
}
else {
while(p1->enlace != NULL) {
p2 = p1;
p1 = p1->enlace;
}
p2->enlace = NULL;
free(p1);
}
}
}

14.4.5. Bsqueda de un elemento en la lista


Puede implementarse de diferentes maneras, devolviendo la posicin (direccin) del
nodo que contiene un determinado elemento o uno si el elemento se encuentra en la
lista o NULO si no se encuentra.
Una leve modificacin podra hacer que devolviera la posicin del nodo
anterior al que contiene el valor, de esa manera se podra utilizar la versin
ms simplificada de supresin de nodos.
En el caso de usar nodo cabecera podra ser adecuado saltarse dicho nodo
ficticio.
Si el lenguaje implementa los operadores lgicos en cortocircuito se

460

Programacin en C

simplifica la expresin del bucle.


Se va a disear una funcin que no modifica la lista. Se trata de una funcin que
indica si un valor entero pertenece a la lista o no. El prototipo de la funcin sera el
siguiente:
int pertenece(TipoLista lista, int valor);

La funcin devolver 1 si valor est en la lista y 0 en caso contrario.


Se sigue la misma aproximacin que con los vectores, esto es, recorrer cada uno de
sus elementos y, si se encuentra un nodo con el valor buscado, se devuelve
inmediatamente el valor 1; si se alcanza al final de la lista, significa que no se ha
encontrado, as que en tal caso se retorna el valor 0.
int pertenece(TipoLista lista, int valor) {
struct Nodo * aux;
for (aux=lista; aux != NULL; aux = aux->sig)
if (aux->info == valor)
return 1;
return 0;
}
EJEMPLO

El siguiente programa permite al usuario introducir un carcter que se


buscar en una lista enlazada dada. Obsrvese que el tiempo empleado en
buscar un elemento en la lista se incrementa cuando el nmero de nodos de
la lista crece. Por esta razn en la prctica se utilizan tablas de dispersin
para llevar a cabo bsquedas eficientes. El uso de estas tablas puede producir
colisiones, cuando dos o ms variables tienen la misma direccin en la tabla.
Este problema se puede solucionar utilizando listas enlazadas. Si dos o ms
variables tienen la misma direccin, sta puede ordenarse en una lista
enlazada para esa posicin.
#include <stdio.h>
#include <stdlib.h>
typedef struct nodo {
char dato;
struct nodo *enlace;
} LISTA;
void mostrar_lista(LISTA *ptr);
int buscar_en_lista(LISTA *ptr, char item);
void insertar(LISTA **ptr, char elemento);
main() {
char item;
int encontrado;
LISTA *primero = NULL;
char elemento;
do {
fflush(stdout);
printf("\nIntroduzca elemento: ");
elemento = getchar();
if(elemento != '\t')
insertar(&primero, elemento);
} while(elemento != '\t');
printf("La lista enlazada es como sigue: ");

Gestin de la memoria dinmica

mostrar_lista(primero);
printf("\nIntroduzca un carcter a buscar en la lista:\n");
fflush(stdin);
item = getchar();
encontrado = buscar_en_lista(primero,item);
printf("\nEl caracter %c ",item);
encontrado ? printf(" ha ") : printf(" no ha ");
printf("sido encontrado en la lista: ");
mostrar_lista(primero);
}
void mostrar_lista(LISTA *ptr) {
printf("\n");
while(ptr != NULL) {
printf("%c",ptr->dato);
ptr = ptr->enlace;
}
}
int buscar_en_lista(LISTA *ptr, char item) {
if(ptr == NULL)
return(0);
else {
do {
if(ptr->dato == item)
return(1);
ptr = ptr->enlace;
} while(ptr != NULL);
return(0);
}
}
void insertar(LISTA **ptr, char elemento) {
LISTA *p1, *p2;
p1 = *ptr;
if(p1 == NULL) {
p1 = (LISTA *)malloc(sizeof(LISTA));
if (p1 != NULL) {
p1->dato = elemento;
p1->enlace = NULL;
*ptr = p1;
}
}
else {
while(p1->enlace != NULL)
p1 = p1->enlace;
p2 = (LISTA *)malloc(sizeof(LISTA));
if(p2 != NULL) {
p2->dato = elemento;
p2->enlace = NULL;
p1->enlace = p2;
}
}
}

461

462

Programacin en C

EJEMPLO

Se va a crear una sencilla agenda que contiene el nombre y el nmero de


telfono utilizando listas enlazadas.
Una lista enlazada simple necesita una estructura con varios campos, los
campos que contienen los datos necesarios (nombre y telfono) y otro campo
que contiene un puntero a la propia estructura. Este puntero se usa para saber
dnde est el siguiente elemento de la lista, para saber la posicin en
memoria del siguiente elemento.
struct _agenda {
char nombre[20];
char telefono[12];
struct _agenda *siguiente;
};

Se podra representar la estructura grficamente como se muestra en la


Figura 14.8.

Figura 14.8. Representacin grfica de la estructura utilizada en este ejemplo

El programa utiliza dos funciones muy importantes. La primera es


anadir_elemento(), que se encarga de aadir nuevos elementos a la
lista. Por ejemplo, supngase que se aade un elemento a la lista, con datos
como: nombre = Luis Lpez y telefono = 923 24 12
11. La otra funcin es mostrar_lista(), que recorre la lista entera y
muestra todos sus elementos en la pantalla.
Para controlar la lista se utilizan dos punteros, *primero y *ultimo. El
primero guarda la posicin del primer elemento y el segundo la del ltimo.
#include <stdio.h>
struct _agenda {
char nombre[20];
char telefono[12];
struct _agenda *siguiente;
};
struct _agenda *primero, *ultimo;
void mostrar_menu() {
printf("\n\nMen:\n=====\n\n");
printf("1.- Aadir elementos\n");
printf("2.- Borrar elementos\n");
printf("3.- Mostrar lista\n");
printf("4.- Salir\n\n");
printf("Escoge una opcin: ");fflush(stdout);
}
/* Con esta funcin se aade un elemento al final de la lista */
void anadir_elemento() {

Gestin de la memoria dinmica

463

struct _agenda *nuevo;


/* Se reserva memoria para el nuevo elemento */
nuevo = (struct _agenda *) malloc (sizeof(struct _agenda));
if (nuevo==NULL)
printf( "No hay memoria disponible!\n");
printf("\nNuevo elemento:\n");
printf("Nombre: "); fflush(stdout);
gets(nuevo->nombre);
printf("Telfono: "); fflush(stdout);
gets(nuevo->telefono);
/* siguiente va a ser NULL por ser el ltimo */
nuevo->siguiente = NULL;
/* Ahora se introduce el nuevo elemento en la lista.
Se sita al final de la lista */
if (primero==NULL) {
printf( "Primer elemento\n");
primero = nuevo;
ultimo = nuevo;
}
else {
/* El que hasta ahora era el ltimo tiene que
apuntar al nuevo */
ultimo->siguiente = nuevo;
/* Se hace que el nuevo sea ahora el ltimo */
ultimo = nuevo;
}
}
void mostrar_lista() {
struct _agenda *auxiliar; /* Para recorrer la lista */
int i;
i=0;
auxiliar = primero;
printf("\nMostrando la lista completa:\n");
while (auxiliar!=NULL) {
printf("Nombre: %s, Telfono: %s\n",
auxiliar->nombre, auxiliar->telefono);
auxiliar = auxiliar->siguiente;
i++;
}
if (i==0)
printf( "\nLa lista est vaca!!\n" );
}
int main() {
char opcion;
primero = (struct _agenda *) NULL;
ultimo = (struct _agenda *) NULL;
do {
mostrar_menu();
opcion = getch();
switch ( opcion ) {
case '1': anadir_elemento();
break;

464

Programacin en C

case '2': printf("No disponible todava!\n");


break;
case '3': mostrar_lista(primero);
break;
case '4': exit( 1 );
default: printf( "Opcin no vlida\n" );
break;
}
} while (opcion!='4');
}

14.5. Lista doblemente enlazada


Algunas tareas de insercin y supresin de nodos se ven dificultadas por la necesidad
de acceder al nodo anterior.
Para facilitar estas tareas o para otros problemas concretos en los que sea necesario
recorrer la lista en los dos sentidos se utilizan las listas doblemente enlazadas.
Todo lo dicho para listas simplemente enlazadas es vlido para las doblemente
enlazadas, tan slo se debe ser ms cuidadoso con las operaciones porque involucran
cuatro punteros.
Se va a dotar a cada nodo de dos punteros: uno al siguiente nodo en la lista y otro
al anterior.
Los nodos sern variables de este tipo:
struct DNodo {
int info; // Valor del nodo.
struct DNodo * ant; // Puntero al anterior.
struct DNodo * sig; // Puntero al siguiente.
};
Una lista es un puntero a un elemento struct DNodo (o a NULL). Nuevamente, se

define un tipo para poner nfasis en que un puntero representa a la lista que cuelga de
l.
typedef struct DNodo * TipoDLista ;

Aqu se tiene una representacin grfica de una lista doblemente enlazada:


lista

Figura 14.9. Representacin grfica de una lista doblemente enlazada

Puede observarse que cada nodo tiene dos punteros, uno al nodo anterior y otro al
siguiente. Qu nodo sigue al ltimo nodo? Ninguno, o sea, NULL. Y cul antecede
al primero? Ninguno, es decir, NULL.
Se vern una operacin de insercin y otra de supresin para ilustrar los problemas
que se pueden plantear.

Gestin de la memoria dinmica

465

14.5.1. Procedimiento Inserta


En este caso en la posicin exacta p (suponiendo que es legal) implica insertar antes,
se debe buscar el nodo anterior?
Si la lista no tiene nodo falso, sera necesario pasar por referencia la
variable posicin que mantiene la lista.
Si la lista tiene nodo falso no es necesario pasar por referencia la variable
posicin que mantiene la lista.
Se puede hacer la misma funcin para insertar despus del nodo apuntado
por p, pero controlando en este caso si se intenta aadir al final de la lista
doblemente enlazada o en cualquier otra posicin.
14.5.1.1. Insercin por cabeza
La insercin por cabeza es relativamente sencilla. Se trata en primer lugar el caso
general, la insercin por cabeza en una lista no vaca. Por ejemplo, en sta:
lista

Se va a hacer el estudio paso a paso.


1. Se comienza pidiendo memoria para un nuevo nodo:
nuevo
lista

2. Se asigna el valor indicado al campo info:


nuevo

lista

3. Se ajustan sus punteros ant y sig:


nuevo

lista

4. Se ajusta el puntero ant del que hasta ahora ocupaba la cabeza:


nuevo

lista

466

Programacin en C

5. Y, finalmente, se hace que lista apunte al nuevo nodo:


nuevo
3

lista

El caso de la insercin en la lista vaca es trivial, se pide memoria para un nuevo


nodo cuyos punteros ant y sig se ponen a NULL y se hace que la cabeza apunte a
dicho nodo.
A continuacin se presenta la funcin que codifica el mtodo descrito. Se ha
factorizado y dispuesto al principio los elementos comunes al caso general y al de la
lista vaca.
TipoDLista inserta_por_cabeza(TipoDLista lista, int valor) {
struct DNodo * nuevo;
nuevo = (DNodo *)malloc(sizeof (struct DNodo));
nuevo->info = valor;
nuevo->ant = NULL;
nuevo->sig = lista;
if (lista != NULL)
lista->ant = nuevo;
lista = nuevo;
return lista;
}

14.5.1.2. Insercin en una posicin determinada


Se aborda ahora el caso de la insercin de un nuevo nodo en la posicin n de lista
doblemente enlazada.
lista

3
0

2
2

Si n est fuera del rango de ndices vlidos, se inserta en la cabeza (si n es


negativo) o en la cola (si n es mayor que el nmero de elementos de la lista).
A simple vista se perciben ya diferentes casos que requerirn estrategias diferentes:
La lista vaca: la solucin en este caso es trivial.
Insercin al principio de la lista: se sigue la misma rutina diseada para
insertar por cabeza y se utilizar la funcin que fue diseada en su momento.
Insercin al final de la lista.
Insercin entre dos nodos de una lista.
Se va a desarrollar completamente el ltimo caso. Nuevamente se utilizar una lista
concreta para deducir cada uno de los detalles del mtodo. Se insertar el valor 1 en la
posicin 2 de esta lista.

Gestin de la memoria dinmica

lista

467

1. Se comienza localizando el elemento que ocupa actualmente la posicin n. Un


simple bucle efectuar esta labor.
aux
lista

2. Se solicita memoria para un nuevo nodo, se le asignas el valor y se apunta con


el puntero nuevo.
aux
lista

nuevo

3. Se hace que nuevo->sig sea aux.


aux
lista

nuevo

4. Se hace que nuevo->ant sea aux->ant.


aux
lista

nuevo

5. Se hace que el nodo anterior a aux tenga como siguiente a nuevo, es decir,
aux->ant->sig = nuevo.

468

Programacin en C

aux
lista

nuevo

6. Slo resta que el nodo anterior a aux sea nuevo con la asignacin
aux->ant=nuevo.
aux
lista

nuevo

Ahora que e ha ilustrado el procedimiento, se puede escribir la funcin.


TipoDLista inserta_en_posicion(TipoDLista lista, int pos, int valor) {
struct DNodo * aux, * nuevo;
int i;
/* Caso especial: lista vaca */
if (lista == NULL) {
lista = inserta_por_cabeza(lista, valor);
return lista;
}
/* Insercin en cabeza en lista no vaca */
if (pos <= 0) {
lista = inserta_por_cabeza(lista, valor);
return lista;
}
/* Insercin no en cabeza */
nuevo = (Dnodo*)malloc(sizeof (struct DNodo));
nuevo->info = valor;
for (i = 0, aux = lista;
i < pos && aux != NULL;
i++, aux = aux->sig);
if (aux == NULL) /* Insercin por cola. */
lista = inserta_por_cola(lista, valor);
else {
nuevo->sig = aux;
nuevo->ant = aux->ant;
aux->ant->sig = nuevo;
aux->ant = nuevo;
}
return lista;
}

Gestin de la memoria dinmica

469

14.5.2. Procedimiento Suprime


Elimina de la lista el nodo apuntado por p, suponiendo que es legal, y libera la
memoria ocupada por dicho nodo:
Si la lista no tiene nodo ficticio entonces hay que pasar por referencia el
apuntador al primer nodo de la lista.
Si la lista tiene nodo ficticio:
o Se puede suprimir nodos en cualquier posicin de la lista sin
necesidad de alterar la variable posicin que mantiene la lista.
o La funcin es genrica.
14.5.2.1. Borrado de la cola
Se va a desarrollar la funcin de borrado del ltimo elemento de una lista doblemente
enlazada, pues presenta algn aspecto interesante.
Se va a desarrollar nuevamente el caso general sobre una lista concreta para
deducir el mtodo a seguir. Se toma, por ejemplo, la siguiente:
lista

1.

Se comienza localizando el ltimo elemento de la lista (con un bucle) y


apuntndolo con un puntero.
aux
lista

2.

lista

aux

Se elimina el ltimo nodo (el apuntado por aux).


atrs

lista

4.

Y se localiza ahora el penltimo en un slo paso (es aux->ant).


atrs

3.

aux

Y se pone el campo sig del que hasta ahora era el penltimo nodo (el
apuntado por atras) a NULL.
atrs

lista

aux

470

Programacin en C

El caso de la lista vaca tiene fcil solucin, no hay nada que borrar. Es ms
problemtica la lista con slo un nodo. El problema con ella estriba en que no hay
elemento penltimo (el anterior al ltimo es NULL). Se tiene, pues, que detectar esta
situacin y tratarla adecuadamente.
TipoDLista borra_por_cola(TipoDLista lista) {
struct struct DNodo * aux, * atras;
/* Lista vaca */
if (lista == NULL)
return lista;
/* Lista con un nodo */
if (lista->sig == NULL) {
free(lista);
lista = NULL;
return lista;
}
/* Caso general */
for (aux=lista; aux->sig!=NULL; aux=aux->sig);
atras = aux->ant;
free(aux);
atras->sig = NULL;
}

14.5.2.2. Borrado de la primera aparicin de un elemento


Nuevamente hay un par de casos triviales, si la lista est vaca, no hay que hacer nada
y si la lista tiene un slo elemento, slo se ha de actuar si ese elemento tiene el valor
buscado, en cuyo caso se libera la memoria del nodo en cuestin y se convierte la lista
en una lista vaca.
Se va a desarrollar el caso general. Supngase que en esta lista se ha de eliminar el
primer y nico nodo con valor 8.
lista

Se va a proceder a hacer el estudio paso a paso.


1. Se comienza por localizar el elemento y apuntarlo con un puntero auxiliar
aux.
aux
lista

2. Se hace que el nodo que sigue al anterior de aux sea el siguiente de aux. O
sea, se hace aux->ant->sig=aux->sig.
aux
lista

Gestin de la memoria dinmica

471

3. Ahora se hace que el node que antecede al siguiente de aux sea el anterior a
aux. Es decir, aux->sig->ant=aux->ant.
aux
lista

4. Y ya se puede liberar la memoria ocupada por el nodo apuntado por aux.


aux
lista

Se ha de ser cautos. Hay un par de casos especiales que merecen ser tratados
aparte, el borrado del primer nodo y el borrado del ltimo nodo. Se va a estudiar
cmo proceder en el primer caso, se va a tratar de borrar el nodo de valor 3 en la lista
del ejemplo anterior.
1. Una vez apuntado el nodo por aux, se sabe que es el primero porque apunta
al mismo nodo que lista.
aux
lista

2.

Se hace que el segundo nodo deje de tener antecesor, es decir, que el puntero
aux->sig->ant valga NULL (que, por otra parte, es lo mismo que hacer
aux->sig->ant=aux->ant).
aux
lista

3.

Ahora se hace que lista pase a apuntar al segundo nodo (lista=aux->sig).


aux
lista

4.

Y ahora se puede liberar el nodo apuntado por aux (free(aux)).


aux
lista

El otro caso, en el que se borra el ltimo elemento de la lista, se abordara como


sigue.

472

Programacin en C

1.

Se empieza por localizar el nodo a borrar con aux y se detecta que


efectivamente es el ltimo porque aux->sig es NULL:
aux
lista

2.

Se hace que el siguiente nodo del que antecede a aux sea NULL, esto es
aux->ant->sig=NULL.
aux
lista

3.

Y se libera el nodo apuntado por aux.


aux
lista

TipoDLista borra_primera_ocurrencia(TipoDLista lista, int valor) {


struct Nodo * aux;
for (aux=lista; aux!=NULL; aux=aux->sig)
if (aux->info == valor)
break;
if (aux == NULL) /* No se encontr */
return lista;
if (aux->ant == NULL) /* Es el primero de la lista */
lista = aux->sig;
else
aux->ant->sig = aux->sig;
if (aux->sig != NULL) /* No es el ltimo de la lista */
aux->sig->ant = aux->ant;
free(aux);
return lista;
}

14.6. Otras estructuras de datos dinmicas


La posibilidad de trabajar con registros enlazados abre las puertas al diseo de
estructuras de datos muy elaboradas que permiten efectuar ciertas operaciones muy
eficientemente. El precio a pagar es una mayor complejidad de nuestros programas C
y, posiblemente, un mayor consumo de memoria (se estn almacenando valores y
punteros, aunque slo interesan los valores).

Gestin de la memoria dinmica

473

Se trata por tanto de estructuras de datos complejas, pero capaces de ofrecer


tiempos de respuesta mucho mejores que las listas que se han estudiado o capaces de
permitir implementaciones sencillas para operaciones que an no se han estudiado.
Se van a presentar algunos ejemplos ilustrativos.

14.6.1. Listas circulares


Las listas circulares, por ejemplo, son listas sin final. El nodo siguiente al que parece
el ltimo nodo es el primero. Ningn nodo est ligado a NULL, como se puede
apreciar en la Figura 14.10.
Pueden aparecer simple o doblemente enlazadas, generalmente con nodo ficticio,
aunque no siempre.
lista

Figura 14.10. Ejemplo de lista circular

Sus caractersticas son:


El campo siguiente del ltimo nodo apunta al primer nodo de la
lista enlazada (nodo ficticio o nodo con el primer elemento de la lista).
El campo anterior del primer nodo de la lista (nodo ficticio o nodo
con el primer elemento de la lista) apunta al ltimo nodo de la lista
enlazada.
Las operaciones seran idnticas a las vistas hasta ahora, con una
salvedad: no existe un nodo final que apunte a NULO.
La variable que mantiene la lista, puntero raz o encabezamiento, puede
apuntar a cualquier nodo de la lista.
Se suele utilizar el nodo ficticio:
o Para indicar principio y fin de la lista.
o Se identifica por tener en el campo elemento un valor especial,
distinto de todos los posibles de la lista.

14.6.2. Pila
Muchas operaciones computacionales se ven simplificadas por el uso de pilas y colas
controladas por software. Son estructuras de datos que almacenan y recuperan sus
elementos atendiendo a un estricto orden.
Una pila es una lista, con la restriccin de que las inserciones y las eliminaciones
slo se pueden realizar en una posicin: al final de la lista. Esta posicin final se
denominado tope o cima.
Una caracterstica de la pila es que el ltimo elemento que se introduce es siempre
el primer elemento que se saca. Por esta razn se conocen como estructuras de tipo
LIFO (Last In, First Out), esto es, existe algn elemento que est en la cima de la pila
y es el nico visible o accesible.

474

Programacin en C

En la Figura 14.11 puede apreciarse una representacin grfica del concepto de


pila.

tope

Elemento 7
Elemento 6
Elemento 5
Elemento 4
Elemento 3
Elemento 2
Elemento 1

Figura 14.11. Representacin grfica de una pila

Las operaciones que generalmente incluye una pila son:


Crea_vaca(P)
o Inicia o crea la pila P como una pila vaca, sin ningn elemento.
Tope(P)
o Devuelve el valor del elemento del tope o cima de la pila.
o No siempre utilizada.
Saca(P), generalmente Pop(P)
o Suprime el elemento del tope o cima de la pila.
o Generalmente, adems de suprimir tambin se devuelve el valor
almacenado en esa posicin y no se utiliza la funcin antes vista.
Mete(x ,P), generalmente Push(x, P)
o Inserta el valor x en la posicin a continuacin del tope o cima
pasando a ser este el nuevo tope o cima de la pila.
o Dependiendo de la representacin utilizada, se debe comprobar,
previamente, si la pila est llena.
Vaca(P)
o Devuelve verdadero si la pila P est vaca, y falso en caso
contrario.
En este modelo las operaciones bsicas son la operacin de entrada, Push(), y las
operaciones de salida, Tope() y Pop().
Sacar o suprimir un elemento de una pila vaca se considera un error. Sin embargo,
quedarse sin espacio al realizar una introduccin de un elemento es un error de
implementacin o representacin.
La implementacin de una pila mediante listas enlazadas se puede hacer de
diferentes formas.
Una primera forma sera utilizando una lista simplemente enlazada. Para ello se
inserta y extrae por el principio de la lista donde el tope es siempre el primer elemento
de la lista. Si se utiliza nodo ficticio, se crean funciones ms sencillas y generales. En
la Figura 14.12 se ilustra esta primera forma de implementacin.

Gestin de la memoria dinmica

Pila

475

Figura 14.12. Representacin grfica de una pila mediante listas enlazadas

Otra posibilidad, usando tambin una lista simplemente enlazada, es insertar y


extraer por el final de la lista. En este caso para evitar recorridos innecesarios se
mantiene un puntero al ltimo elemento insertado, aunque slo evitara dichos
recorridos para la insercin de un nuevo elemento, no as en la supresin de un
elemento de la pila.
Dada la poltica de insercin/extraccin de este TAD, ste resulta muy til para
resolver una gran variedad de problemas de tratamiento de la informacin, as como
matemticos, estadsticos o financieros.
EJEMPLO

El siguiente ejemplo muestra la forma de implementar una pila de caracteres.


La pila se implementa como una lista enlazada, las operaciones
introducir() y extraer() manipulan el primer nodo de la lista que
acta como pila.
En este caso va a ser importante hacer uso de la funcin vacia() en la
funcin extraer(), sobre la pila vaca.
#include <stdio.h>
#include <stdlib.h>
typedef struct nodo {
char dato;
struct nodo *enlace;
} PILA;
void mostrar_pila(PILA *ptr);
int vacia(PILA *ptr);
void introducir(PILA **ptr, char item);
void extraer(PILA **ptr, char *item);
int main() {
PILA *pila = NULL;
char item;
introducir(&pila,'a');
introducir(&pila,'b');
introducir(&pila,'c');
printf("La pila es como sigue: ");
mostrar_pila(pila);
extraer(&pila,&item);
printf("\nEl primer elemento obtenido es %c\n",item);
extraer(&pila,&item);
printf("\nEl segundo elemento obtenido es %c\n",item);
extraer(&pila,&item);
printf("\nEl tercer elemento obtenido es %c\n",item);
extraer(&pila,&item);
return(1);

476

Programacin en C

}
void mostrar_pila(PILA *ptr) {
while(ptr != NULL) {
printf("%c",ptr->dato);
ptr = ptr->enlace;
}
printf("\n");
}
int vacia(PILA *ptr) {
if(ptr == NULL)
return(1);
else
return(0);
}
void introducir(PILA **ptr, char item) {
PILA *p;
if(vacia(*ptr)) {
p = malloc(sizeof(PILA));
if(p != NULL) {
p->dato = item;
p->enlace = NULL;
*ptr = p;
}
}
else {
p = malloc(sizeof(PILA));
if(p != NULL) {
p->dato = item;
p->enlace = *ptr;
*ptr = p;
}
}
}
void extraer(PILA **ptr, char *item) {
PILA *p1;
p1 = *ptr;
if(vacia(p1)) {
printf("Error! La pila esta vacia.\n");
*item = '\0';
}
else {
*item = p1->dato;
*ptr = p1->enlace;
free(p1);
}
}

Gestin de la memoria dinmica

477

Ejemplo de ejecucin
La pila es como sigue: cba
El primer elemento obtenido es

El segundo elemento obtenido es


El tercer elemento obtenido es

b
a

Error! La pila esta vaca.


Presione otra tecla para continuar

14.6.3. Cola
Se trata de una lista en la que se establecen las siguientes restricciones en cuanto a las
operaciones de insercin y extraccin:
La insercin se realiza al final de la lista (fondo): encolar.
La eliminacin se realiza al comienzo de la lista (frente): desencolar.
As pues se trata de una lista de tipo FIFO (Firs In, First Out), primero en entrar,
primero en salir. En la Figura 14.13 se presenta grficamente una cola.

frente

fondo

Figura 14.13. Representacin grfica de una cola

Existen diferentes situaciones en las que son tiles las colas. En general, en toda
situacin en la que es necesario mantener una estructura FIFO. Ejemplos
significativos son las colas de impresin y de procesos de los sistemas operativos.
Las operaciones que generalmente incluye una cola son:
Crea_vaca(C)
o Inicia o crea la cola C como una cola vaca, sin ningn elemento.
Frente(C)
o Devuelve el valor del elemento del frente de la cola.
o No siempre se utiliza.
Desencola(C) o Saca(C)
o Suprime el elemento del frente o primer elemento de la cola,
pasando a ser el nuevo frente el siguiente elemento.
o Generalmente, adems de suprimir tambin se devuelve el valor
almacenado en esa posicin y no se utiliza la funcin antes vista.
Encola(x, C) o Pon_en_cola(x, C)

478

Programacin en C

Inserta el valor x en la posicin a continuacin del fondo, o


ltimo elemento de la cola, pasando a ser el nuevo fondo.
Vaca(C)
o Devuelve verdadero si la cola C est vaca, y falso en caso
contrario.
La implementacin de una cola mediante listas enlazadas se puede hacer de
diferentes formas, utilizando para ello una lista simplemente enlazada. En este caso se
extrae por el principio de la lista, es decir, por el frente, se aade al final de la lista
(fondo). Para evitar recorridos innecesarios se mantiene un puntero al ltimo
elemento insertado.
Si se utiliza nodo ficticio, las funciones sern ms sencillas y generales.
En la Figura 14.14 se ilustra la implementacin de una cola mediante una lista
simplemente enlazada.
o

cola
fondo
frente

10

Figura 14.14. Representacin grfica de una cola mediante una lista simplemente enlazada
EJEMPLO

El ejemplo muestra la forma de introducir datos en una cola y cmo eliminar


datos de una cola. Al contrario de las operaciones sobre pilas los datos de la
cola se recuperan en el orden en que se insertan.
#include <stdio.h>
#include <stdlib.h>
typedef struct nodo {
char dato;
struct nodo *enlace;
} NODO;
typedef struct {
NODO *frente, *fondo;
}COLA;
void mostrar_cola(COLA cola);
void iniciar_cola(COLA * cola);
int vacia(COLA cola);
void encolar(COLA * cola, char item);
char desencolar(COLA * cola);
int main(void)
{
COLA una_cola;
char item;
iniciar_cola(&una_cola);
encolar(&una_cola, 'a');

Gestin de la memoria dinmica

479

encolar(&una_cola, 'b');
encolar(&una_cola, 'c');
printf("La cola es como sigue: ");
mostrar_cola(una_cola);
while (!vacia(una_cola)) {
item = desencolar(&una_cola);
printf("\nEl siguiente elemento obtenido de la cola es %c\n", item);
}
return (1);
}
void iniciar_cola(COLA *ptr)
{
ptr->frente = ptr->fondo = NULL;
}
void mostrar_cola(COLA cola)
{
NODO *ptr;
ptr=cola.frente;
while (ptr != NULL) {
printf("%c", ptr->dato);
ptr = ptr->enlace;
}
printf("\n");
}
int vacia(COLA cola)
{
if (cola.frente == NULL)
return (1);
else
return (0);
}
void encolar(COLA * ptr, char item)
{
NODO
*p;
p = (NODO *) malloc(sizeof(NODO));
if (p != NULL) {
p->dato = item;
p->enlace = NULL;
if (!vacia(*ptr)) {
ptr->fondo->enlace = p;
ptr->fondo = p;
} else
ptr->frente = ptr->fondo = p;
}
}

480

Programacin en C

char desencolar(COLA *ptr)


{
NODO *p;
char item;
if(!vacia(*ptr)) {
p = ptr->frente;
item = p->dato;
ptr->frente = ptr->frente->enlace;
free(p);
}
else
item ='\0';
return item;
}

Ejemplo de ejecucin
La cola es como sigue: cba
El primer elemento obtenido de la cola es a
El segundo elemento obtenido de la cola es b
El tercer elemento obtenido de la cola es c

14.7. Resumen
Se han presentado los conceptos bsicos relacionados con memoria dinmica
utilizando matrices y listas enlazadas.
Existen muchas estructuras dinmicas de datos que permiten acelerar sobremanera
los programas que gestionan grandes conjuntos de datos. Apenas se ha empezado a
conocer y aprender a manejar las herramientas ms elementales con las que se
construyen los programas que utilizan estas estructuras.
Se debe elegir entre representacin con matrices o representacin con punteros
(listas enlazadas, simples, dobles, circulares). La base en que se ha de sustentar la
eleccin debe tener en cuenta diversos factores, tales como:
Operaciones que se deben realizar.
Operaciones se van a utilizar ms frecuentemente.
Tamao que puede alcanzar la lista y el hecho de si se conoce el tamao
mximo de sta.
Adems, otros aspectos fundamentales como los siguientes:
La utilizacin de matrices obliga a la especificacin de un tamao
mximo en tiempo de compilacin. Si no es posible, se deben utilizar
listas enlazadas.
La utilizacin de matrices puede desaprovechar mucha memoria, sobre
todo si existe una gran diferencia entre el tamao mximo y el real
ocupado durante la ejecucin.
Elegir aquella representacin en la que las operaciones que ms se van a
utilizar sean ms rpidas.

Gestin de la memoria dinmica

481

Ciertas operaciones son ms rpidas en la representacin con listas


enlazadas que con matrices (Insertar, Suprimir).
Ciertas operaciones son ms rpidas en la representacin con matrices que
con listas enlazadas (Acceder a un elemento).
Aunque la representacin ms compleja (lista doblemente enlazada) requiera
tiempo constante en muchas operaciones, stas son algo ms lentas y sofisticadas que
las operaciones anlogas en las otras estructuras ms sencillas.
El consumo de memoria es mayor en la lista ms compleja, as que puede no
compensar la ganancia en velocidad o, sencillamente, es posible que no pueda
permitirse el lujo de gastar el doble de memoria extra.
Puede que la aplicacin slo efecte operaciones baratas sobre cualquier lista.
Por ejemplo, si se necesita una lista en la que siempre se insertan y eliminan nodos
por la cabeza, jams por el final, la lista simplemente enlazada ofrece tiempo
constante para esas dos operaciones y, adems, es mucho ms sencilla y consume
menos memoria que una lista doblemente enlazada.
No se trata de memorizar algoritmos de manejo de estructuras complejas, ms bien
entender los ejemplos presentados, para elegir y construir la estructura ms adecuada
a cada problema.

14.8. Cuestiones y ejercicios


1.

2.

3.
4.
5.
6.

7.
8.

Escribe un programa que lea por teclado un vector de double cuyo tamao
se solicitar previamente al usuario. Una vez ledos los componentes del
vector, el programa copiar sus valores en otro vector distinto que ordenar
con el mtodo de la burbuja.
Escribe una funcin que lea por teclado un vector de double cuyo tamao
se solicitar previamente al usuario. Escribe, adems, una funcin que reciba
un vector como el ledo en la funcin anterior y devuelva una copia suya con
los mismos valores, pero ordenados de menor a mayor.
Disea un programa que haga uso de las dos funciones anteriores.
Reacurdese liberar toda memoria dinmica solicitada antes de finalizar ste.
Escribe una funcin que reciba un vector de enteros y devuelva otro con sus
n mayores valores, siendo n un nmero menor o igual que la talla del vector
original.
Crear una lista enlazada que funcione como agenda con los campos nombre
y telfono que permita, adems de aadir nuevos elementos, eliminar y
mostrar la agenda completa.
Escribir un programa que cree una lista enlazada de nmeros enteros
positivos al azar, la insercin debe realizarse por el ltimo nodo. Recorrer la
lista para mostrar los elementos por pantalla y eliminar todos los nodos que
superen un valor dado.
Escribir una funcin que devuelva verdadero si la lista est vaca.
Se tiene un archivo de texto de palabras separadas por un blanco o el carcter
de fin de lnea. Escribir un programa para formar una lista enlazada con las
palabras del archivo. Una vez formada la lista se pueden aadir nuevas

482

9.

Programacin en C

palabras o borrar alguna de ellas. Al finalizar el programa escribir las


palabras de la lista en el archivo.
Escribir un programa para obtener una lista enlazada con los caracteres de
una cadena ledos desde el teclado. Cada nodo de la lista tendr un carcter.
Una vez que se tiene la lista, ordenarla alfabticamente.

15. Gestin del cdigo fuente en proyectos


software en C
En el captulo 11 se ha introducido el concepto de funcin en el lenguaje C como la
implementacin de un mdulo. Tambin se estudian las importantes ventajas que
presenta el desarrollo de software mediante un uso extensivo de mdulos, es decir, la
divisin del cdigo en partes ms pequeas y sencillas de crear, manejar, probar y
depurar. As, un programa en C constara de un nico fichero conteniendo una gran
cantidad de lneas de cdigo, divididas en diferentes funciones, cada una de ellas
representando una nica tarea bien definida.
Si bien esta estrategia ya aprovecha las ventajas del diseo modular, en la prctica
se estara desaprovechando otro elemento fundamental: el fichero. Al utilizar un nico
fichero para escribir todas las funciones del programa que se est desarrollando, se
presentan ciertos inconvenientes:
La modificacin de una nica funcin implica que, al generar el
ejecutable, se recompilar el cdigo de todas las funciones, aunque no
hayan sufrido ninguna modificacin, con el consiguiente coste en tiempo.
El mismo inconveniente se presenta en el caso de la sustitucin de una
funcin por otra, o la incorporacin de nuevas funciones al cdigo fuente
del programa.
Si el cdigo del programa en desarrollo utiliza variables globales (algo
que se debe evitar en la medida de lo posible), al estar declaradas en un
nico fichero, estas seran accesibles por todas las funciones, con lo que
se intensifican sus efectos perniciosos.
Si en lugar de utilizar un nico fichero, las funciones se escriben en varios, se
pueden reducir, o incluso, eliminar estos inconvenientes. As el cambio, parcial o
total, de una funcin o la adicin de nuevas funciones, no implica la recompilacin de
todas las que constituyen el cdigo fuente del programa. Tan slo se recompilarn las
funciones presentes en el archivo modificado, bien por contener alguna funcin
modificada, bien porque se ha aadido alguna nueva. Posteriormente, se enlaza el
cdigo objeto generado con el que ya se haba generado en compilaciones anteriores,
correspondiente a los archivos que contienen el resto de las funciones, y que no han
sido modificados. As, se ahorra gran cantidad de tiempo, pues en el proceso de
generacin de un ejecutable la fase que consume ms tiempo y recursos es la
compilacin, mientras que el enlazado resulta ms rpido. Este proceso se ilustra en la
Figura 15.1 y en la Figura 15.2.
Por otro lado, si algunas funciones deben compartir el acceso a una variable global,
se pueden agrupar en un fichero y declarar dicha variable con el especificador
static, que limita su mbito nicamente a ese fichero. De esta manera, los efectos
perniciosos que presenta el uso de variables globales no afectaran al resto del cdigo,
y se limitarn nicamente al citado fichero.
La mayora de los entornos de desarrollo proporcionan herramientas que permiten
gestionar, de manera sencilla, proyectos software formados por varios ficheros fuente.
En general, estas herramientas trabajan en torno al concepto de proyecto, que define
el tipo de objetivo que se pretende construir, normalmente un ejecutable (se deber
- 483 -

484

Programacin en C

especificar el tipo de ejecutable). Posibilitan aadir ficheros fuente al proyecto,


incluso editarlos, y aadir ficheros objeto de bibliotecas externas con las que se
enlazar para producir el ejecutable. Adems, suelen proporcionar otro tipo de
facilidades como un depurador integrado, herramientas de trazado que permiten
estudiar el consumo de recursos de memoria del ejecutable...

Figura 15.1. Generacin del ejecutable partiendo de varios ficheros fuente

Figura 15.2. El fichero 3 contiene funciones que han sido modificadas, es el nico que se
recompila

La mayora de los entornos actuales suelen, adems, presentar una interfaz grfica
de usuario, lo que facilita enormemente acceder a todas las herramientas. En estos
casos el entorno suele recibir el nombre de Entorno de Desarrollo Integrado (EDI)
(tambin conocidos por sus siglas en ingls, IDE, Integrated Development
Environment). En algunos casos estos EDI son entornos grficos que engloban y
recubren un conjunto de herramientas (rdenes en la lnea de rdenes) no grficas.
Por ejemplo, en MS Windows se dispone de MS Visual C++
(http://www.microsoft.com) o Borland C++ Builder (http://www.borland.com),

Gestin del cdigo fuente en proyectos software en C

485

mientras que en GNU/Linux se dispone de KDevelop (http://www.kdevelop.org/) y


Anjuta (http://anjuta.sourceforge.net), ver Figura 15.3, que en realidad son
herramientas frontales (front-end) que hacen uso de distintas rdenes como
autogen, autoconf, make, gcc, gdb...

Figura 15.3. Entorno de desarrollo integrado Anjuta

Estos entornos integrados suelen presentar varias ventanas, en una de ellas se


muestra informacin sobre el proyecto como, por ejemplo, los ficheros que forman
parte del mismo, o las funciones y variables, permitiendo el acceso directo a los
mismos en el editor de texto sin ms que seleccionarlos directamente. En otra ventana
se dispone un editor de texto para poder visualizar y escribir el cdigo fuente. Una
tercera ventana suele proporcionar informacin sobre los procesos que tienen lugar,
como la compilacin, el enlazado o la propia ejecucin del ejecutable generado.
En este captulo se mostrar el funcionamiento de dos tipos de herramientas para la
gestin de proyectos: por un lado, en la primera seccin, la orden make, parte de los
entornos de desarrollo clsicos en los sistemas UNIX, aunque tambin presente en los
entornos de desarrollo en sistemas MS Windows, incluyendo los EDIs ms
sofisticados. Por otro lado, en la seccin dos, se mostrar el funcionamiento bsico de
un EDI, mostrando las funcionalidades ms habituales en este tipo de entornos.
Finalmente, la tercera y ltima seccin concluye el captulo con las conclusiones del
mismo.

15.1. Gestin de proyectos con MAKE


Se trata de una de las rdenes ms importantes y conocidas de los sistemas de

486

Programacin en C

desarrollo UNIX. Permite gestionar la creacin de ejecutables partiendo de ficheros


de cdigo objeto y de cdigo fuente, siguiendo un conjunto de reglas, algunas de ellas
definidas por defecto y otras definidas por el usuario.
Para ilustrar el funcionamiento de make se utilizar como ejemplo la construccin
de un programa que realiza las siguientes tareas:
Permitir la creacin listas, de longitud definida por el usuario,
conteniendo valores enteros aleatorios, es decir, listas desordenadas.
Permitir realizar la ordenacin utilizando un mtodo a elegir por el
usuario entre varios que se presentan: burbuja, insercin u ordenacin
rpida.
Cada vez que se ordena una lista, devolver un valor que indica el tiempo
consumido en el proceso.
El usuario puede visualizar las listas, tanto desordenadas como ordenadas.
En una primera aproximacin, se implementarn todas las funciones en nico
fichero. Dichas funciones son:
Las funciones que implementan los distintos algoritmos de ordenacin y
algunas auxiliares: llama_ord_rapida(), ord_rapida(), coloca(),
burbuja(), intercambia().
Funciones auxiliares encargadas de tareas varias como la creacin del
men de la aplicacin, generacin de listas desordenadas y visualizacin
de listas: menu(), desordena_lista(), muestra_lista().
Reserva dinmica de memoria: asignar_mem().
La funcin principal: main().
Como ya se ha explicado, cualquier modificacin en alguna de las funciones
desarrolladas implica la recompilacin y enlazado de todo el cdigo fuente para
generar el ejecutable. Incluso si se desea incorporar nuevos algoritmos de ordenacin,
se debe aadir el cdigo necesario y recompilar, de nuevo, todo el cdigo fuente. La
tarea de compilacin es la que ms recursos computacionales consume, sobre todo
tiempo. Por tanto, si se pudiera evitar volver a realizar trabajo ya hecho, es decir, si se
pudiera evitar recompilar todo el cdigo, y compilar slo aquello que se ha
modificado, se ganara tiempo en el proceso de desarrollo.
Para generar un ejecutable, en un entorno de lnea de rdenes como UNIX, se debe
invocar el nombre del compilador instalado y, a continuacin, el nombre del fichero o
ficheros que contienen el cdigo fuente. Generalmente este compilador realiza las dos
tareas fundamentales en el proceso de generacin de ejecutables, la compilacin a
cdigo objeto de los ficheros conteniendo el cdigo fuente, y el posterior enlazado de
todos los ficheros objeto generados y otros necesarios en un nico fichero ejecutable.
Siguiendo con el ejemplo propuesto, si todas las funciones se escriben en un nico
fichero, ordena.c, la orden a invocar sera:
gcc ordena.c -o ordena
El nombre del compilador es, en este caso, gcc (compilador GNU C) el ms
habitual en todos los sistemas UNIX, aunque tambin existe una versin, libre y
gratuita, para los sistemas de Microsoft (http://www.redhat.com/software/cygwin,
http://cygwin.com, http://gnuwin.epfl.ch/es/index.html). En caso de utilizar otro
compilador se debe consultar la documentacin correspondiente. Generalmente el
nombre suele ser cc o tambin precedido de un prefijo.

Gestin del cdigo fuente en proyectos software en C

487

La opcin -o le indica al compilador que el nombre del ejecutable debe ser


ordena. Si no se utiliza esta opcin gcc generar un ejecutable con el nombre
a.out. Esto es vlido para prcticamente todos los compiladores de C en entornos
UNIX.

15.1.1. Proyecto basado en varios ficheros


Como ya se ha comentado la utilizacin de un nico fichero resulta costoso, sobre
todo cuando el nmero de funciones y, por tanto, el nmero de lneas de cdigo, se
incrementa. As, la primera tarea consistir en dividir el fichero ordena.c en
varios ficheros que contendrn las funciones definidas.
Las funciones se agruparn en ficheros siguiendo un criterio funcional, es decir,
cada fichero contendr aquellas que estn relacionadas por la funcionalidad que
definen, de manera que representa una determinada tarea de alto nivel. Por ejemplo,
se puede crear un fichero que contenga todas las funciones que implementan los
algoritmos de ordenacin, y aquellas otras auxiliares que slo resulten tiles para el
correcto funcionamiento de las primeras. Los ficheros creados sern:
principal.c. Contiene la funcin main() del ejemplo.
ordena.c. Contiene las funciones que implementan los distintos
algoritmos de ordenacin y algunas auxiliares: llama_ord_rapida(),
ord_rapida(), coloca(), burbuja(), intercambia().
aux.c. Contiene las funciones auxiliares encargadas de tareas varias,
como la creacin del men de la aplicacin, generacin de listas
desordenadas y visualizacin de listas: menu(), desordena_lista(),
muestra_lista().
mem.c. Contiene funciones para la reserva dinmica de memoria:
asignar_mem().
As mismo, se deben crear los ficheros de cabecera correspondientes: ordena.h,
aux.h y mem.h, conteniendo los prototipos de las funciones exportadas, y las
definiciones y declaraciones necesarias para puedan ser utilizadas. De esta manera, en
ordena.h se escriben los prototipos de las funciones que implementan los
algoritmos de ordenacin, a su vez definidas en ordena.c, y que, posteriormente
sern invocadas en principal.c.
Para generar el ejecutable se puede invocar la misma orden, teniendo en cuenta
que, ahora, se tienen cuatro ficheros.
gcc ordena.c mem.c aux.c principal.c -o ordena
As, se compilan los cuatro ficheros, generando sendos ficheros temporales
conteniendo el correspondiente cdigo objeto. A continuacin, la propia orden se
encarga de enlazar los ficheros objeto generados para producir, al final, el ejecutable
ordena. Todo este proceso lo realiza la orden gcc, sin que el usuario se deba
preocupar de los distintos ficheros temporales que se generan. stos son creados y
borrados de forma transparente por el propio gcc, segn sea necesario.
Sin embargo, esta segunda aproximacin sigue siendo poco eficiente, ya que
cualquier cambio del contenido de uno de los ficheros implica la recompilacin de
todos, por lo que no se habra producido ninguna mejora con respecto a la situacin

488

Programacin en C

anterior. Adems, la lnea de rdenes que se debe escribir cada vez que se genera el
ejecutable resulta muy larga y, por tanto, ms propensa a que se cometan errores. Esta
situacin se agrava conforme crece el nmero de archivos fuente que forman el
proyecto.

Figura 15.4. Proceso de generacin del ejecutable ordena

As, se debe realizar la compilacin de cada fichero por separado, para pasar a
continuacin al enlazado de todos los ficheros objeto generados (ver Figura 15.4). La
secuencia de rdenes sera la siguiente:
gcc -c ordena.c
gcc -c mem.c
gcc -c aux.c
gcc -c principal.c
De esta manera se compilan por separado los cuatro ficheros, generando cuatro
ficheros objeto, cuyo nombre coincidir con el del correspondiente fichero fuente
salvo la extensin, que pasa de ser .c a .o. Esto se realiza al invocar gcc con la
opcin -c, que indica que se debe generar el correspondiente fichero con el cdigo
objeto, pero no realizar el enlazado. Para generar el ejecutable se enlazan los cuatro
ficheros objeto generados utilizando, para ello, el mismo comando gcc:
gcc ordena.o mem.o aux.o principal.o -o ordena

Figura 15.5. Proceso de generacin del ejecutable ordena, tras una modificacin en
ordena.c

Si se desea realizar un cambio en uno de los ficheros, por ejemplo, cambiar el


cdigo de uno de los algoritmos de ordenacin, slo se deber recompilar el fichero (o
ficheros) cuyo contenido haya sido modificado, en el ejemplo desarrollado, el fichero

Gestin del cdigo fuente en proyectos software en C

489

ordena.c. As, una vez generado el nuevo fichero objeto correspondiente al fichero
fuente modificado, ordena.o, slo queda enlazarlo con el resto de ficheros objeto
existentes, mem.o, aux.o y principal.o, que no ha sido necesario regenerar
(ver Figura 15.5).
En este caso la secuencia de comandos a ejecutar sera:
gcc ordena.c
gcc ordena.o mem.o aux.o principal.o -o ordena
Como se puede apreciar, no se realiza el proceso de compilacin de los ficheros no
modificados, con el correspondiente ahorro de tiempo.
En cualquier caso, realizar todo el proceso mediante la invocacin directa de la
orden puede dar lugar a gran cantidad de confusiones, sobre todo, cuando el nmero
de ficheros involucrados crece. De hecho, un proyecto de tamao medio suele estar
constituido por un nmero de ficheros bastante elevado, no siendo nada extrao
encontrarse con proyectos con varias decenas o, incluso centenares, de ficheros. Esta
situacin puede resultar muy difcil de gestionar, pudindose dar el caso de no
incorporar cambios realizados, o aparecer errores cuya solucin puede resultar muy
ardua de encontrar.
Una situacin muy tpica es el cambio de alguna variable o prototipo de una
funcin modificada en un fichero fuente o fichero de cabecera (extensin .h). Puede
haber ficheros donde esta funcin o esta variable son utilizadas, luego podra precisar
cambios o, cuando menos, recompilacin de estos ficheros. Si no se realiza, se estara
enlazando cdigo objeto obsoleto, lo que podra implicar importantes errores.

15.1.2. Funcionamiento bsico de MAKE


Tradicionalmente los entornos UNIX han proporcionado una herramienta, make, que
permite gestionar proyectos software complejos, constituidos por mltiples ficheros
fuente, cubriendo todas las tareas tpicas en un proceso de desarrollo de software,
desde la generacin del ejecutable objetivo final del proyecto, hasta la eliminacin de
ficheros no necesarios o la instalacin en un sistema. Aunque asociada a entornos
UNIX tambin se encuentra habitualmente en entornos de desarrollo no UNIX, como
MS Visual C++ para MS Windows.
La orden make parte de la existencia de un fichero, tpicamente makefile o
Makefile (cuidado porque en UNIX se distingue entre maysculas y minsculas),
donde se especifican las dependencias existentes entre los distintos ficheros que
constituyen el proyecto software. La orden make utiliza esta especificacin de
dependencias, as como el atributo de fecha y hora de ltima modificacin de los
diferentes ficheros, para establecer las tareas (recompilacin, enlazado...) que se
deben realizar, y en qu orden, con el objetivo de minimizar el nmero de
recompilaciones.
Aunque se presenta dentro de un contexto de desarrollo de aplicaciones en lenguaje
C, make, se puede utilizar con cualquier lenguaje de programacin que disponga de
un compilador que se pueda invocar desde una lnea de rdenes. De hecho, el uso de
make no se limita al desarrollo de software. Se puede utilizar para describir cualquier

490

Programacin en C

tarea donde algunos archivos se deben actualizar automticamente a partir de otros,


siempre que stos ltimos hayan sufrido algn cambio en su contenido.

Figura 15.6. rbol de dependencias del proyecto ordena

La orden make se basa en las interdependencias entre archivos, en los ficheros


objetivo que se deben generar (ejecutables o ficheros con cdigo objeto) y en las
rdenes que se deben invocar para conseguir los citados ficheros objetivo. Todo esto
se especifica en un fichero makefile en forma de reglas de construccin.
Sea el rbol de la Figura 15.6 el que representa las interdependencias existentes
entre los diferentes ficheros del ejemplo propuesto. Para crear el ejecutable ordena
se precisa la existencia de cuatro ficheros objeto: aux.o, ordena.o, mem.o y
principal.o. Si cualquiera de estos cuatro ficheros se regenera, pasar a tener una
fecha y hora de ltima modificacin posterior a las que tiene el ejecutable ordena,
en cuyo caso se debe regenerar ste ltimo. Si ordena no existe, sera una situacin
anloga, dado que los cuatro ficheros objeto siempre tienen una fecha y hora de
ltima modificacin posterior a la de un fichero inexistente. La regla que indica como
se debe construir este objetivo es:
ordena: principal.o ordena.o mem.o aux.o
gcc principal.o ordena.o mem.o aux.o -o ordena
En la primera lnea se indica el objetivo, palabra ordena antes del smbolo (:), y
a continuacin la lista de dependencias, enumeracin de los nombres de archivo que
deben estar presentes para que se pueda construir el objetivo, separados mediante uno
o varios espacios en blanco. La segunda es la lnea de rdenes que se invocar para
conseguir el objetivo indicado. sta siempre debe ser precedida de un carcter
tabulador. En este caso, se realiza en enlazado de todos los ficheros con el cdigo
objeto del proyecto para generar el ejecutable ordena.
La sintaxis general de una regla es:
lista-objetivos: lista-dependencias
<tabulador>lista-rdenes
Donde lista-objetivos es una lista de archivos objetivo separados por uno o
ms espacios en blanco. En lista-dependencias se enumeran los nombres de
archivo, separados por uno o ms espacios en blanco, de los que depende la
construccin de los archivos objetivo. Por ltimo, lista-rdenes, incluye la lista
de rdenes, separadas mediante un carcter nueva lnea, que se deben ejecutar para
crear los archivos objetivo. Es importante recordar que cada orden debe comenzar con
un carcter tabulador, siendo sta la nica manera que tiene make de distinguirlas de
otro tipo de lneas.

Gestin del cdigo fuente en proyectos software en C

491

Siguiendo con las dependencias expresadas en la Figura 15.6, el fichero


principal.o depende, para su construccin, de los ficheros ordena.h, mem.h,
aux.h y principal.c. Si principal.o no existe, o bien la fecha y hora de
ltima modificacin del mismo es anterior a la fecha y hora de ltima modificacin de
cualquiera de sus dependencias, entonces principal.o debe ser regenerado,
siguiendo la siguiente regla:
principal.o: ordena.h mem.h aux.h principal.c
gcc -c principal.c
En la primera lnea se indican los ficheros de los que depende la creacin de
principal.o, en este ejemplo, los ficheros cabecera que se incluyen en
principal.c, conteniendo declaraciones y definiciones que se utilizan en el
mismo, y el propio fichero principal.c. La segunda, es la lnea de rdenes que se
debe invocar para generarlo, en este caso la compilacin a cdigo objeto del fichero
principal.c. Para ello se invoca gcc con la opcin -c. Resulta obvio que no se
puede realizar un proceso de enlazado, dado que slo se dispone del cdigo objeto
correspondiente a las funciones definidas en principal.c.
Por cada uno de los ficheros objeto que aparecen en la lista de dependencias del
ejecutable a generar debe existir una regla anloga, constituyendo as el conjunto de
reglas que forman parte del makefile que define el proyecto. As, un fichero
makefile bsico que define el proyecto sera:
ordena: principal.o ordena.o mem.o aux.o
gcc principal.o ordena.o mem.o aux.o -o ordena
principal.o: ordena.h mem.h aux.h principal.c
gcc -c principal.c
ordena.o: ordena.h ordena.c
gcc -c ordena.c
mem.o: mem.h mem.c
gcc -c mem.c
aux.o: aux.h aux.c
gcc -c aux.c
Esta sera la descripcin de un fichero makefile muy bsico, sin hacer uso de
muchas de las facilidades que proporciona, y que da a make toda su potencia y
flexibilidad. Por ahora, ser suficiente para ilustrar su funcionamiento bsico.
Una vez se han creado los ficheros con el cdigo fuente y el makefile, tan slo
queda ejecutar la orden, as:
make
No es necesario indicar nada ms, la propia orden supone la existencia de un
fichero con el nombre Makefile o makefile. Si se desea utilizar un archivo con
un nombre diferente, se debe invocar la orden make con la opcin -f seguido del
nombre del archivo:
make -f otro_makefile
En cualquier caso, al ser invocado sin indicar objetivo alguno a conseguir, make
tomar el que primero aparece en el fichero que contiene las reglas. En el ejemplo, la
regla correspondiente al primer objetivo es el la encargada de generar el ejecutable
final, ordena.
La sintaxis de make es:

492

Programacin en C

make [opciones] [objetivos] [definiciones de macros]


Opciones ms frecuentes:
-d
Visualiza informacin de depuracin.
-f archivo
Indica a make que utilice archivo como el
fichero que contiene las reglas.
-h
Muestra un mensaje de ayuda.
-n
No ejecuta ninguna orden del archivo
de reglas; nicamente las visualiza.
-s
Funcionamiento sin mensajes.
Cuando el software en desarrollo crece en tamao, la cadena de dependencias se
hace igualmente larga. Para resolver esta cadena, make crea un rbol que representa
las dependencias, colocando en la raz del mismo el objetivo a conseguir. En el
ejemplo presentado crea un rbol anlogo al de la Figura 15.6. Entonces comienza a
resolver las dependencias expresadas en dicho rbol, partiendo de los nodos finales
del mismo y utilizando, para ello, las reglas adecuadas. De esa manera se van creando
los objetivos intermedios, recorriendo el rbol desde los niveles ms bajos hacia la
raz del mismo, lo que implica la creacin del objetivo final, el ejecutable ordena.
Si se desea invocar una regla para la creacin de un objetivo intermedio, tan slo
debe invocarse en la lnea de rdenes. En el ejemplo, si se desea obtener el objetivo
ordena.o la lnea de rdenes ser:
make ordena.o
Esta orden crear dicho objetivo, satisfaciendo de forma recursiva todas las
dependencias necesarias. El resto de objetivos que no estn implicados en su cadena
de dependencias sern ignorados.
Si por cualquier razn se desea que se regeneren dependencias y objetivos
intermedios que ya estn actualizados, tan slo se debe hacer que el atributo de fecha
y hora de ltima modificacin de los correspondientes ficheros fuente sea posterior a
la del correspondiente objetivo del que son dependencia. La orden ms indicada en
este caso es touch, cuya sintaxis es:
touch [opciones] [lista de archivos]
Opciones ms frecuentes:
-a
Cambia la fecha de acceso del archivo.
-r archivo
Utiliza la fecha de archivo para la
modificacin.
-t [[SS]AA]MMDDhhmm[.ss] Utiliza la fecha indicada en lugar de la
fecha actual.
-m
Cambia la fecha de modificacin.
Siguiendo con el ejemplo, la lnea de rdenes ser:
touch *.c
La cual actualiza la fecha y hora de ltima modificacin de todos los ficheros
fuente, implicando la ejecucin de todas las reglas, como si no existiese ningn
objetivo.

Gestin del cdigo fuente en proyectos software en C

493

El algoritmo que sigue make es:


Si archivo objetivo existe entonces
inicio
si todos lo ficheros de los que depende el objetivo existen entonces
inicio
si la fecha y hora de ltima modificacin del archivo
objetivo es posterior a la fecha y hora de ltima modificacin
de todos los archivos de los que depende entonces
inicio
El archivo objetivo existe y est actualizado, no se hace nada
fin
si no
inicio
Se ejecuta la regla asociada para obtener dicho objetivo
fin
fin
si no
inicio
para cada dependencia que no existe hacer
inicio
convertir el archivo dependencia en archivo objetivo
aplicar recursivamente este mismo algoritmo
fin
Se ejecuta la regla asociada para obtener dicho objetivo
fin
si no
inicio
si todos los ficheros de los que depende el objetivo existen entonces
inicio
Se ejecuta la regla asociada para obtener dicho objetivo
fin
si no
inicio
para cada dependencia que no existe hacer
inicio
convertir el archivo dependencia en archivo objetivo
aplicar recursivamente este mismo algoritmo
fin
Se ejecuta la regla asociada para obtener dicho objetivo
fin
fin

15.1.3. Facilidades que proporciona MAKE


Hasta ahora se ha introducido el funcionamiento bsico de make, suficiente para
crear proyectos sencillos e, incluso, de un tamao ya considerable. Sin embargo,
make proporciona una serie de facilidades que permiten una gestin ms flexible y
sencilla de proyectos, incluyendo los de grandes dimensiones.

494

Programacin en C

Para empezar, como en todo fichero que se crea en un proyecto software, es


conveniente incluir comentarios en el fichero de reglas (makefile) que ayuden a
una mejor comprensin sobre cmo est construido el mismo. Los comentarios en los
ficheros de reglas son lneas que comienzan con el carcter (#). Es importante tener
presente que se trata de un comentario de lnea, por lo que si el comentario que se
desea incluir consta de varias lneas, cada una de ellas debe ser precedida del carcter
indicado.
#Este es un ejemplo de un comentario en un fichero makefile
#con ms de una lnea

Otra caracterstica muy til es la posibilidad de utilizar macros, que permiten


realizar sustituciones sencillas de cadenas de caracteres. Son muy similares a las
macros en C, siguiendo incluso la misma norma de utilizar identificadores en
maysculas. Se deben declarar antes de ser utilizadas, generalmente al comienzo del
archivo de reglas. La sintaxis es:
IDENTIFICADOR_MACRO = cadena de caracteres
O tambin:
define IDENTIFICADOR_MACRO
cadena de caracteres
endef
Una vez se ha definido una macro en un archivo de reglas, cadena de
caracteres sustituir cada aparicin en el resto del archivo de:
$(IDENTIFICADOR_MACRO)
El ejemplo que se est desarrollando tendr este makefile:
#El ejemplo se completa utilizando
#comentarios y definicin de macros
OBJS = principal.o aux.o mem.o ordena.o
CC = gcc
OPS = -O -c
ordena : $(OBJS)
$(CC) $(OBJS) -o ordena
principal.o : principal.c aux.h mem.h ordena.h
$(CC) -c principal.c
aux.o : aux.c aux.h
$(CC) -c aux.c
mem.o : mem.c mem.h
$(CC) -c mem.c
ordena.o : ordena.c ordena.h
$(CC) -c ordena.c
Al igual que ocurre con las macros en C, en un fichero makefile se utilizan para
poder sustituir fcilmente cadenas de caracteres. Por ejemplo, la macro definida como
CC identifica la orden que se utiliza como compilador. De esa manera, si se cambia
dicha orden, slo es necesario modificar la definicin de la macro. Otro ejemplo
tpico es OBJS, que se define como una cadena que contiene todos los ficheros

Gestin del cdigo fuente en proyectos software en C

495

objeto que se van a enlazar para conseguir el objetivo por defecto. De esta manera se
evitan posibles errores al no tener que repetir dos veces esta cadena.
Tambin se dispone de un conjunto de macros internas, ya predefinidas (ver Tabla
15.1), y que se pueden utilizar en la lnea de rdenes. Adems, si la lnea de rdenes
que comienza con el carcter (@) sta no tendr eco en consola.
Macro interna
Significado
$@
Nombre del objetivo actual.
$?
La lista de dependencias que han cambiado ms recientemente
que el objetivo actual.
$<
Nombre de la dependencia actual que ha cambiado ms
recientemente que el objetivo actual.
$^
Lista de todas las dependencias.
Tabla 15.1. Macros internas predefinidas por make

Cuando la cadena de caracteres que se debe escribir es muy larga y se desea dividir
en varias lneas, para una mejor lectura, se debe utilizar el carcter (\). Siguiendo con
el ejemplo:
OBJS = principal.o aux.o
\
mem.o ordena.o
CC = gcc
OPS = -O -c
SOURCES = principal.c aux.c
\
mem.c ordena.c
HEADERS = mem.h aux.h ordena.h
ordena : $(OBJS)
$(CC) $^ -o $@
@ echo Construccin terminada
principal.o : principal.c aux.h mem.h ordena.h
aux.o : aux.h
mem.o : mem.h
ordena.o : ordena.h
proyecto.tar : $(SOURCES) $(HEADERS) makefile
tar -cvf proyecto.tar $^
clean:
rm *.o
Se puede apreciar que hay bastantes cambios. El primero aparece en la primera
regla y consiste en la utilizacin de la macro interna $^, que ser sustituida por la
cadena de dependencias en la regla actual, $(OBJS), que a su vez es otra macro, a
sustituir por su definicin. En cuanto a la otra macro interna, $@, ser sustituida por el
nombre del objetivo en la regla actual, ordena.
Las reglas para la generacin de los ficheros objeto carecen de lnea de rdenes.
Esto se debe a que make, adems de macros internas, tiene reglas y macros
predefinidas, que evitan redundancias. Una regla predefinida invoca la orden $(CC)
-c xxx.c -o xxx.o para todos los objetivos que tengan la extensin .o y no
exista una regla especificando como crearlo, como sucede en el ejemplo. Es ms, no
es necesario incluir el fichero fuente en las dependencias, ya que make supone que

496

Programacin en C

existe un fichero con el mismo nombre que el objetivo, pero con extensin .c, y ste
ser el que utilice. En cuanto a macros predefinidas, CC tiene como valor predefinido
el nombre del compilador por defecto, y CFLAGS las opciones del compilador por
defecto. Estas definiciones se pueden modificar, como ocurre en el ejemplo con CC.
Por ltimo, existe ms de un objetivo: proyecto.tar y clean. Estos objetivos
no se llevarn a cabo, ya que el objetivo que se crea por defecto no depende de ellos.
Para que se cumpla cualquiera de ellos se debe invocar make con la indicacin
expresa de que as se haga, como se muestra a continuacin:
make proyecto.tar
make clean
Entonces se salta la regla correspondiente al objetivo por defecto, ordena, y se
ejecuta la correspondiente a proyecto.tar o clean, respectivamente. El primero
de ambos construye un fichero, proyecto.tar, que empaqueta todos los archivos
fuente del proyecto, incluyendo el propio makefile, y utilizando para ello la orden
tar. El segundo borra todos los archivos objeto existentes en el directorio.

15.1.4. Objetivos ficticios


Un objetivo ficticio (phony tarjet) es aqul que no se corresponde con el nombre de
un fichero. Se trata de un identificador que sirve para poder lanzar una orden o un
conjunto de rdenes mediante su invocacin explcita. Hay dos razones para utilizar
un objetivo ficticio:
Evitar un conflicto con un fichero con el mismo nombre.
Incrementar el rendimiento.
Si se escribe una regla cuyas rdenes no crean un fichero objetivo, dichas rdenes
se ejecutarn cada vez que se invoque explcitamente dicha regla. Volviendo al
ejemplo, las lneas que se corresponden con esta situacin seran:
clean:
rm *.o
Dado que la orden rm no crea un fichero con el nombre clean, no debera existir
ninguno con dicho nombre. Por tanto, cada vez que se invoca la orden make clean,
se ejecutar la orden rm, borrando todos los archivos cuya extensin sea .o.
Si por cualquier circunstancia existiera un archivo, con nombre clean, y dado que
esta regla carece de lista de dependencias, ficheros con cuyos atributos fecha y hora
de ltima modificacin no se podrn hacer correspondiente comparacin, make
concluye que el objetivo existe y est actualizado, y por consiguiente nunca se
ejecutar la regla asociada.
Para evitar esta situacin, se puede declarar el objetivo ficticio de forma explcita.
Para ello se utiliza un fichero objetivo especial y predefinido en make, .PHONY, de la
siguiente manera:
.PHONY: clean
As, la invocacin a la orden make clean, ejecutar las rdenes asociadas a la
correspondiente regla, independientemente de exista o no un fichero llamado clean.
Al tratarse de un objetivo ficticio, make sabe que clean no identifica a un fichero
real que deba ser reconstruido o actualizado a partir de otros ficheros y, por tanto, no

Gestin del cdigo fuente en proyectos software en C

497

ejecuta la correspondiente regla predefinida. Busca la regla que se debe lanzar para
cumplir la dependencia y la lanza. De manera definitiva, el fichero Makefile del
ejemplo debera contener las lneas:
.PHONY: clean
clean
rm *.o
Otros objetivos predefinidos se recogen en la Tabla 15.2.
.PHONY
Los requisitos que aparecen en la correspondiente lista de este
objetivo se consideran objetivos ficticios. Cuando estas dependencias
se consideran como objetivos, las rdenes asociadas a las
correspondientes reglas se ejecutan de forma incondicional, exista o
no un fichero con el mismo nombre y, si existe, independientemente
de los atributos de fecha y hora de ltima modificacin.
.SUFFIXES La lista de dependencias es una lista de sufijos (extensiones) que se
utilizarn en el chequeo de reglas de sufijos.
.DEFAULT

La lista de rdenes especificados con este objetivo especial


(predefinido) es la que se utilizar como la correspondiente lista de
rdenes para aquellos objetivos que carezcan de una regla, bien
definida por el programador o bien predefinida.
Tabla 15.2. Algunos objetivos predefinidos (especiales)

La orden make tiene muchas ms opciones y caractersticas, de hecho es una


herramienta realmente potente y flexible, permitiendo crear y gestionar proyectos
software de gran complejidad y tamao de manera muy sencilla. Por otro lado, y
dadas las caractersticas de este libro, no se va a entrar ms en profundidad todas las
posibilidades que ofrece, limitando las explicaciones dadas a un uso bsico de la
herramienta, pero suficiente para poder crear proyectos que implementen los ejemplos
y ejercicios propuestos en este libro e, incluso, de mayor envergadura. Si el lector
desea profundizar ms en el conocimiento de esta herramienta se recomienda la
lectura de la bibliografa utilizada para elaborar este captulo.

15.2. Entornos de Desarrollo Integrado


Los Entornos de Desarrollo Integrado, EDIs, (en ingls IDE, Integrated Development
Environment) permiten realizar una gestin de un proyecto software, constituido por
un grupo de ficheros fuente, documentacin..., similar a la vista con la herramienta
make, pero utilizando una interfaz grfica de usuario (Graphical User Iterfaz, GUI)
lo que facilita, de manera significativa, la tarea de desarrollar software.
De hecho, en muchos casos estos EDIs no son ms que herramientas frontales
(front end) de las rdenes utilizadas en un entorno de desarrollo no grfico, como el
compilador, el depurador o el mismo make. A esto se le aaden otras facilidades
como pueden ser:
Un editor avanzado, con resaltado de la sintaxis mediante coloreado,
autoindentacin (incluso con estilos diferentes), bsqueda avanzada,
identificacin de mdulos (subrutinas o funciones)...

498

Programacin en C

Un navegador que permite acceder rpidamente a ficheros, funciones,


variables o clases.
Una ventana que muestra el resultado de la ejecucin de las diferentes
rdenes
Un depurador (debugger) que permite realizar las tareas de depuracin del
programa utilizando el cdigo fuente original. Se puede controlar la
ejecucin del programa fijando puntos de parada en el cdigo fuente, se
pueden observar los valores de distintas variables utilizando los nombres
de las propias variables, y se puede ejecutar el programa paso a paso,
observando el contenido de la pila de llamadas del programa. Antes de
utilizarlo hay que asegurarse de que el programa est compilado para usar
sesin de depuracin1. Para que funcione el depurador, es preciso incluir
informacin adicional y especfica para la depuracin en el archivo
ejecutable del programa.
En el mercado existe una amplia variedad de EDIs, pudiendo incluso encontrar el
mismo producto en diferentes sistemas operativos, como ocurre con el de Borland
(http://www.borland.com) que dispone de versiones de su Borland C++ Builder para
MS Windows y la correspondiente versin para GNU/Linux denominada Kylix en su
versin de C++. No es objeto de este libro el mostrar en detalle el funcionamiento de
un EDI particular, ms bien dar unas pautas generales que permitan al lector sacar el
mximo partido del EDI con el que trabaja. Para ello se mostrar como crear un
proyecto, nuevamente elemento central de desarrollo del software, como aadir
archivos fuente al mismo, como compilar y generar ejecutables, y como lanzar y
utilizar el depurador, con dos EDIs: MS Visual C++ (http://www.microsoft.com) y
KDevelop (http://www.kde.org, http://www.kdevelop.org).
El primero, representante claro del esfuerzo de Microsoft por liderar tambin este
mercado, es uno de los ms ampliamente utilizado dentro de las herramientas
propietarias. Prueba significativa de que Microsoft tiene gran inters en promocionar
su utilizacin es la poltica especial de licencias que aplica, con importantes
descuentos para estudiantes y convenios especiales para las Universidades.
El segundo EDI, KDevelop, es fruto del esfuerzo por parte de un grupo de
desarrolladores de dotar a los entornos GNU/Linux y, por extensin, cualquier UNIX,
de una herramienta de desarrollo amigable e intuitiva, en el marco de un entorno de
escritorio2 denominado KDE (http://www.kde.org). El proyecto fue iniciado en 1998
y actualmente es uno de los ms ampliamente utilizados en el entorno GNU/Linux y
UNIX en general. Se trata de una herramienta frontal (front end), que utiliza las
rdenes ya estudiadas en este mismo captulo (gcc, make) y otras que ayudan a
gestionar todo el proyecto (autotools), todo ello integrado en una interfaz grfica
muy amigable y sencilla de manejar. A diferencia de MS Visual C++, KDevelop es
una herramienta software libre, lo que implica, entre otras cuestiones, libre de costes.
1

En GNU C y, en general, en los compiladores C sobre entornos UNIX la opcin suele ser g.
En los EDIs se debe buscar el correspondiente cuadro de dilogo de compilacin que permitir
marcar dicha opcin.
2 Entorno de escritorio en el sentido ms amplio del trmino. Incluye un amplio abanico de
aplicaciones que van desde una simple calculadora, a un gestor de archivos, navegador Web, o,
incluso, procesador de texto, hoja de clculo y herramienta de presentaciones.

Gestin del cdigo fuente en proyectos software en C

499

Existen otras alternativas como, por ejemplo, Anjuta (http://anjuta.sourceforge.org)


pero en el momento de escribir estas lneas el entorno de escritorio y desarrollo en el
que est englobado, GNOME (http://www.gnome.org), se encuentra en un proceso de
profunda renovacin, incluyendo la reimplementacin de grandes porciones de cdigo
y bibliotecas bsicas, con lo que no ha sido posible probar la nueva versin de esta
herramienta.

15.2.1. Creacin de un proyecto software


sta es la primera tarea a realizar. Una vez arrancado el entorno de desarrollo,
generalmente se presenta al desarrollador un cuadro de dilogo o un asistente que le
ayuda en la tarea, que implica varios aspectos:
Eleccin del tipo de proyecto, esto es, un ejecutable en modo texto,
ejecutable con interfaz grfica de usuario, biblioteca de carga dinmica...
Especificacin de la ubicacin de los archivos que forman el proyecto.
En su caso, bibliotecas externas que se deben utilizar en el desarrollo.

Figura 15.7. Creacin de un nuevo proyecto en MS Visual C++

En la Figura 15.7 se puede apreciar la interfaz presentada, los datos solicitados al


desarrollador por el asistente de MS Visual C++ son:
Tipo de aplicacin, los ejemplos presentados en este libro son de tipo
Win32 Console Application, es decir, aplicaciones sin interfaz grfica o,
como se suelen denominar, modo texto o consola. Si se desea crear una
aplicacin con interfaz grfica el tipo de proyecto sera Win32

500

Programacin en C

Application. Se puede apreciar el gran nmero de tipos de software que


se pueden generar.
Nombre del proyecto y, por extensin, del ejecutable que se acabar
generando.
Ubicacin de los ficheros que constituyen el proyecto.
Creacin del espacio de trabajo (Workspace). En el mtodo de trabajo de
MS Visual C++ se consideran distintos niveles de desarrollo, por un lado
el espacio de trabajo que rene proyectos relacionados entre s. El
proyecto tiene como objetivo la creacin de un objeto, como por ejemplo
un ejecutable. Siempre que se crea un proyecto se debe hacer dentro de un
espacio de trabajo, bien existente, bien creado por defecto a la vez que el
proyecto.
Esta informacin ser suficiente para que MS Visual C++ genere todos los
archivos necesarios, incluyendo, si fuera necesario, un pequeo esqueleto de la
aplicacin que se desea desarrollar (Figura 15.8).

Figura 15.8. Creacin de un esqueleto bsico de la aplicacin (si se desea)

Una vez se ha terminado, la aplicacin muestra una ventana (Figura 15.9) con:
La correspondiente barra de mens, con todas las opciones que ofrece la
aplicacin. Las tpicas como File, Edit y las especficas como Project con
todas las opciones relacionadas con la gestin del proyecto (aadir
archivos nuevos o ya existentes, por ejemplo), o la opcin Build con todas
las opciones relacionadas con las tareas de compilacin, enlazado y
generacin de ejecutables.

Gestin del cdigo fuente en proyectos software en C

501

Un navegador de proyecto (panel a la izquierda), con varias pestaas


(navegador de archivos del proyecto, navegador de clases). Ambos
permiten acceder a archivos o a variables, funciones y clases de forma
rpida y sencilla, con una sola pulsacin de ratn.
Un panel en la parte inferior con varias pestaas. Cada una est asociada a
una ventana de salida de informacin. Por ejemplo, la pestaa Build
muestra la ventana que imprimir los mensajes que se producen durante
los procesos de compilacin y enlazado, la pestaa Debug muestra una
ventana con los mensajes que producen en el proceso de depurado, y as
sucesivamente.
Por ltimo, un espacio vaco donde se irn mostrando las ventanas del
editor de texto integrado con el cdigo fuente de cada fichero que se est
editando. Cada una de estas ventanas aparece dentro de la ventana
principal del entorno, en una tpica disposicin de documento mltiple
MDI (Multiple Document Interface).

Figura 15.9. Ventana principal MS Visual C++

En este momento se puede empezar a escribir el cdigo fuente del programa, sin
ms que empezar a aadir archivos, bien de nueva creacin, bien ya existentes, todo
desde la misma opcin de men: Project -> New o Project -> Files respectivamente.
En el entorno KDevelop la tarea de creacin de un proyecto es muy similar. De
hecho la apariencia que presentan ambos entornos es muy parecida, por no decir
idntica. Cuando se instala KDevelop ejecuta un pequeo programa de configuracin
(KDevelop Setup) que permite proporcionar al entorno datos como rutas donde

502

Programacin en C

encontrar documentacin, bibliotecas de funciones, configuracin del editor de


texto... Una vez ha finalizado este asistente, se inicia KDevelop con una interfaz,
como ya se ha comentado, muy parecida a la MS Visual C++. De hecho, una vez se
ha seleccionada la opcin File -> New Project arranca un asistente (Figura 15.10) en
el que se solicita:
Tipo de proyecto. Nuevamente se debe tener en cuenta si se quiere crear
una aplicacin de consola (como ya se indic todos los ejemplos de este
libro son de este tipo) o una aplicacin con interfaz grfica, disponiendo
entre distintas alternativas en este ltimo caso. Tambin se debe
especificar el lenguaje C o C++.
En un segundo paso del asistente (Figura 15.11) se solicita informacin
sobre la ubicacin de los ficheros del proyecto e informacin sobre el
autor que aparecer en los ficheros de cabecera (.h) y los ficheros fuente
(.c).
En los siguientes pasos se configura el acceso al sistema concurrente de
versiones o CVS (Concurrent Version System) y las plantillas para los
ficheros de cabecera y fuente que se generen.

Figura 15.10. Asistente de KDevelop solicitando tipo de proyecto

Gestin del cdigo fuente en proyectos software en C

503

Figura 15.11. Asistente de KDevelop solicitando informacin nombre y ubicacin del proyecto

Una vez finalizado el asistente, KDevelop se encarga de generar todos los ficheros
necesarios, incluyendo informacin sobre la aplicacin en desarrollo (sendos ficheros
de texto LEEME, README), y de gestin del proyecto basado en autogen y
autoconf (autotools), adems de ficheros fuente que constituyen un esqueleto
bsico de la aplicacin que se desea desarrollar. La apariencia de KDevelop una vez
finalizado este proceso se puede apreciar en la Figura 15.12. Como se puede ver se
trata, de nuevo, de una interfaz basada en documento mltiple con paneles anlogos a
los que presenta MS Visual C++, por lo que no se volver a repetir la descripcin.
Para aadir nuevos archivos de cdigo fuente, bien de nueva creacin, bien
archivos ya existentes, al proyecto recin creado se selecciona la opcin de men
Proyecto -> Aadir archivos, o si es distinto para los nuevos. Una vez aadido el
archivo se podr comenzar con la edicin.

15.2.2. Edicin del cdigo fuente


Ambos entornos proporcionan sendos editores de texto muy sofisticados, con gran
cantidad de opciones y apoyos a la edicin de cdigo fuente. Por ejemplo, coloreado
del texto segn sintaxis del lenguaje (syntax highlighting), autoindentancin,
bsqueda avanzada, bsqueda y sustitucin de texto

504

Programacin en C

Figura 15.12. Ventana principal y gestin de proyectos en KDevelop

La Figura 15.13 y la Figura 15.14 muestran ambas aplicaciones en pleno proceso


de edicin del cdigo fuente del mismo ejemplo. En ambos casos se puede acceder a
las distintas partes del cdigo fuente bien directamente, en la ventana del editor que
contiene el fichero, bien a travs del navegador (panel izquierdo en ambos casos).
Segn la pestaa seleccionada en el mismo, se accede directamente a un archivo que
forma parte del proyecto, o a la declaracin de una variable o a la definicin de una
funcin. Todo con un nico clic de ratn.
No se entra en una descripcin profunda de las posibilidades de los editores
porque, adems de no ser un aspecto que necesite muchas explicaciones, la habilidad
en el manejo slo se puede adquirir con la prctica, por lo que se recomienda al lector
que haga un esfuerzo en esta materia, implementando los ejemplos y ejercicios
propuestos en este libro.

15.2.3. Construccin de un ejecutable


Ambos entornos cuentan con una opcin de men Construir (Build) que contiene
todas las opciones relativas a compilacin, individual de un fichero o en conjunto de
todos los ficheros fuente que forman el proyecto, enlazado y generacin del objetivo
final que, generalmente, ser un ejecutable (Figura 15.15, Figura 15.16). Tambin es
posible lanzar la ejecucin del binario generado, arrancando, si fuera necesario, una
consola (de MS DOS en el caso de MS Visual C++ o de UNIX en el caso de
KDevelop) donde mostrar dicha ejecucin.

Gestin del cdigo fuente en proyectos software en C

505

Figura 15.13. Edicin de cdigo fuente en MS Visual C++

Figura 15.14. Acceso a travs del navegador de KDevelop a la funcin burbuja en el cdigo
fuente

506

Programacin en C

Figura 15.15. Opciones de construccin de MS Visual C++

Figura 15.16. Opciones de construccin en KDevelop

Gestin del cdigo fuente en proyectos software en C

507

As, una vez que se ha escrito el cdigo fuente de la aplicacin en desarrollo, se


puede:
Elegir la opcin de compilar un fichero individualmente seleccionado
Build -> Compile fichero.c (en el caso de MS Visual C++) o Construir ->
Compilar (en el caso de KDevelop) y as observar, dentro de la ventana
Build (panel inferior del MS Visual C++) o Mensajes (panel inferior de
KDevelop), los errores sintcticos que se han podido cometer.
Generar el ejecutable, seleccionando Build -> Build ejecutable.exe (en el
caso de MS Visual C++) o Construir -> Construir ejecutable (en el caso
de KDevelop). Esta opcin compila todos los ficheros fuente que sean
ms actuales que los objeto generados en una construccin anterior (de
forma anloga a como hace make), los enlaza y genera el ejecutable.
Generar el ejecutable, igual que en el caso anterior, y ejecutarlo,
seleccionando Build -> Execute ejecutable.exe (en el caso de MS Visual
C++) o Construir -> Ejecutar (en el caso de KDevelop). En ambos
entornos, si el ejecutable generado es de modo consola se lanza una
consola de modo texto y se ejecuta en la misma el ejecutable generado.
Ver figura Figura 15.17.

Figura 15.17. Generacin y ejecucin de la aplicacin en desarrollo en MS Visual C++

Es importante que el desarrollador preste atencin a todos los mensajes que recibe
por parte del entorno en los procesos de compilacin y enlazado. De hecho, ambos
entornos suelen indicar el punto donde se ha producido el error, en el propio cdigo

508

Programacin en C

fuente dentro de la ventana de edicin, sin ms que seleccionar el correspondiente


mensaje de error en la ventana de Build (MS Visual C++) o Mensajes (KDevelop).

15.2.4. Utilizacin del depurador


Otro aspecto relevante de estos entornos es la integracin de un depurador. ste
permite al desarrollador ejecutar la aplicacin de forma controlada, pudiendo realizar
dicha ejecucin paso a paso, detenerla en puntos concretos del cdigo, examinar el
contenido de variables utilizando los nombres de las propias variables, observar el
contenido de la pila del programa, contenido de registros del procesador... Y todo ello
utilizando el cdigo fuente original. El depurador relaciona automticamente el
cdigo objeto compilado que est asociado a cada lnea del programa con el cdigo
fuente que le corresponde.
Pero para poder utilizarlo, tal y como se ha indicado, el cdigo fuente se debe
compilar con las opciones de depuracin activadas. Estas opciones introducen en el
cdigo objeto generado los smbolos y cdigo necesarios para que se puedan realizar
todas las tareas anteriormente descritas. La opcin de Project -> Settings (MS Visual
C++), o Proyecto -> Opciones (KDevelop) abre un cuadro de dilogo donde se
puede activar, entre otras, esta opcin de compilacin (Figura 15.18).

Figura 15.18. Configurando el proyecto en KDevelop para que inserte informacin de depuracin

Es preciso recordar que el cdigo generado incluye smbolos y cdigo adicional


que ralentiza el programa, adems de producir binarios de mayor tamao. Una vez ya
se ha depurado el programa, y se desea generar la versin definitiva del proyecto en
desarrollo se deben desactivar estas opciones de compilacin.
Para arrancar el compilador no hay ms que seleccionar Build -> Start debug
(MS Visual C++) o Depurar -> Iniciar (KDevelop). En ese momento se disponen de
nuevas opciones (Figura 15.19):

Gestin del cdigo fuente en proyectos software en C

509

Ejecutar la aplicacin normalmente, pero bajo el control del depurador. Si


se produce un error de ejecucin, el depurador detiene la misma,
marcando la ltima lnea de cdigo fuente que se ha ejecutado,
proporcionando al desarrollador la fuente posible del error producido.
Marcar puntos de ruptura (breakpoints) y lanzar la ejecucin de la
aplicacin. En este caso el depurador detiene dicha ejecucin en cada uno
de los puntos de ruptura que se han marcado, de manera que pueda
examinar el contenido de variables y estructuras de datos en ese punto,
comprobando si se corresponde con los valores esperados.
Ejecutar paso a paso (step by step). No hay mucho que explicar, en vez de
ejecutar automticamente todas las instrucciones, una tras otra, es el
desarrollador el que, pulsando un simple botn, indica al depurador que se
ejecute una instruccin, y despus otra, y otra, y as sucesivamente. No es
necesario entrar en todas las funciones, una invocacin se puede
considerar como una instruccin simple y saltar a la siguiente.
Visualizar en todo momento del contenido de variables, locales y
globales, parmetros, registros del procesador, pila del programa

Figura 15.19. Depurador integrado en KDevelop, con las opciones de men y un punto de ruptura
marcado (breakpoint)

Todas estas opciones, y algunas ms, estn disponibles en el correspondiente men


de depurador. Tambin se dispone de una barra de herramientas, justo debajo de la
barra de men, conteniendo botones que implementan las principales acciones
disponibles en el men del depurador.

510

Programacin en C

15.3. Conclusiones
En este captulo se han presentado, por un lado, la necesidad de modularizar el
cdigo, algo en lo que ya se haba insistido en captulos anteriores. Por otro lado, se
han presentado herramientas que ayudan al desarrollador en sus tareas.
A la hora de dividir el cdigo en diferentes archivos de cdigo fuente es importante
tener presentes las reglas de mbito de C. Existen especificadotes de almacenamiento,
static, extern, que ayudan a resolver cualquier problema en este sentido, tanto al
manejar variables globales como funciones. Tambin es importante utilizar
adecuadamente los archivos de cabecera (.h), donde se deben realizar las
definiciones y declaraciones que se desea compartir entre diferentes archivos de
cdigo fuente.
En cuanto a las herramientas de apoyo, se presentan dos tipos: sin interfaz grfica
(lo que no implica necesariamente complejidad, ni que estn faltas de potencia, de
hecho algunas con interfaz grfica no son ms que recubrimientos de stas) como
make. Existen otras ms potentes (autotools) pero ms complejas de utilizar. El
otro tipo, con interfaz grfica, los entornos integrados de desarrollo, EDI. De stos
ltimos se han presentado algunas caractersticas relevantes de dos de los ms
conocidos, por un lado MS Visual C++, en el mbito comercial (propietario, de pago),
por otro lado KDevelop en el mbito del software libre (entornos GNU/Linux y otros
UNIX, y libre de pago de licencias).
Es importante recordar al lector que se han tomado como ejemplo ilustrativo estos
entornos, pero existen ms, tanto de pago como gratuitos y tambin software libre3, y
en cualquier plataforma, MS Windows, GNU/Linux y otros UNIX. Las plataformas
Apple (http://www.apple.com) cuentan en su nuevo sistema operativo, Mac OS X,
con un excelente de entorno de desarrollo, con un funcionamiento similar al estudiado
en este captulo, y que se proporciona gratuitamente junto con el propio sistema. An
as se debe tener presente que este sistema es un UNIX, y como tal tambin se puede
utilizar KDevelop.
Por lo tanto, debe ser el desarrollador quien elija el entorno que mejor se adapte a
sus necesidades, a las caractersticas del proyecto que se desea desarrollar... La
interfaz que presentarn estas herramientas ser siempre muy parecida a la estudiada,
con las mismas opciones, o similares. Sin embargo, siempre se puede recurrir a la ms
bsica y existente en todas las plataformas, propietarios y libres: GNU C, GNU Make
y el editor que ms le guste, vi, emacs

15.4. Bibliografa
A la hora de desarrollar el presente captulo se han consultado diferentes fuentes
bibliogrficas entre las que cabe destacar las siguientes:
KDevelop. Ayuda en lnea de KDevelop.
3

Software libre no significa software gratis. Significa que se debe distribuir, junto con los
binarios, el cdigo fuente. Generalmente el software libre suele estar tambin libre del pago
de licencias.

Gestin del cdigo fuente en proyectos software en C

511

Microsoft. Visual C++ Home. http://msdn.microsoft.com/visualc/.


[ltima vez visitado, 9-9-2004].
Nelly, M. (2002). Roundup. C++ IDEs. LINUX Format. N 35, pp. 3643. http://www.linuxformat.co.uk/archives/LXF35.roundup.pdf. [ltima
vez visitado, 9-9-2004].
Sarwar, S. M., Koretsky, R., Sarwar, S. A. (2003). El Libro de Linux.
Addison-Wesley. ISBN: 847829060-5.
Stallman, R. M., McGrath. (2002). GNU Make. A program for
Directing Recompilation. Free Software Foundation. ISBN: 1-882114-825. Versin en lnea (2000) GNU make Version 3.79 disponible en
http://www.gnu.org/manual/make-3.79.1/html_chapter/make_toc.html.
[ltima vez visitado, 9-9-2004].

You might also like