PRÓLOGO
4
CAPÍTULO Nº 0 : INTRODUCCIÓN
5
CAPÍTULO Nº 1 : DIAGRAMAS DE FLUJO
6
Símbolos utilizados en Diagramas de Flujo
Instrucciones de Asignación
Instrucciones de Entrada y Salida
6
7
7
Instrucciones de Control
Inicio y Término
Niveles de un diagrama de flujo.
7
8
9
Contadores
Análisis del diagrama.
Operadores Matemáticos:
11
14
15
Estructura While (Mientras)
17
Resumen Diagramas de Flujo
20
Resumen Diagramas de Flujo
Operador Módulo
21
24
Problemas Resueltos y Propuestos
26
CAPÍTULO Nº 2 : LENGUAJE C
Un poco de historia
36
36
Forma General del Lenguaje C.
Estructura de un Programa
Bibliotecas
Indentación
Identificadores
Comentarios
37
38
39
39
39
40
Tipos de datos fundamentales
40
Resumen Palabras clave
41
Operadores
Operadores Matemáticos
Operadores de Relación
Operadores Lógicos
41
41
42
42
Comenzando a Programar
Declaración de variables
Asignación:
Inicialización
42
42
43
44
Entrada / Salida Estándar
printf()
scanf()
44
44
46
Programación de Computadores y el Lenguaje C
Problemas Resueltos y Propuestos
CAPÍTULO Nº 3: ESTRUCTURAS DE CONTROL
Estructura if
Proposición if - else
Estructura while (Mientras)
Algo más del if
Algo más del while
Estructura for
Ciclo do-while
Proposición break
Proposición Continue
Proposición switch
Problemas Resueltos y Propuestos
CAPÍTULO Nº 4 : FUNCIONES EN C
(versión 2003)
50
54
54
54
56
59
60
61
62
63
63
63
66
72
Instrucción return
Variables Globales y Locales
Llamadas a Funciones
Paso de Parámetros a funciones
Paso de variables por valor
73
74
75
77
78
Problemas Resueltos y Propuestos
83
CAPÍTULO Nº 5 : ARREGLOS O MATRICES
87
Arreglos de n-dimensiones
Constantes Simbólicas
Cadenas de Caracteres
Cadenas sin tamaño definido
Como retornar un string
Arreglos como parámetros
89
90
94
95
96
97
Problemas Resueltos y Propuestos
101
CAPÍTULO Nº 6 : MODOS DE ALMACENAMIENTO
110
Variables Automáticas (auto)
110
Variables Externas (extern)
110
Variables Registro (register)
111
Variables Estáticas (static)
112
Variables estáticas externas
112
CAPÍTULO Nº 7 : PUNTEROS EN C
Operador &
Operador *
Paso de parámetros por referencia
Aritmética de Punteros
Algo más sobre punteros
malloc, free y sizeof
Profesor: Roberto Uribe P.
113
113
114
115
118
119
119
Pág. Nº 2
Universidad de Magallanes - Departamento de Ingeniería en Computación
Un ejercicio gráfico
121
Problemas Propuestos
124
CAPÍTULO Nº 8 : TYPEDEF, STRUCT Y UNION
125
Typedef
125
Estructuras
Arreglos de estructuras
Algo más sobre estructuras
Estructuras Anidadas
Funciones y Estructuras
Punteros a Estructuras
Algo más sobre punteros a estructuras
126
127
131
131
133
134
135
Uniones (unión)
136
Problemas Resueltos y Propuestos
138
CAPÍTULO Nº9 : ARCHIVOS EN C (EN CONSTRUCCIÓN)
144
Tipo usado en archivos
FILE
144
144
Funciones para manejo de archivos
Un ejemplo interesante con fread
144
149
ÚLTIMAS NOTAS
151
ANEXO Nº 1 : ESTRUCTURA DE UN COMPILADOR
152
ANEXO Nº 2 : ALGUNOS CONCEPTOS
153
Profesor: Roberto Uribe P.
Pág. Nº 3
Programación de Computadores y el Lenguaje C
(versión 2003)
UNIVERSIDAD DE MAGALLANES
FACULTAD DE INGENIERÍA
ESCUELA DE COMPUTACIÓN
Programación de Computadores
Profesor: Roberto Uribe Paredes
Prólogo
Cada vez que hacemos uso de una computadora, debemos enfrentarnos a distintos
software, que van desde el sistema operativo hasta las planillas electrónicas, procesadores de
texto, software de comunicación, etc. Todos ellos son programas, y han demandado un arduo
trabajo que involucró tanto a analistas, diseñadores y programadores.
Las etapas en el desarrollo de un software son bastantes y generalmente no fáciles, más
aún cuando en la actualidad las necesidades son complejas. El presente texto no pretende
abarcar el área de desarrollo de software ni las etapas de este, sino insertar al alumno en el
pensamiento lógico, necesario para desarrollar nuestros primeros programas.
Para cumplir este objetivo primero se emplea un método gráfico para resolución de
problemas, Diagramas de Flujo. Luego se inserta al alumno en el formalismo de un lenguaje
de programación, C, empezando por resolver los problemas vistos con el método anterior.
Finalmente, se van complicando los problemas a resolver, ahora sólo utilizando el lenguaje
formal, de tal manera de ir adquiriendo los conocimientos necesarios para resolver ciertos
problemas.
El texto1 contiene problemas y ejercicios resueltos para cada capítulo, además, cada
uno de estos ejercicios tiene el análisis y la explicación del método usado para resolverlo.
También se incluye la ejecución de algunos de los programas, es decir, el cómo funciona.
Al finalizar el curso el alumno estará capacitado para analizar, resolver e implementar
soluciones (programas) a diversos problemas de aplicaciones computacionales.
1
La primera versión del texto comenzó a escribirse en 1995.
Profesor: Roberto Uribe P.
Pág. Nº 4
Universidad de Magallanes - Departamento de Ingeniería en Computación
Capítulo Nº 0 : Introducción
Dentro de los métodos usados para resolver problemas en lo que llamamos
programación estructurada está el conocido como Diseño Top-Down. Este consiste en dividir
el problema en partes, de tal manera de crear subproblemas que son más fáciles de resolver, si
estos subproblemas son lo suficientemente complicados, entonces se vuelve a dividir. Esta
división se hace hasta dejar pequeños problemas, los cuales se pueden resolver como módulos
independientes. Luego de esto se van uniendo los módulos (soluciones) de tal manera de
resolver el problema original.
El diseño top-down responde a la idea de que es más fácil manejar pequeños problemas
y resolverlos uno a uno a enfrentar un sólo problema de mayor complejidad. Cuando se habla
de este diseño se lo asocia a la frase dividir para reinar, que es el mismo concepto, es decir,
dividir nuestro problema para cumplir nuestros objetivos.
En los primeros capítulos de este texto los problemas planteados son lo bastante
sencillos para poder resolverlos como un módulo. Posteriormente cuando la complejidad
aumenta se aplica el diseño top-down, esto es posible ya que nuestros pequeños problemas los
resolvemos en lo que conoceremos como funciones.
Profesor: Roberto Uribe P.
Pág. Nº 5
Programación de Computadores y el Lenguaje C
(versión 2003)
Capítulo Nº 1 : Diagramas de Flujo
La programación de computadores, es darle órdenes a un computador, comúnmente
escritas, para ello existen pasos previos:
- ¿Para qué queremos programarlo?
- ¿Qué secuencia de órdenes será la más eficiente?
- ¿Qué lenguaje emplearemos?
Por lo general para responder a estas interrogantes se hace uso de diagramas (se
entiende más fácilmente un mapa que una secuencia de coordenadas).
Existen a lo menos tres formas gráficas para representar soluciones a problemas:
- Diagrama de Flujo.
- Diagrama de BOHM y Jacopini
- Diagrama F.P.L. (First Programming Languaje).
El más común es el Diagrama de Flujo.
Los diagramas de flujo están siendo cada vez menos utilizados, esto debido, a que los
computadores tienden cada vez más a los lenguajes naturales. Los niveles en que se representa
un diagrama pueden ser tres:
* Nivel conceptual
* Nivel Algorítmico.
* Nivel Instrucción.
Definir estos tres niveles es arbitrario y depende de la complejidad del problema.
Antes de comenzar a solucionar un problema hay que tener muy claro cuales son los
objetivos generales que se persiguen, esto es, saber exactamente lo que se esta pidiendo (¿Para
qué queremos programarlo?). El próximo paso a seguir es determinar una secuencia de
instrucciones que de solución al problema, esta secuencia se conoce como "Algoritmo", para
problemas clásicos como ordenar y buscar existen ya diferentes algoritmos de solución.
Ejemplos de algoritmos de ordenación:
- Método de la Burbuja
- Método de Selección
- Algoritmo de Mezcla
Símbolos utilizados en Diagramas de Flujo
Todo lenguaje de programación posee tres tipos básicos de instrucciones:
- Asignación
- Entrada/Salida
Profesor: Roberto Uribe P.
Pág. Nº 6
Universidad de Magallanes - Departamento de Ingeniería en Computación
- Control
(Los diagramas de flujo no están ajenos a esta clasificación).
Instrucciones de Asignación
Se refiere comúnmente a acciones a seguir o cálculos que quedan almacenados en
algún lugar (variables de memoria):
Ejemplos:
Tomar Lapiz
Sumar A y B
producto=A*B
Se usa el rectángulo para identificar asignaciones.
Instrucciones de Entrada y Salida
Toda máquina en general requiere comunicarse con el mundo exterior (generalmente
usuarios), es decir, necesita de instrucciones de entrada y salida, estas se denotan por un
rombo (o romboide).
Ejemplos :
Imprimir
Datos
Salida
Entrada
Leer
Palabra
Los rombos se acompañan de una flecha, que indica la entrada o salida de información.
Instrucciones de Control
Un programa es una secuencia de instrucciones que dependiendo de resultados
parciales, la secuencia puede ser alterada. Para ello se utiliza el símbolo:
No
Pregunta
Si
Dependiendo de la condición, si es verdadera o falsa toma uno de los sentidos.
Profesor: Roberto Uribe P.
Pág. Nº 7
Programación de Computadores y el Lenguaje C
(versión 2003)
Ejemplo:
No
Nota >= 50
Reprobado
Si
Aprobado
Se utiliza a veces el símbolo de decisión con más de dos salidas, por ejemplo:
<30
NotaFinal
>=50
>=30
Reprobado
Da Examen
Aprobado
Inicio y Término
Para especificar el Principio o fin de una secuencia, se utiliza el símbolo.
Ejemplo:
Inicio
Proceso
Fin
Los símbolos de diagramas de flujo se unen con rectas, dos rectas se pueden unir, pero
una recta no puede dividirse en dos.
Profesor: Roberto Uribe P.
Pág. Nº 8
Universidad de Magallanes - Departamento de Ingeniería en Computación
Ejemplos:
Paso1
Sumar i y j
Paso2
Error
Paso3
Paso1
Paso2
Paso4
Incorrecto !!
Fin
Correcto !!
Para que exista una bifurcación en un Diagrama de Flujo., necesariamente debe existir
un símbolo de decisión.
Sumar i y j
Correcto !!
No
Pregunta?
Paso1
Si
Paso2
Niveles de un diagrama de flujo.
- Nivel Conceptual
- Nivel Algorítmico
- Nivel Instrucción
Profesor: Roberto Uribe P.
Pág. Nº 9
Programación de Computadores y el Lenguaje C
(versión 2003)
Ejemplos:
1). Nivel Conceptual:
(Llamada de teléfono)
2) Nivel Algoritmo
(llamada de teléfono público)
Inicio
Inicio
Levantar fono
Buscar
Teléfono
No
Si
¿Hay Tono?
Discar
Insertar
Moneda
Hablar
Discar
Colgar
No
Fin
¿Contestan?
Si
Hablar
Colgar
Fin
3) Nivel Instrucción.
Ejemplo: Leer A y B, luego sumar A y B y asignarla a C.
Inicio
Leer
A
Leer
B
C=A+B
C
Fin
Profesor: Roberto Uribe P.
Pág. Nº 10
Universidad de Magallanes - Departamento de Ingeniería en Computación
Lo que hace el programa anterior es leer dos variables A y B desde algún dispositivo de
entrada (como el teclado), sumarlas y asignar el resultado a C.
Inicio
A=5
B=6
C=0
C=A+B
C
Fin
En este caso A y B ya tenían valores asignados antes de la suma. La suma de "A" y "B"
se asigna a "C", independiente del valor anterior de "C".
Este ejemplo hace lo mismo que el anterior, pero se inicializan las variables al
comienzo del programa. En ambos ejemplos se utilizaron instrucciones básicas de entrada,
salida y de asignaciones.
El nivel de instrucción es mas próximo a la programación de computadores, los niveles
anteriores son simplemente para representar divisiones más gruesas de un problema, como las
situaciones cotidianas indicadas.
Contadores
Los contadores son variables de memoria (de ahora en adelante "variable"), que se
incrementan o decrementan de acuerdo a la necesidad del programador, se denotan de la
siguiente manera (asignación):
contador=contador+1
El símbolo igualdad tiene un significado diferente que en aritmética, "lo que existe a la
derecha es una expresión y lo que existe a la izquierda es una variable". Una vez realizada la
operación la variable asume el valor de la expresión. Para este caso, el contador fue
incrementado en 1.
Generalmente los contadores son utilizados por ejemplo, para llevar la cuenta de los
datos que vamos a ingresar por teclado, cuantos datos estamos imprimiendo en pantalla, es
decir, en ciclos o iteraciones. En si un contador forma parte de una asignación.
Profesor: Roberto Uribe P.
Pág. Nº 11
Programación de Computadores y el Lenguaje C
(versión 2003)
Ejemplo de asignaciones e incrementos:
Inicio
A=0
B=5
A=A+1
A=A+B
A=0,B=5
A=1
A=6
Fin
La forma de analizar este diagrama es de la siguiente manera:
- A parte en 0, B se inicializa en 5.
- Luego en la línea A=A+1, esto se reemplaza por A=0+1, es decir A queda igualada a
1 (se incrementa en 1).
- Finalmente A=A+B, se reemplaza por A=1+5, es decir 6 (se incrementa en 5).
Ejercicio 1.1:
Suponga que para aprobar una asignatura, a un alumno se le calcula el promedio
aritmético, luego si este es superior o igual a 50, entonces esta aprobado (eximido), si el
promedio es menor a 50, pero mayor o igual a 30, tiene derecho a dar examen. Si el promedio
es menor que 30, reprueba sin derecho a dar examen, la escala de evaluación es de 0 a 100.
Entonces,
Sumar tres notas, si el promedio es mayor o igual a 50, entonces "eximido", si es mayor
o igual que 30 y menor que 50, entonces "da examen", si es menor que 30, entonces
"reprobado". El examen vale 30% y el promedio 70%.
Realizaremos un primer intento de programa. Como son tres notas las que hay que leer,
entonces crearemos tres variables, una para cada nota. También una variable para almacenar
la suma y otra para el promedio.
Sin Lectura del examen, sólo imprimiendo "eximido", "da examen" y "reprobado".
Profesor: Roberto Uribe P.
Pág. Nº 12
Universidad de Magallanes - Departamento de Ingeniería en Computación
Inicio
Leer
Nota1
Leer
Nota2
Leer
Nota3
Suma=Nota1+Nota2+Nota3
promedio=suma/3
Es
No
Si
promedio>=50
Es
No
promedio>=30
Si
“Eximido”
“Reprobado”
“Da Examen”
promedio
Fin
El promedio se imprime al final para evitar hacerlo tres veces (una por cada bifurcación). El
promedio podría haberse calculado sin necesidad de utilizar la variable suma, es decir, sólo
con promedio=(nota1+nota2+nota3)/3, utilizando las mismas normas que en matemáticas
(ósea la suma entre paréntesis). Esto depende exclusivamente del programador.
En nuestro programa faltó calcular la nota final si el alumno da examen. Una nueva
versión de nuestro diagrama sería:
Profesor: Roberto Uribe P.
Pág. Nº 13
Programación de Computadores y el Lenguaje C
(versión 2003)
Inicio
Leer
Nota1
Leer
Nota2
Leer
Nota3
Suma=Nota1+Nota2+Nota3
promedio=suma/3
Es
No
promedio>=50
Si
Es
No
promedio>=30
Si
NotaFinal=promedio
NotaFinal=promedio
Examen
NotaFinal=Examen*0.3+promedio*0.7
NotaFinal
Fin
Análisis del diagrama.
En nuestro problema necesitábamos lee 3 notas, por lo tanto hacemos 3 lecturas, una
para cada nota, en diagramas de flujo esto no es tan estricto, podrían haberse leído las 3 notas
dentro de un mismo símbolo.
nota1, nota2, nota3
Profesor: Roberto Uribe P.
Pág. Nº 14
Universidad de Magallanes - Departamento de Ingeniería en Computación
Luego de esto sumamos las notas y se lo asignamos a la variable suma, posteriormente
obtenemos el promedio.
Como ya se tiene el promedio de las notas y necesitamos saber si este es mayor o igual
a 50, si esta entre 30 y 49 o si es menor a 30. Entonces primeramente preguntamos si es mayor
o igual a 50 , para ello utilizamos el símbolo de pregunta (símbolo de control). De esto
obtenemos dos líneas, en una sacamos por pantalla aprobado, en la otra línea volvemos
preguntar es mayor o igual a 30, con esto tenemos si da examen o reprueba.
En este caso se utilizó la variable nota_final para imprimir una sola vez el resultado al
final del programa (diagrama).
Las variables utilizadas fueron:
nota1
nota2
nota3
suma
promedio
nota_final
: primera nota leída.
: segunda nota que se lee.
: es la variable donde se almacena la tercera nota.
: variable que guarda el resultado de la suma de las notas.,
: promedio de las notas.
: nota final obtenida.
Operadores Matemáticos:
Estos se utilizan para realizar los cálculos necesarios en los programas, estos van en el
lado derecho de una asignación, es decir en la expresión. En el ejemplo anterior se utilizó el
operador suma en la línea suma=nota1+nota2+nota3 y el operador división en la línea
promedio=promedio/3. Los operadores matemáticos son:
+
*
/
:
:
:
:
Suma
Resta
Multiplicación
División
Existe otro operador, el módulo o resto de la división entera, este se verá al final de este
capítulo.
Ejercicio 1.2: En la mayoría de los problemas se necesita repetir alguna operación
(repeticiones o iteraciones).
Entonces, resolver un algoritmo para el cálculo del promedio con "N" notas, donde el
número de notas es un valor conocido.
Cuando se dice que una variable tiene un valor conocido, esto implica que se ingresa al
programa, en este caso vía teclado.
Para simplificar el problema, eliminar la condición cuando la nota es menor de 30, o
sea con nota menor a 50 se da examen.
Profesor: Roberto Uribe P.
Pág. Nº 15
Programación de Computadores y el Lenguaje C
(versión 2003)
Solución (Existen otras):
- Utilizaremos las variables del ejercicio anterior.
- Para el ejercicio 2 agregaremos dos variables:
Num_Notas : número de notas a leer.
cont : contador de notas, servirá para saber cuando terminar el programa.
Esta variable se inicializará en 1.
- Entonces:
Inicio
cont=1
suma=0
promedio=0
nota_final=0
Num_Notas=0
nota=0
Leer
Num_ Notas
Es
No
Si
cont<=Num_Notas
promedio=suma/Num_Notas
Leer
nota
Es
No
suma=suma+nota
Si
Promedio>=50
cont=cont+1
Leer
Examen
nota_final=promedio
nota_final=examen*0.3+
promedio*0.7
imprimir
nota_final
Fin
Profesor: Roberto Uribe P.
Pág. Nº 16
Universidad de Magallanes - Departamento de Ingeniería en Computación
En este ejercicio se inicializó todas las variables, esto se usa como norma para ayudar
a mantener el orden en los programas.
Estructura While (Mientras)
La estructura denotada por:
Es
?
No
Si
Se puede utilizar como "estructura while", la palabra "Es" se reemplaza por
"Mientras", en el ejercicio 2.
Mientras
No
cont<=Num_Notas
Si
Proceso
Entonces se produce un ciclo o "Loop". La idea del ciclo while es formular la siguiente
pregunta: Mientras, la condición sea verdadera .... hacer ....
Una forma adecuada de ver si un programa funciona correctamente es trazándolo, en
nuestra jerga diríamos que lo ejecutamos en papel. Para ello creamos tantas columnas como
variables declaradas tengamos, luego vamos colocando valores en las columnas a medida que
las variables asuman estos valores, con esto vemos el comportamiento de cada variable y de los
ciclos.
Por ejemplo veamos la ejecución del ejercicio Nº 1.2.
Primero supongamos que las notas que se van a leer son tres, 60, 60 y 50, entonces el
comportamiento de las variables sería como se muestra en la tabla siguiente:
1
2
3
4
Num_Notas
0
3
Profesor: Roberto Uribe P.
nota
0
60
60
50
suma
0
60
120
170
cont
1
2
3
4
promedio
0
nota_final
0
55
55
examen
Pág. Nº 17
Programación de Computadores y el Lenguaje C
(versión 2003)
Imagine que se va ejecutando sentencia a sentencia (cada sentencia esta representada
por una figura).
En la fila 1 se muestra la inicialización de todas las variables, menos examen. La
siguiente instrucción es la lectura de Num_Notas (cantidad de notas), se ingresa un 3 al
programa, y en la ejecución se coloca el 3 en la variable que corresponde, esto se ve en la fila
2. Luego se para en la instrucción de control y se pregunta Es cont<=Num_Notas, entonces
cont tiene el valor 1 y Num_Notas es 3, por lo tanto el control del programa se va por la
alternativa si. Después de esto viene la instrucción de lectura, donde se ingresa la primera
nota, si ingresamos el 60, entonces la variable nota asume este valor, y lo agregamos en
nuestra tabla en la fila 2 (columna nota). Pasamos a la siguiente instrucción,
suma=suma+nota, el valor se suma es 0 y el valor de nota es 60, entonces la suma da 60, este
valor lo colocamos en la fila 2 columna suma. Seguido se incrementa el contador en
cont=cont+1, como cont valía 1, entonces ahora vale 2, con esto se termina el primer ciclo ya
que el control vuelve hacia arriba y nuevamente se hace la pregunta Es cont<=Num_Notas.
Como cont es menor que Num_Notas, entonces el control se va por la derecha
(alternativa si), se lee la segunda nota (60) y se asigna a la variable nota, luego a suma se le
asigna suma+nota, el valor de suma es 60 y nota vale 60, por lo tanto ahora suma tiene el
valor 120, este valor se coloca en la fila 3, columna suma. La siguiente instrucción es el
incremento del contador, cont tenía el valor 2, ahora se incrementa en 1, por lo tanto ahora
tiene el valor 3.
El control vuelve a la instrucción de control, como cont sigue siendo menor o igual que
Num_Notas, entonces el control entra nuevamente en el ciclo. Se lee la nota 50, se hace la
suma de nota que vale 50 y suma que vale 120, entonces ahora suma vale 170. Luego el
contador cont se incrementa en 1, es decir, queda en 4. Retorna a la instrucción de control y
ahora la condición va a ser falsa, ya que cont tiene el valor 4 y Num_Notas 3, entonces el
control elige la alternativa no.
La siguiente instrucción es promedio=suma/Num_Notas, con esto promedio asume el
valor 55, este se coloca en la fila 4. Luego se pregunta Es promedio>=50, esto verdadero, por
lo tanto se va por la alternativa si. Luego se realiza la asignación nota_final=promedio, este se
ve en la fila 4, ahora nota_final vale 55.
Finalmente el valor 55 se imprime en pantalla.
Como ejercicio analice cuando las notas ingresadas son 60, 45, 40 y 50. La ejecución
resultaría como la siguiente tabla:
1
2
3
4
5
Num_Notas
0
4
Profesor: Roberto Uribe P.
nota
0
60
45
40
50
suma
0
60
105
145
195
cont
1
2
3
4
5
promedio
0
nota_final
0
examen
48,75
50,625
55
Pág. Nº 18
Universidad de Magallanes - Departamento de Ingeniería en Computación
En este caso el promedio es menor que 50, entonces se debe dar examen, suponiendo
que en el examen se obtuvo un 55, entonces se calcula la nota final multiplicando promedio por
0.7 y examen por 0.3. Finalmente nota_final va a valer 50,625.
El problema recién resuelto, sólo permite 1 alumno.
Ejercicio 1.3:
- Para el problema anterior (Nº 1.2), resolver para un curso completo, donde el Nº de
alumnos es un dato conocido. Obtener promedio e imprimirlo.
- Para simplificar el ejercicio, sólo se calculará el promedio de los alumnos, no la
determinación si se dará o no examen.
Profesor: Roberto Uribe P.
Pág. Nº 19
Programación de Computadores y el Lenguaje C
(versión 2003)
Inicio
cont_alum=1
Num_Alum=0
cont_notas=1
suma=0
promedio=0
Num_Notas=0
nota=0
Leer
Num_ Alum
Mientras
No
cont_alum<=
Num_Alum
Si
cont_notas=1
suma=0
Fin
Leer
Num_ Notas
Leer
Nombre_ Alumno
Mientras
cont_notas<=
Num_Notas
Si
Leer
nota
No
suma=suma+nota
cont_notas=cont_notas+1
promedio=suma/Num_Notas
promedio
Nombre_ Alumno
cont_alum=cont_alum+1
Profesor: Roberto Uribe P.
Pág. Nº 20
Universidad de Magallanes - Departamento de Ingeniería en Computación
Resumen Diagramas de Flujo
Como regla general, un programa se hace de la siguiente forma:
Inicio
Inicialización
de Variables
Ingreso de
Datos
Cálculos
Resúmenes
Impresión de
Resultados
Fin
- Estructura If.....Then.....Else
IF
ELSE
(Falso)
Condición
?
No
THEN
(Verdadero)
Si
PROCESO
PROCESO
- Estructura While
WHILE
(Falso)
Condición
?
HAGA
(Verdadero)
Proceso
Profesor: Roberto Uribe P.
Pág. Nº 21
Programación de Computadores y el Lenguaje C
(versión 2003)
Existen dos tipos de estructura while:
1.- Cuando se conoce a priori el número de veces que se repite un proceso,
- Promedio Nota
- Cálculo para n alumnos
2.- Cuando no se conoce las veces que se repite un proceso.
Ejemplo:
∞
F=
Σ
1/i
i=1
Determinar F de tal manera que si 1/i ≤ 10 -3, entonces terminar. Es decir, se van
obteniendo resultados hasta que el rango sea menor o igual a 0,001. Con esto se obtiene una
aproximación del resultado.
Inicio
F=0
i=1
Eps=0,001
While
No
Si
1/i >= Eps
i ,F
Fin
F = F + 1/ i
i=i+1
Como el contador i va aumentando, entonces 1/i va disminuyendo.
- Cuando se conoce antes la cantidad de iteraciones de un proceso, la estructura While
puede reemplazarse por una estructura For (Para), esta se simboliza de la sgte. manera:
Profesor: Roberto Uribe P.
Pág. Nº 22
Universidad de Magallanes - Departamento de Ingeniería en Computación
contanotas: 1
Si
n
Proceso
Es decir, el contador contanotas se irá incrementando de 1 hasta n, y por lo tanto el proceso
dentro del ciclo for se realizará n veces.
Para el ejercicio del promedio de un curso con "N" alumnos y "N" notas (ejercicio Nº
3), si utilizamos ciclos for, nuestro diagrama quedaría:
Inicio
cont_alum=1
Num_Alum=0
cont_notas=1
suma=0
promedio=0
Num_Notas=0
nota=0
Leer
Num_ Alum
cont_alum: 1
Num_Alum
Si
suma=0
Fin
Num_Notas
Nombre_ Alumno
cont_notas: 1
Num_Notas
Si
Leer
nota
suma=suma+nota
promedio=suma/Num_Notas
promedio
Nombre_ Alumno
Profesor: Roberto Uribe P.
Pág. Nº 23
Programación de Computadores y el Lenguaje C
(versión 2003)
Con estructura for se eliminan la inicialización de contadores y el incremento de estos.
Más adelante veremos que el for proporcionado por el lenguaje C es mucho más flexible.
Nota: Todos los lenguajes de programación poseen las estructuras hasta ahora vistas, o
por lo menos asignación, if y while (o alguna instrucción para iteraciones).
Operador Módulo
Aparte de los operadores aritméticos comunes, existe uno llamado módulo, este se usa
en variadas aplicaciones y su uso es bastante frecuente en programación. El operador módulo
nos permite obtener el resto de la división entera, ejemplo:
5:2=2
1 -------> resto de la división
ósea, 5 dividido 2 es igual a 2 y sobra 1, este es el módulo o resto. Utilizaremos el símbolo %
para denotar el operador módulo, que es precisamente el que utiliza C. Para el ejemplo
anterior,
5 % 2 -----> 1
Otros ejemplos:
12 : 3 = 4
0 -------> resto (módulo)
26 : 3 = 8
2 -------> módulo
es decir,
12 % 3 -----> 0
26 % 3 -----> 2
Ejercicio 4:
- Hacer un programa en el cual se ingrese un número y como resultado se indique si es
par o impar.
Entonces, sabemos que un número par es aquel que cuando se divide por 2 su resto es
0, por lo tanto el diagrama sería:
Profesor: Roberto Uribe P.
Pág. Nº 24
Universidad de Magallanes - Departamento de Ingeniería en Computación
Inicio
num=0
“Ingrese un
Número”
num
No
(num % 2)=0
“El número es
impar”
Si
“El número es
par”
Fin
Dentro de las aplicaciones típicas donde se utiliza el módulo están, determinar si un
número es o no primo, obtención de los n primeros números perfectos, hacer que cualquier
número sea convertido en otro dentro de un rango específico.
Por ejemplo, para el último caso, suponga que se obtienen números entre 0 y 999, y se
desea convertirlos al rango 0 a 10, el proceso principal sería:
datonuevo= num % 11
Es decir, cualquier número módulo 11 daría un número entre 0 y 10 (y en este caso
quedaría almacenado en la variable datonuevo). Generalmente se usa para que cualquier
número obtenido de un proceso de generación de números al azar (números aleatorios) esté
dentro del rango deseado.
Profesor: Roberto Uribe P.
Pág. Nº 25
Programación de Computadores y el Lenguaje C
(versión 2003)
Problemas Resueltos y Propuestos
Los siguientes ejercicios son clásicos en los primeros cursos de programación. Se
recomienda al alumno que resuelva los problemas y luego los compare con las soluciones
planteadas en el libro, recuerde que un problema se puede resolver de muchas formas
(demasiadas quizá!). Para cada solución propuesta en el libro, ejecute en papel el programa,
de esa manera se dará cuenta del funcionamiento de este.
Resueltos:
1.-
Hacer el diagrama de flujo que calcule:
xy ,
donde x e y son valores conocidos.
2.-
Resolver :
n
x = ∑i
;
n : valor conocido
i =1
3.-
Escribir el diagrama de flujo para la ecuación de 2º grado en la forma :
ax2 + bx + c = 0
a, b, c constantes conocidas
4.-
con : x1 =
−b + b 2 − 4 • a • c
2•a
x2 =
−b − b 2 − 4 • a • c
2•a
Hacer un diagrama de flujo que lea un archivo de datos numéricos :
Condiciones :
a) cuente los números leídos
b) entregue el mayor número leído
c) entregue el menor número leído
Condición de fin de archivo es eof (eof : end of file), es decir cuando el dato leído es eof,
entonces terminar el programa.
5.Crear un programa que lea una serie de números y finalmente entregue una lista de
cuantos eran pares y cuantos impares.
La condición de término será cuando la cantidad de impares o pares supere 15.
6.-
Escribir el diagrama de flujo para la sumatoria :
Profesor: Roberto Uribe P.
Pág. Nº 26
Universidad de Magallanes - Departamento de Ingeniería en Computación
n
y = ∑ xi
;
n, x : valores conocidos
i =1
7.-
Realizar el diagrama de flujo para el calculo de factorial de N.
Tomar en cuenta :
0! = 1
1! = 1
N! = N*(N-1)! = N*(N-1)*(N-2)* .... *(N-N)!
N : valor conocido
8.-
Escribir el diagrama de flujo para ex
donde :
ex = 1 + x1 + x2 + x3 + ...... + xn
n!
1! 2! 3!
x : valor conocido, n=1000
Propuestos:
9.-
Resolver :
sen x = x - x3 + x5 - x7 ...... ((-1)n *x2n+1)
(2n+1)!
3! 5! 7!
con x en radianes. x : valor conocido, n=10
10.-
Se define "promedio geométrico" como :
Prom = n (nota1 • nota 2•. ....•notaN
Realizar diagrama de flujo para resolver dicho problema. Asuma que es posible
realizar la operación de "raíz enésima de un número" ( n ); donde n valor conocido.
11.- Escriba un programa (diagrama de flujo) que calcule a sobre b.
a
a!
=
b b !(a − b )!
12.-
;
a,b,c: valores conocidos.
Cree un diagrama de flujo que obtenga el resultado de la siguiente sumatoria:
Profesor: Roberto Uribe P.
Pág. Nº 27
Programación de Computadores y el Lenguaje C
n
(versión 2003)
∑ (2i − 1)
2
(suponga n conocido)
i =1
13.- Resuelva la siguiente expresión :
∞
Σx
(2n-1)
/(2n-1)!
i=1
- La condición de término es :
x(2n-1) < 0.001
(2n-1)!
14.-
Escriba un diagrama de flujo que transforme un número de cualquier base a decimal.
Simplificación: asuma que la base no es mayor a 10.
Debe indicar la base a la que pertenece el número y la cantidad de dígitos que contiene
este.
Ejemplo : Para transformar el número 143 en base 5 a decimal el programa debería
funcionar así:
Programa que transforma números a decimal :
ingrese la base : 5
ingrese la cantidad de dígitos : 3
ingrese digito 1 : 1
ingrese digito 2 : 4
ingrese digito 3 : 3
El número en base 10 es : 48
Note que la base puede ser cualquiera ingresada por teclado.
Profesor: Roberto Uribe P.
Pág. Nº 28
Universidad de Magallanes - Departamento de Ingeniería en Computación
Soluciones:
1.x elevado a y es la multiplicatoria de x y veces, es decir, esto lo asociamos a un ciclo
que
nos permita ir de 1 hasta y. Usualmente al ciclo va ligado un contador que lleve en número de
veces del ciclo. También, debemos tomar en cuenta una variable auxiliar que nos permita ir
almacenando el valor anterior de la multiplicatoria. Lo último es que x e y son valores
conocidos, entonces debemos ingresarlos, por ejemplo por teclado.
Inicio
x=0 , y=0
cont=1
resultado=1
x,y
While
No
Haga
cont<= y
resultado=x*resultado
"x elevado a y es",
resultado
cont = cont + 1
Fin
Finalmente, debe tomar en cuenta que la variable auxiliar resultado se inicializa en 1,
esto dado que 1 es el neutro multiplicativo, no puede ser 0, ya que el resultado final sería 0. El
contador cont podría comenzar en 0, pero habría que modificar la condición del while por
cont<y (menor absoluto), de otro modo el resultado sería erróneo (se multiplicaría una vez
mas). Para un mejor entendimiento de la solución ejecute (en papel) el programa y modifique
los valores de resultado y cont, de tal manera de comprobar los errores que se provocarían.
Nuestro programa funciona para x distinto de 0 e y mayor o igual a 0, de otra manera
el programa arrojaría un resultado erróneo. Modifique el diagrama para tomar en cuenta los
casos anteriores.
2.Para este ejercicio, la sumatoria va de 1 hasta n, con n conocido (ingresado al
programa). Entonces, los elementos que necesitamos son: un ciclo (por ejemplo while), un
contador y una variable auxiliar que almacena la suma sucesiva.
Profesor: Roberto Uribe P.
Pág. Nº 29
Programación de Computadores y el Lenguaje C
(versión 2003)
Inicio
i=1 ,x=0
n=1
"Sumatoria de i, de
1 hasta n, Ingrese n
mayor o igual a 1."
n
While
No
Haga
i<=n
x=x + i
"El resultado de
la sumatoria es ",
x
i=i+1
Fin
Observe que en este caso la sumatoria (variable x) comienza en 0. El programa podría
modificarse de tal manera que si el valor ingresado es menor que 1, entonces enviar un
mensaje de error al usuario.
3.Para este caso no es necesario utilizar ciclos, ya que se conocen todos los valores de los
datos y sólo se debe resolver la fórmula indicada. Lo único que hay que considerar es cuando
el discriminante de la ecuación sea negativo, en este caso, como no podemos obtener la raíz de
un número negativo, entonces lo que hacemos es que la salida por pantalla sólo represente al
número imaginario. En este caso no se podría operar con un número imaginario, sin embargo,
en un lenguaje de programación se podrían crear las estructuras necesarias para realizar las
operaciones entre números imaginarios.
Profesor: Roberto Uribe P.
Pág. Nº 30
Universidad de Magallanes - Departamento de Ingeniería en Computación
Inicio
a=0,b=0 ,c=0
discr=0
x1=0,x2=0
"resolución de la
ecuación cuadrática:
ax2+bx+c=0
Ingrese valores para:
a, b, c"
a,b,c
Si
No
Si
a=0
x1=-c/b
discr=b*b-4*a*c
"x = ", x1
Si
No
Si
discr>=0
discr=-discr
"Soluciones Imaginarias"
"x1= ", - b/(2*a), "+", discr /(2*a), "i"
"x1= ", - b/(2*a), "-", discr /(2*a), "i"
x1=(-b+ discr )/(2*a)
x2=(-b - discr )/(2*a)
"Soluciones Reales",
"x1 = ", x1
"x2 = ", x2
Fin
En la solución al problema se tomó en cuenta cuando el valor de a es 0, en ese caso la
ecuación resulta de primer grado, por lo tanto la fórmula para resolverla es la trivial. Cuando
el resultado son números imaginarios, la salida por pantalla es sólo representativa. Asuma que
en este caso, lo que esta entre comillas dobles es texto y lo demás son valores. Un ejemplo de
una solución imaginaria en nuestro diagrama podría ser (para a=2,b=4,c=5):
x1 = -2 + 12 i
x2 = -2 - 12 i
4.Para este problema podemos tomar en cuenta los siguientes supuestos: se puede leer un
dato numérico y preguntar si este es eof. A pesar de que los datos están en un archivo, se
ocupa el mismo símbolo para la entrada.
Profesor: Roberto Uribe P.
Pág. Nº 31
Programación de Computadores y el Lenguaje C
(versión 2003)
En este caso debemos usar un ciclo while que termine cuando el dato sea igual a eof.
No se puede usar for, ya que no se sabe la cantidad de veces que se repite el ciclo. No es
necesario usar un contador.
Inicio
dato,max,min
dato
No
Si
dato=EOF
Si
"No existen
datos"
max=dato
min=dato
No
While
dato<>EOF
Haga
Si
dato>max
max=dato
"El máximo es:", max,
"El mínimo es:", min
Si
dato<min
min=dato
dato
Fin
En nuestro programa se ha considerado el caso en que el archivo este vacío, es decir, el
primer dato es EOF. Luego de haber leído el primer dato, se asigna como máximo y mínimo,
esto resulta lógico dado que es el único dato leído. La primera iteración no afecta, sin
embargo, al leer el segundo dato, este se compara con el máximo (el dato anterior), si resulta
ser mayor, entonces se reemplaza, de igual manera con el mínimo. Así sucesivamente se va
comparando el nuevo dato con el máximo y mínimo anterior.
5.Para el siguiente diagrama, sabemos que tenemos que leer una serie de números, pero
no sabemos cuantos. Entonces, necesitamos un ciclo cuya condición de término sea "la suma de
pares o impares sea mayor o igual a 15", por lo tanto, se crean dos variables, una que cuente
los pares y la otra los impares. Para terminar el ciclo una u otra debe ser mayor o igual a 15
Profesor: Roberto Uribe P.
Pág. Nº 32
Universidad de Magallanes - Departamento de Ingeniería en Computación
(basta que uno llegue a 15 para terminar). Es esencial notar la importancia del o en la
pregunta.
Inicio
cuenpar=0
cuenimpar=0
num=0
"Ingrese Datos”
Si
Si
cuenpar>15
No
Si
Si
cuenimpar>15
No
num
No
“Cantidad de”
"Pares:”, cuenpar
“Impares:”,cuenimpar
Si
(num mod 2)=0
cuenimpar=cuenimpar+1
Si
cuenpar=cuenpar+1
Fin
Las dos condiciones del diagrama anterior se pueden reemplazar por una sola.
Si
While
cuenimpar>15 o cuenpar>15
No
Nota: La condición podría haber sido:
No
While
cuenimpar<=15 y cuenpar<=15
Si
Es decir, "si ambos son menores que 15, entonces lea datos". Note que están invertidos
los símbolos y se reemplazó el o por el y.
6.Este problema involucra una sumatoria y una multiplicatoria (potencia). Si
utilizáramos funciones (módulos) podríamos resolver la sumatoria y llamar al módulo que
calcula el xi cada vez (con un i diferente por vez). Sin embargo, por ahora resolveremos ambos
problemas en un solo ciclo.
Profesor: Roberto Uribe P.
Pág. Nº 33
Programación de Computadores y el Lenguaje C
(versión 2003)
Entonces, necesitamos una variable que almacena la sumatoria (suma) y otra la
potencia (pot), con un contador (i) que vaya de 1 a n.
Inicio
suma=0,
pot=1, i=1
n
While
i<=n
No
Si
pot=pot * x
“La sumatoria es :",
suma
suma=suma + pot
i=i + 1
Fin
Este ejercicio puede haber sido resuelto usando la estructura for. Modifique el
programa para que arroje un error si el n ingresado es menor que 1.
7.El factorial de un número es la multiplicatoria de 1 hasta el número, además, sabemos
que si el número es 0 o 1, el factorial es 1.
Inicio
fact=1
i=1
n
Si
Si
n=0
No
i:1
“El factorial es :",
fact
n
Si
fact=fact * i
Fin
Observe que si n es igual a 0, la línea va directo hacia un símbolo de salida. Esto es
correcto dado que fact se inicializó en 1 y por lo tanto la salida para n=0 será 1.
8.El este ejercicio el ciclo iterará hasta 1000, sin embargo, esto es a modo de ejemplo,
dado que al usar un compilador cualquiera el programa fallaría por un problema de overflow
Profesor: Roberto Uribe P.
Pág. Nº 34
Universidad de Magallanes - Departamento de Ingeniería en Computación
(esto es lógico ya que 1000! es un valor excesivamente grande). Al traducir el diagrama a un
lenguaje el valor 1000 debe modificarse por otro (ejemplo 10).
Inicio
eelevx=1,x=0
pot=1,div=0
fact=1, i=1
x
No
While
i<=1000
Si
pot=pot * x
“e elevado a x es :",
elevx
fact=fact * i
div=pot / fact
Fin
eelevx=eelevx + div
i=i + 1
Observe que la variable elevx comienza en 1, esto es para obtener el primer valor de la
sumatoria.
Usualmente, para el cálculo de este tipo de constantes se utiliza un rango, de tal
manera que cuando este sea despreciable, entonces se termina el programa. Por ejemplo,
cuando la variable div sea inferior a 0,001, entonces agregar el valor que se obtenga en el
próximo ciclo no influirá demasiado en nuestra respuesta.
Modifique el programa de tal manera que este termine cuando div sea menor o igual a
0,001.
Profesor: Roberto Uribe P.
Pág. Nº 35
Programación de Computadores y el Lenguaje C
(versión 2003)
Capítulo Nº 2 : Lenguaje C
Un poco de historia
C es un lenguaje de propósito general, es decir, se pueden desarrollar aplicaciones de
diversas áreas. Dentro de sus principales características podemos mencionar que, es un
lenguaje estructurado, tiene una abundante cantidad de operadores y tipos de datos, es un
lenguaje de nivel medio, pero se puede codificar a alto nivel, produce código objeto altamente
optimizado, posee punteros y capacidad de aritmética de direcciones.
C fue creado en los Laboratorios Bell de AT&T para correr originalmente en el sistema
operativo Unix, sin embargo en la década de los 80 aparecieron versiones para computadores
personales. Existe una íntima relación entre Unix y C, ya que el primero fue reescrito en C, así
como el propio compilador de C y la mayoría de las herramientas de Unix, sin embargo, no
esta ligado a ningún sistema operativo o máquina.
C fue diseñado por Dennis Ritchie en 1972 a partir de un lenguaje llamado B escrito
por Ken Thompson en 1970, que a su vez fue inspirado en uno llamado BCPL concebido por
Martin Richard en 1967.
Entre sus múltiples ventajas podemos mencionar que C es un lenguaje muy portable, es
decir, es independiente de la arquitectura de la máquina y con alguna o ninguna modificación
un programa puede correr en una amplia variedad de computadores. Es relativamente flexible
en la conversión de datos (a veces se toma como desventaja). Su eficiencia y claridad han
hecho que el lenguaje ensamblador casi no haya sido utilizado en Unix. El compilador de C es
pequeño y tiene un gran poderío debido a sus múltiples bibliotecas.
Sin embargo, tiene sus desventajas y entre ellas se puede mencionar: la excesiva
libertad en la escritura del código fuente hace que muchas veces se cometan errores de
programación, que, por ser correctos sintácticamente no se detectan en tiempo de compilación
o a simple vista. Carece de instrucciones de entrada y salida, de manejo de strings (cadenas de
caracteres), quedando el trabajo en manos de las bibliotecas provocando con esto algunos
problemas de portabilidad.
Hay múltiples ejemplos de aplicaciones construidas en C, como ser: construcción de
sistemas operativos, procesadores de texto, administradores de bases de datos (por ejemplo
Clipper), programas para Windows y Windows 95, las APIs (Application Program Interface) de
las Librerías de Enlace Dinámico (DLL) de Windows están construidas mayoritariamente en C.
Con algo de experiencia se pueden crear compiladores, sobre todo con ayuda de herramientas
como Lex y Yacc que también están hechas en C.
Profesor: Roberto Uribe P.
Pág. Nº 36
Universidad de Magallanes - Departamento de Ingeniería en Computación
Forma General del Lenguaje C.
Para crear un programa en C, se escribe el código fuente (programa), luego se compila
y finalmente se enlaza con las bibliotecas (se hace un link, en nuestra jerga diríamos “se
linkea”).
Código Fuente
Compilar
Enlazar (link)
Todo lenguaje de programación posee palabras claves, estas son aquellas palabras que
reserva el lenguaje para identificar ciclos, estructuras y en general cualquier cosa que sea
parte de instrucciones, por ejemplo, las palabras while, if, struct son palabras claves en C.
En C las palabras claves o reservadas deben escribirse en minúsculas, esto ya que C
diferencia entre mayúsculas y minúsculas (a diferencia del lenguaje Pascal).
El Lenguaje C se escribe a partir de un programa principal y de funciones, el programa
principal también es una función (función principal), y se denomina main(), esta debe
estar siempre presente y es la primera en ser llamada.
Para hacer un programa en C, lo primero que se debe hacer es crear el programa
fuente (con extensión ".c") en un editor de textos, en el caso de Unix, este puede ser el llamado
vi que esta presente en la mayoría de las versiones de este sistema operativo, entonces se haría
de la siguiente manera:
vi <nombre_del_programa_c>
luego escribir el programa, salir del editor y compilarlo con el compilador cc u otro que posea
el sistema:
cc <nombre_del_programa_c>
Después de esto se genera el archivo ejecutable a.out, que se ejecuta:
a.out
ó
./a.out
(los símbolos "./" especifican en directorio actual)
Luego se podría cambiar el nombre del ejecutable con el comando mv de unix, esto
sería:
mv a.out <nombre_nuevo>
o compilar el programa con la opción -o:
cc
-o
<nombre_archivo_ejecutable>
Profesor: Roberto Uribe P.
<nombre_del_programa_C>
Pág. Nº 37
Programación de Computadores y el Lenguaje C
(versión 2003)
Ejemplo:
1) vi primero.c
2) cc primero.c
quedaría a.out
1) vi primero.c
2) cc -o primero primero.c
el ejecutable sería primero
Para el caso de los compiladores para computadores personales, estos vienen en
ambientes que integran un editor de texto y el compilador. En este caso para compilar y
enlazar se usa el menú del editor o teclas de función (Hot keys), esto último depende del
producto.
En la actualidad, todos o la mayoría de los compiladores de C soportan el estándar
ANSI de C. Dependiendo de la máquina donde se ejecute el compilador, se incluyen bibliotecas
aparte para diferentes procesos, como ser, bibliotecas con funciones para aplicaciones
gráficas, etc.
Estructura de un Programa
Cuando se escribe el programa se recomienda usar el siguiente formato:
llamadas a bibliotecas
declaración de funciones
declaración de variables globales
(prototipos de funciones)
main()
{
declaración de variables locales
sentencias
}
definición de funciones
Nota: Dentro de las sentencias también se pueden encontrar llamadas a funciones, las
funciones son escritas del siguiente modo:
func()
{
declaración de variables locales
sentencias
}
o también:
func(variables de paso)
declaración de variables de paso
{
declaración de variables locales
sentencias
}
A las variables de paso, se llamarán parámetros de la función. Sin embargo, las
funciones se tratarán más adelante.
Profesor: Roberto Uribe P.
Pág. Nº 38
Universidad de Magallanes - Departamento de Ingeniería en Computación
Bibliotecas
Por lo general las bibliotecas contienen funciones creadas por el programador o
entregadas con el producto. Por ejemplo las funciones de entrada y salida vienen especificadas
en la biblioteca de entradas y salidas estándar stdio.h (Standar Input Ouput Header). Cuando
desde el programa fuente se llama a una función que no esta escrita en dicho programa, al
compilar se mantiene el nombre y luego al enlazar con las bibliotecas se busca el nombre de la
función dentro de estas.
Para poder utilizar una biblioteca o archivo de cabecera se usa el símbolo # seguido de
la palabra reservada include, luego el nombre de la biblioteca, esto se hace al comienzo del
programa. Por ejemplo, para llamar a la biblioteca stdio.h se hace de la siguiente manera:
# include <stdio.h>
El nombre aparece entre menor y mayor (< >), esto significa que al enlazar buscará el
archivo en el directorio especificado para los archivos de cabecera, si el archivo aparece entre
comillas dobles (" "), entonces lo buscará en el directorio actual, es decir desde donde se
ejecuta el programa, por ejemplo:
# include "stdio.h"
sin embargo, stdio.h siempre se encuentra en el directorio donde están todas las bibliotecas, es
decir se utiliza <>.
Indentación
Este concepto se refiere a mantener un orden dentro de la programación, es decir para
que un programa sea ordenado y legible, entre otras cosas, este debe de ser modular y además
ser indentado, para revisiones futuras y mantención de los programas. Indentar es simplemente
ubicar en columnas diferentes los ciclos o estructuras internas a otras, por ejemplo:
#include <stdio.h>
main()
{
.
int i,suma=0;
.
i=0;
.
while(i<=10)
.
{
.
.
i=i+1;
.
.
.
.
suma=suma+i;
.
}
.
printf("\n
sumatoria=%d
}
\n" ,suma);
Identificadores
Estos son simplemente los nombres que pueden asumir variables o constantes dentro
del programa. Existen algunas restricciones para los identificadores:
- No deben ser iguales a nombres de palabras reservadas o bibliotecas.
Profesor: Roberto Uribe P.
Pág. Nº 39
Programación de Computadores y el Lenguaje C
(versión 2003)
- Deben comenzar con una letra o el caracter ‘_’.
- No deben tener en medio el caracter espacio.
Entonces, las variables o constantes comienzan con una letra o el caracter '_' y luego
pueden ser letras, dígitos o '_'.
Ejemplo de identificadores:
Contador
Conta
var1
_variable3_
Suma_total
Recuerde: en C se diferencia entre mayúsculas y minúsculas, por lo tanto, la variable
var1 es distinta a VAR1.
Comentarios
Un comentario es una cadena de caracteres que no es tomada en cuenta por el
compilador, esta va dentro de "/*" y "*/",
Ejemplo:
/*
esto es un comentario */
se utilizarán por lo general para documentar los programas, o sea para hacerlo más legible y
ordenado, o simplemente para referencias personales del programador, por ejemplo: para
indicar las últimas modificaciones hechas al programa, así el programador sabe que fue lo
último que hizo en el programa.
/* Esta es la ultima version, lo ultimo que hice fue:
Crear la funcion de ingreso de datos. La funcion de impresion de
resultados funciona en forma correcta.
Ya son las 3 de la mañana y aun no termino mi tarea.
*/
Puede parecer no necesario, pero generalmente los programadores tienen varias
versiones de sus programas, las cuales van modificando, hasta que logran terminar una en
forma completa y sin errores. Ocurre también que los programas pueden ser dejados de lado y
vueltos a retomar tiempo después, típicamente uno no se acuerda de las últimas modificaciones
hechas o de las cosas que funcionaban y de las que no.
Tipos de datos fundamentales
Los tipos de datos son los tipos de valores que puede tomar una variable, ya sea
numérico, caracter u otro. Por ejemplo, en C, el número 1 no es igual a 1,00; esto debido a que
el primero es un número entero y el segundo es un flotante (o número real).
Los tipos de datos fundamentales en C son:
Profesor: Roberto Uribe P.
Pág. Nº 40
Universidad de Magallanes - Departamento de Ingeniería en Computación
Tipo
char
short int
int
long int
unsigned char
unsigned short int
unsigned int
unsigned long int
float
long float
void
largo bytes(MS-DOS)
1
2
2
4
1
2
2
4
4
8
0
Nombre
caracter
entero corto
entero
entero largo
caracter sin signo
entero corto sin signo
entero sin signo
entero largo sin signo
flotante
doble o flotante largo
vacío
abreviado
short
long
unsigned short
unsigned
unsigned long
double
Por lo tanto, el nombre de cada tipo de dato es una palabra reservada.
Resumen Palabras clave
auto break case char continue default
do
double
else enum extern float for
goto if
int
long register
return short sizeof static struct switch typedef union unsigned
void
while.
Cuando recién se comieza a programar en C, se ocupan sólo algunas de estas.
Operadores
Son caracteres especiales que tienen un significado específico o determinado, estos
indican al compilador realizar operaciones matemáticas o lógicas.
Operadores Matemáticos
- menos unuario
- resta
+ suma
* producto
/ división
-- Decremento
++ Incremento
% módulo
= asignación
Precedencia (prioridad de los operadores matemáticos):
Operadores
- (unuario)
* / %
+ =
Profesor: Roberto Uribe P.
Asociatividad
++
--
derecha a izquierda
izq. a derecha
izq. a derecha
derecha a izq.
Pág. Nº 41
Programación de Computadores y el Lenguaje C
(versión 2003)
Operadores de Relación
Estos se utilizan dentro de proposiciones del tipo while, if, for; es decir, en estructuras
de control .
<
>
<=
>=
==
!=
:
:
:
:
:
:
menor que
mayor que
menor o igual que
mayor o igual que
igual a
distinto de (no igual a)
Operadores Lógicos
!
&&
||
: negación (unuario)
: y lógico
: o lógico
not
and
or
Prioridad:
!
<
==
&&
||
<=
!=
>
>=
Tabla de verdad para los operadores lógicos
! (not)
&& (and)
|| (or)
x
y
!x
x&&y
x||y
__________________________________________________
0
0
1
0
0
0
1
1
0
1
1
0
0
0
1
1
1
0
1
1
__________________________________________________
Existen estos mismos operadores (y algunos más) para trabajar a nivel de bit, pero
estos se verán después en un apartado.
Comenzando a Programar
Cada vez que se escribe una línea, esta debe llevar ";" al final, con lo cual el
compilador entiende que llega al final de una instrucción o proposición. Esto no ocurre en las
llamadas a Bibliotecas (#include) y en las definiciones (#define) o cuando es una sentencia
compuesta (una línea compuesta), es decir en el caso de un ciclo if, while, for, case, etc.
Declaración de variables
Antes de usar una variable dentro de un programa, es necesario indicarle al
compilador que va a ser usada, para ello se declara:
Profesor: Roberto Uribe P.
Pág. Nº 42
Universidad de Magallanes - Departamento de Ingeniería en Computación
Declaración:
<tipo> <lista_de_variables>;
tipo ::= es el tipo de dato a utilizar char, int, float, etc.
lista_de_variables::=<variable1[,variable2[,variable3........]]]>
la coma se usa para separar dos variables, lo anterior indica
que puede ir más de una variable.
Ejemplo:
main()
{
int conta;
char siono,primera_le;
int suma,promedio,i;
float exponencial;
.
.
.
}
/* se declara una variable entera */
/*ambas variables son de tipo caracter*/
/*se puede volver a repetir el tipo*/
/*variable de punto flotante */
Si una variable es de un tipo determinado, esto implica que puede tomar valores sólo
del tipo indicado. Por ejemplo, si una variable es del tipo int no puede asignársele valores con
decimales.
Recuerde:
es lo mismo
que
int i;
int j;
int k;
int i, j, k;
Asignación:
Como ya es sabido, una asignación es almacenar en una variable el resultado de una
operación o proceso. En "C" una asignación se representa como:
<variable> = <expresión>;
donde:
expresión ::= expresión operador_matemático expresión
o
expresión ::= constante|variable
Ejemplo:
a=5;
expo=max;
/* a la variable a se le asigna el valor 5 (constante)*/
/* a la variable expo se le asigna el contenido de la
variable max*/
expofin=ult+4;
/* a expofin se le asigna el contenido de la
variable ult sumada a la constante 4*/
Profesor: Roberto Uribe P.
Pág. Nº 43
Programación de Computadores y el Lenguaje C
(versión 2003)
Inicialización
Esta es una asignación que se utiliza para dar un valor de inicio a una variable.
Ejemplo:
main()
{
int i=0,j=0,k;
int suma;
k=1;
suma=(k+1)*200;/*la
variables*/
.
.
}
/*inicializacion en la declaracion i, j*/
/*inicializacion despues de ser declarada*/
inicializacion
puede
ser
hecha
usando
Otros ejemplos de asignaciones:
sumatoria=sumatoria*nota;
/*Expresion involucra multiplicación de
variables*/
/*expresion
es
una
constante
de
tipo
primela_le='A';
caracter*/
resultado=factorial(dato);
/*a resultado se le asigna el valor
retornado por la funcion factorial*/
El último ejemplo es más complejo, en este caso el valor que retorna de la función
factorial se asigna a la variable resultado. Funciones se verá en el próximo capítulo.
Entrada / Salida Estándar
A diferencia de otros lenguajes, no existen instrucciones de entrada y salida, para ello
existe una librería. La biblioteca (o archivo de cabecera) llamada stdio.h contiene todas las
funciones de entrada y salida estándar, entre ellas las funciones printf() y scanf(). La f
significa que es entrada o salida con formato.
printf()
Esta es la función de salida por pantalla, su formato es el siguiente:
printf(cadena_de_control,lista_de_argumentos)
La cadena de control comienza y termina con comillas dobles, dentro de estas va una
cadena de caracteres que puede incluir caracteres de control o formato. La lista de argumentos
se refiere a indicar las variables que serán mostradas o impresas.
Ejemplo:
/*
Mi primer programa */
/* primer.c */
#include <stdio.h>
main()
{
Profesor: Roberto Uribe P.
Pág. Nº 44
Universidad de Magallanes - Departamento de Ingeniería en Computación
printf("Este es el primer programa que entiendo");
return 0;
}
Nota: Este programa funciona correctamente, luego en Unix sería:
$ cc primer.c
$ a.out
Este es el primer programa que entiendo$
se compila (el nombre es primer.c)
se ejecuta
este es el resultado.
El símbolo $ es el prompt del sistema operativo Unix.
Notar que en el ejemplo no se utilizó lista de argumentos, la cadena de control sólo fue
usada para imprimir una cadena de caracteres (palabras).
Cuando se comienza a estudiar un nuevo lenguaje, lo primero que hay que aprender de
él es como imprimir algo en pantalla, el segundo paso es leer desde teclado.
Si se desea imprimir el valor de una variable se debe utilizar el formato adecuado. El
formato comienza con el caracter "%", luego le sigue el caracter de conversión, entre estos no
debe haber espacios en blanco. El caracter de conversión es aquel que indica cual es el tipo de
dato que se imprimirá, es decir, si es entero, caracter, etc.
Caracter de conversión
c
d
e
f
g
s
p
tipo
caracter
entero
número flotante en notación científica
punto flotante
con el formato e o f , es más corto
cadena de caracteres (string)
puntero
Si se le agrega la l o L a entero o flotante se indica que es long.
Ejemplo:
#include <stdio.h>
main()
{
int a=20,b;
int c=0 ;
b=50;
c=a*b;
printf("a * b = %d", c );
printf("\n a=%d y b=%d" , a, b );
return 0;
Cadena de Control
Lista de argumentos
Lista de argumentos
}
Cadena de Control
Profesor: Roberto Uribe P.
Pág. Nº 45
Programación de Computadores y el Lenguaje C
(versión 2003)
Para a, b y c se uso formato entero. En Unix, la salida de este programa sería:
a * b = 1000
a=20 y b=50
Los caracteres barra invertida (backslash) y n juntos, provocan un retorno de carro
(return) en pantalla. Los espacios dentro del printf también aparecen en pantalla (note el
primer espacio de la segunda línea).
Dentro de la función printf se usan caracteres de control (o secuencia de escape), para
ello se usa el caracter \ (barra invertida) seguida de algún otro caracter, por ejemplo, algunos
caracteres especiales se representan como:
caracter
valor ascii
nueva línea
\n
tabulador
\t
comillas
\”
retorno de carro
\r
apóstrofos
\’
barra invertida
\\
retroceso
\b
alimentación de forma \f
10
9
34
13
39
92
8
12
En los strings se usa el caracter nulo \0 para indicar el fin de la cadena de caracteres.
scanf()
Esta es la función para entrada estándar, se utiliza de un modo parecido a la anterior.
Ejemplo:
int numdato;
printf("ingrese el número de datos:");
scanf("%d",&numdato);
/*ingreso de numdato*/
Entonces utiliza formato, en este caso %d indica que se lee con formato entero. En el
ejemplo el caracter "&" se usa para indicar la dirección de memoria, es decir, "lee a un valor
con formato entero y lo guarda en la dirección de memoria numdato". Por ahora no se le dará
mucho énfasis al caracter &, hasta el capítulo de punteros, por ahora sólo es necesario saber
que para leer un int, char o float se utiliza dicho caracter.
tabla scanf()
caracter de conversión
d
o
x
u
Profesor: Roberto Uribe P.
tipo
Entero decimal
Entero Octal
Entero Hexadecimal
Entero Decimal sin signo
Pág. Nº 46
Universidad de Magallanes - Departamento de Ingeniería en Computación
e
f
c
s
nº de punto flotante
equivalente a e
caracter
cadena de caracteres (string)
Ejercicio Nº 2.1: Escribir un programa que lea 2 datos numéricos y que realice las
siguientes operaciones: suma, resta, multiplicación y división.
Para desarrollar este problema, tenemos que tomar en cuenta que se pide?, luego, que
necesitamos para hacer esto?. Entonces, sabemos que se nos pide leer dos datos de teclado e
imprimirlos en pantalla, es decir, necesitamos las funciones de entrada/salida scanf y printf.
Como sabemos, para utilizar dichas funciones debemos llamar a la biblioteca que las contiene,
en este caso stdio.h.
Por lo tanto la primera línea del programa debe ser:
# include <stdio.h>
luego de esto necesitamos escribir la función principal:
main()
{
la llave indica el principio de la función main. Ahora, como tenemos que leer 2 datos y hacer
cuatro operaciones, podemos crear 6 variables para guardar los datos y los resultados. Como
dentro de las operaciones está la división, esta puede arrojar un resultado con decimales, para
trabajar con decimales se ocupan los números reales que en C están representados por el tipo
float, entonces:
float dato1,dato2,suma,resta,mult,div;
Note que el nombre de las variables es representativo de lo que son o de para que se
usan, por ejemplo, la variable mult es para guardar la multiplicación, la div es para
almacenar la división, etc.... esa es una idea para hacer entendible un programa.
Después de esto, corresponde leer los datos. Para que el usuario del programa entienda
que hace, este debe indicarle que hacer,
printf("Ingrese primer numero:");
scanf("%f",&dato1);
printf("\n ingrese segundo numero:");
scanf("%f",&dato2);
Posteriormente se hacen las operaciones indicadas,
suma=dato1+dato2;
/* en la variable suma se almacena la suma de a y b
*/
resta=dato1-dato2;
mult=dato1*dato2;
div=dato1/dato2;
y finalmente la salida a pantalla.
printf("\n LA SUMA ES: %f", suma);
printf("\n LA RESTA ES: %f", resta);
printf("\n LA MULTIPLICACION ES: %f", mult);
Profesor: Roberto Uribe P.
Pág. Nº 47
Programación de Computadores y el Lenguaje C
(versión 2003)
printf("\n LA DIVISION ES: %f", div);
}
Lo importante por ahora es notar que todas la variables se declararon de tipo flotante
(float), esto para realizar la división con los mismos números ingresados. Sin embargo los
datos podrían haber sido int, en este caso se podría haber utilizado una conversión en el
momento de hacer la operación (cast) y convertir los datos enteros de entrada a flotante, por
ejemplo.
int A=10,B=4;
float f;
f=(float)A/(float)B;
Es decir, A y B se convierten a flotante para hacer la división entre flotantes y asignarla
a f. Esto es conocido como cast o conversión explícita.
La construcción de un programa debe estar pensada en usuarios no expertos, y por lo
tanto evitar cualquier ingreso erróneo, o estar preparados para ello. En nuestro programa, si
la variable dato2 es cero, la división se indetermina y ocurriría un error en tiempo de
ejecución, esto arrojaría un error como:
Arithmetic Execption
o
Devide Cero - Error
Lo adecuado sería preguntar ..... Es dato2 distinto de Cero??...., para ello se utilizan
las estructuras de control. En nuestro programa podríamos hacer dicha pregunta antes de
calcular la división, se haría con un if. Esto quedaría de la siguiente forma:
if (dato2==0)
/* Se pregunta "es dato2 igual a cero" */
printf("\n LA DIVISION ES INDETERMINADA");
else
{
div=dato1/dato2; /*el calculo de la division se hace aca y no antes*/
printf("\n LA DIVISION ES: %f", div);
}
El programa final sería:
# include <stdio.h>
main()
{
float dato1,dato2,suma,resta,mult,div;
printf("Ingrese primer numero:");
scanf("%f",&dato1);
printf("\n ingrese segundo numero:");
scanf("%f",&dato2);
suma=dato1+dato2; /* en la variable suma se almacena la suma de a y b */
resta=dato1-dato2;
mult=dato1*dato2;
printf("\n LA SUMA ES: %f", suma);
printf("\n LA RESTA ES: %f", resta);
printf("\n LA MULTIPLICACION ES: %f", mult);
if (dato2==0)
/* Se pregunta "es dato2 igual a cero" */
printf("\n LA DIVISION ES INDETERMINADA");
else
/* "de lo contrario" */
Profesor: Roberto Uribe P.
Pág. Nº 48
Universidad de Magallanes - Departamento de Ingeniería en Computación
{
div=dato1/dato2; /*el calculo de la division se hace aca y no antes*/
printf("\n LA DIVISION ES: %f", div);
}
return 0;
}
La sentencia if se verá en la siguiente sección.
El lenguaje C permite en la salida realizar la operación, es decir, en los mismos printf
podríamos haber realizados las operaciones, con esto nos evitamos declarar las variables que
almacenan los resultados de las operaciones. Por ejemplo, se pueden reemplazar los printf
anteriores por:
:
printf("\n LA
printf("\n LA
printf("\n LA
:
:
printf("\n
:
SUMA ES: %f", dato1+dato2);
RESTA ES: %f", dato1-dato2);
MULTIPLICACION ES: %f", dato1*dato2);
LA DIVISION ES: %f", dato1/dato2);
Note que para los printf y scanf se utilizó el formato para flotantes %f.
Profesor: Roberto Uribe P.
Pág. Nº 49
Programación de Computadores y el Lenguaje C
(versión 2003)
Problemas Resueltos y Propuestos
Acorde con lo introductorio del capítulo, los siguientes ejercicios permiten ejercitar la
sintaxis y notación del lenguaje C más que programar.
Resueltos:
1.Escriba un programa en C que permita realizar el cálculo del área de un triángulo,
para ello se deben ingresar los valores para la base y la altura.
2.-
Haga un programa que obtenga la raíz cuadrada de un número ingresado por teclado.
3.Cree un programa que calcule la derivada de un polinomio de máximo grado 4, asuma
que los exponentes son positivos.
Recuerde que para obtener la derivada de :
C×xn
C×n×xn-1
es
Ejemplo : Para obtener la derivada de
2 x4 + x3 + 2 x
el programa debería hacer:
Programa que calcula la derivada de un polinomio
ingrese
ingrese
ingrese
ingrese
ingrese
constante
constante
constante
constante
constante
de
de
de
de
de
x4
x3
x2
x1
x0
:
:
:
:
:
2
1
0
2
0
La derivada es : 8x3 + 3x2 + 0x + 2
4.Suponga un vehículo a una velocidad constante de 1.6 km./min. Haga un programa que
exprese la velocidad en km./hora y en m/seg.
Propuestos:
5.Escriba un programa al cual se le ingrese una temperatura en grados Celcius y haga la
conversión a grados Farenheit y Kelvin.
ºF = (1.8 * ºC) + 32
ºK = ºC + 273.2
Profesor: Roberto Uribe P.
Pág. Nº 50
Universidad de Magallanes - Departamento de Ingeniería en Computación
Soluciones:
1.Como resulta obvio para este ejercicio, debemos utilizar la librería de entrada y salida
estándar, además, declarar dos variables que almacenen la base y la altura del triángulo.
Recordando que la fórmula para el cálculo del área es: (base * altura) / 2; el programa
sería:
#include <stdio.h>
main()
{
float base, altura;
printf("\nPrograma que calcula el area de un triangulo\n");
printf("Ingrese base del triangulo : ");
scanf("%f",&base);
printf("Ingrese altura del triangulo : ");
scanf("%f",&altura);
printf("El area del triangulo es : %f",(base*altura)/2);
return 0;
}
Note que las variables fueron declaradas como flotantes (reales), esto para permitir el
uso de valores con decimales.
2.En este problema nos encontramos con algo nuevo, el cálculo de la raíz cuadrada. La
idea es utilizar todas la herramientas que nos provee el lenguaje, entonces, para este caso no se
pretende realizar el cálculo manual de la raíz, sino utilizar la función raíz cuadrada que viene
en la librería matemática de C.
La función llamada sqrt (de square root), permite calcular la raíz cuadrada de un
número, para ello recibe como parámetro un dato de tipo flotante o double. Además, hay que
incluir la librería matemática math.h.
Entonces:
#include <stdio.h>
#include <math.h>
main()
{
double dato;
printf("\nPrograma que calcula la Raiz Cuadrada de un numero\n");
printf("Ingrese un numero : ");
scanf("%lf",&dato);
printf("La raiz cuadrada de %lf es %lf ",dato,sqrt(dato));
return 0;
}
La instrucción sqrt(dato), retorna la raíz cuadrada de dato, es decir, un valor
numérico (float) que reemplaza la llamada a la función en la posición donde esta está.
¿Y que ocurre si el dato ingresado es negativo?. En este caso la función sqrt envía un
error a pantalla y retorna un 0. Por ejemplo, si el valor ingresado fuera -4, el programa se
comportaría de la siguiente manera:
Programa que calcula la Raiz Cuadrada de un numero
Profesor: Roberto Uribe P.
Pág. Nº 51
Programación de Computadores y el Lenguaje C
(versión 2003)
Ingrese un numero : -4
sqrt: DOMAIN error
La raiz cuadrada de -4.000000 es 0.000000
sin embargo, habría que determinar antes si es que el dato es negativo, pero esto se puede
resolver con las herramientas entregadas en el proximo capítulo (estructura if).
Nota: En el caso de utilizar el compilador cc de unix, debe utilizarse un parámetro
adicional para utilizar la librería matemática, si el programa fuente es raiz.c, el comando
para compilar sería:
cc raiz.c -lm
en el caso de los actuales compiladores para computadores personales, como ser Turbo C de la
Borland, no es necesario hacer esto.
3.Este problema podría parecernos difícil, sin embargo, está bastante acotado y por el
contrario, es sumamente sencillo. Existen dos formas de hacerlo, una es utilizando un ciclo que
nos permita leer las cinco constantes, otra es simplemente no utilizando ciclos, en ese caso
debemos declarar todas las variables (una por cada constante que se lea).
#include <stdio.h>
main()
{
int c4,c3,c2,c1,c0;
printf("\nPrograma que calcula la derivada de un polinomio de grado4.\n");
printf("ingrese constante de x4 :");
scanf("%d",&c4);
printf("ingrese constante de x3 :");
scanf("%d",&c3);
printf("ingrese constante de x2 :");
scanf("%d",&c2);
printf("ingrese constante de x1 :");
scanf("%d",&c1);
printf("ingrese constante de x0 :");
scanf("%d",&c0);
printf("\nLa derivada es :");
printf("%dx3 + %dx2 + %dx + %d",c4*4,c3*3,c2*2,c1);
return 0;
}
4.Para este problema, sabiendo que: 1 Kilómetro es igual a 1000 metros, 1 hora son 60
minutos y un minuto son 60 segundos, la operación es:
1.6 Km./min. = 1.6 * 60 Km./Hora
1.6 Km./min. = 1.6 *1000 m/min. = (1.6 * 1000) / 60 m/seg.
Podríamos hacer el programa para que funciones en forma general, es decir, que se
ingrese cualquier valor en Km./min. y convertirlo a lo solicitado:
Profesor: Roberto Uribe P.
Pág. Nº 52
Universidad de Magallanes - Departamento de Ingeniería en Computación
#include <stdio.h>
main()
{
float dato;
printf("\nConversion de Km./min. a Km./Hora y a m/seg. \n");
printf("Ingrese un valor (en Km./min.) : ");
scanf("%f",&dato);
printf("Convertido a Km./Hora es : %f \n", dato*60);
printf("Convertido a m/seg. es : %f \n",(dato*1000)/60);
return 0;
}
Profesor: Roberto Uribe P.
Pág. Nº 53
Programación de Computadores y el Lenguaje C
(versión 2003)
Capítulo Nº 3: Estructuras de Control
Las estructuras de control permiten cambiar la secuencia de las instrucciones, es decir
tomar una alternativa o ruta diferente dentro de un programa. En un programa, el control fluye
secuencialmente, es decir, toma una instrucción, la ejecuta y luego continua con la siguiente;
esto siempre y cuando no exista un cambio explícito del control. En el último ejemplo del
capítulo anterior, se vio como cambia el flujo de control por el uso de una proposición if.
En este capítulo se mostrará también el modo de empleo de los operadores de relación
y lógicos y su uso dentro de las proposiciones de control.
Estructura if
Esta estructura es de la siguiente forma:
if (expresión)
sentencia_1
sentencia_siguiente
Si la expresión es verdadera entonces se ejecuta la sentencia_1, luego se ejecuta la
sentencia_siguiente.
Si la expresión resulta ser falsa se ejecuta inmediatamente la sentencia_siguiente.
Recuerde: Una sentencia puede contener una sola línea, si tiene más de una se utiliza {
y }, las llaves indican que lo que está dentro de estas es parte de la sentencia.
Proposición if - else
No
if (expresión)
sentencia_1
else
sentencia_2
sentencia_siguiente
Es equivalente a
Si
expresión
sentencia_2
sentencia_1
sentencia_siguiente
Si la expresión es verdadera se ejecuta la sentencia_1, de lo contrario se ejecuta la
sentencia_2, luego se ejecuta la sentencia_siguiente (independiente cual de las dos sentencias
se ejecute, siempre se ejecuta la sentencia_siguiente).
Recuerde: una expresión dentro de una estructura de control puede contener
operadores de relación, lógicos o matemáticos.
expresión::= expresión operador_de_relación expresión
expresión::= expresión operador_lógico expresión
expresión::= expresión operador_matemático expresión
expresión::= variable|constante
La idea de lo anterior es indicar que dentro de la expresión pueden haber preguntas
compuestas, observe los siguientes ejemplos.
Profesor: Roberto Uribe P.
Pág. Nº 54
Universidad de Magallanes - Departamento de Ingeniería en Computación
Ej.:
if (dato>=50)
printf("\n APROBADO CON NOTA: %d", dato);
else
printf("\n REPROBADO");
printf("\n programa finalizado");
Ej.:
result=dato % 2; /*se asigna el valor obtenido a result*/
if (result==0)
/*luego se compara*/
printf("\n resultado par");
Otra forma sería:
if ((dato%2)!= 0) /*se hace dentro del if */
printf("\n dato impar");
Aquí la expresión fue:
(variable operador_matematico constante) operador_relacion constante
Ej.:
if (promedio>=30 && promedio<50) /*condicion con dos preguntas*/
printf("\n debe dar examen");
else
if (promedio>=50)
printf("\n Eximido");
else
printf("\n Reprobado sin derecho a Examen");
printf("\n Fin programa");
Para este ejemplo, si el promedio es menor que 50 y el promedio es mayor o igual a 30,
entonces, se imprimen el mensaje en pantalla debe dar examen. De lo contrario (quedan dos
alternativas que sea menor que 30 o mayor o igual a 50), vuelve a preguntar en otro if si
promedio es mayor o igual a 50, de lo contrario, y la única alternativa que resta es que sea
menor que 30. En este caso un if estaba dentro del else de otro if.
Ej.:
scanf("%d",&x);
if (x>=20 && x<=50 && x!=30) /*se uso el oper. logico y */
{
printf("\n x=%d", x);
printf("\n x esta entre 20 y 50, pero no es 30\n");
}
Observemos el comportamiento del if de este ejemplo, suponiendo que el dato ingresado
es 25.
x>=20
Verdadero
&&
&&
x<=50
Verdadero
&&
&&
x!=30
Verdadero
Es decir, el resultado sería verdadero y por lo tanto el control se mete dentro del if.
En C, un if entrega un valor distinto de 0 para verdadero e igual a cero para falso. Por
ejemplo, el siguiente if esta correcto, y siempre va a ser verdadero:
Profesor: Roberto Uribe P.
Pág. Nº 55
Programación de Computadores y el Lenguaje C
(versión 2003)
opcion=1;
if (opcion)
printf("\nEste if es verdadero");
Como la variable opcion es 1, entonces el if va a ser verdadero, es como preguntar:
if (opcion!=0)
Igualmente correcto es preguntar:
if (dato%2)
printf("\nEl dato es impar\n");
Es decir, si el resultado de dato%2 es distinto de 0, entonces el if es verdadero, lo que
indicaría que la variable dato es impar. Note que % es un operador matemático.
Estructura while (Mientras)
La estructura while o ciclo while nos permite repetir un proceso una cantidad
determinada de veces (mientras la expresión sea verdadera). Se escribe de la siguiente
manera:
while (expresión)
sentencia_1
sentencia_siguiente
La sentencia_1 se repite tantas veces como la condición (expresión) sea verdadera.
Ejercicios: Los siguientes problemas son clásicos en programación, para resolverlos
recuerde el modo de desarrollo de los ejercicios resueltos en el capítulo anterior.
Ejercicio Nº 3.1: Escriba un programa en C para el cálculo de xy, con x e y datos
conocidos.
Resolución: La idea entonces es multiplicar x y veces, como no sabemos el valor de y no
podemos hacer la operación en una sola instrucción. La solución a esto es usar un ciclo
repetitivo (hasta ahora el único conocido es el while), de tal manera que se ejecute y veces.
Profesor: Roberto Uribe P.
Pág. Nº 56
Universidad de Magallanes - Departamento de Ingeniería en Computación
# include <stdio.h>
main()
{
int cont=1,producto=1,x,y;
printf("Este programa calcula x elevado a y (con y entero positivo)");
printf("\n\nIngrese x (entero): ");
scanf("%d",&x);
printf("\n\nIngrese y mayor o igual a 0 : ");
scanf("%d",&y);
if (y<0)
printf("y es menor que 0 (aun no implementado)\n");
else
if (y==0)
printf("\n El resultado es: %d",producto); /* osea 1*/
else
{
while (cont<=y)
{
producto=producto*x; /* se multiplica el valor anterior de
producto por x */
cont=cont+1;
}
printf ("/n El resultado es: %d",producto);
}
return 0;
}
En este programa se utilizan todos los conocimientos adquiridos hasta ahora. Para
simplificar el problema, por ahora el cálculo se hace para exponentes positivos, sin embargo,
modificarlo para exponentes negativos no requiere mucho esfuerzo, al igual que para x real.
Para entender la solución hay que tener en cuenta varias consideraciones. La primera
es el uso de una variable auxiliar producto, la que mantendrá los valores acumulados de las
multiplicaciones sucesivas. Este se inicializa en 1. Como norma general para aplicaciones
matemáticas las variables que mantienen valores acumulados se inicializan, en el caso se
multiplicatorias con el elemento neutro multiplicativo, es decir, 1; para el caso de sumatorias,
el elemento neutro de la suma, osea, 0.
El uso de la variable auxiliar permite que el valor de la variable x no se modifique, si
esta se llegase a modificar, entonces el resultado del programa sería errado.
El contador cont, es para llevar la cuenta y terminar el ciclo cuando el número de
veces que se hizo la multiplicación sea y.
Como última acotación, note el indentado aplicado al programa, recuerde que esto
hace que el código fuente se vea ordenado y más fácil de entender (sobre todo los ciclos).
Ejercicio Nº 3.2: Realizar un programa que calcule el factorial de un número ingresado
por teclado.
Recuerde que:
0! = 1
1! = 1
n! = n*(n-1)! = n*(n-1)*(n-2)* ....... *(n-n)!
Profesor: Roberto Uribe P.
Pág. Nº 57
Programación de Computadores y el Lenguaje C
(versión 2003)
Resolución: Como primera cosa a recordar es incluir la biblioteca de entrada y salida
estándar (ya que debemos ingresar y mostrar datos en pantalla), luego incluir la función
main(). Otra cosa que debemos comenzar a mejorar es la interfaz con el usuario, es decir, que
los mensajes arrojados a pantalla sean adecuados y claros, además, en este caso prepararnos
para un ingreso erróneo, como ser, que el dato ingresado sea menor que 0.
Una solución podría ser:
# include <stdio.h>
main()
{
int cont=1,fact=1,num;
printf("Este programa calcula el factorial de un numero,");
printf("\n\nIngrese un numero entero mayor que cero :");
scanf("%d",&num);
if (num<0)
printf("ERROR, no se puede calcular el factorial de %d\n",num);
else
if (num==0)
printf("\n El Factorial de %d es: %d",num,fact);
else
{
while (cont<=num)
{
fact=fact*cont;
cont=cont+1;
}
printf("\n El factorial de %d es: %d",num,fact);
}
return 0;
}
Este programa podría fallar si el dato ingresado es muy grande, esto debido a que el
resultado puede ser superior al máximo número representable por un entero (en PC, 2 bytes).
Para ampliar el máximo valor se debe declarar la variable fact como long int o long.
Si se comparan los ejercicios anteriores (2 y 3) debemos notar que las soluciones son
similares, esto debido a que ambos cálculos se realizan de la misma forma. En el ejercicio 2 la
multiplicación se va haciendo con el valor original (x) y en el 3 con el contador(cont). Muchos
ejercicios matemáticos se resuelven de manera similar, sobre todo aquellos que involucran
series y sucesiones.
Revisemos el siguiente ejercicio.
Ejercicio Nº 3.3: Escribir un programa que calcule ex. Recuerde que:
ex = 1 + x1 + x2 + x3 + ...... + xn
1!
2!
3!
n!
x : valor conocido. Asuma n=10.
/* Programa que calcula e elevado a x */
# include <stdio.h>
main()
Profesor: Roberto Uribe P.
Pág. Nº 58
Universidad de Magallanes - Departamento de Ingeniería en Computación
{
float cont=1,fact=1,pot=1,exp=1,div;
/* exp fue inicializado en 1 para obtener el primer valor
de la sumatoria*/
float n=10,x;
printf("Calculo de e elevado a x");
printf("Ingrese un numero:");
scanf("%f",&x);
if (x<0)
printf("Aun no implementado");
else
{
while(cont<=n)
{
fact=fact*cont;
pot=pot*x;
div=pot/fact;
exp=exp+div;
cont=cont+1;
}
printf("\n El exponencial es: %f \n",exp);
}
return 0;
}
Este ejercicio se resolvió según el diagrama de flujo del capítulo Nº 1. Recuerde que
para verificar el comportamiento de un programa se puede ejecutar en papel, esto siempre y
cuando el programa no sea extenso.
Algo más del if
Observe los siguientes ejemplos, ambos están correctos,
1)
if (x==0)
if (y==0)
printf("\n ERROR");
else
printf("\n NO HAY ERROR");
2)
if (x==0)
if (y==0)
printf("\n ERROR");
else
printf("\n NO HAY ERROR");
Ambos funcionan y compilan correctamente, sin embargo, lo que hay que determinar es
a que if pertenece el else . Este es un problema de tipo semántico que se resuelve con la
siguiente regla: "un else se asocia con el if más cercano". Por lo tanto, el indentado del caso 1
es el más adecuado (el indentado es sólo una norma, no es obligatorio).
Existe una combinación para la estructura if, y es la siguiente:
if (expresión_1)
sentencia_1
else if (expresión_2)
sentencia_2
Profesor: Roberto Uribe P.
Pág. Nº 59
Programación de Computadores y el Lenguaje C
(versión 2003)
else if (expresión_3)
sentencia_3
.
.
.
.
else if (expresión_n)
sentencia_n
else
sentencia_por_defecto
sentencia_siguiente
En este caso sólo se ejecuta la proposición que sea verdadera, ninguna otra. En caso
que todas sean falsas, entonces se ejecuta la sentencia por defecto del else.
Algo más del while
Siendo C muy permisivo en algunas situaciones (por lo que muchas veces es o fue
criticado por sus detractores), tome en cuenta lo siguiente para la estructura while:
La función getchar() lee un caracter desde stdin (teclado).
Ej.:
char c;
c=getchar(); /* getchar()-->lee un caracter y luego es asignado a c */
Entonces un ciclo while puede ser de la siguiente manera:
while ((c=getchar())==' ' )
;
/* sentencia nula */
Esto es lo mismo que:
while ((c=getchar( ))==' ');
En este caso primero se lee un caracter por teclado, luego este caracter se asigna a la
variable c y finalmente c se compara con el espacio, si es igual (es verdadero) entonces se
repite el ciclo, es decir, el while no termina hasta que encuentre un caracter distinto de
espacio en blanco. Note que lo interesante es que la asignación se realizó en el while.
Otra manera de escribirlo es:
c=' ';
while (c==' ')
{
c=getchar();
}
Para el siguiente caso vea que es lo que sucede:
while (c=getchar()==' ');
Profesor: Roberto Uribe P.
Pág. Nº 60
Universidad de Magallanes - Departamento de Ingeniería en Computación
Nota: Como no existen los paréntesis, primero se hace la comparación y luego la
asignación, es decir a c se le asigna 0 ó distinto de 0, según el resultado de la comparación.
Esto ocurre debido a la prioridad de los operadores.
La opción inversa de getchar() es:
putchar(c);
/*envia a pantalla la variable que es de tipo caracter */
Para el caso de getchar y putchar, si c es declarado como int, también es válido,
esto porque se hace la conversión del dato char a su representación entera.
Por ejemplo, las siguientes sentencias están correctas:
putchar(‘m’);
Esta imprime el caracter m,
putchar(56);
imprimiría el caracter número 56 de la tabla o conjunto de caracteres en uso.
Estructura for
Cuando se conoce a priori la cantidad de veces que se tiene que repetir un proceso,
entonces, el ciclo while puede ser reemplazado por un ciclo for. Este se denota por:
for (expresión_1; expresión_2; expresión_3)
sentencia
sentencia_siguiente
La primera expresión es la inicialización de algún contador, la segunda es la condición,
es decir, mientras sea verdadera se ejecuta la sentencia que esta dentro del for, de lo contrario
se ejecuta la sentencia siguiente. La tercera corresponde al incremento del contador. Por
ejemplo:
for (i=1;i<=10;i=i+1)
printf("hola ");
printf("\nfueron muchos holas");
En este caso se imprime 10 veces hola, y al final fueron muchos holas.
La estructura for se basa en la estructura while, y lo anterior es equivalente a:
expresión_1;
while (expresión_2)
{
sentencia;
expresión_3;
}
sentencia_siguiente;
Profesor: Roberto Uribe P.
Pág. Nº 61
Programación de Computadores y el Lenguaje C
(versión 2003)
Ejercicio Nº 3.4: Escriba un programa que calcule la siguiente sumatoria, utilizando el
ciclo for:
n
x = ∑i
;
n : valor conocido
i =1
#include <stdio.h>
main()
{
int sumatoria=0,i,n;
printf ("\n Este programa calcula la sumatoria i de 1 a n");
printf ("\n\n Ingrese n: ");
scanf ("%d",&n);
for (i=1;i<=n;i=i+1)
sumatoria=sumatoria+i;
printf ("\n la sumatoria es: %d \n", sumatoria);
return 0;
}
El for puede haber sido hecho como:
i=1;
while (i<=n)
{
sumatoria=sumatoria+i;
i=i+1;
}
/* es lo mismo que sumatoria+=i; */
/* es lo mismo que i++; */
Para los incrementos y decrementos se pueden utilizar los operadores ++ y --. Es igual
escribir: i=i+1 que i++
Ejemplo del cálculo del factorial de n utilizando ciclo for.
for (cont=1;cont<=n;cont++)
fact=fact*cont;
/* fact*=cont; */
printf("\n El Factorial es: %d",fact);
Ciclo do-while
Con los ciclos vistos hasta ahora (for y while), la condición se analiza al comienzo del
bucle (otra forma de nombrar a un ciclo), para el caso del do-while, la condición se analiza al
final del bucle. Esto implica, que el ciclo se ejecutará por lo menos una vez. La forma general
de un ciclo do-while es
do
sentencia
while (condicion);
El ciclo do-while itera mientras la condición sea verdadera. En el siguiente ejemplo se
leen caracteres de teclado mientras sean distinto de ‘a’, ‘b’ y ‘c’.
Profesor: Roberto Uribe P.
Pág. Nº 62
Universidad de Magallanes - Departamento de Ingeniería en Computación
do
{
car=getchar();
printf("%d",car); /*imprimiria los valores ascii de los
caracteres leidos */
}
while (car!='a' && car!='b' && car!='c');
Proposición break
Esta proposición rompe el flujo normal de control. Break termina un ciclo o una
proposición switch.
while (1)
{
scanf("%d",&num);
if ((num%2)==0)
{
printf("\n este era par");
break;
}
}
/* break envia el control hasta esta linea */
En este ejemplo el while resulta ser siempre verdadero. Se va leyendo un número y se
termina el ciclo cuando el dato leído es par, esto por causa de la sentencia break.
Proposición Continue
La palabra reservada continue se usa de manera similar al break en un ciclo, es decir,
ignora todas las sentencias que siguen a la sentencia continue, sin embargo, no termina el
ciclo, no evítale incremento del contador o que la condición sea evaluada. Si la condición sigue
siendo verdadera, entonces el ciclo sigue iterando.
Proposición switch
La proposición switch tiene la función de un if anidado, esta es una sentencia de
selección múltiple (es como la proposición case en Pascal). En el switch se va comparando el
valor de la expresión con cada una de las constantes enteras que aparecen en las sentencias
case. La forma de esta es:
switch(expresión_entera)
{
case constante_entera1: sentencia;
[break;]
case constante_entera2: sentencia;
[break;]
case constante_entera3: sentencia;
[break;]
:
:
:
[default: sentencia]
/* optativo */
}
Profesor: Roberto Uribe P.
Pág. Nº 63
Programación de Computadores y el Lenguaje C
(versión 2003)
Cuando la expresión resulta ser igual a una de las constantes, entonces se ejecuta la
sentencia correspondiente hasta el break o hasta el final del switch. En caso contrario se
ejecuta la sentencia default, que es opcional.
Si existen más de una línea en la sentencia case no es necesario utilizar llaves. Por
último la proposición break también es opcional. Revisemos los siguientes ejemplos:
#include <stdio.h>
main()
/* cuenta cuantas a, e, i, o y u se han ingresado*/
{
char letra;
int conta=0,conte=0,conti=0,conto=0,contu=0,otros=0;
do
{
letra=getchar();
switch(letra)
{
case 'a' : conta++;
break;
case 'e' : conte++;
break;
case 'i' : conti++;
break;
case 'o' : conto++;
break;
case 'u' : contu++;
break;
default :
otros++;
/* es optativa */
}
} while(letra!=‘\t’);
return 0;
}
La expresión debe ser entera, sin embargo, si se utilizan constantes o variables de tipo
caracter, estas se convierten a sus valores enteros. Al término de cada case se usa el break para
terminar el switch, que lleva el control hasta la línea siguiente después del switch. El programa
termina cuando el caracter leído es un tabulador.
Se debe tomar en cuenta que a diferencia del if, en el switch sólo se puede comprobar
igualdad. Además, no deben haber dos sentencias case con constantes iguales dentro de un
mismo switch.
Ejemplo: Si quisiéramos contar las vocales (en general) leídas, el programa podría ser:
#include <stdio.h>
main()
/* cuenta cuantas vocales se han ingresado*/
{
char letra;
int contvocal=0,otros=0;
while (1)
{
letra=getchar();
switch(letra)
{
case 'a' :
case 'e' :
case 'i' :
Profesor: Roberto Uribe P.
Pág. Nº 64
Universidad de Magallanes - Departamento de Ingeniería en Computación
case 'o' :
case 'u' :
default :
contvocal++;
printf("%d",contvocal);
break;
otros++;
}
}
return 0;
}
En este ejemplo, si se lee una letra a, como no existe break, no termina el switch,
entonces el control pasa al case siguiente, y así hasta que llega a la sentencia donde se
incrementa la variable contvocal, y se ejecuta el break.En este ejemplo no fue necesario
colocar break en cada case.
Es importante notar que el while se ejecuta mientras no sea el final de la entrada EOF
(End of File). La entrada también podría haber sido un archivo. Por ejemplo, si crearamos un
archivo llamado prueba.txt, podríamos haber ejecutado el programa de la siguiente
manera :
a.out < prueba.txt
lo que hace que el while procese cada carácter contenido en prueba.txt.
Profesor: Roberto Uribe P.
Pág. Nº 65
Programación de Computadores y el Lenguaje C
(versión 2003)
Problemas Resueltos y Propuestos
Con los conocimientos adquiridos en este capítulo Ud. debe ser capaz de resolver en C
todos los ejercicios resueltos y propuestos del capítulo anterior.
Resueltos:
1.- Escriba un programa en lenguaje C que verifique si un número ingresado por teclado es o
no primo.
Recuerde que un número primo es aquel que sólo es divisible por 1 y por el mismo.
2.- Escriba un programa en C que realice lo siguiente :
- lea un número entero
- lea un operador matemático
- lea otro número entero.
luego debe realizar la operación indicada y arrojar el resultado por pantalla. Los operadores
matemáticos pueden ser de suma, resta, multiplicación y división.
Ej.:
ingrese un número entero
ingrese operador
ingrese el segundo número
: 5
: *
:10
El resultado de 5 * 10 es : 50
3.Opinión personal: Con los conocimientos (herramientas) que Ud. tiene hasta ahora,
hacer el siguiente programa lo considera: fácil, complicado, realmente difícil. Porqué?
- leer cuatro números enteros
- imprimirlos en orden ascendente (de menor a mayor).
4.- Realizar un programa que lea un número hexadecimal y lo convierta a su representación en
base 10.
- Restricciones : No usar ninguna función conocida para la conversión, sólo la supuesta
más adelante.
El dígito que se leerá tiene que ser de tipo char.
- Recuerde que :
13C(16) ----> 316(10)
- ya que :
1*162+3*161+12*160
256 + 48 + 12
316
Profesor: Roberto Uribe P.
Pág. Nº 66
Universidad de Magallanes - Departamento de Ingeniería en Computación
- Para facilitar la solución: lea la cantidad de dígitos que posee el número. Luego lea
los dígitos uno a uno.
Ejemplo :
ingrese cantidad de dígitos: 3
ingrese dígito 1 : 1
ingrese dígito 2 : 3
ingrese dígito 3 : C
El número en base 10 es : 316
- Supuesto : Existe una función llamada convcharaint(dato) que transforma un dato
tipo char a int, ejemplo:
datochar='1';
datoint=0;
datoint=convcharaint(datochar);
/* el datoint resulta igual a 1 */
5.En C la entrada y salida estándar puede ser dirigida a archivo, esto simplemente
usando los caracteres < o >. Por ejemplo, supongamos que se tiene un programa que lee 3
caracteres y los va imprimiendo en pantalla. El ingreso de los caracteres puede haber sido
hecha con getchar() o scanf(). Si en vez de hacer la entrada por teclado, se crea un archivo
de texto que contiene los tres caracteres (suponga “abc”) y luego el programa se ejecuta de la
siguiente manera:
$a.out<arch
a
b
c
$
Cree un programa que lea caracteres desde stdin e imprima el número de vocales leídas
y el número de líneas que tenía el archivo. Un archivo termina cuando el caracter leído es la
constante EOF.
Propuestos:
6.Durante la última Gran Guerra, un grupo de ingenieros del área informática fue
contratado por una de las partes en conflicto para investigar y decodificar los mensajes en
clave que se enviaba el enemigo. Dichos mensajes, eran una serie de caracteres entre letras y
números.
Durante un periodo considerable de tiempo, estos investigaron la posible solución. Una
vez que llegaron a ella, se dieron cuenta que los dígitos que contenía el mensaje correspondían
a letras del mensaje real, esto último según la siguiente tabla:
M
0
Profesor: Roberto Uribe P.
U
1
R
2
C
3
I
4
E
5
L A
6 7
G
8
O
9
Pág. Nº 67
Programación de Computadores y el Lenguaje C
(versión 2003)
Es decir, para la siguiente frase en clave:
H9Y S5 39N934529N 69S P240529S 5X404D9S D56 2709
Su traducción es:
Hoy se conocieron los primeros eximidos del ramo
Entonces, se decidió crear un programa para hacer mas rápido el proceso de
decodificación. Asuma que el ingreso es caracter a caracter, es decir, considerando lo
explicado en el ejercicio anterior es posible leer los datos desde un archivo y direccionar la
entrada con el símbolo menor.
7.- Para el siguiente programa:
# include <stdio.h>
main()
{
int i=1;
int n;
float S=0.0,x;
printf("\nIngrese n :");
scanf("%d",&n);
printf("\nIngrese x :");
scanf("%f",&x);
while(i<=n)
{
S=S+x/(float)i;
i=i+1;
}
printf("\n\nS es : %f",S);
return 0;
}
a.- Indique línea a línea, que proceso se va realizando, esto en forma clara.
b.- Para n=10 y x=100, obtenga el valor final para S y i en cada ciclo (el valor puede
quedar expresado).
c.- Obtenga la fórmula matemática.
8.Haga un programa que obtenga los 5 primeros números perfectos. Un número perfecto
es aquel número positivo que es igual a la suma de los enteros positivos (excluyendo el mismo)
que son divisores del número.
Ejemplo : El primer número perfecto es el 6 ya que sus divisores son el 1, 2 y 3 y la
suma de estos 1 + 2 + 3 = 6.
Soluciones:
1.Lo primero es recordar que un número primo es aquel que sólo es divisible por 1 y por
el mismo. Entonces, una de las formas de resolver este problema, luego de ingresado el dato, es
Profesor: Roberto Uribe P.
Pág. Nº 68
Universidad de Magallanes - Departamento de Ingeniería en Computación
recorrer con un contador desde 2 hasta el dato-1 y tener un otro contador que se vaya
incrementando cada vez que la división de dato por el primer contador sea entera..
#include <stdio.h>
main()
{
int num,i=2,cont=0;
printf("\nPrograma que verifica si un numero es o no primo.\n");
printf("Ingrese un numero : ");
scanf("%d",&num);
if (num<=0)
printf("Error, el numero debe ser mayor que cero.\n");
else
if (num==1)
printf("El numero %d es primo.",num);
else
if (num==2)
printf("El numero %d es primo.",num);
else
{
for(i=2;i<num;i++)
if ((num%i)==0)
cont++;
if (cont>0)
printf("El numero %d no es primo.",num);
else
printf("El numero %d es primo.",num);
}
return 0;
}
Observe que el for va de 2 a num-1, esto ya que de otro modo cont se incrementaría al
hacer el módulo por 1 y num.
2.#include <stdio.h>
main()
{
int num1,num2,oper;
printf("\nPrograma que realiza las 4 operaciones basicas.\n");
printf("ingrese un número entero
:");
scanf("%d",&num1);
printf("ingrese operador
:");
scanf("%c",&oper);
printf("ingrese el segundo número
:");
scanf("%d",&num2);
printf("\nEl resultado de %d %c %d es :",num1,oper,num2);
if (oper=='+')
printf("%d",num1+num2);
else
if (oper=='-')
printf("%d",num1-num2);
else
if (oper=='*')
printf("%d",num1*num2);
else
/* solo falta la division*/
if (num2!=0)
printf("%d",num1/num2);
else
printf("La division se indetermina);
return 0;
}
Profesor: Roberto Uribe P.
Pág. Nº 69
Programación de Computadores y el Lenguaje C
(versión 2003)
Una forma más sencilla de construir este programa es usando la instrucción switch, la
parte principal del programa quedaría:
switch(oper)
{
case '+': printf("%d",num1+num2);
break;
case '-': printf("%d",num1-num2);
break;
case '*': printf("%d",num1*num2);
break;
case '/': if (num2!=0)
printf("%d",num1/num2);
else
printf("La division se indetermina);
break;
default: printf("operacion no reconocida"); /* en caso de no
ser ninguna de las anteriores*/
} /*fin switch*/
3.A simple vista podría parecer fácil ordenar 4 números, sin embargo, no es tan sencillo.
La dificultad radica principalmente en que la cantidad de comparaciones que se tienen que
hacer son un mínimo de 4!, es decir, 24. Esto, ya que tendríamos que crear 4 variables e ir
comparándolas entre ellas. En si, el problema resulta mas engorroso que difícil. En capítulos
posteriores cuando se aprenda el concepto de arreglos podremos aprender como resolver este
tipo de problemas sin tanta complicación.
4.Los programas para cambio de base de un número son clásicos en programación, en
este caso, como los números hexadecimales usan caracteres es necesario declarar de tipo char
los dígitos que se ingresarán. También se indica que existe una función que permite convertir
un char a int. Como aún no manejamos funciones sólo debemos recordar que un función va a
retornar un valor, que se puede utilizar como un dato cualquiera (en este caso un dato int). Si
el dato ingresado a la función es A, retorna 10, si es B, 11, etc.
Otra cosa a tomar en cuenta, es que la cantidad de dígitos es variable, entonces,
debemos utilizar un ciclo que nos permita leer todos los dígitos.
#include <stdio.h>
main()
{
int i=0,numdig=0,valor=0,digint;
char digchar;
printf("\nPrograma que transforma un numero Hexadecimal a Decimal.\n");
printf("Ingrese cantidad de digitos : ");
scanf("%d",&numdig);
if (numdig<=0)
printf("Error, el numero debe ser mayor que cero.\n");
else
for(i=1;i<=numdig;i++)
{
printf("Ingrese digito %d :",i);
scanf("%c",&digchar); /*se lee un char*/
digint=convcharaint(digchar);
/*convierte el char a int*/
pot=1;
/* Es necesario que pot se inicialice cada vez en 1*/
for(j=1;j<=numdig-i;j++)
/*calculo de la potencia de 16*/
pot=pot*16;
valor=valor+digint*pot;
}
Profesor: Roberto Uribe P.
Pág. Nº 70
Universidad de Magallanes - Departamento de Ingeniería en Computación
printf("El numero en base 10 es : %d.",valor);
return 0;
}
Observe que en el cálculo de la potencia, el for llega hasta numdig-1, esto ya que si son
n dígitos, entonces el primero se multiplica por 16n-1 y el último por 160.
Algunos cambios que se pueden hacer son:
for(i=1;i<=numdig;i++)
{
printf("Ingrese digito %d :",i);
scanf("%c",&digchar); /*se lee un char*/
for(j=1,pot=1;j<=numdig-i;j++)
pot=pot*16;
valor=valor+convcharaint(digchar)*pot;
}
En este caso dentro del for se inicializaron ambas variables. Además, la función se
llamo en la misma operación, de esta manera se evita usar una variable (digint).
Lo último que se tiene que considerar, es que este programa no compilaría dado que la
función convcharaint no existe, en el próximo capítulo estaremos en condiciones de poder
implementar dicha función.
5.Como se mencionó en el encabezado de la pregunta, es posible que la entrada pueda
ser a través de teclado o bien un archivo, para ello sólo hay que usar el símbolo menor al
momento de ejecutar el programa. Para este ejercicio supongamos que creamos un archivo en
un editor de texto cualquiera, en Unix podría ser el conocido vi, en DOS el edit o el mismo
editor que trae el compilador de C con el que se esta trabajando (por ejemplo el Turbo C de la
Borland trae su propio editor).
#include <stdio.h>
main()
{
int car,cuentavoc=0,cuantalin=0,cuentatab=0;
car=getchar();
while (car!=EOF)
{
switch(car)
{
case 'a':
case 'e':
case 'i':
case 'o':
case 'u': cuentavoc++;
break;
case '\t': cuentatab++;
break;
case '\n': cuentalin++;
break;
} /*fin switch*/
car=getchar();
}
printf("\nNumero de Vocales : %d",cuentavoc);
printf("\nNumero de Tabuladores : %d",cuentatab);
printf("\nNumero de Lineas : %d",cuentalin);
return 0;
}
Profesor: Roberto Uribe P.
Pág. Nº 71
Programación de Computadores y el Lenguaje C
(versión 2003)
Capítulo Nº 4 : Funciones en C
El lenguaje "C" esta creado a partir de funciones y su programación se hace a través
de ellas, estas facilitan la estructuración y el entendimiento de los programas, "dividir para
reinar".
Una función puede realizar un proceso simple o complejo, como ser el cálculo de
factorial, raíz cuadrada, determinante de una matriz, lectura de datos, procesos con punteros,
etc...
Una función consta de un encabezado y un cuerpo. Todas las funciones devuelven un
valor o un valor vacío (void), este valor especifica el tipo de dato de la función, el cual se tiene
que especificar, si no es así se asume entero.
Ej.:
mi_primera_func()
{
printf("\n\n esta sera una de las muchas");
printf("\n pero muchas, funciones que se");
printf("\n haran en este entretenido");
printf("\n ramo");
/* la ausencia de acentos es porque la funcion esta hecha para
correr en Unix*/
}
Para la función anterior el tipo de dato no se especificó por lo tanto es int, es decir el
encabezado podría haber sido:
int mi_primera_func()
{
}
El cuerpo de la función va entre llaves. La función anterior no devuelve un valor sólo
realiza un proceso.
Una función puede ser llamada desde la función principal main() o de cualquier otra.
La llamada se realiza colocando el nombre en el lugar donde, se desea que se ejecute la
función. La función tiene que ir declarada antes del main y se puede escribir después de la
función principal, esto último no es obligatorio, pero es una buena norma para programar.
Ejemplo:
# include <stdio.h>
int mi_segunda_func();
Declaración de la función
main()
{
printf("\n esto fue escrito antes de la funcion...");
mi_segunda_func();
printf (" \n la función ya se ejecuto");
return 0;
}
int mi_segunda_func()
Definición de la función
{
printf("\n\n Estoy dentro de la funcion");
}
La salida de este programa sería:
Profesor: Roberto Uribe P.
Pág. Nº 72
Universidad de Magallanes - Departamento de Ingeniería en Computación
esto fue escrito antes de la función...
Estoy dentro de la funcion
la función ya se ejecuto
A diferencia de otros lenguajes no existen procedimientos propiamente tal (procedure
en Pascal o subroutine en Fortran). Sin embargo las funciones en C pueden cumplir este
papel. Además existen funciones de tipo void (vacío) que no devuelven valores. Para el caso
anterior la función podría haber sido void o sea:
void mi_segunda_func()
Instrucción return
Esta es la expresión que permite devolver el valor de la función hacia donde es
llamada, además de terminar la ejecución de dicha función.
Ejemplo:
int suma()
{
int A, B;
int sum;
A=5;
B=8;
sum=A+B;
return(sum);
}
/* declaraciones de variables locales */
Esta función nos permite ver el funcionamiento del return. El valor que retorna la
función debe ser del mismo tipo con que fue declarada la función. El resto del programa sería:
#include <stdio.h>
int suma();
/* Global */
main()
{
int result;
result=suma();
printf("\n suma es: %d",result)
return 0;
}
/*imprimiria 13*/
/* aqui deberia ir escrita la funcion */
En la sentencia result=suma(); a la variable result se le asigna el valor retornado
de la función suma(). El nombre de la función es reemplazada por el valor que ella retorna,
entonces también es válido hacer:
result=10+suma();
Lo que implicaría que result tendría el valor 23.
Consideraciones :
Profesor: Roberto Uribe P.
Pág. Nº 73
Programación de Computadores y el Lenguaje C
(versión 2003)
-Las dos primeras funciones (mi_primera_func y mi_segunda_func) no
contenían la intrucción return, esto no ocasiona problemas debido a que no retornan valor.
Sin embargo, se acostumbra a colocar return. Algunos compiladores podrían enviar una
advertencia al no encontrar return en una función.
-La función principal main() tiene forma de función, sin embargo, técnicamente
no lo es, por lo tanto es ilegal hacer llamadas a ella. Los programas que invocan a main()
funcionan a veces, esto dependiendo de la implementación ya que algunas si tienen a main()
como función normal.
Variables Globales y Locales
Variables Globales: Estas se declaran fuera de las funciones, antes del main o en otros
archivos y pueden utilizarse durante todo el programa y en cualquier parte de este, incluso en
las funciones.
Variables Locales: Estas se declaran dentro de bloques o funciones y permanecen
activas solamente en ese ámbito.
Ejemplo de variables locales:
float raizcua()
{
int i, j,
float raizfi;
:
:
}
/*funcion que retorna un valor flotante*/
/*variables locales*/
Para este ejemplo, las variables i, j y raizfi son locales a la función raizcua, es
decir, dichas variables comienzan a existir cuando se llama a la función y desaparecen cuando
esta termina. Estas variables no pueden ser accesadas directamente desde otras funciones, así
como desde la función raizcua no se puede tener acceso a variables declaradas en otras
funciones. Además, es importante saber que dichas variables no conservan su valor, o sea, si la
función es nuevamente llamada, entonces son de nuevo creada, y por lo tanto inicialmente
contienen un valor basura. A estas variables se les llama automáticas.
Tome en cuenta el siguiente ejemplo:
#include <stdio.h>
int func1();
float func2();
int var1=0;
/*variable global*/
main()
{
int i=0;
:
:
}
Profesor: Roberto Uribe P.
/*variable local a main*/
Pág. Nº 74
Universidad de Magallanes - Departamento de Ingeniería en Computación
int func1()
{
int i=0;
:
}
float func2()
{
int var1=0;
:
}
/*variable local a func1*/
/*variable local a func2*/
Quizás con lo anterior podríamos tener alguna confusión, esto provocado porque tanto
en la función main como en func1 existe una variable del mismo nombre, es más, existe una
variable global llamada var1 que también esta declarada en func2.
Como se dijo anteriormente las variables locales no pueden ser accesadas
directamente desde otras funciones, por lo tanto, las variables i del main e i de func1 son
distintas, si se llama a func1 desde el main se crea la variable i de func1 y la otra variable (la
del main) no existe dentro de esta función.
Un caso similar es con var1, como es global puede ser llamada de cualquier parte, por
ejemplo desde func1 o main. Sin embargo, dentro de func2 la variable activa es la local, por
lo tanto var1 global se desactiva dentro de esta función. Sin embargo, var1 global no pierde su
valor, sólo se desactiva momentáneamente (por el periodo que dura func2).
Es posible también encontrar declaraciones de variables locales en bloques internos
dentro de alguna función, por ejemplo :
:
main()
{
int cont=0, suma=0;
:
{
float mult=1, div=0;
:
:
}
:
}
Para estos casos, el bloque interno se crea con { y se termina con }, las variables
anteriores mult y div sólo existen dentro del bloque.
Llamadas a Funciones
Debemos recordar que una función puede ser llamada una o más veces y desde
cualquier otra función, así como también se pueden llamar a muchas funciones dentro de una
misma función.
Una manera de ejemplificar esto, es a través del siguiente esquema:
Profesor: Roberto Uribe P.
Pág. Nº 75
Programación de Computadores y el Lenguaje C
(versión 2003)
int multinum()
# include <stdio.h>
double eelevx();
long factorial();
long potencia();
int multinum();
main()
{
:
:
:
long factorial()
double eelevx()
}
long potencia()
Se asume que el programa posee cuatro funciones, una que calcula ex, otra el factorial
de un número, otra un número cualquiera elevado a otro y otra (multinum) que realiza
cualquier otra operación. Entonces, vemos que desde el programa principal (main) se llama a
multinum, a factorial y a eelevx, sin embargo desde esta última también se llama a
factorial, además de potencia..
La idea de las funciones es desarrollar programas aplicando el Modelo Top-Down, es
decir, ir dividiendo el problema en partes más pequeñas y estas a su vez en otras más chicas,
esto hasta que se encuentren subproblemas posibles de resolver, luego ir uniendo las
soluciones a estos subproblemas, hasta permitir resolver el problema completo (problema
original). Es por ello que se habla de dividir para reinar o dividir para conquistar, es más fácil
resolver cualquier problema si este se divide en pequeñas partes que puedan ser manejables.
Se debe tener cierto cuidado con las llamadas a funciones, ya que podrían ocurrir
problemas tales como ciclos o loops infinitos, estos provocados porque no existe un adecuado
término de la función. Observe el siguiente ejemplo:
func1()
main()
func2()
func3()
Profesor: Roberto Uribe P.
Pág. Nº 76
Universidad de Magallanes - Departamento de Ingeniería en Computación
El esquema anterior podría provocar un loop infinito si no se determina correctamente
el final de alguna de las funciones. Esto también podría ocurrir en las llamadas recursivas.
func1()
main()
Las funciones recursivas son aquellas donde existe una llamada a si misma. Las
llamadas recursivas a funciones son muy utilizadas en programación y dan un aspecto mas
entendible a esta. El siguiente ejemplo muestra una función que se llama a si misma,
int funrec()
{
funrec();
return 0;
}
sin embargo para este ejemplo, como antes del return se llama a si misma, nunca alcanza la
instrucción de término, por lo tanto queda en un ciclo infinito.
Paso de Parámetros a funciones
Una variable global permanece activa durante todo el periodo de ejecución de un
programa, por lo tanto, el espacio de memoria que ocupa dicha variable permanece ocupada
hasta que no termina el programa. Si se ocupan demasiadas variables de este tipo es posible
que en algún momento se acabe la memoria RAM, sobre todo en computadores personales (a
pesar que ahora estos traen la suficiente). El uso excesivo de variables globales atenta contra
la modularidad del programa y lo hace más difícil de entender, esto último, ya que las
funciones terminan amarradas a las variables del caso, lo que limita su reusabilidad.
Una manera de evitar el exceso de variables globales es pasando parámetros a las
funciones.
Ejemplo: El siguiente programa incluye una función calcula el factorial de un número
cualquiera:
Profesor: Roberto Uribe P.
Pág. Nº 77
Programación de Computadores y el Lenguaje C
#include <stdio.h>
long int factorial(int n);
main()
{
int datoin;
long result;
printf("\n ingrese número: ");
scanf("%ld",&datoin);
result=factorial(datoin);
printf("\n el factorial es: %ld", result);
}
long int factorial(int n )
{
long fact=1 ;
int cont=1;
for (cont=1;cont<=n;cont++)
fact=fact*(long)cont;
return(fact);
}
(versión 2003)
Parámetro ingresado a la
función en la llamada
Parámetro de la función
Declaración del
parámetro ingresado
En la definición de la función se incluyó un parámetro, entonces, este debe ser
declarado, esto se puede hacer en la línea siguiente antes de la llave que indica el comienzo de
la función. Como a la función se le pasa un parámetro, entonces, al llamarla también se debe
incluir un parámetro.
Lo que ocurre en el ejemplo es: desde el main se llama a la función factorial, a la
cual se le pasa el parámetro datoin. En términos formales, se le pasa el valor (o contenido) de
la variable datoin. La variable n de la función factorial asume el valor que fue ingresado a
la función. Una vez que se realiza el proceso de cálculo del factorial, este retorna el resultado
almacenado en fact hacia donde es llamada la función. En el main el valor retornado por la
función es asignado a la variable result.
La parte esencial del programa, el calculo del factorial, es igual a los ejemplos
anteriores, sin embargo, ahora esta incluido dentro de una función, lo que le otorga una mayor
modularidad al programa. De esta manera para calcular el factorial de otro número no se
tiene que modificar la función.
En el ejemplo, no es necesario asignar a una variable el resultado arrojado por la
función, de esta manera nos ahorraríamos una variable,
printf("\n el factorial es: %ld",factorial(datoin));
Si se pasa más de un parámetro a la función se deben separar por coma y dentro de
esta deben estar todos declarados.
Existen dos formas de pasar parámetros a funciones, por valor (ejemplo anterior) o por
referencia, esta última se verá más adelante en el capítulo de punteros.
Paso de variables por valor
Cuando se dice que una variable ha sido pasada por valor, quiere decir, que su
contenido no se modifica dentro de la función. Para entender esto, estudiemos el siguiente
ejemplo:
Profesor: Roberto Uribe P.
Pág. Nº 78
Universidad de Magallanes - Departamento de Ingeniería en Computación
#include <stdio.h>
xxfun(int bli);
main()
{
int i;
i=5;
printf("\nAntes de la funcion i es igual a: %d",i);
xxfun(i);
printf("\nDespues de la funcion i es igual a: %d",i);
}
xxfun(int bli)
/* la variable bli asume el valor con */
{
/*la cual es llamada la función */
bli=bli+10;
printf("\nDentro de la funcion bli es: %d",bli);
}
/* función xxfun() no retorna valores */
La salida de este programa es:
Antes de la funcion i es igual a: 5
Dentro de la funcion bli es: 15
Despues de la funcion i es igual a: 5
La función xxfun() no logra modificar la variable de entrada i. El parámetro bli
toma el valor de entrada (es decir 5), se incrementa en 10, pero es la variable bli la que se
modifica y no i del main. Una vez que termina la función la variable bli deja de existir, si se
llamara de nuevo, tomaría el nuevo valor de entrada a la función, cualquiera sea este.
Para complicar un poco más el asunto, supongamos el siguiente ejemplo:
#include<stdio.h>
int prueba(int dato);
main()
{
int dato=5,i=6,resultado=0;
printf("\n dato=%d, i=%d, resultado=%d",dato,i,resultado);
resultado=prueba(dato);
printf("\n dato=%d, i=%d, resultado=%d",dato,i,resultado);
}
int prueba(int dato)
{
int i=1;
dato++;
printf("\n dato=%d, i=%d",dato,i);
return(dato);
}
La salida al programa es:
dato=5, i=6, resultado=0
dato=6, i=1
dato=5, i=6, resultado=6
¿Porqué?. En la primera línea no hay problema, son los valores iniciales de las
variables declaradas en la función principal. En el segundo printf (el de la función), i es una
variable local de la función prueba, que es distinta a el i de main. La variable dato de la
Profesor: Roberto Uribe P.
Pág. Nº 79
Programación de Computadores y el Lenguaje C
(versión 2003)
función prueba también es local (y por lo tanto distinta a dato de main), y en este caso no
importa que tenga el mismo nombre del parámetro ingresado a la función, por lo tanto esta
recibe el valor 5 y se incrementa en 1 (osea 6). Para el último printf las variables dato e i no
se modifican, la variable resultado cambia su contenido ya que se le asigna el valor
retornado de la función (6). Por último, dentro de la función prueba no se puede imprimir la
variable resultado, ya que esta no esta declarada y tendríamos un error de compilación.
Ejercicio Nº 4.1: Hacer una función que calcule el máximo entre 2 números enteros:
La idea es que se le ingrese dos parámetros y la función retorne el mayor, por lo tanto
no debemos ni leer de teclado ni imprimir en pantalla. Para saber cual de dos números es
mayor, sólo debemos compararlos, entonces la parte principal del programa podría ser:
declarar una variable que guarde el mayor
int num_max;
luego hacer la comparación entre los dos datos ingresados, supongamos dato1 y dato2, es
decir,
if (dato1>dato2)
num_max=dato1;
else
num_max=dato2;
y listo!. Entonces, la función completa sería:
int maxnum(int dato1,int dato2)
{
int num_max;
if (dato1>dato2)
num_max=dato1;
else
num_max=dato2;
return(num_max);
}
Para hacer más eficiente nuestra función, podemos ahorrarnos la variable num_max y
haber hecho:
int maxnum(int dato1,int dato2)
{
if (dato1>dato2)
return(dato1);
else
return(dato2);
}
Es importante indicar en este punto, que el objetivo de crear funciones es para que ellas
realicen sólo lo necesario (ojalá una sola cosa), de esta manera se simplifican los problemas y
se facilita la reutilización de dichas funciones.
Profesor: Roberto Uribe P.
Pág. Nº 80
Universidad de Magallanes - Departamento de Ingeniería en Computación
Ejercicio Nº 4.2: Crear un programa que lea números enteros y que se obtenga el
mayor de la lista ocupando la función maxnum del ejemplo anterior. El programa debe terminar
cuando la suma de los valores absolutos de los datos leídos sea superior a 1000.
Entonces, como ya es sabido debemos incluir la biblioteca de entrada y salida, declarar
la función maxnum y comenzar con la función principal. Luego, debemos declarar una variable
que mantenga el máximo, la suma, y una variable que contenga los datos que vamos a ir
leyendo desde teclado. Como no sabemos la cantidad de datos a leer podemos utilizar un ciclo
while e ir leyendo cada dato dentro de este. Para la suma, sabemos que debemos hacer suma
igual a suma más el dato que leamos, pero si el dato es negativo, entonces, suma debe ser igual
a suma menos el dato.
#include <stdio.h>
int maxnum();
main()
{
int suma=0,num,max;
printf("\nIngrese un dato: ");
scanf("%d",&num);
max=num;
if (num<0)
/*para el valor absoluto*/
suma=suma-num;
else
suma=suma+num;
while (suma<=1000)
{
scanf("%d",&num)
max=maxnum(max,num);
if (num<0)
suma=suma-num;
else
suma=suma+num;
}
printf ("\nEl maximo numero leido fue: %d",max);
}
Tomar en cuenta que el primer número que leemos va a ser asignado al máximo, esto
resulta ser lógico, ya que es el único leído. Posteriormente, a la función se le pasa como
parámetros el dato que leemos y el anterior valor máximo y el mayor de ambos será asignado a
max. Finalmente debemos incluir el código fuente de la función que calcula el máximo al final
de programa.
¿Como podemos hacer mas eficiente el programa?, una forma sería que el calculo del
valor absoluto de un número sea una función, esto sería: crear una función a la cual se le pase
como parámetro un número y retorne el valor absoluto de este.
Entonces nuestro programa completo quedaría:
#include <stdio.h>
int maxnum(int abs);
int abs(int dato);
main()
{
int suma=0,num,max;
printf("\nIngrese un dato: ");
scanf("%d",&num);
max=num;
Profesor: Roberto Uribe P.
Pág. Nº 81
Programación de Computadores y el Lenguaje C
(versión 2003)
suma=suma+abs(num);
while (suma<=1000)
{
scanf("%d",&num)
max=maxnum(max,num);
suma=suma+abs(num);
}
printf ("\nEl maximo numero leido fue: %d",max);
return 0;
}
int maxnum(int dato1,int dato2)
{
if (dato1>dato2)
return(dato1);
else
return(dato2);
}
int abs(int dato)
{
if (dato<0)
return(-dato);
else
return(dato);
}
/*funcion que calcula el mayor */
/*funcion que calcula el valor absoluto */
Por último, debemos saber que la notación usada hasta ahora para el paso de
parámetros es conocida como Notación Moderna. En la Notación Clásica o Kernighan &
Ritchie, los tipos de los parámetros se especifican fuera de los paréntesis de la cabecera de la
función y antes del cuerpo de la función. Por ejemplo, la cabecera de las funciones anteriores
serían:
int maxnum(dato1,dato2) /*funcion que calcula el mayor */
int dato1,dato2;
{
:
:
}
int abs(dato)
int dato;
{
:
:
}
/*funcion que calcula el valor absoluto */
Esta notación es aún frecuente encontrarla, sobre todo en textos y código antiguo. En el
estándar ANSI se mantiene únicamente por compatibilidad.
Profesor: Roberto Uribe P.
Pág. Nº 82
Universidad de Magallanes - Departamento de Ingeniería en Computación
Problemas Resueltos y Propuestos
Resueltos:
1.-
Implemente la función potencia (xy).
2.Escriba la función convcharaint() del capítulo anterior, recuerde que la cabecera de la
función debe ser:
int convcharaint(char dato)
3.Escriba un programa en el cual el usuario ingrese un número y este arroje una lista de
todos los números primos menores o iguales a el número ingresado.
Para lo anterior, haga una función que determine si el número es primo o no, el
parámetro de entrada es el número y la función sólo debe retornar 0 si el número no es primo y
distinto de 0 si lo es.
Propuestos :
4.-
Calcular la raíz cuadrada de un número utilizando la fórmula de Newton.
Gn=(G0 + N/G0 )/2
N es el número para el cual se quiere obtener la raíz;
G0 es la aproximación anterior (la cual se inicializa en N/2 );
Gn es la nueva aproximación.
El cálculo se realiza hasta que el valor absoluto de la diferencia entre la aproximación
anterior y la nueva sea menor que cierto valor. Por ejemplo :
|Gn-G0| < 10-6
(0.000001)
Soluciones:
1.El algoritmo para este problema esta resuelto, por lo tanto, al implementar la función
debemos eliminar las salidas a pantalla, ya que lo único que deseamos es el resultado.
float potencia(float x, int y)
{
float pot=1;
int i=0;
if (x==0)
return(0);
for(i=1;i<=abs(y);i++)
pot=pot*x;
if (y<0)
return(1/pot); /*exponente negativo*/
else
return(pot);
/*exponente positivo*/
}
Profesor: Roberto Uribe P.
Pág. Nº 83
Programación de Computadores y el Lenguaje C
(versión 2003)
En la función, x puede ser cualquier número real, además, se implementó el caso de que
el exponente fuese negativo, para ello en el for hay una llamada a la función abs,
implementada anteriormente.
2.En este caso se puede utilizar algunas de las características permisivas de C, como por
ejemplo: hacer la resta entre caracteres.
Suponiendo que la máquina donde se ejecuta el programa use la tabla de códigos ascii,
lo que implica que los dígitos están en orden correlativo de '0' a '9', C nos permite hacer la
operación '1' - '0' lo que nos entregaría como resultado 1 (int), ya que el caracter '1' se
encuentra una posición mas allá del '0'. Por lo tanto, para obtener el valor numérico de una
variable caracter que contenga un dígito sería: variable_char - '0'.
Entonces:
int convcharaint(char dato)
{
if (dato>='0' && dato<='9') /* es digito*/
return(dato-'0');
else
switch(dato)
{
case 'A' : return(10);
case 'B' : return(11);
case 'C' : return(12);
case 'D' : return(13);
case 'E' : return(14);
case 'F' : return(15);
default : return(-1);
/*para indicar error*/
}
}
Note que no se uso el break, esto ya que cualquier case que sea verdadero termina la
función. Además, se retorna un -1 en caso de ingresar un caracter que no corresponde,
suponiendo que el valor resultado va a ser mayor que 0.
Una modificación interesante es, reemplazar el switch por :
if (dato>=’A’ && dato<=’F’)
return dato-‘A’+10;
/* es dígito */
3.Para esta pregunta ya sabemos como determinar si un número es o no primo, lo que
resta es ajustar el procedimiento para que haga lo que se nos pidió en el encabezado.
#include <stdio.h>
int esprimo(int dato);
main()
{
int num,i=2;
printf("\nPrograma para primos.\n");
printf("Ingrese un numero mayor que 0 : ");
Profesor: Roberto Uribe P.
Pág. Nº 84
Universidad de Magallanes - Departamento de Ingeniería en Computación
scanf("%d",&num);
if (num<=0)
printf("Error, el numero debe ser mayor que cero.\n");
else
{
printf("\nTodos los numeros primos hasta el %d son :\n",num);
for (i=1;i<=num;i++)
if (esprimo(i))
printf("%d\t",i);
}
return 0;
}
int esprimo(int dato)
{
int cont=0;
if (dato==1)
return 0;
/*el 1 no es primo */
if (dato==2)
return 1;
else
for(i=2;i<dato;i++)
if ((dato%i)==0)
cont++;
if (cont>0)
return 0;
else
return 1;
}
La función cálcula si es primo de la misma manera que en el capítulo anterior,
generalmente las funciones de este tipo no tienen salidas a pantalla y sólo hacen el proceso.
Otra forma de construir la función es:
int esprimo(int dato)
{
int i=0;
if (dato==1)
return 0;
if (dato==2)
return 1;
else
for(i=2;i<dato;i++)
if ((dato%i)==0)
return 0;
return 1;
}
En este caso, la función termina a la primera ocurrencia de un módulo igual a 0, esto
resulta lógico dado que no es necesario continuar con el for porque ya se sabe que el número
no es primo. Esta modificación le otorga una mayor eficiencia y rapidez al algoritmo. Otra
modificación que implicaría más eficiencia, sería que el for llegara sólo hasta la raíz cuadrada
de dato.
Un algoritmo más eficiente sería :
Profesor: Roberto Uribe P.
Pág. Nº 85
Programación de Computadores y el Lenguaje C
(versión 2003)
:
else
for(i=sqrt(dato);i>=2;i--)
if ((dato%i)==0)
return 0;
return 1;
:
Partir desde la raíz y decrementa el contador hasta 2, es muy diferente de partir de 2 y
llegar hasta la raíz. Esto dado que en la primera alternativa la raíz se calcula una sola vez, en
la inicialización. En el segundo caso, la raíz se evalúa en la condición, es decir, se calcula la
raíz en cada ciclo, lo que involucra un costo adicional en tiempo.
Note que sqrt(dato) es un valor double, sin embargo, se convierte en forma automática a
int al asignárselo a y. Al usar la función raíz cuadrada debe agregar el archivo de cabecera
math.h..
Profesor: Roberto Uribe P.
Pág. Nº 86
Universidad de Magallanes - Departamento de Ingeniería en Computación
Capítulo Nº 5 : Arreglos o matrices
Una matriz o arreglo es un conjunto de elementos (variables) todos del mismo tipo y
que agrupados dan lugar a un área de memoria que puede ser tratada como una variable
independiente y de una forma especial. Los elementos que componen el arreglo se encuentran
ordenados por su posición relativa dentro de este.
Las matrices pueden ser unidimensionales (vectores) o multidimensionales. Estos
poseen un índice o subíndice asociado que va a indicar un componente específico, este índice es
de tipo entero.
La declaración de un arreglo unidimensional es:
tipo nombre_arreglo[tamaño];
donde tamaño es el largo del arreglo y tipo el tipo de dato del arreglo y por lo tanto de
cada uno de sus componentes. En un arreglo sus componentes pueden ser de cualquier tipo,
básico o creado por el usuario, pero todos del mismo tipo.
Para identificar a un elemento se hace de la siguiente manera:
nombre_arreglo[posición]
donde posición es la ubicación del elemento dentro del arreglo. La posición va de 0 a
tamaño-1.
Ejemplo:
int numeros[10];
En términos reales, cuando se declara un arreglo se reservan espacios contiguos en
memoria. En el ejemplo se declaró un arreglo de enteros de tamaño 10, es decir, los 10
elementos del arreglo son int. Entonces, gráficamente podríamos suponer que un arreglo es:
0
1
2
3
4
5
6
7
8
9
Si escribimos
numeros[6]
nos estamos refiriendo al séptimo elemento de la matriz, si hubiéramos elegido el sexto lo
correcto sería: numeros[5].
Para asignar valores a un arreglo se debe hacer componente a componente:
numeros[6]=20;
numeros[9]=10;
entonces, el arreglo quedaría:
Profesor: Roberto Uribe P.
Pág. Nº 87
Programación de Computadores y el Lenguaje C
0
1
2
3
4
(versión 2003)
5
6
20
7
8
9
10
Otra forma es inicializarlo en la declaración, de la siguiente manera:.
float numreal[4]={0.0,0.0,0.0,0.0};
char frase[5]={'a','b','c','d','e'};
En este ejemplo declaramos un arreglo frase de tipo char, estos se conocen como
cadenas de caracteres o string.
La forma de imprimir una componente:
printf("\nLa componente 3 es %d",numeros[2]);
No se puede imprimir de una sola vez un arreglo de tipo distinto a char. Para el caso
anterior no se puede hacer
printf("...%d %d %d %d...",numeros);
diez %d y luego el nombre del arreglo, si se puede diez %d y luego las diez componentes en la
lista de argumentos. Para el caso de los string se puede utilizar el formato %s
printf("\n arreglo de caracteres es: %s",frase);
/* imprime abcde */
Igualmente, para ingresar datos se debe hacer de a una componente a la vez:
scanf("%d",&numeros[0]); /*lee el dato de la primera componente*/
Para el caso de un arreglo de tipo caracter se puede hacer la lectura de todo el string,
no debe llevar & porque el nombre del arreglo es un puntero al primer elemento de este.
scanf("%s",frase);
/*no lleva & */
scanf("%c",&frase[0]); /*Lee solo una componente del string*/
Nos podemos dar cuenta lo engorroso que resulta leer componente a componente un
arreglo (distinto de char), sobre todo si este es de tamaño grande (por ejemplo, 50). Sin
embargo, para ello utilizamos el ciclo for. Por ejemplo, para crear un arreglo de tipo flotante
de tamaño 50, ingresar los datos e imprimirlos en pantalla, el programa sería:
#include <stdio.h>
main()
{
float reales[50];
int i=0;
printf("Ingrese 50 numeros reales\n");
for(i=0;i<50;i++)
reales[i]=0.0;
/*inicializacion del arreglo (no es necesario)*/
for(i=0;i<50;i++)
scanf("%f",&reales[i]); /*Lee todas las componentes del arreglo.*/
Profesor: Roberto Uribe P.
Pág. Nº 88
Universidad de Magallanes - Departamento de Ingeniería en Computación
printf("\El arreglo es\n");
for(i=0;i<50;i++)
printf("%f\t",reales[i]); /*imprime las componentes del arreglo*/
return 0;
}
Arreglos de n-dimensiones
Para declarar arreglos de dos dimensiones se hace de la siguiente manera:
tipo nombre_arreglo[tamaño1][tamaño2];
Donde tamaño1 es la cantidad de filas y tamaño2 es la cantidad de columnas de la
matriz.
En general, para manipular vectores o matrices se hace de manera similar que en
matemáticas, es decir, uno se refiere a una matriz a través de su nombre y a una componente
específica por sus índices.
Ejemplo:
int A[6][4];
Es decir, la matriz A es de 6x4, y para referirnos a A23, se hace A[1][2], tomando en
cuenta que usualmente en matemáticas los subíndices van de 1 a n. La matriz anterior
podríamos representarla como
0
1
2
3
0
1
2
3
4
5
A[1][2]
A[3][1]
Entonces, para inicializar una matriz de dos dimensiones con cero, la parte principal
constaría de dos for (uno por cada dimensión) con dos índices. Para la matriz anterior
for (i=0;i<6;i++)
for (j=0;j<4;j++)
A[i][j]=0;
Ejercicio Nº 5.1 : Hacer un programa que calcule la matriz resultado de la
multiplicación de una constante por una matriz de nxn.
Solución: Como la matriz va a ser de n por n, se asume que el usuario ingresará el
tamaño de la matriz, entonces, podemos definir una matriz de tamaño máximo, por ejemplo
100, esto se debe hacer ya que necesariamente debemos definir el tamaño. Sabemos que para
multiplicar una constante por una matriz debemos multiplicar cada componente de la matriz
por la constante. Por lo tanto, en nuestro programa debemos ingresar la cantidad de filas y
Profesor: Roberto Uribe P.
Pág. Nº 89
Programación de Computadores y el Lenguaje C
(versión 2003)
columnas de la matriz, los datos que esta contendrá y el valor de la constante. Una solución
posible es:
#include <stdio.h>
main()
{
int matriz[100][100],resul[100][100],const,i,j,fil,col;
printf("Programa que calcula la multiplicacion de una matriz\n");
printf("por una constante.\n");
for (i=0;i<100;i++) /*se llena la matriz completa de ceros*/
for (j=0;j<100;j++)
matriz[i][j]=0;
/*lectura de datos*/
printf("Ingrese filas de la matriz");
scanf("%d",&fil);
printf("Ingrese Columnas de la matriz");
scanf("%d",&col);
printf("Ingrese datos de la Matriz\n");
for (i=0;i<fil;i++) /* el rango ahora es fil x col*/
for (j=0;j<col;j++)
{
printf("(%d,%d)=",i,j);
/*para que se vea una buena salida*/
scanf("%d",&matriz[i][j]);
}
printf("Ingrese Constante");
scanf("%d",&const);
/* proceso de multiplicacion*/
for (i=0;i<fil;i++) /* el rango ahora es fil x col*/
for (j=0;j<col;j++)
result[i][j]=matriz[i][j]*const;
/* impresion de resultados*/
printf("La matriz resultante es:");
for (i=0;i<fil;i++)
{
for (j=0;j<col;j++)
printf("%d",result[i][j]);
printf("\n");
/*para que cada fila salga en una linea*/
}
return 0;
}
Como se ve en este ejercicio, hay bastantes mensajes a pantalla, de esta manera se
logra que el usuario final entienda de que se trata el programa, además de saber cuando
ingresar los datos. Como antes se ha dicho, la inicialización no es necesaria, pero es una buena
costumbre hacerlo. Los elementos de la matriz que están fuera del rango fil x col no se
accesan, esto ya que los for no lo permiten.
Constantes Simbólicas
En nuestro ejemplo, si tuviéramos que modificar el tamaño máximo por cualquier
motivo, tendríamos que rectificar algunas líneas de código, si el programa fuera mucho mas
extenso, esto se complicaría. En C existe las llamadas constantes simbólicas, que son valores
que se asignan a identificadores, los cuales no se pueden modificar. Esto se hace a través de la
directiva #define. Por ejemplo:
# define MAX 100
Profesor: Roberto Uribe P.
Pág. Nº 90
Universidad de Magallanes - Departamento de Ingeniería en Computación
nos permitiría tener una constante llamada MAX con un valor de 100. La idea de esto es usar
MAX en vez de 100, de esa manera, si se tiene que cambiar el valor 100 por otro, sólo se cambia
MAX, de esa manera se modifica una sola línea del programa, esto nos simplifica el trabajo al
momento de hacer mantenciones o depuraciones de nuestros programas.
Los arreglos tienen múltiples aplicaciones, sobre todo en el arrea de las matemáticas,
pero su espectro cubre mucho más que eso. Por ejemplo, un arreglo de dos dimensiones nos
permitiría almacenar una lista con nombres, y si a esto agregáramos un arreglo de enteros,
podríamos tener la lista de un curso y el promedio de cada alumno, o podría ser la lista de
clientes y la deuda que poseen:
char NombresALum[20][30];
int Notas[20];
/* dos arreglos que nos permiten guardar 20 nombres y 20 notas*/
0
1
2
3
4
:
:
:
:
0
B
C
M
P
:
:
:
:
:
1
a
e
a
e
2
r
c
n
r
3
r
h
d
e
NombresAlum
..........
i a
i
i o l a
z
29
...............
19
Notas
0
0 60
1 51
2 45
3 48
: :
: :
:
:
:
19
Para leer el arreglo Notas se hace de la manera ya vista, para NombresAlum se puede
hacer con dos ciclos for o bien con un ciclo for y utilizando el formato %s para string. Por
ejemplo:
for (i=0,i<20; i++)
/* filas */
scanf("%s",NombresAlum[i]);
Esto leería las 20 filas, donde cada scanf estaría leyendo una cadena. Para que
nuestro diseño funcione, se asume que cada alumno tiene su nota en la misma posición donde
va su nombre, es decir, el alumno NombresAlum[11] tiene su nota en Notas[11].
Para el ejemplo anterior siempre es mejor usar estructuras (concepto nuevo), sin
embargo, es una buena idea dado los conocimientos hasta ahora vistos.
Profesor: Roberto Uribe P.
Pág. Nº 91
Programación de Computadores y el Lenguaje C
(versión 2003)
Una de las precauciones que debemos tener con los arreglos es con sus rangos, ello
porque C no comprueba los límites de los arreglos, por lo tanto queda en manos del
programador comprobar que estos se respeten. Por ejemplo, en C lo siguiente es permitido:
int A[20],i;
for (i=0;i<30;i++)
scanf("%d",&A[i]);
No hay error de compilación, sin embargo, esto podría provocar problemas, ya que
alguna variable declarada con anterioridad puede ocupar los espacios de memoria posteriores
al arreglo y por lo tanto ser sobrescrita.
En general, para declarar arreglos de n-dimensiones, el formato es el siguiente:
tipo nombre_arreglo[tamaño1][tamaño2]....[tamañoN];
Ejercicio Nº 5.2: Una de las aplicaciones usuales de arreglos en programación es el
ordenamiento de datos. Entonces, crear un programa al cual se le ingresen 5 números enteros y
los imprima en orden ascendente.
Solución: Una forma de resolver este problema es usando un arreglo que contenga los
datos, luego ordenarlos dentro del arreglo e imprimirlos. La parte fundamental del problema
es ordenar dichos datos. Hay variadas formas de hacerlo, una es ir comparando cada
componente con la siguiente de tal manera de ir moviendo el menor hacia el extremo izquierdo
del arreglo.
Por ejemplo, supongamos los siguientes datos, 100,20,50,8,0. Entonces, tomamos la
componente 0 (100) y lo comparamos con la componente 1 (20), luego si es mayor cambiamos
el contenido, luego volvemos a comparar la componente 0 (que ahora sería 20) con la
componente 2, y si es mayor lo intercambiamos, etc. El proceso sería: Ingresar los datos al
arreglo, y luego
100
20 50
8
0
Como 100 es mayor que 20, se cambian
20 100 50
8
0
20 es mayor que 8, se cambian
El arreglo finalmente quedaría
Profesor: Roberto Uribe P.
20 100 50
8
0
Como 20 no es mayor que 50, no se cambian
8 100 50
20
0
8 es mayor que 0, se cambian
0 100 50
20
8
Pág. Nº 92
Universidad de Magallanes - Departamento de Ingeniería en Computación
Hay que reconocer que en realidad lo que se compara son los contenidos de las
componentes, ya que estas según los resultados de las comparaciones van a ir variando. Ahora,
una vez que se recorrió el arreglo para la primera componente, se vuelve a hacer el mismo
proceso para la segunda componente y con las restantes. Es suficiente llegar hasta la penúltima
componente, ya que esta se compara con la siguiente.
¿Cual sería el algoritmo para esto?. Como es un arreglo, inmediatamente pensamos en
un ciclo for para recorrer todo el vector. Ahora, el arreglo no se recorre una vez, sino, una por
cada componente, es decir, se toma el componente 0 y se va comparando con las componentes
0+1 hasta n. Entonces una aproximación sería:
for(i=0;i<.....
for(j=i+1;j<n;j++)
Como el for interno (j) recorre hasta el final y comienza en 1 mas que el externo,
entonces i tiene que llegar hasta n-1. Osea:
for(i=0;i<(n-1);i++)
for(j=i+1;j<n;j++)
Otra cosa importante que debemos saber, es como hacer el intercambio de datos entre
dos variables. Para ello se utiliza una variable auxiliar, ejemplo:
aux=a;
a=b;
b=aux;
de esa manera no se pierde el contenido de la variable a. Entonces nuestro programa quedaría:
#include <stdio.h>
#define largo 5
main()
{
int datos[largo],i=0,j=0,aux;
printf("Este programa ordenara los numeros que Ud. ingrese\n\n");
printf("Ingrese %d numeros enteros\n",largo);
for(i=0;i<largo;i++)
scanf("%d",&datos[i]);
printf("\El arreglo sin ordenar es:\n");
for(i=0;i<largo;i++)
printf("%d\t",datos[i]);
for(i=0;i<(largo-1);i++)
/*parte principal*/
for(j=i+1;j<largo;j++)
if (datos[i]>datos[j])
/*para el intercambio de los contenidos*/
{
aux=datos[i];
datos[i]=datos[j];
datos[j]=aux;
}
printf("\El arreglo ordenado es:\n");
for(i=0;i<largo;i++)
printf("%d\t",datos[i]);
return 0;
}
Profesor: Roberto Uribe P.
Pág. Nº 93
Programación de Computadores y el Lenguaje C
(versión 2003)
La ejecución de la parte principal de este ejercicio incluyendo índices y los datos del
arreglo es:
i
j
0
0
0
0
1
1
1
2
2
3
1
2
3
4
2
3
4
3
4
4
datos[0]
100
20
20
8
0
0
0
0
0
0
0
datos[1]
20
100
100
100
100
50
20
8
8
8
8
datos[2]
50
50
50
50
50
100
100
100
50
20
20
datos[3]
8
8
8
20
20
20
50
50
100
100
50
datos[4]
0
0
0
0
8
8
8
20
20
50
100
aux
100
100
20
8
100
50
20
100
50
100
Datos ingresados
Guardó el
valor anterior.
Este es uno de los métodos de ordenamiento más conocidos y es denominado como
Método de la Burbuja, existen otros métodos como el de Selección y el de Mezcla.
Cadenas de Caracteres
Los string o cadenas de caracteres se manipulan como un arreglo común, sin embargo,
se pueden manejar de forma especial, como se pudo ver en los ejemplos anteriores de ingreso y
salida a pantalla. Para trabajar con cadenas debemos primero tener claro ciertos conceptos
respecto de ellas.
- Un string es un arreglo de caracteres que termina con el caracter nulo ‘\0’. Es por
ello que usualmente al declarar un string se le agrega un elemento más para que contenga
dicho caracter. Sin embargo, C permite trabajar con constantes de caracteres, estas están
encerradas entre comillas dobles, en este caso, si no se le incluye el caracter nulo al final, C lo
hace de manera automática. Un ejemplo es:
"Aqui estamos otra vez"
que sería lo mismo que
"Aqui estamos otra vez\0"
Por ejemplo, si se desea copiar un string en otro sin saber el largo del primero,
podemos hacer:
for(i=0;str1[i]!=‘\0’;i++)
str2[i]=str1[i];
str2[i]=‘\0’;
Con esto se copió str1 en str2, como dentro del for no se copio el caracter nulo, se
hizo al final.
Para manipular cadenas existe una biblioteca llamada string.h, en la cual se
encuentran una serie de funciones que permiten entre otras cosas: comparar dos string, copiar
Profesor: Roberto Uribe P.
Pág. Nº 94
Universidad de Magallanes - Departamento de Ingeniería en Computación
un string en otro, saber el tamaño de una cadena, ver si un caracter esta dentro del string, etc.
Algunas de ellas son:
Nombre Función:
strcpy(str2,str1);
strcmp(str1,str2);
strlen(str1);
strcat(str1,str2);
strchr(str1,car1);
Descripción
Copia str1 en str2.
Compara los dos string str1 y str2, devuelve
un valor entero que es : menor, mayor o igual
a
cero,
dependiendo
si
str1
es
lexicográficamente menor, mayor o igual a
str2.
Devuelve un entero que va a ser el largo de
str1.
Concatena el string str2 al final de str1.
Retorna un puntero a la primera aparición del
caracter car1 en str1.
Ejemplos:
if (strcmp(nombre,string1)>0)
printf("El string %s es mayor que %s",nombre,string1);
:
:
strcat(nombre,string1);
printf("Ahora %s tiene largo %d",nombre,strlen(nombre));
:
Cadenas sin tamaño definido
En C se puede hacer la inicialización de cadenas no delimitadas, para ello se declara el
arreglo y se le asigna una constante string:
char normal[50]; /*declaracion tradicional*/
char s1[]="Mensaje de Error";
char s2[]="Ingrese de nuevo el dato";
Con esto nos evitamos saber a priori cuanto espacio debe tener el arreglo para
contener todos los caracteres. C automáticamente crea un arreglo con el tamaño suficiente
para contener el string mas el caracter nulo.
Existe otra forma de declarar un string sin un tamaño específico, por ejemplo:
#include <stdio.h>
#include <string.h>
main()
{
char normal[50]; /*declaracion tradicional*/
char *s1;
char *s2="Esto no tiene largo\0";
char *s3="\0";
/*inicializada en nulo*/
printf("El string %s tiene largo %d",s2,strlen(s2));
strcpy(s3,s2);
Profesor: Roberto Uribe P.
Pág. Nº 95
Programación de Computadores y el Lenguaje C
(versión 2003)
:
:
En términos formales, en el ejemplo se declararon punteros a char, para ello se utilizó
el operador *. Para el caso de s2 y s3 es idéntico al ejemplo anterior. Sin embargo, es ilegal
hacer strcpy(s3,s2) y habría un error en tiempo de ejecución, de no ser así (depende del
sistema operativo), puede ocurrir cualquier cosa.
A veces se utilizan los punteros a char como arreglos y se realizan operaciones sobre
ellos, como copiar, concatenar, etc. Cuando los programas son cortos no se provoca ningún
error, sin embargo, cuando son más extensos, lo que implica mayor uso de memoria,
usualmente ocurren errores de asignación de esta, esto provocado porque el arreglo no tiene la
capacidad de almacenar todos los caracteres. Estos errores provocan la caída del programa o
la mala ejecución de este.
Para solucionar este problema, se hace una asignación dinámica de memoria al
arreglo (dado que es un puntero a char). Esto en términos generales es asignar memoria a una
variable en tiempo de ejecución, para ello se utiliza la función malloc(), esta se verá más
adelante.
Recuerde: Las variables globales obtienen memoria en tiempo de compilación. Las
variables locales utilizan la pila de memoria.
Como retornar un string
En mucha ocasiones es necesario que una función retorne un arreglo, en este capítulo
veremos el caso de retornar una cadena de caracteres. Para ello, hay que declarar la función
como char y además agregar el caracter asterisco antes del nombre de la función.
Para ilustrar lo anterior veamos el siguiente ejemplo:
#include <stdio.h>
#include <string.h>
char *agregahola(char *nombre);
main()
{
char nombre[50];
printf("Ingresa un nombre :");
scanf("%s",nombre);
printf("%s",agregahola(nombre));
}
char *agregahola(char *nombre)
{
char s[50]
strcpy(s,"Hola ");
strcat(s,nombre);
return s;
}
Profesor: Roberto Uribe P.
Pág. Nº 96
Universidad de Magallanes - Departamento de Ingeniería en Computación
Recuerde que strcat concatena en este caso, el contenido de la variable nombre al
final de s. Como lo que se retorna es un string, entonces, el segundo printf de la función
principal es correcto.
Este programa funciona sin problemas, pero lo ideal es declarar s con un tamaño
capaz de contener el string que se le concatena. Además, es posible que al compilar aparezca
una advertencia respecto de retornar una dirección de una variable local. Esto último se verá
en el capitulo de punteros.
Por último, el parámetro pasado a la función, fue declarado como: char *nombre lo
que indica que lo que se pasa es un puntero a char. En este caso no hay error, ya que el string
como tal es un puntero, y además, el tamaño de este parámetro es el tamaño de la variable que
es pasada a la función.
Arreglos como parámetros
Los arreglos como cualquier otra variable se pueden pasar como parámetros a las
funciones y se hace de la misma manera. Supongamos:
#include <stdio.h>
#define MAX 50
void muestra(int arr[MAX]);
main()
{
int vector[MAX];
:
muestra(vector);
:
:
}
void muestra(int arr[MAX])
{
int i=0;
for(i=0;i<MAX;i++)
printf("%d\t",arr[i]);
return;
}
En si el nombre de un arreglo es un puntero al primer elemento de este, entonces al
pasarlo como parámetro a una función los valores de sus componentes pueden ser modificados.
Por ejemplo, la siguiente función logra modificar el arreglo vector:
void modifica(int arr[MAX])
{
int i=0;
for(i=0;i<MAX;i++)
arr[i]=arr[i]*10;
return;
}
La llamada desde el main debería ser:
modifica(vector);
Profesor: Roberto Uribe P.
Pág. Nº 97
Programación de Computadores y el Lenguaje C
(versión 2003)
Ejercicio Nº 5.3 : Una de las aplicaciones frecuentes con arreglos es la búsqueda de
datos en estos. Supongamos entonces que, se tiene un arreglo con N datos enteros. Cree una
función que permita la búsqueda de un dato específico dentro del arreglo. Los datos no están
ordenados dentro del arreglo.
Solución : La primera forma de resolverlo es ir comparando uno a uno los datos del
arreglo con el dato ingresado (dato que se busca).Entonces, debemos usar un ciclo que nos
permita recorrer el arreglo, y dentro de este hacer la comparación :
for (i=0;i<N;i++)
if(a[i]==dato)
printf(“dato encontrado”);
else
printf(“dato no encontrado”);
Este método se conoce jocosamente como el método carretero, ya que es el que podría
demorarse más en buscar.
Un ejemplo de programa completo sería :
#include <stdio.h>
#define N 15
/* N va a ser el máximo de datos */
int busca(int a[N], int dato);
main()
{
int arr[N], dato, i=0;
printf(“Ingrese datos para llenar el arreglo (%d)”,N);
for(i=0;i<N;i++)
{
printf(“dato %d: ”,i);
scanf(“%d”,&arr[i]);
}
printf(“Ingrese un dato a buscar”);
scanf(“%d”,&dato);
if (busca(arr,dato))
printf(“Dato Encontrado”);
else
printf(“Dato no existe en el arreglo”);
return 0;
}
int busca (int a[N], int dato)
{
int i=0;
for(i=0;i<N;i++)
if (a[i]==dato)
return 1; /* encontrado
return 0; /* no encontrado */
}
*/
Observe que se pasaron como parámetros tanto el arreglo como el dato que se busca.
Esta función retorna 1 si el dato existe y después de recorrer todo el arreglo y comprobar que
el dato no se encuentra retorna 0.
Por último, si los datos dentro del arreglo se encuentran ordenados, se podría utilizar
algún otro método más eficiente para la búsqueda, un método que se demorara menos.
Profesor: Roberto Uribe P.
Pág. Nº 98
Universidad de Magallanes - Departamento de Ingeniería en Computación
Ejercicio Nº 5.4: Suponga que se tienen los dos arreglos mencionados anteriormente,
NombresAlum y Notas, los que almacenan los nombres (y apellidos) de un curso y el promedio
de cada alumno. En el problema planteado se asume que las notas están en las mismas
posiciones de los nombres (pero en distintos arreglos). Entonces, se debe crear una función que
permita ingresar un nombre y arroje el promedio de la persona.
Solución: El proceso sería, tomar el nombre ingresado por teclado y compararlo con
toda la lista de nombres del arreglo NombresAlum, con esto obtenemos la posición en que se
encuentra dicho nombre. Seguido, tomamos la posición obtenida y buscamos el promedio
ubicado en esa posición en el arreglo Notas.
Entre las cosas a considerar están: para comparar los nombres de la lista con el
ingresado se debe usar la función strcmp() para comparar string, por lo tanto hay que incluir
en el programa al archivo de cabecera string.h. Los arreglos están declarados como globales,
de esa manera los podemos ocupar en cualquier función que tenga el programa.
La primera parte del programa es:
# include<stdio.h>
# include<string.h>
char NombresALum[20][30];
int Notas[20];
/* dos arreglos que nos permiten guardar 20 nombres y 20 notas*/
La función sería:
void consulta()
{
char nombre[30]="\0";
int posicion=-1, promedio=0;
printf("Ingrese un nombre :");
scanf("%s",nombre);
posicion=buscanombre(nombre);
if (posicion==-1)
{
printf("Error, Nombre no encontrado\n");
return;
}
else
{
promedio=buscapromedio(posicion);
printf("El promedio de %s es : %d\n",nombre,promedio);
}
return;
}
Entonces, tenemos la función que ingresa el nombre, para hacer más modular el
programa creamos dos funciones más, una que busca el nombre y otra que busca el promedio.
La primera función pasa como parámetro el nombre, si no es encontrado retorna -1, de esa
manera evitamos errores. Una vez encontrada la posición, pasamos como parámetro a la
segunda función este dato. Las funciones serían de la siguiente forma:
Profesor: Roberto Uribe P.
Pág. Nº 99
Programación de Computadores y el Lenguaje C
(versión 2003)
int buscanombre(char *s)
{
int i=0;
for(i=0;i<20;i++)
if (strcmp(s,NombresAlum[i])==0) /*Si los nombres son iguales*/
return(i);
/*retorna la posicion*/
return(-1);
}
Con esto logramos ubicar la posición, si no la encuentra el for recorre todo el arreglo y
por lo tanto retorna un -1. La función que busca el promedio sería:
int buscapromedio(int pos)
{
return(Notas[pos])
}
/*No es necesario recorrer el arreglo ya que tenemos la posición. */
Profesor: Roberto Uribe P.
Pág. Nº 100
Universidad de Magallanes - Departamento de Ingeniería en Computación
Problemas Resueltos y Propuestos
Resueltos:
1.Escribauna función a la que se ingresa una cadena de caracteres de largo indefinido y
arroje como resultado la misma cadena, pero sin los caracteres espacio en blanco (' ') y
tabulador ('\t'). Escriba el programa completo.
Restricción: el programa debe hacerse utilizando un solo arreglo de caracteres.
Ejemplo:
Ingrese tira de caracteres:
Espero que todos terminen
a la hora.
La misma cadena es:
Esperoquetodosterminenalahora.
2.Escriba un programa que lea una matriz de NxM, donde N y M son valores conocidos
menores que 40. El programa debe convertir esta matriz en su traspuesta.
Se entiende por matriz Traspuesta aquella que sus componentes j,i son iguales a las
componentes i,j de la matriz original, es decir:
A=
a11 a12 a13 .... a1m
a21 a22 a23 .... a2m
..................
an1 an2 an3 .... anm
T
A=
a11 a21 a31 .... an1
a12 a22 a32 .... an2
....................
a1m a2m a3m .... anm
Condición: utilice el mismo arreglo para hacer el ejercicio.
3.Escriba una función que realice una búsqueda en texto, para ello a la función debe
ingresar dos string de tamaño indefinido, el primero es el texto en el que se va a buscar una
palabra (segundo string). La función debe retornar 1 si es encontrada la palabra y 0 de lo
contrario.
4.Un grupo de expertos del área informática contratado específicamente para resolver
los mensajes en clave tiene una nueva tarea. Cada vez que la otra parte en conflicto se da
cuenta que sus mensajes son decodificados, esta inventa una nueva forma de codificarlos.
Para el siguiente mensaje captado por el grupo de ingenieros:
Ehp o uoy
sare se
tnonru ee
adbtapcss
moarmuoft
o reoenuu
sp t,s ed
oee tmri
lrsnpouzo
Profesor: Roberto Uribe P.
Pág. Nº 101
Programación de Computadores y el Lenguaje C
(versión 2003)
u tio co.
caedrqh
Finalmente se determinó que este texto correspondía a una matriz de caracteres, es
decir, para el texto anterior la matriz es:
E
s
t
a
m
o
s
h
a
n
d
o
p
r
o
b
a
r
p
o e
l r s
u
t
c a e
o
e
n
t
r
e
t
e
n
i
d
r
a
m
o
,
p
o
r
u o
e
e
c s
o f
n u
e
m r
u z
c o
q h
s
u
p
u
e
s
t
o
y
e
s
t
u
d
i
o
.
Para obtener el mensaje final, se tienen que leer la matriz columna a columna, es decir,
es mensaje decodificado sería:
Estamos luchando por aprobar este entretenido ramo, por supuesto que con mucho
esfuerzo y estudio.
Cree una función a la cual se le pasa como parámetro esta matriz (de 11x9) y que
retorne un string con el texto decodificado.
5.-
Diseñe un programa que simule una Base de Datos, para ello utilice sólo arreglos.
Imagine que tiene una empresa con 20 trabajadores, a cada uno se le paga en forma
diferente la hora de trabajo extra, además todos realizan horas extras de 0 a N siendo estos
valores enteros positivos.
El programa debe hacer lo siguiente:
- Leer el nombre de cada trabajador.
- Leer la cantidad de horas extras realizadas en el mes.
- Leer el valor de la hora extra asignado para cada empleado.
- Finalmente, debe entregar un resumen con todos los datos del empleado, además del
dinero que recibirá de acuerdo a las horas trabajadas y al valor por hora, esto debe ser al
finalizar el programa.
Ejemplo:
Ingreso de Datos:
Nombre
: Perico Perez Palotes
Horas extra : 43
Valor Hora : 1500
Nombre
Horas extra
Valor Hora
Nombre
Horas extra
Profesor: Roberto Uribe P.
:
:
:
:
:
Leonardo Da Vince
58
2100
Cleopatra Contreras C.
0
Pág. Nº 102
Universidad de Magallanes - Departamento de Ingeniería en Computación
Valor Hora : 1980
. . . . . . . . . . . . .
Resumen:
Nombre
Perico Perez Palotes
Leonardo Da Vince
Cleopatra Contreras C.
. . . . . . . . . . . .
Horas
43
58
0
. . . .
Valor
1500
2100
1980
Total
64500
121800
0
Propuestos:
6.Escriba una función a la que se le ingrese como parámetro un string , dicha función
debe retornar la cantidad de espacios en blanco, tabuladores, puntos y comas que tiene el
arreglo.
Condición: no se conoce el tamaño del string ingresado a la función.
7.Haga un programa que determine si un palabra o frase ingresada vía teclado es o no
palíndrome.
Una palabra palíndrome es aquella se lee de la misma manera al derecho y al revés,
ejemplo:
ingrese frase: alla
la frase es palíndrome.
8.Escriba un programa que lea una matriz cuadrada de NxN donde N es valor conocido y
menor que 40. El programa debe verificar que la matriz ingresada es simétrica o no.
Se entiende por matriz simétrica aquella que el valor de sus componentes i,j es igual a
j,i, es decir:
a11 a12 a13 ... a1n
a21 a22 a23 ... a2n
................ .
an1 an2 an3 ... ann
Donde:
ai,j = aj,i
9.Ud. pertenece a un equipo de expertos en inteligencia y se le ha encomendado crear un
método para codificar mensajes. Después de pensar un poco en la posible fórmula, llego a la
genial idea de utilizar el código ascii para hacer la codificación del mensaje.
El método consiste en que a cada letra del mensaje original se le coloca en el mensaje
que se enviará la letra siguiente, es decir, a la letra se le suma 1 lo que implicaría colocar la
siguiente letra de la tabla ascii.
Profesor: Roberto Uribe P.
Pág. Nº 103
Programación de Computadores y el Lenguaje C
(versión 2003)
Por ejemplo, para el siguiente mensaje:
Oh! maestro, los que van a morir te saludan!!
El mensaje codificado sería:
Pi! nbftusp, mpt rvf wbo b npsjs uf tbmvebo!!
Cree un programa que lea el mensaje a codificar (mensaje real), luego codifíquelo
utilizando un arreglo de tamaño no definido, y finalmente imprima este mensaje en pantalla.
Para ello utilice una función a la cual se le pasa como parámetro el mensaje original y retorne
un string con el mensaje codificado.
Nota: si la letra es z, entonces se debe colocar la a en el mensaje codificado.
10.-
Para el siguiente programa:
# include <stdio.h>
main()
{
int a[20],aux,n=0,y,j;
printf("Ingrese el número de datos a leer");
scanf("%d",&n);
for(i=0;i<n;i++)
{
printf("\n Ingrese dato a[%d]:",i);
scanf("%d",&a[i]);
}
for(i=0;i<(n-1);i++)
for(j=i+1;j<n;j++)
{
if(a[i]>a[j])
{
aux=a[i];
a[i]=a[j];
a[j]=aux;
}
}
}
Suponiendo que los datos ingresados fueron 5 (10,-5,15,5,-20), muestre la ejecución
del ciclo for principal, indicando el comportamiento de las variables y del arreglo.
11.- Implemente la función strcat, esta función concatena dos string. A dicha función se le
ingresa como parámetros 2 cadenas de caracteres de tamaño no conocido.
12.- Implemente un programa que cuente las palabras que contiene un string: Utilice la
función gets() para leer el string (esta función permite leer un string incluido los espacios en
blanco y tabuladores). Las palabras pueden estar separadas por espacios, tabuladores, puntos,
comas, punto y comas.
Escriba una función que tenga como parámetro el string y retorne el número de
palabras.
Profesor: Roberto Uribe P.
Pág. Nº 104
Universidad de Magallanes - Departamento de Ingeniería en Computación
13.-
Diseñe un programa que simule una Base de Datos, para ello utilice sólo arreglos.
Suponga que se tiene un curso de 40 alumnos y que cada alumno rindió 3 pruebas en el
semestre.
El programa debe hacer lo siguiente (cada uno de los procesos debe ser una función):
- Ingresar el Apellido y Nombre de todos los alumnos.
- Leer las notas de cada alumno.
- Calcular el promedio de cada alumno y dejarlo en un nuevo arreglo.
- Finalmente debe entregar un listado con el apellido, nombre, notas y promedio. Al
final de la lista debe entregar el promedio general del curso.
Restricciones: Todas las operaciones deben ser hechas en funciones. Además, declare
los arreglos en forma local, dentro del main.
El programa debe funcionar de la siguiente manera:
Ingrese datos de los alumnos:
Nombre:
Apellido:
Miguel Angel
Garay
Nombre:
Apellido:
Alejandra
Cardenas
Nombre:
Apellido:
.
.
.
Pamela
Zuniga
Ingrese Notas:
Miguel Angel Garay:
Nota 1:
65
Nota 2:
88
Nota 3:
44
Alejandra Cardenas:
Nota 1:
50
Nota 2:
29
Nota 3:
60
Pamela Zuniga:
Nota 1:
58
Nota 2:
100
Nota 3:
15
.
.
.
Resumen:
Nombre
Miguel Angel Garay
Alejandra Cardenas
Pamela Zuniga
.........
Promedio General:
1
65
50
58
2
88
29
100
3
44
60
15
Promedio
66
46
58
XX
Nota: Los nombre fueron cambiados para proteger a los inocentes ;-).
Profesor: Roberto Uribe P.
Pág. Nº 105
Programación de Computadores y el Lenguaje C
(versión 2003)
Soluciones:
1.El objetivo de trabajar con la misma cadena es para aumentar la complejidad del
problema, sin embargo, una solución más sencilla sería: ir copiando caracter a caracter de un
arreglo a otro, salvo los espacios y tabuladores. La solución para el problema original es la
siguiente:
# include <stdio.h>
char *elim(char *st);
main()
{
char s[40]="\0";
printf("Ahora :\n");
gets(s);
printf("%s",elim(s));
return 0;
}
char *elim(char *st)
{
int i=0,j=0;
for(i=0;st[i]!='\0';)
{
putchar(st[i]);
/*para ver el proceso en pantalla*/
if (st[i]==' ' || st[i]=='\t')
/*encuentra espacio o tabulador*/
{
putchar('<'); /*marca el inicio de lo que se mueve a la izquierda*/
for(j=i+1;st[j]!='\0';j++)
{
st[j-1]=st[j];
/*copia caracter a caracter a la izq.*/
putchar(st[j]);
/*lo muestra en pantalla*/
}
putchar('>');
/*fin de lo que se movio a la izq.*/
st[j-1]='\0';
}
else
i++;
}
printf("\n\n");
return(st);
}
La idea de este programa es: al encontrar un espacio o tabulador, copia todo el arreglo
una posición a la izquierda, luego, revisa nuevamente desde esa posición y repite el proceso al
encontrar otro espacio o tabulador. Importante es notar que si se cumple la condición y una vez
que se movió el resto del arreglo, se inserta un caracter '\0' al final menos 1, esto para que no
quede basura al final del string. Las salidas a pantalla en la función es para ver como se va
realizando el proceso, es decir, va mostrando lo que se esta moviendo a la izquierda.
La función anterior tiene dos for, es decir, hace un recorrido al arreglo por cada
espacio o tabulador. Analice el siguiente algoritmo :
char *elim(char *st)
{
int i,dist=0;
for (i=0;st[i]!=’\0’;i++)
if (st[i]==’ ’ || st[i]==’\t’)
dist++;
else
Profesor: Roberto Uribe P.
/* encuentra espacio o tabulador */
Pág. Nº 106
Universidad de Magallanes - Departamento de Ingeniería en Computación
st[i-dist]=st[i];
st[i-dist]=’\0’;
return (st);
}
La nueva versión es mucho más eficiente, recorre sólo una vez el arreglo.
2.Para resolver este ejercicio, lo que se hace es usar una variable auxiliar llamada aux
para hacer el intercambio desde el elemento (i,j) al elemento (j,i) de la matriz en cuestión. Si se
desea trabajar con otra matriz de diferente tamaño, basta con cambiar el valor de N y M en la
cabecera del programa.
#include <stdio.h>
#define MAX 50
#define N 3
#define M 4
main()
{
int i=0,j=0,arr[MAX][MAX],aux,mayor;
if (N>=M)
mayor=N;
else
mayor=M; /* es necesario para poder decir cuantas veces se hara un
intercambio del elemento (i,j) al elemento (j,i) */
for (i=0;i<N;i++)
for(j=0;j<M;j++)
{
printf("\nIngrese el elemento [%d],[%d] : ",i,j);
scanf("%d",&arr[i][j]);
}
/* Aquí se leyeron todos los elementos de la matriz */
printf("\n\nSu Matriz original es : ");
for (i=0;i<N;i++)
{
printf("\n");
for(j=0;j<M;j++)
printf("%d\t",arr[i][j]);
} /* Aquí simplemente se imprimieron los elementos de la matriz */
for(i=0;i<mayor;i++)
for(j=i+1;j<mayor;j++)
{
aux=arr[j][i];
arr[j][i]=arr[i][j];
arr[i][j]=aux;
} /* Aquí la matriz se convirtio en su traspuesta */
printf("\n\nSu Matriz Traspuesta es : ");
for (i=0;i<M;i++)
{
printf("\n");
for(j=0;j<N;j++)
printf("%d\t",arr[i][j]);
}
return 0;
}
Profesor: Roberto Uribe P.
Pág. Nº 107
Programación de Computadores y el Lenguaje C
(versión 2003)
3.En este ejercicio se usó una función encuentra que lo primero que hace es buscar el
primer caracter del substring sub_str en el primer texto ingresado str y después que lo haya
encontrado se comienza a comparar el resto del substring, contando el número de
coincidencias (mediante un contador llamado flag)y si el número de coincidencias es igual
al tamaño del substring entonces se retorna 1, y si no se encontró se retorna 0.
#include <stdio.h>
#include <string.h>
#define MAX 100
int encuentra(char primero[MAX],char segundo[MAX]);
main()
{
int i;
char arr1[MAX],arr2[MAX];
printf("\nIngrese el primer texto : ");
gets(arr1); /* La funcion gets() permite leer espacios y elimina el salto
de línea almacenado en el buffer del teclado al presionar
la tecla enter para introducir el string arr1. A
diferencia de la función scanf() */
printf("\n\nIngrese el segundo texto : ");
gets(arr2);
printf("\n\n%d",encuentra(arr1,arr2));
return 0;
}
int encuentra(char str[MAX],char sub_str[MAX])
{
int i=0,j=0,k=0,flag;
for(i=0;str[i]!='\0';i++)
if (str[i]==sub_str[0]) /* No se mete dentro del if hasta que */
{
/* encuentre el primer elemento de sub_str */
flag=1;
/* en str */
k=i;
for(j=1;j<strlen(sub_str);j++)
{
k=k+1;
if (str[k]!=sub_str[j])
break;
flag++;
}
if (flag==strlen(sub_str))
return 1;
}
return 0; }
4.Aquí se recibe como parámetro un arreglo de dos dimensiones, y a cada elemento del
string st se le asigna el elemento (j,i) de la matriz recibida.
char *funcion_dec(char arr[11][9])
{
int i=0,j=0,k=0;
char st[100];
for(i=0;i<11;i++)
for(j=0;j<9;j++)
{
st[k]=arr[j][i];
k++;
}
st[k]='\0';
return st;
}
Profesor: Roberto Uribe P.
Pág. Nº 108
Universidad de Magallanes - Departamento de Ingeniería en Computación
Profesor: Roberto Uribe P.
Pág. Nº 109
Programación de Computadores y el Lenguaje C
(versión 2003)
Capítulo Nº 6 : Modos de Almacenamiento
Existen cuatro formas de almacenamiento, estas determinan el alcance (Scope) y el
tiempo que permanece la variable en memoria.
Variables Automáticas (auto)
Todas las variables declaradas en una función son por defecto automáticas, es decir su
alcance es local (a la función).
Ej.:
main()
{
auto int suma=0;
}
En el ejemplo se declaró la variable suma explícitamente como automática. No es
necesario poner la palabra reservada auto. Lo anterior es equivalente a:
main()
{
int suma=0;
}
La variable está activa en el par de llaves en la cual es declarada.
Variables Externas (extern)
Si una variable se declara fuera de una función se dice que es externa (lo que
conocemos como variable global). Su alcance va ha ser todo archivo o los archivos fuentes.
Ej.:
int parcial=1:
main()
{
extern parcial;
.
}
Sumatoria()
{
extern int parcial;
}
/* variable definida externamente */
/* no es necesario */
/* se declara la variable definida */
/* anteriormente */
/* tampoco es necesario */
Para usar la variable externa no es necesario declararla como extern dentro de las
funciones del mismo archivo, ya que si se omite la declaración se asume por defecto que es
externa (o global).
Por ejemplo, si se tiene un segundo archivo fuente se puede declarar en las primeras
líneas de este.
Profesor: Roberto Uribe P.
Pág. Nº 110
Universidad de Magallanes - Departamento de Ingeniería en Computación
Ej.:
extern int parcial;
void estructuras();
{
:
:
}
Se asume que la variable esta definida en otro archivo. Por ejemplo, supongamos que
tenemos dos archivos, princip.c y secundar.c:
princip.c
secundar.c
# include<stdio.h>
int fact();
int result=0;
main()
{
:
:
}
int fact()
{
:
:
}
float pot(int x,int y)
{
int pot=1
extern int result;
:
:
:
result=pot;
}
Entonces, la variable result utilizada en la función pot del archivo secundar.c es la
declarada en princip.c.
Si el programa hubiese sido hecho en Unix, habría que generar los códigos objetos de
ambos archivos y luego el ejecutable. Para hacer esto se utiliza la opción -c al momento de
compilar.
cc -c princip.c
genera princip.o
cc -c secundar.c
genera secundar.o
cc princip.o secundar.o
genera a.out
Variables Registro (register)
Las variables de tipo register nos permiten almacenar una variable en algún registro de
la CPU en vez de la memoria RAM, esto con la finalidad de que dicha variable sea manipulada
mucho más rápida que en memoria. Esto siempre y cuando sea posible ocupar algunos de los
registros, de no ser así, la variable queda declarada como automática. El uso más común para
este tipo de variable es en los contadores, ejemplo:
Ej.:
main()
{
register int conta;
:
:
}
Profesor: Roberto Uribe P.
Pág. Nº 111
Programación de Computadores y el Lenguaje C
(versión 2003)
Variables Estáticas (static)
Tienen el mismo alcance que las variables automáticas, pero no desaparecen cuando se
termina la función, es decir, conservan su valor.
El siguiente ejemplo demuestra lo anterior:
Ej.:
#include <stdio.h>
imprime()
main()
{
int i=0;
for (i=1; i<4; i++)
imprime();
}
imprime()
{
int local=1;
static int varstatic=1;
printf("\nlocal= %d
varstatic=%d",local,varstatic);
local++;
varsta++;
}
Salida por pantalla =>
local=1
local=1
local=1
varstatic=1
varstatic=2
varstatic=3
Como ya sabemos la variable local local pierde su valor y por lo tanto cada vez que es
llamada la función se inicializa. En el caso de varstatic, la primera vez se inicializa, pero
posteriormente se pasa por alto dicha inicialización y mantiene el último valor asignado.
Variables estáticas externas
Estas son variables globales declaradas como estáticas y su alcance es solamente el
archivo donde fue declarada. Supongamos que tenemos dos archivos uno.c y dos.c, en el
primero declaramos dos variables globales:
# include<stdio.h>
int general;
static int enarchivo;
main()
{
:
:
}
La variable general es conocida en todas las funciones del archivo uno.c y dos.c, la
variable enarchivo es conocida solamente en las funciones del archivo uno.c.
Profesor: Roberto Uribe P.
Pág. Nº 112
Universidad de Magallanes - Departamento de Ingeniería en Computación
Capítulo Nº 7 : Punteros en C
Los punteros (o apuntadores) son una de las herramientas más poderosas de C, sin
embargo en este caso, poder implica peligro. Es fácil cometer errores en el uso de punteros, y
estos son los más difíciles de encontrar, una expresión típica usada en este caso por los
programadores se refiere a la "perdida de un puntero", lo cual indica que ocurrió un problema
en la asignación de algún puntero.
La ventaja de la utilización de punteros en C es que mejora enormemente la eficiencia
de algunos procesos, además , permite la modificación de los parámetros pasados a las
funciones (paso de parámetros por referencia) y son usados en los procesos de asignación
dinámica de memoria.
Puntero es una representación simbólica de una dirección de memoria, es decir,
contiene la dirección de un objeto o variable.
Operador &
Contiene la dirección o posición de memoria en la cual se ha almacenado una variable.
El operador & es unuario, es decir, tiene un solo operando, y devuelve la dirección de memoria
de dicho operando. Supongamos el siguiente ejemplo:
main()
{
int auxiliar=5;
printf("\nauxiliar=%d --->dirección=%p",auxiliar,&auxiliar);
}
por pantalla =>
auxiliar=5 ---> dirección=289926.
En este ejemplo se utilizó el modificador de formato %p que muestra la dirección según
el formato del computador usado. Hasta ahora no había importado el lugar donde se
almacenaban los datos.
Los punteros se pueden declarar en los programas para después utilizarse y tomar las
direcciones como valores. Por ejemplo:
int *punt;
int j=5;
/* un apuntador a int */
Se declaro un apuntador punt a int, es decir, el contenido del puntero es una dirección
que apunta a un objeto de tipo entero. Si se hiciese la siguiente asignación:
punt=&j;
estaría correcta e implicaría que punt apunta a la dirección de memoria de la variable j.
Gráficamente sería:
Profesor: Roberto Uribe P.
Pág. Nº 113
Programación de Computadores y el Lenguaje C
Direccines
de Memoria
5001
(versión 2003)
5002
5006
5003
5004
5005
5006
5
punt (apunta a
la dir. 5006
que guarda un
int)
5007
5008
j (variable
int que
contiene el
dato 5)
entonces, el valor de punt y &j sería 5006.
Los punteros pueden ser inicializados, para ello existen varias formas:
punt=NULL;
punt=&j;
punt=(int *) 285395;
/* asignación de dirección */
/* absoluta */
En la primera línea se le asigna un nulo, es como decir que apunta a ninguna parte. En
la tercera línea se hace la asignación de una posición especifica de memoria.
Entonces, para declarar un puntero, primero se especifica el tipo de dato (básico o
creado por el usuario), luego el nombre del puntero precedido por el caracter *.
Operador *
El operador * es unuario y toma a su operando como una dirección de memoria,
entonces el operador accesa al contenido de esa dirección. Por ejemplo, suponga las variables
declaradas anteriormente y la asignación,
int *punt;
int j=5;
punt=&j;
entonces, si imprimimos en pantalla:
printf("El contenido de punt es: %d",*punt);
imprimiría:
El contenido de punt es: 5
con esto se imprimió el contenido de la dirección a la que apunta punt. Habría sido igual
haber impreso la variable j. Si se declarara otra variable int, igualmente es valido hacer:
int *punt;
int j=5,otro;
punt=&j;
:
:
otro=*punt;
Profesor: Roberto Uribe P.
Pág. Nº 114
Universidad de Magallanes - Departamento de Ingeniería en Computación
que asignaría a otro el dato 5. Si se hace la siguiente asignación:
*punt=200;
se modifica el contenido de la dirección a la que apunta punt. Por lo tanto también se modificó
la variable j, ya que la dirección de esta es a la que apunta punt.
Hay ciertas restricciones en la asignación de punteros, por ejemplo:
int *p,*q,*r;
/* 3 punteros a entero*/
int array[20],var1;
register int j;
p=&j;
q=568200;
r=&array;
p=&(var1+5);
Todas las asignaciones anteriores están erróneas. A un puntero no se le puede asignar
la dirección de una variable register, de hecho, porque esta está almacenada en un registro de
la CPU. A un puntero no se le puede asignar un valor cualquiera si no existe una conversión
explícita de tipo. No puede recibir la dirección de un nombre de un arreglo dado que este
último es un puntero al primer elemento del arreglo. La última asignación también es ilegal ya
que se pretende asignar la posición de var1 mas 5 posiciones de memoria.
Las siguientes asignaciones están correctas:
int *p,*q;
int array[20],var1;
p=NULL;
q=&array[5];
p=q;
Primero a p se le asigno el valor nulo, luego a q se le asigno la dirección del sexto
elemento del arreglo y finalmente a p se le asigno la misma dirección que q, por lo tanto
apuntan a la misma posición.
Hasta ahora se ha ocupado implícitamente punteros en nuestros programas, ejemplo de
ello es al ocupar scanf, donde pasamos como parámetro la dirección de la variable, también,
en arreglos, como el caso de cuando es pasado a una función y sus valores resultan
modificados.
Paso de parámetros por referencia
Sabemos que cuando se pasa como parámetro una variable a una función, esta no logra
modificar el valor de la variable, eso es dado que fue pasada por valor. Para lograr que se
modifique el contenido de una variable debe pasarse por referencia, para ello se pasa a la
función la dirección de la variable y no el dato.
Profesor: Roberto Uribe P.
Pág. Nº 115
Programación de Computadores y el Lenguaje C
(versión 2003)
Ejercicio Nº 7.1: Supongamos que se desea hacer una función que haga el intercambio
de datos entre dos variables (swap).
Solución: Primero hagamos el programa principal, de tal manera de saber como se
hace la llamada por referencia. Este podría ser:
#include <stdio.h>
void swap();
/*funcion para el intercambio*/
main()
{
int a=100,b=200;
printf("a=%d
b=%d",a,b);
swap(&a,&b);
printf("a=%d
b=%d",a,b);
}
Nuestro programa resulta bastante simple, lo importante es notar que ahora los
parámetros a y b van precedidos de el operador &, es decir, no se pasan los datos 100 y 200,
sino, las direcciones de las variables.
Ahora, como a la función se le pasaron direcciones de variables int, entonces en la
cabecera de la función deben declararse parámetros como punteros a int. Esto sería:
void swap(int *x,int *y)
Dentro de nuestra función lo que tenemos que hacer es intercambiar los valores. Como
los parámetros son punteros, entonces debemos trabajar con los contenidos de dichos punteros
(o los contenidos de las direcciones a la que apuntan los punteros), para ello utilizamos el
operador asterisco. La función completa es:
void swap(int *x,int *y)
{
int aux;
aux=*x;
*x=*y;
*y=aux;
}
Con esto logramos nuestro objetivo, la salida al programa es:
a=100
a=200
b=200
b=100
Ejercicio Nº 7.2: Cree un programa que lea dos números flotantes, luego una función
que tenga dos parámetros y salga el mayor en el primer parámetro y el menor en el segundo.
Solución: El programa principal sería:
#include <stdio.h>
void maxymin(int *x,int *y);
main()
{
int i,j;
scanf("%d",&i);
Profesor: Roberto Uribe P.
Pág. Nº 116
Universidad de Magallanes - Departamento de Ingeniería en Computación
scanf("%d",&j);
maxymin(&i,&j);
printf("El Max es i=%d, El Min es j=%d",i,j);
return 0;
}
La función no variaría mucho respecto al ejercicio anterior:
void maxymin(int *x,int *y)
{
int aux;
if (*x<*y)
{
aux=*x;
*x=*y;
*y=aux;
}
return;
}
En el if se pregunto si "el contenido del puntero x es menor que el contenido del
puntero y".
En esta función podríamos utilizar la función anterior swap para hacer el cambio. En
ese caso, aparte de incluir la declaración de dicha función antes del main, maxymin queda:
void maxymin(int *x,int *y)
{
int aux;
if (*x<*y)
swap(&*x,&*y);
}
Hay que notar que al hacer &*x hablamos de la dirección del contenido del puntero x.
Esto se hace utilizando la misma lógica de pasar la dirección. Sin embargo, hablar de &*x es
hablar de x, en otras palabras, si decimos que "el dato 120 (por ejemplo) esta almacenado en
la dirección x" ,es lo mismo que "el dato *x esta almacenado en x o en &*x". Entonces la
llamada a swap puede ser hecha como:
if (*x<*y)
swap(x,y);
Lo que cumple con pasar las direcciones de las variables a las funciones, esto ya en este
caso los parámetros de por si son direcciones (punteros).
Nota: Supongamos las siguientes sentencias:
int a=10,*p;
p=&a;
es posible imprimir:
printf("%d",*&a);
printf("%p",&*p);
printf("%p",&p);
Profesor: Roberto Uribe P.
Pág. Nº 117
Programación de Computadores y el Lenguaje C
(versión 2003)
es decir, en la primera línea se imprime "el contenido de la dirección &a" que es lo mismo que
imprimir a, pero, no se puede imprimir &*a, esto provocaría un error. En la segunda línea es
igual a imprimir p. En la tercera se imprime la dirección donde esta almacenado el puntero p.
Aritmética de Punteros
Las operaciones matemáticas que se pueden usar con punteros es el decremento,
incremento y resta entre dos punteros, es resto de las operaciones quedan prohibidas. La
operatoria de punteros queda sujeta al tipo base de este, por ejemplo:
int *q,dato;
q=&dato;
q++;
En este caso si el puntero q apunta a la dirección 30000, el incremento apuntaría q a la
dirección 30002. Esto ya que un entero tiene 2 bytes de largo y el incremento hace que se
apunte al siguiente entero. Lo mismo ocurriría con q--. Si a q se le incrementa en 20 haría que
q apuntara al vigésimo entero que esté despues de la posición original del puntero. Suponga el
siguiente ejemplo:
int *p,arreglo[20],i;
:
:
p=&arreglo[0];
for(i=0;i<20;i++)
{
printf("%d",*p);
p++;
}
En este caso se imprime todo el arreglo arreglo utilizando el puntero p. Esto es
efectivo dado que al declarar el arreglo, sus elemento se ubican en posiciones contiguas de
memoria. También sería correcto utilizar
p=&arreglo[0];
printf("El decimo elemento es : %d",*(p+9));
para referirnos al décimo elemento del arreglo. Como hay que tener cierto cuidado con el
manejo de punteros, veamos este ejemplo:
int *p,arreglo[20],i;
:
:
p=&arreglo[0];
for(i=0;i<20;i++)
{
printf("%d",*p);
p++;
}
printf("El decimo elemento es : %d",*(p+9));
Este es similar al anterior, sin embargo, no hace lo que supuestamente queremos,
porque?, la razón es que el puntero fue incrementado 20 veces y despues del for ya no apunta
al primer elemento del arreglo, por lo tanto puede imprimir cualquier valor (que sería aquel
ubicado 9 variables enteras más allá).
Profesor: Roberto Uribe P.
Pág. Nº 118
Universidad de Magallanes - Departamento de Ingeniería en Computación
También es posible hacer la resta entre dos punteros, esto con el objeto de saber
cuantos datos del tipo base (de los punteros) se encuentran entre ambos.
Igualmente se puede hacer la comparación entre punteros:
if (p>q)
printf("\np es mayor que q\n");
En este caso indicaría que p esta ubicado en una posición de memoria superior a la de q.
Algo más sobre punteros
Los punteros no solamente pueden apuntar a tipos conocidos, sino que también a tipos
creados por el usuario, como es el caso de punteros a estructuras. Podemos mencionar además
que un uso frecuente de estos son en estructuras de datos para trabajar con listas enlazadas,
arboles, etc., sin embargo esto último no corresponde verlo en este libro.
Otro ejemplo de uso de punteros es cuando un arreglo es definido como un arreglo de
punteros, por ejemplo, la siguiente declaración crea un arreglo de punteros de 5 elementos:
int *arrepunt[5];
donde cada una de las componentes del arreglo es un puntero a entero. Entonces, sentencias
como las siguientes son correctas:
arrepunt[0]=&var1;
printf("\n%d",*arrepunt[0]);
entonces, al primer elemento del arreglo se le asignó la dirección de la variable entera var1,
posteriormente se imprimió el contenido de dicho puntero.
malloc, free y sizeof
Muchas veces en nuestros programas necesitaremos ocupar memoria en el momento en
que este se está ejecutando, para ello existen dos funciones malloc y free. La primera, pide
memoria del conjunto de memoria disponible, free hace lo contrario, devuelve la memoria
pedida para que pueda ser ocupada posteriormente. Estas funciones se encuentran en la
librería llamada stdlib.h y son la base de la asignación dinámica de memoria en C.
Por ejemplo, si creamos un puntero a char y en algún momento necesitamos memoria
para guardar algún dato, se puede hacer:
char *p1;
:
p1=malloc(20);
lo que nos permitirá crear 20 lugares de memoria para guardar datos, y p1 quedaría
apuntando al primero de ellos. El parámetro pasado es un entero, pero en realidad el tipo
Profesor: Roberto Uribe P.
Pág. Nº 119
Programación de Computadores y el Lenguaje C
(versión 2003)
definido en stdlib.h es size_t que es parecido a un unsigned int. El prototipo de la función
malloc es la siguiente:
void *malloc(size_t cantidad_de_bytes)
El valor devuelto por malloc es un puntero (void *) que se convierte a el tipo de
puntero de la parte izquierda de la asignación. Sin embargo, para evitar problemas de
portabilidad lo ideal es hacer la conversión explícita, de la forma:
p1=(char *)malloc(20);
que convierte lo retornado por malloc a un puntero a char.
Para devolver la memoria solicitada, la instrucción sería:
free(p1);
En el ejemplo anterior un char ocupa un byte, no es problema pedir espacio para 20
char, sin embargo, si el puntero fuese entero, se tiene que multiplicar la cantidad pedida por el
tamaño en bytes de un entero que es 2, entonces, si no sabemos el largo en bytes de un tipo o de
una variable se nos complicaría pedir memoria, además que, dependiendo el computador los
tipos podrían tener diferente largo.
Para resolver estos problemas y para hacer nuestro programa más portable existe el
operador en tiempo de compilación llamado sizeof (tamaño de) que retorna el tamaño en bytes
del parámetro ingresado, que puede ser una variable o un identificador de tipo. Por ejemplo, si
deseáramos imprimir el tamaño (en bytes) de un entero, lo correcto sería hacer:
printf("%d",sizeof(int));
Suponga lo siguiente:
int a,matriz[20][10];
double var33;
:
printf("%d",sizeof(var33));
printf("%d",sizeof(matriz));
printf("%d\t\t%d",sizeof(a),sizeof(int));
:
Imprimiría el tamaño de las variables var33, matriz (que sería 20*10*el largo de un
entero) y el tamaño del tipo entero. En el ejemplo, sólo es obligatorio utilizar paréntesis cuando
se pregunta el tamaño de un tipo.
Con lo anterior podríamos crear memoria para un puntero a enteros usando
int *p2;
p2=(int *)malloc(10*sizeof(int));
lo que crearía espacio para 10 enteros (y p2 apuntaría al primero de ellos).
Profesor: Roberto Uribe P.
Pág. Nº 120
Universidad de Magallanes - Departamento de Ingeniería en Computación
Un ejercicio gráfico
El siguiente ejercicio pretende mostrar gráficamente que ocurre con las variables y
punteros durante la creación y asignación.
int *p;
int z;
p
z
¿??
¿??
despues de la declaración anterior, es decir, crea una variable entera z la cual tiene su espacio
en memoria, aunque su valor es desconocido (basura). Además, una variable puntero p (a int)
la cual aún no tiene espacio asignado (para contener valores) y no apunta a ningura parte.
z=100;
p
z
¿??
100
p
z
p=&z;
100
Despues de estas asignaciones, la variable z contiene el valor 100 y la variable p apunta a la
dirección donde está z. Entonces, recién ahora se puede hablar del contenido de p, es decir, si
se ejecuta:
printf(“%d”,*p);
Se imprime el valor 100. Si después,
p=NULL;
p
z
100
NULL
Donde NULL es la forma de representar que el puntero apunta a nada. En la figura se muestra
como barras paralelas decreciendo en tamaño.
Si luego se hace:
p=(int *)malloc(sizeof(int));
Profesor: Roberto Uribe P.
Pág. Nº 121
Programación de Computadores y el Lenguaje C
(versión 2003)
se crea el espacio necesario en memoria para guarda un valor entero, a la que apuntará la
variable p, a este valor sólo se puede accesar a través de p. Gráficamente:
p
z
100
¿??
*p=150;
p
z
100
150
Después de esta asignación, el contenido de p es igual a 150, y z aún mantiene el valor 100.
Si ahora hacemos nuevamente:
p=&z;
p
z
100
150
Con esto, en nuestra jerga diriamos que: “se nos perdio un puntero”. En realidad, formalmente
p no está perdido, si no que ahora apunta a z, pero nos es imposible recuperar el valor que
tenía antes. Como se muestra en la figura, no hay ninguna forma de acceder al espacio de
memoria que contiene el valor 150, siendo que ese espacio sigue ocupado. Si después de esto,
se ejecuta la siguiente linea:
*p=200;
Profesor: Roberto Uribe P.
Pág. Nº 122
Universidad de Magallanes - Departamento de Ingeniería en Computación
ocurre lo siguiente:
p
z
200
150
Entonces, ahora, tanto z como el contenido de p tienen el valor 200. Finalmente, si:
printf(“%d %d”,z,*p);
imprimiría en pantalla:
200 200
Un último comentario es recalcar que lo que almacena p, es una dirección de memoria,
es decir, la dirección de memoria a la que está apuntando, en los esquemas anteriores esta se
representa a través de la flecha
Profesor: Roberto Uribe P.
Pág. Nº 123
Programación de Computadores y el Lenguaje C
(versión 2003)
Problemas Propuestos
1.-
Para el siguiente extracto de programa:
int *p,arreglo[6],i;
:
:
/* datos del arreglo: 5 4 56 32 21 50 */
p=arreglo;
for(i=0;i<6;i++)
{
printf("%d",*p);
printf("%p",p);
p++;
}
a) Indique que hace el programa.
b) Si el primer elemento del arreglo esta ubicado en la posición de memoria 13500,
entonces, a que dirección de memoria queda apuntando p al finalizar el ciclo for?, escriba cada
valor que va saliendo por pantalla.
2.Escriba un programa que lea 100 números enteros y los almacene en un arreglo. Luego
escriba una función que entregue a la función principal el valor máximo y mínimo, para ello
ocupe parámetros pasados por referencia (a la función se le pasan tres parámetros: el arreglo
y dos pasados por referencia que contendrán el máximo y mínimo).
3.Escriba un programa que ingrese primero 2 datos (x,y) y que calcule la potencia de x
elevado a y, luego un tercer dato (z) para el cual se calculará el factorial.
La potencia y el factorial deben ser funciones a las cuales se le pasan parámetros por
referencia. Los datos (x,y,z) deben ser leídos desde el programa principal.
Profesor: Roberto Uribe P.
Pág. Nº 124
Universidad de Magallanes - Departamento de Ingeniería en Computación
Capítulo Nº 8 : typedef, struct y union
Typedef
El lenguaje C dispone de un mecanismo denominado typedef que se utiliza para
redefinir tipos otorgándoles nuevos nombres, esto permite entre otras cosas que las
mantenciones futuras del programa sean más fáciles, también permite una mayor
transportabilidad entre diferentes máquinas y evitar posibles problemas con los tipos de datos.
El formato para typedef es:
typedef <tipo> <nuevo_tipo>;
Ejemplo:
typedef int enteros;
En este caso se redefinió el tipo int, de esta manera el nuevo tipo enteros es más
descriptivo. Entonces, con lo anterior podemos declarar variables usando enteros. Por
ejemplo, las siguientes declaraciones
enteros a,b,c=5;
enteros arra[20];
crean variables de tipo int, tres variables y un arreglo de 20 elementos.
Ejemplo:
# define N 50
/* define una const. N=50 */
typedef float arrfloat[N];
.
.
.
main()
{
arrfloat numeros,otronum[N];
:
:
numeros[i]=5.05; /*asignacion valida*/
:
}
En el ejemplo anterior se definió un nuevo tipo que es un arreglo de flotantes, es decir,
en la declaración que esta dentro del main, la variable numeros es un arreglo de 50 y la
variable otronum es un arreglo de 50 por 50. Por ejemplo, para inicializar en 0 al arreglo
otronum, se hace de la manera conocida:
for(i=0;i<N;i++)
for(j=0;j<N;j++)
otronum[i][j]=0.0;
Profesor: Roberto Uribe P.
Pág. Nº 125
Programación de Computadores y el Lenguaje C
(versión 2003)
Estructuras
Las estructuras son conjuntos de una o más variables que pueden ser de igual o distinto
tipo (En Pascal se llaman register --> registro). La idea de agrupar datos en estructuras es
para relacionar más adecuadamente esos datos. Por ejemplo, si deseáramos mantener en un
programa los datos personales de un individuo, primero tendríamos que identificar los
atributos de interés, como ser nombre, apellido, edad, fono, etc. Luego, crear una variable para
cada atributo (eso con los conocimientos adquiridos hasta ahora). Finalmente hacer las
operaciones.
Las estructuras nos permiten trabajar dichos datos como una sola variable. Para
definir una estructura se hace de la siguiente manera:
struct <nombre_estructura>
{
<tipo> <nombre_campo1>;
<tipo> <nombre_campo2>;
:
:
}[lista de variables];
Entonces, para el ejemplo anterior podríamos definir la siguiente estructura:
struct persona
{
char nombre[30];
char apellido[30];
int edad;
long fono;
};
La palabra clave struct declara una estructura, el identificador persona es el nombre
de la estructura y puede ser utilizado en declaraciones posteriores. Cada elemento de la
estructura se conoce como "miembro" o "campo". Al final de la llave pueden ir identificadores
que serán variables del tipo indicado (struct persona); esto reserva memoria para cada una
de las variables.
Ejemplo:
struct persona
{
char nombre[30];
char apellido[30];
int edad;
long fono;
}pers1,pers2;
También se pueden declarar variables locales como:
:
:
main()
{
struct persona persona3;
Profesor: Roberto Uribe P.
Pág. Nº 126
Universidad de Magallanes - Departamento de Ingeniería en Computación
struct persona persona4={"Rene","Mancilla",18,222120L};
.
.
.
}
En la segunda declaración (persona4), se inicializó con datos.
En términos reales una estructura define un nuevo tipo de dato, y por lo tanto pueden
aplicársele todo lo visto para los tipos de datos básicos, como ser: crear arreglos de
estructuras, punteros a estructuras, estructuras como parámetros a funciones, etc.
Para referirse a un miembro de una estructura se hace de la siguiente manera:
<nombre_estructura>.<nombre_miembro>
Por ejemplo, para ingresar datos vía teclado a la variable persona3, sería:
:
:
printf("\nIngrese datos");
printf("\nNombre :");
scanf("%s",persona3.nombre);
printf("\nApellido:");
scanf("%s",persona3.apellido);
printf("\nEdad
:");
scanf("%d",&persona3.edad);
printf("\nFono
:");
scanf("%ld",&persona3.fono);
:
:
Es decir, de la misma manera que si fuese cualquier variable. Lo único a considerar es
en el caso de leer el campo nombre (arreglos), si este se leyera caracter a caracter el subíndice
se asocia a el miembro y no a la variable struct. Osea, la forma correcta sería:
for(i=0;i<30;i++)
scanf("%c",persona3.nombre[i]);
y no,
scanf("%c",persona3[i].nombre);
que sería un error, ya que se estaría hablando del miembro nombre de la componente i de un
arreglo llamado persona3.
Arreglos de estructuras
Comúnmente las estructuras se llaman registros, en la vida real estos registros se
agrupan, es decir, usualmente en nuestras aplicaciones necesitamos conjuntos de datos y no
solamente datos independientes, ejemplo de esto es, una lista de empleados, una lista de
clientes, una lista de cuentas corrientes, etc. Cada uno de estos conjuntos agrupan a n registros
(estructuras) y cada registro contiene datos particulares que caracterizan al elemento.
Profesor: Roberto Uribe P.
Pág. Nº 127
Programación de Computadores y el Lenguaje C
(versión 2003)
Como cualquier otro tipo, se pueden crear arreglos donde cada una de sus
componentes sea una estructura. Por ejemplo, se podría crear un arreglo para mantener una
lista de nombres de personas. Usando la estructura anterior, esto podría ser:
:
main()
{
struct persona individuos[100];
:
/* para 100 personas */
y luego referirnos a alguna componente como:
individuos[0].nombre
para hablar del miembro nombre de la persona ubicada en la posición 0 del arreglo. Un
arreglo de estructuras se maneja como cualquier otro arreglo.
Estos conjuntos de datos los encontramos con el nombre de Bases de Datos, aunque
formalmente cada conjunto de datos es una Tabla que forma parte de una Base de Datos. Nos
podemos imaginar que una Tabla es un archivo en nuestro disco duro, sin embargo, como aún
no poseemos los conocimientos para trabajar con archivos en C, podemos trabajar con los
datos en memoria, a través de arreglos.
Ejercicio Nº 8.1: Crear un programa que permita mantener una lista con los datos de
los obreros de una empresa, para ello suponga que el máximo de obreros es de 40. Además,
agregue los datos correspondientes a, número de horas extra que hace en el mes, el valor por
hora extra que se le paga a cada obrero y el total de sobresueldo (horas extra por valor hora).
Realice las siguientes operaciones: Ingresar el total de datos para los 40 obreros, luego
imprima una lista resumida de los datos (sólo nombre, horas extra, valor por hora y total).
Solución: Como se pide una lista de 40 obreros podemos crear una estructura que
contenga los atributos importantes de los obreros, luego declarar un arreglo de 40 para
contener todos los registros necesarios.
#include <stdio.h>
#define MAX 40
struct obrero
/* definicion de estructura */
{
char nombre[30];
/*incluido apellido*/
char direccion[30];
int hextra;
/*Horas Extras*/
int hvalor;
/*Valor por Hora*/
long total;
/*hextra*hvalor*/
};
main()
{
struct obrero personal[MAX];
int i=0;
for (i=0;i<MAX;i++)
Profesor: Roberto Uribe P.
Pág. Nº 128
Universidad de Magallanes - Departamento de Ingeniería en Computación
{
printf("Nombre
:");
scanf("%s,personal[i].nombre);
printf("\nDireccion
:");
scanf("%d",personal[i].dirección);
printf("\nHoras Extras :");
scanf("%d",&personal[i].hextra);
printf("\nValor Hora
:");
scanf("%d",&personal[i].hvalor);
printf("\n\n);
personal[i].total=personal[i].hextra*personal[i].hvalor;
}
system("clear") /*llamada al comando de 'borrar pantalla' en Unix*/
printf("\nNombre\t\tHoras\t\tValor\t\tTotal\n")
for (i=0;i<MAX;i++)
{
printf("%s",personal[i].nombre);
printf("\t\t%d",personal[i].hextra);
printf("\t\t%d",personal[i].hvalor);
printf("\t\t%d",personal[i].total);
}
return 0;
}
Una modificación a nuestro programa para reemplazar struct obrero en las
declaraciones es usar typedef para renombrar el tipo. Para esto hay que agregar la sentencia
despues de la definición de la estructura y colocar por ejemplo:
nuevo tipo
typedef struct obrero empleados;
tipo
y con esto se puede usar el tipo empleados en vez del tipo struct obrero. Entonces, en el
main la declaración del arreglo personal puede ser reemplazada por:
empleados personal[MAX];
En este ejercicio, se podría haber utilizado funciones para las operaciones, de esta
manera el programa es más modular. Por ejemplo, el llenado del arreglo podría ser:
void llenar(empleados perso[MAX])
{
int i=0;
for (i=0;i<MAX;i++)
{
printf("Nombre
:");
scanf("%s,perso[i].nombre);
printf("\nDireccion
:");
scanf("%d",perso[i].dirección);
printf("\nHoras Extras :");
scanf("%d",&perso[i].hextra);
printf("\nValor Hora
:");
scanf("%d",&perso[i].hvalor);
printf("\n\n);
perso[i].total=perso[i].hextra*perso[i].hvalor;
}
}
Profesor: Roberto Uribe P.
Pág. Nº 129
Programación de Computadores y el Lenguaje C
(versión 2003)
entonces, la llamada a esta función desde el main es:
llenar(personal);
Note que el parámetro pasado a la función es todo el arreglo de estructuras, es por eso
que cada uno de sus componentes sale modificado, si se hubiese pasado a la función cada
componente tendríamos que haber usado parámetros pasados por referencia.
Ejercicio Nº8.2: Para el ejercicio anterior, crear una función que permita responder la
siguiente pregunta, dado el nombre de un obrero ¿Cual es el sobresueldo que obtiene este
mes?. Supongamos que el nombre se ingresa fuera de esta función, entonces este es un
parámetro que ingresa a dicha función.
Solución: Con lo planteado en el ejercicio, entonces entendemos que los parámetros
que se pasan a la función son el string que contiene el nombre ingresado y el arreglo personal,
esto último ya que este es local (al main). Entonces la cabecera de nuestra función sería:
void busca(empleados personal[MAX],char *nom)
El algoritmo principal que hay que implementar dentro de la función es el buscar un
obrero específico, esto implica que cada obrero del arreglo debe compararse con el nombre
ingresado. Lo anterior significa que hay que recorrer el arreglo, es decir, necesitamos un for.
Para comparar dos string podemos usar la función strcmp de la librería string.h. Entonces,
nuestra función completa quedaría:
void busca(empleados personal[MAX],char *nom)
{
int i;
for (i=0;i<MAX;i++)
{
if (strcmp(personal[i].nombre,nom)==0)
{
printf("\nEl obrero %s tiene un sobre sueldo de %ld\n",
personal[i].nombre,personal[i].total);
return; /*para terminar la funcion cuando el nombre es encontrado*/
}
}
printf("\nError, el nombre no existe\n");
return;
}
Recuerde que el pasar el parámetro nom como char *nom, es decir como puntero,
implica que el tamaño del string nom es el tamaño del string que fue pasado en la llamada a la
función.
Ejercicio Nº 8.3: Una aplicación que sería de interés realizar y que es muy común en
los sistemas de bases de datos es el ordenamiento de los datos según algún atributo. Entonces,
para el ejercicio 14 hacer una función a la cual se le pase como parámetro el arreglo de datos
y esta realice el ordenamiento por nombre del obrero.
Solución: Como la información esta contenida en un arreglo y conocemos el método de
ordenamiento llamado burbuja, poseemos entonces lo necesario para implementar la función.
La única diferencia con el método anteriormente visto, es que ahora las comparaciones de los
Profesor: Roberto Uribe P.
Pág. Nº 130
Universidad de Magallanes - Departamento de Ingeniería en Computación
subíndices del arreglo se deben hacer utilizando las funciones de string.h, ya que los elementos
a comparar son strings.
void ordena(empleados personal[MAX])
{
int i=0,j=0,auxext,auxval;
long auxtot;
char aux[30];
for(i=0;i<(MAX-1);i++)
for(j=i+1;j<MAX;j++)
if (strcmp(personal[i].nombre,personal[j].nombre)>0)
{
strcpy(aux,personal[i].nombre);
/*intercambia nombre*/
strcpy(personal[i].nombre,personal[j].nombre);
strcpy(personal[j].nombre,aux);
strcpy(aux,personal[i].direccion); /*intercambia direccion*/
strcpy(personal[i].direccion,personal[j].direccion);
strcpy(personal[j].direccion,aux);
auxext=personal[i].hextra;
/*intercambia hextra*/
personal[i].hextra=personal[j].hextra;
personal[j].hextra=auxext;
auxval=personal[i].hvalor;
/*intercambia hvalor*/
personal[i].hvalor=personal[j].hvalor;
personal[j].hvalor=auxval;
auxtot=personal[i].total;
/*intercambia total*/
personal[i].total=personal[j].total;
personal[j].total=auxtot;
}
return;
}
Como en los casos anteriores el arreglo sale modificado. Note que se usaron las
funciones strcmp para comparar y strcpy para copiar.
Algo más sobre estructuras
Cuando se declara una variable de un tipo estructura, sus miembros se ubican en
posiciones contiguas de memoria.
Sobre estructuras también se puede aplicar el operador tamaño de, los siguientes
ejemplo son correctos:
printf("%d",sizeof(empleados));
printf("%d",sizeof(personal));
En la primera sentencia se imprimiría el tamaño en bytes del tipo empleados (al menos
30+30+2+2+4). En la segunda sentencia se imprime el tamaño de todo el arreglo de
estructuras.
Estructuras Anidadas
Se produce un anidamiento de estructuras cuando el miembro de una estructura es otra.
Profesor: Roberto Uribe P.
Pág. Nº 131
Programación de Computadores y el Lenguaje C
(versión 2003)
Ejemplo:
#include <stdio.h>
#define N 50
#define LARGO 30
struct fecha {
int dia;
int mes;
int anho;
};
struct identifica {
char nombre[LARGO];
char apellido[LARGO];
struct fecha fnac;
char direccion[LARGO];
/*...*/
char ciudad[LARGO];
};
struct obrero{
struct identifica datos;
long sueldobase;
};
main()
{
struct fecha auxdia,otrodia={1,1,1990};
struct identifica auxperson;
struct obrero listado[N];
:
:
}
En este caso se definieron tres estructuras, donde las dos últimas contienen un miembro
que es una estructura. Para referirse al miembro de una estructura que a su vez es otra se usa
igualmente el operador punto. Por ejemplo:
auxdia.anho
auxperson.fnac.dia
listado[i].datos.fnac.dia
En el primer caso se habla del miembro anho de la variable auxdia, en el segundo del
miembro dia del miembro fnac de la variable auxperson. En el último caso, como nos
referimos a un arreglo hablamos del campo dia del miembro fnac del miembro datos de la
componente i del arreglo listado.
Entonces, para llenar de valores a la estructura auxperson se debe hacer:
scanf("%s",auxperson.nombre);
scanf("%s",auxperson.apellido);
scanf("%d",&auxperson.fnac.dia);
scanf("%d",&auxperson.fnac.mes);
scanf("%d",&auxperson.fnac.anho);
:
:
Profesor: Roberto Uribe P.
Pág. Nº 132
Universidad de Magallanes - Departamento de Ingeniería en Computación
Funciones y Estructuras
En las actuales versiones de C se permiten realizar ciertas operaciones que en
versiones anteriores no, como por ejemplo hacer la asignación directa de una estructura a
otra, pasar como parámetro a una función una estructura completa y que una función retorne
un tipo estructura.
Para el caso de la asignación, la copia se realiza miembro a miembro. Suponga que
existe una variable pers2 del tipo struct identifica, en el siguiente ejemplo se realiza la
asignación de auxperson a pers2.
pers2=auxperson;
También se puede hacer la copia de un solo miembro,
strcpy(pers2.nombre,auxperson.nombre);
Para ver el paso de una estructura como parámetro y el retorno de una estructura,
revisemos el siguiente ejemplo:
#include <stdio.h>
#include <string.h>
struct obrero{
char nombre[30];
int edad;
long fono;
};
void muestra(struct obrero x);
struct obrero asigna();
main()
{
struct obrero aa,bb={"Perico",28,226368L}; /* L al final indica que es
long*/
muestra(bb);
aa=asigna();
muestra(aa);
return 0;
}
void muestra(struct obrero x)
{
printf("\nNombre : %s",x.nombre);
printf("\nEdad
: %d",x.edad);
printf("\nFono
: %ld\n",x.fono);
return;
}
struct obrero asigna()
{
struct obrero x;
scanf("%s",x.nombre);
scanf("%d",&x.edad);
scanf("%ld",&x.fono);
return(x);
}
Profesor: Roberto Uribe P.
Pág. Nº 133
Programación de Computadores y el Lenguaje C
(versión 2003)
La novedad de este ejemplo es que una función retorna un variable de tipo struct
que a su vez es asignada a la variable del mismo tipo aa. La función muestra sólo
imprime los valores del parámetro en pantalla.
obrero
Punteros a Estructuras
Otra forma de asignarle valores a una estructura es que los valores se asignen dentro
de la función, para ello debemos usar parámetros por referencia, es decir, pasar la dirección
de memoria de la estructura, por ejemplo, nuestra función asigna puede ser llamada de la
siguiente manera:
asigna(&aa);
entonces, debería ser declarada anteriormente como
void asigna();
En la cabecera de la función debemos declarar el parámetro como un puntero a una
estructura, es decir,
void asigna(struct obrero *est)
donde est es un puntero.
Dentro de la función debemos utilizar el contenido de la variable. Para hacer más
entendible declararemos una variable auxiliar y luego copiaremos los datos.
void asigna(struct obrero *est)
{
struct obrero x;
scanf("%s",x.nombre);
scanf("%d",&x.edad);
scanf("%ld",&x.fono);
strcpy((*est).nombre,x.nombre);
(*est).edad=x.edad;
(*est).fono=x.fono;
return;
}
Como sabemos, para hablar del contenido de un puntero utilizamos el operador *, sin
embargo, no podemos hacer:
*est.edad
ya que sería un error, esto debido a que el operador punto tiene mayor prioridad que el
operador asterisco, por ello se utilizan los paréntesis. En el caso anterior, se estaría hablando
del contenido del miembro edad de la variable est, es decir, edad debería ser un puntero, y eso
no es así.
Para simplificar nuestra función sólo bastaba hacerla de la siguiente manera:
Profesor: Roberto Uribe P.
Pág. Nº 134
Universidad de Magallanes - Departamento de Ingeniería en Computación
void asigna(struct obrero *est)
{
scanf("%s",(*est).nombre);
scanf("%d",&(*est).edad);
scanf("%ld",&(*est).fono);
return;
}
Es posible reemplazar
(*est).edad
por
est->edad
donde, el operador -> reemplaza el formato anterior. Este operador es comúnmente llamado
operador flecha y se construye con el signo menos seguido del signo mayor que.
Algo más sobre punteros a estructuras
Supongamos que se declara un puntero a la estructura fecha definida anteriormente,
como fue indicado debe usarse el operador flecha para referirse a sus miembros:
:
struct fecha *f;
:
f->dia=10;
f->mes=06;
f->anho=1968;
:
Entonces, la siguiente sentencia:
++f->dia;
incrementa el miembro dia, es decir, sería equivalente a escribir:
f->dia=f->dia+1;
o
++(f->dia);
En cambio, la sentencia
(++f)->dia;
incrementaría el puntero f, es decir, f apuntaría a la siguiente dirección de memoria donde
exista una variable de tipo struct fecha, en la componente dia.
Suponga ahora que tenemos las siguientes definiciones antes del main
Profesor: Roberto Uribe P.
Pág. Nº 135
Programación de Computadores y el Lenguaje C
(versión 2003)
:
:
struct fecha {
int dia;
int mes;
int anho;
};
typedef struct fecha *puntfec;
:
si dentro del main declaramos una variable como:
main
{
puntfec f2;
:
entonces f2 es un puntero a la estructura fecha, formalmente es una variable del tipo puntfec
(que es un puntero), y por lo tanto debe manejarse como lo visto anteriormente.
Si hiciésemos la siguiente declaración,
puntfec *f3;
entonces, f3 es un puntero al tipo puntfec que a su vez en un puntero. En otras palabras f3 es
un puntero a un puntero a fecha. Por lo tanto, para referirnos a el contenido de f3 debemos
usar el operador *, esto tomando en cuenta la prioridad del operador flecha (que es mayor).
Osea,
(*f3)->anho
para referirnos al miembro año de la variable.
Uniones (unión)
Una unión es una variable que permite almacenar tipos de datos distintos en el mismo
espacio de memoria. La sintaxis de una variable unión es idéntica a una variable struct. Ej.:
union unica {
int entero;
float flotante;
char caracter;
};
Las declaraciones son de la siguiente forma:
union unica valores;
union unica arrunion[5];
union unica *ptrunion;
La idea es que el compilador guarda espacio para almacenar la mayor de las
alternativas, es decir, el tamaño en bytes del miembro que ocupa más espacio, en este caso, el
tamaño para un float.
Sí
Profesor: Roberto Uribe P.
Pág. Nº 136
Universidad de Magallanes - Departamento de Ingeniería en Computación
int
es de 2 bytes
float es de 4 bytes
char es de 1 byte.
Para el caso de la variable valores, guarda un espacio de 4 bytes.
Sí la variable valores hubiese sido un struct, entonces se hubiese podido almacenar los
tres miembros, sin embargo, como es unión sólo almacena uno.
Ejemplo: Asignación a uniones.
valores.entero=505;
Asigna 505 a la unión ocupando 2 bytes.
valores.flotante=0.6;
Borra el dato 505 y almacena 0.6 en 4 bytes.
Se puede representar valores gráficamente como:
Si fuese estructura:
float
int
char
Si fuese union:
char
int
float
Entonces, el tamaño es de 4 bytes, si fuese struct contendría 7 bytes.
Profesor: Roberto Uribe P.
Pág. Nº 137
Programación de Computadores y el Lenguaje C
(versión 2003)
Problemas Resueltos y Propuestos
Resueltos:
Problema específico :
Existe un programa escrito en C, dicho programa se encarga de manejar los datos de
una empresa, esta puede tener hasta un máximo de 100 empleados.
Para lo anterior se utilizan las siguientes estructuras:
struct obrero{
int codigo;
char apellido[30];
char nombre[25];
int Edad;
char direccion[35];
int coddepto;
};
struct depto{
int coddepto;
char nomdepto[35];
};
struct sueldo{
int codigo;
long sueldobase;
long otros;
};
struct aux{
int codigo;
char apellido[30];
};
No se preocupe de la forma de lectura y almacenamiento en disco, sólo realice las
funciones que se le piden.
Al ejecutarse el programa, este carga los datos en las siguientes variables:
struct obrero arobrero[100];
/*arreglo que contiene los datos de cada
obrero */
struct depto ardepto[30];
/*arreglo que contiene el codigo y nombre de
cada depto. */
struct sueldo arsueldo[100];
/*arreglo que contiene los sueldos de los
empleados */
struct aux obreord[100]; /* arreglo de enteros */
Todas estas variables son globales.
Cada obrero al ser ingresado al sistema se le asigna un número (código), este es en
orden correlativo.
Dicho código se utiliza para identificar al obrero, por ejemplo, en el arreglo arsueldo,
se ocupa para indicar el sueldo de un obrero cuyo código esta indicado.
Profesor: Roberto Uribe P.
Pág. Nº 138
Universidad de Magallanes - Departamento de Ingeniería en Computación
Ejemplo:
Arreglo arobrero:
codigo
apellido
1
2
3
4
5
6
:
:
:
-1
-1
-1
nombreedad
direccion
coddepto
Alexander
Sanchez
Valencia
Gonzalez
Gonzalez
Nicolai
Alfredo
Josefina
Ramiro
Miguelina
25
32
29
25
31
O'Higgins 54
R. Correa 1050
I. C. Pinto 33
Mardones 0166
Bories
1420
3
4
3
2
3
......
......
......
......
......
......
.....
.....
.....
.....
..
..
..
..
.....
.....
.....
.....
..
..
..
..
Arreglo ardepto:
coddepto
1
2
3
.
.
.
-1
-1
nomdepto
Administración
Mantención
Informática
.....
.....
.....
.....
.....
Arreglo arsueldo:
codigosueldobase
1
2
3
4
.
.
.
-1
-1
230.000
180.000
200.000
140.000
otros
51.000
33.000
40.000
23.000
....
...
....
...
Entonces Josefina Valencia del departamento
40.000 pesos. (los datos con -1 indican vacíos o nulos).
de Informática gana 200.000 +
Problemas:
1.-
Se le pide crear un módulo (función) que realice lo siguiente:
Ingrese un nombre de departamento y que imprima en pantalla el apellido, nombre,
sueldo total (sueldobase+otros) de todos los empleados pertenecientes a ese depto.
2.Usando las estructuras y arreglos anteriores, haga una función que lea por teclado un
nombre de departamento y un valor numérico, luego imprima las personas que pertenecen a
ese departamento (apellido, nombre, sueldototal), con la condición de que el sueldo total no
Profesor: Roberto Uribe P.
Pág. Nº 139
Programación de Computadores y el Lenguaje C
(versión 2003)
supere el valor numérico ingresado. Es decir todas las personas del depto .... que ganen menos
de .....
3.Escriba una función que ordene los obreros alfabéticamente por apellido de menor a
mayor (a-z). Para ello no modifique el arreglo original, duplique en otro arreglo (obreord), el
código y apellido. Luego de duplicado y ordenado, imprima en pantalla el apellido, nombre y
sueldo total, obviamente ordenado por apellido.
4.La empresa a estimado aumentar el sueldo base de todos los empleados de un
departamento determinado en un 10 %.
Cree una función que realice este proceso. Es decir que lea el nombre de un depto. y
que luego aumente en 10 % el sueldo base de todos los empleados de dicho depto.
Propuestos:
Problema específico
Con los conocimientos que hasta ahora poseemos, suponga que se diseño una Base de
Datos para llevar el control de las inscripciones de créditos de los alumnos de la carrera de
Ingeniería de Ejecución en Computación e Informática. Dicha Base de Datos tiene las
siguientes Tablas:
ALUMNOS(n_mat,rut,apellido,nombre)
ASIGNATURAS(cod,nombre_ramo,tel,descripcion,semcar,annocar)
INSCRIPCION(n_mat,cod,semestre,anno)
NOTAS(n_mat,cod,opcion,semestre,anno,promedio)
REQUISITOS(cod,cod_req)
Donde:
n_mat
cod
tel
semcar
annocar
semestre
anno
cod_req
:
:
:
:
:
:
:
:
número de matrícula del alumno,
código de la asignatura,
horas de teoría, ejercicios y laboratorio,
semestre en que se dicta la asignatura,
año en que se dicta la asignatura.
semestre en que se inscribió el ramo(1 del segundo año),
año en que se inscribió el ramo (ej. 1 de 1996)
código del ramo requisito.
La representación de sus estructuras en C es:
struct alumnos{
long n_mat ;
long rut;
char apellido[30];
char nombre[30];
}alum[200];
struct asignaturas{
char cod[7];
char nombre_ramo[30];
int tel;
char descripcion[40];
Profesor: Roberto Uribe P.
Pág. Nº 140
Universidad de Magallanes - Departamento de Ingeniería en Computación
int semcar;
int annocar;
}asig[25];
struct inscripcion{
long n_mat;
char cod[7];
int semestre;
int anno;
}insc[5000];
/* este arreglo es sólo un supuesto */
struct notas{
long n_mat;
char cod[7];
int opcion;
int semestre;
int anno;
float promedio;
}not[5000];
struct requisitos{
char cod[7];
char cod_req[7];
}req[100];
Explicación:
Para la siguiente serie de problemas asuma que los arreglos ya contienen los datos, Ud.
sólo debe responder las consultas que se le piden.
Como ejemplo de los datos contenidos en los arreglos, suponga:
:
asignaturas[10]=("PRG2161","Programación
de
Computadores","402","Programación
C",1,2)
:
asignaturas[15]=("LII3161","Lenguajes
de
Programación
II","402","Orientación
Objetos",1,3)
:
:
inscripcion[405]=(9416035,"PRG2161",1,1996)
inscripcion[406]=(9516010,"PRG2161",1,1996)
:
en
a
Nota: los datos dentro de los arreglos NO ESTÁN ORDENADOS!!,
Problemas :
5.Imprima una lista con los nombres y apellidos de todos los alumnos que inscribieron
ramos en un semestre y año determinado, por ejemplo: el segundo semestre de 1995. Los
parámetros semestre y año son ingresados a la función (no leídos por teclado).
6.La alumna Carolina Bonacic puede inscribir Lenguajes de Programación I??, es
decir, ingrese como parámetro a una función dos string, el primero un nombre de alumno y el
segundo un nombre de un ramo. Verifique que el alumno puede o no inscribir el ramo, retorne
1 si puede y 0 si no. Recuerde que un ramo puede tener como requisito más de una asignatura.
7.Imprimir la lista de los créditos tomados por el alumnos Juan Coñuecar el segundo
semestre de 1995, indicando :
Profesor: Roberto Uribe P.
Pág. Nº 141
Programación de Computadores y el Lenguaje C
Nombre Ramo
8.-
(versión 2003)
Opción
TEL
Promedio
Suponga que en la cabecera de un programa aparecen las siguientes definiciones:
struct num
{
int re;
int im;
};
/*parte real*/
/*parte imaginaria*/
typedef num imagin;
Es decir, se tiene un nuevo tipo de dato para el manejo de números imaginarios. Ud.
debe de implementar las funciones que permitar realizar la suma, resta, multiplicación y
división de dos numeros imaginarios. Las funciones deben ingresar ambos operandos y
retornar el resultado. Sólo se pide las funciones.
9.Uno de los problemas que hay al finalizar un Semestre Académico es preparar el
horario para el semestre siguiente, este problema es sumamente complejo, a Ud. se le pide
implementar una pequeña parte de la solución.
Debe hacer un programa que permita ingresar el nombre de un ramo (string), el
número de semestre (int) a el horario, para ello tome en cuenta el horario tiene 6 bloques y 5
días, donde cada bloque puede ser ocupado por una asignatura.
Haga la implementación completa para el ingreso de los datos y validaciones. Es decir,
el usuario ingresa un bloque y un día y el programa debe indicar si este ya esta ocupado por
alguna asignatura, si no es así, entonces puede ingresar los datos correspondientes
Simplificación del problema: Asuma que la solución es sólo para una carrera. No tome
en cuenta el número de alumnos por sala.
Recomendaciones: Primero resuelva el problema suponiendo que existe sólo una sala.
Luego para n salas, por ejemplo 5.
Para aumentar la complejidad del problema modifique el programa para que las
asignaturas puedan ocupar máximo dos bloques a la semana.
Profesor: Roberto Uribe P.
Pág. Nº 142
Universidad de Magallanes - Departamento de Ingeniería en Computación
Profesor: Roberto Uribe P.
Pág. Nº 143
Programación de Computadores y el Lenguaje C
(versión 2003)
Capítulo Nº9 : Archivos en C (en construcción)
Tipo usado en archivos
FILE
El tipo FILE es definido por C en el archivo de cabecera stdio.h para el manejo de
archivos. Esta es una estructura, y se usará siempre con punteros a esta.
La definición de ésta estructura depende del compilador, pero en general mantienen un
campo con la posición actual de lectura/escritura, un buffer para mejorar las prestaciones de
acceso al archivo y algunos campos para uso interno.
Funciones para manejo de archivos
A continuación se presenta un listado de las funciones más frecuentes para la
administración de archivos en C.
Función fopen :
Sintaxis:
FILE *fopen(char *nombre, char *modo);
Esta función sirve para abrir y crear archivos en disco. El valor de retorno es un puntero
a una estructura FILE. Los parámetros de entrada son:
1. nombre: una cadena que contiene un nombre de archivo válido, esto depende del
sistema operativo que estemos usando. El nombre puede incluir el camino completo.
2. modo: es una cadena que especifica el tipo de archivo que se abrirá o se creará y el tipo
de datos que puede contener, de texto o binarios:
o
r: sólo lectura. El archivo debe existir.
o
w: se abre para escritura, se crea un archivo nuevo o se sobrescribe si ya existe.
o
a: añadir, se abre para escritura, el cursor se sitúa al final del archivo. Si el
archivo no existe, se crea.
o
r+: lectura y escritura. El archivo debe existir.
o
w+: lectura y escritura, se crea un archivo nuevo o se sobrescribe si ya existe.
o
a+: añadir, lectura y escritura, el cursor se sitúa al final del archivo. Si el
archivo no existe, se crea.
o
t: tipo texto, si no se especifica t ni b, se asume por defecto que es t
o
b: tipo binario.
Profesor: Roberto Uribe P.
Pág. Nº 144
Universidad de Magallanes - Departamento de Ingeniería en Computación
Por ejemplo: el siguiente código hable un archivo para lectura y escritura de tipo binario. Al
comienzo aparece declarada la variable fdata como puntero a FILE.
FILE *fdata;
:
:
if ((fdata=fopen("datos.dat","r+b"))==NULL)
{
printf("Error al abrir archivo");
return 0;
}
Función fclose :
Sintaxis:
int fclose(FILE *archivo);
Cierra un archivo y almacena los datos que aún están en el buffer de memoria,
actualiza algunos datos de la cabecera del archivo que mantiene el sistema operativo.
Además permite que otros programas puedan abrir el archivo para su uso.
Un valor de retorno cero indica que el archivo ha sido correctamente cerrado, si ha
habido algún error, el valor de retorno es la constante EOF. El parámetro es un puntero a la
estructura FILE del archivo que queremos cerrar.
Función fgetc :
Sintaxis:
int fgetc(FILE *archivo);
Esta función lee un carácter desde un archivo.
El valor de retorno es el carácter leído como un unsigned char convertido a int. Si
no hay ningún carácter disponible, el valor de retorno es EOF. El parámetro es un puntero a
una estructura FILE del archivo del que se hará la lectura.
Función fputc :
Sintaxis:
int fputc(int caracter, FILE *archivo);
Esta es la función inversa a fgetc y escribe un carácter a un archivo.
El valor de retorno es el carácter escrito, si la operación fue completada con éxito, en
caso contrario será EOF. Los parámetros de entrada son el carácter a escribir, convertido a
int y un puntero a una estructura FILE del archivo en el que se hará la escritura.
Profesor: Roberto Uribe P.
Pág. Nº 145
Programación de Computadores y el Lenguaje C
(versión 2003)
Función feof :
Sintaxis:
int feof(FILE *archivo);
Esta función permite verificar si se ha llegado el fin del archivo. Muy frecuentemente
deberemos trabajar con todos los valores almacenados en un archivo de forma secuencial, la
forma que suelen tener los cilos o bucles para leer todos los datos de un archivo es
permanecer leyendo mientras no se detecte el fin de archivo. Por ejemplo:
while(!feof(fDB))
El valor de retorno es cero mientras no se haya alcanzado el fin de archivo (cuando
llega al final del archivo retorna un número distinto de cero). El parámetro es un puntero a
la estructura FILE.
Función rewind :
Sintaxis:
void rewind(FILE *archivo);
La función rewind o rebobinar, ubica el control del archivo al principio de este.
Esta función es heredada de los tiempos de las cintas magnéticas, donde literalmente había
que rebobinar la cita hasta el principio.
Función fgets :
Sintaxis:
char *fgets(char *cadena, int n, FILE *archivo);
Esta función está diseñada para leer cadenas de caracteres. Leerá hasta n-1
caracteres o hasta que lea un retorno de línea. En este último caso, el carácter de retorno de
línea también es leído.
El parámetro n nos permite limitar la lectura para no exceder el límite de la cadena.
El valor de retorno es un puntero a la cadena leída, si se leyó con éxito, y es NULL si
se detecta el final del archivo o si hay un error. Los parámetros son: la cadena a leer, el
número de caracteres máximo a leer y un puntero a una estructura FILE del archivo del que
se leerá.
Función fputs :
Profesor: Roberto Uribe P.
Pág. Nº 146
Universidad de Magallanes - Departamento de Ingeniería en Computación
Sintaxis:
int fputs(const char *cadena, FILE *archivo);
La función fputs escribe una cadena en un archivo. No se añade el carácter de
retorno de línea ni el carácter nulo final.
El valor de retorno es un número no negativo o EOF en caso de error. Los parámetros
de entrada son la cadena a escribir y un puntero a la estructura FILE del archivo donde se
realizará la escritura.
Función fread :
Sintaxis:
size_t fread(void *varreg, size_t tamaño, size_t nreg, FILE*archivo);
Esta función está pensada para trabajar con registros de longitud constante. Es
capaz de leer desde un archivo uno o varios registros de la misma longitud y a partir de una
dirección de memoria determinada. El usuario es responsable de asegurarse de que hay
espacio suficiente para contener la información leída. El valor de retorno es el número de
registros leídos, no el número de bytes. Los parámetros son: un puntero a la zona de
memoria donde se almacenarán los datos leídos (varreg), el tamaño de cada registro, el
número de registros a leer y un puntero a la estructura FILE del archivo del que se hará la
lectura.
Función fwrite :
Sintaxis:
size_t fwrite(void *varreg,size_t tamaño,size_t nreg, FILE *archivo);
Esta función es equivalente a fread, pero para escritura. Puede de escribir en un
archivo uno o varios registros de la misma longitud almacenados a partir de una dirección
de memoria determinada.
El valor de retorno es el número de registros escritos, no el número de bytes. Los
parámetros son: un puntero a la zona de memoria donde se almacenarán los datos leídos, el
tamaño de cada registro, el número de registros a leer y un puntero a la estructura FILE del
archivo del que se hará la lectura.
Función fprintf :
Sintaxis:
int fprintf(FILE *archivo, const char *formato, ...);
Es equivalente a printf, pero la salida se dirige a un archivo en lugar de la salida
estandar.
Profesor: Roberto Uribe P.
Pág. Nº 147
Programación de Computadores y el Lenguaje C
(versión 2003)
Función fscanf :
Sintaxis:
int fscanf(FILE *archivo, const char *formato, ...);
Equivalente a scanf, pero la entrada se toma de un archivo en lugar del teclado (o
entrada estandar stdin).
Función fflush :
Sintaxis:
int fflush(FILE *archivo);
fuerza la salida de los datos acumulados en el buffer de salida del archivo.
Para mejorar las prestaciones del manejo de archivos se utilizan buffers, almacenes
temporales de datos en memoria, las operaciones de salida se hacen a través del buffer, y
sólo cuando el buffer se llena se realiza la escritura en el disco y se vacía el buffer. En
ocasiones nos hace falta vaciar ese buffer de un modo manual, para eso sirve ésta función.
fflush
El valor de retorno es cero si la función se ejecutó con éxito, y EOF si hubo algún
error. El parámetro de entrada es un puntero a la estructura FILE del archivo del que se
quiere vaciar el buffer. Si es NULL se hará el vaciado de todos los archivos abiertos.
Función fseek :
Sintaxis:
int fseek(FILE *archivo,long int desplazamiento,int origen);
Situa el control de archivo (o cursor) para leer o escribir en un lugar especificado en
desplazamiento.
El valor de retorno es cero si la función tuvo éxito, y un valor distinto de cero si hubo
algún error.
Los parámetros de entrada son: un puntero a una estructura FILE del archivo en el
que queremos cambiar el cursor de lectura/escritura, el valor del desplazamiento(en bytes)
y el punto de origen desde el que se calculará el desplazamiento.
El parámetro origen puede tener tres posibles valores:
1. SEEK_SET : el desplazamiento se cuenta desde el principio del archivo. El primer byte
del archivo tiene un desplazamiento cero.
2. SEEK_CUR : el desplazamiento se cuenta desde la posición actual del cursor.
3. SEEK_END : el desplazamiento se cuenta desde el final del archivo.
Profesor: Roberto Uribe P.
Pág. Nº 148
Universidad de Magallanes - Departamento de Ingeniería en Computación
Función ftell :
Sintaxis:
long int ftell(FILE *archivo);
La función ftell sirve para averiguar la posición actual del cursor de lectura/escritura
de un archivo.
El valor de retorno será esa posición en bytes, o -1 si hay algún error.
Función remove :
Sintaxis :
int remove(char *filename);
Esta función sirve para borrar permanentemente un archivo. El parámetro de entrada
es la ruta absoluta o relativa del archivo a borrar.
Si la eliminación del archivo fue exitosa, el valor de retorno es cero, de lo contrario
es un número distinto de cero.
Un ejemplo interesante con fread
Supongamos la siguiente estructura:
struct _obrero
{
char nombre[30];
int edad;
long fono;
};
typedef _obrero obrero;
supongamos ahora que se tiene un archivo llamado datos.dat, el cual fue escrito usando la
misma estructura (con fwrite). Entonces para leer todo el archivo y mostrarlo en pantalla
sería:
void muestradatos()
{
FILE *fdata;
Int total=0;
obrero a;
if ((fdata=fopen("datos.dat","r+b"))==NULL)
{
printf("Error al abrir archivo");
return;
}
while(!feof(fdata))
{
Profesor: Roberto Uribe P.
Pág. Nº 149
Programación de Computadores y el Lenguaje C
(versión 2003)
fread(&a, sizeof(obrero), 1, fdata);
muestraobrero(a);
total++;
}
printf(“\ntotal de datos leidos:: %d\n”,total);
}
void muestraobrero(obrero a)
{
printf(“%s \t %d \t %ld \n”,a.nombre, a.edad, a.fono);
return;
}
Observe que a fread se le paso la dirección de a, eso, dado que el parámetro requerido
por la función es un puntero, en este caso un puntero a obrero. El parámetro 1, es que leemos
sólo un dato de tipo obrero.
Cada vez que se hace una lectura el control del archivo se mueve al dato siguiente, y el
ciclo termina cual se llega al final de archivo.
Si escribe y ejecuta este código, es posible que ocurra lo siguiente, la variable total
con un dato más y en el listado, aparece repetido el último obrero, porque??.
Este es un error típico y es producto de que despues de leer el último dato, aún no se
alcanza el fin de archivo, por lo tanto se mete una vez más al while, se incrementa nuevamente
total y el fread falla, asi que se muestra el último valor que teniala variable a.
Si se reemplaza el while por:
while(!feof(fdata))
{
if (fread(a, sizeof(obrero), 1, fdata)<1)
printf(“error al leer obrero”);
else
{
muestraobrero(a);
total++;
}
}
Con esto se arregla el problema, y es porque la última lectura no alcanza a leer un dato
de tipo obrero, por lo tanto el valor de fread es menor que 1.
Profesor: Roberto Uribe P.
Pág. Nº 150
Universidad de Magallanes - Departamento de Ingeniería en Computación
Últimas Notas
El siguiente paso a seguir en programación es el estudio de las estructuras de datos,
este tema corresponde tanto a la construcción se complejas estructuras generalmente usando
punteros como es el caso de listas enlazadas, listas doblemente enlazadas, arboles binarios y
arboles en general, así como también recursividad y medición de la eficiencia de algoritmos
para las estructuras mencionadas.
No es necesario el conocimiento de un lenguaje específico para comprender estructuras
de datos, basta usar seudolenguajes, sin embargo, es necesario para poder realizar la
implementación de dichos algoritmos.
En las carreras de Ingeniería en Computación o Informática estos tópicos se ven en
las asignaturas que siguen la línea de Programación de Computadores como Estructuras de
Datos y Algoritmos.
Respecto del lenguaje C, lo visto en este curso es sólo una parte, queda mucho más por
aprender ☺.
Profesor: Roberto Uribe P.
Pág. Nº 151
Programación de Computadores y el Lenguaje C
(versión 2003)
Anexo Nº 1 : Estructura de un Compilador
A nivel general un compilador verifica que el programa fuente cumpla con los
requisitos que exige el lenguaje, es decir, primero verifica que los caracteres y símbolos usados
sean los permitidos (análisis léxico), despues ve si estos símbolos están usados adecuadamente,
osea, en el orden o lugar correcto (análisis sintáctico). La estructura fundamental de un
compilador queda expuesto en el siguiente diagrama:
Programa
Fuente
Analizador
Léxico
(lexer)
Tabla de
Símbolos
(Tokens)
Analizador
Sintáctico
(Parser)
Optimización
de
Código
Generación de Código
Intermedio
(análisis Semántico)
Profesor: Roberto Uribe P.
Generación
de
Código
Listado
Errores (Semánticos, Sintácticos)
Código Objeto
Para máquinas
particulares
Pág. Nº 152
Universidad de Magallanes - Departamento de Ingeniería en Computación
Anexo Nº 2 : Algunos Conceptos
Lenguajes de Programación: Son formas de indicarle al computador de manera detallada qué
es lo que nosotros queremos que haga.
Lenguaje de alto nivel: Es el lenguaje más próximo al hombre, el que nosotros entendemos.
Lenguaje de bajo nivel o de máquina: Es el lenguaje más próximo a la máquina.
C: Es un lenguaje de programación de nivel medio (compilado), es de propósito general.
Pascal: Lenguaje de programación de alto nivel (compilado), de propósito general.
Fortran: Lenguaje de alto nivel desarrollado para aplicaciones matemáticas y científicas, su
nombre viene de FORmula TRANslation. En un principio era un lenguaje no estructurado, pero
las nuevas versiones si lo son.
Compilador: Permite traducir las instrucciones escritas en lenguaje de alto nivel en
instrucciones de bajo nivel.
Programación Estructurada: Método de Programación que pretende evitar al máximo los
errores en los programas, su técnica es la de dividir el programa en bloques o etapas que se
irán definiendo y concretando en forma descendente hasta llegar a completar el programa o
resolver el problema.
Las unidades básicas de Programación Estructurada son:
Secuencia: Una serie de procesos, que va uno tras de otro.
Selección: Proceso que permite elegir otros procesos.
Iteración: Proceso que ocurre una cantidad de veces (Loop).
Lenguaje Estructurado. Tipo de Lenguaje creado para facilitar la programación estructurada.
Diagramas de Flujo: Es una representación gráfica de la definición, análisis o solución de un
programa en el cual los símbolos se utilizan para representar operaciones, datos, etc. Permite
visualizar el comportamiento de cada paso de un programa.
Algoritmos: Son una secuencia de pasos muy detallados y ordenados lógicamente que nos
permiten realizar una determinada acción.
Profesor: Roberto Uribe P.
Pág. Nº 153
0
Puede agregar este documento a su colección de estudio (s)
Iniciar sesión Disponible sólo para usuarios autorizadosPuede agregar este documento a su lista guardada
Iniciar sesión Disponible sólo para usuarios autorizados(Para quejas, use otra forma )