programación i - fcfm.buap.mx · programaciÓn i mÓnica macÍas para la licenciatura en...

95
PROGRAMACIÓN I MÓNICA MACÍAS PARA LA LICENCIATURA EN MATEMÁTICAS Y LA LICENCIATURA EN MATEMÁTICAS APLICADAS ACTUALIZACIÓN: PRIMAVERA 2017 BENEMÉRITA UNIVERSIDAD AUTÓNOMA DE PUEBLA FACULTAD DE CIENCIAS FÍSICO MATEMÁTICAS

Upload: dophuc

Post on 24-Sep-2018

216 views

Category:

Documents


0 download

TRANSCRIPT

PROGRAMACIÓN I MÓNICA MACÍAS

PARA LA LICENCIATURA EN MATEMÁTICAS Y LA LICENCIATURA EN MATEMÁTICAS APLICADAS

ACTUALIZACIÓN: PRIMAVERA 2017

BENEMÉRITA UNIVERSIDAD AUTÓNOMA DE PUEBLA FACULTAD DE CIENCIAS FÍSICO MATEMÁTICAS

1

CONTENIDO

1 PRELIMINARES, CONCEPTOS BÁSICOS 5

2 CONCEPTO DE ALGORITMO 9

3 LENGUAJES DE PROGRAMACIÓN 10

4 DATOS Y EXPRESIONES EN UN PROGRAMA 13

5 PROCESO DE RESOLUCIÓN DE PROBLEMAS UTILIZANDO UN

LENGUAJE DE PROGRAMACIÓN 14

5.1 Fase de solución ............................................................................................... 14

5.2 Fase de implementación del diseño ................................................................. 19

6 LENGUAJE C 21

7 INSTRUCCIONES DE ENTRADA Y SALIDA 39

8 ESTRUCTURAS SECUENCIALES Y SELECTIVAS 44

8.1 Estructura secuencial ....................................................................................... 44

8.2 Estructuras selectivas ....................................................................................... 45 8.2.1 Alternativa simple (si-entonces) ............................................................... 45

8.2.2 Alternativa doble (si entonces sino) ......................................................... 46

8.2.3 Alternativa múltiple (selector) .................................................................. 47

8.2.4 Alternativa múltiple (no selectora) ........................................................... 48 8.2.5 Estructuras de decisión anidadas o en cascada ......................................... 50 8.2.6 Operador ternario ...................................................................................... 51

9 ESTRUCTURAS REPETITIVAS 52

9.1 Estructura mientras .......................................................................................... 52 9.2 Estructura hacer mientras ................................................................................. 53 9.3 Estructura desde o para .................................................................................... 54

9.4 Estructuras repetitivas anidadas ....................................................................... 56 9.5 Instrucciones que alteran el flujo normal de un ciclo ...................................... 57

10 NÚMEROS PSEUDOALEATORIOS EN EL LENGUAJE C 57

11 ARREGLOS 60

11.1 Arreglos unidimensionales ........................................................................... 62 11.2 Arreglos bidimensionales ............................................................................. 64

2

12 CARACTERES, CADENAS DE CARACTERES Y ARREGLOS DE

CARACTERES 68

12.1 Caracteres ..................................................................................................... 68 12.2 Cadenas de caracteres................................................................................... 72 12.3 Arreglos de caracteres .................................................................................. 75

13 FUNCIONES 75

13.1 Funciones definidas por el usuario en el lenguaje C .................................... 77 13.2 Prototipo de funciones .................................................................................. 79 13.3 Paso de parámetros por valor y por referencia ............................................. 83 13.4 Reglas básicas de alcance o ámbito (scope) ................................................. 87

13.5 Variables locales y variables globales .......................................................... 88

14 ANEXOS 90

14.1 Prácticas sanas de programación .................................................................. 90

15 REFERENCIAS 93

3

ÍNDICE DE TABLAS

Tabla 1. Algunos múltiplos del byte. 8

Tabla 2. Ejemplos del tamaño en bytes de algunos tipos de datos. 9

Tabla 3. Identificadores de variables. 14

Tabla 4. Simbología en un diagrama de flujo según la ISO y el ANSI. 17

Tabla 5. Simbología en un diagrama de flujo según DFD, RAPTOR y PSeInt. 18

Tabla 6. Tipos de datos estándar en el lenguaje C. 28

Tabla 7. Operadores aritméticos. 31

Tabla 8. Prioridad de operadores aritméticos. 31

Tabla 9. Operadores relacionales. 32

Tabla 10. Operadores lógicos. 32

Tabla 11. Operadores de asignación. 34

Tabla 12. Prioridad de operadores. 37

Tabla 13. Valores resultantes según expresiones aplicadas a las variables a, b, c y d. 37

Tabla 14. Secuencias de escape para printf. 40

Tabla 15. Especificaciones de formato para printf . 40

Tabla 16. Códigos de formato para scanf. 42

Tabla 17. Código ASCII de caracteres de control. 69

Tabla 18. Código ASCII de caracteres alfabéticos, numéricos y especiales. 69

4

ÍNDICE DE FIGURAS

Figura 1. Estructura funcional de un ordenador. .............................................................. 6

Figura 2. Representación binaria del abecedario. ............................................................. 8

Figura 3. Intérprete. ........................................................................................................ 12

Figura 4. Fases de la compilación. ................................................................................. 12

Figura 5. Fases para producir código ejecutable a partir de código fuente en C. ........... 24

Figura 6. Estructura secuencial. ...................................................................................... 45

Figura 7. Alternativa simple. .......................................................................................... 45

Figura 8. Alternativa doble. ............................................................................................ 47

Figura 9. Alternativa múltiple (selector). ....................................................................... 47

Figura 10. Alternativa múltiple (no selectora). .............................................................. 49

Figura 11. Ejemplo de estructuras selectivas anidadas 1................................................ 50

Figura 12. Ejemplo de estructuras selectivas andadas 2. ................................................ 50

Figura 13. Ejemplo de estructuras selectivas anidadas 3................................................ 51

Figura 14. Estructura mientras. ...................................................................................... 52

Figura 15. Estructura repetir hacer mientras. ................................................................. 54

Figura 16. Estructura desde o para. ................................................................................ 55

Figura 17. Otra forma de representar a la estructura desde o para. ................................ 55

Figura 18. Representación gráfica de un arreglo unidimensional. ................................. 62

Figura 19. Representación gráfica del vector V con 10 elementos de tipo real. ............ 63

Figura 20. Representación gráfica de un arreglo bidimensional . .................................. 65

Figura 21. Representación gráfica del arreglo bidimensional C . .................................. 67

Figura 22. Ejemplo del llamado de una función en el lenguaje C. ................................. 77

Figura 23. Representación gráfica y en memoria de variables y apuntadores. .............. 85

Figura 24. Ejemplo de alcance de una variable en el lenguaje C. .................................. 89

5

1 PRELIMINARES, CONCEPTOS BÁSICOS

Computadora

Es un dispositivo electrónico capaz de ejecutar cálculos y tomar decisiones

lógicas a grandes velocidades, dotada de memoria y de métodos de tratamiento de

información, utilizando programas informáticos (Deitel y Deitel, 1995).

Hardware y software

Los varios dispositivos como el teclado, la pantalla, los discos duros, la

memoria, los circuitos electrónicos, cables, y otros elementos físicos que conforman la

máquina en sí, se conocen como hardware. Por otro lado, los programas o aplicaciones

informáticas que se ejecutan en una computadora se conocen como software.

Dispositivos de entrada

Son los que permiten ingresar datos a la máquina para poder ser procesados,

ejemplos de ellos: micrófono, teclado, ratón, joystick (palanca o control de mando que

se usa en consolas o videojuegos para desplazar un cursor, un personaje, etc), lápiz

óptico, interfaz táctil, escáner, cámara, lector de código de barras, etcétera.

Dispositivos de salida

Son los que permiten visualizar los resultados de los datos transformados o

tratados en la computador, ejemplos de ellos: monitor, impresora, bocinas, plotter

(dispositivo que permite dibujar o representar diagramas y gráficos), fax, sintetizador de

voz, etcétera.

Organización de la computadora

Si no se toman en cuenta las diferencias en apariencia física, todas las

computadoras pueden ser divididas en seis unidades lógicas o secciones como se pude

observar en la figura 1 y según Deitel y Deitel (1995) dichas secciones pueden

describirse de la siguiente manera:

1. Unidad de entrada. Esta es la sección “de recepción” de la computadora.

Obtiene información, es decir, datos e instrucciones de la computadora a partir

de varios dispositivos de entrada y pone esta información a la disposición de las

otras unidades, de tal forma que la información pueda ser procesada.

2. Unidad de salida. Toma la información que ha sido procesada por la

computadora y la coloca en varios dispositivos de salida para dejar la

información disponible para su uso fuera de la computadora.

3. Unidad de memoria. Esta es la sección donde se almacenan por un corto o

largo periodo tanto los datos como las instrucciones.

6

Figura 1. Estructura funcional de un ordenador.

Adaptado de Berzal (s.f., p. 7)

a. La memoria principal, primaria o central, también llamada de acceso

directo o interna trabaja a mayor velocidad que la memoria auxiliar, por

lo que se le conoce como de acceso rápido. Retiene información que ha

sido introducida a través de la unidad de entrada, de tal forma que esta

información pueda estar disponible de inmediato para su proceso cuando

sea necesario. También retiene información ya procesada hasta que dicha

información pueda ser colocada por la unidad de salida en dispositivos

de salida. Existe la memoria de lectura y escritura que es volátil,

conocida como RAM y la memoria de sólo lectura que es permanente,

conocida como ROM. Para que un programa se ejecute, debe estar

almacenado o cargado en la memoria principal.

La memoria cuenta con dirección o localidad y valores o contenido que

se almacenan en dichas localidades.

b. La memoria secundaria, auxiliar, externa o masiva es más lenta que la

anterior pero de mayor capacidad. Los datos y programas se suelen

almacenar aquí, para que cuando se ejecute varias veces un programa o

se utilicen repetidamente unos datos, no sea necesario introducirlos de

nuevo. Los programas o datos que no se estén utilizando de forma activa

por otras unidades están por lo regular colocados en dispositivos de

almacenamiento secundario (por ejemplo, discos duros) en tanto se

necesiten otra vez, es posible que sean horas, días, meses o inclusive

años después.

4. Unidad de procesamiento central (CPU). Esta es la sección “administrativa”

de la computadora, responsable (coordinadora) de la supervisión de la operación

de las demás secciones. El CPU le indica a la unidad de entrada cuándo debe

7

leerse la información y colocarse en la unidad de memoria, señala a la ALU

cuándo deberá utilizar información de la unidad de memoria en cálculos, y le

indica a la unidad de salida cuándo enviar información de la unidad de memoria

a ciertos dispositivos de salida.

5. Unidad aritmética y lógica (ALU). Esta es la sección de “fabricación” de la

computadora. Es responsable de la ejecución de cálculos como: suma, resta,

multiplicación y división. Contiene los mecanismos de decisión que permiten

que la computadora, por ejemplo, compare dos elementos existentes de la unidad

de memoria para determinar si son o no iguales.

6. Unidad de control. Detecta señales de estado procedentes de las distintas partes

del ordenador y genera señales de control dirigidas a todas las unidades para,

precisamente, controlar el funcionamiento de la máquina. Capta de la memoria

principal las instrucciones del programa que ejecuta el ordenador, las decodifica1

(el concepto de codificar se explica más adelante en esta sección) y las ejecuta

una a una. Contiene un reloj que sincroniza todas las operaciones elementales

involucradas en la ejecución de una instrucción. La frecuencia del reloj

determina, en parte, la velocidad de funcionamiento del ordenador.

Sistema operativo

Un sistema operativo es un conjunto de programas también llamado sistema de

software, que sirve de interfaz entre los usuarios y un sistema de cómputo (el sistema de

cómputo compuesto por hardware y software), cuyo propósito es “ofrecer un ambiente

en el que el usuario pueda ejecutar programas de una forma cómoda y eficiente”

(Candela, García, Quesada, Santana y Santos, 2007, p. 3).

El sistema operativo coordina el funcionamiento del hardware, iniciando todos

los elementos que lo conforman para que estén preparados para recibir trabajo, por lo

que, ordena cuándo y cómo deben trabajar y a qué programas se les deben asignar;

coordina y lleva el seguimiento de la ejecución de todos los programas en el sistema de

cómputo, entonces, toma decisiones para evitar que se produzcan conflictos entre ellos

y trata de que el sistema de cómputo se lo más eficiente posible (Candela, García,

Quesada, Santana y Santos, 2007).

Entre sus objetivos se encuentran: facilitar la utilización de los recursos del

sistema de cómputo, es decir, facilitar el uso del software y del hardware, por lo que,

permite ejecutar programas, gestiona procesos (programas en ejecución con todos los

recursos que requieren) e implementa la comunicación entre ellos, administra el tiempo

de acción del o los procesadores y la memoria principal, realiza operaciones de entrada

y salida para las aplicaciones que utiliza el usuario, detecta y notifica errores, manipula

archivos de todo tipo, es decir, manipula el sistema de archivos, protege y controla el

acceso de programas, procesos o usuarios a los recursos definidos por un sistema de

cómputo, entre otras tareas (Candela, García, Quesada, Santana y Santos, 2007).

1 Según el Diccionario de la Real Academia Española (DRAE, 2014) “decodificar o descodificar es

aplicar inversamente las reglas de su código a un mensaje codificado para obtener la forma primitiva de

éste”.

8

Ejemplos de sistemas operativos: MS-DOS, OS/2, UNIX, SOLARIS, IRIX,

MULTICS, GNU/Linux, Windows (3.x, 95, 98, NT, 2000, XP, Vista, 7, 8, 10, Phone),

MAC-OS, Android, etcétera.

Codificación de la información

Codificar es representar los elementos de un conjunto mediante los de otro, de

forma tal que, a cada elemento del primer conjunto le corresponda uno y sólo un

elemento distinto del segundo. Una codificación asocia signos con los elementos de un

conjunto a los que se les denomina significados. Por ejemplo, se codifican los números

del sistema decimal con los símbolos o signos {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, es decir, se

ponen en correspondencia los símbolos con cantidades (Marzal y Gracia, 2006).

Código binario o código máquina

La unidad básica de información en las computadoras es el bit (binary digit,

dígito binario) cuyos valores establecen una de dos posibilidades mutuamente

excluyentes. Los dígitos binarios 0 y 1 se usan para representar los dos estados posibles

de un bit particular.

Cualquier dato que se introduce en la computadora o que sea manipulado por

ella se codifica en su interior por una sucesión de ceros y unos (que físicamente se

presenta por corrientes eléctricas, campos magnéticos, etc.). Entonces puede asignarse

cualquier significado a un patrón de bits particular, en tanto esto se haga en forma

consistente, lo que le da el significado es la interpretación de un patrón de bits; así por

ejemplo, a un caracter le corresponde, según el código utilizado para representarlo, un

byte, que es el conjunto de 8 bits, es decir, un byte es el número de bits necesario para

almacenar un caracter. En la figura 2 se muestran ejemplos de representación en código

binario de algunos caracteres del teclado, concretamente, el abecedario.

A 01000001 G 01000111 M 01001101 Q 01010001 V 01010110

B 01000010 H 01001000 N 01001110 R 01010010 W 01010111

C 01000011 I 01001001 Ñ 10100101 S 01010011 X 01011000

D 01000100 J 01001010 O 01001111 T 01010100 Y 01011001

E 01000101 K 01001011 P 01010000 U 01010101 Z 01011010

F 01000110 L 01001100

Figura 2. Representación binaria del abecedario.

Adaptado de Berzal (s.f., p. 5)

Para medir la capacidad de almacenamiento de la computadora se utilizan

múltiplos del byte, como se observa en la tabla 1, y en la tabla 2 se presentan algunos

ejemplos del tamaño en bytes de ciertos tipos de datos.

Tabla 1. Algunos múltiplos del byte.

Adaptado de Berzal (s.f., p. 6)

Unidad Abreviación Capacidad en bytes

Kilobyte 1 Kb 210 bytes = 1,024 bytes

Megabyte 1 Mb 220 bytes = 1,048,576 bytes

9

Gigabyte 1 Gb 230 bytes = 1,073,741,824 bytes

Terabyte 1 Tb 240 bytes = 1,099,511,627,776 bytes

Petabyte 1 Pb 250 bytes = 1,125,899,906,842,624 bytes

Exabyte 1 Eb 260 bytes 11,529,215,046,068,46,976 bytes

Tabla 2. Ejemplos del tamaño en bytes de algunos tipos de datos.

De Berzal (s.f., p.6)

Datos Descripción Tamaño

Texto 1 novela de 200 páginas, 50 líneas por

página y 80 caracteres por línea

800,000 bytes

Aproximadamente:

781.25 Kb

Imagen en blanco y

negro

1024x768 pixeles2, 1 bpp (bit por

píxel)

98,304 bytes

96 Kb

Imagen en color 1024x768 pixeles, 24 bpp (bit por

píxel)

2,359,296 bytes

2304 Kb

2.25 Mb

Sonido de baja calidad 3 minutos, 11000 muestras por

segundo, 8 bits por muestra

1,980,000 bytes

1933.59375 Kb

Cerca de 2 Mb

Sonido de alta calidad 3 minutos, 44100 muestras por

segundo, 12 bits por muestra, dos

canales (estéreo)

23,814,000 bytes

Cerca de 23 Mb

Video (Calidad DVD) 90 minutos, 25 fotogramas3 por

segundo, 750x576 pixeles de

resolución, 24 bpp (bit por pixel)

174,960,000,000

bytes

170859375 kb

141105.652 Mb

Cerca de 163 Gb

En la entrada y salida de la computadora, los cambios de código se realizan de

forma automática para que las personas no tengan que introducir ni interpretar la

información codificada.

2 CONCEPTO DE ALGORITMO

Algoritmo

Es una secuencia ordenada, finita e inequívoca de pasos a seguir para resolver un

determinado problema, en otras palabras, es la descripción exacta y sin ambigüedades

de la secuencia de pasos elementales a aplicar para, a partir de los datos del problema

encontrar la solución buscada (Cairó, 2006).

2 “Un píxel, del inglés pixel es la superficie homogénea más pequeña de las que componen una imagen,

que se define por su brillo y color” (DRAE, 2014). 3 “Fotograma es cada una de las imágenes que se suceden en una película cinematográfica” (DRAE,

2014).

10

Características de un algoritmo

Para que un algoritmo sea completo deberá contemplar todas las alternativas

lógicas posibles que las distintas combinaciones de los datos de entrada puedan

presentar.

Cualquier problema puede tener diferentes formas de solución, es decir, distintos

algoritmos, cada uno de ellos con ventajas e inconvenientes, por lo que, se busca elegir

el que cumpla una serie de características a la hora de programar; para Cairó (2006) son

suficientes los siguientes elementos:

Finitud: el algoritmo debe terminar en algún momento, se dice que no debe

ciclarse, es decir, debe tener un número finito de pasos, independientemente de

su complejidad.

Precisión: los pasos a seguir se deben indicar clara y ordenadamente.

Determinismo: si se sigue el algoritmo varias veces proporcionándole los

mismos datos, se deben obtener siempre los mismos resultados.

Eficiencia: al momento de traducirse a un lenguaje de programación, no debe

tardar mucho tiempo en ejecutarse el programa ni usar mucho espacio de la

memoria de la computadora.

3 LENGUAJES DE PROGRAMACIÓN

Programar

En el nivel más simple consiste en ingresar en la computadora una secuencia de

órdenes para lograr un cierto objetivo. Es elaborar programas para la resolución de

problemas empleando una computadora (DRAE, 2014).

Programa

Es la expresión de un algoritmo que consiste en un conjunto de instrucciones que

la computadora puede entender y ejecutar. Es una serie de operaciones que realiza la

computadora para llegar a un resultado con un grupo de datos específicos. Existen dos

tipos importantes de programas:

Programas de aplicaciones: desarrollados para llevar a cabo tareas específicas

ya sea de tipo administrativo, científico o de entretenimiento.

Programas del sistema: son independientes de cualquier área específica de

aplicación y permiten gestionar los recursos de una computadora. Ejemplo: el

sistema operativo.

11

Lenguaje de programación

Un lenguaje de programación es un conjunto de símbolos junto con un conjunto

de reglas para combinar y utilizar dichos símbolos al escribir programas. Los lenguajes

de programación se componen de: léxico, es decir, de un conjunto de símbolos

permitidos o un vocabulario; sintaxis, o sea, de reglas que indican cómo realizar las

construcciones correctas del lenguaje y finalmente de semántica, en otras palabras,

reglas que permiten determinar el significado de cualquier construcción del lenguaje

(Bermúdez et al., 2003).

Un lenguaje de programación es un medio para expresar algoritmos y puede ser

tanto entendido por los humanos como interpretado por las computadoras.

Clasificación de los lenguajes de programación

El lenguaje máquina ordena a la computadora las operaciones fundamentales

para su funcionamiento a través de combinaciones de ceros y unos, formando así

órdenes entendibles sólo para el hardware de cada máquina en particular. Es

mucho más rápido que los lenguajes de alto nivel (éstos se describen más

adelante en este apartado), pero es difícil de usar por la cantidad excesiva de

instrucciones que se generan, donde encontrar un fallo es casi imposible.

El lenguaje de bajo nivel (ensamblador) es un derivado del lenguaje máquina

y está formado por abreviaturas de letras y números llamadas nemotécnicos. Con

la aparición de este lenguaje se crearon los programas traductores para poder

pasar los programas escritos en lenguaje ensamblador a lenguaje máquina.

Como ventaja con respecto al código máquina es que se tienen menos

instrucciones, los programas creados ocupan menos memoria. Las desventajas

son que dependen totalmente de la máquina en la que se está usando, lo que

impide la transportabilidad de los programas (posibilidad de ejecutar un mismo

programa en diferentes máquinas o entornos sin hacer modificaciones

importantes).

Lenguajes de alto nivel son los que manejan un lenguaje más cercano al que

utiliza una persona. Estos lenguajes permiten al programador olvidarse por

completo del funcionamiento interno de la computadora para la que están

diseñando el programa. Tan sólo necesitan de otros programas llamados

compiladores o intérpretes que entiendan las instrucciones como las

características de la máquina.

Ejemplos de lenguajes de alto nivel son: Fortran, Pascal, COBOL, SQL, C,

C++, SmallTalk, Java, LISP, PROLOG, ADA, MODULA-2, Basic, G, AutoLisp,

Python, etc. Éstos a su vez se suelen clasificar de acuerdo a los tipos de

problemas que permiten resolver y el estilo de programación que fomentan.

Traductores de lenguaje

Cuando se escribe un programa en un lenguaje de alto nivel, el conjunto de

instrucciones y todo lo que lo conforma se le llama archivo fuente, código fuente o

12

programa fuente. Los traductores de lenguaje son programas que traducen a su vez los

programas fuente a código máquina y se dividen en:

Compiladores

Intérpretes

Un intérprete es un traductor que toma un programa fuente, traduce y ejecuta

cada instrucción o bloque lógico antes de traducir y ejecutar el siguiente, es decir lo

hace instrucción a instrucción (ver figura 3).

Figura 3. Intérprete.

Un compilador es un programa que traduce los programas fuente a lenguaje

máquina y el programa traducido se le llama programa objeto o código objeto. El

compilador traduce instrucción a instrucción pero no las ejecuta.

La compilación y sus fases

La compilación es el proceso de traducción de programas fuente a programas

objeto, estos últimos normalmente conocidos como código máquina. Para conseguir el

programa final que se pude ejecutar se necesita de un programa llamado montador o

enlazador (linker), como se observa en la figura 4. En la sección 6: Lenguaje C se

detallarán las fases que se siguen al momento de compilar para obtener de un programa

fuente un programa ejecutable.

Figura 4. Fases de la compilación.

13

4 DATOS Y EXPRESIONES EN UN PROGRAMA

Todo programa de computadora tiene dos entidades a considerar: los datos y el

programa en sí.

Un dato es la expresión general que describe a los objetos con los cuales opera

una computadora. Existen dos clases de datos: simples (sin estructura) y estructurados

(compuestos). La principal característica de los datos simples es que ocupan sólo una

casilla de memoria y los datos estructurados se caracterizan por el hecho de que con un

nombre se hace referencia a un grupo de casillas de memoria, es decir, un dato

estructurado tiene varios componentes.

Los distintos tipos de datos se representan en diferentes formas en la

computadora, a nivel de máquina, un dato es un conjunto o secuencia de bits (dígitos 0

y 1) pero los lenguajes de alto nivel usan los siguientes datos simples:

Numéricos:

o entero, llamado de punto o coma fija, y es un subconjunto finito de los

números enteros.

o real, llamado de coma flotante o punto flotante4, que consiste en un

subconjunto finito de los números reales.

Caracter: es un conjunto finito y ordenado de símbolos que la computadora

reconoce. Con éstos en sucesión se forman cadenas de caracteres.

Lógico o booleano, indican los dos posibles valores: verdadero o falso.

Por otro lado, los datos estructurados más conocidos son: arreglos, cadenas de

caracteres y registros (en las secciones 11 y 12 se revisarán los arreglos y las cadenas de

caracteres, los registros forman parte del temario de Programación II).

Las constantes son elementos cuyo valor no cambia durante todo el desarrollo

del algoritmo o durante la ejecución del programa, pueden ser enteras, reales, lógicas,

de caracter o de cadena.

Una variable es un elemento cuyo valor puede cambiar durante el desarrollo del

algoritmo o ejecución de un programa. Es una localidad de memoria en una

computadora que almacena un valor, dicho valor puede ser entero, real, lógico o

caracter. Dependiendo del lenguaje de programación que se utilice, puede ser necesario

declarar una variable, esto es, declarar significa establecer desde un inicio, el tipo de

dato que puede almacenar una variable antes de almacenar dicho valor, ya que, si no se

declara no se puede usar a la variable, con ello, una vez que una variable se declara de

un cierto tipo, sólo puede almacenar valores de ese tipo, por ejemplo, si se declara de

tipo entera, sólo puede contener enteros pero no reales o caracteres; si se declara de tipo

4 La representación coma flotante o punto flotante es una forma de notación científica usada por la

computadoras para representar números racionales tanto extremadamente grandes como extremadamente

pequeños, de una manera eficiente y compacta con la que se puede realizar operaciones aritméticas.

14

real no puede contener valores lógicos, etc. Si se intenta dar un valor a una variable que

no es de su tipo, se genera un error de tipo. Sin embargo, existen también lenguajes de

programación que no obligan a declarar variables, así, una variable puede almacenar

valores de distintos tipos sin problema alguno.

Los nombres que se les asignan a los datos simples, por ejemplo: una variable o

constante, o a los datos estructurados, por ejemplo: un arreglo, se conocen como

identificadores. Un identificador se forma por medio de letras, dígitos y el caracter

guion bajo, comenzando siempre con un carácter ya sea en minúsculas o mayúsculas.

Ejemplos de identificadores que representan a variables de cierto tipo se muestran en la

tabla 3.

Una expresión es una combinación de operadores, operandos, paréntesis y

nombres de funciones especiales. Los operandos pueden ser constantes, variables u

otras expresiones. Los operadores puedes ser símbolos de operación: aritméticos,

lógicos (AND, OR, NOT) y/o relacionales (>, <, >=, <=, =, < >).

Tabla 3. Identificadores de variables.

Nombre de la variable (Identificador) Valor Tipo

Altura 2 Entero

Base 5 Entero

Radio 2.5 Real

simbolo_1 ‘a’ Caracter

Simbolo_2 ‘#’ Caracter

5 PROCESO DE RESOLUCIÓN DE PROBLEMAS UTILIZANDO

UN LENGUAJE DE PROGRAMACIÓN

Pueden ser identificadas dos etapas en el proceso de resolución de problemas

utilizando un lenguaje de programación (Cairó, 2006):

1. Fase de solución

2. Fase de implementación del diseño en un lenguaje de programación

5.1 Fase de solución

Análisis del problema: en esta etapa se debe definir claramente el problema y

entender lo que se pide, se deben detectar los datos o elementos que se

tienen para usar, llamados datos de entrada, e identificar los datos que se

van a dar como salida, conocidos como resultados.

Diseño o construcción de la solución: En este proceso se diseña o plantea el

algoritmo a utilizar, para esto se tienen diferentes herramientas:

descripciones narradas, diagramas o pseudocódigo.

15

Verificación del algoritmo (llamado también pruebas de escritorio) para

comprobar que el algoritmo es correcto para cualquier caso posible. Se

hace a mano siguiendo las instrucciones que se plantearon en el

algoritmo o bien usando algún software especial.

Diseño de un algoritmo

Los problemas complejos se pueden resolver eficazmente con la computadora

cuando se rompen en subproblemas que sean más fáciles de solucionar que el original.

Este método se suele denominar: divide y vencerás.

Descomponer un problema original en subproblemas más simples y, a

continuación dividir esos subproblemas en otros más simples, que pueden ser

implementados para su solución en la computadora, se denomina diseño descendente

(top-down design). Normalmente, los pasos diseñados en el primer esbozo del algoritmo

son incompletos e indicarán sólo unos pocos pasos. Tras esta primera descripción, éstos

se amplían en una descripción más detallada con más pasos específicos. Este proceso se

denomina refinamiento del algoritmo. Para problemas más complejos se necesitan con

frecuencia diferentes niveles de refinamiento antes de que se pueda obtener un

algoritmo claro, preciso y completo (Bermúdez, et al., 2003).

Las ventajas más importantes del diseño descendente son: el problema es más

comprensible al dividirse en partes más simples denominados módulos, las

modificaciones en dichos módulos son más sencillas de realizar y la comprobación del

problema es asequible. La programación de cada módulo se hace mediante:

programación estructurada.

La programación estructurada es un conjunto de técnicas para desarrollar

algoritmos fáciles de escribir, verificar, leer y modificar; aquí algunas de sus

características:

Tiene un solo punto de entrada y uno de salida.

Toda acción del algoritmo es accesible, es decir, existe al menos un camino que

va desde el inicio hasta el fin del algoritmo, se puede seguir y pasa a través de

dicha acción.

No posee lazos o bucles (ciclos) infinitos.

Cabe mencionar que existen distintas clasificaciones para categorizar a los

lenguajes de programación de alto nivel, así, además de los lenguajes de programación

imperativos o procedurales (que se basan en la programación estructurada) existen los

lenguajes de programación orientados a objetos, declarativos, etcétera.

Ahora bien, en la programación estructurada, un programa puede ser escrito

utilizando únicamente tres tipos de estructuras: secuenciales (instrucciones que se

ejecutan una tras otra en forma consecutiva), selectivas (para evaluar condiciones y

tomar decisiones sobre qué camino seguir) y repetitivas (para procesar un conjunto de

datos más de una vez, dependiendo del resultado de una condicional); todas ellas se

explican a detalle en las secciones 8 y 9. Cabe señalar que, dichas estructuras son el

corazón de la programación estructurada y por ende, las bases en el temario de la

asignatura Programación I.

16

Métodos para representar algoritmos

Existen tres métodos para representar un algoritmo: descripción narrada, diagramas

de flujo y pseudocódigo; a continuación se detalla cada uno de ellos.

Descripción narrada: consiste en hacer un relato de la solución en lenguaje

natural.

Pseudocódigo: proviene de la composición de las dos palabras: pseudo (falso) y

código (instrucción). Utiliza palabras en lenguaje natural (generalmente inglés,

aunque puede ser en cualquier idioma, por ejemplo el castellano, como lo utiliza

la herramienta de software PSeInt, abreviatura de Pseudo Intérprete) mezcladas

con instrucciones de programación y palabras clave reservadas para representar

una acción en específico. No tiene que escribirse en un lenguaje de

programación particular ni existe una única forma de expresarlo o estándar, esto

depende de cómo se expresa cada persona. Como todo método para representar

algoritmos se centra en los aspectos lógicos de la solución del problema no en

un lenguaje de programación específico.

Diagrama de flujo: flowchart u ordinogramas: es la representación gráfica de

un algoritmo, es decir, representa la solución del problema utilizando símbolos

que tienen un significado único y específico. En la tabla 4 se presentan los

símbolos que satisfacen las recomendaciones de la International Organization

for Standardization (ISO) y el American National Standars Institute (ANSI).

Actualmente existen diversas herramientas de software que permiten tanto

elaborar como probar el funcionamiento de un diagrama de flujo a través de

trazas, sirviendo además para el aprendizaje de algoritmos estructurados.

Arellano, Nieva, Solar y Arista (2012) destacan a las siguientes herramientas:

- DFD (siglas de Data Flow Diagrams, Diagramas de Flujo de Datos, última

actualización en octubre de 2008),

- RAPTOR (acrónimo del inglés Rapid Algorithmic Prototyping Tool for

Ordered Reasoning; actualizado constantemente hasta la fecha),

- PSeInt (abreviatura de Pseudo Intérprete; facilita escribir algoritmos en

pseudocódigo generando el diagrama de flujo a partir de éste; también se

actualiza constantemente hasta la fecha),

- Progranimate,

- Otros

Cada una de las herramientas mencionadas utiliza su propia simbología para

algunos de los significados que se presentan en la tabla 4, no respetando el

estándar ISO y ANSI, así, en la tabla 5 se muestran los símbolos equivalentes de

las herramientas DFD, RAPTOR y PSeInt, siendo estas últimas dos las más

populares.

17

Tabla 4. Simbología en un diagrama de flujo según la ISO y el ANSI.

Adaptado de Cairó (2006, pp. 5 y 6)

Símbolo Significado Símbolo Significado

Se utiliza para marcar el

inicio y el fin del

diagrama de flujo.

Representa conexión de

una parte del diagrama

con otra parte del

diagrama entre páginas

diferentes.

Se utiliza para introducir

datos de entrada.

Expresa lectura.

Se utiliza para representar

la impresión de un

resultado, expresa salida

o escritura.

Expresa conexión de una

parte del diagrama con

otra parte de dicho

diagrama en una misma

página.

Representa un proceso, en

su interior se colocan

asignaciones, operaciones

aritméticas, etc.

Flechas de dirección o

flujo del diagrama.

Flechas de dirección o

flujo del diagrama.

Se utiliza para

representar una decisión

múltiple, en su interior

se almacena un selector

y dependiendo de su

valor se sigue por una de

las ramas o caminos.

Representa una decisión,

en su interior se coloca

una condición y

dependiendo de la

evaluación se sigue uno u

otro camino.

Se utiliza para expresar

un módulo de un

problema (subproblema)

que hay que resolver

antes de continuar con el

flujo normal del

diagrama.

acciones

Se utiliza para representar

al ciclo for o para, es

decir, un conjunto de

acciones se repiten un

número determinado de

veces, según lo dicte la

condición especificada.

acciones

Se utiliza para

representar al ciclo while

o mientras, es decir, un

conjunto de acciones se

repiten mientras la

condición es verdadera.

acciones

Se utiliza para representar

al ciclo hasta que: un

conjunto de acciones se

realizan mientras la

condición es falsa, o en

otras palabras, el ciclo

termina hasta que la

condición sea verdadera,

aunque al menos una vez

se realizan las acciones.

selector

18

Tabla 5. Simbología en un diagrama de flujo según DFD, RAPTOR y PSeInt.

Significado Simbología DFD Simbología RAPTOR Simbología PSeInt

Entrada o

lectura

Salida o

escritura

Ciclo while o

mientras

Ciclo for o

para

No existe en

RAPTOR

Ciclo repetir

Hasta que

Módulo de un

problema

(subproblema)

Decisión

múltiple

Para

acciones

Fin (para)

MQ

acciones

Fin (MQ)

No existe en

DFD ni en

RAPTOR

acciones

No existe

en

RAPTOR

acciones

No existe

en DFD

19

Reglas para la construcción de un diagrama de flujo

Según Cairó (2006):

1. Todo diagrama de flujo debe tener un inicio y un fin, construyéndose de arriba

hacia abajo (top-down) y de izquierda a derecha (left-to-right).

2. Las líneas utilizadas para indicar la dirección del flujo del diagrama deben ser

rectas: verticales u horizontales y estar conectadas a otros símbolos: lectura,

decisión, salida, proceso, etc., pero no pueden quedar sin apuntar a un símbolo.

3. La notación utilizada en el diagrama de flujo debe ser independiente del lenguaje

de programación a utilizarse. La solución presentada se puede escribir

posteriormente en diferentes lenguajes de programación.

4. Es conveniente colocar comentarios que ayuden a entender lo que se está

realizando.

5.2 Fase de implementación del diseño

Codificar o escribir el programa, traducir el algoritmo a un específico lenguaje

de programación (el concepto de codificar se revisó en la sección 1:

Preliminares, conceptos básicos).

Ejecutar o correr el programa: que lleve a cabo cada una de las instrucciones

que lo conforman.

Verificar que funcione correctamente el programa.

Instrucciones generales de un programa

Tipos de instrucciones básicas que contiene cualquier lenguaje de programación:

1. Instrucciones de inicio/fin.

2. Instrucciones de asignación.

3. Instrucciones de lectura, conocidas también como instrucciones de entrada.

4. Instrucciones de escritura, conocidas también conocidas como instrucciones de

salida.

5. Instrucciones de bifurcación o decisión.

Elementos básicos de un programa:

1. Palabras reservadas son palabras que tienen un significado especial para un

compilador por lo que no pueden utilizarse para cualquier otro propósito distinto

para el que fueron identificadas.

2. Identificadores, por ejemplo: nombres de variables, constantes o funciones

declaradas por el usuario.

20

3. Caracteres especiales, por ejemplo: coma, apóstrofo, comillas, etc.

4. Expresiones

5. Estructuras: secuenciales, selectivas, repetitivas

6. Contadores: variables cuyo valor se incrementa o decrementa en una cantidad

constante en cada iteración si se utilizan ciclos.

7. Acumuladores: variables cuya misión es almacenar cantidades resultantes de

operaciones sucesivas como sumas o multiplicaciones por ejemplo. Realiza la

misma función que un contador, con la diferencia de que el incremento o

decremento en cada operación es variable en lugar de constante.

8. Interruptores o conmutadores (switch) a veces se les denomina indicadores o

banderas, son variables que pueden tomar diversos valores a lo largo de la

ejecución del programa y que permite comunicar información de una parte a otra

del mismo. Los interruptores pueden tomar dos valores diferentes: 1 y 0

(encendido/apagado o abierto/cerrado).

Tipos de errores presentados en la fase de implementación

Errores de sintaxis. Se presentan al momento de compilar un programa,

generalmente por cometer errores al escribir una instrucción o los signos de

puntuación correctos violando la sintaxis con la que se escriben los programas,

según el lenguaje que se esté usando. Así, el compilador indica tanto la línea

donde se ha cometido el error como la clase de error para poder corregirlo.

Errores lógicos. Se presentan al momento de ejecutar un programa, ya que no

se obtienen los resultados esperados, generalmente porque el algoritmo que se

planteó no fue el correcto o porque la traducción o implementación al lenguaje

de programación utilizado no fue el adecuado, malinterpretando el algoritmo.

Esta clase de errores son más difíciles de corregir.

Errores en tiempo de ejecución. Se presentan al momento de ejecutar o correr

el programa, por ejemplo, si nuestro programa hace referencia al lector de una

memoria externa y dicho lector no está listo con una memoria, entonces marcará

un error; otro ejemplo sería si se espera que el usuario ingrese un dato de tipo

entero y lo que ingresa es un dato de tipo caracter o decimal, entre otros

ejemplos.

Warnings. No propiamente son errores, sino advertencias de que algo debería

cambiarse porque es obsoleto o puede causar conflictos.

21

6 LENGUAJE C

Historia breve del lenguaje C

El lenguaje C fue creado por el científico estadounidense Dennis Ritchie en

1972 como derivado de un lenguaje de programación conocido como B. Hacia finales

de los 70, el lenguaje C evolucionó a lo que hoy se le conoce como C tradicional y su

rápida expansión sobre varios tipos de computadoras, denominadas plataformas de

hardware, provocó muchas variantes que, aunque similares no eran compatibles. Esto

generó problemas para los desarrolladores de programas que necesitaban escribir

códigos que pudieran funcionar en varias plataformas. Por tanto, en 1989 se aprobó un

estándar para el lenguaje C con una definición no ambigua e independiente de la

máquina donde se desarrollara. El documento que plasma esto se conoce como

ANSI/ISO5 9899: 1990, por ello, esta versión se conoce como ANSI C, es la versión

estandarizada que se utiliza en todo el mundo (Deitel y Deitel, 1995).

Características generales del lenguaje C

Es un lenguaje de propósito general, es decir, puede ser usado para crear

aplicaciones con distintos objetivos o propósitos, por ejemplo: realizar cálculos

matemáticos, crear sistemas operativos (el kernel del sistema operativo

GNU/Linux y Mac OS X están escritos en C), gestionar bases de datos

(PostgreSQL está escrito en C), comunicar computadoras, capturar datos, editar

imágenes (GIMP en su mayoría está escrito en lenguaje C), generar aplicaciones

científicas (el paquete R en estadística y MATLAB en cálculo están escritos en

el lenguaje C), industriales, de simulación, etcétera.

El código que produce es eficiente y cuenta con características de los lenguajes

de bajo nivel que permiten realizar implementaciones óptimas (rápidas).

Es portátil, es decir, los códigos o programas generados con el lenguaje pueden

ejecutarse en cualquier arquitectura de computadora o cualquier sistema

operativo con variantes mínimas.

Es estructural, en otras palabras, el programa se divide en subprogramas o

segmentos con estructuras simples de secuencia, selección e iteración.

Es sensitivo al (del) contexto, es decir, distingue instrucciones e identificadores

escritos en mayúsculas y minúsculas, por ejemplo, una variable identificada

5 ANSI (American National Standards Institute, Instituto Nacional Estadounidense de Estándares) es una

organización sin fines de lucro que supervisa el desarrollo de estándares para productos, servicios,

procesos y sistemas en los Estados Unidos. Es miembro de la Organización Internacional para la

Estandarización (ISO). También coordina estándares del país estadounidense con estándares

internacionales, de tal modo que los productos de dicho país puedan usarse en todo el mundo. Esta

organización aprueba estándares que se obtienen como fruto del desarrollo de tentativas de estándares por

parte de otras organizaciones, agencias gubernamentales, compañías y otras entidades. Estos estándares

aseguran que las características y las prestaciones de los productos son consistentes, es decir, que la gente

use dichos productos en los mismos términos y que esta categoría de productos se vea afectada por las

mismas pruebas de validez y calidad (“Instituto Nacional Estadounidense de Estándares”, 2015).

22

como A, es diferente a la variable identificada como a, o bien, la instrucción If

no será reconocida por el compilador, más la instrucción if sí lo será.

A continuación se detallan las tres partes principales de las que consta todo

sistema en C:

Un entorno

Biblioteca estándar

El lenguaje en sí

Entorno

Existen diferentes IDEs (acrónimo del inglés: Integrated Development

Enviroment y en español: Entorno Integrado de Desarrollo) que incluyen o integran una

interfaz visual con iconos, menús y ventanas para trabajar con comodidad, con un editor

de textos para escribir el código fuente en el lenguaje C, uno o varios compiladores,

enlazadores, depuradores y demás herramientas. Suelen presentar diferentes asistencias

para la escritura de programas, como: sugerencias de autocompletado, coloreado de la

sintaxis del código fuente, ayuda acerca del lenguaje, etc.

Es necesario recalcar que un IDE no es un compilador, este último es parte de

todo el IDE. Novara (201) presenta algunos ejemplos de IDEs:

DevC++ (disponible sólo para el sistema operativo Windows)

Visual Studio (disponible sólo para el sistema operativo Windows)

Eclipse (disponible para los sistemas operativos Windows, Mac y GNU/Linux)

Monodevelop (disponible para los sistemas operativos Windows, Mac y

GNU/Linux)

ZinjaI, (disponible para los sistemas operativos Windows, Mac y GNU/Linux)

Entre otros

Ya sea que se utilice un IDE o no, en general, cuando se crea un programa en C

se siguen diferentes fases para obtener un código ejecutable a partir del código fuente.

La diferencia con utilizar un IDE está en que el usuario o programador no usa comandos

para llevar a cabo las diferentes fases, sino los iconos y menús que proporciona el

propio IDE.

En la figura 5 se pueden observar las diferentes fases que se siguen para obtener

un programa ejecutable. Primero, el programador escribe el programa en C utilizando

un editor de texto, generando con ello un archivo fuente con la extensión .c. Segundo,

se hace el llamado al compilador que traduce el programa fuente a código máquina, éste

conocido también como código objeto (ver apartado Traductores de lenguaje de la

sección 3: Lenguajes de programación), pero justo antes de ello, el propio compilador

llama a un programa conocido como preprocesador, el cual manipula el programa

fuente, incluyendo otros archivos a compilar y reemplazando símbolos especiales con

texto de programa (por ejemplo, cambiando el nombre de una constante por su

23

correspondiente valor). Tercero, el compilador propiamente traduce el código fuente

para generar el código objeto, éste es un código aún no ejecutable, pues generalmente

estará incompleto. Cuarto y último, para obtener el código binario final, se ejecuta el

enlazador, el cual se encargará de vincular el código objeto con el código de las

funciones a las que se haya hecho referencia en el programa fuente pero cuyo código se

encuentra en otro u otros archivos, por ejemplo, en la biblioteca estándar (este concepto

será revisado en el siguiente apartado Biblioteca Estándar de esta sección) o bibliotecas

creadas por los propios programadores (Deitel y Deitel, 1995).

Cabe mencionar que, cada IDE puede hacer uso de uno o varios tipos de

compiladores para C, entre los existentes están:

GCC

DJGPP

Miracle C

LCC Win 32

Intel C++ Compiler (conocido como ICC o ICL)

Mingw

Borland C++

Biblioteca Estándar

La biblioteca estándar de C contiene una amplia colección de funciones que

resuelven problemas particulares y comunes, por ejemplo: cálculos matemáticos,

manipulación de cadenas o caracteres, detección de errores, mandar o recibir datos hacia

o desde los dispositivos de salida y entrada respectivamente, obtención de fecha y hora,

etc. Estas funciones mejoran tanto el rendimiento de los programas como la portabilidad

pero no forman parte en sí del lenguaje C (Deitel y Deitel, 1995). Las funciones que

pertenecen a la biblioteca estándar ayudan a ahorrar tiempo al programar, evitando que

se reescriba código para resolver un problema cuando la función ya existe, pues

permiten la reutilización de código.

Por otro lado, la biblioteca estándar hace uso de archivos de cabecera, los cuales

contienen tanto los prototipos de función de cada una de las funciones a las que hacen

referencia como las definiciones de varios tipos de datos y constantes requeridas para

dichas funciones. Un archivo de cabecera tiene un nombre con la extensión de archivo

.h y se incluye en un programa a través de la instrucción include como se detalla a

continuación.

#include <Nombre_del_archivo_de_cabecera.h>

Lo anterior indica al preprocesador (concepto revisado en el apartado Entorno de

esta sección) que incluya el archivo identificado por Nombre_del_archivo_

de_cabecera.h, el cual se delimita entre los símbolos < >, va seguido del carácter

almohadilla y la palabra reservada include. Ejemplos de nombres de archivos de

cabecera son: stdio.h, stdlib.h, math.h, string.h, time.h, cyte.h, errno.h, etc. Estos

ficheros se encuentran almacenados en el directorio por default o por defecto donde está

almacenado el compilador al momento de instalarlo.

24

Figura 5. Fases para producir código ejecutable a partir de código fuente en C.

25

El archivo de cabecera ctype.h contiene, por ejemplo, las funciones prototipo

que pueden ser utilizadas para convertir letras de minúsculas a mayúsculas o viceversa;

el archivo de cabecera math.h contiene prototipos para las funciones matemáticas

trigonométricas, raíz cuadrada, potencia, etc.; el archivo de cabecera stdlib.h contiene

entre otras cosas, prototipos de función para generar números aleatorios.

Ahora bien, un prototipo de función (el tema de funciones se verá a detalle en la

sección 13: Funciones) es útil para: indicar al compilador el tipo de dato que regresará

la función (entero, real, caracter, etc.), la cantidad de argumentos que espera recibir la

función para procesarlos, así como el tipo y el orden en que la función espera recibir a

dichos argumentos. De esta forma, el compilador puede determinar errores por parte del

programador al momento de llamar a una función, evitando problemas en tiempo de

ejecución de un programa (Deitel y Deitel, 1995).

Tanto en el resto de esta sección como en las secciones posteriores, se

describirán los elementos que conforman al lenguaje C, el cual se aprenderá en esta

asignatura.

Estructura de un programa

Todo programa en C está conformado por funciones (el tema de funciones será

revisado con mayor detalle en la sección 13: Funciones), ya sean las que se encuentran

en la biblioteca estándar o las que son creadas por el propio programador para resolver

un problema en particular. Además, consta de una función indispensable llamada

main( ) a partir de donde el programa comienza a ejecutarse. Después, contiene la llave

izquierda que abre { para dar inicio al cuerpo de la función principal y termina con la

llave derecha que cierra }. Estas llaves y la porción de programa o instrucciones que

existe entre ellas se les conocen como bloque (Deitel y Deitel, 1995). Este bloque puede

estar constituido por varios elementos que conforman al lenguaje C: operadores

relacionales, de asignación, condicionales, variables, constantes, ciclos, etc., todos ellos

se revisarán a lo largo de este documento.

En general, un programa escrito en el lenguaje C puede contener las siguientes

secciones y/o elementos:

Sección include (se explicó en el apartado Biblioteca Estándar de esta misma

sección).

Cabe mencionar que, cuando se van a agregar funciones que no pertenecen a la

biblioteca estándar, sino que fueron elaboradas por el programador y forman

parte de un archivo de cabecera, entonces, esta sección tiene la siguiente

sintaxis:

#include “Nombre_del_archivo”

Como se ve, lo único que se modifica son los signos de mayor y menor que, por

las comillas dobles; el nombre del archivo de cabecera no precisamente debe

contener la extensión .h. Con esta forma se indican dos cosas al compilador: que

incluya el archivo especificado en el programa fuente y que dicho archivo se

encuentra en una ruta distinta a la que se tiene por defecto. Si entre comillas se

26

especifica la ruta además del nombre del archivo, entonces el compilador

buscará al archivo en esa ruta, pero si no se especifica, el compilador buscará al

archivo en el mismo directorio donde se encuentra el fichero fuente.

Sección de declaración de constantes (se explicará más adelante en esta sección)

y tipos de datos estructurados.

Sección de declaración de variables globales (se explicará en la sección 13:

Funciones).

Declaración de prototipo de funciones o forward (se explicará en la sección 13:

Funciones).

Si no existieran funciones prototipo, puede existir sólo un bloque de funciones

creadas por el programador.

Función main o función principal.

Si se declararon prototipo de funciones y la programación de cada función se

encuentra en el mismo archivo fuente, generalmente constituyen la sección final

del programa.

Ejemplo de una estructura:

/*Sección include, agregado de archivos de cabecera de la biblioteca

estándar*/

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

/*Declaración de constantes*/

#define TAM 99

#define FREC 10

/*Prototipo de función*/

void moda(int []);

/*Función principal*/

int main( )

{

int i, respuestas[TAM];

srand(time(NULL));

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

respuestas[i]=1 + (rand()%9);

moda(respuestas);

return 0;

}

27

/*Función declarada por el programador*/

void moda(int resultados[ ])

{

int i,j,mayor=0,posicion,frecuencia[FREC]={0};

for(i=0;i<TAM;i++) ++frecuencia[resultados[i]];

printf("\n%s%15s%15s\n","Respuesta", "Frecuencia","Histograma");

for(i=1;i<FREC;i++)

{

printf("%8d%10d ",i,frecuencia[i]);

if (frecuencia[i]>mayor)

{

mayor=frecuencia[i];

posicion=i;

}

for(j=1;j<=frecuencia[i];j++) printf("*");

printf("\n");

}

printf("\nLa moda es = %d y se repitio %d veces",

posicion,mayor);

return;

}

Identificadores

Son nombres dados a constantes, variables, funciones, tipos, etiquetas de un

programa, y están formados por una secuencia de letras (mayúsculas y/o minúsculas),

dígitos y el caracter guion bajo. El primer carácter de un identificador debe ser una letra

o el carácter de subrayado, y serán significativos los primeros 31 caracteres, toda

carácter más allá de este límite será ignorado por cualquier compilador (Ceballos,

1997).

Palabras clave o reservadas del lenguaje C

Existen un total de 32 palabras clave que están reservadas para uso exclusivo

del compilador C y que tienen un significado especial para dicho compilador, por lo que

no pueden utilizarse como identificadores. Algunas versiones de compiladores pueden

tener palabras adicionales (Ceballos, 1997).

Las palabras reservadas son:

Palabras que se refieren a los tipos de datos que se pueden utilizar: int,

float, long, double, short, char, unsigned,

signed, volatile, const, enum, static, typedef,

sizeof.

28

Palabras que se refieren a instrucciones que controlan el flujo de datos: if, else, switch, case, default, break, for,

while, do, continue, goto.

Otras: struct, return, union, register, extern,

void, auto.

Tipos de dato estándar

Los tipos de datos básicos en el lenguaje C son:

1. caracter (que se declara con la palabra reservada char) para poder

manejar cualquier caracter del teclado;

2. real (que se declara con la palabras reservadas double o float) para

manejar números con parte decimal, y

3. entero (que se declara con la palabra reservada int) para manejar

números enteros.

Con el tipo de dato caracter, se puede formar una variable de tipo cadena, que es

una secuencia de caracteres entre comillas. La tabla 6 muestra una lista detallada de los

tipos de datos que existen en el lenguaje C.

Tabla 6. Tipos de datos estándar en el lenguaje C.

De Ceballos (1997, pp. 54-61).

Tipo de dato Lo que representa

char

signed char

Cualquier caracter del teclado, que se forma con 1 byte.

Almacena un valor entero en el rango -128 a 127

correspondiente a un carácter del código ASCII (el

código ASCII se ve en la sección 12: Caracteres,

cadenas de caracteres y arreglos de caracteres), aunque

solamente los valores del 0 a 127 son equivalentes a un

carácter

unsigned char

Cualquier caracter del teclado, que se forma con 1 byte.

Almacena un valor en el rango 0 a 255, valores

correspondientes a los números ordinales de los 256

caracteres ASCII

int

signed int

short int

Un número entero dentro del intervalo cerrado

[-32768, 32767] formado por 2 bytes (aunque algunos

compiladores pueden utilizar 4 bytes, haciendo crecer el

rango)

unsigned int

unsigned short int

Un número entero positivo dentro del intervalo cerrado

[0, 65535] formado por 2 bytes (aunque algunos

compiladores pueden utilizar 4 bytes, haciendo crecer el

rango)

29

long int

signed long int

Un número entero dentro del intervalo cerrado

[-2147483648, 2147483647] formado por 4 bytes

unsigned long int Un número entero positivo dentro del intervalo cerrado

[0, 4294967295] formado por 4 bytes

float

Un número real (de simple precisión, es decir, con

precisión de 6 a 7 dígitos significativos) dentro del

intervalo cerrado [-3.402823E+38, -1.701411E-38] para

números negativos y el intervalo cerrado [1.7014eeE-38,

+3.402823E+38] para números positivos; formado con 4

bytes.

Para formarse el número real en simple precisión se

utilizan 23 bits para la mantisa (quien define la

precisión), 1 bit para el signo +/- de la mantisa, 7 bits

para el exponente (quien define el rango) y 1 bit para el

sigo del exponente.

double

Un número real (en doble precisión, es decir, con

precisión de 15 a 16 dígitos significativos) dentro del

intervalo cerrado [-1.79769E+308, -2.22507E-308] para

números negativos y el intervalo cerrado [2.22507E-308,

1.79769E+308] para números positivos, formado con 8

bytes.

Para formarse el número real en doble precisión se

utilizan 52 bits para la mantisa (quien define la

precisión), 1 bit para el signo +/- de la mantisa, 10 bits

para el exponente (quien define el rango) y 1 bit para el

sigo del exponente.

long double

Un número real (en doble precisión formato largo, con no

más de 19 dígitos significativos) dentro del intervalo

cerrado [-1.189731E+4932, 1.189731E+4932] formado

por 10 bytes. Aunque el ANSI C no garantiza un rango y

una precisión mayores que las de double y por tanto, el

rango y la precisión no están normalizados (pueden ser

64 bits para la mantisa y 16 para el exponente).

Declaración de variables y constantes

Como se mencionó en la definición del término variable en la sección 4: Datos y

expresiones en un programa, todas las variables que se vayan a utilizar en un programa

escrito en el lenguaje C deben declararse antes de usarse. Una variable en C se declara

de la siguiente manera:

tipo_de_dato identificador_1, identificador_2, …, identificador_n ;

donde tipo_de_dato puede ser: int, char, double, etc.

Por ejemplo:

30

double radio;

declara una variable llamada radio que solo puede almacenar valores de

tipo real.

int k;

declara una variable que sólo puede almacenar valores de tipo entero.

Una constante en C se define de la siguiente manera:

#define identificador valor

donde valor puede ser un caracter, un número entero, real o una cadena.

El identificador generalmente se forma con letras en mayúscula para

poder diferenciar rápidamente nombres de constantes de nombres de

variables, pero no es obligatorio.

Por ejemplo:

#define E 2.718281828459

declara la constante llamada E para identificar al número de Euler con

sólo 12 decimales; el valor de dicha constante no puede ser alterado a lo

largo del programa.

Asignación simple

El signo de igualdad = es el operador básico de asignación. Con este operador

se inicializa el valor de una variable y se le cambia su valor a lo largo de un programa.

variable = expresión ;

Por ejemplo, i = 7; donde la variable i almacena el valor inicial de 7

NOTA IMPORTANTE: La inicialización de una variable en el mismo

momento en que es declarada reduce el tiempo de ejecución de un programa.

Proposiciones

Cuando una expresión va seguida de un punto y coma se convierte en

proposición. Ejemplos: i = 7; printf (“Hola”);

Operadores aritméticos

La tabla 7 muestra los operadores aritméticos permisibles en el lenguaje C.

31

Tabla 7. Operadores aritméticos.

Adaptado de Bermúdez et al. (2003, p. 37)

Operador Operación que realiza

+ Suma enteros o reales

- Resta enteros o reales

* Multiplica enteros o reales

/ Divide enteros o reales. Si ambos operandos son enteros el resultado es

entero, en el resto de los casos el resultado es real

% Devuelve el módulo o resto de la división de enteros solamente

-

(unario)

El operando entero o real es multiplicado por -1

Prioridad de los operadores

La tabla 8 muestra el orden de prioridad dado a cada operador aritmético y de

asignación, es decir, si se tiene una expresión donde se involucran operadores

aritméticos de suma, resta, multiplicación etc., sin asociar a las operaciones con

paréntesis, entonces es necesario saber cuáles operaciones se realizan primero y cuáles

después de acuerdo al orden de prioridad.

Tabla 8. Prioridad de operadores aritméticos.

Adaptado de Bermúdez et al. (2003, p. 37)

Operadores Asociatividad

-(unario) Derecha a izquierda

* / % Izquierda a derecha

+ - Izquierda a derecha

= Derecha a izquierda

Como se aprecia en la tabla 8, el operador unario es el que tiene mayor orden de

prioridad, es decir, se evaluará primero en una expresión donde haya varios operadores

aritméticos, seguido de la multiplicación, la división y el módulo. En caso de haber más

de un mismo operador con el mismo orden de prioridad, entonces se evaluarán de

izquierda a derecha según se encuentren colocados en la expresión; después se llevarán

a cabo la suma y la resta siguiendo la misma regla explicada antes; finalmente, si en la

expresión también hay un operador de asignación, entonces, una vez que se evalúa toda

la expresión, es decir, una vez que se tiene el resultado calculado se asignará a la

variable especificada y si existiera más de una asignación entonces el orden en que será

asignado el resultado es de derecha a izquierda y no de izquierda a derecha como en los

demás operadores. Por ejemplo:

h = co * co + ca * ca

32

En la expresión se tiene asignación, multiplicación y suma; siguiendo el orden

de prioridad, lo primero que se evaluará será la multiplicación, después la suma

y finalmente la asignación. Como existen dos multiplicaciones, entonces, se

realizará primero la de la izquierda y después la de la derecha, es decir, primero

se multiplicará co*co, después se multiplicará ca*ca, y ambos resultados se

sumarán, finalmente el resultado de esa suma se asignará a la variable h.

Aunque es necesario conocer, entender y aprender el orden de prioridad de los

operadores, si se tiene duda sobre qué operaciones se realizan primero y qué

operaciones después, se pueden agrupar las operaciones en paréntesis, ya que estos

últimos siempre tendrán el orden de prioridad más alto, es decir, lo que esté encerrado

entre paréntesis siempre se evaluará primero. A lo largo del curso se analizarán varios

ejemplos aplicativos de esta clase de expresiones.

Operadores de relación y lógicos

Cada operador relacional toma dos expresiones como operando y da como

resultado el valor 0 o 1. En el lenguaje C cualquier valor distinto de 0 se considera

verdadero y el valor 0 se considera falso. La tabla 9 muestra los operadores

relacionales existentes en el lenguaje C.

Tabla 9. Operadores relacionales.

Adaptado de Bermúdez et al. (2003, p. 38)

Operador Operación Ejemplos

< Primer operando menor que el

segundo

a < 3

> Primer operando mayor que el

segundo

b > w

<= Primer operando menor o igual que

el segundo

-7.7 <= 99.335

>= Primero operando mayor o igual que

el segundo

-1.3 >= (2.0 * x + 3.3)

== Primer operando igual que el

segundo

c == ’w’

!= Primer operando distinto del segundo x != -2.5

Los operadores lógicos, al igual que los operadores anteriores, cuando se aplican

a expresiones producen los valores 0 o 1 de acuerdo a su tabla de verdad. La tabla 10

muestra los operadores lógicos existentes en el lenguaje C.

Tabla 10. Operadores lógicos.

Adaptado de Bermúdez et al. (2003, p. 38)

Operador Operación Ejemplo

&&

AND lógico.

Al utilizarse en estructuras condicionales dará

como resultado el valor lógico 1 si todos los

operandos a evaluar son distintos de 0. Si uno

(z<x) && (y>w)

Si el valor que almacena

la variable z es menor que

el valor que almacena la

33

de ellos es 0 el resultado es el valor lógico 0.

variable x, entonces el

resultado de esa

evaluación es 1; si el valor

que guarda la variable y es

mayor que el valor que

guarda la variable w,

entonces el resultado de

esa evaluación es 1, por

tanto 1 AND 1 es 1

H<=0 && 5

Si el valor que contiene la

variable H es mayor a 0,

entonces el resultado de

esa evaluación es 0; el

valor 5 no se compara con

otro valor, pero es

diferente de 0, por tanto,

la evaluación de esa

expresión es 1, entonces,

0 AND 1 es 0

||

OR lógico.

Al utilizarse en estructuras condicionales dará

como resultado el valor lógico 0 si los

operandos a evaluar son todos cero. Si uno de

los operandos tiene un valor distinto de 0, el

resultado es 1.

(x==y) || (z!=p)

Si el valor que almacena

la variable x es igual al

que almacena y, entonces

el resultado de esa

evaluación es 1; si el valor

que contiene la variable z

es distinto a lo que

contiene p, entonces el

resultado de esa

evaluación es 1; por tanto,

1 OR 1 es 1.

Si el valor que almacena x

es distinto de y, entonces

el resultado de esa

evaluación es 0; si el valor

de z es diferente al de p,

entonces el resultado de

esa evaluación es 1, así, 0

OR 1 es 1.

Si ambas evaluaciones son

falsas, entonces se tiene 0

OR 0 es 0.

34

!

NOT lógico.

Al utilizarse en estructuras condicionales dará

como resultado el valor lógico 0 si el

operando a evaluar tiene un valor distinto de 0

y 1 en caso contrario.

!a

Si a vale 8, como es

distinto de cero, entonces

la evaluación se toma

como verdadera, al

emplearse la negación, el

resultado de la evaluación

será falsa, es decir, 0.

Si a vale 0, entonces se

considera el valor de

verdad falso, al negarse,

se obtiene el valor

verdadero, es decir, 1.

Operadores de asignación

Además del operador de asignación simple presentado en esta sección, existen

los operadores de asignación que se muestran en la tabla 11.

Tabla 11. Operadores de asignación.

Adaptado de Bermúdez et al. (2003, p. 39)

Operador Operación

= Asignación simple

+= Suma y después asigna

-= Resta y después asigna

*= Multiplica y después asigna

/= Divide y después asigna

%= Obtiene el módulo y después

asigna

++ Incrementa una unidad

-- Decrementa una unidad

Si expr1 y expr2 son expresiones y op un operador aritmético, entonces:

expr1 op= expr2 es equivalente a expr1 = (expr1) op (expr2)

Ejemplos:

35

c += 3 es equivalente a c = c+3, si c tiene almacenado el valor de 8,

después de aplicar el operador +=, c valdría 11, es decir, 8+3, y el resultado se

asigna a la misma variable c, por tanto, c pierde el valor anterior de 8 y

almacena ahora 11.

k *= 3+x es equivalente a k = k*(3+x), si k tuviera almacenado el valor

de 2, y x el valor de 5, entonces, después de aplicar el operador *=, k valdría 16,

es decir, se suma el valor de la variable x, el cual es 5, con el valor 3, el

resultado es 8, después ese valor se multiplica por lo que contiene k, es decir,

por 2, resultando 16 y ese valor se almacena en la propia variable k, es decir, k

pierde el valor inicial de 2 y ahora almacena 16.

NOTA IMPORTANTE 1: una expresión con un operador de asignación (como

en c+=3) se compila más aprisa que la expresión equivalente expandida (c = c+3)

porque en la primera expresión, la variable c se evalúa únicamente una vez, en tanto que

en la segunda se evalúa dos veces.

NOTA IMPORTANTE 2: Los nombres de variables se dice que son lvalues

(por “left values”) porque pueden ser utilizados en el lado izquierdo de un operador de

asignación. Las constantes se dice que son rvalues (por “right values”) porque sólo

pueden ser utilizadas en el lado derecho de un operador de asignación. Los lvalues

también pueden ser utilizados como rvalues, pero no al revés.

Operadores incrementales y decrementales

Los operadores de incremento ++ y decremento -- son unarios y tienen la misma

prioridad que el operador unario -, éstos se asocian de derecha a izquierda. Tanto ++

como -- se pueden aplicar a variables pero no a constantes o expresiones. Además

pueden estar en la posición de prefijos o sufijos, con diferentes significados posibles.

Incremento: ++, añade o suma 1 a una variable

Decremento: --, resta 1 a una variable

Ejemplo:

x = x + 1 es equivalente a ++x

si x vale 10, después de aplicar el operador ++, x valdría 11, es decir, se toma el

valor inicial de x, el cual es 10 y se le suma 1, cuyo resultado es 11, este

resultado se almacena en la propia variable x, por tanto, x pierde el valor inicial

10 y almacena en su lugar el valor 11.

x = x - 1 es equivalente a --x

si x vale 10, después de aplicar el operador --, x valdría 9, es decir, se toma el

valor inicial de x, el cual es 10 y se le resta 1, cuyo resultado es 9, este resultado

se almacena en la propia variable x, por tanto, x pierde el valor inicial 10 y

almacena en su lugar el valor 9.

36

Estos operadores pueden ir antes o después de la variable:

1. Antes de la variable (preincremento o predecremento). Si el operador ++ o --

aparece antes del operando entonces el operando se incrementa o decrementa

antes de que se evalúe la expresión

Ejemplo: x = 10;

y = ++x; después de esta evaluación: y = 11 y x = 11

según el orden de prioridad, primero se lleva a cabo el incremento de x,

por tanto si x vale 10, se le suma una unidad y se obtiene el valor 11, ese

resultado se almacena en la misma variable x, es decir, x pierde el valor

inicial de 10 y ahora almacena 11; después se lleva a cabo la asignación,

así, el valor que contiene x es asignando a la variable y, por tanto, y vale

11.

2. Después del operando (postincremento o postdecremento). Si el operador ++ o

-- aparece después del operando, entonces primero se evalúa la expresión con el

valor actual del operando y posteriormente se incrementa o decrementa el

operando.

Ejemplo: x = 10;

y = x++; después de esta evaluación: y = 10 y x = 11

según el orden de prioridad, primero se lleva a cabo la asignación del

valor de x a la variable y, es decir, si x vale 10, y vale también 10;

después se lleva a cabo el incremento de x, es decir, si x vale 10, se le

suma una unidad y se obtiene el valor 11, ese resultado se almacena en la

misma variable x, es decir, x pierde el valor inicial de 10 y ahora

almacena 11.

Es importante hacer notar que al incrementar o decrementar una variable en un

enunciado por sí mismo, es decir, que no se encuentra involucrado con otras

expresiones, las formas de preincremento o predecremento, y postincremento o

postdecremento tienen el mismo efecto. Es decir:

++x; --x;

x++; dan el mismo resultado,

sumando una unidad a la variable x

x--; dan el mismo resultado

restando una unidad a la variable x

Expresiones como ++(x+1) son un error, ya que sólo se utilizan de forma directa

en variables.

Por lo explicado anteriormente, los operadores de incremento y decremento al

afectar el valor de una variable, se les consideran operadores de asignación como se

listaron en la tabla 11.

37

Tabla de prioridad y orden de evaluación

La tabla 12 muestra el orden de prioridad más completo que el mostrado en la

tabla 7, considerando todos los operadores mencionados hasta ahora.

Tabla 12. Prioridad de operadores.

Adaptado de Bermúdez et al. (2003, p. 40)

Operadores Asociatividad

( ) Izquierda a derecha

- (unario) ++ -- ! Derecha a izquierda

* / % Izquierda a derecha

+ - Izquierda a derecha

< <= > >= Izquierda a derecha

== != Izquierda a derecha

&& Izquierda a derecha

|| Izquierda a derecha

= += -= *= %= Derecha a izquierda

Ejemplos de prioridad de operadores: se declaran cuatro variables tipo entero y se les

asignan los valores que se muestran a continuación, de tal manera que, dichas variables

se utilizan para realizar diferentes operaciones aritméticas y de asignación, obteniendo

los resultados que se presentan en la tabla 13, junto con las expresiones equivalentes a

las operaciones aritméticas propuestas.

int a,b,c,d;

a=2; b=-3; c=7; d=-19;

Tabla 13. Valores resultantes según expresiones aplicadas a las variables a, b, c y d.

Expresión Expresión equivalente Valor resultante

a/b (a / b) 0

b/b/a (b/b)/a 0

c%a (c%a) 1

a%b (a%b) 2

d/b%a (d/b)%a 0

-a*d (-a)*d 38

a%-b*c (a%(-b))*c 14

38

9/c+-20/d (9/c) + ((-20)/d) 2

-d%c-b/a*5+5 (((-d)%c)-

((b/a)*5)) +5

15

7-a%(3+b) 7-(a%(3+b)) Error. Floating point exception,

puesto que no se puede dividir

entre el número cero

---a -(-(-a)) -2

a=b=c=-33 a=(b=(c=-33)) a=-33, b=-33, c=-33

Símbolos para escribir comentarios en un programa

Lo programadores insertan comentarios para documentar los programas y

mejorar la legibilidad de éstos. Los comentarios no hacen que la computadora lleve a

cabo acción alguna cuando se ejecute el programa, pues son tratados como texto simple,

no como enunciados, instrucciones o palabras reservadas. Los símbolos que permiten

hacer esto son:

/* texto explicativo */

o bien:

// texto informativo

La diferencia entre ellos radica en que, el primero permite que el texto utilice

más de un renglón y el segundo sólo uno.

Ejemplo 1 Ejemplo 2

/* Programa que imprime un mensaje

en pantalla y muestra el uso de

comentarios */

#include <stdio.h>

int main( )

{

printf (“Ejemplo . . . ”);

return 0;

}

#include <stdio.h>

int main( )

{

int i;//i sirve como contador

i = 10;

//Resto del programa

}

39

7 INSTRUCCIONES DE ENTRADA Y SALIDA

Las operaciones de entrada y salida (abreviadas E/S en español o I/O en inglés)

permiten leer y escribir datos a y desde archivos (también llamados ficheros) o bien,

dispositivos de entrada y salida en general, como lo es el teclado y la pantalla

respectivamente. Dichas operaciones no forman parte del conjunto de sentencias del

lenguaje C sino que pertenecen al conjunto de funciones de la biblioteca estándar de C

(la biblioteca estándar fue descrita en la sección 6: Lenguaje C). Por lo tanto, todo

programa deberá contener la línea inicial: #include <stdio.h>. Esta línea le dice al

compilador que incluya el archivo de cabecera stdio.h (Standard Input Output Header)

en el programa, permitiendo así usar las funciones que manipulan la entrada y salida de

datos (Ceballos, 1997).

Las siguientes funciones son algunas de las más utilizadas para entrada y salida

de datos: printf, scanf, getchar, putchar, puts y gets. Cada una de estas funciones tiene

una sintaxis que las identifica y en esta sección se explican únicamente: printf, scanf,

getchar y putchar. Tanto la función puts como gets se ven con más detalle en la sección

12: Caracteres, cadenas de caracteres y arreglos de caracteres.

Función printf( )

Según Ceballos (1997) se le llama instrucción de salida porque permite presentar

información en un dispositivo de salida, generalmente la pantalla, dando formato al

texto o a los valores. Ceballos presenta la sintaxis de esta instrucción de la siguiente

manera:

printf (cadena de control, lista de argumentos);

donde:

cadena de control especifica el texto que se mandará a pantalla y el formato que se le

dará tanto al texto como a los valores que se quieran presentar. Es una cadena de

caracteres delimitada por comillas dobles “ ” y formada por: texto, secuencias de escape

y especificaciones de formato.

Una secuencia de escape está formada por el carácter \ seguida de una letra o

combinación de dígitos que se utiliza para acciones como: salto de línea, tabular y

representar caracteres no imprimibles (ver tabla 14).

Una especificación de formato siempre comienza con el caracter % seguido de

una serie de símbolos para dar formato a la presentación de los valores, seguidos de una

o dos letras que especifican el tipo de datos que se mostrarán en pantalla: numéricos,

caracter, etc. (ver tabla 15). Cada especificación de formato debe corresponder con un

argumento en la lista de argumentos.

lista de argumentos representa el valor o valores a escribir en pantalla, éstos pueden ser:

variables, constantes, resultados de evaluaciones de funciones o de evaluaciones de

distintas operaciones aritméticas; cuando es más de un argumento deben ir separados

por comas.

40

Tabla 14. Secuencias de escape para printf.

Secuencia Acción que realiza

\n Genera una nueva línea.

\t Genera una tabulación horizontal.

\v Genera una tabulación vertical.

\b

Retrocede el cursor una posición borrando el caracter sobre el que

se posiciona.

\r

Genera un retorno de carro, es decir, el cursor se posiciona al inicio

de la línea donde se encuentra el cursor, por lo que, si se escribe

algún texto éste sobreescribirá a lo que ya exista en la línea.

\a Emite un sonido.

\” Imprime la comilla doble.

\\ Imprime la barra hacia atrás conocida como diagonal invertida.

Tabla 15. Especificaciones de formato para printf .

Adaptado de Ceballos (1997, pp. 112-114).

Código Formato

%d

%i

El argumento a mandar a un dispositivo de salida es un número entero

con signo en notación decimal.

%u

El argumento a mandar a un dispositivo de salida es un número entero

sin signo, en notación decimal.

%hd

%hi

El argumento a mandar a un dispositivo de salida es un número entero

corto, base 10.

%hu

El argumento a mandar a un dispositivo de salida es un número entero

corto sin signo, base 10.

%lu

El argumento a mandar a un dispositivo de salida es un número entero

largo sin signo, base 10.

%ld

El argumento a mandar a un dispositivo de salida es un número entero

largo.

%o

El argumento a mandar a un dispositivo de salida es un número entero

sin signo, en notación octal.

%x,

%X

El argumento a mandar a un dispositivo de salida es un número entero

sin signo, en notación hexadecimal, minúscula o mayúscula.

41

%g,

%G

Despliega en un dispositivo de salida un valor en punto flotante, ya sea

en forma de punto flotante f, o en la forma exponencial: e o E.

%f

El argumento a mandar a un dispositivo de salida es un número real

(como flotante, con decimales), para tipo double y float.

%Lf

El argumento a mandar a un dispositivo de salida es un número real

largo con signo.

%e,

%E

El argumento a mandar a un dispositivo de salida es un número real

con notación científica en minúscula o mayúscula.

%c El argumento a mandar a un dispositivo de salida es un solo carácter.

%s

El argumento a mandar a un dispositivo de salida es una cadena de

caracteres.

%%

El argumento a mandar a un dispositivo de salida es el caracter signo

de tanto por ciento.

%p

El argumento a mandar a un dispositivo de salida es el valor en

hexadecimal de la dirección física que ocupa una variable en la

memoria.

Función scanf( )

Según Ceballos (1997) esta función permite leer o ingresar datos desde el

teclado, por lo que se le conoce como una instrucción de entrada y presenta su sintaxis

de la siguiente forma:

scanf (cadena de control, lista de argumentos);

donde:

cadena de control está formada por códigos de formato de entrada que están precedidos

por un signo % y delimitados por comillas “ ” para especificar el tipo de valor que se

leerá desde teclado (ver la tabla 16).

lista de argumentos que se forma con una o más variables que almacenarán el valor o

valores que se van a leer desde el teclado; cada nombre de variable debe ir precedida

por el carácter & (si las variables son de tipo numérico o caracter, no así con las de tipo

cadena) y separados por comas. Cuando se especifica más de un argumento, los valores

correspondientes en la entrada (al momento de teclearlos o introducirlos) hay que

separarlos por uno o más espacios en blanco, tabuladores o enter.

El carácter & (llamado ampersand) es conocido en el lenguaje C como el

operador de dirección. Al ser combinado con un nombre de variable, le indica a la

42

computadora la posición en memoria donde se almacenará el valor y la computadora

entonces almacena el valor en esa posición.

Tabla 16. Códigos de formato para scanf.

Adaptado de Ceballos (1997, pp. 119-120).

Código Acción que realiza

%d

Permite leer desde el teclado un número entero base10.

%i

Permite leer desde el teclado un entero decimal, octal o

hexadecimal, opcionalmente signado, es decir, con signo + o -.

%hu

Permite leer desde teclado un número entero corto sin signo,

base 10.

%u

Permite leer desde el teclado un número entero sin signo, base

10.

%o

Permite leer desde el teclado un número en octal.

%x

Permite leer desde el teclado un número en hexadecimal.

%hd

%hi

Permite leer desde el teclado un número entero corto, base 10.

%lu

Permite leer desde el teclado un número entero largo sin signo,

base 10.

%f

%e,

%E

%g,

%G

Permite leer desde el teclado un número real, por ejemplo, el

tipo flotante con decimales.

%ld

%li

Permite leer desde el teclado un número entero largo, base 10.

%c

Permite leer desde el teclado un solo carácter.

%s

Permite leer desde el teclado una palabra o cadena de caracteres

sin espacios.

43

%lf

Permite leer desde el teclado un número real (tipo double).

%Lf

Permite leer desde el teclado un número real largo (tipo long

double).

Función getchar( )

Permite leer un único caracter desde el teclado (Cairó, 2006). Por ejemplo, el

siguiente código permitirá al usuario ingresar desde teclado un caracter y dicho caracter

será almacenado en la variable car.

car = getchar( ); /*getchar captura el carácter que el

usuario haya pulsado en el teclado y lo almacena en la

variable car.*/

Función putchar( )

Permite imprimir un único caracter en la pantalla (Cairó, 2006). Por ejemplo, el

siguiente código mostrará en la pantalla el caracter que contenga la variable car.

putchar(car);

Buffer o memoria intermedia

Las funciones estándar de E/S tienen la característica fundamental de que las

operaciones de E/S de datos se realiza a través de un buffer o memoria intermedia,

como una técnica implementada en software para hacer a dichas operaciones más

eficientes (Ceballos, 1997).

Un buffer es un área de datos en la memoria RAM asignada por el programa

que se está ejecutando y que abre automáticamente cinco archivos:

stdin: dispositivo de entrada estándar (teclado)

stdout: dispositivo de salida estándar (pantalla)

stderr: dispositivo de error estándar (pantalla)

sdtaux: dispositivo auxiliar estándar (puerto serie)

stdprn: dispositivo de impresión estándar (impresora en paralelo)

De estos cinco, dos de ellos: el dispositivo serie y el de impresión paralela,

dependen de la configuración de la máquina, por lo tanto, pueden no estar presentes. El

que aquí importa es el dispositivo de entrada estándar (teclado) y por ello se describe a

44

continuación lo que típicamente sucede al ingresar datos principalmente de tipo

caracter.

Las funciones scanf y getchar tienen una característica común: leen los datos

requeridos de la entrada estándar referenciada por stdin. Es necesario tener presente que

cuando son tecleados los datos, no son leídos directamente del dispositivo de entrada,

sino que éstos son depositados en la memoria intermedia buffer, asociada con el

dispositivo de entrada. Los datos son leídos de la memoria intermedia cuando son

validados y esto ocurre cada vez que pulsamos la tecla Enter. Esta tecla se representa

con el carácter LF y también se almacena en el buffer junto con todo lo que haya

tecleado el usuario. Por tanto, después de presionar Enter, las funciones scanf y getchar

toman del buffer lo que se haya ingresado hasta antes del caracter LF, es decir, hasta lo

que esté antes del Enter. Pero si las funciones scanf y/o getchar se vuelven a usar, éstas

toman lo que está en el buffer, pero encuentran el caracter LF antes almacenado, por lo

que, para ellas significa que el usuario no ingresó ningún dato y del programa se ejecuta

la siguiente línea de código, es decir, el efecto que se tiene en el segundo llamado de las

funciones scanf y/o getchar es que el usuario no tiene oportunidad de ingresar sus

datos, por lo que verá la ejecución de la siguiente línea de código(Ceballos, 1997). En

conclusión LF o la tecla Enter genera problemas de lecturas no deseadas SOLO

cuando el tipo de dato a ingresar es de tipo caracter o char.

La solución a lo anterior es limpiar el buffer asociado con stdin antes de una

lectura o ingreso de datos con las funciones scanf y getchar, utilizando por ejemplo, las

funciones:

fflush(stdin) disponible en el sistema operativo Windows, o bien,

__fpurge(stdin) disponible en el sistema operativo GNU/Linux (al inicio de

la función se encuentran dos guiones bajos consecutivos).

Estas funciones pueden colocarse inmediatamente después de haber usado las

instrucciones scanf y/o getchar.

En la sección 12: Caracteres, cadenas de caracteres y arreglos de caracteres se

tratará a fondo el tema de los caracteres en el lenguaje C.

8 ESTRUCTURAS SECUENCIALES Y SELECTIVAS

8.1 Estructura secuencial

Es aquella en la que una acción o instrucción sigue a otra en secuencia o en

orden. La salida de una es la entrada de la siguiente y así sucesivamente (ver figura 6).

Dichas acciones pueden ser instrucciones de entrada, salida o proceso (Deitel y Deitel,

1995).

45

Figura 6. Estructura secuencial.

8.2 Estructuras selectivas

Se utilizan para tomar decisiones lógicas. Se les llama estructuras de decisión o

alternativas. Pueden ser: simples, dobles o múltiples.

8.2.1 Alternativa simple (si, entonces)

Ejecuta una o varias acciones cuando se cumple una determinada condición, es

decir, se evalúa la condición y si ésta es verdadera se ejecuta la acción o acciones

específicas, pero si la condición es falsa entonces se hace nada como se muestra en la

figura 7.

Figura 7. Alternativa simple.

En el lenguaje C, la estructura de alternativa simple se especifica mediante la

instrucción if, cuya sintaxis se muestra a continuación.

if (expresión)

{

proposiciones;

}

proposición_siguiente;

46

donde la expresión debe ser de tipo numérica, relacional o lógica.

Si el resultado de evaluar expresión es verdadero entonces se ejecutarán las

proposiciones (que pueden ser una o varias instrucciones de entrada, salida, proceso,

otra condicional o ciclos) y después proposición_siguiente. Por otra parte, si el

resultado de la evaluación es falso entonces no se realiza acción alguna y continúa el

flujo del programa a la siguiente línea de código proposición_siguiente.

Ahora bien, si se tiene sólo una proposición o instrucción a ejecutarse, no es

necesario colocar llaves para englobar la sentencia, pero si es más de una, sí son

necesarias.

8.2.2 Alternativa doble (si, entonces, sino, entonces)

Como se muestra en la figura 8, se ejecutará la acción o acciones S1 si al

evaluarse una condición ésta es verdadera, o bien se realizará la o las acciones S2 si la

condición es falsa, pero nunca ambas acciones S1 y S2 al mismo tiempo.

En el lenguaje C, la estructura de alternativa doble se especifica mediante la

instrucción if else, cuya sintaxis se muestra a continuación.

if (expresión)

{

proposiciones_1;

}

else

{

proposiciones_2;

}

proposición_siguiente;

donde la expresión debe ser de tipo numérica, relacional o lógica.

Si el resultado de evaluar expresión es verdadero entonces se ejecutarán las

proposiciones_1 (que pueden ser una o varias instrucciones de entrada, salida, proceso,

otra condicional o ciclos). Por otro lado, si el resultado de la evaluación es falso

entonces no se realizarán las proposiciones_1 sino que se ejecutarán las

proposiciones_2 (que también pueden ser una o varias instrucciones de entrada, salida,

proceso, otra condicional o ciclos). Después de evaluarse la condición y ejecutarse lo

pertinente, si la condición es verdadera o falsa se continúa con el flujo del programa a la

siguiente línea de código proposición_siguiente.

Al igual que en la alternativa simple, si se tiene sólo una proposición o

instrucción a ejecutarse en el if, entonces no es necesario colocar llaves para

englobar la proposición o sentencia, y lo mismo aplica para el else.

47

Figura 8. Alternativa doble.

8.2.3 Alternativa múltiple (selector)

La estructura de decisión múltiple (selector) evaluará únicamente el valor de una

variable que podría tomar n valores distintos. Según se elija uno de esos valores de la

variable, se realizará una y sólo una de las n acciones, o lo que es igual, el flujo del

algoritmo seguirá un determinado camino entre los n posibles como se muestra en la

figura 9 (Cairó, 2006).

Figura 9. Alternativa múltiple (selector).

Adaptado de Cairó(2006, p. 58)

En el lenguaje C, esta estructura se especifica como se muestra a continuación.

switch (expresión)

{

case constante1: sentencias1;

break;

case constante2: sentencias2;

break;

case constante3: sentencias3;

break;

default: sentenciasN;

break;

}

48

donde expresión es una variable de tipo entera o caracter y sentencias pueden ser una o

varias instrucciones de entrada, salida, proceso, condicionales o ciclos.

La sentencia switch evalúa la expresión entre paréntesis y compara su valor con

las constantes de cada case, si coincide la expresión con alguna constante entonces se

ejecutarán todas las sentencias después de los dos puntos de dicho case de coincidencia

hasta la instrucción break, es decir, la instrucción break permite salir de la estructura

switch y no continuar con la ejecución de las sentencias de otro case con el cual no

coindice el valor de la variable, por tanto, se debe utilizar la sentencia break para

concluir le ejecución de las sentencias en cada case.

En el caso de que ninguna de las constantes coincida con la expresión entonces

se ejecutarán la o las sentenciasN que se encuentran después de los dos puntos de la

palabra clave default. La sentencia switch puede incluir cualquier número de cláusulas

case y opcionalmente la cláusula default, es decir, se puede omitir si no se requiere

realizar algo en particular al no coincidir el valor de la variable con algún valor

especificado en los distintos case. Las sentencias pueden ser una o varias instrucciones

de entrada, salida, proceso, otra condicional o ciclos.

8.2.4 Alternativa múltiple (no selectora)

En una estructura de decisión múltiple (no selectora) existe más de una

condición a evaluarse como se muestra en la figura 10 y funciona de la siguiente

manera. Primero se evalúa Condición1 y si ésta es verdadera entonces se ejecutan

Acciones1 y continua con AccionesSiguientes, pero si la condición es falsa entonces se

evalúa Condición2, nuevamente, si la Condición2 es verdadera entonces se realizan

Acciones2 y continua con AccionesSiguientes pero si la condición es falsa entonces se

repite el proceso anterior por cada condición que exista, de tal manera que si se llega a

la CondiciónN y ésta es verdadera entonces se realizan las AccionesN y después

AccionesSiguientes pero si la condición es falsa entonces se ejecutan AccionesDefault,

es decir, esta o estas instrucciones se llevarán a cabo si ninguna de las condiciones

anteriores fue verdadera.

En el lenguaje C, la estructura de alternativa múltiple (no selectora) se especifica

mediante la instrucción if else if cuya sintaxis se muestra a continuación (en realidad es

una forma particular de presentar la estructura if anidada como se verá en la siguiente

sección 8.2.5).

if (expresión1)

{

sentencias1;

}

else if (expresión2)

{

sentencias2;

}

else if (expresión3)

{

sentencias3;

49

}

else

{

sentenciasN;

}

proposición_siguiente;

donde la expresión1, expresión2, expresión3, etc. deben ser de tipo numérico, relacional

o lógico. Si la expresión1 es verdadera se ejecutan las sentencias1 (que pueden ser una o

varias instrucciones de entrada, salida, proceso, otra condicional o ciclos) y continúa

con proposición_siguiente, pero si la condición es falsa entonces se examina la

expresión2 y nuevamente, si ésta es verdadera entonces se ejecutan las sentencias2 (que

pueden ser una o varias instrucciones de entrada, salida, proceso, otra condicional o

ciclos) para continuar con proposición_siguiente pero si la condición es falsa se evalúa

la tercera expresión y así sucesivamente hasta llegar al else, ejecutándose las

sentenciasN sólo si todas las expresiones anteriores fueron falsas, para después

continuar con proposición_siguiente.

Al igual que en la alternativa simple y doble, si se tiene sólo una proposición o

instrucción a ejecutarse, no es necesario colocar llaves para englobar la sentencia para

el if, cada else if que exista y el else, pero si es más de una, sí son necesarias.

Figura 10. Alternativa múltiple (no selectora).

50

8.2.5 Estructuras de decisión anidadas o en cascada

Las estructuras si interiores a otras estructuras iguales se denominan anidadas,

encajadas o en cascada. Una estructura de selección de n alternativas o de decisión

múltiple, tanto selectora como no selectora, puede ser construida utilizando una

estructura si anidada, es decir unas interiores a otras formando una cascada o escalera

como se muestran en las figuras 11,12 y 13. De hecho la estructura múltiple no selectora

es en realidad una estructura anidada pero presentada con una distribución un tanto

diferente.

Figura 11. Ejemplo 1 de estructuras selectivas anidadas.

Figura 12. Ejemplo 2 de estructuras selectivas andadas.

51

Figura 13. Ejemplo 3 de estructuras selectivas anidadas.

8.2.6 Operador ternario

En el lenguaje C existe un operador conocido como ternario, es decir, necesita

tres argumentos (de ahí su nombre) obligatorios, que no se pueden omitir. El operador

evalúa una condición de tal manera que si ésta es verdadera entonces se ejecuta una

única instrucción o sentencia, pero si la condición es falsa entonces se ejecuta otra única

instrucción, en otras palabras, con este operador, a diferencia de las otras instrucciones

condicionales descritas, sólo se ejecuta una instrucción pero no un conjunto de

instrucciones.

En el operador ternario se usa el caracter de interrogación que cierra para separar

la condición de la instrucción que se realizará si la condición es verdadera, y se usa el

caracter de dos puntos para separar la instrucción que se realizará si la condición es

verdadera de la instrucción que se realizará si la condición es falsa. Su sintaxis es como

se muestra a continuación.

expresión1? sentenciaV : sentenciaF

Si expresión1 es verdadera, entonces se ejecuta sentenciaV, si es falsa se ejecuta

sentenciaF.

La importancia de este operador radica en que se utiliza comúnmente en la

asignación que se le hace a una variable dependiendo del resultado de una condicional.

Con este uso, la condicional aparece del lado derecho de una asignación como se ilustra

en el siguiente ejemplo: mayor = a>b ? a : b;

En la sentencia anterior, se evalúa la expresión a>b y si ésta es verdadera

entonces a la variable mayor se le asignará el valor de la variable a, pero si la expresión

es falsa entonces a la variable mayor se le asignará el valor de la variable b.

52

9 ESTRUCTURAS REPETITIVAS

En la práctica, durante la solución de problemas, es muy común encontrar

operaciones que se deben ejecutar un número determinado de veces, si bien las

instrucciones son las mismas los datos varían (Cairó, 2006).

Las estructuras que repiten una secuencia de instrucciones un número

determinado de veces se denominan bucles, y se llama iteración al hecho de repetir la

ejecución de una secuencia de acciones. En un bucle o ciclo se deben tener en cuenta

qué es lo que contiene el bucle y cuántas veces se debe repetir.

Para detener la ejecución de los bucles se utiliza una condición de parada, esta

condición puede especificarse al principio o al final del bucle según el problema a

resolver. Se tienen así tres tipos de estructuras repetitivas o iterativas:

1. La condición de salida del bucle se verifica al principio de dicho bucle, por lo

que el ciclo se realiza mientras se cumple una condición.

2. La condición de salida se origina al final del bucle; el bucle se ejecuta mientras

se verifica una cierta condición pero después de haberse ejecutado por lo menos

una vez la o las instrucciones.

3. La condición de salida se verifica con un contador que cuenta el número de

iteraciones a realizarse.

9.1 Estructura mientras

Cuando se ejecuta la instrucción mientras, lo primero que se realiza es la

evaluación de la condición (una expresión booleana), si se evalúa como falsa entonces

ninguna acción se ejecuta dentro del bucle y el programa prosigue en la siguiente

instrucción fuera del bucle. Si la expresión booleana es verdadera entonces se ejecuta el

cuerpo del ciclo, después de lo cual se evalúa de nuevo la expresión booleana. Este

proceso se repite una y otra vez mientras la condición es verdadera (ver figura 14).

Figura 14. Estructura mientras.

53

En este tipo de ciclos puede suceder que nunca se ejecuten acciones si la

condición nunca es verdadera, o bien, puede darse un ciclo infinito si la condición

nunca se vuelve falsa.

Como un caso particular, si el problema que se resuelve requiere leer una lista de

valores con un bucle mientras, se debe incluir algún tipo de mecanismo para terminar el

bucle como Deitel y Deitel (1995) lo describen:

a. Preguntar al usuario antes de la iteración,

b. Saber de antemano el número de iteraciones exactas que se van a llevar a cabo,

c. Con un valor llamado centinela, que es un valor especial usado para indicar el

final de una lista de datos.

Ahora bien, en el lenguaje C, la estructura repetitiva mientras se especifica mediante

la instrucción while cuya sintaxis se muestra en seguida.

while (expresión)

{

sentencias1;

}

sentencias2;

donde expresión es cualquier expresión numérica, relacional o lógica. Puede ser

numérica puesto que cualquier valor diferente de cero se considera un valor booleno

verdadero, mientras que el cero se considera un valor booleano falso.

La instrucción ejecuta una sentencia simple o compuesta cero o más veces

dependiendo del valor de la expresión, es decir, se ejecutarán las instrucciones mientras

la expresión es verdadera, en el momento en que se convierte en falsa se ejecuta la línea

después del fin del while, es decir, sentencias2.

Si se tiene sólo una proposición o instrucción a ejecutarse, no es necesario

colocar llaves para englobar la sentencia del ciclo, pero si es más de una, sí son

necesarias.

9.2 Estructura hacer mientras

Esta estructura se utiliza cuando se debe ejecutar al menos una vez un bucle

antes de comprobar la condición de repetición (ver figura 15).

La estructura hacer mientras se ejecuta mientras una condición determinada es

verdadera, la cual se comprueba al final del bucle pero cuando la condición es falsa

continúa con las instrucciones fuera del ciclo.

La estructura es adecuada cuando no se sabe el número de veces que se debe

repetir un ciclo pero se sabe que se debe ejecutar por lo menos una vez, además es

eficiente para verificar los datos de entrada en un programa (Cairó, 2006).

54

Figura 15. Estructura hacer mientras.

En el lenguaje C, la estructura repetitiva hacer mientras se especifica con las

instrucciones do while como se muestra a continuación.

do

{

sentencias;

}while ( expresión );

La sentencia do while funciona de la siguiente manera: se ejecuta primero la

sentencia o bloque de sentencias después del do, luego se evalúa la expresión y si es

falsa termina la proposición do while pero si es verdadera (diferente de cero) entonces

se repite la sentencia o sentencias dentro del do{ }.

En esta estructura si se tiene sólo una proposición no es necesario colocar llaves

para englobar la sentencia a ejecutarse, pero si es más de una sí son necesarias.

9.3 Estructura desde o para

Esta estructura se utiliza cuando se sabe cuántas veces se desea ejecutar las

acciones de un ciclo. Se utiliza para repetir un conjunto de instrucciones un número

definido de veces. Comienza con un valor inicial de la variable llamada índice y las

acciones especificadas se ejecutan a menos que el valor inicial sea mayor que el final

cuando la variable índice sufre incrementos, o bien se detiene si la variable índice es

menor que el final si la variable índice sufre decrementos (Cairó, 2006). Lo anterior se

aprecia en las figuras 16 y 17.

NOTA IMPORTANTE: toda estructura desde o para se puede sustituir por una

estructura mientras, y viceversa, sin embargo, se recomienda aplicar la estructura desde

o para sólo en aquellos problemas en los que se conoce previamente el número de veces

que se debe repetir el ciclo, y utilizar las estructura mientras en aquellos problemas en

que el número de veces que se tenga que repetir el ciclo dependa de la condición a

evaluar y no de un número determinado de veces.

55

Figura 16. Estructura desde o para.

Figura 17. Otra forma de representar a la estructura desde o para.

Adaptado de Bermúdez et al. (2003, p. 18).

En el lenguaje C, la estructura repetitiva desde o para se especifica con la

instrucción for como se muestra a continuación.

for (inicialización; condición; incremento/decremento)

{

Sentencias1;

}

Sentencias2;

donde:

inicialización es una proposición de asignación que se utiliza para establecer la variable

de control o índice. Se pueden utilizar varias variables de control en un ciclo de

repetición for, por lo que al inicializar más de una variable, éstas se separan por comas.

56

condición es una expresión relacional o lógica que determina cuándo terminará el ciclo

de repetición.

incremento/decremento define cómo cambiará la variable de control (variable índice) o

las variables de control (si hay más de una) en cada repetición; dichos cambios

generalmente son incrementos o decrementos sobre las variables índice.

Las tres partes que conforman el encabezado del ciclo for (inicialización,

condición e incremento/decremento) tienen que estar separadas por puntos y comas

independientemente de que se omita la inicialización o el incremento/decremento, pues

estas dos partes sí pueden omitirse, no así la condición. Ahora bien, a pesar de que se

pueden omitir las dos partes antes mencionadas, se recomienda utilizar cada una de las

tres, es decir, se recomienda respetar la propia estructura de la instrucción. En caso de

omitir la inicialización, ésta se debe realizar en algún punto antes del ciclo; si lo que se

omite es el incremento/decremento, entonces éstos deben realizarse dentro del cuerpo de

la estructura for.

Al igual que la estructura while y do while antes mencionadas, en la estructura

for, si se tiene sólo una proposición no es necesario colocar llaves para englobar la

sentencia a ejecutarse, pero si es más de una sí son necesarias.

Por último, como se explicó en la nota importante anterior de esta sección, todo

while tiene su equivalente en for y viceversa, a modo de ejemplo se presenta en seguida

el código equivalente.

Ejemplo de un ciclo for Equivalencia con un ciclo while

for (expr1; expr2; expr3)

proposición;

expr1;

while(expr2){

proposición;

expr3;

}

9.4 Estructuras repetitivas anidadas

Como en las estructuras condicionales, es posible insertar un bucle dentro de

otro. La estructura interna debe estar incluida totalmente dentro de la externa y no puede

existir solapamiento. El anidamiento se puede dar entre mismas o diferentes estructuras

repetitivas, es decir, puede existir un ciclo mientras dentro de otro ciclo mientras, un

ciclo hacer mientras dentro de otro ciclo hacer mientras o un ciclo para dentro de otro

ciclo para; pero también, puede existir un ciclo mientras dentro de un ciclo para, un

ciclo hacer mientras dentro de un bucle mientras, o cualquier clase de combinación

dependiendo del problema a resolver.

Un ciclo interno completo se repetirá el número de veces que la condición del

ciclo externa sea verdadera. Por ejemplo, si se tuvieran dos anidamientos, es decir un

tercer ciclo dentro de un segundo y éste dentro de un primer ciclo, entonces el ciclo más

interno, o sea, el tercero, se ejecutará la cantidad de veces que sea verdadera la

57

condición del segundo ciclo multiplicado por la cantidad de veces que sea verdadera la

condición del primer ciclo; y el segundo bucle se ejecutará la cantidad de veces que la

condición del primer ciclo sea verdadera.

9.5 Instrucciones que alteran el flujo normal de un ciclo

En ocasiones es necesario disponer de instrucciones que permitan la salida en un

punto intermedio de cualquier bucle sin que se verifique la condición principal de dicho

bucle, es decir, salir del ciclo antes de llegar a la condición. Estas instrucciones sólo

están disponible en algunos lenguajes de programación y en general no es recomendable

usarlas, porque el programa no es tan legible o “limpio” como debe ser.

Sentencia break

Finaliza la ejecución de una proposición switch, for, while y do while en la cual

aparece dicha instrucción, saltándose la condición normal del ciclo de repetición.

Sentencia continue

Interrumpe la ejecución normal de un bucle (for, while y do while) pero no lo

finaliza, sino que, finaliza la iteración en curso, transfiriendo el control del programa a

la condicional del bucle, para decidir si se debe realizar una nueva iteración o no. Es

decir, continue termina la ejecución de la iteración actual de un bucle, pero no la

ejecución del bucle en sí como lo hace break. La instrucción continue evita que se

ejecuten las instrucciones que existan después de ella en la iteración del bucle, saltando

hasta la condicional.

Función exit( )

Otra forma de terminar un ciclo de repetición es utilizar la función exit. A

diferencia del break, terminará no sólo con la ejecución del bucle sino con la ejecución

del programa y regresa el control al sistema operativo.

10 NÚMEROS PSEUDOALEATORIOS EN EL LENGUAJE C

Existen multitud de problemas que requieren que una computadora simule el

comportamiento de un sistema con alguna variable o variables aleatorias, es decir, que

genere números cuyo orden no sea predecible y puedan considerarse al azar, por

ejemplo: el número de viajeros en una estación de autobuses, programación de juegos

(tirada de un dado), etc. Sin embargo, en la práctica, no existen números que, generados

por una computadora, sean realmente aleatorios, pues hay que tener en cuenta que una

computadora es una máquina determinista, es decir, determinada por las condiciones

iniciales y en la que no hay ni puede haber operaciones aleatorias puras. Pero, una

computadora sí puede generar números pseudoaleatorios (Rodríguez y Galindo, 2009).

58

Un número pseudoaleatorio es un número generado por la computadora que,

no es realmente aleatorio pero se comporta como si se hubiera producido al azar

(Rodríguez y Galindo, 2009).

El lenguaje C permite generar números pseudoaleatorios usando una de dos

funciones disponibles según en el sistema operativo en el que se esté trabajando: la

función random( ) en el sistema operativo GNU/Linux y la función rand( ) en el

sistema operativo Windows. Ambas funciones no necesitan un argumento entre

paréntesis y devuelven un número pseudoaleatorio entre los valores del intervalo

cerrado [0, RAND_MAX].

RAND_MAX es una constante del lenguaje C que equivale al menos al valor

32,767, que es lo máximo que se puede almacenar en una variable de tipo entero con

signo, aunque su valor depende de la biblioteca donde se haya implementado dicha

constante, es decir, depende del compilador que se esté utilizando (se habló de los

distintos tipos de compiladores para el lenguaje C en la sección 6 Lenguaje C). El valor

32,767 se encuentra en la mayoría de las bibliotecas, aunque se puede llegar a encontrar

como máximo el número: 2,147,483,647 (Rodríguez y Galindo, 2009).

Las funciones antes mencionadas generarán números pseudoaleatorios a partir

de un número inicial llamado semilla y, es con ese valor inicial que genera el primer

número, después, a partir de dicho número genera el segundo, con el segundo genera el

tercero y así sucesivamente hasta concluir con la cantidad de números pseudoaleatorios

que se necesiten producir.

Cuando se requiere cambiar el valor máximo del intervalo cerrado de números

pseudoaleatorios que da por default el lenguaje C, entonces se debe dimensionar

usando un factor de dimensionamiento, donde dicho factor determina el valor

máximo. Para ello, se usa la función que genera los números pseudoaleatorios seguida

del operador módulo y el factor de dimensionamiento antes mencionado (Deitel y

Deitel, 1995). A continuación se detalla el procedimiento suponiendo a n una variable

entera como el factor de dimensionamiento y proporcionada por el programador o

usuario:

random( ) % n Es la sintaxis en el sistema operativo GNU/Linux para generar un número

pseudoaleatorio entre los valores del intervalo cerrado [0, n-1].

rand( ) % n Es la sintaxis en el sistema operativo Windows para generar un número pseudoaleatorio

entre los valores del intervalo cerrado [0, n-1].

Ahora bien, cuando se requieren cambiar ambos valores del intervalo cerrado,

además del dimensionamiento, se debe llevar a cabo un desplazamiento. Suponiendo a

m y n como variables enteras proporcionadas por el programador o usuario y, m < n,

entonces:

random( ) % (n-m+1) + m Es la sintaxis en el sistema operativo GNU/Linux para generar un número

pseudoaleatorio entre los valores del intervalo cerrado [m, n].

59

rand( ) % (n-m+1) + m Es la sintaxis en el sistema operativo Windows para generar un número pseudoaleatorio

entre los valores del intervalo cerrado [m, n].

Ejemplos:

/*Se genera un número pseudoaleatorio en el sistema operativo GNU/Linux dentro del

intervalo cerrado [0, 7] y se almacena en la variable numero.*/ numero = random( ) % 8;

/*Se genera un número pseudoaleatorio en el sistema operativo GNU/Linux dentro del

intervalo cerrado [10, 50] y se almacena en la variable numero.*/ numero = random( ) % (50-10+1) + 10;

/*Se genera un número pseudoaleatorio en el sistema operativo Windows dentro del

intervalo cerrado [0, 7] y se almacena en la variable numero.*/ numero = rand( ) % 8;

/*Se genera un número pseudoaleatorio en el sistema operativo Windows dentro del

intervalo cerrado [10, 50] y se almacena en la variable numero.*/ numero = rand( ) % (50-10+1) + 10;

Por otro lado, en cada ejecución de un programa donde se usan las funciones

antes descritas, se obtiene la misma secuencia de números pseudoaleatorios porque la

semilla es un número fijo. Para evitar esto, se usa de la biblioteca stdlib.h y la

función srandom( ) si se está trabajando en el sistema operativo GNU/Linux, o la

función srand( ) si se está trabajando en el sistema operativo Windows. Ambas

funciones se deben utilizar con un parámetro que servirá como semilla o número inicial.

Si el parámetro especificado es un número fijo (por ejemplo, una constante o un número

generado por las mismas funciones rand o random) se tiene el mismo problema, es

decir, se obtiene la misma secuencia de números pseudoaleatorios en cada corrida del

programa; para corregir esto, se puede hacer uso de alguna de las siguientes funciones:

- Función time( ) del lenguaje C que se encuentra en la biblioteca time.h

Esta función devuelve la fecha y hora actual que tenga establecido el sistema

operativo.

La forma de utilizar la función descrita junto con la función srand o srandom

se muestra a continuación:

srandom( time(NULL) ) en el sistema operativo GNU/Linux, o

srand( time(NULL) ) en el sistema operativo Windows.

60

La función time( ) devuelve un valor en segundos correspondiente al instante

actual en que se ejecuta el programa, dicho valor se calcula a partir de las cero

horas del 1 de enero de 1970 (inicio en que se utilizó por primera vez el sistema

operativo Unix). En Windows también devuelve el tiempo en segundos

partiendo de un valor inicial6.

- Función getpid( ) que se puede encontrar en las bibliotecas: sys/types.h o

unistd.h Esta función devuelve el número de proceso que se le haya asignado al

programa que se está ejecutando actualmente, el cual siempre es distinto en cada

corrida de dicho programa.

La forma de utilizar la función descrita junto con la función srand o srandom

se muestra a continuación:

srandom( getpid( ) ) en el sistema operativo GNU/Linux, o

srand( getpid( ) ) en el sistema operativo Windows.

Finalmente, la función drand48( ) se usa para obtener números pseudoaleatorios

con decimales y la función srand48( ) se usa para cambiar la semilla de inicio, por lo

que también se puede hacer uso de time( ) y getpid( ) como se explicó anteriormente.

Estas funciones no se encuentran en el sistema operativo Windows. Ejemplos:

numero = drand48( )*(20.0-10.0) + 10.0

numero = drand48( )*(n-m)+n

11 ARREGLOS

Introducción a las estructuras de datos

Una estructura de datos es una colección de datos simples que se caracterizan

por su organización y las operaciones que se definan en ella.

Las estructuras de datos estáticas son aquellas en las que el tamaño ocupado en

memoria se define antes de que el programa se ejecute y no puede modificarse dicho

tamaño durante la ejecución del programa. Estas estructuras están implementadas en

casi todos los lenguajes de programación, ejemplos: arreglos, registros y ficheros o

archivos.

6 Nota: la función time( ) es de tipo long int. Un tipo de dato long int = 2,147,483,647; 1 año =

31,536,000 segundos, por lo que el valor máximo del long int se alcanzará aproximadamente el 18 de

febrero de 2038.

61

Las estructuras de datos dinámicas no tienen las limitaciones o restricciones en

el tamaño de memoria ocupada. Mediante el uso de un tipo de dato específico

denominado puntero, es posible construir estructuras de datos dinámicas que son

soportadas por la mayoría de los lenguajes. Las estructuras dinámicas por excelencia

son: listas, árboles y grafos.

Los tipos de datos simples tienen como característica común que cada variable

representa a un elemento, los tipos de datos estructurados tienen como característica

común que un identificador (nombre) puede representar múltiples datos individuales,

pudiendo cada uno de éstos ser referenciado independientemente.

Datos simples

Estándar

Entero

Real

Caracter

Lógico

Definido por el programador

Subrango

Enumerativo

Datos estructurados

Estáticos

Arreglos (vector o matriz)

Registros

Archivo o ficheros

Conjuntos

Cadenas

Dinámicos

Listas (pilas o colas)

Listas enlazadas

Árboles

Grafos

En este curso de Programación I se trabajan solamente estructuras de datos

estáticas y en los siguientes apartados de esta sección se revisan en particular los

arreglos.

Concepto de arreglo

Un arreglo es una colección finita, homogénea y ordenada de elementos, en otras

palabras, es una estructura compuesta por varios elementos, todos del mismo tipo

(entero, real, carácter o booleano) y almacenados consecutivamente en memoria. Cada

componente puede ser accedido directamente por el nombre del arreglo seguido de uno

o varios subíndices encerrados entre corchetes [ ] según sea la dimensión del arreglo; los

subíndices indican la posición de cada componente del arreglo y pueden ser únicamente

valores enteros positivos, variables tipo entero positivos o expresiones numéricas

enteras positivas (Cairó, 2006).

Existen 2 tipos de arreglos según su dimensión: unidimensional, porque para

acceder a un elemento del arreglo sólo se tiene que utilizar un índice, y

multidimensional, porque para acceder a un elemento del arreglo se utilizan múltiples

62

índices. Para este último tipo de arreglo, el más utilizado es el bidimensional, es decir,

el de dos dimensiones, por ello, a continuación se describen tanto el arreglo

unidimensional como el bidimensional.

11.1 Arreglos unidimensionales

El arreglo unidimensional, conocido también como vector, es una colección

finita, homogénea y ordenada de datos, en la que se hace referencia a cada elemento del

arreglo por medio de un índice. Este último indica la casilla en la que se encuentra el

elemento. Para hacer referencia a un componente de un arreglo se deben utilizar tanto el

nombre del arreglo como el índice del elemento (Cairó, 2006). En la figura 18 se puede

observar la representación gráfica de un arreglo unidimensional.

Figura 18. Representación gráfica de un arreglo unidimensional.

Adaptado de Cairó (2006, p. 177).

En el lenguaje C, el arreglo unidimensional se declara de la siguiente manera:

tipo nombre[tamaño]

donde:

tipo es uno de los tipos predefinidos por el lenguaje: float, int, etc.

nombre: es un identificador que nombra el arreglo y sigue las mismas reglas

especificadas para dar nombre al identificador de una variable.

tamaño es una constante entera positiva que especifica el número de elementos

del arreglo.

En el lenguaje C, los arreglos unidimensionales inician en la posición cero [0],

por lo que la posición máxima del arreglo está dada por el número máximo de

elementos menos 1.

También, el lenguaje C no revisa los límites del arreglo unidimensional, por lo

que es responsabilidad del programador el realizar este tipo de operación para no

acceder a una posición negativa o mayor al límite de elementos.

63

Los elementos de un arreglo pueden ser inicializados en la declaración del

arreglo mismo, para ello, se coloca el signo igual después de la declaración y una lista

de valores inicializadores separados por comas, dicha lista encerrada entre llaves.

De Cairó (2006) se pueden analizar los siguientes ejemplos sobre declaración e

inicialización de arreglos unidimensionales:

/*Declara un arreglo unidimensional llamado arreglo para

almacenar 10 elementos de tipo real o flotante de doble

precisión.*/

double arreglo[10];

/*Se declara el arreglo unidimensional llamado V con 10

elementos de tipo real y al mismo tiempo se inicializa cada

casilla del arreglo con cada uno de los valores que se

encuentran entre llaves y separados por comas. La

representación gráfica del arreglo V se muestra en la

figura 19.*/

float V[10] = {32.5,15.8, 70.1, 5.9, 0, -12.2, 13.3, 90.4,

56.6, -9.8};

Figura 19. Representación gráfica del vector V con 10 elementos de tipo real.

Si se quiere acceder al primer elemento del arreglo se debe escribir V[0], pero si se

quiere acceder al quinto elemento se debe escribir V[4]. Por otra parte, el valor de V[7]

es 90.4, el de V[3+5] es 56.6 y el resultado de V[2] + V[5] es 57.9.

/*Lo siguiente muestra un error de sintaxis porque se están

proporcionando más inicializadores de arreglo que elementos

existen dentro del mismo.*/

double Valores[5] = {32.1,27.3,64.4,18.6,95.7,14};

/*Si en la lista de inicialización se omite el tamaño del

arreglo, el número de elementos en el arreglo será el

número de elementos incluidos en la lista inicializadora.*/

int n[ ] = {1,2,3,4,5};

/*Si dentro del arreglo existe un número menor de

inicializadores que de elementos, los elementos restantes

son inicializados a cero automáticamente.*/

64

int A1[10] = {0};

/*El primer componente del arreglo se inicializa con el

número 5 y el resto con el número 0.*/

int B[5] = {5};

Una vez que se definen los arreglos, sus elementos pueden recibir valores a

través de múltiples asignaciones, o bien, como ocurre frecuentemente en la práctica, a

través de un ciclo, este último, generalmente un ciclo para o for, pues se conoce

previamente la cantidad de elementos que contiene el arreglo.

/*Ejemplo de asignación individual después de haber

declarado un arreglo. El primer valor del arreglo vale

23.897*/

arreglo[0]=23.897

Si las posiciones de un arreglo no son inicializados o no se les asigna

explícitamente un valor, se considera que almacenan “basura”, pues en memoria

siempre existen datos. No se debe olvidar almacenar datos en un arreglo antes de

realizar operaciones con ellos, tal como sucede con las variables.

Se pueden realizar distintas operaciones con arreglos unidimensionales durante

el proceso de resolución de un problema, pero generalmente se tiene:

Asignación

Lectura/escritura

Recorrido (acceso secuencial)

actualizar (añadir, borrar, insertar)

Ordenación

Búsqueda

Puesto que en un arreglo se trabaja más de un dato, generalmente las

operaciones antes listadas se tienen que realizar usando ciclos como se mencionó con la

inicialización.

11.2 Arreglos bidimensionales

El arreglo bidimensional se considera un vector de vectores, generalmente

llamado tabla (término financiero) o matriz. Formalmente es una colección finita,

homogénea y ordenada de datos, en la que se hace referencia a cada elemento del

arreglo por medio de dos índices, uno para indicar el renglón o fila y el segundo para

indicar la columna del arreglo (Cairó, 2006). La figura 20 muestra la representación

gráfica de un arreglo bidimensional.

En el lenguaje C, la declaración de esta estructura es de la siguiente manera:

65

tipo nombre[num_renglones][num_columnas]

donde:

tipo es uno de los tipos predefinidos por el lenguaje: float, int, etc.

nombre: es un identificador que nombra el arreglo y sigue las mismas reglas

especificadas para dar nombre al identificador de una variable.

num_renglones es una constante entera positiva que especifica el número de filas

del arreglo.

num_columnas es una constante entera positiva que especifica el número de

columnas del arreglo.

Figura 20. Representación gráfica de un arreglo bidimensional .

Adaptado de Cairó (2006, p.214).

En el lenguaje C, los arreglos bidimensionales inician en las posiciones [0][0],

por lo que la posición máxima del arreglo está dada por el número máximo de

renglones menos 1 con el número máximo de columnas menos 1. Suponiendo que se

tienen N renglones y M columnas, entonces la posición máxima está en [N-1][M-1],

además, se almacenarán N x M elementos del mismo tipo.

El lenguaje C no revisa los límites de un arreglo, es responsabilidad del

programador el realizar este tipo de operaciones para no acceder a una posición negativa

o mayor al límite de elementos tanto para los renglones como para las columnas.

Ejemplos de declaración e inicialización de arreglos bidimensionales (Cairó,

2006):

66

/*Declara un arreglo bidimensional tipo entero llamado M

con 3 filas y 4 columnas*/

int M[3][4];

/*Declara un arreglo bidimensional llamado matriz de 4

renglones y 4 columnas, y un arreglo unidimensional llamado

b5 con 100 elementos, ambos arreglos tipo real o flotante*/

float matriz[4][4], b5[100];

/* Se almacena el valor 10 en el primer renglón y primera

columna de M*/

M[0][0]=10;

/* Se almacena el valor 3.14 en el tercer renglón y tercera

columna de matriz*/

matriz[2][2]=3.14;

/*Todos los elementos del arreglo se inicializan con cero

porque la cantidad de valores asignados es menor que la

dimensión del arreglo bidimensional.*/

int A[3][6] = {0};

/*O bien:*/

int A[3][6] = {{0}};

/*Los primeros cuatro componentes de la primera fila se

inicializan con los valores; 3, 4, 6 y 8. El resto con

cero.*/

int B[3][6] = {3,4,6,8};

/*O bien:*/

int B[3][6] ={ {3,4,6,8}};

/*Cada componente del arreglo recibe un valor. La

asignación se realiza fila a fila.*/

int C[3][6] = {6, 23, 8, 4, 11, 33, 21, 0, 6, 10, 23, 4, 8,

2, 1, 6, 9, 15};

/*La asignación anterior también se puede realizar de esta

forma. La representación gráfica del arreglo bidimensional

C se muestra en la figura 21.*/

int C[3][6] = {{6, 23, 8, 4, 11, 33}, {21, 0, 6, 10, 23,

4}, {8, 2, 1, 6, 9, 15}};

/*Error de sintaxis ya que la fila tiene espacio para seis

elementos y se asignan siete.*/

67

int C[3][6] = {{6, 23, 8, 4, 11, 35, 8}};

Si las posiciones de un arreglo no son inicializados o no se les asigna

explícitamente un valor, se considera que almacenan “basura”, pues en memoria

siempre existen datos. No se debe olvidar almacenar datos en un arreglo

bidimensional antes de realizar operaciones con ellos, tal como sucede con las

variables.

Figura 21. Representación gráfica del arreglo bidimensional C con 3x6 elementos de tipo

entero.

Adaptado de Cairó (2006, p.215).

Algunas de las operaciones que se realizan con arreglos bidimensionales son

asignación, lectura/escritura, y aquellas propias para matrices matemáticas, por ejemplo:

suma, multiplicación, inversa, etc., por lo que, el acceso a las posiciones de la tabla o

matriz se realiza normalmente utilizando ciclos anidados, pues para una matriz de m

por n, donde m es el número de renglones y n el número de columnas, generalmente se

requiere recorrer n columnas por cada renglón que constituye a la matriz, es decir,

recorrer n columnas m veces.

Finalmente, el máximo de dimensiones que puede tener un arreglo

multidimensional de más de dos dimensiones, queda determinado por el lenguaje de

programación que se utilice o por el espacio en memoria, por lo que la cantidad de

índices que se utilizan para acceder a sus valores, depende de la dimensión del arreglo.

En en el lenguaje C se declaran de la siguiente manera y como máximo se tienen

hasta 12 subíndices de arreglo:

tipo nombre[tamao1][tamaño2]…[tamaño_n]

68

NOTA IMPORTANTE: los corchetes, utilizados para cerrar el subíndice de un

arreglo de cualquier dimensión, son considerados como operadores y tienen el mismo

nivel de precedencia que los paréntesis (el orden de precedencia se revisó en el apartado

Tabla de prioridad y orden de evaluación de la sección 6: Lenguaje C).

12 CARACTERES, CADENAS DE CARACTERES Y ARREGLOS

DE CARACTERES

12.1 Caracteres

Los lenguajes de programación utilizan juegos de caracteres llamados alfabetos

para comunicarse con las computadoras. Las primeras computadoras sólo utilizaban

información numérica digital mediante el código o alfabeto digital y los primeros

programas se escribieron en ese tipo de código denominado código máquina, basado en

los dígitos 0 y 1 (como se mencionó en la sección 1: Preliminares, conceptos básicos)

por ser inteligible directamente por la computadora. La difícil tarea de programar en

código máquina hizo que el alfabeto evolucionara y los lenguajes de programación

comenzaran a utilizar códigos o juegos de caracteres similares a los utilizados en los

lenguajes humanos. Así, hasta el día de hoy, la mayoría de las computadoras trabajan

con diferentes tipos de juegos de caracteres, de los cuales destacan el código ASCII por

American Standard Code for Information Interchange y el EBCDIC por Extended

Binary Coded Decimal Interchange Code (Deitel y Deitel, 1995).

El código ASCII básico utiliza 7 bits para cada caracter a representar (el

concepto de bit se dio en la sección 1: Preliminares y conceptos básicos) lo que da un

total de 27 = 128 caracteres distintos. El código ASCII ampliado utiliza 8 bits, y en este

caso, consta de 28 = 256 caracteres. Este código ASCII adquirió una gran popularidad

ya que es el estándar en todas las familias de computadoras personales y se presenta en

la tablas 17 y 18 (Deitel y Deitel, 1995).

En general, un caracter ocupará un byte (o sea 8 bits) de almacenamiento de

memoria y se puede definir como un símbolo del juego de caracteres de la computadora.

Deitel y Deitel (1995) indican que el código ASCII se compone de los siguientes

tipos de caracteres:

Alfabéticos (a…z A…Z)

Numéricos (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

Especiales (+, -, *, /, {, }, <, etc.)

De control: son caracteres no imprimibles que realizan una serie de funciones

relacionadas con la escritura, transmisión de datos, separador de archivos, etc.,

en realidad con los dispositivos de entrada/salida. Destacan entre ellos: del

(eliminar o borrar), cr (retorno de carro), lf (avance de línea), ff (avance de

página), stx (inicio de texto), etc.

69

Tabla 17. Código ASCII de caracteres de control.

Adaptado de “ASCII” (2016).

Binario Dec. Hex. Abreviatura Representación Nombre/significado

0000 0000 0 00 NUL ^@ Caracter nulo

0000 0001 1 01 SOH ^A Inicio de encabezado 0000 0010 2 02 STX ^B Inicio de texto

0000 0011 3 03 ETX ^C Fin de texto 0000 0100 4 04 EOT ^D Fin de transmisión

0000 0101 5 05 ENQ ^E Enquiry 0000 0110 6 06 ACK ^F Acknowledgement

0000 0111 7 07 BEL ^G Timbre

0000 1000 8 08 BS ^H Retroceso 0000 1001 9 09 HT ^I Tabulación horizontal

0000 1010 10 0A LF ^J Avance de línea 0000 1011 11 0B VT ^K Tabulación vertical

0000 1100 12 0C FF ^L Avance de página 0000 1101 13 0D CR ^M Retorno de carro

0000 1110 14 0E SO ^N Shift Out

0000 1111 15 0F SI ^O Shift In 0001 0000 16 10 DLE ^P Data Link Escape

0001 0001 17 11 DC1 ^Q Control de dispositivo 1

XON

0001 0010 18 12 DC2 ^R Control de dispositivo 2 0001 0011 19 13 DC3 ^S Control de dispositivo

XOFF 0001 0100 20 14 DC4 ^T Control de dispositivo 4

0001 0101 21 15 NAK ^U Negative

Acknowledgement

0001 0110 22 16 SYN ^V Synchronous Idle 0001 0111 23 17 ETB ^W Fin de transmisión de

bloque 0001 1000 24 18 CAN ^X Cancelar

0001 1001 25 19 EM ^Y End of Medium

0001 1010 26 1A SUB ^Z Substitute 0001 1011 27 1B ESC ^[ or ESC Escape

0001 1100 28 1C FS ^\ Separador de archivo 0001 1101 29 1D GS ^] Separador de grupo

0001 1110 30 1E RS ^^ Separador de registro 0001 1111 31 1F US ^_ Separador de unidad

0111 1111 127 7F DEL ^?, Delete,

or

Backspace

Borrado o retroceso

Tabla 18. Código ASCII de caracteres alfabéticos, numéricos y especiales.

Adaptado de “ASCII” (2016).

Binario Dec. Hex. Representación Binario Dec. Hex. Representación 0010

0000

32 20 espacio ( ) 0101

0000

80 50 P

0010

0001

33 21 ! 0101

0001

81 51 Q

0010

0010

34 22 " 0101

0010

82 52 R

70

0010

0011

35 23 # 0101

0011

83 53 S

0010

0100

36 24 $ 0101

0100

84 54 T

0010

0101

37 25 % 0101

0101

85 55 U

0010

0110

38 26 & 0101

0110

86 56 V

0010

0111

39 27 ' 0101

0111

87 57 W

0010

1000

40 28 ( 0101

1000

88 58 X

0010

1001

41 29 ) 0101

1001

89 59 Y

0010

1010

42 2A * 0101

1010

90 5A Z

0010

1011

43 2B + 0101

1011

91 5B [

0010

1100

44 2C , 0101

1100

92 5C \

0010

1101

45 2D - 0101

1101

93 5D ]

0010

1110

46 2E . 0101

1110

94 5E ^

0010

1111

47 2F / 0101

1111

95 5F _

0011

0000

48 30 0 0110

0000

96 60 `

0011

0001

49 31 1 0110

0001

97 61 a

0011

0010

50 32 2 0110

0010

98 62 b

0011

0011

51 33 3 0110

0011

99 63 c

0011

0100

52 34 4 0110

0100

100 64 d

0011

0101

53 35 5 0110

0101

101 65 e

0011

0110

54 36 6 0110

0110

102 66 f

0011

0111

55 37 7 0110

0111

103 67 g

0011

1000

56 38 8 0110

1000

104 68 h

0011

1001

57 39 9 0110

1001

105 69 i

0011

1010

58 3A : 0110

1010

106 6A j

0011

1011

59 3B ; 0110

1011

107 6B k

0011

1100

60 3C < 0110

1100

108 6C l

0011

1101

61 3D = 0110

1101

109 6D m

0011 62 3E > 0110 110 6E n

71

1110 1110

0011

1111

63 3F ? 0110

1111

111 6F o

0100

0000

64 40 @ 0111

0000

112 70 p

0100

0001

65 41 A 0111

0001

113 71 q

0100

0010

66 42 B 0111

0010

114 72 r

0100

0011

67 43 C 0111

0011

115 73 s

0100

0100

68 44 D 0111

0100

116 74 t

0100

0101

69 45 E 0111

0101

117 75 u

0100

0110

70 46 F 0111

0110

118 76 v

0100

0111

71 47 G 0111

0111

119 77 w

0100

1000

72 48 H 0111

1000

120 78 x

0100

1001

73 49 I 0111

1001

121 79 y

0100

1010

74 4A J 0111

1010

122 7A z

0100

1011

75 4B K 0111

1011

123 7B {

0100

1100

76 4C L 0111

1100

124 7C |

0100

1101

77 4D M 0111

1101

125 7D }

0100

1110

78 4E N 0111

1110

126 7E ~

0100

1111

79 4F O

Una constante caracter se define como cualquier caracter encerrado entre

apóstrofos o comilla sencilla, específicamente en el lenguaje C, se usa la comilla

sencilla.

Para ingresar o mostrar en un dispositivo de salida tipos de datos caracter en el

leguaje C, se usan las instrucciones vistas en la sección 7: Instrucciones de entrada y

salida, las cuales pueden ser: scanf, printf, putchat o getchar.

Cairó (2006) menciona las operaciones que se realizan generalmente con

caracteres:

Verificar si un caracter es o no es un dígito.

Verificar si un caracter es o no es una letra

Verificar si un caracter es una letra minúscula o mayúscula

Convertir una letra a minúscula o mayúscula

72

Para realizar lo anterior, en el lenguaje C existen como parte de la biblioteca

ctype.h, las siguientes funciones: isdigit( ), isalpha( ), islower( ),

isupper( ), tolower( ) y toupper( ). Todas reciben como argumento el

valor que se quiere verificar o convertir (Cairó, 2006).

12.2 Cadenas de caracteres

Una cadena se define como una secuencia finita de caracteres que finaliza con el

caracter nulo ‘\0’ y se almacena en un área contigua de la memoria. Una constante

tipo cadena consiste en una cadena encerrada entre apóstrofos, comillas sencillas o

dobles comillas, específicamente en el lenguaje C, se encierra la cadena usando el

caracter de doble comilla, la cual no se puede modificar.

Las cadenas se pueden usar en aplicaciones de gestión, generación y

actualización de listas de dirección, inventarios, bases de datos, traductores de

lenguajes, etc.

Según Deitel y Deitel (1995) las operaciones comunes que se realizan con

cadenas de caracteres son las siguientes:

Cálculo de longitud de la cadena (número de caracteres que contiene la cadena,

incluyendo el espacio en blanco, sin incluir el caracter nulo. La cadena que no

contiene ningún carácter se denomina cadena vacía o nula y su longitud es

cero).

Comparación de cadenas (saber si dos cadenas con iguales o diferentes).

Concatenación de cadenas (unir cadenas para formar nuevas cadenas).

Extracción de subcadenas (extraer una porción de una cadena a la cual se le da

el nombre de subcadena).

Búsqueda de información en una cadena (búsqueda de ciertos caracteres, frases,

etc.).

Insertar subcadenas (agregar nuevas cadenas a cadenas ya formadas).

Borrar cadenas (eliminar cierta cantidad de caracteres a partir de una posición,

es decir, borrar subcadenas).

Cambiar una cadena o subcadena por otra subcadena o cadena.

Convertir cadenas en números y viceversa.

Para ingresar o mostrar en un dispositivo de salida tipos de datos cadena en el

lenguaje C, se usan las instrucciones vistas en la sección 7: Instrucciones de entrada y

salida, éstas son: scanf y printf. Así como las que describen en los ejemplos siguientes:

puts y gets.

char nombre[15];

73

/* Declara un arreglo para 15 caracteres. Si no se

especifica el caracter nulo al final de la inicialización,

lectura o asignación del arreglo, entonces, nombre es un

simple arreglo de caracteres, mas no una cadena, pero, si

en la inicialización, lectura o asignación de nombre se le

coloca el caracter nulo al final de todos los caracteres

que contenga, entonces no es un simple arreglo de

caracteres, sino una cadena. En resumen, la diferencia

entre un arreglo de caracteres y una cadena es la

existencia del caracter nulo. Si nombre debe ser una

cadena, entonces puede almacenar como máxima 14 caracteres,

pues además contendrá el caracter nulo.*/

char cadena[ ] = {‘p’,’r’,’i’,’m’,’e’,’r’,’o’,’\0’};

/* Se declara la cadena llamada cadena y se inicializa

caracter a caracter terminando con el caracter nulo. La

variable cadena contendrá la palabra “primero”, la cual

contiene 7 caracteres, pues en el conteo no se suma el

caracter nulo. El arreglo cadena se ajusta a un tamaño de 8

elementos en memoria, pues el caracter nulo ocupa un

espacio en memoria.*/

char frase[ ] = “El perro ladra sin parar.”;

/* Se declara la cadena llamada frase que contiene una

longitud de 25 caracteres, pero en memoria existen 26

asignaciones porque se agrega en automático el caracter

nulo a los caracteres.*/

#define SALUDO “Un saludo afectuoso”

/* Se declara una constante llamada SALUDO que contiene la

frase “Un saludo afectuoso”, es decir, la longitud de la

cadena es de 19 caracteres, mientras que en memoria se

almacenan 20 porque cuando no se inicializa caracter a

caracter sino como cadena (usando las comillas dobles)

automáticamente se agrega el caracter nulo.*/

Como se especificó en la sección 7: Instrucciones de Entrada y Salida (E/S),

existe el parámetro %s para mandar a pantalla variables o constantes de tipo cadena y

para ingresar desde el teclado cadenas que se almacenarán en variables de tipo cadena,

por ejemplo:

printf(“%s”, “Un afectuoso saludo”);

/*Imprime la cadena “Un afectuoso saludo”*/

printf(“%s”, SALUDO);

74

/*Imprime el valor de la constante tipo cadena, llamada

SALUDO*/

printf(“%s”, cadena);

/*Imprime el valor de la variable cadena que contiene la

palabra “primero”*/

puts(frase);

/* Imprime lo que contiene la variable frase: “El perro

ladra sin parar.” La instrucción puts no necesita que se le

indique el tipo de dato que está enviando a pantalla como

con printf y es la más apropiada para escribir cadenas de

caracteres. Esta función baja automáticamente una línea

después de imprimir. (Cairó, 2006)*/

scanf(“%s”, nombre);

/* En la variable nombre se almacenarán los caracteres que

se ingresen desde el teclado. Cuando se da un valor a una

variable desde el teclado, no es necesario teclear el

caracter nulo, éste se asigna automáticamente después de

pulsar la tecla enter. El problema con esta forma de

ingresar datos, es que el carácter espacio en blanco no se

almacenará en la variable, de hecho, si se escriben

caracteres después de un espacio en blanco, todos ellos no

se almacenarán.*/

gets(cadena);

/* Otra forma de ingresar caracteres desde teclado y a

diferencia del ejemplo anterior, si se ingresan caracteres

en blanco, éstos serán también almacenados en la variable

(Cairó, 2006). */

scanf(“%[^\n]”, frase);

/* En frase se almacenarán todos los caracteres que se

ingresen desde el teclado incluyendo el carácter de espacio

en blanco. El ingresar caracteres termina cuando se pulsa

la tecla enter, sin que este carácter se almacene en

memoria, al igual que la lectura con el tipo de dato %s, se

agrega automáticamente a la cadena el caracter nulo. En

realidad la traducción a lo que está entre corchetes es:

almacenas todo lo ingresado menos el caracter enter.*/

En el lenguaje C ya existen varias funciones para manipular cadenas. Dichas

funciones forman parte de la biblioteca string.h. Algunos ejemplos son:

75

strlen, para obtener la cantidad de caracteres que tiene una cadena.

strcmp, para comparar dos cadenas y saber si son iguales o no.

strcpy, para copiar el contenido de una cadena a otra cadena.

tolower, para convertir los caracteres de una cadena a minúsculas.

toupper, para convertir los caracteres de una cadena a mayúsculas.

12.3 Arreglos de caracteres

Un arreglo unidimensional de caracteres se define como una colección finita,

homogénea y ordenada de datos, en que se hace referencia a cada elemento del arreglo

por medio de un índice y no necesariamente finaliza con el caracter nulo (Cairó, 2006).

Como se mencionó anteriormente, la diferencia sustancial entre una cadena y un arreglo

de caracteres es la existencia al final de la cadena del carácter nulo.

Por otra parte, se pueden utilizar arreglos bidimensionales de caracteres para

manejar conjuntos de cadenas, esto es, dado que una cadena se define como un arreglo

unidimensional que termina con el caracter nulo, entonces, si se desea almacenar

cadenas de caracteres en arreglos bidimensionales, cada fila almacenaría una cadena y

cada columna almacenaría los caracteres correspondientes de cada cadena (Cairó,

2006). Por ejemplo, si se desea almacenar un grupo de 5 cadenas de caracteres con 30

caracteres como máximo para cada cadena, entonces se podría hacer una declaración

como la siguiente en el lenguaje C:

char cadenas[5][31];

/*El segundo valor es 31 porque se tiene que considerar el

almacenar el caracter nulo*/

así, el primer índice se utilizaría para hacer referencia a la fila, es decir a cada cadena; y

el segundo índice serviría para señalar el caracter de cada cadena.

13 FUNCIONES

Como se mencionó en el apartado Diseño de un algoritmo de la sección 5:

Proceso de resolución de problemas utilizando un lenguaje de programación, la

resolución de un problema complejo se facilita si dicho problema se divide en

problemas más pequeños, es decir, si se divide en subproblemas, los cuales son tratados

de forma individual e independiente, diseñando así subalgoritmos que se convierten en

subprogramas (Bermúdez et al., 2003). Los subprogramas que se forman con éste

método pueden ser de dos tipos: funciones o procedimientos, estos últimos también

conocidos como subrutinas.

76

Los subprogramas están diseñados para ejecutar alguna tarea específica, se

escriben solamente una vez, pero pueden utilizarse en diferentes puntos de un programa,

de manera que se puede evitar la duplicación innecesaria de código.

Los subprogramas o módulos se escriben sin entrar en detalles con otros

módulos facilitando así la localización de errores.

Un subprograma puede realizar las mismas acciones que un programa: leer datos

desde dispositivos de entrada, hacer asignaciones, realizar cálculos, devolver resultados

a través de un dispositivo de salida, etc., pero en general se utiliza para un propósito

específico. El subprograma recibe datos desde el programa principal o desde cualquier

subprograma que lo llama, procesa dichos datos y si es necesario, devuelve resultados al

programa o subprograma que lo haya llamado.

El enfoque en estos apuntes será sobre los subprogramas llamados funciones,

que son los únicos que existen en el lenguaje C.

Función

Matemáticamente hablando una función es una operación que toma uno o más

valores llamados argumentos y produce un valor denominado resultado, es decir,

genera el valor de la función para los argumentos dados, por otro lado, una función

computacionalmente hablando es una colección de sentencias que ejecuta una tarea

específica y no puede contener a otra función (Ceballos, 1997).

Todos los lenguajes de programación tienen funciones incorporadas o intrínsecas

que se utilizan escribiendo sus nombres con los argumentos adecuados entre paréntesis,

(como las funciones matemáticas del lenguaje C: sqrt, pow, sin, etc.) y funciones

definidas por el usuario o programador, es decir, escritas por él mismo.

Las funciones incorporadas al sistema se denominan funciones internas o

intrínsecas y las funciones definidas por el usuario, funciones externas o extrínsecas.

Cuando las funciones estándares o internas no permiten realizar el tipo de cálculo

deseado es necesario recurrir a las funciones externas.

Una vez que una función ha sido escrita y depurada puede utilizarse una y otra

vez. Cuando una función se manda a llamar, el control se pasa a dicha función para su

ejecución y cuando ésta finaliza, el control es devuelto al módulo que la llamó para

continuar con la ejecución del mismo después de donde se efectuó la llamada, como se

muestra en la figura 22 (Bermúdez et al., 2003). En ese ejemplo, dentro de la función

principal del lenguaje C, o sea, la función main se hace el llamado a la función

denominada func1, por lo que la secuencia de ejecución se pasa a dicha función,

realizándose una a una las instrucciones dentro de func1, pero al llegar al llamado de

func2, nuevamente la secuencia de la ejecución se desvía hacia la función func2. Se

ejecutan las instrucciones que existan dentro de la función func2 hasta alcanzar el

return, con lo cual se regresa el control a func1 para continuar con la ejecución de las

instrucciones que contenga hasta llegar al return de func1, con lo cual se regresa el

control a la función principal main. Como nuevamente se hace el llamado a la función

func1, se vuelve a realizar el proceso descrito hasta regresar a la función principal main

para ejecutarse todas las instrucciones que contenga main hasta llegar al final del

77

programa, en conclusión, como se muestra en la figura 22, el orden de ejecución es

conforme la numeración del 1 al 8.

Figura 22. Ejemplo del llamado de una función en el lenguaje C.

Adaptado de Ceballos (1997, p. 251).

13.1 Funciones definidas por el usuario en el lenguaje C

Todo programa en C consta al menos de la función main( ), que es donde inicia

la ejecución de un programa. También puede constar de otras funciones que ya ofrece el

lenguaje y de aquellas que diseña el programador.

Las funciones que declara el usuario pueden aparecer en cualquier orden y en uno o

varios archivos fuente, conformándose de un encabezado y un cuerpo. De manera explícita se

puede decir que, es un bloque o una proposición compuesta. La estructura básica de la

definición de una función es la siguiente:

tipo_de_retorno nombre (parámetros formales)

{

declaraciones;

proposiciones;

return(expresión);

}

a) Encabezado de una función

tipo_de_retorno indica el tipo del valor devuelto por la función. Puede ser cualquier

tipo básico, estructura o unión. Por defecto es del tipo int. Cuando no se requiere que

devuelva algún valor se usará el tipo void.

nombre es un identificador que indica el nombre de la función. Sigue las mismas reglas

que se tienen para nombrar una variable.

78

Parámetros formales es una secuencia de declaraciones separadas por coma. Cada

parámetro, es decir, variable o arreglo, debe ir con el tipo de dato correspondiente. Si no

se pasan argumentos a la función se puede utilizar la palabra reservada void.

Generalmente se usa la palabra parámetro formal para una variable nombrada en la

lista entre paréntesis de la definición de función, y argumento o parámetro actual para

el valor empleado al hacer la llamada de la función.

b) Cuerpo de la función

Está formado por una sentencia compuesta que contiene sentencias que definen

lo que hace la función. Las declaraciones de variables utilizadas en la función por

defecto son locales a la función, es decir, sólo son definidas, vistas y válidas dentro de

la función donde son empleadas, pero fuera de dicha función, no son conocidas y no

tienen valor.

return (expresión) se utiliza para devolver el valor de la función, el cual debe ser del

mismo tipo declarado en el encabezado de la función. Si la sentencia return no se

especifica o se especifica sin contener una expresión, la función no devuelve un valor.

Cuando explícitamente se devuelve un valor, éste puede ir o no entre paréntesis, es

decir, los paréntesis son opcionales.

La instrucción return puede ser o no la última que aparezca en el cuerpo de la

función y puede aparecer más de una vez, dependiendo del problema que se esté

resolviendo. En el caso de que la función no retorne algún valor, la instrucción se puede

omitir.

c) Llamado de una función

Para ejecutar una función hay que llamarla. La llamada a una función se hace

mediante su nombre con los argumentos o parámetros actuales o reales entre

paréntesis. Generalmente se asigna el valor que regresa una función a una variable del

mismo tipo de ésta.

Cuando se llama a una función, el valor del primer parámetro actual es pasado al

primer parámetro formal, el valor del segundo parámetro actual es pasado al segundo

parámetro formal y así sucesivamente. Todos los argumentos excepto los arreglos, son

pasados por valor. Esto es, a la función se pasa una copia del argumento, no su

dirección en memoria, esto hace que la función C no pueda alterar los contenidos de las

variables transmitidas. Si se desea poder alterar los contenidos de los argumentos en la

llamada entonces hay que pasarlos por referencia (método que se verá más adelante, en

la sección 13.5).

Ejemplos: Ejemplo 1 Ejemplo 2

79

#include <stdio.h>

void letrero(void)

{

printf(“\n Esta es una función

muy simple”);

return;

}

int main( ) {

letrero( );

return 0;

}

#include <stdio.h>

void letrero2(int i)

{

if (i>0) printf(“\n i es

positivo”);

else printf(“\n i es

negativo”);

}

int main( ) {

letrero2(10);

return 0;

}

Ejemplo 3

#include <stdio.h>

int suma(int a, int b)

{

int valor;

valor = a+b;

return (valor);

}

int main( )

{

int res;

int n1=23;

int n2=55;

res = suma(5,10) ;

printf( “La suma es: %d \n”, res);

res = suma(n1,n2);

printf(“La suma de %d + %d = %d

\n”, n1,n2,res);

printf(“La suma de -25 + -12 =

%d”,suma(-25,-12));

return 0;

}

13.2 Prototipo de funciones

Antes de usar o llamar a una función, el lenguaje C debe tener conocimiento del

tipo de dato que regresará y el o los tipos de los parámetros que la función espera

recibir. Por tanto, esos valores los conoce el lenguaje cuando la función se declara y

define, es decir, cuando la función se programa antes de la función main, o en general,

antes de su llamado en cualquier parte del programa. Sin embargo, existen casos donde

la función es llamada sin ser declarada previamente o sin conocer el código que la

conforma provocando errores de compilación, por lo que, el lenguaje permite el uso de

declaraciones forward (también llamadas prototipos de funciones) o bien, genera

automáticamente una declaración prototipo implícita (Ceballos, 1997).

80

Un prototipo de función (también conocido como declaración forward) permite

conocer el nombre de la función, el tipo de resultado que devolverá, así como los tipos y

nombres de los parámetros a recibir; todo ello antes de especificar el cuerpo de la

función, es decir, antes de haberla definido o programado7.

La importancia de usar prototipos de funciones o declaración explícita radica

en que:

Se hace el código más estructurado y por lo tanto más fácil de leer.

Permite conocer las características de la función antes de ser utilizada.

Lo anterior aplica dependiendo del alcance de la función. Básicamente si una

función ha sido definida antes de que sea usada o llamada, entonces se puede usar la

función sin problemas.

7 Las funciones prototipo de las funciones pertenecientes a la biblioteca estándar del

lenguaje C son provistas por los ficheros de cabecera estándar: archivos .h, como se

mencionó en la sección 6: Lenguaje C.

Ejemplo adatado de Deitel y Deitel (1995, p. 156):

/*Encontrar el mayor de tres números reales*/

#include <stdio.h>

float maximo(float, float, float); /* Prototipo de la función

maximo */

int main( )

{

float a, b, c;

printf(“Ingrese los tres datos:”);

scanf(“%f%f%f”, &a, &b, &c);

printf(“El máximo es: %f\n”, máximo(a,b,c));

return 0;

}

float maximo (float x, float y, float z)

{

float max = x;

if (y > max) max = y;

if (z > max) max = z;

return max;

}

81

El prototipo de la función maximo es: float maximo(float, float,

float;

Ese prototipo tiene la misma sintaxis que la primera línea de la definición de la

función (la que se encuentra después de la función main), excepto que termina con

punto y coma y no contiene los nombres de los parámetros: x, y, z, de hecho son

opcionales en el prototipo de una función, de modo que, para el prototipo se puede

escribir también: float maximo(float x, float y, float z);

Lo más común es omitir los identificadores para colocar sólo los tipos.

Es importante recalcar que si la definición de una función o cualquier uso que

de ella se haga no corresponden con su prototipo ocasionará un error.

Conversiones de tipo

En el prototipo de función, es importante que los argumentos coincidan con el

tipo apropiado, es decir, que haya coerción de argumentos. Por ejemplo, la función

matemática sqrt de la biblioteca estándar puede ser llamada con un argumento entero,

aun cuando el prototipo de la función en el archivo de cabecera math.h especifica un

parámetro de tipo double, sin que esto ocasione un error en el resultado. El prototipo de

función hará que el valor entero se convierta a double. (Deitel y Deitel, 1995).

En general, los valores de los argumentos que no correspondan precisamente a

los tipos de los parámetros del prototipo de función serán convertidos al tipo

apropiado, antes de que la función sea llamada. Estas conversiones pueden llevar a

resultados incorrectos si no son seguidas las reglas de promoción del leguaje C. Las

reglas de promoción definen cómo deben ser convertidos los tipos de datos a otros tipos

de datos, sin perder información. Por ejemplo, un tipo double convertido a entero

truncará la parte fraccional del valor double. Nuevamente, en general, convertir tipos

enteros grandes a tipos enteros pequeños puede dar domo resultado valores modificados

(Deitel y Deitel, 1995).

Las reglas de promoción se aplican automáticamente a expresiones que

contengan valores de dos o más tipos distintos de datos (también conocidas como

expresiones de tipo mixto). El tipo de cada valor en una expresión de tipo mixto es

automáticamente promovido al tipo más alto en la expresión, es decir, se crea una

versión temporal de cada valor y se utiliza para la expresión conservándose sin cambios

los valores originales (Deitel y Deitel, 1995). Ceballos (1997) presenta las reglas de

promoción que se aplican automáticamente:

1. Si en una operación, el operador de precisión más alta es de tipo long double,

entonces, el otro operador es convertido a tipo long double.

2. Si en una operación, el operador de precisión más alta es de tipo double,

entonces, el otro operador es convertido a tipo double.

3. Si en una operación, el operador de precisión más alta es de tipo float, entonces,

el otro operador es convertido a tipo float.

82

4. Si en una operación, el operador de precisión más alta es de tipo unsigned long

int, entonces, el otro operador es convertido a tipo unsigned long int.

5. Si en una operación, el operador de precisión más alta es de tipo long int,

entonces, el otro operador es convertido a tipo long int.

6. Si en una operación, el operador de precisión más alta es de tipo unsigned int,

entonces, el otro operador es convertido a tipo unsigned int.

7. Si en una operación, el operador de precisión más alta es de tipo int, entonces, el

otro operador es convertido a tipo int.

8. En una operación, cualquier operando de tipo short es convertido a tipo int.

9. En una operación, cualquier operando tipo char, es convertido a tipo int.

10. Cualquier operando de tipo unsigned short es convertido a tipo unsigned int.

11. Cualquier operando de tipo unsigned char es convertido a tipo unsigned int.

La conversión de valores a tipos inferiores por lo regular resulta en un valor

incorrecto. Por lo tanto, un valor puede ser convertido sólo a un valor inferior,

asignando de manera explícita el valor a una variable de tipo inferior o mediante el uso

de un operador cast. Los valores de los argumentos de las funciones son convertidos a

los tipos de parámetro en un prototipo de función, como si hubieran sido asignados

directamente a las variables de esos tipos, es decir, las conversiones son ejecutadas

independientemente sobre cada argumento en la llamada (Deitel y Deitel, 1995). En

general, los reales son convertidos a enteros, truncando la parte fraccionaria y un double

pasa a float redondeando y perdiendo precisión si el valor double no puede ser

representado exactamente como float.

Ahora bien, el operador cast (casting) permite realizar una conversión forzada o

explícita con la siguiente sintaxis:

(nombre_de_tipo_de_dato) expresión

Lo anterior provoca la conversión del valor de la expresión al tipo nombrado

entre paréntesis, aplicando las reglas de conversión expuestas anteriormente.

Ejemplos:

float r;

int i;

r = i; /* i es convertido a float para ser asignado a r */

i = r; /*r es truncada para quedar como entero y ser

asignado a i */

i = 7/3; /* i = 2 */

r = 7/3; /* i = 2.000000 */

i = 12.6/3; /* i = 4 */

r = 12.6/3; /* i = 4.2 */

83

Declaración de prototipo implícita

La declaración de prototipo implícita, la cual se mencionó al inicio de esta

sección, se da cuando la función es llamada sin existir una declaración previa de la

misma y la definición o código de la función se hace o se escribe después de la función

principal main. En este caso, el lenguaje C por defecto construye una función prototipo

con tipo de resultado int, pero no se supone nada en relación con los argumentos. Esto

obliga entonces a que el tipo del resultado en la definición de la función sea del tipo int

y a que, si los argumentos pasados a la función no son correctos, el compilador no los

detecte (Ceballos, 1997).

La función escribir es declarada implícitamente para retornar un valor int, ya

que es invocada antes de su definición. El compilador crea automáticamente una

función prototipo utilizando la información de la llamada a la función.

13.3 Paso de parámetros por valor y por referencia

En C existen dos tipos de parámetros en el llamado a una función: por valor o

por referencia.

Paso de parámetros por valor

Significa que la función que se invoca recibe los valores de sus argumentos en

variables temporales y no en las originales, por lo que la función no puede alterar

directamente una variable de la función que hace la llamada, sólo puede modificar su

copia privada y temporal. Significa también copiar los parámetros actuales en sus

Ejemplo:

#include <stdio.h>

int main( )

{

int r, b = 5 ;

r = escribir(b) ;

printf(“%d\n”, r);

return 0;

}

int escribir (int y)

{

return y * 3 ;

}

84

correspondientes parámetros formales, operación que se hace automáticamente cuando

se llama a una función, con lo cual no se modifican los parámetros actuales. Con este

método pueden ser transferidas constantes, variables y expresiones.

Paso de parámetros por referencia o dirección

Lo que se transfiere no son los valores sino las direcciones de las variables que

contienen esos valores, con lo cual los argumentos actuales de la función pueden verse

modificados. Una variable tiene una posición de memoria asignada (área de

almacenamiento o dirección) desde la cual se puede obtener o actualizar su valor, por

tanto, el paso de parámetros por referencia es útil cuando la función que llama requiere

que la función llamada modifique el valor de las variables que se pasan como

argumentos.

Lo anterior se logra utilizando apuntadores (el tema de apuntadores no se trata

ampliamente en este documento, pues corresponde a la asignatura Programación II, sin

embargo, se menciona de forma básica y concisa, de tal manera que se pueda entender

el paso de parámetros por referencia).

Apuntadores

Una variable por lo regular almacena un valor específico, por su parte, un

apuntador almacena la dirección de una variable que contiene un valor específico. En

este sentido, un nombre de variable se refiere directamente a un valor y, un apuntador

se refiere indirectamente a un valor. El referirse a un valor a través de un apuntador se

conoce como indirección (Deitel y Deitel, 1995).

Los apuntadores, como cualquier otra variable, deben ser declarados antes de

que puedan usarse, lo cual se hace de la siguiente manera:

Tipo_de_dato *nombre_del_apuntador

donde:

Tipo_de_dato es cualquiera de los que existen en el lenguaje C

* es el símbolo obligatorio que se coloca antes del nombre del apuntador

Nombre_del_apuntador sigue las mismas reglas que los identificadores de variables

También, los apuntadores deben ser inicializados cuando son declarados en una

expresión de asignación, ya sea con el valor de la constante simbólica NULL, o bien,

con una dirección de memoria. Un apuntador con el valor NULL apunta a nada.

En la figura 23 se muestra un ejemplo gráfico de apuntadores. Los rectángulos

representan porciones de memoria; arriba de ellos se encuentran los identificadores de

las variables, es decir, los nombres con los que se reconocen esas porciones de memoria

por parte del programador, y los números debajo de los rectángulos son las posiciones

reales de memoria (en hexadecimal) que ocupan las variables. El contenido de cada

rectángulo es el valor que almacena cada variable. En la parte izquierda se tiene una

85

representación gráfica del apuntador aptValor “apuntando” a la variable valor, y en la

parte derecha se muestra la dirección real que almacena, la cual corresponde a la

variable valor. Además, el apuntador aptSuma al haberse inicializado con el valor

NULL, no apunta a alguna porción de memoria como lo hace aptValor.

Como se observa en el ejemplo, con los apuntadores se hace uso del operador de

referencia & u operador de dirección (que como se ha visto antes, se usa con la

instrucción scanf), el cual regresa la dirección del operando que lo acompaña. Así, a la

variable aptValor se le asigna la dirección de memoria donde se encuentra la variable

valor, por lo que se suele decir que “aptValor apunta a la variable valor”. Por otro lado,

el operador de dirección no puede ser aplicado a constantes o expresiones.

int valor = 55, *aptSuma = NULL, *aptValor = &valor;

aptValor valor aptSuma aptValor

0028FEEC 0028FEE8 0028FEE4 0028FEEC

55 0028FEE8

Figura 23. Representación gráfica y en memoria de variables y apuntadores.

Otro operador que se usa con apuntadores es el *, conocido como el operador de

indirección o de desreferencia. Este operador lo que hace es devolver el valor al que

apunta (valga la redundancia) un apuntador. Retomando el ejemplo anterior, si se usara

la instrucción: printf(“%d”, *aptValor), se tendría en pantalla el valor 55.

NOTA: para mandar a pantalla el valor de las direcciones de memoria en

hexadecimal se puede utilizar la especificación de formato %p en la instrucción de

salida printf (revisar la tabla especificaciones de formato para printf de la sección 7:

Instrucciones de entrada y salida). Por ejemplo: printf(“%p-%p-%p”, &valor,

aptValor, &aptValor); tendría como salida: 0028FEE8-0028FEE8-

0028FEEC.

Llamadas por referencia en una función

Cuando se llama a una función con argumentos que deban ser modificados, se

pasan las direcciones de los argumentos. Esto puede necesitarse cuando se requiere

modificar una o más variables de quien llama o cuando se requiere pasar un apuntador a

un objeto de datos grades, como una estructura de datos: arreglo, evitando así el hacer

demasiadas copias de los valores (Deitel y Deitel, 1995).

Por regla, cuando se llama a una función, los argumentos enviados en la llamada

son pasados por valor, a menos que se use el operador de dirección (&) a las variables

cuyos valores serán modificados, pues con ello, se estará especificando que los

argumentos se pasan por referencia. Los arreglos no se pasan con el operador & porque

el lenguaje C pasa de forma automática la posición inicial en memoria del arreglo, de

86

hecho, el nombre de un arreglo es equivalente a escribir &nombre_arreglo[0] (Deitel y

Deitel, 1995). En otras palabras, los arreglos automáticamente se pasan por

referencia escribiendo sólo su nombre.

Cuando se pasa a una función la dirección de una variable, se puede utilizar el

operador de indireccion (*) dentro de la función para modificar el valor de esa posición

de memoria, es decir, para modificar el valor de la variable de quien llama.

Ejemplo adaptado de Ceballos (1997, p.268):

#include <stdio.h>

void sumar(int, int, int, int *);

/*Prototipo de la función sumar*/

int main( )

{

int v = 5, res = 0;

sumar(4, v, v*2-1, &res);

printf(“%d”,res);

return 0;

}

void sumar(int a, int b, int c, int *s)

{

b+=2;

*s = a + b + c;

}

La llamada a la función sumar pasa los parámetros 4, v, y v*2-1 por valor; el

primero es una constante, el segundo es una variable y el terceo una expresión

aritmética; pero el parámetro res es pasado por referencia. Cualquier cambio que sufra

el argumento s, sucede también en su correspondiente parámetro actual res. En cambio,

la variable v no se ve modificada, a pesar de haber variado b dentro de la función, ya

que ha sido pasada por valor. Aquí, la variable res dentro de main se inicia con el valor

0, pero al pasarse su dirección de memoria, el apuntador s apunta a dicha variable; como

dentro de la función sumar, se usa el operador de indirección para modificar el valor al

que apunta s, entonces el valor de la variable res es cambiado por la suma de los valores

que contienen las variables a, b y c. Es decir, la salida a pantalla del valor que almacena

res no será 0, sino 20.

Por último, como se mencionó antes, en el prototipo de una función se pueden

omitir los nombres de los parámetros, por ello, en el prototipo de la función sumar, el

87

apuntador sólo se especifica con el tipo de dato al que apunta y el *, sin escribir el

nombre s.

13.4 Reglas básicas de alcance o ámbito (scope)

El alcance o ámbito de un identificador es la porción del programa en el cual

dicho identificador puede ser referenciado. Por ejemplo, cuando en un bloque

declaramos una variable local, puede ser referenciada sólo en ese bloque o en los

bloques anidados dentro de él. Los tres alcances posibles para un identificador son:

alcance de archivo, alcance de bloque y alcance del prototipo de función.

Un identificador declarado por fuera de cualquier función tiene alcance de

archivo. Tal identificador es conocido en todas las funciones desde el punto donde el

identificador se declara hasta el final del archivo. Las variables globales, las

definiciones de funciones y los prototipos de función colocados fuera de una función

tienen alcance de archivo (Deitel y Deitel, 1995).

Los identificadores dentro de un bloque, tienen alcance llamado así, de bloque.

Se entiende por bloque lo que se encierra entre llaves. Las variables locales declaradas

al principio de una función tienen alcance de bloque como lo tienen los parámetros de

función, que son consideradas por la función como variables locales. Cualquier bloque

puede contener declaraciones de variables. Cuando los bloques están anidados y un

identificador de un bloque externo tiene el mismo nombre que un identificador de un

bloque interno, el identificador del bloque externo estará “oculto” hasta que el bloque

interno termine. Esto significa que, en tanto se ejecute el bloque interno, éste ve el valor

de su propio identificador local y no el valor del identificador de nombre idéntico del

bloque que lo contiene (Deitel y Deitel, 1995).

Los únicos identificadores con alcance de prototipo de función son los que se

utilizan en la lista de parámetros del prototipo de una función. Tal y como se mencionó,

los prototipos de función no requieren de nombres en la lista de parámetros, sólo

requieren de tipos. Si en la lista de parámetros de un prototipo de función se utiliza un

nombre, el compilador ignorará dicho nombre. Los identificadores utilizados en un

prototipo de función, pueden ser reutilizados en cualquier parte del programa, sin

ambigüedad (Deitel y Deitel, 1995).

Ejemplo:

{

int a = 5;

printf(“\n%d”, a);

{

int a = 7;

printf(“\n%d”, a);

}

printf(“\n%d”, ++a);

}

88

Se ha declarado la misma variable dos veces, pero aunque tengan el mismo

nombre son variables distintas y por lo tanto sus valores son distintos, en la segunda

declaración de la variable a, ésta se destruye cuando alcanza el fin del bloque de

proposiciones (primer llave cerrada), por lo que los valores que se verán impresos en

pantalla son: 7 y 6.

13.5 Variables locales y variables globales

La regla de alcance es utilizada comúnmente para utilizar variables globales y

locales.

Las variables globales se declaran al inicio del programa fuera del main y fuera

de cualquier función, en cambio las variables locales se declaran dentro de algún

bloque. La diferencia sustancial entre estos dos tipos de variables es el alance: las

variables globales pueden modificar su valor en cualquier parte del programa, mientras

que las variables locales sólo pueden ser usadas en el bloque donde fueron definidas.

Cada variable local de una función comienza a existir sólo cuando se llama a la

función y desaparece cuando la función termina. Debido a que las variables locales

aparecen y desaparecen con la invocación de funciones, no retienen sus valores entre

dos llamadas sucesivas y deben ser inicializadas explícitamente en cada entrada, de no

hacerlo, contendrán basura, es decir, cualquier dato que se halle en la localidad de

memoria a la que apunta.

Las variables globales (externas) se mantienen permanentemente en existencia,

en lugar de aparecer y desaparecer cuando se llaman y terminan las funciones,

mantienen sus valores aún después de que se regresa del llamado a la función que les

fijó algún valor.

Ejemplo:

#include <stdio.h>

int x = 20;

void escribe_x(void);

int main( )

{

int x = 12;

escribe_x ( );

printf (“El valor de x (local) es= %d \n”, x);

89

return 0;

}

void escribe_x( )

{

printf(“El valor de x (global) es = %d \n”, x);

}

La salida será: 20, 12.

Una variable local a un subprograma no tiene significado en otros subprogramas.

Si un subprograma asigna un valor a una de sus variables locales, este valor no es

accesible a otros programas, es decir, no pueden utilizar ese valor. A veces, también es

necesario que una variable tenga el mismo nombre en diferentes subprogramas. Por el

contrario, las variables globales tienen la ventaja de compartir información de diferentes

subprogramas sin una correspondiente entrada en la lista de parámetros.

Las variables definidas en un ámbito son accesibles en el mismo, es decir, en

todos los procedimientos interiores. La figura 24 presenta un ejemplo de declaración de

variables en distintos bloques de código y con ello, especifica desde dónde son válidas o

accesibles dichas variables.

Figura 24. Ejemplo de alcance de una variable en el lenguaje C.

Variables definidas

en

Accesibles desde

A A, B, C, D, E, F, G

B B, C

C C

D D, E, F, G

E E, F, G

F F

G G

90

14 ANEXOS

14.1 Prácticas sanas de programación

Deitel y Deitel (1995) al final de cada capítulo de su obra detallan prácticas sanas

de programación, es decir aquello que se debe y no se debe hacer a la hora de

programar, aquí se rescatan algunas de dichas prácticas por temas abarcados en la

materia.

Sobre estructuras secuenciales

1. Seleccionar nombres de variables significativas, es decir, que indiquen lo que

almacenarán.

2. Si las variables van a constar de varias palabras, utilizar el guion bajo para

separar dichas palabras.

3. Separar declaraciones de variables y enunciados ejecutables por una línea en

blanco para enfatizar dónde terminan las declaraciones y dónde comienzan los

enunciados ejecutables.

4. Colocar una línea en blanco antes y después de cada estructura de control para

mayor legibilidad.

5. Colocar espacios en blanco a ambos lados de un operador lógico o relacional

para resaltar el operador y hacer que el programa sea más legible.

6. Colocar una sangría en el o los enunciados del cuerpo de una estructura if.

7. Si no se está totalmente seguro del orden de evaluación en una expresión

compleja, utilizar paréntesis para obligar al orden, exactamente como se haría en

expresiones algebraicas.

8. Utilizar sólo letras mayúsculas para los nombres de constantes simbólicas, así

resaltarán en el programa y harán recordar que no se pueden cambiar sus valores

porque son constantes y no variables.

9. En expresiones donde se utilice el operador lógico &&, colocar primero la

condición que más probabilidades tenga de ser falsa; en expresiones que utilicen

el operador lógico ||, colocar primero la condición que más probabilidades tenga

de ser verdadera. Esto puede reducir el tiempo de ejecución de un programa.

10. Escribir las llaves de principio y de terminación de los enunciados compuestos

(como los ciclos o condiciones simples) antes de empezar a escribir en el interior

de dichas llaves los enunciados individuales, esto ayudará a evitar la omisión de

una llave o ambas.

11. Inicializar variables que se utilicen como contadores o como totales.

12. Al ejecutar una división por una expresión cuyo valor pudiera ser cero, probar de

forma explícita este caso y manejarlo de manera apropiada en el programa que

se esté creando, por ejemplo, se puede imprimir un mensaje de error en lugar de

permitir que ocurra un error fatal al momento de ejecutarse el programa.

13. En una estructura switch, cuando la cláusula default se enlista al final, el

enunciado break no es requerido, pero se recomienda incluirlo para fines de

claridad y simetría con otros cases.

91

Sobre estructuras repetitivas

1. Controlar el contador de ciclos con valores enteros, es decir, aunque se permite

usar variables reales, lo adecuado es usar variables de tipo int.

2. Colocar sangrías en los enunciados del cuerpo de cada estructura de control

repetitiva.

3. Demasiados niveles anidados pueden dificultar la comprensión de un programa.

Como regla general evitar el uso de más de tres niveles.

4. Colocar sólo expresiones que involucren las variables de control en las secciones

de inicialización y de incremento de una estructura for. Las manipulaciones de

las demás variables deberían de aparecer, ya se antes del ciclo (si se ejecutan una

vez, como los enunciados de inicialización) o dentro del cuerpo del ciclo (si se

ejecutan una vez en cada repetición, como son los enunciados incrementales o

decrementales).

5. Aunque el valor de la variable de control puede ser modificado en el cuerpo de

un ciclo for, ello podría ocasionar errores sutiles, por tanto, lo mejor es no

cambiarlo.

6. Al ciclar a través de un arreglo, el subíndice de un arreglo no debe de pasar

nunca por debajo de 0 ni ser mayor que el número total de elementos del arreglo

menos uno (tamaño - 1).

Sobre funciones

1. Colocar una o dos líneas en blanco entre definiciones de funciones para

separarlas y para mejorar la legibilidad del programa.

2. Aunque una función por omisión regrese un valor de tipo entero, no omitirlo en

la declaración de la función sino utilizar el tipo int en forma explícita.

3. Aunque hacerlo no es incorrecto, para evitar cualquier ambigüedad, es mejor no

utilizar los mismos nombres en los argumentos que se pasan a una función y en

los que se usan en el momento de la definición de la función.

4. Dar nombres a las funciones de acuerdo a lo que evaluarán como se sugiere con

las variables.

5. Incluir prototipos de función para todas las funciones que se vayan a utilizar.

6. Si se requiere que una función regrese más de un valor o una función recibirá

demasiados parámetros, entonces se sugiere dividir a esa función en funciones

más pequeñas.

7. La declaración de una variable como global en lugar de local, permite que

ocurran efectos colaterales no deseados cuando una función que no requiere de

acceso a dicha variable, la modifica accidentalmente. En general, el uso de

variables globales debe ser evitado.

92

¿Cómo NO realizar una práctica de programación?

1. Ignorar los mensajes de error.

2. Ignorar las advertencias (warnings).

3. Escribir código directamente sin plantear un algoritmo.

4. Aunque el código no compile o no funcione, seguir programando.

5. Si el código tiene un error que no se produce siempre, ignorarlo y seguir

escribiendo.

6. Si el código tiene un error que se produce siempre, cambiar cosas

aleatoreamente hasta que desaparezca.

7. Construir enormes porciones de código sin compilar, ejecutar y probar.

8. No escribir comentarios, salvo los obligatorios.

9. Ignorar los enunciados y detalles que se especifican en un problema a resolver.

10. Ignorar las normas de programación y estructura de un programa.

11. No aprender a utilizar el depurador ni otras herramientas.

12. No aislar o separar el problema en subproblemas.

93

15 REFERENCIAS

Arellano Pimentel, J. J., Nieva García O. S., Solar González, R. y Arista López, G.

(diciembre, 2012). Software para la enseñanza-aprendizaje de algoritmos

estructurados. Revista Iberoamericana de Educación en Tecnología y

Tecnología en Educación. 8, 23-33.

ASCII (2016, septiembre 13). En Wikipedia. Recuperado de: https://es.wikipedia.org/wiki/ASCII

Bermúdez Juárez B., Beltrán Martínez, B., Bello López, P., Cervantes Márquez, A. P.,

Castillo Zacatelco, H., De La Rosa Flores, R., … Vázquez Flores, A. (2003).

Notas de Curso. Facultad de Ciencias de la Computación de la Benemérita

Universidad Autónoma de Puebla, México.

Berzal Galiano, F. (s.f.). Introducción a la informática. Recuperado de:

http://elvex.ugr.es/decsai/java/

Candela S., García, C. R., Quesada, A., Santana, F. J. y Santos, J. M. (2007).

Fundamentos de sistemas operativos, teoría y ejercicios resueltos. España:

Thompson.

Cairó Battistutti, O. (2006). Fundamentos de programación. Piensa en C (1ra. ed.).

México: Pearson Educación de México, S.A. de C.V.

Ceballos, F. J. (1997). Enciclopedia del Lenguaje C. México: Alfaomega Grupo Editor.

Deitel, H.M. y Deitel, P. J. (1995). Cómo programar en C/C++ (2da. ed.). México:

Prentice Hall Hispanoamericana, S.A.

DRAE (2014). Diccionario de la Real Academia Española. Recuperado de:

http://www.rae.es/ (concepto de pixel o píxel y decodificar o descodificar en la

sección 1: Preliminares, conceptos básicos; concepto de programar en la sección

3: Lenguajes de programación).

Hooshyar, D., Alrashdan, M. y Mikhak, Masih. (enero-marzo, 2013). Flowchart-based

Programming Environments Aimed at Novices. International Journal of

Innovative Ideas, 13(1), 52-62.

Instituto Nacional Estadounidense de Estándares (2015, abril 15). En Wikipedia.

Recuperado de: http://es.wikipedia.org/wiki/Instituto_Nacional_Estadounidense_de

_Est%C3%A1ndares

Marzal, A. y Gracia, I. (2006). Introducción a la programación con Python, Edición

Internet. Departamento de Lenguajes y Sistemas Informáticos, Universitat

Jaume I.

Novara, Pablo. (2010). Fundamentos de programación, Asignatura correspondiente al

plan de estudios de la carrera de Ingeniería Informática (Anexo 1). Universidad

94

Nacional del Litoral, Facultad de Ingeniería y Ciencias Hídricas, Departamento

de Informática. Recuperado de: http://zinjai.sourceforge.net/Anexo1.pdf

Rodríguez Corral, J. M. y Galindo Gómez, J. (2009). Aprendiendo C (3ra. ed.). España:

Servicio de Publicaciones de la Universidad de Cádiz.