Programación I - FCFM - Benemérita Universidad Autónoma de

Facultad de Ciencias Físico Matemáticas
Apuntes para la materia:
Programación I
M.I. Mónica Macías Pérez
Actualización: Primavera 2015
ÍNDICE
1
Preliminares, conceptos básicos
3
2
Concepto de algoritmo
7
3
Lenguajes de programación
8
4
Datos y expresiones en un programa
10
5
Proceso de resolución de problemas utilizando un lenguaje de programación 12
5.1 Fase de solución
12
5.2 Fase de implementación del diseño
16
6
Lenguaje C
18
7
Instrucciones de entrada y salida (E/S)
32
8
Estructuras secuenciales y selectivas
8.1 Estructura secuencial
8.2 Estructuras selectivas
8.2.1 Alternativa simple (si – entonces)
8.2.2 Alternativa doble (si entonces sino)
8.2.3 Alternativa múltiple (selector)
8.2.4 Alternativa múltiple
8.2.5 Estructuras de decisión anidadas o en cascada
8.2.6 Operador ternario
37
38
38
39
40
41
43
43
Estructuras repetitivas
Estructura mientras
9.1
Estructura hacer mientras
9.2
Estructura desde o para
9.3
Estructuras repetitivas anidadas
9.4
Instrucciones que alteran el flujo normal de un ciclo
9.5
45
45
47
48
50
50
9
10 Números pseudoaleatorios en el lenguaje C
51
11 Arreglos
11.1
11.2
54
55
58
Arreglos unidimensionales
Arreglos bidimensionales
12 Caracteres, cadenas de caracteres y arreglos de caracteres
Caracteres
12.1
Cadenas de caracteres
12.2
Arreglos de caracteres
12.3
62
66
69
13 Funciones
13.1
13.2
69
71
73
Funciones definidas por el usuario en el lenguaje C
Prototipo de funciones
1
13.3
13.4
13.5
14 Anexos
14.1
15 Referencias
Paso de parámetros por valor y por referencia
Reglas básica de alcance o ámbito (scope)
Variables locales y globales
77
81
82
Prácticas sanas de programación
84
87
2
1. PRELIMINARES, CONCEPTOS BÁSICOS
Computadora
Es un dispositivo electrónico capaz de ejecutar cálculos y tomar decisiones
lógicas a grandes velocidades, dotada de memoria y de métodos de tratamiento de
información, utilizando programas informáticos (Deitel & Deitel, 1995).
Hardware y software
Los varios dispositivos (como el teclado, la pantalla, los discos, la memoria,
etc.), circuitos electrónicos, cables, y otros elementos físicos que conforman la máquina
en sí y un sistema de computación se conocen como el hardware. Los programas de
computación que se ejecutan en una computadora se conocen como el software.
Dispositivos de entrada
Son aquellos que permiten ingresar datos a la máquina para poder ser
procesados. Ejemplos de ellos: micrófono, teclado, ratón, joystick1, lápices ópticos,
dispositivos touch, scanner, cámaras, etc.
Dispositivos de salida
Son aquellos que permiten visualizar los resultados de los datos transformados o
tratados en la computadora. Ejemplos: monitores, impresoras, bocinas, plotters
(dispositivo que permite dibujar o representar diagramas y gráficos), fax, etc.
Organización de la computadora
Si no se toman en cuenta las diferencias en apariencia física, todas las
computadoras pueden ser divididas en seis unidades lógicas o secciones como se pude
observar en la figura 1 (Deitel & Deitel, 1995):
1. Unidad de entrada. Esta es la sección “de recepción” de la computadora.
Obtiene información (datos e instrucciones de computadora) a partir de varios
dispositivos de entrada y pone esta información a la disposición de las otras
unidades, de tal forma que la información pueda ser procesada. La mayor parte
de la información se introduce en las computadoras a través de teclados tipo
máquina de escribir, ratones, lectores de código de barras, etc. que son
propiamente los dispositivos de entrada.
2. Unidad de salida. Toma la información que ha sido procesada por la
computadora y la coloca en varios dispositivos de salida para dejar la
información disponible para su uso fuera de la computadora. La mayor parte de
la información sale de las computadoras mediante despliegues en pantallas o
mediante impresión en papel. Ejemplos de dispositivos de salida son: monitor,
impresora, plotters, sintetizador de voz, etc.
1
El joystick es una palanca o control de mando que se usa especialmente en consolas o programas
informáticos de videojuegos, el cual permite desplazar rápida y manualmente el cursor, un personaje, etc.
3
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.
a. La memoria principal, primaria o central, también llamada de acceso
directo o interna trabaja a mayor velocidad que la memoria auxiliar, por
lo que se le conoce como de acceso rápido. Retiene información que ha
sido introducida a través de la unidad de entrada, de tal forma que esta
información pueda estar disponible de inmediato para su proceso cuando
sea necesario. También retiene información ya procesada hasta que dicha
información pueda ser colocada por la unidad de salida en dispositivos
de salida. Existe la memoria de lectura y escritura que es volátil,
conocida como RAM y la memoria de sólo lectura que es permanente,
conocida como ROM.
Para que un programa se ejecute, debe estar almacenado o cargado en la
memoria principal.
La memoria cuenta con una dirección o localidad y un valor o
contenido que se guarda en dichas localidades.
b. La memoria secundaria, auxiliar, externa o masiva es más lenta que la
anterior pero de mayor capacidad. Los datos y programas se suelen
almacenar aquí, para que cuando se ejecute varias veces un programa o
se utilicen repetidamente unos datos, no sea necesario introducirlos de
nuevo. Los programas o datos que no se estén utilizando de forma activa
por otras unidades, están por lo regular colocados en dispositivos de
4
almacenamiento secundario (como discos) en tanto se necesiten otra vez,
es posible que sean horas, días, meses o inclusive años después.
4. Unidad de procesamiento central (CPU). Esta es la sección “administrativa”
de la computadora, responsable (coordinadora) de la supervisión de la operación
de las demás secciones. El CPU le indica a la unidad de entrada cuándo debe
leerse la información y colocarse en la unidad de memoria, señala a la ALU
cuándo deberá utilizar información de la unidad de memoria en cálculos, y le
indica a la unidad de salida cuándo enviar información de la unidad de memoria
a ciertos dispositivos de salida.
5. Unidad aritmética y lógica (ALU). Esta es la sección de “fabricación” de la
computadora. Es responsable de la ejecución de cálculos como: suma, resta,
multiplicación y división. Contiene los mecanismos de decisión que permiten
que la computadora, por ejemplo, compare dos elementos existentes de la unidad
de memoria para determinar si son o no iguales.
6. Unidad de control. Detecta señales de estado procedentes de las distintas partes
del ordenador y genera señales de control dirigidas a todas las unidades para,
precisamente, controlar el funcionamiento de la máquina.
Capta de la memoria principal las instrucciones del programa que ejecuta el
ordenador, las decodifica2 (el concepto de codificar se explica más adelante en
este apartado) 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
El propósito de un sistema operativo es facilitar la utilización de los recursos de
la computadora: software y hardware). Permite ejecutar programas, realizar operaciones
de entrada y salida de los programas, detección y notificación de errores, manipulación
de archivos de todo tipo entre otras cosas. Ejemplos de sistemas operativos: MS-DOS,
OS/2, UNIX, SOLARIS, IRIX, GNU/Linux, Windows (3.x, 95, 98, NT, 2000, XP,
Vista, 7, 8, 10, Phone), MAC-OS, Android, etc.
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 & Gracia, 2006).
2
Decodificar o descodificar es aplicar inversamente las reglas de su código a un mensaje codificado para
obtener la forma primitiva de este (DRAE, 2014).
5
Código binario o código máquina
La unidad básica de información en las computadoras es el bit (binary digit,
dígito binario) cuyos valores establecen una de dos posibilidades mutuamente
excluyentes. Los dígitos binarios 0 y 1 se usan para representar los dos estados posibles
de un bit particular.
Cualquier dato que se introduce en la computadora o que sea manipulado por
ella se codifica en su interior por una sucesión de ceros y unos (que físicamente se
presenta por corrientes eléctricas, campos magnéticos, etc.). Entonces puede asignarse
cualquier significado a un patrón de bits particular, en tanto esto se haga en forma
consistente, lo que le da el significado es la interpretación de un patrón de bits; así por
ejemplo, a un caracter le corresponde, según el código utilizado para representarlo, un
byte, que es el conjunto de 8 bits, es decir, un byte es el número de bits necesario para
almacenar un caracter. En la figura 2 se muestran ejemplos de representación en código
binario de algunos caracteres del teclado, concretamente, el abecedario.
A
B
C
D
E
F
01000001
01000010
01000011
01000100
01000101
01000110
G
H
I
J
K
L
01000111
01001000
01001001
01001010
01001011
01001100
M
N
Ñ
O
P
01001101
01001110
10100101
01001111
01010000
Q
R
S
T
U
01010001
01010010
01010011
01010100
01010101
V
W
X
Y
Z
01010110
01010111
01011000
01011001
01011010
Figura 2. Representación binaria del abecedario. (Berzal, s.f.)
Para medir la capacidad de almacenamiento de la computadora se utilizan
múltiplos del byte, como se observa en la tabla 1, y en la tabla 2 se presentan algunos
ejemplos del tamaño en bytes de ciertos tipos de datos.
Tabla 1. Algunos múltiplos del byte.
Unidad
Kilobyte
Megabyte
Gigabyte
Terabyte
Petabyte
Exabyte
Abreviación
1 Kb
1 Mb
1 Gb
1 Tb
1 Pb
1 Eb
Capacidad en bytes
210 bytes
220 bytes
230 bytes
240 bytes
250 bytes
260 bytes
1,024 bytes
1,048,576 bytes
1,073,741,824 bytes
1,099,511,627,776 bytes
1,125,899,906,842,624 bytes
11,529,215,046,068,46,976 bytes
Tabla 2. Ejemplos del tamaño en bytes de ciertos tipos de datos. (Berzal, s.f.)
Datos
Texto
Tamaño
1 novela de 200 páginas, 50 líneas por
800,000 bytes
página y 80 caracteres por línea
Aproximadamente:
780 Kb
6
Imagen en blanco y 1024x768 pixeles3, 1 bpp (bit
negro
píxel)
Imagen en color
1024x768 pixeles, 24 bpp (bit
píxel)
Sonido de baja calidad 3 minutos, 11000 muestras
segundo, 8 bits por muestra
Sonido de alta calidad 3 minutos, 44100 muestras
segundo, 12 bits por muestra,
canales (estéreo)
Video (Calidad DVD) 90 minutos, 25 fotogramas4
segundo,
750x576
pixeles
resolución, 24 bpp (bit por pixel)
por
por
por
por
dos
por
de
98,304 bytes
Menos de 100 Kb
2,359,296 bytes
Unos 2300 Kb
1,980,000 Kb
Casi 2 Mb
23,814,000 bytes
Casi 23 Mb
167,961,600,000
bytes
Menos de 160 Gb
En la entrada y salida de la computadora, los cambios de código se realizan de
forma automática para que las personas no tengan que introducir ni interpretar la
información codificada.
2. CONCEPTO DE ALGORITMO
Algoritmo
Es una secuencia ordenada, finita e inequívoca de pasos a seguir para resolver un
determinado problema, en otras palabras, es la descripción exacta y sin ambigüedades
de la secuencia de pasos elementales a aplicar para, a partir de los datos del problema
encontrar la solución buscada (Cairó, 2006).
Características de un algoritmo
Para que un algoritmo sea completo deberá contemplar todas las alternativas
lógicas posibles que las distintas combinaciones de valores de los datos puedan
presentar.
Cualquier problema puede tener diferentes formas de solución, es decir, de
construir el algoritmo, cada uno de ellos con sus ventajas e inconvenientes, por lo que,
se busca elegir el que cumpla una serie de características a la hora de programar (Cairó,
2006):

Finitud: el algoritmo debe terminar en algún momento, se dice que no debe
ciclarse, es decir, debe tener un número finito de pasos, independientemente de
su complejidad.
3
Un píxel, del inglés pixel es la superficie homogénea más pequeña de las que se componen una imagen,
que se define por su brillo y color (DRAE, 2014).
4
Fotograma es cada una de las imágenes que se suceden en una película cinematográfica. (DRAE, 2014).
7

Precisión: los pasos a seguir se deben indicar clara y ordenadamente.

Determinismo: si se sigue el algoritmo varias veces proporcionándole los
mismos datos, se deben obtener siempre los mismos resultados.

Eficiencia: al momento de traducirse a un lenguaje de programación, no debe
tardar mucho tiempo en ejecutarse el programa ni usar mucho espacio de la
memoria de la computadora.
3. LENGUAJES DE PROGRAMACIÓN
Programar
En el nivel más simple consiste en ingresar en la computadora una secuencia de
órdenes para lograr un cierto objetivo. Es elaborar programas para la resolución de
problemas empleando una computadora (DRAE, 2014).
Programa
Es la expresión de un algoritmo, que consiste en un conjunto de instrucciones
que la computadora puede entender y ejecutar. Es una serie de operaciones que realiza
la computadora para llegar a un resultado con un grupo de datos específicos. Ejemplos:

Programa de aplicación: desarrollado para llevar a cabo tareas específicas ya
sea de tipo administrativo, científico o de entretenimiento.

Programa de sistema: es más general que uno de aplicación, e independiente de
cualquier área específica de aplicación. Ejemplo: el sistema operativo.
Lenguaje de programación
Un lenguaje de programación es un conjunto de símbolos, junto con un conjunto
de reglas para combinar y utilizar dichos símbolos para expresar programas. Los
lenguajes de programación se componen de un léxico (conjunto de símbolos permitidos
o vocabulario), una sintaxis (reglas que indican cómo realizar las construcciones
correctas del lenguaje) y una semántica (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 máquina las operaciones fundamentales para
su funcionamiento. Consiste en la combinación de ceros y unos para formar las
8
órdenes entendibles por el hardware de la máquina. Es mucho más rápido que
los lenguajes de alto nivel. La desventaja es que son bastantes difíciles de
manejar y usar, además de tener enormes cantidades de instrucciones donde
encontrar un fallo es casi imposible y depende de la máquina que en ese
momento se esté manejando.
 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 tiene menos
instrucciones, los programas creados ocupan menos memoria. Las desventajas
son que dependen totalmente de la máquina en la que se está usando, lo que
impide la transportabilidad de los programas (posibilidad de ejecutar un mismo
programa en diferentes máquinas o entornos sin hacer modificaciones
importantes).
 Lenguajes de alto nivel son aquellos cuyo lenguaje es más cercano al que
utiliza una persona. Estos lenguajes permiten al programador olvidarse por
completo del funcionamiento interno de la computadora para la que están
diseñando el programa. Tan sólo necesitan de otros programas llamados
compiladores o intérpretes que entiendan las instrucciones como las
características de la máquina.
Ejemplos de lenguajes de alto nivel son: Fortran, Pascal, COBOL, SQL, C,
C++, SmallTalk, Java, LISP, PROLOG, ADA, MODULA-2, Basic, G, AutoLisp,
Python, etc. Estos a su vez se suelen clasificar de acuerdo a los tipos de
problemas que permiten resolver y el estilo de programación que fomentan.
Traductores de lenguaje
Cuando se escribe un programa en un lenguaje de alto nivel, el conjunto de
instrucciones y todo lo que lo conforma se le llama archivo fuente, código fuente o
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).
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.
9
La compilación y sus fases
Programa fuente
Intérprete
Traducción y ejecución línea a línea
Figura 3. Intérprete
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), ver figura 4. En el apartado 6: Lenguaje C se detallarán las fases
que se siguen al momento de compilar para obtener de un programa fuente un programa
ejecutable.
Programa fuente
Compilador (traductor)
Programa objeto
Montador
Programa ejecutable en lenguaje máquina
Figura 4. Fases de compilación
4. DATOS Y EXPRESIONES EN UN PROGRAMA
Todo programa de computadora tiene dos entidades a considerar: los datos y el
programa en sí.
Un dato es la expresión general que describe a los objetos con los cuales opera
una computadora. Existen dos clases de datos: simples (sin estructura) y estructurados
(compuestos). La principal característica de los datos simples es que ocupan sólo una
casilla de memoria y los datos estructurados se caracterizan por el hecho de que con un
10
nombre se hace referencia a un grupo de casillas de memoria, es decir, un dato
estructurado tiene varios componentes.
Los distintos tipos de datos se representan en diferentes formas en la
computadora, a nivel de máquina, un dato es un conjunto o secuencia de bits (dígitos 0
y 1) pero los lenguajes de alto nivel usan los siguientes datos simples:

Numéricos:
o entero, llamado de punto o coma fija, y es un subconjunto finito de los
números enteros.
o real, llamado de coma flotante o punto flotante5, que consiste en un
subconjunto de los números reales.

Caracter: es un conjunto finito y ordenado de símbolos que la computadora
reconoce. Con éstos en sucesión se forman cadenas de caracteres.

Lógico o booleano, indican los dos posibles valores: verdadero o falso.
Por otro lado, los datos estructurados más conocidos son: arreglos, cadenas de
caracteres y registros (en apartados posteriores se revisarán los arreglos y las cadenas de
caracteres).
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 del programa. Son localidades de memoria en una computadora
que almacenan un valor. Dicho valor puede ser entero, real, lógico o caracter,
dependiendo del lenguaje que se utilice. Es de un cierto tipo y puede tomar únicamente
valores de ese tipo, es decir si es entera, no puede contener caracteres o reales, si es real
no puede tomar valores lógicos, etc. Si se intenta dar valor a una variable que no es de
su tipo, se genera un error de tipo.
Los nombres que se les asignan a los datos simples, por ejemplo: una variable o
constante, o a los datos estructurados, por ejemplo: un arreglo, se conocen como
identificadores. Un identificador se forma por medio de letras, dígitos y el caracter
guion bajo, comenzando siempre con un carácter ya sea en minúsculas o mayúsculas.
Ejemplos de identificadores que representan a variables de cierto tipo se muestran en la
tabla 3.
5
La representación coma flotante o punto flotante es una forma de notación científica usada por la
computadoras para representar números racionales extremadamente grandes y pequeños de una manera
eficiente y compacta con la que se puede realizar operaciones aritméticas.
11
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)
altura
Valor
2
Tipo
Entero
base
5
Entero
Radio
2.5
Real
simbolo_1
‘a’
Caracter
Simbolo_2
‘#’
Caracter
5. PROCESO DE RESOLUCIÓN DE PROBLEMAS UTILIZANDO
UN LENGUAJE DE PROGRAMACIÓN
Pueden ser identificadas dos etapas en el proceso de resolución de problemas
utilizando un lenguaje de programación (Cairó, 2006):
1. Fase de solución
2. Fase de implementación del diseño en un lenguaje de programación
5.1 Fase de solución
 Análisis del problema: en esta etapa se debe definir claramente el problema y
entender lo que se pide, se deben detectar los datos o elementos
que se tienen para usar, llamados datos de entrada, e identificar
los datos que se van a dar como salida o resultados.
 Diseño o construcción de la solución: En este proceso se diseña o plantea el
algoritmo a utilizar, para esto se tienen diferentes herramientas:
descripciones narradas, diagramas o pseudocódigo.
 Verificación del algoritmo (llamado también pruebas de escritorio) para
comprobar que el algoritmo es correcto para cualquier caso
posible. Se hace a mano siguiendo las instrucciones que se
plantearon en el algoritmo o bien usando algún software especial.
12
Diseño de un algoritmo
Los problemas complejos se pueden resolver eficazmente con la computadora
cuando se rompen en subproblemas que sean más fáciles de solucionar que el original.
Este método se suele denominar: divide y vencerás.
Descomponer el problema original en subproblemas más simples y, a
continuación, dividir esos subproblemas en otros más simples, que pueden ser
implementados para su solución en la computadora, se denomina diseño descendente
(top-down design). Normalmente, los pasos diseñados en el primer esbozo del algoritmo
son incompletos e indicarán sólo unos pocos pasos. Tras esta primera descripción, éstos
se amplían en una descripción más detallada con más pasos específicos. Este proceso se
denomina refinamiento del algoritmo. Para problemas más complejos se necesitan con
frecuencia diferentes niveles de refinamiento antes de que se pueda obtener un
algoritmo claro, preciso y completo (Bermúdez, et. al., 2003).
Las ventajas más importantes del diseño descendente son: el problema es más
comprensible al dividirse en partes más simples denominados módulos, las
modificaciones en dichos módulos son más sencillas de realizar y la comprobación del
problema es asequible. La programación de cada módulo se hace mediante:
programación estructurada.
La programación estructurada es un conjunto de técnicas para desarrollar
algoritmos fáciles de escribir, verificar, leer y modificar; aquí algunas de sus
características:

Tiene un solo punto de entrada y uno de salida.

Toda acción del algoritmo es accesible, es decir, existe al menos un camino que
va desde el inicio hasta el fin del algoritmo, se puede seguir y pasa a través de
dicha acción.

No posee lazos o bucles (ciclos) infinitos.
Ahora bien, un programa puede ser escrito utilizando únicamente tres tipos de
estructuras: secuencial (instrucciones que se dan paso a paso), selectiva (para tomar
decisiones) y repetitiva (para procesar un conjunto de datos); todas ellas se verán más
adelante.
Métodos para representar algoritmos
Existen tres métodos para representar un algoritmo: descripción narrada, diagramas
de flujo y pseudocódigo; a continuación se detalla cada uno de ellos.

Descripción narrada: consiste en hacer un relato de la solución en lenguaje
natural.
13

Pseudocódigo, utiliza palabras en lenguaje natural (generalmente inglés, aunque
también existe en castellano como el que se utiliza la herramienta de software
PSeInt, abreviatura de Pseudo Intérprete) reservados para representar una acción
en específico, similares a sus homónimas en los lenguajes de programación.

Diagrama de flujo, flowchart u ordinogramas: es la representación gráfica de
un algoritmo, es decir, representa la solución del problema utilizando símbolos
que tienen un significado único y específico. En la tabla 4 se presentan los
símbolos que satisfacen las recomendaciones de la International Organization
for Standardization (ISO) y el American National Standars Institute (ANSI)
(Cairó, 2006).
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.
Ejemplos de estas herramientas son (Arellano, Nieva, Solar y Arista, 2012):
-
DFD (última actualización en octubre de 2008),
-
RAPTOR (acrónimo del inglés Rapid Algorithmic Prototyping Tool for
Ordered Reasoning; actualizado constantemente),
-
PSeInt (abreviatura de Pseudo Intérprete; facilita escribir algoritmos en
pseudocódigo generando el diagrama de flujo a partir de este; también se
actualiza constantemente),
-
Progranimate, etc.
Cada una de estas herramientas utiliza su propia simbología para algunos de los
significados que se presentan en la tabla 4, no respetando el estándar ISO y
ANSI, así, en la tabla 5 se muestran los símbolos equivalentes de las
herramientas DFD, RAPTOR y PSeInt
Tabla 4. Simbología en un diagrama de flujo según la ISO y el ANSI.
Símbolo
Significado
Símbolo
Significado
Se utiliza para marcar el
inicio y el fin del
diagrama de flujo.
Representa conexión de
una parte del diagrama
con otra parte del
diagrama entre páginas
diferentes.
Se utiliza para introducir
datos de entrada.
Expresa lectura.
Se utiliza para representar
la impresión de un
resultado, expresa salida
o escritura.
Expresa conexión de una
parte del diagrama con
otra parte de dicho
diagrama en una misma
página.
Representa un proceso, en
su interior se colocan
asignaciones, operaciones
aritméticas, etc.
14
selector
acciones
Flechas de dirección o
flujo del diagrama.
Flechas de dirección o
flujo del diagrama.
Se utiliza para
representar una decisión
múltiple, en su interior
se almacena un selector
y dependiendo de su
valor se sigue por una de
las ramas o caminos.
Representa una decisión,
en su interior se coloca
una condición y
dependiendo de la
evaluación se sigue uno u
otro camino.
Se utiliza para expresar
un módulo de un
problema (subproblema)
que hay que resolver
antes de continuar con el
flujo normal del
diagrama.
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.
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.
Se utiliza para
representar al ciclo while
o mientras, es decir, un
conjunto de acciones se
repiten mientras la
condición es verdadera.
acciones
acciones
Tabla 5. Simbología en un diagrama de flujo según DFD, RAPTOR y PSeInt.
Significado
Simbología DFD
Simbología RAPTOR
Simbología PSeInt
Entrada o
lectura
Salida o
escritura
Ciclo while o
mientras
MQ
acciones
Fin (MQ)
15
Para
Ciclo for o
para, no
existe en
RAPTOR
acciones
Fin (para)
Módulo de un
problema
(subproblema)
Decisión
múltiple, no
disponible ni
en DFD ni en
RAPTOR
Reglas para la construcción de un diagrama de flujo
Las siguientes reglas fueron tomadas de (Cairó, 2006).
1. Todo diagrama de flujo debe tener un inicio y un fin, construyéndose de arriba
hacia abajo (top-down) y de izquierda a derecha (right-toleft).
2. Las líneas utilizadas para indicar la dirección del flujo del diagrama deben ser
rectas: verticales u horizontales y estar conectadas a otros símbolos: lectura,
decisión, salida, proceso, etc., pero no pueden quedar sin apuntar a un símbolo.
3. La notación utilizada en el diagrama de flujo debe ser independiente del lenguaje
de programación a utilizarse. La solución presentada se puede escribir
posteriormente en diferentes lenguajes de programación.
4. Es conveniente colocar comentarios que ayuden a entender lo que se está
realizando.
5.2 Fase de implementación del diseño
16

Codificar o escribir el programa, traducir el algoritmo a un específico lenguaje
de programación (el concepto de codificar se revisó en el apartado 1:
Preliminares, conceptos básicos).

Ejecutar o correr el programa: que lleve a cabo cada una de las instrucciones
que lo conforman.

Verificar que funcione correctamente el programa.
Instrucciones generales de un programa
Tipo 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 de entrada
4. Instrucciones de escritura conocidas también conocidas como de salida
5. Instrucciones de bifurcación o decisión
Elementos básicos de un programa:
1. Palabras reservadas son palabras que tienen un significado especial para un
compilador por lo que no pueden utilizarse para cualquier otro propósito distinto
para el que fueron identificadas.
2. Identificadores (por ejemplo: nombres de variables, constantes o funciones
declaradas por el usuario)
3. Caracteres especiales (por ejemplo: coma, apóstrofo, comillas, etc.)
4. Expresiones
5. Estructuras: secuenciales, selectivas, repetitivas
6. Contadores: variables cuyo valor se incrementa o decrementa en una cantidad
constante en cada iteración si se utilizan ciclos.
7. Acumuladores: variables cuya misión es almacenar cantidades resultantes de
sumas sucesivas. Realiza la misma función que un contador con la diferencia de
que el incremento o decremento de cada suma es variable en lugar de constante.
17
8. Interruptores o conmutadores (switch) a veces se les denomina indicadores o
banderas, son variables que puede tomar diversos valores a lo largo de la
ejecución del programa y que permite comunicar información de una parte a otra
del mismo. Los interruptores pueden tomar dos valores diferentes: 1 y 0
(encendido/apagado o abierto/cerrado).
Tipos de errores presentados en la fase de implementación

Errores de sintaxis. Esta clase de errores se presenta al momento de compilar el
programa, generalmente por cometer errores al escribir una instrucción o los
signos de puntuación correctos que violan la sintaxis con la que se escriben
programas en el lenguaje que se esté usando. De acuerdo al compilador que se
utilice, éste marca la línea donde se ha cometido el error y la clase de error para
poder corregirlo.

Errores lógicos. Estos se presentan al momento de ejecutar el programa, ya que
no obtenemos los resultados esperados porque el algoritmo que se planteó no fue
el correcto o porque la traducción o implementación al lenguaje de
programación utilizado no fue el adecuado, malinterpretando el algoritmo. Esta
clase de errores son más difíciles de corregir.

Errores en tiempo de ejecución. Estos errores se presentan al momento de
ejecutar o correr el programa, por ejemplo, si nuestro programa hace referencia
al lector de una memoria externa y dicho lector no está listo con una memoria,
entonces marcará un error; otro ejemplo sería si se espera que el usuario ingrese
un dato de tipo entero y lo que ingresa es un dato de tipo caracter o decimal,
entre otros ejemplos.

Warnings. No propiamente son errores, sino advertencias de que algo debería
cambiarse porque es obsoleto o puede causar conflictos.
6. LENGUAJE C
Historia breve del lenguaje C
El lenguaje C fue creado por el científico estadounidense Dennis Ritchie en
1972 como derivado de un lenguaje de programación conocido como B. Hacia finales
de los 70, el lenguaje C evolucionó a lo que hoy se le conoce como C tradicional y su
rápida expansión sobre varios tipos de computadoras, denominadas plataformas de
hardware, provocó muchas variantes que, aunque similares no eran compatibles. Esto
generó problemas para los desarrolladores de programas que necesitaban escribir
códigos que pudieran funcionar en varias plataformas. Por tanto, en 1989 se aprobó un
estándar para el lenguaje C con una definición no ambigua e independiente de la
máquina donde se desarrollara. El documento que plasma esto se conoce como
18
ANSI/ISO6 9899: 1990, por ello, esta versión se conoce como ANSI C, es la versión
estandarizada que se utiliza en todo el mundo (Deitel & Deitel, 1995).
Características generales del lenguaje C

Es un lenguaje de propósito general, es decir, puede ser usado para varios
propósitos como: cálculos matemáticos, crear sistemas operativos, manejar
bases de datos, comunicación entre computadoras, capturar datos, etc.

El código que produce es eficiente y cuenta con características de los lenguajes
de bajo nivel que permiten realizar implementaciones óptimas (rápidas).

Es portátil, es decir, los códigos o programas generados con el lenguaje pueden
ejecutarse en cualquier arquitectura de computadora o cualquier sistema
operativo con variantes mínimas.

Es estructural, en otras palabras, el programa se divide en subprogramas o
segmentos con estructuras simples de secuencia, selección e iteración.

Es sensitivo al (del) contexto, es decir, distingue instrucciones e identificadores
escritos en mayúsculas y minúsculas, por ejemplo, una variable identificada
como A, es diferente a la variable identificada como a, o bien, la instrucción If
no será reconocida por el compilador, más la instrucción if sí lo será.
A continuación se detallan las tres partes principales de las que consta todo
sistema en C:

Un entorno

Biblioteca estándar

El lenguaje en sí
Entorno
Existen diferentes IDEs (acrónimo del inglés: Integrated Development
Enviroment y en español: Entorno Integrado de Desarrollo) que incluyen o integran una
6
ANSI (American National Standards Institute) que corresponde al Instituto Nacional Estadounidense de
Estándares es una organización sin fines de lucro que supervisa el desarrollo de estándares para
productos, servicios, procesos y sistemas en los Estados Unidos. Es miembro de la Organización
Internacional para la Estandarización (ISO). También coordina estándares del país estadounidense con
estándares internacionales, de tal modo que los productos de dicho país puedan usarse en todo el mundo.
Esta organización aprueba estándares que se obtienen como fruto del desarrollo de tentativas de
estándares por parte de otras organizaciones, agencias gubernamentales, compañías y otras entidades.
Estos estándares aseguran que las características y las prestaciones de los productos son consistentes, es
decir, que la gente use dichos productos en los mismos términos y que esta categoría de productos se vea
afectada por las mismas pruebas de validez y calidad (Wikipedia, 2015).
19
interfaz visual con iconos, menús y ventanas, para trabajar con comodidad con un editor
de textos para escribir el código fuente en el lenguaje C, uno o varios compiladores,
enlazadores, depuradores y demás herramientas. Suelen presentar diferentes asistencias
para la escritura de programas, como: sugerencias de autocompletado, coloreado de la
sintaxis del código fuente, ayuda acerca del lenguaje, etc.
Es necesario recalcar que un IDE no es un compilador, este último es parte de
todo el IDE. Ejemplos de IDEs son (Novara, 2010):

DevC++ (disponible sólo para el sistema operativo Windows),

Visual Studio (disponible sólo para el sistema operativo Windows),

Eclipse,

Monodevelop,

ZinjaI, (estos último tres disponibles para los sistemas operativos Windows,
Mac y GNU/Linux)

etc.
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, 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 propiamente traduce el código fuente
para generar el código objeto, éste es un código aún no ejecutable pues generalmente,
estará incompleto. Cuarto y último, para obtener el código binario final, se ejecuta el
enlazador, el cual se encargará de vincular el código objeto con el código de las
funciones a las que se haya hecho referencia en el programa fuente pero cuyo código se
encuentra en otro u otros archivos, por ejemplo, en la biblioteca estándar (este concepto
será revisado en la siguiente sección de este apartado) o bibliotecas creadas por los
propios programadores (Deitel & Deitel, 1995).
Cabe mencionar que, cada IDE puede hacer uso de uno o varios tipos de
compiladores para C, entre los existentes están:

Borland C++

GCC
20

DJGPP

Miracle C

LCC Win 32

Intel C++ Compiler (conocido como ICC o ICL)

Mingw
Errores y warnings
(advertencias)
Compilador
Editor de
textos
Archivo fuente
.c
Preprocesador
Salida estándar
Código objeto
Archivo
binario
ejecutable
Enlazador
Salida estándar
Errores y warnings
(advertencias)
Figura 5. Fases para producir código ejecutable a partir de código fuente en C.
Biblioteca Estándar
La biblioteca estándar de C contiene una amplia colección de funciones que
resuelven problemas particulares y comunes, por ejemplo: cálculos matemáticos,
manipulación de cadenas o caracteres, detección de errores, mandar o recibir datos hacia
o desde los dispositivos de salida y entrada respectivamente, obtención de fecha y hora,
etc. Estas funciones mejoran tanto el rendimiento de los programas como la portabilidad
pero no forman parte en sí del lenguaje C (Deitel & 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 la sección Entorno de este
apartado) 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
21
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 el
apartado 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 & Deitel, 1995).
Tanto en el resto de este apartado como en los apartados posteriores, se
describirán los elementos que conforman al lenguaje C.
Estructura de un programa
Todo programa en C está conformado por funciones (el tema de funciones será
revisado con mayor detalle en el apartado 13: Funciones), ya sean las que se
encuentran en la biblioteca estándar o las que son creadas por el propio programador
para resolver un problema en particular. Además, consta de una función indispensable
llamada main( ) a partir de donde el programa comienza a ejecutarse. Después,
contiene la llave izquierda que abre { para dar inicio al cuerpo de la función principal y
termina con la llave derecha que cierra }. Estas llaves y la porción de programa que
existe entre ellas se les conocen como bloque (Deitel & Deitel, 1995). Este bloque
puede estar constituido por varios elementos que conforman al lenguaje C: operadores
relacionales, de asignación, condicionales, variables, constantes, ciclos, etc., todos ellos
se revisarán a lo largo de este documento.
En general, un programa escrito en el lenguaje C puede contener las siguientes
secciones y/o elementos:

Sección include (se explicó en la sección Biblioteca Estándar de este mismo
apartado).
Cabe mencionar que, cuando se van a agregar funciones que no pertenecen a la
biblioteca estándar, sino que fueron elaboradas por el programador y forman
parte de un archivo de cabecera, entonces, esta sección tiene la siguiente
sintaxis:
#include “Nombre_del_archivo”
22
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 miso directorio donde se encuentra el fichero fuente.

Sección de declaración de constantes (se explicará más adelante en este
apartado) y tipos de datos estructurados.

Sección de declaración de variables globales (se explicará en el apartado 13:
Funciones).

Declaración de prototipo de funciones o forward (se explicará en el apartado
13: Funciones).

Si no existieran funciones prototipo, puede existir sólo un bloque de funciones
creadas por el programador.

Función main o función principal.

Si se declararon prototipo de funciones y la programación de cada función se
encuentra en el mismo archivo fuente, generalmente constituyen la sección final
del programa.
Ejemplo de una estructura:
/*Sección include, agregado de archivos de cabecera de la biblioteca
estándar*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/*Declaración de constantes*/
#define TAM 99
#define FREC 10
/*Prototipo de función*/
void moda(int []);
/*Función principal*/
int main( )
{
int i, respuestas[TAM];
srand(time(NULL));
23
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");
}
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:
24
 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,
void, auto.
return,
union,
register,
extern,
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 (1.7, 1.0923321e5, 33.092332e-3) y
3. entero (que se declara con la palabra reservada int) para manejar
números enteros.
Con el tipo de dato caracter, se puede formar una variable de tipo cadena, que es
una secuencia de caracteres entre comillas. La tabla 6 muestra una lista detallada de los
tipos de datos que existen en el lenguaje C.
Tabla 6. Tipos de datos estándar en el lenguaje C (Bermúdez et. al. , 2003).
Tipo de dato
char
Lo que representa
cualquier caracter del teclado, que se forma con la
combinación de 8 bits
unsigned char
cualquier caracter del teclado, que se forma con la
combinación de 8 bits
signed char
cualquier caracter del teclado, que se forma con la
combinación de 8 bits
int
un número entero dentro del intervalo cerrado [-32768,
32767] formado por la combinación de 16 bits
unsigned int
unsigned short
int
un número entero positivo dentro del intervalo cerrado [0,
65535] formado por la combinación de 16 bits
signed int
short int
un número entero dentro del intervalo cerrado [-32768,
32767] formado por la combinación de 16 bits
25
long int
un número entero dentro del intervalo cerrado [-2147483648,
2147483647] formado por la combinación de 32 bits
signed long int
un número entero dentro del intervalo cerrado [-2147483648,
2147483647] formado por la combinación de 32 bits
unsigned long
int
un número entero positivo dentro del intervalo cerrado [0,
4294967295] formado por la combinación de 32 bits
float
un número real dentro del intervalo cerrado [3.4E-38,
3.4E+38]
double
un número real dentro del intervalo cerrado [1.7E-308,
1.7E+308]
long double
un número real dentro del intervalo cerrado [1.7E-308,
1.7E+308]
Declaración de variables y constantes
Todas las variables que se vayan a utilizar en un programa 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.
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.
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 toma 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.
26
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 (Bermúdez et. al. , 2003).
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
El operando entero o real es multiplicado por -1
(unario)
Prioridad de los operadores
La tabla 8 muestra el orden de prioridad dado a cada operador aritmético y de
asignación.
Tabla 8. Prioridad de operadores aritméticos (Bermúdez et. al., 2003).
Operadores
-(unario)
Asociatividad
Derecha a izquierda
*
/
Izquierda a derecha
+
-
=
%
Izquierda a derecha
Derecha a izquierda
Operadores de relación y lógicos
27
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 (Bermúdez et. al. , 2003).
Operador Operación
<
Primer operando menor que el
segundo
>
Primer operando mayor que el
segundo
<=
Primer operando menor o igual que
el segundo
>=
Primero operando mayor o igual que
el segundo
==
Primer operando igual que el
segundo
!=
Primer operando distinto del segundo
Ejemplos
a < 3
b > w
-7.7 <= 99.335
-1.3 >= (2.0 * x + 3.3)
c == ’w’
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 (Bermúdez et. al. , 2003).
Operador Operación
Ejemplo
AND lógico. Da como resultado el valor
lógico 1 si ambos operandos son distintos de
&&
0. Si uno de ellos es 0 el resultado es el valor (z<x) && (y>w)
lógico 0.
||
OR lógico. El resultado es cero si ambos
operandos son cero. Si uno de los operándoos
(x ==y) || (z!=p)
tiene un valor distinto de 0, el resultado es 1.
!
NOT lógico. El resultado es 0 si el operando
tiene un valor distinto de cero, y 1 en caso !a
contrario.
Operadores de asignación
Además del operador de asignación simple presentado anteriormente, existen los
operadores de asignación que se muestran en la tabla 11.
28
Tabla 11. Operadores de asignación (Bermúdez et. al., 2003).
Operador Operación
Asignación simple
=
+=
Suma más asignación
-=
Resta más asignación
*=
Multiplicación más asignación
/=
División más asignación
%=
Módulo más asignación
++
Incremento
--
Decremento
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
k *= 3+x es equivalente a k = k*(3+x)
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
29
Decremento: --, resta 1 a una variable
Ejemplo:
x = x + 1 es equivalente a ++x
x = x - 1 es equivalente a --x
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
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
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++;
son iguales
--x;
x--;
son iguales
Expresiones como ++(x+1) son un error, ya que sólo se utilizan de forma directa
en variables.
Por lo explicado anteriormente, los operadores de incremento y decremento al
afectar el valor de una variable, se les consideran operadores de asignación como se
listaron en la tabla 11.
Tabla de prioridad y orden de evaluación
La tabla 12 muestra el orden de prioridad más completo que el mostrado en la
tabla 7, considerando todos los operadores mencionados hasta ahora.
30
Tabla 12. Prioridad de operadores (Bermúdez et. al. , 2003).
Operadores
( )
Asociatividad
Izquierda a derecha
- (unario)
*
/
+
-
<
<=
==
++
--
!
%
Derecha a izquierda
Izquierda a derecha
Izquierda a derecha
>
>=
Izquierda a derecha
!=
Izquierda a derecha
&&
Izquierda a derecha
||
Izquierda a derecha
=
+=
-=
*=
%=
Derecha a izquierda
Ejemplos de prioridad de operadores: se declaran tres variables tipo entero y se les
asignan los siguientes valores, la tabla 13 muestra los valores resultantes de cada
variable según la operación realizada con cada una de ellas en forma secuencial:
int a,b,c,d;
a=2; b=-3; c=7; d=-19;
Tabla 13. Valores resultantes según expresiones aplicadas a las variables a, b, c y d (Bermúdez
et. al., 2003).
Expresión
Expresión equivalente
Valor resultante
a/b
b/b/a
c%a
a%b
d/b%a
-a*d
a%-b*c
9/c+-20/d
-d%c-b/a*5+5
0
0
1
2
0
38
14
2
15
7-a%(3+b)
(a / b)
(b/b)/a
(c%a)
(a%b)
(d/b)%a
(-a)*d
(a%(-b))*c
(9/c) + ((-20)/d)
(((-d)%c)((b/a)*5)) +5
7-(a%(3+b))
---a
a=b=c=-33
-(-(-a))
a=(b=(c=-33))
Error. Floating point exception,
puesto que no se puede dividir
entre el número cero
-2
a=-33
b=-33
c=-33
31
Símbolos para escribir comentarios en un programa
Lo programadores insertan comentarios para documentar los programas y
mejorar la legibilidad de estos. Los comentarios no hacen que la computadora lleve a
cabo acción alguna cuando se ejecute el programa, pues es tratado como texto simple,
no como enunciados, instrucciones o palabras reservadas. Los símbolos que nos
permiten hacer esto son:
/* cualquier_texto */
o bien:
// cualquier_texto
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 un renglón.
Ejemplo 1
Ejemplo 2
/* Programa que imprime un mensaje
en pantalla y muestra el uso de
comentarios */
#include <stdio.h>
#include <stdio.h>
int main( )
{
printf (“Ejemplo . . . ”);
int main( )
{
int i;//i sirve como contador
i = 10;
//Resto del programa
}
return 0;
}
7. INSTRUCCIONES DE ENTRADA Y SALIDA
Las operaciones de entrada y salida (abreviadas E/S en español o I/O en inglés)
permitir 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 el apartado 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).
32
Las siguientes funciones son algunas de las más utilizadas para entrada y salida
de datos: printf, scanf, getchar, putchar, puts y gets. Cada una de estas funciones tiene
una sintaxis que las identifica y en este apartado se explican únicamente: printf, scanf,
getchar y putchar. Tanto la función puts como gets se ven con más detalle en el
apartado 12: Caracteres, cadenas de caracteres y arreglos de caracteres.
Función printf( )
Se le llama instrucción de salida porque permite presentar información en un
dispositivo de salida, generalmente la pantalla, dando formato al texto o a los valores.
Su sintaxis es (Ceballos, 1997):
printf (cadena de control, lista de argumentos);
donde:
cadena de control especifica el texto que se mandará a pantalla y el formato que se le
dará tanto al texto como a los valores que se quieran presentar. Es una cadena de
caracteres delimitada por comillas dobles “ ” y formada por: texto, secuencias de escape
y especificaciones de formato.
Una secuencia de escape está formada por el carácter \ seguida de una letra o
combinación de dígitos que se utiliza para acciones como: salto de línea, tabular y
representar caracteres no imprimibles (ver tabla 14).
Una especificación de formato siempre comienza con el caracter % seguido de
una serie de símbolos para dar formato a la presentación de los valores, seguidos de una
o dos letras que especifica 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 (la lista de argumentos se describe a continuación).
lista de argumentos representa el valor o valores a escribir en pantalla, estos 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 (Ceballos, 1997).
Secuencia Nombre
\n
\t
\v
\b
\r
Genera una nueva línea.
Genera una tabulación horizontal.
Genera una tabulación vertical.
Retrocede el cursor una posición borrando el caracter sobre el que
se posiciona.
Genera un retorno de carro, es decir, el cursor se posiciona al inicio
33
\a
\”
\\
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.
Emite un sonido.
Imprime la comilla doble.
Imprime la barra hacia atrás conocida como diagonal invertida.
Tabla 15. Especificaciones de formato para printf (Ceballos, 1997).
Código
%d
%i
Formato
El argumento a mandar a un dispositivo de salida es un número entero
con signo en notación decimal
%u
El argumento a mandar a un dispositivo de salida es un número entero
sin signo, en notación decimal
%hd
%hi
El argumento a mandar a un dispositivo de salida es un número entero
corto, base 10
%hu
El argumento a mandar a un dispositivo de salida es un número entero
corto sin signo, base 10
%lu
El argumento a mandar a un dispositivo de salida es un número entero
largo sin signo, base 10
%ld
El argumento a mandar a un dispositivo de salida es un número entero
largo
%o
El argumento a mandar a un dispositivo de salida es un número entero
sin signo, en notación octal
%x,
%X
El argumento a mandar a un dispositivo de salida es un número entero
sin signo, en notación hexadecimal, minúscula o mayúscula
%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
%c
%s
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
El argumento a mandar a un dispositivo de salida es un solo carácter
El argumento a mandar a un dispositivo de salida es una cadena de
caracteres
34
%%
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 ocupada una variable en la
memoria.
Función scanf( )
Permite leer o ingresar datos desde el teclado, por lo que se le conoce como una
instrucción de entrada. Su sintaxis es (Ceballos, 1997):
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 como el operador de
dirección en C. Al ser combinado con un nombre de variable, le indica a la
computadora la posición en memoria donde se almacenará el valor. La computadora
entonces almacena el valor en esa posición.
Tabla 16. Códigos de formato para scanf (Ceballos, 1997).
Código
Formato
%d
%hu
Lee desde el teclado un número entero base10
Lee desde el teclado un entero decimal, octal, hexadecimal,
opcionalmente signado
Lee desde teclado un número entero corto sin signo, base 10
%u
Lee desde el teclado un número entero sin signo, base 10
%o
Lee desde el teclado un número en octal
%x
Lee desde el teclado un número en hexadecimal
%i
35
%hd
%hi
%lu
%f
%e,
%E
%g,
%G
%ld
%li
%c
Lee desde el teclado un número entero corto, base 10
Lee desde el teclado un número entero largo sin signo, base 10
Lee desde el teclado un número real (como flotante, con
decimales)
Lee desde el teclado un número entero largo, base 10
%s
Lee desde el teclado un solo caracter
Lee desde el teclado una palabra o cadena de caracteres sin
espacios
%lf
Lee desde el teclado un número real (tipo double)
%Lf
Lee desde el teclado un número real largo (tipo long double)
Función getchar( )
Lee un único caracter desde el teclado (Cairó, 2006). Por ejemplo, el siguiente
código permitirá al usuario ingresar desde teclado un caracter y dicho caracter será
almacenado en la variable car.
car = getchar( );
Función putchar( )
Imprime un único caracter en la pantalla (Cairó, 2006). Por ejemplo, el siguiente
código mostrará en la pantalla el caracter que contenga la variable car.
putchar(car);
Buffer o memoria intermedia
Las funciones estándar de E/S tiene la característica fundamental de que la E/S
se realiza a través de un buffer o memoria intermedia. La utilización de un buffer o
memoria intermedia para realizar las operaciones de E/S es una técnica implementada
en software, diseñada para hacer las operaciones de E/S 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)
36
 stderr: dispositivo de error estándar (pantalla)
 sdtaux: dispositivo auxiliar estándar (puerto serie)
 stdprn: dispositivo de impresión estándar (impresora 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.
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
los datos, cuando son tecleados no son leídos directamente del dispositivo, 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, esto
ocurre cada vez que pulsamos la tecla Enter.
Las funciones scanf y getchar interpretan el carácter \n (nueva línea) como un
separador. Al pulsar Enter se transmiten en realidad dos caracteres CR + LF (en el
sistema operativo GNU/ Linux equivalen a un sólo carácter ‘\n’). Esto quiere decir que,
el caracter que actúa como separador es CR, quedando almacenado en el buffer
asociado con stdin, el caracter LF. Éste es un caracter válido para las funciones scanf y
getchar por lo que si se usan nuevamente estas funciones en el mismo programa no
esperan una nueva entrada desde teclado sino toman lo que ha quedado almacenado en
el buffer de entrada, es decir LF, generando problemas de lecturas no deseadas
(Ceballos, 1997).
La solución a lo anterior es limpiar el buffer asociado con stdin antes de una
lectura con las funciones scanf y getchar, por ejemplo, usando la función:
fflush(stdin) disponible en el sistema operativo Windows, o bien,
__fpurge(stdin) disponible en el sistema operativo GNU/Linux.
En al apartado 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 (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 puedes ser instrucciones de entrada, salida o proceso (Deitel & Deitel,
1995).
37
Acción 1
Acción 2
...
Acción n
Figura 6. Estructura secuencial
8. 2 Estructuras selectivas
Se utilizan para tomar decisiones lógicas. Se les llama estructuras de decisión o
alternativas. Pueden ser: simples, dobles o múltiples.
8.2.1 Alternativa simple (si-entonces)
Ejecuta una o varias acciones cuando se cumple una determinada condición, es
decir, se evalúa la condición y si ésta es verdadera se ejecuta la acción o acciones
específicas, pero si la condición es falsa entonces se hace nada como se muestra en la
figura 7.
condición
falso
verdadero
acciones
Figura 7. Alternativa simple
En el lenguaje C, la estructura de alternativa simple se especifica mediante la
instrucción if, cuya sintaxis se muestra a continuación.
if (expresión)
{
proposiciones;
}
proposición_siguiente;
38
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 ninguna acción y continúa el
flujo del programa a la siguiente línea de código proposición_siguiente.
Ahora bien, si se tiene sólo una proposición o instrucción a ejecutarse, no es
necesario colocar llaves para englobar la sentencia, pero si es más de una, sí son
necesarias.
8.2.2 Alternativa doble (si entonces sino)
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.
verdadero
falso
condición
Acción S1
Acción S2
Figura 8. Alternativa doble
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.
39
Si el resultado de evaluar expresión es verdadero entonces se ejecutarán las
proposiciones_1 (que puede 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 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, no es necesario colocar llaves para englobar la sentencias tanto
para el if como para el else, pero si es más de una, sí son necesarias.
8.2.3 Alternativa múltiple (selector)
La estructura de decisión múltiple (selector) evaluará únicamente el valor de una
variable que podría tomar n valores distintos. Según se elija uno de esos valores de la
variable, se realizará una y sólo una de las n acciones, o lo que es igual, el flujo del
algoritmo seguirá un determinado camino entre los n posibles como se muestra en la
figura 9 (Cairó, 2006).
valor n
valor1
variable
valor2
Acción 1
Acción 2
valor3
valor4
Acción n
Acción 4
Acción 3
…
Figura 9. Alternativa múltiple (selector)
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;
40
}
donde expresión es una variable de tipo entera o caracter y sentencias puede ser una o
varias instrucciones de entrada, salida, proceso, condicionales o ciclos.
La sentencia switch evalúa la expresión entre paréntesis y compara su valor con
las constantes de cada case, si coincide la expresión con alguna constante entonces se
ejecutarán todas las sentencias después de los dos puntos de dicho case de coincidencia
hasta la instrucción break, es decir, la instrucción break permite salir de la estructura
switch y no continuar con la ejecución de las sentencias de otro case con el cual no
coindice el valor de la variable, por tanto, se debe utilizar la sentencia break para
concluir le ejecución de las sentencias en cada case.
En el caso de que ninguna de las constantes coincida con la expresión entonces
se ejecutarán la o las sentenciasN que se encuentran después de los dos puntos de la
palabra clave default. La sentencia switch puede incluir cualquier número de cláusulas
case y opcionalmente la cláusula default 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
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, ésta o éstas instrucciones se llevarán a cabo si ninguna de las condiciones 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.
if (expresión1)
{
sentencias1;
}
else if (expresión2)
{
sentencias2;
}
else if (expresión3)
{
sentencias3;
41
}
…
else
{
sentenciasN;
}
proposición_siguiente;
donde la expresión1, expresión2, expresión3, etc. deben ser de tipo numérica, relacional
o lógica. Si la expresión1 es verdadera se ejecutan las sentencias1 (que puede 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
puede 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 fueron falsas para continuar con
proposición_siguiente.
Figura 10. Alternativa múltiple (no selectora).
42
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, else if y else, pero si es más de una, sí son necesarias.
8.2.5 Estructuras de decisión anidadas o en cascada
Las estructuras si interiores a otras se denominan anidadas, encajadas o en
cascada. Una estructura de selección de n alternativas o de decisión múltiple 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.
8.2.6. Operador ternario
En el lenguaje C existe un operador conocido como ternario, es decir, necesita
tres argumentos (de ahí su nombre) obligatorios, que no se pueden omitir. El operador
evalúa una condición de tal manera que si ésta es verdadera entonces se ejecuta una
única instrucción o sentencia, pero si la condición es falsa entonces se ejecuta otra única
instrucción, en otras palabras, con este operador, a diferencia de las otras instrucciones
condicionales descritas, sólo se ejecuta una instrucción pero no un conjunto de
instrucciones.
En el operador ternario se usa el caracter de interrogación que cierra para separar
la condición de la instrucción que se realizará si la condición es verdadera, y se usa el
caracter de dos puntos para separar la instrucción que se realizará si la condición es
verdadera de la instrucción que se realizará si la condición es falsa. Su sintaxis es como
se muestra a continuación.
expresión1? sentenciaV : sentenciaF
Si expresión1 es verdadera, entonces se ejecuta sentenciaV, pero 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.
43
Figura 11. Ejemplo de estructuras selectivas anidadas 1.
Figura 12. Ejemplo de estructuras selectivas anidadas 2.
44
Figura 13. Ejemplo de estructuras selectivas anidadas 3.
9. ESTRUCTURAS REPETITIVAS
En la práctica, durante la solución de problemas, es muy común encontrar
operaciones que se deben ejecutar un número determinado de veces, si bien las
instrucciones son las mismas los datos varían (Cairó, 2006).
Las estructuras que repiten una secuencia de instrucciones un número
determinado de veces se denominan bucles, y se llama iteración al hecho de repetir la
ejecución de una secuencia de acciones. En un bucle o ciclo se deben tener en cuenta
qué es lo que contiene el bucle y cuántas veces se debe repetir.
Para detener la ejecución de los bucles se utiliza una condición de parada, ésta
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
45
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).
falso
condición
verdadero
acciones
Figura 14. Estructura mientras.
En éste 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 se lista a continuación (Deitel & Deitel, 1995):
a. Preguntar al usuario antes de la iteración,
b. Saber de antemano el número de iteraciones exactas que se van a llevar a cabo,
c. Con un valor llamado centinela, que es un valor especial usado para indicar el
final de una lista de datos.
Ahora bien, en el lenguaje C, la estructura repetitiva mientras se especifica mediante
la instrucción while cuya sintaxis se muestra en seguida.
while (expresión)
{
sentencias1;
}
sentencias2;
donde expresión es cualquier expresión numérica, relacional o lógica. Puede ser
numérica puesto que cualquier valor diferente de cero se considera un valor booleno
verdadero, mientras que el cero se considera un valor booleano falso.
La instrucción ejecuta una sentencia simple o compuesta cero o más veces
dependiendo del valor de la expresión, es decir, se ejecutarán las instrucciones mientras
46
la expresión es verdadera, en el momento en que se convierte en falsa se ejecuta la línea
después del fin del while, es decir, sentencias2.
Si se tiene sólo una proposición o instrucción a ejecutarse, no es necesario
colocar llaves para englobar la sentencia del ciclo, pero si es más de una, sí son
necesarias.
9.2 Estructura hacer mientras
Esta estructura se utiliza cuando se debe ejecutar al menos una vez un bucle
antes de comprobar la condición de repetición (ver figura 15).
acciones
condición
verdadero
falso
Figura 15. Estructura repetir hacer mientras.
La estructura hacer mientras se ejecuta mientras una condición determinada es
verdadera, la cual se comprueba al final del bucle pero cuando la condición es falsa
continúa con las instrucciones fuera del ciclo.
La estructura es adecuada cuando no se sabe el número de veces que se debe
repetir un ciclo pero se sabe que se debe ejecutar por lo menos una vez, además es
eficiente para verificar los datos de entrada en un programa (Cairó, 2006).
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{ }.
47
En esta estructura si se tiene sólo una proposición no es necesario colocar llaves
para englobar la sentencia a ejecutarse, pero si es más de una sí son necesarias.
9.3 Estructura desde o para
Esta estructura se utiliza cuando se sabe cuántas veces se desea ejecutar las
acciones de un ciclo. Se utiliza para repetir un conjunto de instrucciones un número
definido de veces. Comienza con un valor inicial de la variable llamada índice y las
acciones especificadas se ejecutan a menos que el valor inicial sea mayor que el final
cuando la variable índice sufre incrementos, o bien se detiene si la variable índice es
menor que el final si la variable índice sufre decrementos, (Cairó, 2006) como se
aprecia en las figuras 16 y 17.
NOTA IMPORTANTE: toda estructura desde o para se puede sustituir por una
estructura mientras, y viceversa, sin embargo, se recomienda aplicar la estructura desde
o para sólo en aquellos problemas en los que se conoce previamente el número de veces
que se debe repetir el ciclo, y utilizar las estructura mientras en aquellos problemas en
que el número de veces que se tenga que repetir el ciclo dependa de la condición a
evaluar y no de un número determinado de veces.
En el lenguaje C, la estructura repetitiva desde o para se especifica con la
instrucción for como se muestra a continuación.
for (inicialización; condición; incremente/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 e
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
48
omite es el incremento/decremento, entonces éstos deben realizarse dentro del cuerpo de
la estructura for.
falso
falso
variable
índice >= o >
variable final
variable
índice <= o <
variable final
verdadero
verdadero
acciones
acciones
Incrementar
variable índice
Decrementar
variable índice
Figura 16. Estructura desde o para.
indicevi
indice <= o < o > o >= vf
indiceicremento o decremento
No
ivi
i <= o < o > o >= vf
iincremento o decremento
Si
Si
Acciones
Acciones
Figura 17. Otra forma de representar a la estructura desde o para (Bermúdez et. al., 2003).
Al igual que la estructura while y do while antes mencionadas, en la estructura
for, si se tiene sólo una proposición no es necesario colocar llaves para englobar la
sentencia a ejecutarse, pero si es más de una sí son necesarias.
Por último, como se explicó en la nota importante anterior de este apartado, todo
while tiene su equivalente en for y viceversa, a modo de ejemplo se presenta en seguida
el código equivalente.
49
Ejemplo de un ciclo for
for (expr1; expr2; expr3)
proposición;
Equivalencia con un ciclo while
expr1;
while(expr2){
proposición;
expr3;
}
9.4 Estructuras repetitivas anidadas
Como en las estructuras condicionales, es posible insertar un bucle dentro de
otro. La estructura interna debe estar incluida totalmente dentro de la externa y no puede
existir solapamiento. El anidamiento se puede dar entre mismas o diferentes estructuras
repetitivas, es decir, puede existir un ciclo mientras dentro de otro ciclo mientras, un
ciclo hacer mientras dentro de otro ciclo hacer mientras o un ciclo para dentro de otro
ciclo para; pero también, puede existir un ciclo mientras dentro de un ciclo para, un
ciclo hacer mientras dentro de un bucle mientras, o cualquier clase de combinación
dependiendo del problema a resolver.
Un ciclo interno completo se repetirá el número de veces que la condición del
ciclo externa sea verdadera. Por ejemplo, si se tuvieran dos anidamientos, es decir un
tercer ciclo dentro de un segundo y éste dentro de un primer ciclo, entonces el ciclo más
interno, o sea, el tercero, se ejecutará la cantidad de veces que sea verdadera la
condición del segundo ciclo multiplicado por la cantidad de veces que sea verdadera la
condición del primer ciclo; y el segundo bucle se ejecutará la cantidad de veces que la
condición del primer ciclo sea verdadera.
9.5 Instrucciones que alteran el flujo normal de un ciclo
En ocasiones es necesario disponer de instrucciones que permitan la salida en un
punto intermedio de cualquier bucle sin que se verifique la condición principal de dicho
bucle, es decir, salir del ciclo antes de llegar a la condición. Estas instrucciones sólo
están disponible en algunos lenguajes de programación y en general no es recomendable
usarlas, porque el programa no es tan legible o “limpio” como debe ser.
Sentencia break
Finaliza la ejecución de una proposición switch, for, while y do while en la cual
aparece dicha instrucción, saltándose la condición normal del ciclo de repetición.
Sentencia continue
Interrumpe la ejecución normal de un bucle (for, while y do while) pero no lo
finaliza, sino que, finaliza la iteración en curso, transfiriendo el control del programa a
la condicional del bucle, para decidir si se debe realizar una nueva iteración o no. Es
50
decir, continue termina la ejecución de la iteración actual de un bucle, pero no la
ejecución del bucle en sí como lo hace break. La instrucción continue evita que se
ejecuten las instrucciones que existan después de ella en la iteración del bucle, saltando
hasta la condicional.
Función exit( )
Otra forma de terminar un ciclo de repetición es utilizar la función exit. A
diferencia del break, terminará no sólo con la ejecución del bucle sino con la ejecución
del programa y regresa el control al sistema operativo.
10. NÚMEROS PSEUDOALEATORIOS EN EL LENGUAJE C
Existen multitud de problemas que requieren que una computadora simule el
comportamiento de un sistema con alguna variable o variables aleatorias, es decir, que
genere números cuyo orden no sea predecible y puedan considerarse al azar, por
ejemplo: el número de viajeros en una estación de autobuses, programación de juegos
(tirada de un dado), etc. Sin embargo, en la práctica, no existen números que, generados
por una computadora, sean realmente aleatorios, pues hay que tener en cuenta que una
computadora es una máquina determinista, es decir, determinada por las condiciones
iniciales y en la que no hay ni puede haber operaciones aleatorias puras. Pero, una
computadora sí puede generar números pseudoaleatorios (Rodríguez y Galindo, 2009).
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 el apartado 6: Lenguaje C). El
valor 32,767 se encuentra en la mayoría de las bibliotecas, aunque se puede llegar a
encontrar como máximo el número: 2,147,483,647 (Rodríguez y Galindo, 2009).
Las funciones antes mencionadas generarán números pseudoaleatorios a partir
de un número inicial llamado semilla y, es con ese valor inicial que genera el primer
número, después, a partir de dicho número genera el segundo, con el segundo genera el
51
tercero y así sucesivamente hasta concluir con la cantidad de números pseudoaleatorios
que se necesiten producir.
Cuando se requiere cambiar el valor máximo del intervalo cerrado de números
pseudoaleatorios que da por default el lenguaje C, entonces se debe dimensionar
usando un factor de dimensionamiento, donde dicho factor determina el valor
máximo. Para ello, se usa la función que genera los números pseudoaleatorios seguida
del operador módulo y el factor de dimensionamiento antes mencionado. 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 (Deitel &
Deitel, 1995):
random( ) % n
Es la sintaxis en el sistema operativo GNU/Linux para generar un número
pseudoaleatorio entre los valores del intervalo cerrado [0, n-1].
rand( ) % n
Es la sintaxis en el sistema operativo Windows para generar un número pseudoaleatorio
entre los valores del intervalo cerrado [0, n-1].
Ahora bien, cuando se requieren cambiar ambos valores del intervalo cerrado,
además del dimensionamiento, se debe llevar a cabo un desplazamiento. Suponiendo a
m y n como variables enteras proporcionadas por el programador o usuario y, m < n,
entonces:
random( ) % (n-m+1) + m
Es la sintaxis en el sistema operativo GNU/Linux para generar un número
pseudoaleatorio entre los valores del intervalo cerrado [m, n].
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;
52
/*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;
Ahora bien, 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 números pseudoaleatorios en cada corrida del
programa; para corregir esto, se puede hacer uso de alguna de las siguientes funciones:
-
Función time( ) del lenguaje C que se encuentra en la biblioteca time.h
Esta función devuelve la fecha y hora actual que tenga establecido el sistema
operativo.
La forma de utilizar la función descrita junto con la función srand o srandom
se muestra a continuación:
srandom( time(NULL) ) en el sistema operativo GNU/Linux, o
srand( time(NULL) ) en el sistema operativo Windows.
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 inicial7.
-
Función getpid( ) que se puede encontrar en las bibliotecas: sys/types.h o
unistd.h
Esta función devuelve el número de proceso que se le haya asignado al
programa que se está ejecutando actualmente, el cual siempre es distinto en cada
corrida de dicho programa.
La forma de utilizar la función descrita junto con la función srand o srandom
se muestra a continuación:
7
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.
53
srandom( getpid( ) ) en el sistema operativo GNU/Linux, o
srand( getpid( ) ) en el sistema operativo Windows.
Por otra parte, la función drand48( ) se usa para obtener números
pseudoaleatorios con decimales y la función srand48( ) se usa para cambiar la semilla
de inicio, por lo que también puede hacer uso de time( ) y getpid( ) como se explicó
anteriormente. Estas funciones no se encuentran en el sistema operativo Windows.
Ejemplos:
numero = drand48( )*(20.0-10.0) + 10.0
numero = drand48( )*(n-m)+n
11. ARREGLOS
Introducción a las estructuras de datos
Una estructura de datos es una colección de datos simples que se caracterizan
por su organización y las operaciones que se definan en ella.
 Datos simples
 Estándar
 Datos estructurados
 Estáticos

Entero

Arreglos (vector o matriz)

Real

Registros

Caracter

Archivo o ficheros

Lógico

Conjuntos

Cadenas
 Definido por el programador

Subrango

Enumerativo
 Dinámicos

Listas (pilas o colas)

Listas enlazadas

Árboles

Grafos
Los tipos de datos simples o primitivos significan que no están compuestos de
otras estructuras de datos. Los más frecuentes y utilizados por casi todos los lenguajes
son: enteros, reales y caracter; siendo los tipos lógicos, subrango y enumerativos
propios de lenguajes estructurados como Pascal por ejemplo.
54
Los tipos de datos compuestos están construidos basándose en tipos de datos
primitivos, por ejemplo, las cadenas de caracteres están construidas por el tipo básico
caracter. Los tipos de datos compuestos pueden ser organizados en diferentes
estructuras de datos: estáticas o dinámicas.
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: 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.
En este curso se trabajan solamente estructuras de datos estáticas y en los
siguientes apartados se revisan en particular los arreglos.
Concepto de arreglo
Un arreglo es una colección finita, homogénea y ordenada de elementos, en otras
palabras, es una estructura compuesta por varios elementos, todos del mismo tipo
(entero, real, carácter o booleano) y almacenados consecutivamente en memoria. Cada
componente puede ser accedido directamente por el nombre del arreglo seguido de uno
o varios subíndices encerrados entre corchetes [ ] según sea la dimensión del arreglo; los
subíndices indican la posición de cada componente del arreglo y pueden ser únicamente
valores enteros positivos, variables tipo entero positivos o expresiones numéricas
enteras positivas (Cairó, 2006).
Existen 2 tipos de arreglos según su dimensión: unidimensional, porque para
acceder a un elemento del arreglo sólo se tiene que utilizar un índice; y
multidimensional, porque para acceder a un elemento del arreglo se utilizan múltiples
índices. Para este último tipo de arreglo, el más utilizado es el bidimensional, es decir,
el de dos dimensiones, por tanto, a continuación se describen el arreglo unidimensional
y el arreglo 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 debe utilizar tanto el
55
nombre del arreglo como el índice del elemento (Cairó, 2006). En la figura 18 se puede
observar la representación gráfica de un arreglo unidimensional.
Figura 18. Representación gráfica de un arreglo unidimensional (Cairó, 2006).
En el lenguaje C, el arreglo unidimensional se declara de la siguiente manera:
tipo nombre[tamaño]
donde:
tipo es uno de los tipos predefinidos por el lenguaje: float, int, etc.
nombre: es un identificador que nombra el arreglo y sigue las mismas reglas
especificadas para dar nombre al identificador de una variable.
tamaño es una constante entera positiva que especifica el número de elementos
del arreglo.
En el lenguaje C, los arreglos unidimensionales inician en la posición cero [0],
por lo que la posición máxima del arreglo está dada por el número máximo de
elementos menos 1.
También, el lenguaje C no revisa los límites del arreglo unidimensional, por lo
que es responsabilidad del programador el realizar este tipo de operación para no
acceder a una posición negativa o mayor al límite de elementos.
Los elementos de un arreglo pueden ser inicializados en la declaración del
arreglo mismo, haciendo seguir a la declaración con el signo igual y una lista de valores
inicializadores separados por comas (la lista encerrada entre llaves).
Los elementos de un arreglo no son de forma automática inicializados a cero,
al menos que se inicialice el primer elemento a cero, para que los demás queden
automáticamente inicializados a ese valor.
Ejemplos de declaración e inicialización de arreglos unidimensionales (Cairó,
2006):
56
/*Declara un arreglo unidimensional llamado arreglo para
almacenar 10 elementos de tipo real o flotante de doble
precisión.*/
double arreglo[10];
/*Se declara el arreglo unidimensional llamado V con 10
elementos de tipo real y al mismo tiempo se inicializa cada
casilla del arreglo con cada uno de los valores que se
encuentran entre llaves y separados por comas. La
representación gráfica del arreglo V se muestra en la
figura 19.*/
float V[10] = {32.5,15.8, 70.1, 5.9, 0, -12.2, 13.3, 90.4,
56.6, -9.8};
Figura 19. Representación gráfica del vector V con 10 elementos de tipo real.
Si se quiere acceder al primer elemento del arreglo se debe escribir V[0], pero si se
quiere acceder al quinto elemento se debe escribir V[4]. Por otra parte, el valor de V[7]
es 90.4, el de V[3+5] es 56.6 y el resultado de V[2] + V[5] es 57.9.
/*Lo siguiente muestra un error de sintaxis porque se están
proporcionando más inicializadores de arreglo que elementos
existen dentro del mismo.*/
double Valores[5] = {32.1,27.3,64.4,18.6,95.7,14};
/*Si en la lista de inicialización se omite el tamaño del
arreglo, el número de elementos en el arreglo será el
número de elementos incluidos en la lista inicializadora.*/
int n[ ] = {1,2,3,4,5};
/*Si dentro del arreglo existe un número menor de
inicializadores que de elementos, los elementos restantes
son inicializados a cero automáticamente.*/
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};
57
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
declarado un arreglo. El
23.897*/
arreglo[0]=23.897
individual después de haber
primer valor de arreglo vale
Si las posiciones de un arreglo no son inicializados o no se les asigna
explícitamente un valor, se considera que almacenan “basura”, pues en memoria
siempre existen datos. No se debe olvidar almacenar datos en un arreglo antes de
realizar operaciones con ellos, tal como sucede con las variables.
Se pueden realizar distintas operaciones con arreglos unidimensionales durante
el proceso de resolución de un problema, pero generalmente se tiene:

Asignación

Lectura/escritura

Recorrido (acceso secuencial)

actualizar (añadir, borrar, insertar)

Ordenación

Búsqueda
Puesto que en un arreglo se trabaja más de un dato, generalmente las
operaciones antes listadas se tienen que realizar usando ciclos como se mencionó con la
inicialización.
11.2 Arreglos bidimensionales
El arreglo bidimensional se considera un vector de vectores, generalmente
llamado tabla (término financiero) o matriz. Formalmente es una colección finita,
homogénea y ordenada de datos, en la que se hace referencia a cada elemento del
arreglo por medio de dos índices, uno para indicar el renglón o fila y el segundo para
indicar la columna del arreglo (Cairó, 2006). La figura 20 muestra la representación
gráfica de un arreglo bidimensional.
58
Figura 20. Representación gráfica de un arreglo bidimensional (Cairó, 2006).
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.
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 el lenguaje C, los arreglos bidimensionales inician en las posiciones [0][0],
por lo que la posición máxima del arreglo está dada por el número máximo de
renglones menos 1 con el número máximo de columnas menos 1. Suponiendo que se
tienen N renglones y M columnas, entonces la posición máxima está en [N-1][M-1],
además, se almacenarán N x M elementos del mismo tipo.
El lenguaje C no revisa los límites de un arreglo, es responsabilidad del
programador el realizar este tipo de operaciones para no acceder a una posición negativa
o mayor al límite de elementos tanto para los renglones como para las columnas.
59
Los elementos de un arreglo bidimensional no son de forma automática
inicializados a cero, al menos que se inicialice el primer elemento a cero, para que los
demás queden automáticamente inicializados a ese valor.
Ejemplos de declaración e inicialización de arreglos bidimensionales (Cairó,
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
cero.*/
int A[3][6] = {0};
del
arreglo
se
inicializan
con
/*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};
/*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}};
60
Figura 21. Representación gráfica del arreglo bidimensional C con 3 x 6 elementos de tipo
entero (Cairó, 2006).
/*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 n es el número de renglones y m el número de columnas, generalmente se
requiere recorrer m columnas por cada renglón que constituye a la matriz, es decir,
recorrer m columnas n 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 utiliza o por el espacio en memoria, por lo que la cantidad de
índices que se utilizan para acceder a sus valores, depende de la dimensión del arreglo.
En en el lenguaje C se declaran de la siguiente manera y como máximo se tienen
hasta 12 subíndices de arreglo:
tipo nombre[tamao1][tamaño2]…[tamaño_n]
61
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 la sección
Tabla de prioridad y orden de evaluación del apartado 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 el apartado 1: Preliminares, conceptos
básicos), por ser inteligible directamente por la computadora. La difícil tarea de
programar en código máquina hizo que el alfabeto evolucionara y los lenguajes de
programación comenzaran a utilizar códigos o juegos de caracteres similares a los
utilizados en los lenguajes humanos. Así, hasta el día de hoy, la mayoría de las
computadoras trabajan con diferentes tipos de juegos de caracteres, de los cuales
destacan el código ASCII (American Standard Code for Information Interchange) y el
EBCDIC (Extended Binary Coded Decimal Interchange Code) (Deitel & Deitel, 1995).
El código ASCII básico utiliza 7 bits (el concepto de bit se especificó en el
apartado 1: Preliminares y conceptos básicos) para cada caracter a representar, 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 & Deitel, 1995).
En general, un caracter ocupará un byte (8 bits) de almacenamiento de memoria
y se puede definir como un símbolo del juego de caracteres de la computadora.
El código ASCII se compone de los siguientes tipos de caracteres (Deitel &
Deitel, 1995):

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.
62
Tabla 17. Código ASCII de caracteres de control.
Binario
0000 0000
0000 0001
0000 0010
0000 0011
0000 0100
0000 0101
0000 0110
0000 0111
0000 1000
0000 1001
0000 1010
0000 1011
0000 1100
0000 1101
0000 1110
0000 1111
0001 0000
0001 0001
Dec.
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Hex.
00
01
02
03
04
05
06
07
08
09
0A
0B
0C
0D
0E
0F
10
11
Abreviatura
NUL
SOH
STX
ETX
EOT
ENQ
ACK
BEL
BS
HT
LF
VT
FF
CR
SO
SI
DLE
DC1
Representación
^@
^A
^B
^C
^D
^E
^F
^G
^H
^I
^J
^K
^L
^M
^N
^O
^P
^Q
0001 0010
0001 0011
18
19
12
13
DC2
DC3
^R
^S
0001 0100
0001 0101
20
21
14
15
DC4
NAK
^T
^U
0001 0110
0001 0111
22
23
16
17
SYN
ETB
^V
^W
0001
0001
0001
0001
0001
0001
0001
0001
0111
24
25
26
27
28
29
30
31
127
18
19
1A
1B
1C
1D
1E
1F
7F
CAN
EM
SUB
ESC
FS
GS
RS
US
DEL
^X
^Y
^Z
^[ or ESC
^\
^]
^^
^_
^?, Delete,
or
Backspace
1000
1001
1010
1011
1100
1101
1110
1111
1111
Nombre/significado
Caracter nulo
Inicio de encabezado
Inicio de texto
Fin de texto
Fin de transmisión
Enquiry
Acknowledgement
Timbre
Retroceso
Tabulación horizontal
Avance de línea
Tabulación vertical
Avance de página
Retorno de carro
Shift Out
Shift In
Data Link Escape
Control de dispositivo 1
XON
Control de dispositivo 2
Control de dispositivo
XOFF
Control de dispositivo 4
Negative
Acknowledgement
Synchronous Idle
Fin de transmisión de
bloque
Cancelar
End of Medium
Substitute
Escape
Separador de archivo
Separador de grupo
Separador de registro
Separador de unidad
Borrado o retroceso
Tabla 18. Código ASCII de caracteres alfabéticos, numéricos y especiales.
Binario
0010
0000
0010
0001
0010
Dec.
32
Hex.
20
Representación
espacio ( )
33
21
!
34
22
"
Binario
0101
0000
0101
0001
0101
Dec.
80
Hex.
50
Representación
P
81
51
Q
82
52
R
63
0010
0010
0011
0010
0100
0010
0101
0010
0110
0010
0111
0010
1000
0010
1001
0010
1010
0010
1011
0010
1100
0010
1101
0010
1110
0010
1111
0011
0000
0011
0001
0011
0010
0011
0011
0011
0100
0011
0101
0011
0110
0011
0111
0011
1000
0011
1001
0011
1010
0011
1011
0011
1100
0011
1101
35
23
#
36
24
$
37
25
%
38
26
&
39
27
'
40
28
(
41
29
)
42
2A
*
43
2B
+
44
2C
,
45
2D
-
46
2E
.
47
2F
/
48
30
0
49
31
1
50
32
2
51
33
3
52
34
4
53
35
5
54
36
6
55
37
7
56
38
8
57
39
9
58
3A
:
59
3B
;
60
3C
<
61
3D
=
0010
0101
0011
0101
0100
0101
0101
0101
0110
0101
0111
0101
1000
0101
1001
0101
1010
0101
1011
0101
1100
0101
1101
0101
1110
0101
1111
0110
0000
0110
0001
0110
0010
0110
0011
0110
0100
0110
0101
0110
0110
0110
0111
0110
1000
0110
1001
0110
1010
0110
1011
0110
1100
0110
1101
83
53
S
84
54
T
85
55
U
86
56
V
87
57
W
88
58
X
89
59
Y
90
5A
Z
91
5B
[
92
5C
\
93
5D
]
94
5E
^
95
5F
_
96
60
`
97
61
a
98
62
b
99
63
c
100
64
d
101
65
e
102
66
f
103
67
g
104
68
h
105
69
i
106
6A
j
107
6B
k
108
6C
l
109
6D
m
64
0011
1110
0011
1111
0100
0000
0100
0001
0100
0010
0100
0011
0100
0100
0100
0101
0100
0110
0100
0111
0100
1000
0100
1001
0100
1010
0100
1011
0100
1100
0100
1101
0100
1110
0100
1111
62
3E
>
63
3F
?
64
40
@
65
41
A
66
42
B
67
43
C
68
44
D
69
45
E
70
46
F
71
47
G
72
48
H
73
49
I
74
4A
J
75
4B
K
76
4C
L
77
4D
M
78
4E
N
79
4F
O
0110
1110
0110
1111
0111
0000
0111
0001
0111
0010
0111
0011
0111
0100
0111
0101
0111
0110
0111
0111
0111
1000
0111
1001
0111
1010
0111
1011
0111
1100
0111
1101
0111
1110
110
6E
n
111
6F
o
112
70
p
113
71
q
114
72
r
115
73
s
116
74
t
117
75
u
118
76
v
119
77
w
120
78
x
121
79
y
122
7A
z
123
7B
{
124
7C
|
125
7D
}
126
7E
~
Una constante caracter se define como cualquier caracter encerrado entre
apóstrofos o comilla sencilla, específicamente en el lenguaje C, se usa la comilla
sencilla.
Para ingresar o mostrar en un dispositivo de salida tipos de datos caracter en el
leguaje C, se usan las instrucciones vistas en el apartado 7: Instrucciones de entrada y
salida, las cuales pueden ser: scanf, printf, putchat o getchar.
Las operaciones que se realizan generalmente con caracteres suelen ser las
siguientes (Cairó, 2006):
 Verificar si una caracter es o no es un dígito.
 Verificar si un caracter es o no una letra
65
 Verificar si un carácter es una letra minúscula o mayúscula
 Convertir una letra a minúscula o mayúscula
Para realizar lo anterior, en el lenguaje C existen como parte de la biblioteca
ctype.h, las siguientes funciones: isdigit( ), isalpha( ), islower( ),
isupper( ), tolower( ) y toupper( ). Todas reciben como argumento el
valor que se quiere verificar o convertir (Cairó, 2006).
12.2 Cadenas de caracteres
Una cadena se define como una secuencia finita de caracteres que finaliza con el
caracter nulo ‘\0’ y se almacena en un área contigua de la memoria. Una constante
tipo cadena consiste en una cadena encerrada entre apóstrofos, comillas sencillas o
dobles comillas, específicamente en el lenguaje C, se encierra la cadena usando el
caracter de doble comilla, la cual no se puede modificar.
Las cadenas se pueden usar en aplicaciones de gestión, generación y
actualización de listas de dirección, inventarios, bases de datos, traductores de
lenguajes, etc.
Las operaciones comunes que se realizan con cadenas de caracteres son (Deitel
& Deitel, 1995):

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 el apartado 7: Instrucciones de entrada y
66
salida, éstas son: scanf y printf. Así como las que describen en los ejemplos siguientes:
puts y gets.
A continuación se presentan ejemplos de declaración e inicialización de cadenas
en el lenguaje C.
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 el apartado 7: Instrucciones de Entrada y Salida (E/S),
existe el parámetro %s para mandar a pantalla variables o constantes de tipo cadena y
67
para ingresar desde el teclado cadenas que se almacenarán en variables de tipo cadena,
por ejemplo:
printf(“%s”, “Un afectuoso saludo”);
/*Imprime la cadena “Un afectuoso saludo”*/
printf(“%s”, SALUDO);
/*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 datos que está enviando a pantalla como
con printf y es la más apropiada para escribir cadenas de
caracteres. Esta función baja automáticamente una línea
después de imprimir. (Cairó, 2006)*/
scanf(“%s”, nombre);
/* En la variable nombre se almacenarán los caracteres que
se ingresen desde el teclado. Cuando se da un valor a una
variable desde el teclado, no es necesario teclear el
caracter nulo, éste se asigna automáticamente después de
pulsar la tecla enter. El problema con esta forma de
ingresar datos, es que el carácter espacio en blanco no se
almacenará en la variable, de hecho, si se escriben
caracteres después de un espacio en blanco, todos ellos no
se almacenarán.*/
gets(cadena);
/* Otra forma de ingresar caracteres desde teclado y a
diferencia del ejemplo anterior, si se ingresan caracteres
en blanco, éstos serán también almacenados en la variable.
Esta función es la más apropiada para leer cadenas de
caracteres (Cairó, 2006). */
scanf(“%[^\n]”, frase);
/* En frase se almacenarán todos los caracteres que se
ingresen desde el teclado incluyendo el carácter de espacio
en blanco. El ingresar caracteres termina cuando se pulsa
la tecla enter, sin que este carácter se almacene en
68
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 de dichas funciones
son:

strlen, para obtener la longitud de una cadena.

strcmp, para comparar si dos cadenas son iguales o no.

strcpy, para copiar el contenido de una cadena a otra cadena.

tolower, para convertir los caracteres de una cadena a minúsculas.

toupper, para convertir los caracteres de una cadena a mayúsculas.
12.3 Arreglos de caracteres
Un arreglo unidimensional de caracteres se define como una colección finita,
homogénea y ordenada de datos, en que se hace referencia a cada elemento del arreglo
por medio de un índice y no necesariamente finaliza con el caracter nulo (Cairó, 2006).
Como se mencionó en los ejemplos el apartado anterior, la diferencia sustancial entre
una cadena y un arreglo de caracteres es la existencia al final de la cadena del carácter
nulo.
Por otra parte, se pueden utilizar arreglos bidimensionales de caracteres para
manejar conjuntos de cadenas, esto es, dado que una cadena se define como un arreglo
unidimensional que termina con el caracter nulo, entonces, si se desea almacenar
cadenas de caracteres en arreglos bidimensionales, cada fila almacenaría una cadena y
cada columna almacenaría los caracteres correspondientes de cada cadena (Cairó,
2006). Por ejemplo, si se desea almacenar un grupo de 5 cadenas de caracteres con 40
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][30];
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 la sección Diseño de un algoritmo del apartado 5:
Proceso de resolución de problemas utilizando un lenguaje de programación, la
69
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.
El enfoque en éstos apuntes será sobre los subprogramas llamados funciones,
que son los únicos que existen en el lenguaje C.
Función
Matemáticamente hablando una función es una operación que toma uno o más
valores llamados argumentos y produce un valor denominado resultado, es decir,
genera el valor de la función para los argumentos dados, por otro lado, una función
computacionalmente hablando es una colección de sentencias que ejecutan una tarea
específica y no puede contener a otra función (Ceballos, 1997).
Todos los lenguajes de programación tienen funciones incorporadas o intrínsecas
que se utilizan escribiendo sus nombres con los argumentos adecuados entre paréntesis,
(como las funciones matemáticas del lenguaje C: sqrt, pow, sin, etc.) y funciones
definidas por el usuario o programador, es decir, escritas por él mismo.
Las funciones incorporadas al sistema se denominan funciones internas o
intrínsecas y las funciones definidas por el usuario, funciones externas o extrínsecas.
Cuando las funciones estándares o internas no permiten realizar el tipo de cálculo
deseado es necesario recurrir a las funciones externas.
Una vez que una función ha sido escrita y depurada puede utilizarse una y otra
vez. Cuando una función se manda a llamar, el control se pasa a dicha función para su
ejecución y cuando ésta finaliza, el control es devuelto al módulo que la llamó para
continuar con la ejecución del mismo después de donde se efectuó la llamada, como se
muestra en la figura 22 (Bermúdez et. al., 2003). En ese ejemplo, dentro de la función
principal del lenguaje C, o sea, la función main se hace el llamado a la función
denominada func1, por lo que la secuencia de ejecución se pasa a dicha función,
70
realizándose una a una las instrucciones dentro de func1, pero al llegar al llamado de
func2, nuevamente la secuencia de la ejecución se desvía hacia la función func2. Se
ejecutan las instrucciones que existan dentro de la función func2 hasta alcanzar el
return, con lo cual se regresa el control a func1 para continuar con la ejecución de las
instrucciones que contenga hasta llegar al return de func1, con lo cual se regresa el
control a la función principal main. Como nuevamente se hace el llamado a la función
func1, se vuelve a realizar el proceso descrito hasta regresar a la función principal main
para ejecutarse todas las instrucciones que contenga main hasta llegar al final del
programa, en conclusión, como se muestra en la figura 22, el orden de ejecución es
conforme la numeración 1 al 8.
main( )
{
func1( );
…
func1( );
return;}
1
5
4
func1( )
{
func2( );
…
return;
}
2
6
func2( )
{
…
3
return;
7
}
8
Figura 22. Ejemplo del llamado de una función en el lenguaje C (Ceballos, 1997).
13.1 Funciones definidas por el usuario en el lenguaje C
Todo programa en C consta al menos de la función main( ), que es donde inicia
la ejecución de un programa. También puede constar de otras funciones que ya ofrece el
lenguaje y de aquellas que diseña el programador.
Las funciones que declara el usuario pueden aparecer en cualquier orden y en uno o
varios archivos fuente, conformándose de un encabezado y un cuerpo. De manera explícita se
puede decir que, es un bloque o una proposición compuesta. La estructura básica de la
definición de una función es la siguiente:
tipo_de_retorno nombre (parámetros formales)
{
declaraciones;
proposiciones;
return(expresión);
}
a) Encabezado de una función
tipo_de_retorno indica el tipo del valor devuelto por la función. Puede ser cualquier
tipo básico, estructura o unión. Por defecto es del tipo int. Cuando no se requiere que
devuelva algún valor se usará el tipo void.
71
nombre es un identificador que indica el nombre de la función. Sigue las mismas reglas
que se tienen para nombrar una variable.
Parámetros formales es una secuencia de declaraciones separadas por coma. Cada
parámetro, es decir, variable o arreglo, debe ir con el tipo de dato correspondiente. Si no
se pasan argumentos a la función se puede utilizar la palabra reservada void.
Generalmente se usa la palabra parámetro formal para una variable nombrada en la
lista entre paréntesis de la definición de función, y argumento o parámetro actual para
el valor empleado al hacer la llamada de la función.
b) Cuerpo de la función
Está formado por una sentencia compuesta que contiene sentencias que definen
lo que hace la función. Las declaraciones de variables utilizadas en la función por
defecto son locales a la función, es decir, sólo son definidas, vistas y válidas dentro de
la función donde son empleadas, pero fuera de dicha función, no son conocidas y no
tienen valor.
return (expresión) se utiliza para devolver el valor de la función, el cual debe ser del
mismo tipo declarado en el encabezado de la función. Si la sentencia return no se
especifica o se especifica sin contener una expresión, la función no devuelve un valor.
Cuando explícitamente se devuelve un valor, éste puede ir o no entre paréntesis, es
decir, los paréntesis son opcionales.
La instrucción return puede ser o no la última que aparezca en el cuerpo de la
función y puede aparecer más de una vez, dependiendo del problema que se esté
resolviendo. En el caso de que la función no retorne algún valor, la instrucción se puede
omitir.
c) Llamado de una función
Para ejecutar una función hay que llamarla. La llamada a una función se hace
mediante su nombre con los argumentos o parámetros actuales o reales entre
paréntesis. Generalmente se asigna el valor que regresa una función a una variable del
mismo tipo de ésta.
Cuando se llama a una función, el valor del primer parámetro actual es pasado al
primer parámetro formal, el valor del segundo parámetro actual es pasado al segundo
parámetro formal y así sucesivamente. Todos los argumentos excepto los arreglos, son
pasados por valor. Esto es, a la función se pasa una copia del argumento, no su
dirección en memoria, esto hace que la función C no pueda alterar los contenidos de las
variables transmitidas. Si se desea poder alterar los contenidos de los argumentos en la
llamada entonces hay que pasarlos por referencia (método que se verá más adelante, en
la sección 13.5).
Ejemplos:
Ejemplo 1
Ejemplo 2
72
#include <stdio.h>
#include <stdio.h>
void letrero(void)
{
void letrero2(int i)
{
printf(“\n Esta es una función
if (i>0)
printf(“\n i
muy simple”);
positivo”);
else
printf(“\n
i
return;
negativo”);
}
es
es
}
int main( ) {
int main( ) {
letrero2(10);
letrero( );
return 0;
return 0;
}
}
Ejemplo 3
int n2=55;
#include <stdio.h>
res = suma(5,10) ;
int suma(int a, int b)
{
int valor;
valor = a+b;
printf( “La suma es: %d \n”, res);
res = suma(n1,n2);
printf(“La suma de %d + %d =
\n”, n1,n2,res);
%d
return (valor);
}
int main( )
{
int res;
int n1=23;
printf(“La suma de
%d”,suma(-25,-12));
-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 tipo 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).
73
Un prototipo de función (declaración forward) permite conocer el nombre de la
función, el tipo de resultado que devolverá, así como los tipos y nombres de los
parámetros a recibir; todo ello antes de especificar el cuerpo de la función, es decir,
antes de haberla definido o programado8.
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.
Ejemplo (Deitel & Deitel, 1995):
/*Encontrar el mayor de tres números reales*/
#include <stdio.h>
float maximo(float, float, float);
maximo */
/* Prototipo de la función
int main( )
{
float a, b, c;
printf(“Ingrese los tres datos:”);
scanf(“%f%f%f”, &a, &b, &c);
printf(“El máximo es: %f\n”, máximo(a,b,c));
return 0;
}
float maximo (float x, float y, float z)
{
float max = x;
if (y > max) max = y;
if (z > max) max = z;
return max;
}
8
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 el apartado 6: Lenguaje C.
74
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 corresponda 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 & 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 a otros tipos, sin
perder datos. 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 & 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 (se crea una versión
temporal de cada valor y se utiliza para la expresión conservándose sin cambios los
valores originales) (Deitel & Deitel, 1995). A continuación se presentan las reglas de
promoción que se aplican automáticamente (Ceballos, 1997):
1. Si en una operación, el operador de precisión más alta es de tipo long double,
entonces, el otro operador es convertido a tipo long double.
2. Si en una operación, el operador de precisión más alta es de tipo double,
entonces, el otro operador es convertido a tipo double.
3. Si en una operación, el operador de precisión más alta es de tipo float, entonces,
el otro operador es convertido a tipo float.
75
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 & 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
r
i
r
=
=
=
=
7/3
/* i = 2 */
7/3
/* i = 2.000000 */
12.6/3
/* i = 4 */
12.6/3
/* i = 4.2 */
76
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).
Ejemplo:
#include <stdio.h>
int main( )
{
int r, b = 5 ;
r = escribir(b) ;
printf(“%d\n”, r);
return 0;
}
int escribir (int y)
{
return y * 3 ;
}
La función escribir es declarada implícitamente para retornar un valor int, ya
que es invocada antes de su definición. El compilador crea automáticamente una
función prototipo utilizando la información de la llamada a la función.
13.3 Paso de parámetros por valor y por referencia
En C existen dos tipos de parámetros en el llamado a una función: por valor o
por referencia.
Paso de parámetros por valor
77
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.
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 llamadora 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 & 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
78
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 aptSuma al haberse inicializado con el valor
NULL, no apunta a alguna porción de memoria como lo hace aptValor.
int valor = 55,
aptValor
*aptSuma = NULL,
valor
*aptValor = &valor;
aptSuma
0028FEE8
55
0028FEEC
0028FEE8
aptValor
0028FEE4
0028FEEC
Figura 23. Representación gráfica y en memoria de variables y apuntadores.
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 de 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.
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 del apartado 7:
Instrucciones de entrada y salida). Por ejemplo: printf(“%p-%p-%p”, &valor,
aptValor, &aptValor); tendría como salida: 0028FEE8-0028FEE80028FEEC.
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 del llamador o cuando se requiere pasar un apuntador a
un objeto de datos grades, como una estructura de datos o arreglo, evitando así el hacer
demasiadas copias de los valores (Deitel & Deitel, 1995).
79
Por regla, cuando se llama a una función, los argumentos enviados en la llamada
son pasados por valor, a menos que se use el operador de dirección (&) a las variables
cuyos valores serán modificados, pues con ello, se estará especificando que los
argumentos se pasan por referencia. Los arreglos no se pasan con el operador & porque
el lenguaje C pasa de forma automática la posición inicial en memoria del arreglo, de
hecho, el nombre de un arreglo es equivalente a escribir &nombre_arreglo[0] (Deitel &
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 del llamador.
Ejemplo (Ceballos, 1997):
#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
80
que contienen las variables a, b y c. Es decir, la salida a pantalla del valor que almacena
res no será 0, sino 20.
Por último, como se mencionó antes, en el prototipo de una función se pueden
omitir los nombres de los parámetros, por ello, en el prototipo de la función sumar, el
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 & 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 & Deitel, 1995).
Los únicos identificadores con alcance de prototipo de función son aquellos 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 & Deitel, 1995).
Ejemplo:
{
int a = 5;
printf(“\n%d”, a);
{
int a = 7;
81
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 lo tanto sus valores son distintos, en la segunda
declaración de la variable a, ésta se destruye cuando alcanza el fin del bloque de
proposiciones (primer llave cerrada), por lo que los valores que se verán impresos en
pantalla son: 7 y 6.
13.5 Variables locales y variables globales
La regla de alcance es utilizada comúnmente para utilizar variables globales y
locales.
Las variables globales se declaran al inicio del programa fuera del main y fuera
de cualquier función, en cambio las variables locales se declaran dentro de algún
bloque. La diferencia sustancial entre estos dos tipos de variables es el alance: las
variables globales pueden modificar su valor en cualquier parte del programa, mientras
que las variables locales sólo pueden ser usadas en el bloque donde fueron definidas.
Cada variable local de una función comienza a existir sólo cuando se llama a la
función y desaparece cuando la función termina. Debido a que las variables locales
aparecen y desaparecen con la invocación de funciones, no retienen sus valores entre
dos llamadas sucesivas y deben ser inicializadas explícitamente en cada entrada, de no
hacerlo, contendrán basura, es decir, cualquier dato que se halle en la localidad de
memoria a la que apunta.
Las variables globales (externas) se mantienen permanentemente en existencia,
en lugar de aparecer y desaparecer cuando se llaman y terminan las funciones,
mantienen sus valores aún después de que se regresa del llamado a la función que les
fijó algún valor.
Ejemplo (Ceballos, 1997):
#include <stdio.h>
int x = 20;
void escribe_x(void);
int main( )
82
{
int x = 12;
escribe_x ( );
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.
A
B
C
D
E
F
Variables definidas
en
Accesibles desde
A
B
C
D
E
F
G
A, B, C, D, E, F, G
B, C
C
D, E, F, G
E, F, G
F
G
G
Figura 24. Ejemplo del alcance de una variable en el lenguaje C (Deitel & Deite, 1995).
83
14. ANEXOS
14.1 Prácticas sanas de programación (tomado de Deitel & Deitel
(1995))
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 guión 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 en un
programa para mayor legibilidad.
5. Colocar espacios en blanco a ambos lados de un operador lógico o relacional
para resaltar el operador y hacer que el programa sea más legible.
6. Colocar una sangría en el o los enunciados del cuerpo de una estructura if.
7. Si no se está totalmente seguro del orden de evaluación en una expresión
compleja, utilizar paréntesis para obligar al orden, exactamente como se haría en
expresiones algebraicas.
8. Utilizar sólo letras mayúsculas para los nombres de constantes simbólicas, así
resaltarán en el programa y harán recordar que no se pueden cambiar sus valores
porque son constantes y no variables.
9. En expresiones donde se utilice el operador lógico &&, colocar primero la
condición que más probabilidades tenga de ser falsa; en expresiones que utilicen
el operador lógico ||colocar primero la condición que más probabilidades tenga
de ser verdadera. Esto puede reducir el tiempo de ejecución de un programa.
10. Escribir las llaves de principio y de terminación de los enunciados compuestos
(como los ciclos o condiciones simples) antes de empezar a escribir en el interior
de dichas llaves los enunciados individuales; esto ayudará a evitar la omisión de
una llave o ambas.
11. Inicializar variables que se utilicen como contadores o como totales.
12. Al ejecutar una división por una expresión cuyo valor pudiera ser cero, probar de
forma explícita este caso y manejarlo de manera apropiada en el programa que
se esté creando, por ejemplo, se puede imprimir un mensaje de error en lugar de
permitir que ocurra un error fatal al momento de ejecutarse el programa.
13. En una estructura switch, cuando la cláusula default se enlista al final, el
enunciado break no es requerido, pero se recomienda incluirlo para fines de
claridad y simetría con otros cases.
84
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. Coloca 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 ocasiones a 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 y siempre tiene que ser menor que el número total de
elementos del arreglo (tamaño - 1).
7. Asegurarse que la condición de terminación del ciclo impida el acceso a
elementos fuera de ese rango.
Sobre funciones
1. Colocar una o dos líneas en blanco entre definiciones de función 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, no utilizar los mismos nombres para los
argumentos que se pasan a una función y los que se usan en el momento de la
definición de la función, para evitar cualquier ambigüedad.
4. Dar nombres a las funciones de acuerdo a lo que evaluarán como se sugiere con
las variables.
5. Incluir prototipos de función para todas las funciones que se vayan a utilizar.
6. Si se requiere que una función regrese más de un valor o, una función recibirá
demasiados parámetros, entonces se sugiere dividir a esa función en funciones
más pequeñas.
7. La declaración de una variable como global en lugar de local, permite que
ocurran efectos colaterales no deseados cuando una función que no requiere de
acceso a dicha variable, la modifica accidentalmente. En general, el uso de
variables globales debe ser evitado.
85
¿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. Ignora 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.
86
REFERENCIAS
Arellano Pimentel, J. J., Nieva García O. S., Solar González, R. y Arista López, G.
(2012). Software para la enseñanza-aprendizaje de algoritmos estructurados. Revista
Iberoamericana de Educación en Tecnología y Tecnología en Educación. No. 8. ISSN
1850-9959. Red de universidades Nacionales con Carrera en Informática – Universidad
Nacional de la La Plata (RedUNCI-UNLP).
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., Galicia Hernández, Y., López Ramírez,
C., Martín Ortíz, M., Mendoza Alonso, L., Somodevilla Garcia, M.J., Vazquez 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
http://elvex.ugr.es/decsai/java/
a
la
informática.
Recuperado
de:
Cairó Battistutti, O. (2006). Fundamentos de programación. Piensa en C (1ra. ed.).
México: Pearson Educación de México, S.A. de C.V.
Ceballos, F.J. (1997). Enciclopedia del Lenguaje C. México: Alfaomega Grupo Editor.
Deitel, H.M. & 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 en el apartado 1 conceptos básicos)
Hooshyar, D., Alrashdan, M. and Mikhak, Masih. (2013). Flowchart-based
Programming Environments Aimed at Novices. International Journal of Innovative
Ideas. ISSN: 2232-1942. Vol. 3. No. 1.
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
Marzal, A. & Gracia, I. (2006). Introducción a la programación con Python, Edición
Internet. Departamento de Lenguajes y Sistemas Informáticos, Universitat Jaume I.
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.
Wikipedia. (2015). Instituto Nacional Estadounidense de Estándares. Recuperado de:
http://es.wikipedia.org/wiki/Instituto_Nacional_Estadounidense_de_Est%C3%A1ndares
87