PROGRAMACION C

Gu´ıa Pr´actica de Programaci´on en C Septiembre de 2012 Rafael Llobet Azpitarte EDITORIAL ` ` UNIVERSITAT POLITECNICA

Views 86 Downloads 2 File size 1MB

Report DMCA / Copyright

DOWNLOAD FILE

Recommend stories

Citation preview

Gu´ıa Pr´actica de Programaci´on en C Septiembre de 2012

Rafael Llobet Azpitarte

EDITORIAL ` ` UNIVERSITAT POLITECNICA DE VALENCIA

Colección Académica

Para referenciar esta publicación utilice la siguiente cita: LLOBET-AZPITARTE, R.(2012). Guía práctica de programación en C. Valencia : Editorial Universitat Politècnica .

Primera edición 2012 © Rafael Llobet- Azpitarte © todos los nombres comerciales, marcas o signos distintivos de cualquier clase contenidos en la obra están protegidos por la Ley. © de la presente edición: Editorial Universitat Politècnica de València www.editorial.upv.es Distribución: [email protected] Tel. 96 387 70 12

Imprime: By print percom sl. ISBN: 978-84-8363-945-0 Impreso bajo demanda Ref. editorial: 298 Queda prohibida la reproducción, distribución, comercialización, transformación, y en general, cualquier otra forma de explotación, por cualquier procedimiento, de todo o parte de los contenidos de esta obra sin autorización expresa y por escrito de sus autores. Impreso en España

´Indice general ´Indice general

I

1 Introducci´on a la computaci´on

1

1.1 Tratamiento autom´atico de la informaci´on . . . . . . . . . . . . . . . . . . . . . . . . . .

1

1.2 Codificaci´on de la informaci´on: el sistema binario . . . . . . . . . . . . . . . . . . . . .

2

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

4

1.4 El proceso de la programaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5

1.5 Lenguajes de programaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6

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

7

1.7 Compiladores e int´erpretes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8

2 Elementos b´asicos de un programa en C

9

2.1 Estructura b´asica de un programa en C . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9

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

10

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

11

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

11

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

12

2.3.3 Uso de variables: declaraci´on y asignaci´on . . . . . . . . . . . . . . . . . . . . . . . .

13

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

16

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

17

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

19

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

21

2.7.1 Expresiones aritm´eticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

21

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

23

I

´ Indice general

2.7.3 Expresiones l´ogicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

23

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

24

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

24

2.8.2 Conversi´on de tipos: casting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

26

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

28

2.9.1 Declaraci´on. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

2.9.2 El operador de direcci´on & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

2.9.3 El operador de indirecci´on * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

29

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

30

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

31

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

31

2.10.2 Definici´on de constantes: #define. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

32

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

33

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

34

3 Estructuras de control 3.1 Sentencias de selecci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

35

3.1.1 Selecci´on con if-else. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

35

3.1.2 Selecci´on con switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

39

3.2 Sentencias de repetici´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

42

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

42

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

44

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

47

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

50

3.3 Algunas t´ecnicas u´ tiles: contadores, acumuladores y banderas . . . . . . . . . . . . . .

51

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

51

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

52

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

53

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

55

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

57

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

57

4 Funciones

II

35

61

4.1 Introducci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

61

4.2 Funciones de la librer´ıa de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

61

´ Indice general

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

63

4.3.1 Cuestiones sint´acticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

65

4.3.2 Control de flujo y transferencia de la informaci´on. . . . . . . . . . . . . . . . . . . . .

67

4.3.3 Par´ametros y argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

69

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

70

4.3.5 Prototipos de funciones y ficheros de cabecera . . . . . . . . . . . . . . . . . . . . . .

72

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

73

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

76

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

76

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

77

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

79

4.6.1 Funciones que no devuelven ning´un valor . . . . . . . . . . . . . . . . . . . . . . . . .

79

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

80

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

81

4.6.4 Funciones que devuelven m´as de un valor . . . . . . . . . . . . . . . . . . . . . . . . .

82

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

84

5 Vectores

87

5.1 Introducci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

87

5.2 Declaraci´on de vectores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

88

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

89

5.4 Operaciones con vectores: automatizaci´on mediante bucles. . . . . . . . . . . . . . . .

90

5.5 Paso de vectores a una funci´on. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

92

5.6 Devoluci´on de vectores en una funci´on. . . . . . . . . . . . . . . . . . . . . . . . . . . .

98

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

98

5.8 Operaciones con matrices: automatizaci´on mediante bucles . . . . . . . . . . . . . . .

99

5.9 Paso de matrices a una funci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 5.10 Relaci´on entre vectores y punteros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 5.11 Cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 5.11.1 Entrada y salida con cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . 106 5.11.2 Funciones de manipulaci´on de cadenas de caracteres . . . . . . . . . . . . . . . . . . 109

5.12 Ejercicios resueltos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 5.13 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114

III

´ Indice general

6 Estructuras

117

6.1 Introducci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 6.2 Declaraci´on de variables de tipos estructurados . . . . . . . . . . . . . . . . . . . . . . . 117 6.2.1 Definici´on de tipos estructurados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 6.2.2 Declaraci´on de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

6.3 Operaciones con estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 6.3.1 Inicializaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 6.3.2 Acceso a los miembros de una estructura . . . . . . . . . . . . . . . . . . . . . . . . . 119 6.3.3 Asignaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 6.3.4 Otras operaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

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

6.8 Ejercicios resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 6.9 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

7 Gesti´on de ficheros

133

7.1 Introducci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 7.2 Tipos de ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 7.3 Operaciones con ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 7.3.1 Abrir y cerrar ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 7.3.2 Lectura y escritura de ficheros de texto . . . . . . . . . . . . . . . . . . . . . . . . . . 137 7.3.3 Lectura y escritura de ficheros binarios . . . . . . . . . . . . . . . . . . . . . . . . . . 143

7.4 Acceso aleatorio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 7.5 Ejercicios resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 7.6 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148

Bibliograf´ıa IV

151

Cap´ıtulo 1

Introducci´on a la computaci´on La inform´atica es la ciencia que aborda el tratamiento autom´atico de la informaci´on por medio de ordenadores. Es importante conocer el modo en que un ordenador codifica, almacena y procesa la informaci´on, para poder abordar con soltura la tarea de programaci´on. El objetivo de este cap´ıtulo es dar una peque˜na introducci´on a la computaci´on y al proceso de programaci´on.

1.1

Tratamiento autom´atico de la informaci´on

Cualquier sistema de tratamiento de la informaci´on, sea o no autom´atico, puede descomponerse, tal y como muestra la figura 1.1, en tres etapas b´asicas: Entrada: Recogida de datos. Proceso: Tratamiento de los datos. Salida: Presentaci´on de los resultados obtenidos. Cuando el volumen de datos a tratar es muy elevado o se requiere gran velocidad en el proceso de los mismos o simplemente la tarea a realizar es lo suficientemente aburrida

Entrada

Proceso

Salida S

Figura 1.1: Etapas b´asicas en el procesamiento de la informaci´on

1

Cap´ıtulo 1. Introducci´on a la computaci´on

o tediosa para un humano, ser´ıa deseable realizar este proceso de forma autom´atica. En este sentido, los ordenadores tienen la capacidad de procesar gran cantidad de datos a una velocidad de miles de millones de operaciones por segundo. Para que un ordenador pueda llevar a cabo cualquier tarea, deber´a disponer de los elementos hardware necesarios para la entrada de datos (teclado, rat´on, ...), para el procesamiento de los mismos (procesador o CPU), para la salida de resultados (monitor, impresora, ...) y para el almacenamiento temporal o permanente de instrucciones y datos (memoria, disco duro, ...). Adem´as de todos estos elementos, ser´a necesario introducir en el ordenador las instrucciones necesarias para realizar las operaciones deseadas sobre los datos, esto es, los programas (software). Los programas son los que hacen que un ordenador sea vers´atil y pueda utilizarse para realizar tareas muy diversas. En este libro aprenderemos a crear dichos programas, pero antes ser´a necesario tener unos conocimientos m´ınimos sobre el modo en que un ordenador codifica la informaci´on.

1.2

Codificaci´on de la informaci´on: el sistema binario

Un ordenador funciona por impulsos el´ectricos, de modo que u´ nicamente puede diferenciar entre presencia y ausencia de corriente o carga el´ectrica, esto es, s´olo puede diferenciar dos estados. Com´unmente identificamos a estos dos estados mediante los d´ıgitos 0 y 1. Es por ello que cualquier informaci´on que deba ser procesada por un ordenador, necesariamente tiene que estar representada mediante una secuencia de unos y ceros, esto es, debe estar codificada en el sistema binario. En el sistema decimal que empleamos los humanos, cada d´ıgito tiene un peso distinto seg´un la posici´on que ocupa. De derecha a izquierda tenemos las unidades, decenas, centenas, etc, donde el d´ıgito que representa las unidades tiene un peso de 100 , el de las decenas de 101 , el de las centenas de 102 , y as´ı sucesivamente. De modo similar, en el sistema binario, empleado por los ordenadores y otros dispositivos electr´onicos, cada d´ıgito, de derecha a izquierda, tiene un peso de 20 , 21 , 22 , etc. Por ejemplo, el n´umero binario 1101 equivale al decimal 13, ya que 1 ∗ 23 + 1 ∗ 22 + 0 ∗ 21 + 1 ∗ 20 = 13. A los sistemas decimal y binario (por poner algunos ejemplos de sistemas de numeraci´on) se los denomina tambi´en sistema en base 10 y en base 2 respectivamente. En general, para conocer el valor decimal de cualquier n´umero N expresado en base B aplicaremos la n f´ormula:  xi B i N = x0 B 0 + x1 B 1 + ... + xn B n = i=0

donde xi son los d´ıgitos del n´umero expresado en la base B. Denominamos bit a un d´ıgito binario 0/1 y byte a una agrupaci´on de 8 bits. Un bit u´ nicamente nos permite diferenciar entre dos estados posibles. Con una secuencia de 2 bits 2

1.2 Codificaci´on de la informaci´on: el sistema binario

pueden formarse 4 combinaciones distintas (00, 01, 10 y 11), con 3 bits 8 combinaciones (000, 001, 010, 011, 100, 101, 110 y 111). En general, con n bits pueden formarse 2n combinaciones. Podemos codificar los elementos de un conjunto dado (n´umeros enteros, letras, colores, etc.) mediante distintas secuencias de bits. Por ejemplo, con un byte (8 bits) pueden codificarse 28 = 256 elementos. Con 4 bytes (32 bits) pueden codificarse m´as de cuatro mil millones de elementos. Siguiendo con las unidades de medida, diremos que un kilobyte (1 KB) son 1024 bytes, un gigabyte (1 GB) son 1024 KB y un terabyte (1 TB) son 1024 GB (esto es, m´as de mil millones de bytes). Ya hemos visto c´omo un ordenador puede representar n´umeros enteros mediante el sistema binario, pero tambi´en debe ser capaz de representar n´umeros reales, caracteres, im´agenes, sonido y por supuesto instrucciones, por poner s´olo algunos ejemplos. Es necesario disponer de distintos sistemas de codificaci´on para poder representar informaci´on de cualquier tipo. La codificaci´on es el proceso por el cual representamos s´ımbolos o secuencias de un alfabeto mediante los s´ımbolos o secuencias de otro alfabeto, es decir, establecemos una correspondencia biun´ıvoca entre los s´ımbolos de dos alfabetos distintos. En los ejemplos anteriores hemos visto c´omo codificar n´umeros enteros mediante secuencias de 1’s y 0’s en lo que conocemos como sistema de codificaci´on BCD. Pero, tal y como acabamos de mencionar, necesitamos otros sistemas para codificar otros objetos de otros conjuntos. Para codificar los n´umeros reales en el sistema binario se utiliza lo que denominamos representaci´on en coma flotante o formato cient´ıfico. Por ejemplo, el n´umero 12.5 (el cual acabamos de escribir en su notaci´on habitual o coma fija) se expresar´ıa en notaci´on cient´ıfica como 0.125E2. La representaci´on binaria de dicho n´umero consiste en la utilizaci´on de una cierta cantidad de bits para representar lo que se denomina mantisa (0.125) y otra cierta cantidad de bits para representar el exponente. Vali´endonos del sistema BCD utilizado para n´umeros enteros, codificar´ıamos por un lado el n´umero 125 (mantisa en formato normalizado) y por otro lado el exponente (2 en este ejemplo). Otro sistema de codificaci´on es el ASCII (American Standard Code for Information Interchange) utilizado para la representaci´on de caracteres alfanum´ericos. Para ello se define una tabla de correspondencia, en la que a cada car´acter se le asocia un c´odigo binario. Dicha tabla de correspondencia es un est´andar que utilizan todos los sistemas inform´aticos, lo que permite el intercambio de informaci´on entre distintos sistemas. Imaginemos algo tan habitual como enviar un correo electr´onico. El texto en e´ l contenido, se representar´a mediante en una secuencia de 1’s y 0’s, resultado de codificar cada uno de los caracteres del mensaje mediante la tabla ASCII. Dicha secuencia ser´a sencillamente descifrable por cualquier equipo inform´atico, sin m´as que aplicar de nuevo la tabla de conversi´on ASCII en sentido inverso. Resulta evidente que sin la existencia de est´andares de codificaci´on, no ser´ıa posible compartir informaci´on entre los distintos ordenadores. 3

Cap´ıtulo 1. Introducci´on a la computaci´on

1.3

Concepto de algoritmo

Un algoritmo es una secuencia o conjunto ordenado de operaciones que permiten hallar la soluci´on de un problema dado en un tiempo finito. Aunque los algoritmos datan de tiempos babil´onicos y los griegos ya dise˜naron algoritmos, a´un famosos hoy en d´ıa, como por ejemplo el de Euclides (300 A.C.) para calcular el m´aximo com´un divisor de dos n´umeros, fue el matem´atico persa Al-Khowarizmi (siglo IX) el primero que dise˜no´ algoritmos pensando en su eficiencia, en particular, para el c´alculo de ra´ıces de ecuaciones. La palabra algoritmo procede de las sucesivas deformaciones sufridas hasta hoy en d´ıa del nombre Al-Khowarizmi1 . Como ejemplo mostraremos el famoso algoritmo de Euclides para obtener el m´aximo com´un divisor de dos n´umeros enteros: 1) Sean a y b dos enteros positivos. 2) Comparar los dos n´umeros de forma que: 3) Si son iguales, ese es el resultado. Finalizar. 4) Si a es menor que b intercambiar los valores de a y b. 5) Restar el segundo n´umero del primero y almacenar en a el sustraendo y en b el residuo. Ir al paso 2. Si aplicamos esta secuencia de reglas a dos n´umeros enteros positivos, obtendremos el m´aximo com´un denominador de ambos. Por ejemplo, dados los valores a=5 y b=15, a continuaci´on se muestra c´omo evolucionan las variables a y b a medida que se ejecuta el algoritmo propuesto, hasta llegar a obtener el valor del m´aximo com´un divisor (5): a b -----------5 15 15 5 5 10 10 5 5 5 Todo algoritmo debe cumplir las 4 condiciones siguientes: Finitud: Un algoritmo tiene que acabar siempre tras un n´umero finito de pasos. 1 Hay muchas variantes para el nombre de Al Khowarizmi al usar el alfabeto latino, tales como Al-Khorezmi, Al-Khwarizmi, Al-Khawarizmi o Al-Khawaritzmi.

4

1.4 El proceso de la programaci´on

Definibilidad: Toda regla debe definir perfectamente la acci´on a desarrollar sin que pueda haber ambig¨uedad alguna de interpretaci´on. General: Un algoritmo no debe solucionar un problema aislado particular, sino toda una clase de problemas para los que los datos de entrada y los resultados finales pertenecen respectivamente a conjuntos espec´ıficos. Eficacia: Se debe pretender que un algoritmo sea eficaz, esto es, que su ejecuci´on resuelva el problema en el m´ınimo n´umero de operaciones posible. Se dice que un determinado problema es decidible o computable cuando existe un algoritmo que lo resuelve. Existen, sin embargo, muchas clases de problemas no decidibles. Estos problemas, por mucho que aumente la capacidad y velocidad de los computadores, nunca podr´an resolverse. Un ejemplo t´ıpico de problema no decidible es el problema de la teselaci´on. Una tesela (baldosa) es un cuadrado de 1x1, dividido en cuatro partes iguales por sus dos diagonales. Cada una de estas partes tiene un color. El problema es: dada cualquier superficie finita, de cualquier tama˜no (con dimensiones enteras), ¿puede recubrirse usando teselas de forma que cada dos teselas adyacentes tengan el mismo color en los lados que se tocan? Este es un problema indecidible: no se puede demostrar que la superficie pueda recubrirse seg´un el planteamiento del problema, pero tampoco puede demostrarse lo contrario. No existe ning´un algoritmo que resuelva este problema.

1.4

El proceso de la programaci´on

Un programa es la implementaci´on o escritura de un algoritmo en un lenguaje de programaci´on espec´ıfico. Las fases para la correcta elaboraci´on de un programa son las siguientes: 1. An´alisis del problema: definir el problema de la manera m´as precisa posible. ˜ del algoritmo: dise˜nar un m´etodo (algoritmo) que resuelva el problema 2. Diseno planteado. 3. Codificaci´on: escribir (implementar) el algoritmo en un lenguaje de programaci´on. 4. Verificaci´on: comprobaci´on del correcto funcionamiento del programa. Si no funciona correctamente, dependiendo del error habr´a que volver al paso 3, al 2 o incluso al 1. 5. Mantenimiento: desarrollo de mejoras y nuevas funcionalidades. Estas cinco fases es lo que se denomina el ciclo de vida de un proyecto inform´atico. En ocasiones el t´ermino programaci´on se asocia, de forma incorrecta, u´ nicamente al paso 3, pero para desarrollar correctamente un programa es necesario abordar todas las fases descritas. 5

Cap´ıtulo 1. Introducci´on a la computaci´on

1.5

Lenguajes de programaci´on

Un lenguaje de programaci´on es un conjunto de s´ımbolos y palabras especiales que, junto con unas reglas sint´acticas perfectamente definidas, permiten implementar un algoritmo, de modo que e´ ste pueda ser ejecutado por un ordenador. En definitiva, un lenguaje de programaci´on permite construir un programa inform´atico. En general los lenguajes de programaci´on pueden dividirse en: Lenguajes de bajo nivel: Son lenguajes cuya sintaxis est´a m´as pr´oxima a la m´aquina (al ordenador) que al humano. El lenguaje de m´as bajo nivel es el c´odigo m´aquina (cuyas instrucciones son secuencias de unos y ceros), seguido por el lenguaje ensamblador. Los lenguajes de bajo nivel resultan poco legibles para el humano. Lenguajes de alto nivel: Son lenguajes cuya sintaxis est´a m´as pr´oxima al lenguaje natural de los humanos y, por tanto, resultan m´as comprensibles. Existen multitud de lenguajes de alto nivel como C, C++, Java, Pascal, Basic, PHP, etc. (por citar s´olo unos pocos ejemplos). A continuaci´on se muestra, a modo de ejemplo, un programa que realiza la suma de dos n´umeros enteros, escrito en tres lenguajes de programaci´on diferentes: C, Pascal y ensamblador: Suma de dos n´umeros enteros en C: 1

#include

2 3 4

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

5

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

6 7 8 9 10 11

}

Suma de dos n´umeros enteros en Pascal: 1

program suma;

2 3 4 5

6

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

1.6 El lenguaje C

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

6 7 8

’,c);

end

9

Suma de dos n´umeros enteros en ensamblador:

20

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

1.6

El lenguaje C

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

C es un lenguaje estructurado de alto nivel de prop´osito general. Esto quiere decir que sirve para distintos prop´ositos: programas cient´ıficos, programas de gesti´on, comunicaci´on, manejadores de dispositivos (drivers), sistemas operativos, compiladores, etc. C dispone de las estructuras t´ıpicas de los lenguajes de alto nivel pero, a su vez, permite trabajar a bajo nivel, por lo que algunos autores lo clasifican como lenguaje de medio nivel. El lenguaje C fue desarrollado en la d´ecada de los 70 por Kenneth Thompson y Dennis Ritchie en los laboratorios Bell y alcanz´o gran popularidad durante la d´ecada de los 80 tras la publicaci´on de su definici´on por parte de Brian Kernighan y el propio Ritchie2 . La 2 Brian

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

7

Cap´ıtulo 1. Introducci´on a la computaci´on

creaci´on del lenguaje C va ligada a otro fen´omeno de la inform´atica: la creaci´on del sistema operativo UNIX, desarrollado tambi´en en los laboratorios Bell. El n´ucleo (o kernel) del sistema operativo Unix est´a escrito casi en su totalidad en lenguaje C, con excepci´on de alguna parte escrita en ensamblador. Tambi´en el sistema operativo Linux y muchas de sus aplicaciones est´an escritas en este mismo lenguaje. Hoy en d´ıa el lenguaje C se sigue utilizando en multitud de programas. Adem´as, muchos otros lenguajes de programaci´on basan su sintaxis en C, como C++, C#, Java, PHP, Perl, etc.

1.7

Compiladores e int´erpretes

Los ordenadores u´ nicamente entienden el lenguaje m´aquina. Por tanto, un programa escrito en cualquier otro lenguaje debe ser traducido a lenguaje m´aquina para que pueda ser ejecutado. Este proceso de traducci´on se conoce como compilaci´on o interpretaci´on, dependiendo de c´omo se realice. El proceso de compilaci´on consiste en traducir el programa completamente a lenguaje m´aquina antes de ser ejecutado. La interpretaci´on, por contra, consiste en ir traduciendo las instrucciones una a una o en peque˜nos grupos, e ir ejecut´andolas a medida que se traducen. Como puede deducirse, un programa compilado se ejecutar´a a mayor velocidad que uno interpretado, ya que en el primer caso la traducci´on se hace una u´ nica vez, de modo que se almacena en un archivo aparte el programa traducido (denominado c´odigo m´aquina o ejecutable) y a continuaci´on se ejecuta tantas veces como se desee el c´odigo m´aquina generado, mientras que en el segundo caso es necesario ir traduciendo las instrucciones durante la propia ejecuci´on. El proceso de compilaci´on se realiza mediante un programa especial denominado compilador, mientras que el programa encargado de la interpretaci´on se denomina int´erprete. Un programa dado, dependiendo de el lenguaje en que est´e escrito, deber´a ser compilado o interpretado. El lenguaje C requiere del proceso de compilaci´on.

8

Cap´ıtulo 2

Elementos b´asicos de un programa en C 2.1

Estructura b´asica de un programa en C

Un programa escrito en lenguaje C tiene, en su forma m´as sencilla, la siguiente estructura b´asica: int

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

}

Todo programa en C est´a compuesto por una serie de instrucciones o sentencias1 , separadas por punto y coma. Estas instrucciones se encuentran dentro de la funci´on main. En el Cap´ıtulo 4 se estudiar´an en detalle las funciones, de momento es suficiente con entender que una funci´on es un m´odulo o bloque de nuestro programa que contiene una serie de instrucciones y que la funci´on main (funci´on principal) es el punto de inicio de todo programa en C. Para delimitar el conjunto de instrucciones que contiene una funci´on (main en nuestro caso) se utilizan los s´ımbolos { y }.

1A

lo largo de este libro vamos a utilizar indistintamente los t´erminos instrucci´on y sentencia.

9

Cap´ıtulo 2. Elementos b´asicos de un programa en C

El programa m´as sencillo que se puede escribir en C es 1 2 3

int main() { return 0; }

aunque este programa no har´ıa absolutamente nada ya que la u´ nica instrucci´on que contiene (return 0) indica la finalizaci´on del mismo.

2.2

Mostrando mensajes con printf

El programa m´as sencillo, y que adem´as haga algo tangible, es aquel que muestra un mensaje por pantalla. Por ejemplo: 1

#include

2 3 4 5 6

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

Ya se ha dicho que la funci´on main es el punto de inicio de cualquier programa en C, por tanto la primera instrucci´on que se ejecuta en el programa anterior se encuentra en la l´ınea 4 (m´as adelante explicaremos la sentencia #include). La sentencia printf muestra un texto por pantalla, as´ı que el resultado de ejecutar este programa ser´ıa simplemente Hola

La sintaxis de printf, en su formato m´as sencillo, es 1

printf("Texto a mostrar");

En el Apartado 2.5 se estudiar´an otras formas de utilizar la sentencia printf. Adem´as de la funci´on main, vemos que en este programa aparece la instrucci´on 1

#include

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

2.3 Variables y tipos de datos

con mayor profundidad. De momento u´ nicamente es necesario saber que cualquier programa que utilice la instrucci´on printf deber´a contener al inicio del mismo la sentencia #include . La instrucci´on return 0 la pondremos habitualmente como u´ ltima instrucci´on de la funci´on main. En el Cap´ıtulo 4 se estudiar´an con detalle las funciones y se explicar´a el uso de la instrucci´on return.

2.3

Variables y tipos de datos

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

2.3.1

Variables

Una variable puede verse como un contenedor donde almacenar un dato determinado. En realidad una variable representa una posici´on de memoria en la que se encuentra almacenado un dato. Las variables son un mecanismo que ofrecen los lenguajes de programaci´on para facilitar el acceso a los datos sin necesidad de conocer las posiciones o direcciones de memoria en que se encuentran almacenados. Cuando se crea una variable se le asigna un nombre que la identifica. Un nombre de variable puede constar de uno o m´as caracteres y debe cumplir las siguientes restricciones: El primer car´acter debe ser una letra o el car´acter subrayado ( ), mientras que el resto pueden ser letras, d´ıgitos o el car´acter subrayado ( ). Las letras pueden ser min´usculas o may´usculas del alfabeto ingl´es. Por lo tanto, no est´a permitido el uso ˜ vocales acentuadas, s´ımbolos especiales, etc. de las letras ’˜n’, ’N’, No pueden existir dos variables con el mismo nombre dentro de una misma funci´on. El nombre de una variable no puede ser una palabra reservada. Las palabras reservadas son identificadores propios del lenguaje. Por ejemplo, int y main son palabras reservadas del lenguaje C. Por ejemplo, lo siguiente ser´ıan nombres v´alidos de variables: x, y, v1, v2, temperatura, velocidad_maxima, Vmax.

Los siguientes nombres de variables no son v´alidos: 1v, velocidad-maxima, Vm´ ax, #tag.

11

Cap´ıtulo 2. Elementos b´asicos de un programa en C

2.3.2

Tipos de datos

Las variables se clasifican en distintos tipos, seg´un la informaci´on que almacenan. Cuando se crea una variable, es necesario especificar a qu´e tipo de dato pertenece, de este modo se le asigna memoria suficiente para almacenar valores del tipo especificado. En C existen cinco tipos de datos b´asicos: n´umeros enteros, n´umeros reales con precisi´on simple, n´umeros reales de doble precisi´on, caracteres y punteros. El tipo de dato de una variable determina: ˜ (cantidad de bytes) que ocupar´a la variable en la memoria del ordenador. El tamano El rango de valores que la variable podr´a almacenar. El conjunto de operaciones que se puede realizar sobre dicha variable. En la Tabla 2.1 se muestran los tipos b´asicos en C, el tama˜no que ocupa cada uno de ellos en memoria y el rango de valores que pueden almacenar.2 Tabla 2.1: Tipos de datos simples en C

Tipo de dato Car´acter Entero Real Real (doble precisi´on) Puntero

Nombre del tipo en C char int float double

(depende del tipo de puntero)

Bytes 1 4 4 8 4

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

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

12

2.3 Variables y tipos de datos

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

valores entre 0 y 65535.

2.3.3

Uso de variables: declaraci´on y asignaci´on

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

Por ejemplo, para declarar una variable de tipo int y nombre x utilizar´ıamos la siguiente sentencia: 1

int x;

Si se desea declarar m´as de una variable del mismo tipo, puede hacerse separ´andolas por comas del siguiente modo: 1 2

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

Las variables pueden declararse: Al inicio de cada funci´on (por ejemplo de la funci´on principal main). A estas variables se las denomina variables locales. Fuera de las funciones. A estas variables se las denomina variables globales. Salvo en casos muy excepcionales, no se recomienda el uso de variables globales. En la Secci´on 4.4 se explica con m´as detalle la diferencia entre ambos tipos de variables. De momento, u´ nicamente emplearemos variables locales y, por tanto, la variable x del ejemplo anterior la declarar´ıamos al inicio de la u´ nica funci´on que hasta el momento hemos visto: main. 1 2

int main() { int x;

3

. . .

4 5

}

13

Cap´ıtulo 2. Elementos b´asicos de un programa en C

Para almacenar una valor en una variable se utiliza la operaci´on de asignaci´on. En el lenguaje C la asignaci´on se realiza mediante la operaci´on =. Por ejemplo, el siguiente programa declara una variable real de nombre pi y le asigna el valor 3.14; 1 2 3

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

4

. . .

5 6

}

En la operaci´on de asignaci´on, a la izquierda del s´ımbolo = se debe poner siempre el nombre de la variable que recibe el valor y a la derecha el valor que deseamos almacenar o una operaci´on cuyo resultado sea un valor. Por ejemplo la siguiente operaci´on de asignaci´on es incorrecta: 1

3.14 = pi;

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

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

4

. . .

5 6

}

En este caso en la operaci´on de asignaci´on se producir´ıa el truncamiento del n´umero real de modo que en la variable x se almacenar´ıa el valor 2 en lugar de 2.7. Tambi´en es posible almacenar en una variable el resultado de una operaci´on (o expresi´on). El siguiente programa almacena en una variable de nombre area el resultado de multiplicar 3.14 por 4. 1 2 3

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

4

. . .

5 6

14

}

2.3 Variables y tipos de datos

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

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

6

. . .

7

}

8

De forma gen´erica, la operaci´on de asignaci´on tiene siempre la siguiente estructura: 1

nombre_variable = expresion;

donde expresion puede ser un valor constante (por ejemplo x=2), una variable (por ejemplo x=y), una operaci´on (por ejemplo x=2*y), etc. En la Secci´on 2.7 se explica con m´as detalle el concepto de expresi´on. El efecto de la operaci´on de asignaci´on ser´a almacenar en nombre_variable el resultado de expresion. Es muy importante tener la precauci´on de que cuando se utilice una variable como parte de una expresi´on, dicha variable tenga un valor asignado previamente, de lo contrario el resultado de la expresi´on ser´a impredecible. Por ejemplo el siguiente programa, aunque es sint´acticamente correcto, su resultado es impredecible: 1 2 3 4 5

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

6

. . .

7 8

}

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

Cap´ıtulo 2. Elementos b´asicos de un programa en C

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

2.4

Comentarios

En un programa es posible (y recomendable) introducir comentarios. Un comentario es un texto que incluimos en nuestros programas pero que el compilador ignora por completo. El objetivo de los comentarios es hacer los programas m´as legibles, ayudando a la comprensi´on de los mismos. En C existen dos modos de a˜nadir comentarios: Para a˜nadir un comentario de p´arrafo se encierra el texto a comentar entre los s´ımbolos /* y */. Un comentario de p´arrafo puede contener m´as de una l´ınea. Por ejemplo: 1 2 3 4 5 6

/* Este programa calcula el ´ area de un c´ ırculo dado su radio */ int main() { float area, radio; . . . }

Para a˜nadir un comentario de l´ınea se utiliza //. El texto que aparece desde // hasta el final de la l´ınea es ignorado por el compilador. Por ejemplo: 1 2 3 4

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

5

a = 3.14 * r * r; . . .

6 7 8

16

}

2.5 Uso avanzado de printf

2.5

Uso avanzado de printf

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

#include

2 3 4

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

5

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

6 7 8 9 10 11

}

El error del programa anterior radica en que la instrucci´on printf, tal y como se ha escrito, mostrar´a literalmente el texto especificado. Esto es, la ejecuci´on de este programa mostrar´a por pantalla el mensaje: La suma de a y b es c

Si lo que se pretend´ıa es que el programa mostrase La suma de 2 y 3 es 5

entonces la instrucci´on printf se deber´ıa haber escrito del siguiente modo: 1

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

Podemos observar que en la instrucci´on printf aparece en primer lugar un texto encerrado entre dobles comillas, seguido de una serie de variables separadas por comas. El texto encerrado entre comillas contiene una serie de c´odigos especiales %d. Los c´odigos % (existen m´as aparte de %d) indican que esta parte del texto debe ser sustituida por el valor de alguna expresi´on (una variable en el caso m´as sencillo). Concretamente, el c´odigo %d indica que, en la posici´on en la que aparece, debe mostrarse el valor de una expresi´on que de como resultado un valor entero (por ejemplo una variable de tipo int). La expresi´on o variable a mostrar ser´a alguna de las que aparezcan a continuaci´on del texto encerra17

Cap´ıtulo 2. Elementos b´asicos de un programa en C

do entre comillas. La asociaci´on entre los c´odigos % y las expresiones que aparecen a continuaci´on se hace por orden de aparici´on. En consecuencia, en el ejemplo anterior, el primer c´odigo %d se asocia a la variable a, el segundo a la variable b y el tercero a c. Debe observarse que es necesario que la instrucci´on printf contenga tantas expresiones (variables en el ejemplo anterior) como c´odigos %. En la Tabla 2.2 se muestra los c´odigos % (especificadores) que pueden aparecer en una instrucci´on printf. En la primera parte de la tabla se muestran los que se usan de forma m´as habitual y en los que, de momento, nos centraremos. Tabla 2.2: Especificadores m´as habituales.

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

Tipo de expresi´on con que se asocia Entero Real Car´acter Entero sin signo Entero (se muestra en notaci´on hexadecimal) Entero (se muestra en notaci´on octal) Real de doble precisi´on (se muestra en notaci´on exponencial) Real de doble precisi´on (se muestra en notaci´on cient´ıfica) Cadena de caracteres (string)

Adem´as de los especificadores mostrados en la Tabla 2.2, el texto de una instrucci´on printf puede contener otras secuencias de caracteres que se interpretan de un modo especial: \n: Se sustituye por un salto de l´ınea \t: Se sustituye por un tabulador \\: Muestra el car´acter \

A continuaci´on se muestra un ejemplo del uso de printf: 1

#include

2 3 4 5 6

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

7 8 9 10

18

printf("Este texto aparece en la primera l´ ınea."); printf("Este tambi´ en.\nAhora en la segunda.\n"); printf("\tEsto aparece tabulado.\n");

2.6 Leyendo datos con scanf

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

11 12 13 14 15 16 17 18 19

return 0;

20 21

}

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

2.6

Leyendo datos con scanf

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

donde especificadores contiene una secuencia de c´odigos % (tantos como datos se quieran introducir) y lista_de_variables contiene la lista de variables, separadas por coma, donde se almacenar´an los datos. Cada una de estas variables debe ir precedida por el s´ımbolo & (salvo en alg´un caso especial que ya se comentar´a en la Secci´on 5.11). Los especificadores o c´odigos % que usaremos de momento en la instrucci´on scanf ser´an %d (o su equivalente %i), %f y %c para leer variables de tipo entero, real o car´acter respectivamente. Por ejemplo, la siguiente instrucci´on almacena en la variable x un n´umero que el usuario debe introducir por teclado: 19

Cap´ıtulo 2. Elementos b´asicos de un programa en C

1

scanf(" %d", &x);

En este caso la variable x deber´ıa ser de tipo int. El porqu´e las variables deben ir precedidas por el s´ımbolo & es algo que se ver´a en la Secci´on 2.9. De momento basta con saber que es necesario hacerlo de este modo. Cuando se ejecuta una instrucci´on scanf el programa se detiene en espera de que el usuario introduzca los datos necesarios (debe pulsarse la tecla enter para finalizar la introducci´on). En el momento en que los datos han sido introducidos, e´ stos se almacenan en las variables especificadas y el programa contin´ua. A continuaci´on se muestra un programa completo que solicita dos n´umeros y los suma: 1

#include

2 3 4

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

5

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

6 7 8 9 10 11 12 13

}

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

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

En este caso el programa se detendr´a en la instrucci´on scanf hasta que el usuario haya introducido los dos valores.

20

2.7 Expresiones

2.7

Expresiones

Una expresi´on es una combinaci´on de constantes, variables, s´ımbolos de operaci´on, par´entesis y funciones. Por ejemplo, lo siguiente son expresiones v´alidas en C: 1 2 3

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

En la u´ ltima expresi´on del ejemplo aparece una llamada a la funci´on seno (sin). Las funciones se estudiar´an en el Cap´ıtulo 4. ´ a un valor determinado, esto es, el resultado de una expreLas expresiones se evaluan si´on siempre es un valor (entero o real). Las expresiones en C pueden clasificarse en tres categor´ıas: Aritm´eticas Relacionales L´ogicas En los siguientes apartados se explica cada una de ellas.

2.7.1

Expresiones aritm´eticas

Las expresiones aritm´eticas son aquellas que utilizan los operadores +, -, *, / y %. El resultado de una expresi´on aritm´etica es un valor entero o real. El operador * representa la multiplicaci´on. El operador / realiza la divisi´on, si bien debe llevarse especial cuidado con esta operaci´on ya que, dependiendo de los operandos, realizar´a la divisi´on entera o la real. Si al menos uno de los dos operandos es un valor real, entonces se har´a la divisi´on real, pero si ambos operandos son enteros se realizar´a la divisi´on entera. Por ejemplo, el resultado de la expresi´on 5 / 2 es 2 (divisi´on entera) y no 2.5 (divisi´on real) ya que tanto 5 como 2 son n´umeros enteros. Sin embargo, el resultado de 5.0 / 2 es 2.5 ya que, en este caso, alguno de los operandos (el primero de ellos en este ejemplo) es un n´umero real. El operador % realiza la operaci´on m´odulo o resto de la divisi´on entera. Por ejemplo 11 % 3 = 2 ya que la divisi´on entera de 11 y 3 da cociente 3 y resto 2. En general, el resultado de a % b ser´a un n´umero comprendido entre 0 y b-1, ya que el resto debe de ser necesariamente menor que el divisor. La operaci´on m´odulo u´ nicamente puede realizarse entre n´umeros enteros y puede ser u´ til para diversas situaciones. Por ejemplo, para obtener la u´ ltima cifra de un n´umero n basta con hacer n % 10 (por ejemplo, 127 % 10 = 7). Si 21

Cap´ıtulo 2. Elementos b´asicos de un programa en C

se desean las dos u´ ltimas cifras habr´a que hacer n % 100 (127 % 100 = 27). Para saber si un n´umero n es par o impar habr´a que calcular n % 2. Si el resultado de esta operaci´on es 0 quiere decir que n es par, mientras que si el resultado es 1 significar´a que n es impar. Para evaluar las expresiones aritm´eticas existen unas reglas de prioridad y asociatividad: Las operaciones entre par´entesis se eval´uan primero. Los operadores *, / y % se eval´uan antes (tienen mayor prioridad) que + y -. Los operadores de igual prioridad se eval´uan de izquierda a derecha. Por ejemplo, el resultado de 3 + 2 * 4 es 11 (se realiza en primer lugar la multiplicaci´on) mientras que (3 + 2) * 4 da 20 (se realiza en primer lugar la suma). Operadores de incremento y decremento Los operadores ++ y -- sirven, respectivamente, para incrementar y decrementar en una unidad el valor de una variable. Por ejemplo, n++ incrementa en uno el valor de la variable n, mientras que n-- lo decrementa. Como puede observarse, se trata de operadores unarios (´unicamente tienen un operando). La operaci´on n++ es equivalente a n = n + 1 (se calcula n + 1 y el resultado se almacena en la propia variable n, lo que provoca que el valor de n acabe increment´andose en 1). De forma an´aloga, n-- es equivalente a n = n - 1. Tambi´en es posible escribir el operador antes del operando (++n y --n). Esto no introduce ninguna diferencia cuando la operaci´on se hace de forma aislada, aunque es importante tenerlo en cuenta cuando esta operaci´on se combina con otras dentro de la misma expresi´on, ya que modifica el orden en el que se realiza el incremento o decremento. Por ejemplo x / y++ calcula primero la divisi´on x / y y a continuaci´on incrementa la variable y, mientras que x / ++y incrementa en primer lugar el valor de y y a continuaci´on realiza la divisi´on (con el valor de y ya incrementado). Operadores reducidos Los operadores reducidos m´as habituales son +=, -=, *= y /=. Estos operadores permiten expresar de forma m´as compacta algunas operaciones. Por ejemplo, x += 2 equivale a la operaci´on x = x + 2 (esto es, incrementa el valor de la variable x en dos unidades). x *= 5 equivale a la operaci´on x = x * 5. A continuaci´on se muestra un ejemplo con el uso de las operaciones aritm´eticas vistas:

22

2.7 Expresiones

1

#include

2 3 4

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

5

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

6 7 8 9 10

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

11 12 13

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

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

14

return 0;

15 16

}

2.7.2

Expresiones relacionales

Las expresiones relacionales son aquellas que comparan valores. Para ello se utilizan los operadores =, == y != (que se corresponden, respectivamente, con las operaciones menor, menor o igual, mayor, mayor o igual, igual y distinto). El resultado de una expresi´on relacional es 1 cuando el resultado de la comparaci´on es cierto y 0 cuando el resultado es falso. Por ejemplo, dado x = 5, el resultado de la operaci´on x >= 5 es 1 (cierto) y el de x != 5 es 0 (falso). No debe confundirse la operaci´on de comparaci´on == con la operaci´on de asignaci´on = (explicada en la Secci´on 2.3.3). Por ejemplo, la expresi´on x==5 comprueba si la variable x vale 5. En caso afirmativo el resultado de esta expresi´on ser´a 1 y de lo contrario dar´a 0. Por contra, la operaci´on x=5 almacena el valor 5 en la variable x.

2.7.3

Expresiones l´ogicas

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

(falso). 23

Cap´ıtulo 2. Elementos b´asicos de un programa en C

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

Los siguientes puntos resumen el uso de los operadores l´ogicos: exp1 && exp2 se eval´ua a cierto cuando tanto exp1 como exp2 son ciertas. En

cualquier otro caso se eval´ua a falso. exp1 || exp2 se eval´ua a cierto cuando alguna de las expresiones exp1 o exp2 son ciertas. Se eval´ua a falso cuando tanto exp1 como exp2 son falsas. !exp1 se eval´ua a cierto cuando exp1 es falso y viceversa.

Estos tres puntos se resumen en la Tabla 2.3 denominada taba de verdad. Tabla 2.3: Tabla de verdad de los operadores l´ogicos.

exp1 0 0 1 1

2.8 2.8.1

exp2 0 1 0 1

exp1 && exp2 0 0 0 1

exp1 || exp2 0 1 1 1

!exp1 1 1 0 0

Otros conceptos sobre tipos de datos El tipo char

Como se ha visto en la Secci´on 2.3.2, el lenguaje C utiliza el tipo char para almacenar caracteres. En realidad el ordenador trabaja u´ nicamente con n´umeros, por lo que cada car´acter se representa mediante un n´umero. Esto es, el tipo char realmente almacena n´umeros, los cuales pueden ser interpretados como caracteres. La interpretaci´on de qu´e car´acter hay almacenado en una variable de tipo char se realiza mediante una tabla de conversi´on. La tabla m´as conocida (por ser la primera que apareci´o) es el est´andar ASCII (American Standard Code for Information Interchange). En la Tabla 2.4 se muestra la tabla ASCII. En esta tabla, los c´odigos del 0 al 31 en realidad no son caracteres imprimibles sino que representan c´odigos de control (por ejemplo, el c´odigo 13 representa el salto de l´ınea o Carriage Return, mientras que el c´odigo 27 representa la tecla Escape). Entre el 65 y el 90 se codifican las letras may´usculas, mientras que las min´usculas ocupan los c´odigos del 97 al 122. Los caracteres num´ericos (d´ıgitos del 0 al 9) aparecen entre los c´odigos 48 al 57. 24

2.8 Otros conceptos sobre tipos de datos

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

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

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

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

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

SP ! ” # $ % & ’ ( ) * + , . /

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

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

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

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

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

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

\ ]

ˆ _

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

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

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

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

Debe aclararse que la tabla ASCII se defini´o para 7 bits, lo que permite representar 128 caracteres. Esto es insuficiente para muchas lenguas que utilizan caracteres que no est´an ˜ C¸, etc.). Por ello se definieron otros representados en esta tabla (vocales acentuadas, N, est´andares de 8 bits que permit´ıan extender el n´umero de caracteres a 256 (por ejemplo el ISO-8859-1). En C los caracteres deben encerrarse entre comillas simples. Por ejemplo, para almacenar el car´acter A en una variable lo haremos del siguiente modo: 1 2

char c; c = ’A’;

Dado que las variables de tipo char realmente almacenan n´umeros, es posible realizar operaciones aritm´eticas con este tipo de variables, tal y como se muestra en el siguiente programa: 1

#include

2 3 4

int main() { char c;

5 6

c = ’A’;

7 8 9 10 11 12

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

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

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

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

25

Cap´ıtulo 2. Elementos b´asicos de un programa en C

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

13 14 15 16 17 18

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

19 20

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

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

21

return 0;

22 23

}

2.8.2

Conversi´on de tipos: casting

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

float f; f = 5;

La instrucci´on f = 5 asigna un valor entero a una variable de tipo real, produci´endose una conversi´on de entero a real. En este caso se produce una conversi´on sin p´erdida de precisi´on. Otras conversiones, sin embargo, pueden conllevar p´erdida de precisi´on, tal y como se muestra en el siguiente c´odigo: 1 2 3

float radio = 2.5; int area; area = 3.1416 * r * r;

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

2.8 Otros conceptos sobre tipos de datos

que el compilador no la har´ıa de forma autom´atica. Esta conversi´on expl´ıcita se conoce por el nombre de casting. Para realizar el cambio de tipo de una expresi´on debe escribirse el nuevo tipo entre par´entesis a la izquierda de la expresi´on, tal y como se muestra a continuaci´on:: 1 2 3

float radio = 2.5; int area; area = (int)(3.1416 * r * r);

Mediante esta conversi´on expl´ıcita el programador deja claro que es consciente del cambio de tipo que se va a producir, con la consecuente p´erdida de precisi´on. Tambi´en hay ocasiones en las que se desea forzar un cambio de tipo ya que el compilador no lo hace de manera autom´atica. Tomemos el siguiente ejemplo: 1 2 3

int suma=5, n=2; float media; media = suma/n;

En este caso el valor almacenado en media ser´a 2.0 y no 2.5. Esto es debido (tal y como se explic´o en la Secci´on 2.7.1) a que la operaci´on suma/n realiza la divisi´on entera, ya que ambos operandos (suma y n) son enteros. El hecho de que el resultado se almacene posteriormente en una variable de tipo float no cambia para nada la situaci´on ya que en este caso la p´erdida de precisi´on se produce en la propia operaci´on de divisi´on y no en la posterior asignaci´on. Para solucionar este problema deber´ıa forzarse el cambio de tipo de al menos uno de los operandos para que se realice la divisi´on real, tal y como se muestra a continuaci´on: 1 2 3

int suma=5, n=2; float media; media = (float)suma/n;

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

27

Cap´ıtulo 2. Elementos b´asicos de un programa en C

2.9

Punteros

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

2.9.1

Declaraci´on

Para declarar una variable de tipo puntero se emplea la siguiente sintaxis: tipo_del_dato_apuntado * nombre_del_puntero;

Por ejemplo 1

int * p;

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

2.9.2

El operador de direcci´on &

El operador de direcci´on & (tambi´en llamado de referencia) obtiene la direcci´on de memoria de una variable. Por ejemplo, dada una variable x, la operaci´on &x obtiene la direcci´on de memoria donde se encuentra x, tal y como se muestra en el siguiente ejemplo: 1

#include

2 3 4 5

28

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

2.9 Punteros

return 0;

6 7

}

Si quisi´eramos almacenar la direcci´on de x en otra variable, entonces esta otra variable deber´ıa ser de tipo puntero, tal y como se muestra en el siguiente ejemplo: 1

#include

2 3 4 5

6

7

8 9

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

2.9.3

El operador de indirecci´on *

El operador de indirecci´on * (tambi´en llamado de deferencia) se aplica sobre punteros y devuelve el valor de la variable apuntada por el puntero. A continuaci´on se muestra un ejemplo: 1

#include

2 3 4 5

6 7 8 9 10 11 12 13

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

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

En general, si un puntero p contiene la direcci´on de cierta variable x (p=&x), entonces la expresi´on *p equivale a x. Por ejemplo, la operaci´on *p=2 es equivalente a x=2. 29

Cap´ıtulo 2. Elementos b´asicos de un programa en C

Como puede verse, el operador * tiene tres usos distintos en C: Para declarar variables de tipo puntero. Por ejemplo, int * p; Como operador de indirecci´on. Por ejemplo, *p = 2; Como operador de multiplicaci´on. Por ejemplo, 5 * 3; Como puede observarse en el ejemplo anterior, la instrucci´on *p = 5 * 3 utiliza el * con dos significados distintos. El porqu´e nos interesa almacenar la direcci´on de una variable en un puntero y luego acceder a dicha variable a trav´es de su puntero en lugar de utilizar su propio nombre es algo que a estas alturas es dif´ıcil de explicar, aunque debe decirse que es de gran importancia entender el manejo de punteros para poder entender en profundidad muchos aspectos de la programaci´on. De forma muy simplificada diremos que en ocasiones puede haber alguna parte de un programa en la que lo u´ nico que se conozca de algunas variables sea las direcciones de memoria que ocupan pero no sus nombres (los cuales pueden incluso ni existir). En estos casos el u´ nico modo de acceder a dichas variables es mediante el manejo de punteros. De momento basta con entender la sintaxis y manejo de punteros, en cap´ıtulos posteriores, en la Secci´on 4.5.2, se ver´a una situaci´on en la que resultan imprescindibles.

2.9.4

La constante NULL

Una variable de tipo puntero, adem´as de direcciones de memoria, tambi´en puede almacenar la constante NULL. Este valor sirve para indicar que el puntero no contiene ninguna direcci´on de memoria v´alida. Por ejemplo: 1 2

int * p; p = NULL;

// p no contiene ninguna direcci´ on de memoria

Para poder utilizar la constante NULL debe incluirse el fichero stdlib.h mediante la instrucci´on 1

30

#include

2.10 Directivas del precompilador

2.10

Directivas del precompilador

El precompilador es un programa que analiza y modifica el fichero fuente antes de la compilaci´on real, en funci´on de ciertas directivas de compilaci´on que podemos incluir en nuestros programas. Todas las directivas de compilaci´on comienzan por el car´acter # (por ejemplo, #include) y, a diferencia de las sentencias de C, no llevan punto y coma al final. El precompilador, adem´as, elimina todos los comentarios del c´odigo fuente, ya que e´ stos no forman parte del lenguaje y por tanto no ser´ıan entendidos por el compilador. A continuaci´on se explican las directivas #include y #define que utilizaremos de forma habitual en nuestros programas:

2.10.1

Incluir ficheros de cabecera: #include

La directiva #include se utiliza para incluir en nuestro fichero fuente el contenido de otros ficheros. Habitualmente los ficheros que se incluyen son los denominados ficheros de cabecera (header files). La sintaxis empleada es: #include

o #include "fichero"

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

#include

El fichero stdio.h (standard input output header) contiene el c´odigo necesario para que se compilen correctamente todas las funciones relacionadas con la entrada/salida (input/output) de nuestro programa, como por ejemplo printf y scanf. En la Secci´on 4.3.5 se explica con m´as detalle los ficheros de cabecera. La diferencia entre utilizar los corchetes angulados () y las dobles comillas ("...") radica en que, en el primer caso, el fichero incluido ser´a buscado en los directorios que el compilador tenga configurados a tal efecto, mientras que en el segundo caso el fichero incluido ser´a buscado en el mismo directorio en el que se encuentra el fichero fuente.

31

Cap´ıtulo 2. Elementos b´asicos de un programa en C

2.10.2

Definici´on de constantes: #define

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

Por ejemplo, la directiva 1

#define PI 3.1416

define la constante PI con el valor especificado. Una vez definida una constante, e´ sta puede utilizarse a lo largo de todo el programa: 1 2

#include #define PI 3.1416

3 4 5

int main() { float radio, perim;

6

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

7 8 9 10 11 12

}

En este caso el precompilador buscar´a el texto PI como parte de alguna expresi´on y lo sustituir´a por 3.1416, de modo que cuando se inicie el proceso de compilaci´on la l´ınea 1

perim = 2 * PI * radio;

habr´a sido sustituida por 1

32

perim = 2 * 3.1416 * radio;

2.11 Ejercicios resueltos

2.11

Ejercicios resueltos

1. Escribir un programa que solicite la base y la altura de un rect´angulo y muestre por pantalla el a´ rea y el per´ımetro. ´ SOLUCION: 1

#include

2 3 4

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

5

// Pedir datos de entrada printf("Introduce la base: "); scanf(" %f", &base); printf("Introduce la altura: "); scanf(" %f", &alt);

6 7 8 9 10 11

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

12 13 14 15

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

16 17 18 19

return 0;

20 21

}

2. Indicar qu´e mostrar´ıa por pantalla el siguiente programa: 1

#include

2 3 4 5

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

6 7 8 9 10 11 12 13 14 15

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

b/a);

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

b < 5 ) );

16

33

Cap´ıtulo 2. Elementos b´asicos de un programa en C

return 0;

17

}

18

´ SOLUCION: 2 1 E 1

2.12

Ejercicios propuestos

1. Se pretende calcular el importe del combustible que consume un veh´ıculo durante un determinado trayecto. Para ello se pide escribir un programa que solicite como datos de entrada: el consumo medio del veh´ıculo (litros/100 km.), los kil´ometros del trayecto y el precio del litro de combustible. Con esos datos, el programa deber´a calcular y mostrar: el total de litros consumidos y el coste total. 2. Indicar qu´e mostrar´ıa por pantalla el siguiente programa: 1

#include

2 3 4 5

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

6

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

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

34

}

Cap´ıtulo 3

Estructuras de control En los programas que hemos realizado hasta ahora, cada una de las instrucciones se ejecuta en modo secuencial, una tras otra y una u´ nica vez. Sin embargo, es habitual que los programas necesiten ejecutar, en funci´on de cierta condici´on, un grupo de instrucciones u otro (ejecuci´on condicional), o que, por ejemplo, requieran ejecutar un bloque de instrucciones m´as de una vez (bucle). Estas situaciones se resuelven mediante lo que se denomina sentencias de selecci´on y sentencias de repetici´on.

3.1

Sentencias de selecci´on

Las sentencias de selecci´on permiten ejecutar unas instrucciones u otras, en funci´on de cierta condici´on. En este sentido, el lenguaje C dispone de las instrucciones if-else y switch.

3.1.1

Selecci´on con if-else

La instrucci´on if-else permite ejecutar un bloque de instrucciones u otro, dependiendo de que la evaluaci´on de una expresi´on l´ogica resulte ser verdadera o falsa. La sintaxis general de esta instrucci´on es la siguiente: if( expresion ) { instruccion_1_1; instruccion_1_2; . . . instruccion_1_N; } else { instruccion_2_1;

35

Cap´ıtulo 3. Estructuras de control

instruccion_2_2; . . . instruccion_2_N; }

Si la expresi´on de la instrucci´on if se eval´ua a 1 (cierto), se ejecutar´an las instrucciones 1_1 a 1_N, en caso contrario se ejecutar´an las instrucciones 2_1 a 2_N. Por ejemplo, el siguiente programa indica si cierta nota introducida por teclado corresponde a un aprobado o a un suspenso: 1

#include

2 3 4

int main() { float nota;

5

printf("Introduce el valor de la nota: "); scanf(" %f", ¬a); if( nota >= 5 ) { printf("APROBADO\n"); } else { printf("SUSPENDIDO\n"); } return 0;

6 7 8 9 10 11 12 13 14 15

}

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

Por lo tanto la instrucci´on if-else del ejemplo anterior tambi´en podr´ıa haberse escrito como: 1 2 3 4

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

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

3.1 Sentencias de selecci´on

1

#include

2 3 4 5

int main() { float precio; char aplicar_descuento;

6

printf("Introduce el precio del art´ ıculo: "); scanf(" %f", &precio); printf("¿Desea aplicar descuento? (s/n) "); scanf(" %c", &aplicar_descuento);

7 8 9 10 11

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

12 13 14

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

15 16

return 0;

17 18

}

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

#include

2 3 4

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

5 6 7

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

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

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

37

Cap´ıtulo 3. Estructuras de control

22

return 0;

23 24

}

Otro modo de resolver el problema anterior podr´ıa ser el siguiente: 1

. . .

2 3 4 5 6 7 8 9 10 11

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

En ocasiones es necesario anidar un n´umero elevado de instrucciones if-else con el fin de seleccionar una de entre varias acciones. La sintaxis en este caso no difiere para nada de lo expuesto anteriormente, sin embargo, por cuestiones de legibilidad, el c´odigo suele escribirse de modo algo distinto, tal y como se muestra a continuaci´on: if( expresion_1 ) { instrucciones; } else if (expresion_2) { instrucciones; } else if (expresion_3) { instrucciones; } . . .

Debe observarse que el c´odigo que acabamos de escribir coincide con el que se muestra a continuaci´on, aunque el primero resulta m´as legible: if( expresion_1 ) { instrucciones; } else { if (expresion_2) { instrucciones; } else {

38

3.1 Sentencias de selecci´on

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

El siguiente ejemplo muestra la calificaci´on obtenida (suspenso, aprobado, notable o sobresaliente) en funci´on de la nota num´erica, mediante la concatenaci´on de varias instrucciones if-else: 1

#include

2 3 4

int main() { float nota;

5

printf("Introduce el valor de la nota: "); scanf(" %f", ¬a);

6 7 8

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

9 10 11 12 13 14 15 16 17

return 0;

18 19

}

3.1.2

Selecci´on con switch

Tal y como se ha visto en el apartado anterior, dados dos grupos de instrucciones, la instrucci´on if-else permite seleccionar uno de ellos. Si se desea seleccionar entre m´as de dos opciones, se ha visto que es necesario utilizar una combinaci´on de instrucciones if-else. Otra alternativa es es utilizar la instrucci´on switch. Antes de pasar a ver la sintaxis de esta instrucci´on, debe quedar claro que cualquier algoritmo que se implemente utilizando switch puede implementarse igualmente mediante una combinaci´on de instrucciones if-else. La instrucci´on switch simplemente proporciona otra manera de escribir ciertas partes de un programa, lo que en ocasiones puede ofrecer mayor legibilidad a nuestro c´odigo. 39

Cap´ıtulo 3. Estructuras de control

La sintaxis de la instrucci´on switch es la siguiente: switch( expresion ) { case valor_1: instrucciones; case valor_2: instrucciones; . . . case valor_N: instrucciones; default: instrucciones; }

La expresi´on de la sentencia switch debe evaluarse a un entero o a un car´acter (en la mayor´ıa de los casos esta expresi´on ser´a simplemente una variable de tipo int o char). Si el resultado de dicha expresi´on coincide con el valor especificado en alguna de las sentencias case, entonces se ejecutar´an todas las instrucciones que aparecen a continuaci´on de dicho case, hasta que se encuentre una instrucci´on break. Si el valor no coincide con ning´un case, entonces se ejecutar´an las instrucciones especificadas en default. El apartado default es opcional, en caso de no existir y de que la expresi´on del switch no coincida con ninguno de los valores case, entonces no se hace nada. En el siguiente ejemplo el usuario introduce un n´umero entero y el programa muestra el d´ıa de la semana correspondiente: 1

#include

2 3 4

int main() { int dia;

5

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

6 7 8

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

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

}

En este ejemplo la instrucci´on switch eval´ua el valor de la variable dia. A continuaci´on busca un case cuyo valor coincida con el de esta variable y, si lo encuentra, ejecuta las 40

3.1 Sentencias de selecci´on

instrucciones correspondientes. Si el valor de la variable dia no coincide con ninguno de los case, entonces se ejecuta el bloque default. Debe tenerse en cuenta que si se encuentra un case que coincida con el valor de la expresi´on (en este ejemplo dia), se ejecutan todas las instrucciones que aparezcan a continuaci´on, hasta que se alcance una instrucci´on break. Esto quiere decir que si en el ejemplo anterior no se hubieran incluido las instrucciones break y el usuario introduce, por ejemplo, un 6, se hubiera ejecutado no s´olo la instrucci´on printf("SABADO\n") sino tamıa incorrecto\n"). Lo bi´en las instrucciones printf("DOMINGO\n") y printf("D´ habitual ser´a, por tanto, que cada grupo de instrucciones especificados en un case termine con la instrucci´on break. Sin embargo, tal y como se muestra en el ejemplo siguiente, habr´a ocasiones en las que interese no incluir la instrucci´on break. 1

#include

2 3 4

int main() { int curso;

5

printf("Introduce el curso en el que te encuentras: "); scanf(" %d", &curso);

6 7 8

printf("Asignaturas que todav´ ıa debes cursar:\n");

9 10

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

11 12 13 14 15 16 17 18 19 20

}

En el ejemplo anterior, si el usuario introduce, por ejemplo, un 2, el programa mostrar´a: Asignaturas que todav´ ıa debes cursar: ALGOR´ ITMICA PROGRAMACI´ ON AVANZADA PROGRAMACI´ ON DE REDES INGENIERIA DEL SOFTWARE

Obs´ervese que, una vez se entra en un case, se ejecuta el resto de instrucciones hasta alcanzar una instrucci´on break. 41

Cap´ıtulo 3. Estructuras de control

3.2

Sentencias de repetici´on

Las sentencias de repetici´on permiten ejecutar un bloque de instrucciones m´as de una vez, esto es, permiten hacer bucles. En lenguaje C se pueden implementar bucles de tres modos distintos, mediante las sentencias while, do-while y for.

3.2.1

La sentencia while

Para implementar un bucle while se utiliza la siguiente sintaxis: while( expresion ) { instruccion_1; instruccion_2; . . . instruccion_N; }

Mientras la expresi´on de la instrucci´on while sea cierta, se ejecutar´an las instrucciones contenidas en el bucle. Cuando se ejecuta la u´ ltima instrucci´on del bucle (instruccion_N) se vuelve a evaluar de nuevo la expresi´on y, si sigue siendo cierta, se ejecutan de nuevo todas las instrucciones. Se denomina iteraci´on a cada una de las repeticiones. Al igual que ocurre con la instrucci´on if-else, si el bucle contiene una u´ nica instrucci´on, las llaves se pueden omitir. El siguiente ejemplo muestra 10 veces el texto “Hola mundo” y a continuaci´on una vez el texto “Fin del programa”. 1

#include

2 3 4 5 6 7 8 9 10 11

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

Como puede observarse, en este ejemplo se ha utilizado la variable i a modo de contador, para controlar el n´umero de veces que queremos que se repita el bucle (n´umero de iteraciones). En este caso, el bucle se repite 10 veces ya que en cada iteraci´on la variable i se 42

3.2 Sentencias de repetici´on

incrementa en uno. Cuando e´ sta variable valga 10, la condici´on i < 10 ser´a falsa, con lo que ya no se entrar´a de nuevo en el bucle. Para evitar bucles infinitos es imprescindible que alguna de las instrucciones del bucle modifique de alg´un modo la expresi´on de la sentencia while, de lo contrario, una vez se entra en el bucle ya no se puede salir del mismo. El siguiente programa muestra un ejemplo de un bucle infinito: 1

#include

2 3 4 5 6 7 8 9

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

En el siguiente ejemplo se muestra un programa que solicita dos n´umeros y una operaci´on (suma, resta, multiplicaci´on o divisi´on) y realiza la operaci´on especificada. A continuaci´on pregunta si se desea hacer otra operaci´on. En caso afirmativo se repite de nuevo todo el proceso. 1

#include

2 3 4 5

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

6 7

8 9 10 11

12 13 14 15 16 17 18 19 20 21

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

43

Cap´ıtulo 3. Estructuras de control

scanf(" %d", &repetir); } return 0;

22 23 24 25

}

Puede ocurrir que el contenido de un bucle while no se ejecute nunca. El siguiente programa solicita n´umeros enteros hasta que se introduzca un cero y al final muestra cu´antos de ellos eran positivos. 1

#include

2 3 4

int main() { int num, positivos;

5

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

6

7 8 9 10 11 12 13

14 15 16 17

}

En el ejemplo anterior, si el primer n´umero introducido es cero, no llega a entrarse en el bucle while. En este caso la variable positivos se queda con su valor inicial cero. En caso de que se entre en el bucle, al final del mismo se pide un nuevo n´umero que, en caso de que sea distinto de cero, provocar´a que se entre de nuevo en el bucle.

3.2.2

La sentencia do-while

La sintaxis del bucle do-while es la siguiente: do { instruccion_1; instruccion_2; . . . instruccion_N; } while( expresion );

44

3.2 Sentencias de repetici´on

La construcci´on de un bucle do-while es muy similar a la de un bucle while. La u´ nica diferencia es que en el bucle while primero se comprueba la expresi´on y, si es cierta, se ejecutan las instrucciones del bucle, mientras que en el caso del bucle do-while primero se ejecutan las instrucciones del bucle y al final se eval´ua la expresi´on. Si e´ sta resulta ser cierta, entonces se ejecutar´an de nuevo todas las instrucciones, as´ı repetidas veces hasta que la expresi´on sea falsa. Vemos por tanto que en un bucle do-while se har´a como m´ınimo una iteraci´on. Cualquier c´odigo escrito con un bucle do-while puede reescribirse mediante un bucle while y viceversa. Por ejemplo, el siguiente bucle do-while 1 2 3 4

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