programación i - fcfm.buap.mx · coordina y lleva el seguimiento de la ejecución de todos los...
TRANSCRIPT
PROGRAMACIÓN I MÓNICA MACÍAS
PARA LA LICENCIATURA EN MATEMÁTICAS Y LA LICENCIATURA EN MATEMÁTICAS APLICADAS
ACTUALIZACIÓN: ENERO 2020
BENEMÉRITA UNIVERSIDAD AUTÓNOMA DE PUEBLA FACULTAD DE CIENCIAS FÍSICO MATEMÁTICAS
1
CONTENIDO
1 PRELIMINARES, CONCEPTOS BÁSICOS 6
2 CONCEPTO DE ALGORITMO 11
3 LENGUAJES DE PROGRAMACIÓN 12
4 DATOS Y EXPRESIONES EN UN PROGRAMA 14
5 PROCESO DE RESOLUCIÓN DE PROBLEMAS UTILIZANDO UN LENGUAJE
DE PROGRAMACIÓN 17
5.1 Fase de solución ............................................................................................... 17
5.2 Fase de implementación del diseño ................................................................. 22
6 LENGUAJE C 24
7 INSTRUCCIONES DE ENTRADA Y SALIDA 42
8 ESTRUCTURAS SECUENCIALES Y SELECTIVAS 48
8.1 Estructura secuencial ....................................................................................... 48 8.2 Estructuras selectivas ....................................................................................... 48
8.2.1 Alternativa simple (si-entonces) ............................................................... 49 8.2.2 Alternativa doble (si entonces sino) ......................................................... 50
8.2.3 Alternativa múltiple (selector) .................................................................. 51 8.2.4 Alternativa múltiple (no selectora) ........................................................... 52
8.2.5 Estructuras de decisión anidadas o en cascada ......................................... 54
8.2.6 Operador ternario ...................................................................................... 55
9 ESTRUCTURAS REPETITIVAS 56
9.1 Estructura mientras .......................................................................................... 56
9.2 Estructura hacer mientras ................................................................................. 57 9.3 Estructura desde o para .................................................................................... 58
9.4 Estructuras repetitivas anidadas ....................................................................... 61 9.5 Instrucciones que alteran el flujo normal de un ciclo ...................................... 61
10 NÚMEROS PSEUDOALEATORIOS EN EL LENGUAJE C 62
11 ARREGLOS 65
11.1 Arreglos unidimensionales ........................................................................... 66
2
11.2 Arreglos bidimensionales ............................................................................. 69
12 CARACTERES, CADENAS Y ARREGLOS DE CARACTERES 72
12.1 Caracteres ..................................................................................................... 72 12.2 Cadenas de caracteres................................................................................... 77 12.3 Arreglos de caracteres .................................................................................. 80
13 FUNCIONES 81
13.1 Funciones definidas por el usuario en el lenguaje C .................................... 82 13.2 Prototipo de funciones .................................................................................. 85 13.3 Paso de parámetros por valor y por referencia ............................................. 89
13.4 Reglas básicas de alcance o ámbito (scope) ................................................. 93 13.5 Variables locales y variables globales .......................................................... 94
14 ANEXOS 96
14.1 Prácticas sanas de programación .................................................................. 96
15 REFERENCIAS 99
3
ÍNDICE DE TABLAS
Tabla 1. Algunos múltiplos del byte. .............................................................................. 10
Tabla 2. Ejemplos del tamaño en bytes de algunos tipos de datos. ................................ 10
Tabla 3. Identificadores de variables. ............................................................................. 16
Tabla 4. Simbología en un diagrama de flujo según la ISO y el ANSI. ........................ 20
Tabla 5. Simbología en un diagrama de flujo según DFD, RAPTOR y PSeInt. ............ 21
Tabla 6. Tipos de datos estándar en el lenguaje C.......................................................... 32
Tabla 7. Operadores aritméticos. .................................................................................... 34
Tabla 8. Prioridad de operadores aritméticos. ................................................................ 35
Tabla 9. Operadores relacionales.................................................................................... 36
Tabla 10. Operadores lógicos. ........................................................................................ 36
Tabla 11. Operadores de asignación. .............................................................................. 38
Tabla 12. Prioridad de operadores. ................................................................................. 40
Tabla 13. Valores resultantes según expresiones aplicadas a las variables a, b, c y d. .. 41
Tabla 14. Secuencias de escape para printf. ................................................................... 43
Tabla 15. Especificaciones de formato para printf . ....................................................... 43
Tabla 16. Códigos de formato para scanf. ...................................................................... 45
Tabla 17. Código ASCII de caracteres de control. ......................................................... 73
Tabla 18. Código ASCII de caracteres alfabéticos, numéricos y especiales. ................. 74
4
ÍNDICE DE FIGURAS
Figura 1. Estructura funcional de un ordenador. .............................................................. 7
Figura 2. Representación binaria del abecedario. ........................................................... 10
Figura 3. Intérprete. ........................................................................................................ 14
Figura 4. Fases de la compilación. ................................................................................. 15
Figura 5. Fases para producir código ejecutable a partir de código fuente en C. ........... 27
Figura 6. Estructura secuencial. ...................................................................................... 48
Figura 7. Alternativa simple. .......................................................................................... 49
Figura 8. Alternativa doble. ............................................................................................ 50
Figura 9. Alternativa múltiple (selector). ....................................................................... 51
Figura 10. Alternativa múltiple (no selectora). .............................................................. 53
Figura 11. Ejemplo de estructuras selectivas anidadas 1................................................ 54
Figura 12. Ejemplo de estructuras selectivas andadas 2. ................................................ 54
Figura 13. Ejemplo de estructuras selectivas anidadas 3................................................ 55
Figura 14. Estructura mientras. ...................................................................................... 57
Figura 15. Estructura repetir hacer mientras. ................................................................. 58
Figura 16. Estructura desde o para. ................................................................................ 59
Figura 17. Otra forma de representar a la estructura desde o para. ................................ 59
Figura 18. Representación gráfica de un arreglo unidimensional. ................................. 66
Figura 19. Representación gráfica del vector V con 10 elementos de tipo real. ............ 67
Figura 20. Representación gráfica de un arreglo bidimensional . .................................. 69
Figura 21. Representación gráfica del arreglo bidimensional C . .................................. 72
Figura 22. Ejemplo del llamado de una función en el lenguaje C. ................................. 83
5
Figura 23. Representación gráfica y en memoria de variables y apuntadores. .............. 91
Figura 24. Ejemplo de alcance de una variable en el lenguaje C. .................................. 95
6
1 PRELIMINARES, CONCEPTOS BÁSICOS
Computadora
Una computadora, también llamada ordenador, 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).
Para Chaitin (2015):
El ordenador era (y aún es) un nuevo concepto filosófico y matemático fabuloso.
Las computadoras alteran la forma de hacer matemáticas, modifican el tipo de modelos
matemáticos que confeccionamos del mundo… evocan una filosofía digital, sugieren una
forma nueva de observar el mundo donde todo es discreto y nada es continuo, donde todo es
información digital, ceros y unos.
Se inventaron para esclarecer una cuestión filosófica relacionada con los fundamentos de las
matemáticas.
Esta historia increíble comienza con David Hilbert, un matemático alemán muy conocido que
a principios del siglo XX propuso formalizar por completo todo el razonamiento matemático.
Resultó que no se puede formalizar el razonamiento matemático, así que en cierto modo su
idea fue un tremendo fracaso. Pero en otro sentido, la idea de Hilbert fue un éxito porque el
formalismo ha supuesto una de las grandes aportaciones del siglo XX, no para el razonamiento
o la deducción matemáticos, sino para la programación, para el cálculo, para la computación:
un fragmento olvidado de la historia del intelecto. (pp. 15-16, 207)
Hardware y software
Los 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 son: micrófono, teclado, ratón, joystick (palanca o control de mando que se usa en
consolas o videojuegos para desplazar un personaje u objeto), 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 computadora, ejemplos de ellos son: monitor, impresora, bocinas, plotter (dispositivo
que permite dibujar o representar diagramas y gráficos), fax, sintetizador de voz, etcétera.
7
Organización de la computadora
Si no se toman en cuenta las diferencias en apariencia física, las computadoras pueden
ser divididas en seis unidades lógicas o secciones (ver 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
datos e instrucciones de la computadora a partir de varios dispositivos de entrada y
los pone a 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 dejarla 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.
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 pueda estar
disponible de inmediato para su proceso cuando sea necesario. También
retiene información ya procesada hasta que pueda ser colocada por la unidad
de salida en dispositivos de salida. Existe la memoria de lectura y escritura
8
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
principal 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).
4. 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.
5. 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 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.
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).
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”.
9
El sistema operativo coordina el funcionamiento del hardware iniciando todos los
elementos que lo conforman para que estén preparados para recibir trabajo, entonces,
ordena cuándo y cómo deben trabajar los componentes y qué programas se les asignan;
coordina y lleva el seguimiento de la ejecución de todos los programas en el sistema de
cómputo, así, toma decisiones para evitar que se produzcan conflictos entre ellos y trata de
que el sistema de cómputo sea 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).
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.
10
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.
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)
Tabla 1. Múltiplos del byte.
Adaptado de Berzal (s.f., p. 6)
Unidad Abreviación En sistema
decimal
Capacidad en bytes
Kilobyte 1 kB 103 210 bytes = 1,024
Megabyte 1 MB 106 220 bytes = 1,048,576
Gigabyte 1 GB 109 230 bytes = 1,073,741,824
Terabyte 1 TB 1012 240 bytes = 1,099,511,627,776
Petabyte 1 PB 1015 250 bytes = 1,125,899,906,842,624
Exabyte 1 EB 1018 260 bytes = 11,529,215,046,068,46,976
Zettabyte 1 ZB 1021 270 bytes = 424,303,114,717,026,195,081,1
Yottabyte 1 YB 1024 280 bytes = 671,607,471,926,416,918,529,802,1
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
Aprox. 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 o 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
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).
11
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
Problema
Para López García (2009) un problema se pude definir como:
Una situación en la cual se trata de alcanzar una meta y para lograrlo se deben hallar y
utilizar unos medios y unas estrategias. La mayoría de problemas tienen algunos elementos
en común: un estado inicial; una meta, lo que se pretende lograr; un conjunto de recursos,
lo que está permitido hacer y/o utilizar; y un dominio, el estado actual de conocimientos,
habilidades y energía de quien va a resolverlo. (p. 7)
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ó Battistutti, 2006).
La palabra Algoritmo tiene su origen en el nombre del matemático Persa “Mohamed ibn Musa
al Khwarizmi” (825 d.C.). Su apellido fue traducido al latín como Algorismus y posteriormente
pasó al español como Algoritmo… Sus trabajos de álgebra, aritmética y tablas astronómicas
adelantaron enormemente el pensamiento matemático y fue el primero en utilizar la
expresión al-yabr (de la que procede la palabra álgebra). Su trabajo con los algoritmos
introdujo el método de cálculo utilizando la numeración arábiga y la notación decimal. (López
García, 2009, p. 21)
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.
3 “Fotograma es cada una de las imágenes que se suceden en una película cinematográfica” (DRAE, 2014).
12
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ó Battistutti (2006)
son suficientes los siguientes elementos:
Finitud: el algoritmo tiene que 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 de entrada, se deben obtener siempre los mismos resultados.
Eficiencia: al momento de traducirse a un lenguaje de programación, el programa
no debe tardar mucho tiempo en ejecutarse 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). “En mi opinión, sólo entendemos
algo cuando sabemos programarlo (¡uno mismo, no otra persona!). En caso contrario no lo
entendemos en realidad, sólo creemos entenderlo” (Chaitin, 2015, p. 16).
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.
13
Lenguaje de programación
Para Chaitin (2015):
El papel que imaginó Hilbert para el formalismo dentro de las matemáticas tiene su
manifestación óptima en los lenguajes de programación informática, que, de hecho, son
formalismos que se pueden interpretar de manera mecánica, pero que aunque sean formalismos
válidos para computar y calcular no lo son para razonar, ni para demostrar teoremas y,
decididamente, tampoco sirven para inventar conceptos matemáticos nuevos ni para lograr
nuevos descubrimientos matemáticos. (p. 23)
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 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. La desventaja es que depende 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 tanto 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
14
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
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.
4 DATOS Y EXPRESIONES EN UN PROGRAMA
Todo programa de computadora tiene dos entidades a considerar: los datos y el
programa en sí.
15
Figura 4. Fases de la compilación.
Un dato es la expresión general que describe a los objetos con los cuales opera una
computadora. Existen dos clases: 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 fija4, y es un subconjunto finito de los
números enteros.
o real, llamado de coma flotante o punto flotante5, 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 dos posibles valores: verdadero o falso.
4 Tanto la coma como el punto son signos válidos para separar la parte entera de la parte decimal en un
número. 5 La representación coma flotante o punto flotante está basada en la notación científica (recuerda que esta
última consta de coeficiente, base y exponente) y contiene en representación binaria: signo del número,
mantisa (que es el coeficiente, significado o magnitud del número) y exponente (que indica dónde se coloca el
punto decimal, mejor dicho, binario, en relación al inicio de la mantisa. Un exponente negativo representa
números menores que uno). Se usa en las computadoras para representar números racionales de magnitud
“muy grande” o “muy pequeña”, de forma eficiente y compacta con la que se pueden realizar operaciones
aritméticas (Borgwardterzal, s.f.). En el curso Programación II, se explica a detalle el tema de la
representación binaria de los números en las computadoras.
16
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 o ubicación de memoria en una
computadora en la cual se almacena un valor y se identifica por un nombre, 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 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. En la tabla
3 se ejemplifican identificadores que representan a variables de distinto tipo.
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
17
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ó Battistutti, 2006):
1. Fase de solución
2. Fase de implementación 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
comprender 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). Parar un buen análisis se pueden usar distintas
estrategias, por ejemplo, en ocasiones es necesario leer el problema varias veces para
aclarar lo que se va a resolver, precisar el resultado que se desea lograr, determinar la
o las incógnitas del problema, organizar información, agrupar datos en categorías,
buscar o clarificar las palabras o conceptos desconocidos, detectar las palabras clave,
verificar si se ha resuelto algún problema similar cuya solución pudiera ser de utilidad
para este nuevo problema, desechar los datos superfluos, detectar si falta información
para resolver el problema, preguntarse qué está permitido o prohibido hacer y/o
utilizar, cuáles datos se pueden considerar fijos (constantes), cuáles variables, cuáles
se pueden calcular, qué fórmulas se pueden emplear, etc. (López García, 2009).
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.
Verificación del algoritmo, llamado también prueba de escritorio: Consiste en dar
valores a las variables que se han definido en el algoritmo para seguir su flujo y
comprobar si al final el resultado es el esperado y por tanto 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 dividen 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 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
18
amplían en una descripción más detallada con más pasos específicos. Este proceso se
denomina refinamiento del algoritmo (stepwise). 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, cada módulo se puede elaborar de manera independiente por lo que puede haber
más de un programador involucrado y el tiempo de elaboración puede disminuir; la
depuración se puede hacer en cada módulo y el mantenimiento es más sencillo. 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 final 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.
Métodos para representar algoritmos
Existen tres métodos para representar un algoritmo:
Descripción narrada: consiste en hacer un relato de la solución en lenguaje
natural.
Pseudocódigo: proviene de la composición de 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
19
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 ISO (International Organization for
Standardization, Organización Internacional para la Estandarización) y el ANSI6
(American National Standars Institute, Instituto Nacional Americano de
Estandarización) desde 1985. 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: octubre, 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
Las herramientas mencionadas utilizan su propia simbología para algunos de los
significados que se presentan en la tabla 4 y no respetan el estándar ISO, ANSI,
entonces, 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.
6 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 sean 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).
20
Reglas para la construcción de un diagrama de flujo
Según Cairó Battistutti (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.
Tabla 4. Simbología en un diagrama de flujo según la ISO y el ANSI.
Adaptado de Cairó Battistutti (2006, pp. 5 y 6) y López García (2009, p. 27)
Símbolo Significado Símbolo Significado
Se utiliza para marcar el
inicio y el final del
diagrama de flujo. Del
Inicio sólo puede salir
una línea de flujo y al
Final sólo puede llegar
una línea de flujo.
Representa conexión de
una parte del diagrama
con otra parte del
diagrama entre páginas
diferentes.
Se utiliza para salida y
entrada de datos indistin-
tamente del dispositivo
que se use, aunque existe
un símbolo para indicar
lectura o entrada a través
del teclado, así como
existe un símbolo para
indicar la salida o
escritura a pantalla y
otro para salida a la
impresora.
Se utiliza para representar
la impresión de un
resultado, expresa salida
o escritura a la impresora.
Se utiliza para indicar
entrada de datos a través
del teclado.
Se utiliza para presentar
mensajes o resultados en
pantalla.
Expresa conexión entre
dos partes distintas del
diagrama en una misma
página.
Representa un proceso, en
su interior se colocan
asignaciones, operaciones
aritméticas, etc.
21
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
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 (resultado
lógico de verdadero o
falso) se sigue uno u otro
camino. Se utiliza para expresar
un módulo (subrutina o
procedimiento) de un
problema (subproblema)
que hay que resolver
antes de continuar con el
flujo normal del
diagrama.
Llamada a la subrutina o
procedimiento
determinado
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
22
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
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).
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
23
Ejecutar o correr el programa: llevar 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 o primitivas son las 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, nombres de variables, constantes o funciones declaradas por el
usuario o predefinidas por el lenguaje de programación que se utilice.
3. Caracteres especiales, por ejemplo: coma, apóstrofo, comillas, etc. que se usan para
acciones determinadas.
4. Operadores aritméticos, relacionales o lógicos.
5. Expresiones, están compuestas por valores, funciones, primitivas, constantes y/o
variables, o por una combinación de los anteriores mediante operadores aritméticos,
lógicos o de asignación.
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).
9. Estructuras, se definen como “un esquema con cierta distribución y orden que
permite representar una idea de forma simplificada” (López García, 2009, p. 46).
Existen tres estructuras: secuenciales o lineales, selectivas, y repetitivas. La
primera se compone de instrucciones que deben ejecutarse una tras otra en forma
consecutiva o lineal, la segunda permite evaluar condiciones y tomar decisiones y la
tercera permite repetir varias veces un conjunto de instrucciones.
24
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 (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 el programa,
por ejemplo, si el 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.
6 LENGUAJE C
Historia breve del lenguaje C
El lenguaje C fue desarrollado entre 1969 y 1973, según su creador, el científico
estadounidense Dennis M. Ritchie, el período de mayor creatividad fue en 1972 y por ello,
es común leer que en ese año se creó el lenguaje. Se le dio ese nombre porque se considera
el sucesor del lenguaje de programación conocido como B. En 1973, el lenguaje C se había
vuelto tan potente que la mayor parte del kernel (núcleo) de Unix (recuerda que Unix es un
sistema operativo) se reescribió en C. En 1978 Brian W. Kernighan y Dennis M. Ritchie
publicaron el libro de uso del lenguaje C: The C Programming Language, conocido
también como La biblia de C, la cual, tomaron como referencia los fabricantes de
compiladores C de la década de los ochenta y a raíz de esa publicación al C de esa época se
le conoce como K&R C. 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 y a esta versión se le conoce como
25
ANSI C o C89. Un año después se incluyó en la ISO y el documento que plasma los
cambios se conoce como ANSI/ISO 9899: 1990, por ello, es la versión estandarizada que se
utiliza en todo el mundo, conocida como C90 o ISO C90 (ANSI e ISO se describen en la
nota 5 de la página 17). En 1999 se generó un nuevo estándar de programación de C por el
ISO/IEC, conocido como ISO/IEC 9899 o comúnmente como C99; en 2011 se publicó en
el ISO/IEC 9899:2011, la versión C11 (antes conocido como C1X) y finalmente en el 2018
se publicó en el ISO/IEC 9899:2018, la versión C18 (Deitel y Deitel, 1995; Hernández
Orallo, Henández Orallo & Juan Lizandra, 2002; ISO, 2018; Ritchie, 2003).
Características generales del lenguaje C
Es un lenguaje de propósito general, es decir, puede ser usado para crear
aplicaciones con distintos objetivos, 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 case sensitive (distingue entre mayúsculas y minúsculas), es decir, distingue
entre instrucciones e identificadores escritos en mayúsculas y minúsculas, por
ejemplo, una variable identificada 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 y 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
26
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)
CodeBlocks, (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 correspondiente valor). Tercero, el
compilador 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 que existen están: GCC, DJGPP, Miracle C, LCC Win 32, Intel C++
Compiler (conocido como ICC o ICL), Mingw, Borland C++.
27
Figura 5. Fases para producir código ejecutable a partir de código fuente en C.
28
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. 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
29
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, su objetivo principal es coordinar a
otras funciones mediante llamadas o invocaciones. 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 lo siguiente:
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, se usa 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 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 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.
30
Ejemplo:
/*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;
}
/*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");
}
31
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.
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
datos7 que existen en el lenguaje C.
7 El estándar C99 del lenguaje C ofrece el archivo de cabecera stdbool.h que permite tener acceso al
tipo de dato bool, el cual permite a una variable almacenar dos posibles valores: true o false.
32
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
short int
signed short int
Entero corto con signo, es decir un número entero corto
positivo o negativo.
Si está formado por 2 bytes, entonces un número de este
tipo estaría dentro del intervalo cerrado [-32768, 32767],
aunque algunos compiladores pueden utilizar 4 bytes.
unsigned short int
Un número entero corto positivo dentro del intervalo
cerrado [0, 65535] si está formado por 2 bytes (aunque
algunos compiladores pueden utilizar 4 bytes, haciendo
crecer el rango).
int
signed int
Un número entero con signo (positivo o negativo) dentro
del intervalo cerrado [-2147483648, 2147483647] si
estuviera formado con 4 bytes.
unsigned int Un número entero positivo dentro del intervalo cerrado
[0, 4294967295] si está formado por 4 bytes.
long int
signed long int
Un número entero largo con signo (positivo o negativo)
dentro del intervalo cerrado [-9223372036854775808,
9223372036854775807] si estuviera formado por 8
bytes.
unsigned long int
Un número entero largo positivo dentro del intervalo
cerrado [0, 18446744073709551615] si estuviera
formado por 8 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.701411E-38,
+3.402823E+38] para números positivos, o el valor cero;
todo esto si estuviera 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, 8 bits
para el exponente (quien define el rango).
33
double
Un número real de doble precisión, es decir, con
precisión de 15 o 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, o el valor cero;
todo esto si estuviera 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, 11 bits
para el exponente (quien define el rango).
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, -3.362103E-4932] para los
números negativos y el intervalo errado [3.362103E-
4932, 1.189731E+4932] para números positivos, o el
valor cero; todo esto si estuviera formado con 12 bytes.
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:
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.
34
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.
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
35
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
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.
36
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
de ellos es 0 el resultado es el valor lógico 0.
(z<x) && (y>w)
Si el valor que almacena
la variable z es menor que
el valor que almacena la
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
37
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.
!
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
38
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:
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.
39
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.
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
40
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.
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.
Tabla de prioridad y orden de evaluación
La tabla 12 muestra el orden de prioridad considerando todos los operadores
mencionados hasta ahora (más completo que el de la tabla 7).
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
41
+ - 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 con valores
iniciales que 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=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
9/c+-20/d (9/c) + ((-20)/d) 2
-d%c-b/a*5+5 (((-d)%c)-
((b/a)*5)) +5
5
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
Los 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
42
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
}
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 funciones: printf, scanf, getchar, putchar, puts y gets son algunas de las más
utilizadas para entrada y salida de datos. Cada una de ellas tiene una sintaxis que la
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 datos
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);
43
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.
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.
44
%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.
%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.
45
Función scanf( )
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 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,
46
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.
%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, de tal forma que el usuario después de
teclear el carácter en cuestión debe presionar la tecla enter para concluir (Cairó Battistutti,
2006). Por ejemplo, el siguiente código permitirá al usuario ingresar desde teclado un
caracter y dicho caracter será almacenado en la variable car después de presionar enter.
car = getchar( ); /*getchar captura el carácter que el
usuario haya pulsado en el teclado y lo almacena en la
variable car.*/
También existen las funciones getch( ) y getche( ) pero son exclusivas para sistemas
operativos MS-DOS (es decir sistemas operativos Windows). La primera lee un carácter del
teclado pero ese caracter que teclea el usuario no se ve en pantalla y no necesita presionar
enter después del caracter; la segunda función lee un caracter del teclado que sí se visualiza
en pantalla y al igual que getch no es necesario presionar enter para concluir o continuar.
Estas dos funciones pertenecen al archivo de cabecera conio.h, el cual, por cierto, ya es
obsoleto.
Ejemplo: Printf(“Presiona una tecla para continuar ”);
getch(); /*La ejecución del programa continuará después de
que el usuario pulse una tecla, la cual no ser verá en
pantalla.*/
47
Función putchar( )
Permite imprimir un único caracter en la pantalla (Cairó Battistutti, 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 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.
48
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, asignación de valores o procesamiento
(Deitel y Deitel, 1995).
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.
49
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.
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;
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.
Figura 7. Alternativa simple.
50
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.
Figura 8. Alternativa doble.
51
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ó Battistutti, 2006).
Figura 9. Alternativa múltiple (selector).
Adaptado de Cairó Battistutti (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;
}
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
52
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;
}
…
else
{
sentenciasN;
}
proposición_siguiente;
53
Figura 10. Alternativa múltiple (no selectora).
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.
54
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.
55
Figura 13. Ejemplo 3 de estructuras selectivas anidadas.
8.2.6 Operador ternario
En el lenguaje C existe un operador conocido como ternario pues necesita tres
argumentos obligatorios que no se pueden omitir. 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 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.
56
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ó Battistutti, 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). 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.
57
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.
Figura 14. Estructura mientras.
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.
58
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ó Battistutti, 2006).
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 desean 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ó Battistutti, 2006). Lo anterior se aprecia
en las figuras 16 y 17.
59
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 la 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.
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.
60
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.
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 ya se explicó, 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;
}
61
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 iguales 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 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
(el tercero) se ejecutará la cantidad de veces que sea verdadera la 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, termina 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í. 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( )
A diferencia de break, terminará no sólo con la ejecución del bucle sino con la
ejecución del programa y regresará el control al sistema operativo.
62
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).
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 mencionadas generarán números pseudoaleatorios a partir de un número
inicial llamado semilla y, es con ese valor inicial que generan el primer número, después, a
partir de dicho número generan el segundo, con el segundo, 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, el cual 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 deseado (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].
63
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].
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 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
64
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
Devuelve la fecha y hora actual que tenga establecido el sistema operativo. La
forma de utilizarla junto con la función srand o srandom es:
srandom( time(NULL) ) en el sistema operativo GNU/Linux, o
srand( time(NULL) ) en el sistema operativo Windows.
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 inicial8.
- Función getpid( ) que se puede encontrar en las bibliotecas: sys/types.h o unistd.h
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 utilizarla junto con la función srand o srandom es:
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, por defecto el valor que devuelve la función está en el intervalo [0.0, 1.0), es
decir, no devolverá el 1.0. 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.
Si se quiere un número en el intervalo [m, n), n>m, m y n reales, entonces se tendría:
numero = drand48()*(n-m) + m
Ejemplo para generar un número entre el rango [10.0, 20.0)
numero = drand48()*(10.0) + 10.0
8 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.
65
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.
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
Archivos o ficheros
Conjuntos
Cadenas
Dinámicos
Listas (pilas o colas)
Listas enlazadas
Árboles
Grafos
66
En el curso de Programación I solamente se abarcan arreglos y cadenas, en el curso de
Programación II se explican el resto de estructuras. 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ó Battistutti, 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 í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ó Battistutti, 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ó Battistutti (2006, p. 177).
En el lenguaje C, el arreglo unidimensional se declara de la siguiente manera:
tipo nombre[tamaño]
67
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.
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ó Battistutti (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.
68
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.*/
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
69
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ó Battistutti, 2006). La figura 20 muestra la representación gráfica de un
arreglo bidimensional.
Figura 20. Representación gráfica de un arreglo bidimensional .
Adaptado de Cairó Battistutti (2006, p.214).
En el lenguaje C, la declaración de esta estructura es de la siguiente manera:
tipo nombre[num_renglones][num_columnas]
donde:
tipo es uno de los tipos predefinidos por el lenguaje: float, int, etc.
70
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.
En 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ó Battistutti,
2006):
/*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};
71
/*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.*/
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.
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.
72
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]
Figura 21. Representación gráfica del arreglo bidimensional C con 3x6 elementos de tipo entero.
Adaptado de Cairó Battistutti (2006, p.215).
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
73
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). Independientemente del sistema operativo que se esté utilizando, los
primeros 128 caracteres, es decir el ASCII básico va a utilizar un código y ese código no va
a cambiar para representar al mismo caracter, pero los otros 128 caracteres pueden utilizar
un valor diferente para representar a un carácter, por ejemplo, la letra ñ se considera un
carácter especial por estar conformado por dos partes: la n y el símbolo ~, así, si se utiliza
Windows, la letra ñ se representa con el código 164 si se utilizan variables unsigned char, y
con el código -92 si se utilizan variables signed char; pero en GNU/Linux la ñ se representa
con el código 241 si se utilizan variables unsigned char y el código -15 si se utilizan
variables signed char.
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.
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
74
0000 0110 6 06 ACK ^F Acknowledgement 0000 0111 7 07 BEL ^G Timbre o pitido: “bip”
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, o
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
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
75
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
1110
62 3E > 0110
1110
110 6E n
76
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 0111
1111
127 7F
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 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 (getch o getche para windows).
Cairó Battistutti (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
77
Para realizar lo anterior, en el lenguaje C existen como parte de la biblioteca ctype.h,
las siguientes funciones: isdigit( ), isalpha( ), islower( ), isupper(
), isspace( ), tolower( ) y toupper( ). Todas reciben como argumento
el valor que se quiere verificar o convertir (Cairó Battistutti, 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, gets y
fgets.
78
char nombre[15];
/* 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”);
79
/*Imprime la cadena “Un afectuoso saludo”*/
printf(“%s”, SALUDO);
/*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ó Battistutti, 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ó
Battistutti, 2006). Sin embargo, no se recomienda utilizar
esta función por presentar problemas de seguridad, ya que si
se teclea una cadena con más caracteres de los que se pueden
almacenar, entonces intentará acceder a porciones de memoria
no permitidas provocando posibles errores en el programa o en
el sistema operativo en general. Se recomienda utilizar scanf
o fgets.*/
80
fgets(cadena,máximo_de_caracteres,dispositivo_estándar_de_ent
rada);
Concretamente:
fgets(cadena,50,stdin);
/* La función que reemplaza a gets es fgets, esta función
permite almacenar los caracteres tecleados en el arreglo
cadena (también almacena los espacios en blanco), máximo
almacenará lo que se indique como segundo parámetro, en este
ejemplo sólo 50 caracteres y no más, pero además, se debe
contemplar que almacena el carácter enter y automáticamente
el carácter de fin de cadena. El tercer parámetro le indica
al programa que los caracteres se van a ingresar desde el
teclado, pues pudieran leerse los caracteres desde un archivo
o algún otro medio.*/
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:
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.
strrev, invertir los caracteres de una cadena (disponible sólo para windows).
strupr, para convertir los caracteres de una cadena a mayúsculas.
strlwr, para convertir los caracteres de una cadena a minú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ó Battistutti,
81
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ó Battistutti,
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.
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.
82
El enfoque en estos apuntes será sobre los subprogramas llamados funciones, que son
los únicos que existen en el lenguaje C, de hecho todo en el lenguaje C son funciones.
Función
Matemáticamente hablando una función es una regla que asocia a cada elemento de un
cierto conjunto A (llamado dominio de la función) un único elemento de un conjunto B
(conocido como codominio o contradominio de la función), sin embargo,
computacionalmente hablando, una función es una colección de sentencias o instrucciones
que ejecuta una tarea específica, que puede o no recibir valores, llamados parámetros, y no
puede contener a otra función. Para utilizar funciones en un programa es necesario
declararlas previamente.
Todos los lenguajes de programación tienen funciones incorporadas o internas 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,
para regresar 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 siguientes instrucciones que contenga main hasta
llegar al final del 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.
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 (como se mencionó en la sección 6: Lenguaje C, apartado
83
Estructura 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.
Figura 22. Ejemplo del llamado de una función en el lenguaje C.
Adaptado de Ceballos (1997, p. 251).
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
84
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
#include <stdio.h>
void letrero(void)
{
printf(“\n Esta es una función
muy simple”);
return;
}
#include <stdio.h>
void letrero2(int i)
{
if (i>0) printf(“\n i es
positivo”);
else printf(“\n i es
negativo”);
}
85
int main( )
{
letrero( );
return 0;
}
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).
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
86
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 programado9.
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.
9 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”, maximo(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;
}
87
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.
88
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.
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
89
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).
En el siguiente ejemplo, la función escribir se declara implícitamente para retornar un
valor int, ya que se llama 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.
#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 ;
}
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
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.
90
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 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
91
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.
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
92
de forma automática la posición inicial en memoria del arreglo, de 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.
93
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 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;
94
printf(“\n%d”, a);
}
printf(“\n%d”, ++a);
}
Se ha declarado la misma variable dos veces, pero aunque tengan el mismo nombre son
variables distintas y por 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 (primera llave
cerrada), así, 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) existen permanentemente, 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 ( );
95
printf (“El valor de x (local) es= %d \n”, x);
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.
Adaptado de Joyanes Aguilar (2008, p. 214).
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
96
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
97
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.
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.
98
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.
¿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.
99
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/
Borgwardterzal, M. (s.f.). What Every Programmer Should Know About Floating-Point
Arithmetic or Why don’t my numbers add up?. Recuperado de: https://floating-
point-gui.de/
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.
Chaitin, G. (2015). El número omega: Límites y enigmas de las matemáticas (Trad. D.
Otero-Piñeiro). Barcelona, España: Tusquets.
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).
Hernández Orallo, E., Hernández Orallo, J. & Juan Lizandra, M.C. (2002). Programación
con el estándar ISO y la biblioteca de plantillas (STL). C++ estándar. España:
Thomson Paraninfo.
100
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
ISO. (2018). ISO/IEC 9899:2018. Information technology — Programming languages — C.
Recuperado de: https://www.iso.org/standard/74528.html
Joyanes Aguilar L. (2008). Fundamentos de programación. Algoritmos, estrucutra de datos y
objetos (4a. ed.). España: McGraw-Hill/Interamericana de España.
López García, J. C. (2009). Algoritmos y programación. Guía para docentes (2da. ed.).
Recuperado de: http://www.eduteka.org/GuiaAlgoritmos.php
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 Nacional
del Litoral, Facultad de Ingeniería y Ciencias Hídricas, Departamento de
Informática. Recuperado de: http://zinjai.sourceforge.net/Anexo1.pdf
Ritchie, D. M. (2003). The Development of the C Language*. Recuperado de:
http://www.bell-labs.com/usr/dmr/www/chist.html
Rodríguez Corral, J. M. y Galindo Gómez, J. (2009). Aprendiendo C (3ra. ed.). España:
Servicio de Publicaciones de la Universidad de Cádiz.