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.
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