un56

68
Propósitos En esta unidad: Determinarás las estructuras de datos involucradas en la solución de un problema. Diseñarás soluciones empleando arreglos y estructuras (registros). Utilizarás arreglos y estructuras (registros) en programas escritos en lenguaje C. Competencia específica Utilizar estructuras de datos para almacenar y manipular los datos de un programa por medio del desarrollo de programas en lenguaje C. Introducción En muchas ocasiones nos vemos en la necesidad de procesar datos que están relacionados entre sí, a este tipo de datos se le conoce como estructurados, ya que están compuestos de un conjuntos de datos básicos (recuerda la clasificación de datos presentada en la unidad 3 en la tabla 3.2 Tipo de datos). Por ejemplo pensemos en el nombre completo de una persona, que está compuesto nombre, apellido paterno y apellido materno, o bien, en una dirección, formada por nombre de la calle, número y código postal, en este último caso no sólo está formada por varios datos simples sino que además podemos considerarlos de diferentes tipos (Figura 5.3). Figura 5.3: Ejemplos de datos Estructurados Con este tipo de datos será útil poder hacer referencia a ellos bajo un mismo identificador, y así tratarlos como una unidad. Una estructura de datos es un mecanismo de agrupación de datos que facilitan el manejo de datos estructurados y que se caracteriza por la forma en que se acede a sus elementos.

Transcript of un56

Page 1: un56

Propósitos

En esta unidad: Determinarás las estructuras de datos involucradas en la solución de un problema.

Diseñarás soluciones empleando arreglos y estructuras (registros).

Utilizarás arreglos y estructuras (registros) en programas escritos en lenguaje C.

Competencia específica

Utilizar estructuras de datos para almacenar y manipular los datos de un programa por medio del desarrollo de programas en lenguaje C.

Introducción

En muchas ocasiones nos vemos en la necesidad de procesar datos que están relacionados entre sí, a este tipo de datos se le conoce como estructurados, ya que están compuestos de un conjuntos de datos básicos (recuerda la clasificación de datos presentada en la unidad 3 en la tabla 3.2 Tipo de datos). Por ejemplo pensemos en el nombre completo de una persona, que está compuesto nombre, apellido paterno y apellido materno, o bien, en una dirección, formada por nombre de la calle, número y código postal, en este último caso no sólo está formada por varios datos simples sino que además podemos considerarlos de diferentes tipos (Figura 5.3).

Figura 5.3: Ejemplos de datos Estructurados

Con este tipo de datos será útil poder hacer referencia a ellos bajo un mismo identificador, y así tratarlos como una unidad. Una estructura de datos es un mecanismo de agrupación de datos que facilitan el manejo de datos estructurados y que se caracteriza por la forma en que se acede a sus elementos. Pensemos en otro ejemplo en el cual se tienen datos relacionados, supongamos que nos enfrentamos al siguiente problema:

Page 2: un56

Fundamentos de programación Programa Desarrollado

Problema 5.1: Se requiere un programa para llevar el registro de calificaciones de un grupo de diez estudiantes y generar reportes que incluyan datos como el promedio del grupo, la calificación máxima, el número de estudiantes que tienen una calificación superior al promedio del grupo, entre otros.

En este caso, a diferencia de los ejemplos anteriores, es claro que las calificaciones de cada estudiante se puede tratar como un dato simple e independiente de los otros, sin embargo las operaciones que se desean realizar serán las mismas para todo el conjunto de calificaciones, de tal forma que habría que escribir una serie de instrucciones secuenciales para ingresar cada dato y procesarlo. Por ejemplo, para ingresar los datos se requiere leer una por una cada calificación, para obtener el promedio se tendría que hacer la suma de todas y después dividirlas entre 10, hasta aquí no se ha complicado mucho, pero imagina todas las comparaciones que debes hacer para identificar cuál es la calificación mayor. Es claro que este método resulta de lo más ineficiente, y por supuesto si consideramos la posibilidad de modificar el programa para que sea capaz de procesar 60 o más calificaciones, el programa además de extenderse, implica reestructurarlo en su totalidad y que éste sea más complejo que la versión anterior. En cambio si consideramos a todos las calificaciones como un dato estructurado podemos hacer uso de una estructura de dato que nos facilite su manipulación.

Existen diferentes tipos de estructuras de datos, cada una caracterizada por la forma de acceso a sus elementos, y el tipo que estos pueden tener, así tenemos arreglos, listas, colas, tablas, pilas, entre otros. No obstante, para esta unidad nos centraremos sólo en las estructuras de datos que implementa el lenguaje C de forma directa: los arreglos y las estructuras.

5.1. Arreglos

El uso de arreglos facilita y hace más eficiente la declaración y manipulación de una colección de datos de un mismo tipo que están relacionados entre sí, como es el caso de las calificaciones en el Problema1, ya que todas las calificaciones se pueden considerar como valores enteros.

5.1.1. Definición y tipos de arreglos

“Un arreglo se define como una colección finita, homogénea y ordenada de elementos. Finita ya que para todo arreglo debe especificarse el número máximo de elementos que podrá contener; la homogeneidad se refiere a que todos los elementos deben ser del mismo tipo, y ordenada porque es posible determinar cuál es el primer elemento, cual el segundo, y así hasta el enésimo elemento”(Cairo Osvaldo, Guardati Buemo Silvia, 1993).

La posición que ocupa un elemento dentro de un arreglo se le denomina formalmente índice y siempre es un número entero. El tamaño o longitud de un arreglo se define como el número de elementos que lo constituyen. La dimensión de un arreglo está relacionada con el número de índices necesarios para especificar a una elemento en particular.

Page 3: un56

Fndamentos de programación PrllPodemos clasificar a los arreglos de acuerdo a su dimensión como unidimensionales o

multidimensionales.

Los arreglos unidimensionales(también llamados lineales) reciben su nombre debido a que cualquier elemento es referenciado por un único índice, por ejemplo retomando el caso de las calificaciones del problema 5.1, éstas pueden ser almacenadas en un arreglo unidimensional como el que se muestra en la Figura 5.4, en donde el nombre del arreglo es lista y los nombres de las variables donde se almacenan las calificaciones son: lista[0], lista[1], lista[2], lista[3], lista[4] …, lista[9]. En este caso el nombre en común es lista y lo único que cambia para cada elemento es el número que le corresponde a cada variable según la posición que ocupa en la lista. Observa que un solo índice es suficiente para diferenciar a un elemento de otro.

Figura 5.4 Representación gráfica de un arreglo unidimensional

Por otro lado los arreglos multidimensionales son aquellos para los cuales un solo índice no es suficiente para poder referenciar a un elemento individual, los arreglos bidimensionales son el caso más comúnmente utilizado de arreglos multidimensionales y por tanto los únicos que presentaremos.

“Un arreglo bidimensional es un conjunto de datos homogéneos, finito y ordenado, donde se hace referencia a cada elemento por medio de dos índices. El primero de los cuales generalmente se utiliza para indicar renglón y el segundo para indicar columna” (Cairo Osvaldo, Guardati Buemo Silvia, 1993)

Un arreglo bidimensional también puede verse como una tabla de valores, o bien como un arreglo de arreglos, de ahí la necesidad de dos índices, en la Figura I.5 se muestra un ejemplo gráfico de un arreglo bidimensional, en la cual del lado derecho podemos ver al arreglo como una tabla y del lado izquierdo representado como un arreglo de arreglos, Observa que cada renglón de la tabla es cada uno de los elementos del arreglo de arreglos. Es claro que con un solo índice no podríamos identificar a

Page 4: un56

Fundentos de programación Programo

Un único elemento ya que solo podríamos ubicar toda una columna o todo un renglón, en cambio la combinación de renglón-columna si nos identifica a un elemento en particular.

Figura I.5Representación gráfica de un arreglo bidimensional

5.1.2. Declaración e inicialización

En lenguaje C los índices de los arreglo siempre empiezan en cero, es decir, al primer elemento del arreglo le corresponde la posición 0, al segundo la posición sucesivamente hasta llegar al elemento TAM-1, donde TAM corresponde al tamaño del arreglo.

La declaración de un arreglo consiste en reservar espacio de memoria suficiente para el conjunto de datos homogéneos. La declaración de una variable de tipo arreglo sigue las mismas reglas que para las variables simples; con la diferencia de que ahora será necesario especificar el tamaño del arreglo, esto se hace escribiendo el tamaño del arreglo encerrado entre corchetes identificador.

La sintaxis para la declaración de un arreglo unidimensional en lenguaje C es la siguiente: <tipo><nombre>[<tamaño>];

Y para un arreglo bidimensional es: <tipo><nombre>[<tamaño1>] [<tamaño2>];

Page 5: un56

Fundamentos de programación Programa Desarrollado

El tipo de dato para los arreglos puede ser cualquier tipo básico, es decir entero, flotante o carácter (en C int, float, double o char ). De todos ellos los arreglos de tipo carácter (char) tienen un tratamiento especial, ya que un arreglo de este tipo se considerara una cadena. Debido a la importancia que tienen las cadenas en la programación más adelante los trataremos de manera particular.

Al igual que las variables simples, un arreglo puede inicializarse al momento de ser declarado, para ello se utiliza el operador asignación “=”, pero como un arreglo almacena a un conjunto de datos, es necesario inicializarlo con un conjunto de valores, los cuales se indican mediante llaves, separando por comas cada elemento del conjunto de valores iniciales, la sintaxis se muestra a continuación:

<tipo><nombre>[<tamaño>]={<valor0>,<valor1>,…,<valorTAM-1>};

La asignación de cada valor inicial se hace consecutivamente desde el elemento 0, por tanto no es posible asignar valores a elementos salteados.

Veamos como ejemplo la declaración del arreglo unidimensional lista (Figura 5.4) planteado para las calificaciones del problema1. Inicializando sus elementos en la declaración queda como:

int lista[10] = {9,10,8,5,9,6,7,9,4,8};

En el caso de los arreglos bidimensionales la sintaxis es la siguiente:

<tipo><nombre>[<tamaño1>][<tamaño2>]={ {<valor00>,<valor01>,…,<valor0(TAM21)>}, {<valro10>,<valor11>,…,<valor1(TAM21-1)>},…, {<valor(TAM1-1)0>,<valor (TAM2-1)1>,…,<elem(TAM1-1)(TAM2-1)>}

};

Veamos ahora como queda la declaración del arreglo bidimensional tabla mostrado en la Figura I.5, inicializando su valores:

int tabla[5][3]={{9,10,8},{5,9,6},{7,9,4},{8,9,6},{7,9,4}};

Aunque también es posible declararlo de la siguiente forma:

int tabla[5][3]={9,10,8,5,9,6,7,9,4,8,9,6,7,9,4};

Esta es debido a que como ya se dijo antes un arreglo bidimensional se pude ver como un arreglo de arreglos.

Page 6: un56

Fundamentos de programación Programa Desarrollado

Por otro lado, en lenguaje C siempre es necesario especificar el tamaño del arreglo al momento de declararlo, sin embargo esto se puede hacer de forma explícita o implícita.

Explícitamente es cuando se especifica el tamaño dentro de los corchetes que siguen al identificador, como en los ejemplos anteriores.

De forma implícita se hace cuando el arreglo es inicializado con un conjunto de valores, y se omite el tamaño dentro de los corchetes, entonces el compilador asume el tamaño del arreglo igual al tamaño del conjunto de valores iniciales, de tal forma que la declaración del arreglo lista puede quedar como:

int lista[] = {9,10,8,5,9,6,7,9,4,8};

Observa que en este caso no se escribe el tamaño dentro de los corchetes, pero como hay 10 elementos en el conjunto de valores iniciales, el compilador de C asume un tamaño 10 para el arreglo.

Para los arreglos bidimensionales, sólo es posible especificar una dimensión de forma implícita, el tamaño de renglones siempre debe hacerse de forma explícita.

La asignación de un conjunto de valores al arreglo, en una sola operación de asignación, únicamente es posible en su declaración, si se intenta realizar en otro momento el compilador marcará un error, ya que en cualquier otra parte del programa sólo se podrán asignar valores simples a cada uno de los elementos por separado.

Es importante señalar que cuando se desea inicializar el arreglo al declararlo, es posible inicializar sólo algunos de sus elementos, pero en este caso se tendría que especificar explícitamente el tamaño, además se debe recordar que la asignación de valores iniciales es consecutiva desde el elemento 0. Los elementos para los cuales no se indique un valor inicial, automáticamente se inicializan en cero. Por ejemplo la declaración

int lista[10] = {5};

Reservará espacio en memoria para los 10 elementos del arreglo de los cuales al primer elemento se le asignará un 5 y al resto se les asignará un cero.

En el caso de los arreglos bidimensionales es posible declara sólo algunos elementos por renglón, siempre y cuando los elementos sean consecutivos, como en el caso de los unidimensionales. Por ejemplo la siguiente declaración para el arreglo tabla:

int tabla[5][3]={{9,10},{5},{7,9,4},{8,9,}};

Page 7: un56

Fundamentos de programación ograma Desarrollado

Daría como resultado la siguiente asignación de valores iniciales

[0] [1] [0] 9 10 [1] 5 0

[2] 7 9[3] 8 9[4] 0 0

En el caso de que la declaración fuera:

int tabla[5][3]={9,10,5,7,9,4,8,9,};

Entonces la asignación de valores iniciales se haría de la siguiente forma

[0] [1][0] 9 10[1] 7 9[2] 8 9[3] 0 0[4] 0 0

5.1.3. Acceso a los elementos de un arreglo

Para referirse a un elemento del arreglo es necesario indicar el nombre del arreglo seguido del índice o índices correspondientes al elemento que deseamos acceder. Para ello se debe seguir la siguiente sintaxis.

Elementos de un arreglo unidimensional:

<nombre del arreglo>[<índice>];

Elementos de un arreglo bidimensional:

<nombre del arreglo>[<índice de renglón>][<índice de columna>];

Observa que para cada índice se utilizan corchetes separados.

Cada elemento del arreglo se puede tratar igual que a cualquier otra variable, es decir, podemos asignarle un valor, incluir en una expresión algebraica o lógica, imprimir en pantalla su valor, asignarle desde el teclado un valor, etc.

Page 8: un56

Fundamentos de programación Programa Desarrollado

veamos algunos ejemplos: Instrucción

tabla[0][2] = 8;

printf(“%d”,lista[4]);

scanf(“%d”,&tabla[0][0]);

lista[1]++;

Asignar el valor de 8 al tercer elemento del primer renglón de arreglo tablaImprimir en pantalla el quinto elemento del arreglo listaLee un entero desde teclado y asignarlo en la primera posición del arreglo tabla.Incrementar en uno el valor del segundo elemento del arreglo lista

5.1.4. Ciclos y arreglos

Los arreglos y los ciclos están estrechamente relacionados, podríamos afirmar que en cualquier programa que se emplee un arreglo éste será manipulado por medio de una estructura repetitiva. Anteriormente mencionamos que un arreglo se utiliza cuando queremos almacenar y manipular datos relacionados entre sí y, generalmente, cuando tenemos un arreglo debemos ejecutar el mismo conjunto de operaciones sobre cada uno de sus elementos.

En lenguaje C el índice del elemento se puede especificar mediante una expresión, es decir una variable, una constante o como el resultado de una operación, lo que hace posible ir cambiando el índice de un elemento dentro de un ciclo sin tener que escribir una serie de instrucciones secuenciales para realizar la misma operación sobre cada uno de los elementos del arreglo.

La forma general de procesar un arreglo unidimensional por medio de un ciclo se muestra en la Figura I.6, observa cómo la variable contador pos del ciclo, también se utiliza como índice para el elemento del arreglo, de tal forma que en cada iteración se haga referencia a un elemento diferente del arreglo.

Page 9: un56

Fundamentos de programación Programa Desarrollado

Figura I.6Procesamiento de arreglos unidimensionales mediante ciclos

No es difícil intuir que para el caso de los arreglos bidimensionales será necesario no solo un ciclo sino un par de ciclos anidados, ya que tenemos dos dimensiones, cada uno de los ciclos se encargará de variar a un índice, la estructura general para estos ciclos se muestra en la Figura I.7. Dos ciclos están anidados cuando está uno dentro de otro, de tal forma que por cada iteración del externo, el interno completa todo un ciclo. Considerando esto, observa que en la Figura I.7 se toma como contador a la variable i para el ciclo externo, la cual también se utiliza como índice de renglón. Asimismo, se utiliza a la variable j como contador para el ciclo interno, además de índice de columna, de esta manera se está recorriendo el arreglo por renglón.

Page 10: un56

Fundamentos de programación Programa Desarrollado

Figura I.7Procesamiento de arreglos bidimensionales mediante ciclos anidados

Si analizamos el diagrama de flujo podemos observa que para la primera iteración del ciclo externo, la variable i tiene el valor de 0 mientras que j toma los valores desde 0 hasta TAMC-1 (TAMC es el número total de columnas) de tal forma que el renglón se mantiene fijo mientras nos movemos en todas las columnas, en la siguiente iteración cambia el renglón ya que i toma el valor de 1 y recorremos nuevamente todas las columnas, este proceso se repite hasta el valor final de i que es TAMF-1 (TAMF es el número de renglones).

Page 11: un56

Fundamentos de programación Programa Desarrollado

Ejemplo 5.1: Veamos cómo utilizar arreglos en la solución de un problema, resolviendo como ejemplo parte del problema 1, enfocándonos únicamente en la lectura de las 10 calificaciones y el cálculo del promedio.

Para almacenar las calificaciones se puede utilizar un arreglo unidimensional de tipo entero, será necesario pedir al usuario que ingrese las 10 calificaciones para poder realizar las operaciones necesarias para el cálculo del promedio, es decir, la suma de las misma y la división entre el total de calificaciones, para finalmente imprimir en pantalla el promedio, adicionalmente se imprimirá también la lista de calificaciones ingresadas. No existe ninguna restricción que sea necesaria considerar.

Cada uno de los procesos que se van a realizar sobre las calificaciones, es decir leerlas, sumarlas e imprimirlas en pantalla se pueden implementar mediante ciclos, en vez de tener que escribir 10 veces la misma expresión para cada una de las 10 calificaciones. La lectura y la suma de las calificaciones se pueden implementar dentro del mismo ciclo. De esta manera podemos resumir el análisis del problema de la siguiente forma:

Datos de entada: Calificaciones de los 10 estudiantes (calif [ ]) Salida: Promedio de calificaciones (prom) Método:

prom = 9 ���� []�=010

Page 12: un56

rogramación La solución del problema representada en pseudocódigo se muestra en el siguiente algoritmo.

sarrollado inicio

suma ← 0 Desde i ← 0 mientras i<10, i ← i+1

Imprimir “Ingresa la calificación” i Leer calif[i] suma← suma+calif[i]

Fin Desde prom ← prom/10

Imprimir “Las calificaciones ingresadas fueron:” Desde i ← 0 mientras i<10, i ← i+1 Imprimir “Calificación” i “:” calif[i] Fin Desde Imprimir “Calificación promedio = ” prom

Fin

Algoritmo 5.2. Promedio de calificaciones

La codificación del algoritmo anterior es la siguiente:

/*Directivas de preprocesador*/ #include <stdio.h> #include <stdlib.h> /* Definimos como constante simbólica el tamaño del arreglo*/ #define TAM 10 /* Definición de función principal */ main( ) {

/*Declaración del arreglo calificaciones*/ int calif[TAM];

double prom = 0; int i;

printf("*** printf(“* El siguiente programa calcula el promedio de *\n"); printf(“* un grupo de diez estudiantes printf("***

/*Lectura y suma de las calificaciones*/ for(i=0; i < TAM; i++)

{ printf("Proporciona la calificación %d: ",i+1); scanf(“%d”, &calif[i]); prom = prom + calif[i];

Page 13: un56

Fundamentos de programación Programa Desarrollado

} /*Cálculo e impresión del promedio*/

prom = prom/TAM; /*Impresión de las calificaciones*/ printf("\nLas calificaciones ingresadas fueron: \n");

for(i=0; i < TAM; i++) printf("\nCalificacion %d: %d",i+1, calif[i]);

printf("\n\n\tPromedio = %.2f\n\n", prom); system("pause");

}

Programa 5.1: promCalificaciones.c

En la siguiente figura se muestra una ejecución del programa.

Figura 5.8: Ejecución del programa promCalificaciones.c

Observa que el tamaño del arreglo se especifica por medio de una constante simbólica, utilizando la directiva #define, esto facilita el cambiar el tamaño del arreglo sin tener que hacer cambios en todo el código.

Page 14: un56

Fundamentos de programación Programa Desarrollado

Actividad 1. Foro Estructuras de Datos -Ejercicio A Realiza una prueba de escritorio con los datos que se muestran en la ejecución del programapromCalificaciones.c (figura 5.6) y posteriormente escribe, compila y ejecuta el programa en lacomputadora.

Reflexiona cómo solucionarías el problema si no contaras con los arreglos y qué pasaría si en vez de calcular el promedio de 10 calificaciones tuvieras que hacerlo para 50, qué se debería modificar en cada el programa que se presentó en esta sección y qué tendrías que modificar sino contaras con los arreglos. Ingresa tus conclusiones al Foro Estructuras de Datos

A continuación se presenta otro ejemplo para ilustrar el uso de arreglos bidimensionales.

Ejemplo 5.2: Se requiere un programa que calcule el determinante de una matriz de 2x2. Considerando la siguiente información.

Dada la siguiente matriz:

Su determinante se define como: ∆=a00a11-a01a10

Análisis del problema: Para este problema se puede utilizar un arreglo bidimensional de 2 renglones y 2 columnas para almacenar los valores de la matriz, los cuales se pueden solicitar al usuario utilizando la estructura de ciclos anidados presentada en la Figura I.7, nuestro dato de salida será el valor del determinante y adicionalmente también se mostrará en pantalla la matriz ingresada. Datos de entada: Elementos de la matriz (A[ ][ ]) Salida: Valor del determinante (det) Método:

�� ]0][1[ܣ] ∗ 1][0[ܣ] − 1][1[ܣ] ∗ 0][0[ܣ = ݐ La solución del problema se da en el siguiente diagrama de flujo.

Page 15: un56

Fundamentos de programación Programa Desarrollado

Algoritmo 5.3: Determinante de una matriz 2x2

Page 16: un56

Fundamentos de programación ProgramDesarrollado

La codificación del algoritmo se deja como ejercicio al lector, sin embargo, en la siguiente figura se muestra un ejemplo de ejecución del programa.

/* Directivas al preprocesador */ #include <stdlib.h> #include <stdio.h>

/* Constantes con el tamaño de la matriz */ #define TAM 2

/* Función principal */ main() {

int i, j; float det;

float A[TAM][TAM]; /*declaración de la matriz*/

/* Mensaje de bienvenida */ printf("*** printf("* Determinante de una matriz A de 2x2 *\n"); printf("***

/* Lectura de la matriz A */ for(i=0; i<TAM; i++)

for(j=0; j<TAM; j++){ printf("\nProporciona el elemento A[%d][%d]: ", i,j); scanf("%f", &A[i][j]);

} det = A[0][0]*A[1][1] - A[0][1]*A[1][0];

printf("\nA: \n\t");

/* Impresión de la matriz A */ for(i=0; i<TAM; i++){

for(j=0; j<TAM; j++) printf("%8.2f", A[i][j]);

printf("\n\t"); }

printf("\n\n\t\tDeterminante = %.2f\n\n", det); system("pause");

} Programa 5.2: determinantes.c

Page 17: un56

Fundamentos de programación Programa Desarrollado

En la siguiente figura se muestra una ejecución del programa.

Figura 5.9: Ejecución del programa determinante.c

Actividad 1. Foro Estructuras de Datos -Ejercicio B

Realiza una prueba de escritorio con los datos que se muestran en la ejecución del programadeterminante.c (figura 5.7) y posteriormente escribe, compila y ejecuta el programa en lacomputadora. Comparte tu experiencia en el Foro.

5.1.5 Cadenas

Una cadena es una serie de caracteres tratados como una sola unidad. Una cadena puede incluir letras, dígitos y varios caracteres especiales(Deitel H. M., Deitel P. J., 1995).

En algunos lenguajes de programación existe un tipo de dato específico para definir a las cadenas, sin embargo, en lenguaje C las cadenas se implementan por medio de arreglos de tipo carácter ya que no existe un tipo específico para ellas, pero existe todo un conjunto de funciones estándar definidas en la biblioteca string.h mediante las cuales es posible realizar las operaciones más comunes, por ejemplo: copiarlas, concatenarlas, compararlas, entre otras. Además es posible imprimirlas y leerlas de forma similar que un dato simple.

En lenguaje C toda constaten de tipo cadena se indica entre comillas dobles, por ejemplo:

“Calle 2 #135”

Page 18: un56

Fundamentos de programación Programa Desarrollado

Una cadena en C termina siempre con el carácter nulo „\0‟ (cuyo valor ascii es cero) que representa el fin de cadena.

Al declarar arreglos de tipo char que sean una cadena se pueden inicializar directamente con una constante cadena de la siguiente forma:

char cad[50]=”saludo”;

Al inicializar una variable cadena de esta manera, se agrega automáticamente el símbolo de carácter nulo para indicar el fin de cadena, es decir, en la posición 6 del arreglo cad se encuentra almacenado el fin de cadena „\0‟. De forma general, es importante señalar que en un arreglo de tamaño N es posible almacenar correctamente una cadena de máximo N-1 caracteres. De tal forma que en el arreglo cad se puede almacenar una cadena de máximo 49 caracteres.

Las cadenas en C pueden ser asignadas a un arreglo de tipo char utilizando la función scanf mediante el especificador de formato %s, por ejemplo la línea de código:

scanf(“%s”,cad);

De esta manera se lee desde el teclado una cadena y se guarda en el arreglo cad, sólo que en este

caso no se incluye el operador & antes del nombre del arreglo, pues el identificador del arreglo

almacena la dirección del primer elemento del mismo.

La función gets() también nos permite leer del teclado una cadena y asignarla a un arreglo de tipo

char, por ejemplo la instrucción: gets(cad);

Lee una cadena desde el teclado y la almacena en el arreglo cad.

Una diferencia importante entre usar scanf y gets es que con la primera la lectura de la cadena se da por terminada cuando el usuario presiona [Enter] o Espacio, mientras que la segunda termina la lectura de la cadena únicamente cuando el usuario presiona [Enter], tal como se muestra en los siguientes ejemplos: Cadena ingresada Instrucción Contenido del arreglo cad[]“Calle 2 #135” scanf(“%s”,c cad[]:

ad); C a l l e / / …0 0

“Calle 2 #135” gets(cad); cad[]:C a l l e 2 #

Page 19: un56

programación Prorrollado

similarmente, para imprimir en pantalla una cadena se puede utilizar la función printf con el

especificador de formato %s, o bien, la función puts, nuevamente ambas funciones tienen un

comportamiento similar con la única diferencia de que puts incluye siempre un salto de línea al final, esto

se ilustra a continuación.

Código c EjecuciónImpresión de cadena con printf

#include <stdio.h> #include <stdlib.h> main(){ char mensaje[30]=”Mar profundo ”; printf(“%s”,mensaje); system(“pause”); }

Impresión de cadena con puts #include <stdio.h> #include <stdlib.h> main(){ char mensaje[30]=”Mar profundo ”; puts(mensaje); system(“pause”); }

Las funciones que nos permiten el manejo de cadenas se encuentran string.h, para ilustrar algunas se muestra el siguiente programa en C.

Ejemplo 5.3: El programa siguiente verifica si una clave (password) de 8 caracteres alfanuméricos ingresado por el usuario es correcta.

/*Directivas de preprocesador*/ #include <stdio.h> #include <stdlib.h> #include <string.h> /* se incluye la biblioteca decadenas */

main( ) {

/* Declaración de variables */ char pwscorrecto[9]=”jk278la0”; /* Clave correcta */

Page 20: un56

Fundamentos de programación Programa Desarrollado

char pwsingresado[9]; /* para leer la clave que ingrese el usuario */

char nombre[10]; /* Para leer el nombre del usuario */ char mensaje[50]=”Bienvenido ”; /* Lectura de datos */ printf(“Nombre: "); gets(nombre); /* Lectura de una cadena con espacios */

printf(“pasword: "); scanf(“%s”,pwsingresado); /* Lectura de una cadena sin

espacios*/

if (!strcmp(pwscorrecto,pwsingresado)){ /* comparación de claves, si la función strmp regresa 0 son i iguales */ printf(“pasword correcto \n”); strcat(mensaje,nombre); /* pega al final de

mensaje el nombre del usuario*/ puts(mensaje); /* impresión de la cadena con salto

de línea*/ } else {

strcpy(mensaje, “Acceso denegado”); /* copia la cadena acceso denegado al mensaje */

puts(mensaje); /* imprime la cadena*/ }

system("pause");}

Programa 5.3: password.c

En la siguiente figura se muestran dos ejecuciones del programa a) Clave inválida

Figura 5.10: Ejecución del programa password.c (Fuente: elaboración propia utilizando DevC++)

Con esto se concluye la sección de arreglos, recuerda que un arreglo es un conjunto de dato del mismo tipo. En la siguiente sección verás cómo puedes agrupar datos de diferente tipo.

Page 21: un56

Fundamentos de programación Programa Desarrollado

5.2. Estructuras

Las estructuras en C al igual que los arreglos nos permiten tratar a un conjunto de datos bajo un mismo identificador pero a diferencia de los arreglos las estructuras son conjuntos de datos contiguos en memoriano homogéneos de tal forma que una estructura puede estar formada por datos de diferentes tipos.

Una de las aplicaciones para las estructuras es para la manipulación de registros que se recuperan o se almacenan en una base de datos.

5.2. Definición, declaración e inicialización

“Una estructura es una colección de una o más variables, de tipos posiblemente diferentes, agrupadas bajo un nombre para manejo conveniente (en algunos lenguajes también se conocen como registros). Las estructuras permiten tratar a un grupo de variables relacionadas como una unidad, en vez de que se traten en forma separada (Kernighan & Ritchie, 1991, pág. 141)”.

La definición de una estructura en C inicia con la palabra reservada struct seguida del identificador para el tipo de estructura y de un bloque de definiciones de variable que constituyen el conjunto de elementos que forman parte de ella, la sintaxis para definir la estructura es la siguiente:

struct<identificadorEstructura>{ <tipo1><identificadorE1>; <tipo2><identificadorE2>; <tipoN><identificadorEN>; }

Observa que se utiliza la palabra reservada struct para iniciar la definición y que las declaraciones para los elementos de la estructura se encierran entre llaves. Una estructura puede contener a N elementos de diferentes tipos, de cualquiera de los tipos básicos, o incluso un arreglo, veamos un ejemplo:

struct paciente { int nss; /* número de seguro social */ char apellido[50]; char nombre[20]; int edad; float estatura; char sexo;

}

Page 22: un56

Fundamentos de programación Programa Desarrollado

En este ejemplo se está definiendo la estructura paciente que tiene seis elementos: dos enteros (nss y edad), dos cadenas (apellido y nombre), un flotante (estatura) y un carácter (sexo). Sin embargo la definición anterior no reserva espacio en memoria para la estructura, más bien define un tipo de dato, por lo tanto para poder utilizar la estructura, es necesario declarar una variable de este tipo, es aquí cuando se reserva espacio en memoria. La sintaxis para hacer esta declaración es la siguiente:

struct<identificadorEstructura><identificador_var>;

Por ejemplo la declaración:

struct paciente paciente1, paciente2;

Declara a las variables paciente1 y paciente2, las cuales son del tipo paciente y por tanto para cada una de ellas se reserva espacio en memoria suficiente para cada uno de sus seis elementos. Otra forma válida de hacer la declaración es haciéndola seguida a la definición de la estructura, para el ejemplo anterior puede escribirse como sigue:

struct paciente { int nss; char apellido[50]; char nombre[20]; int edad; float estatura; char sexo;

} paciente1, paciente2;

En este caso el identificador para el tipo de estructura puede omitirse, pero entonces la única forma de declarar variables para ese tipo de estructura es en su definición, y no en una declaración por separado, de tal forma que nuestro ejemplo puede quedar como sigue:

struct { int nss; char apellido[50]; char nombre[20]; int edad; float estatura; char sexo;

} paciente1, paciente2;

Por otro lado, al igual que los arreglos, también se pueden inicializar los elementos de una estructura en el momento de la declaración de una variable del tipo de la estructura en cuestión, éstos deben estar encerrados entre llaves y separados por comas. La sintaxis general es la siguiente:

Page 23: un56

Fundamentos de programación Prog Desarrollado

struct<identificadorEstructura><identificador_var> =

{ <valorE1>,<valor2>, ,<valorN> };

Por ejemplo :

struct paciente paciente1 = {1240, “PicaPiedra”, “Pedro”, 45, 1.80, „M‟};

Sólo en el momento de la declaración es posible asignar todos los valores de una estructura (al igual que con los arreglos), así que si después se quiere modificar tendrán que hacerse las modificaciones de forma separada en cada uno de sus elementos, como se muestra en el siguiente subtema.

Actividad 2. Arreglos y estructuras.

Retoma el programa que has trabajado para las evidencias de aprendizaje anteriores y realiza una versión preliminar del mismo pero ahora incorporando lo que has aprendido en esta unidad. Tu facilitador(a) te orientará si tienes dudas. Con esta actividad podrás prepararte para la entrega de tu evidencia de aprendizaje de la unidad.

5.2.2. Acceso a sus elementos

Para referenciar un elemento de la estructura se utiliza el operador punto “.” con la siguiente sintaxis:

<idenficador_var>.<idenficadorEi>

Este operador seguido del identificador del elemento, referencia el elemento indicado, de tal forma que la sentencia para asignar al paciente1 un nss de 23442145 será:

paciente1.nss = 23442145;

Ahora, si se quiere asignar a la misma variable el nombre “Pablo”, la instrucción sería:

paciente1.nombre = “Pablo”;

Del mismo modo se realiza la impresión o lectura del elemento de la estructura, sin perder de vista su tipo. Para ilustrar esto se propone el siguiente ejemplo. Ejemplo 5.4: En el siguiente programa se declara una estructura de tipo perro, que tiene los siguientes elementos:

Elemento TipoRaza char[]Edad intPeso float

Page 24: un56

Fundamentos de programación Proama Desarrollado

Posteriormente se declaran dos variables de éste tipo, una se inicializa en la declaración y a la otra se le asignan valores desde el teclado, ambos se muestran al final en pantalla.

#include <stdio.h> #include <stdlib.h> #include <conio.h>

main(){ /* Declaración de la estructura perro*/ struct perro{ char raza[20];

int edad; float peso;

} fido, pluto = {"labrador", 7, 20} ; /* inicialización de la variable pluto*/

printf("*** **");printf("\n* Comparando perros *");printf("\n*** **");

printf("\n\nIngresa la raza de fido:"); scanf("%s",&fido.raza); /* lectura de un elemento */

printf("Ingresa la edad de fido en a%cos:", 164); scanf("%d",&fido.edad); /* lectura de un elemento */ printf("Ingresa el peso de fido en kilos de fido:"); scanf("%f",&fido.peso); /* lectura de un elemento */

/* impresión de los elementos de las estructuras */ printf("\nFido es de raza %s, tiene %d a%cos ykilos\n",fido.raza,fido.edad,164,fido.peso);printf("\nPluto es de raza %s, tiene %d a%cos ykilos\n",pluto.raza,pluto.edad,164,pluto.peso);

/* comparación de los nombres que son cadenas */ if(!strcmp(pluto.raza,fido.raza)) printf("\nPluto Y Fido son de la misma raza \n"); else

printf("\nFido y Pluto son de razas distintas\n");

/* comparación de elementos de tipo numérico */ if(pluto.peso > fido.peso)

Page 25: un56

Fundamentos de programación Programa Desarrollado

printf("Pluto es m%cs pesado que Fido\n",160); else if(pluto.peso < fido.peso) printf("Fido es m%cs pesado que Pluto\n",160); else

printf("Fido y Pluto pesan lo mismo\n");

if(pluto.edad > fido.edad) printf("Pluto es mas viejo que Fido\n"); else if(pluto.edad < fido.edad) printf("Fido es mas pesado que Pluto\n"); else

printf("Fido y Pluto tienen la misma edad \n");

getch(); }

Programa 5.4: perros.c

Nota: Observa que en este caso, para poder imprimir la letra ñ se utilizó su código Ascii cual en la cadena de control del printf se escribió %c en donde se desea imprimir la ñ. Este mismo truco se hizo para acentuar la letra a.

En la siguiente figura se muestra una ejecución del programa anterior.

Figura 5.11: Ejecución del programa perros.c

Actividad 1. Foro Estructuras de Datos -Ejercicio C

Escribe, compila y ejecuta en la computadora el programa perros.c. Posteriormente, analiza cuáles son las principales diferencia entre las estructuras y los arreglos e ingresa tus comentarios en el Foro Estructuras de Datos.

Page 26: un56

Fundamentos de programación Programa Desarrollado

Para concluir con este capítulo veamos el siguiente ejemplo donde se utilizan estructuras y arreglos: Ejemplo 5.5:Se requiere un programa que permita registrar los datos de los perros que ingresan a refugio para perros, y poder desplegar los datos de alguno de los perros, a cada perro se le asigna una clave numérica consecutiva. Los datos que se registran del perro son:

la fecha de ingreso (cadena) nombre (cadena)

raza (cadena) color (cadena) edad (entero) peso (flotante)

El refugio tiene capacidad máxima para 100 perros.

Para la solución del problema se puede plantear un menú que permita registrar a un perro, o bien, desplegar los datos del perro solicitados. En este caso tenemos como datos de entrada la opción del menú que elija el usuario. En el caso de que sea un registro tendremos también se tienen como datos de entrada los datos del perro. Para la opción de despliegue se tendrá que dar como datos de entrada la clave del perro y la salida serán los datos correspondientes a la clave.

Para desplegar y repetir el menú se requiere un ciclo y una estructura condicional que maneje las opciones del menú. Para almacenar los datos del perro, se puede utilizar una estructura similar al ejemplo anterior. Mediante un arreglo de estas estructuras estaremos en capacidad para manipular los datos de varios perros, el tamaño de este arreglo tendrá que ser igual a la capacidad del refugio (100 perros), de tal forma que el índice del arreglo que toca a cada perro corresponderá con su clave.

Una restricción que hay que tomar en cuenta es que se debe verificar que no se sobre pase la capacidad de los 100 peros, tanto al ingresar datos como al recuperarlos, para ello se llevará un contador (c) que actualice el número de perros ingresados.

Inicio c ← 0 Hacer Imprimir “Refugio para perros -Ladrido Feliz- ” Imprimir “1) Registrar un perro ” Imprimir “2) Buscar un perro ” Imprimir “3) Salir ” Imprimir “Elige una opción: ” Leer op Casos para op

Caso 1: Si c≥100 entonces

Page 27: un56

Fundamentos de programación Programa Desarrollado

Imprimir “El refugio está lleno”Si no

Imprimir “Ingresa los datos del perro:” Imprimir “Clave:” c Imprimir “fecha de ingreso[dd/mm/aa]: ” Leer perros[c].fecha Imprimir “color: ” Leer perros[c].color Imprimir “nombre: ” Leer perros[c].nombre Imprimir “raza: ” Leer perros[c].raza Imprimir “edad: ” Leer perros[c].edad Imprimir “peso: ” Leer perros[c].peso c ← c+1

Fin si-si no Caso2: Imprimir “Clave: ”

Leer clave Mientras clave≥100 v clave <0 hacer

Imprimir “La calve no es válida, ingresa nuevamente la clave:” Leer clave

Fin mientras Imprimir “nombre:”, perros[clave].nombre Imprimir “fecha de ingreso:”, perros[clave].fecha Imprimir “color: ”, perros[clave].color Imprimir “raza: ”, perros[clave].raza Imprimir “edad: ”, perros[clave].edad Imprimir “peso: ”, perros[clave].peso

Caso 3: Otro caso: Imprimir ”Opción no válida”

Fin casos Mientras op≠3

Fin Hacer-mientras Fin

Algoritmo 5.3. Registro perros

Page 28: un56

Fundamentos de programación Programa Desarrollado

La prueba de escritorio se deja como ejercicio al lector. En cuanto a la codificación del algoritmo se muestra a continuación.

#include <stdio.h> #include <stdlib.h> #include <conio.h>

main(){ /* Declaración del arreglo de tipo estructura perro */ struct perro{ char fecha[10];

char raza[30]; char color[50]; char nombre[30]; int edad; float peso;

} perros[100];

int c=0, op, clave;

do{ /* Inicio del ciclo que imprime el menú*/ printf( "\n----------------------------------------\n"); printf( "\nRefugio para perros -Ladrido Feliz- \n" ); printf( "\n----------------------------------------\n"); printf( "1) Registrar un perro \n" ); printf( "2) Buscar un perro \n" ); printf( "3) Salir \n" ); printf( "Elige una opci%cn:",162 );

scanf("%d",&op);

switch (op){ case 1: /*Opción Registrar perro */

printf( "\n------------------------------\n"); if(c>=100) /* Verifica si hay espacio */

printf("El refugio esta lleno\n"); else{ /*Si hay espacio pide los datos del perro y Y los guarda en el registro c del arreglo */ printf( "Ingresa los datos del perro:"); printf( "Clave:%.3d\n", c); printf( "fecha de ingreso[dd/mm/aa]: "); scanf( "%s", perros[c].fecha); printf( "nombre: "); fflush(stdin); gets( perros[c].nombre); printf( "color: ");

Page 29: un56

Fundamentos de programación Programa Desarrollado

gets( perros[c].color); printf( "raza: "); gets( perros[c].raza); printf( "edad: "); scanf("%d" ,&perros[c].edad); printf( "peso: "); scanf("%f" ,&perros[c].peso); c++;

} break;

case 2: /* Opción buscar perro */ printf( "\n-------------------------------\n"); printf( "Clave: "); scanf("%d",&clave); /* verifica que la clave sea válida */ while(clave>=100 || clave <0){

printf("La calve no es válida, ingresa nuevamente la clave:");

scanf("%d",&clave);}

/* Imprime los datos del perro correspondientea la clave */

printf("nombre:%s\n",perros[clave].nombre); printf( "fecha de ingreso: %s\n",

perros[clave].fecha); printf( "color: %s\n", perros[clave].color); printf( "raza: %s\n", perros[clave].raza); printf( "edad: %d a%cos\n",

perros[clave]edad,164); printf( "peso: %.2f kilos\n",

perros[clave].peso); break;

case 3: /* Caso salir, no hace nada */ break;

default: /* Caso opción inválida */ printf( "Opcion no valida\n"); } }while (op!=3); /* El ciclo do-while se repite mientras la

opción no sea salir (3) */ }

Programa 5.5: registroPerros.c

Page 30: un56

Fundamentos de programación Programa Desarrollado

Evidencia de aprendizaje. Programa en C. Avance de solución del problema implementando las estructuras de datos Con ayuda de tu facilitador deberás acotar el problema que describiste en la evidencia de la unidad 1, de tal manera que la solución involucre a las estructuras de datos vistas en esta unidad. Para conocer los detalles de la actividad, ingresa al aula virtual.

Consideraciones específicas de la unidad

Para alcanzar los objetivos de esta unidad te recomendamos que escribas y compiles los programas que se desarrollaron a lo largo de la misma y realices una prueba de escritorio de cada uno de ellos, tal como se sugiere en las actividades opcionales.

Referencias:

Cairo Osvaldo, Guardati Buemo Silvia. (1993). Estructura de Datos. México: McGraw-Hill.

Deitel H. M., Deitel P. J. (1995). Cómo programar en C/C++. México: Prentice Hall.

Joyanes, L., & Zohanero, I. (2005). Programación en C. Metodología, algoritmos y estructuras de datos. aspaño: Mc Graw Hill.

Kernighan, B., & Ritchie, D. (1991). El lenguaje de programción C. México: Prentice-HallHispanoamericana.

López, L. (2005). Programación estructurada en lenguaje C. México: Alfaomega.

Page 31: un56

Fundamentos de programación Prrama Desarrollado

dad 6: Funciones

Propósitos

En esta unidad: Identificarás los sub-problemas en que puede dividirse un problema.

Diseñarás algoritmos modulares para solucionar un problema

Construirás funciones en lenguaje C que realicen tareas específicas

Competencia específica Implementar funciones para resolver problemas a través del desarrollo de programas modulares escritos en lenguaje C.

Introducción

Hasta esta etapa del curso, has aprendido a utilizar las estructuras de control para diseñar soluciones de problemas simples. También has conocido diferentes formas de representar los datos involucrados en un problema, desde simples hasta estructurados (como arreglos, cadenas y estructuras). Sin embargo, todas las soluciones propuestas constan únicamente de un módulo (función), llamado principal (en C, main); lo cual no es conveniente cuando se trata de problemas complejos que requieran de una serie de tareas para lograr su solución, pues ésta sería muy grande y, por lo tanto, difícil de corregir o modificar. En estos casos lo recomendable es crear módulos independientes que realicen cada una de las tareas específicas y que en conjunto implementen la solución del problema. A esta metodología se le llama diseño modular, y está inspirado en la estrategia “divide y vencerás”.

En esta unidad se explicarán las funciones, que son el medio por el cual se implementan los módulos en lenguaje C. Se presentará la sintaxis para crear una función y la forma en la que se utilizan. Además, se resolverá un problema utilizando el diseño modular para ejemplificar el tema.

6.1. Diseño descendente

La descomposición de un programa en módulos más pequeños se conoce como el método de divide y vencerás (divide and conquer) o diseño descendente (top-down). Consiste en dividir un problema en unidades más pequeñas sucesivas hasta que sean directamente ejecutadas por el procesador, en otras palabras, la solución del problema se divide una y otra vez hasta que esté expresada en términos de estructuras de control, cada uno de los niveles o pasos sucesivos se conoce como refinamiento (stepwise).

La metodología del diseño descendente consiste en efectuar una relación entre las etapas de estructuración de manera que se relacionen entre sí a través de entradas y salidas de datos. Es decir,

Page 32: un56

Fundamentos de programación Proama Desarrollado

se descompone el problema en etapas de estructuración jerárquicas, de forma que se pueda considerar cada estructura desde dos puntos de vista: ¿qué hace? y ¿cómo lo hace? Con lo anterior podemos decir que: un módulo se caracteriza por realizar una tarea específica, posee sus propios datos de entrada - llamados parámetros de entrada - y su resultado - llamado salida o valor de retorno -. El diseño de cada módulo debe ser independiente de los otros; si es necesario que exista comunicación entre ellos, ésta únicamente podrá realizarse a través de los parámetros de entrada y del valor de retorno. En este sentido, puede ser visto, por otros módulos, como una caja negra que hacia el exterior sólo muestra qué hace y qué necesita, pero no cómo lo hace.

La creación de un modulo conlleva dos partes: la definición del módulo y la llamada o invocación (ejecución). La primera consta de tres partes:

Definir los parámetros de entrada;

Definir el valor de retorno; Escribir todas las instrucciones que le permitirán llevar a cabo la tarea, es decir, un algoritmo.

La llamada o invocación a un módulo es el proceso de ejecutar el conjunto de instrucciones definidas en el módulo dado un conjunto de entradas específico. La forma general de invocar un módulo es escribiendo su nombre y entre paréntesis los valores de cada uno de los parámetros de entrada, respetando el orden que con el que se definió. No existe un método para saber en cuánto módulos se debe dividir un problema, sin embargo es importante tener en cuenta los siguientes principios del diseño modular:

Las partes altamente relacionadas deben pertenecer a un mismo módulo. Las partes no relacionadas deben residir en módulos diferentes.

Para ejemplificar esta forma de resolver problemas y cómo implementarla, se presenta la siguiente situación:

Problema 6.1: Realiza el análisis y diseño de un programa que lea las temperaturas promedio mensuales registradas en una ciudad a lo largo de un año y calcule el promedio anual. Además, debe convertir las temperaturas mensuales dadas en grados Celsius a grados Fahrenheit al igual que el promedio.

Empecemos por hacer un bosquejo de los pasos que se tienen que realizar para llegar al resultado deseado.

1. Leer las doce temperaturas promedio mensuales 2. Calcular el promedio anual de las temperaturas 3. Convertir las temperaturas promedio mensuales de Celsius a Fahrenheit 4. Convertir el promedio anual de temperaturas a Fahrenheit 5. Imprimir las temperaturas mensuales en grados Fahrenheit y el promedio anual de las

temperaturas, en Celsius y Fahrenheit.

Page 33: un56

Fundamentos de programación Programa Desarrollado

Para registrar las temperaturas mensuales proponemos utilizar un arreglo de tamaño 12. Y de acuerdo con lo anterior, proponemos tres módulos: El primero encargado de leer las temperaturas mensuales dadas en grados Celsius, este módulo necesita el nombre del arreglo donde va a almacenar los datos. Otro módulo encargado de calcular el promedio de las temperaturas, que recibe como parámetros de entrada el arreglo con las temperaturas mensuales y devuelve el promedio en grados Celsius. El último módulo sólo convierte grados Celsius a grados Fahrenheit. Esta información se resume en el diagrama modular que se muestra a continuación.

Figura 6.12: Diagrama modular del problema 6.1

Es mediante un diagrama modular como se representan de manera gráfica los módulos que integran la solución del problema. Y una vez que se han definido, el siguiente paso es diseñar el algoritmo de cada uno de ellos.

En primer lugar se muestra el pseudocódigo de los módulos secundarios.

Page 34: un56

Fundamentos de programación Programa Desarrollado

Módulo leerTemps(temp[ ])

Inicio Desde mes ← 1 mientras mes ≤ 12, mes ← mes + 1

Imprimir “Proporciona la temperaturadel

mes”, mes Leer temps[mes-1]

Fin_Desde Fin

Algoritmo 6.4: Algoritmo del módulo leerTemps(temp[])

Observa que el ciclo del módulo leeTemps se inicia con mes igual a 1 y termina cuando mes es 13, se propone así porque se pide la temperatura de los mes 1, 2, 3,.. 12, así que la variable mes guardara el número de mes correspondiente a cada lectura, sin embargo para almacenar la temperatura en la posición del arreglo indicada se resta uno (temps[mes-1]), así se guarda desde la posición 0 y hasta la posición 11.

Page 35: un56

Fundamentos de programación Programa Desarrollado

Módulo promTemps(temp[]) Inicio

suma ← 0 Desde mes ← 0 mientras mes ≤ 11, mes ← mes + 1

suma ← suma + temps[mes] Fin_Desde Regresa (suma/12)

Fin

Algoritmo 6.2: Algoritmo del módulo promTemps(temp[])

En contraste con el ciclo del módulo leerTemp, en este módulo el contador del ciclo se inicia en 0 y termina en 11 (aunque el valor que tiene al finalizar el ciclo es 12) pues se utiliza para ir sumando cada uno de los elementos del arreglo, así que la variable mes indicará cada una de las posiciones del arreglo.

Módulo aFahrenheit(tempC) Inicio

tempF = 9 5 tempC + 32

Regresa tempF Fin

Algoritmo 6.2: Algoritmos de módulos secundarios del problema 6.1 (pseudocódigo)

Page 36: un56

Fundamentos de programación Programa Desarrollado

A partir de los módulos secundarios presentados se puede plantear el módulo principal.

Módulo Principal Inicio

/* Lectura de las temperaturas, invocando al módulo leerTemps */ Imprimir “Ingresa los promedios de temperaturas mensuales” leerTemps(temps[])

/* Cálculo del promedio utilizando el módulo promTemps */ promC ← promTemps(temps[])

/* Conversión del promedio a grados Fahrenheit, invocando al módulo aFahrenheit */ promF ← aFahrenheit(promC)

/* Conversión de las temperaturas mensuales a grados Fahrenheit */ Desde mes ← 0 mientras mes ≤ 11, mes ← mes + 1

tempsF[mes] ← aFahrenheit(temps[mes]) Fin_Desde

/* Impresión de temperaturas mensuales en Fahrenheit */ Desde mes ← 1 mientras mes ≤ 12, mes ← mes + 1

Imprimir “Temperatura en Fahrenheit del mes”, mes, “ es: ”, tempF[mes-1] Fin_Desde

/* Impresión del promedio en grados Celsius y grados Fahrenheit */ Imprimir “ El promedio en grados Celsius es: “, promC Imprimir “El promedio en grados Fahrenheit es: “, promF

Fin

Algoritmo 6.4: Algoritmo del módulo principal del problema 6.1 (pseudocódigo)

Por esta ocasión se deja como ejercicio al lector que realice las representaciones en diagrama de flujo del módulo principal, considerando que el símbolo que se utiliza para llamar a módulos que no devuelven ningún valor, como es el caso se leerTemps se muestra a continuación:

leerTemps(temp[])

Esto no es caso, del resto de los módulos secundarios que sí devuelven un valor, así que se utiliza el símbolo de proceso (rectángulo).

Con el ejemplo anterior resaltan indudablemente varias ventajas que proporciona un diseño modular:

1. Es posible reutilizar código, ésta indudablemente es una de las más importantes ya que no es necesario escribir el mismo código cada vez que deseamos realizar una tarea semejante. Por

Page 37: un56

Fundamentos de programación Programa Desarrollado

ejemplo, el módulo aFahrenheit se utiliza 13 veces (12 en el ciclo y una para convertir el promedio) y sólo basto definirlo una vez.

2. Fácil detección y corrección de errores, dado que el problema fue divido en pequeños módulos cada uno responsable de una tarea, si en algún momento existiera un error en la solución global, basta identificar cuál de las tareas es la que no se está resolviendo adecuadamente y corregir únicamente aquellos módulos involucrados.

3. Fácil modificación o extensión, si se requiere modificar una tarea del problema o agregar una nueva, no será necesario rediseñar todo el algoritmo, basta con hacer las modificaciones en los módulos pertinente.

4. Un problema se vuelva más sencillo de solucionar si pensamos de manera modular.

Una vez que se ha ilustrado como realizar una solución modular, lo siguiente es explicar cómo se codifica cada uno de los módulos, tema que se abordará en las siguientes secciones. }

Actividad 1. Foro Funciones

En esta actividad reflexionarás sobre las ventajas del diseño descendente y las funciones en Lenguaje C a partir de la solución modular del problema que planteaste en las unidades anteriores. Conforme avances en el diseño modular del mismo ingresa tus comentarios al respecto en el Foro Funciones.

6.2. Definición, declaración e invocación de funciones en C

En el caso particular de C, un módulo se implementa como una función, recuerda que en la unidad 3 se explico que un programa en C está integrado de varias funciones, donde una función se define como una porción de código (un conjunto de instrucciones agrupadas por separado) enfocado a realizar una tarea en específico, resaltando la función principal main.

Al igual que la función principal, cada una de las funciones existe de manera independiente, tiene sus propias variables, instrucciones y un nombre que las distingue de las otras. Además de los parámetros de entrada (también llamados argumentos) que recibe y el tipo de dato que regresa.

La forma general para definir una función es:

<tipo de dato retorno><identificador de la función>( <parámetros deentrada>)

{ <instrucciones de la función> return<expresión>;

}

Page 38: un56

Fundamentos de programación Programa Desarrollado

El <tipo de dato retorno> indica el tipo de dato que la función devuelve (puede ser cualquiera de los tipos básicos), para lo cual se utiliza la palabra reservada return. Cuando la función no va a devolver ningún dato se especifica mediante el tipo void y no debe incluir la palabra reservada return. El <identificador de la función> es el nombre que le vamos a dar a la función y mediante el cual vamos a hacer referencia a ella. Se deben seguir las mismas reglas que los identificadores de las variables y se recomienda que sea nemónicos. Enseguida del nombre y entre paréntesis va la lista de parámetros, que consiste de una lista de declaraciones de variables locales que van a contener los datos de entrada para la función, se debe especificar explícitamente el tipo y nombre para cada uno, separados por comas, aún cuando sean del mismo tipo: tipo1 param1, tipo2 param2,…, tipoN paramN. Por último, las instrucciones del cuerpo de la función van entre llaves.

La primera línea de la definición de una función se conoce como encabezado de la función (en inglés header).

Para ilustrar esto, a continuación se muestra la codificación del módulo aFahrenheit definido en la sección anterior.

float aFahrenheit(float tempC) {

return ((9.0/5.0)*tempC+32);}

Programa 6.1: Codificación del módulo aFahrenheit

La llamada o invocación a una función se realiza cuando se requiere que se ejecuten las instrucciones del cuerpo con valores de entrada determinados. Para invocar a una función se tiene que escribir el nombre seguido de los valores que deben coincidir con el orden, número y tipo de los parámetros de entrada dados en la definición de la función y deben estar encerrados entre paréntesis.

La sintaxis general para invocar una función ya sea predefinida o definida por el programador, es la siguiente:

<identificador de la función>( <lista de valores>);

Los parámetros de entrada de una función son valores, por lo que pueden estar determinados por: valores constantes (8,‟a‟, 5.2, “cadena constante”) o una variable (lo que realmente se pasa a la función

Page 39: un56

Fundamentos de programación Programa Desarrollado

es el valor almacenado) o una expresión (el parámetro de entrada real es el resultado de la evaluación). Por ejemplo, la llamada a la función que definimos se remarcar en la siguiente instrucción:

promF = aFahrenheit(promC);

Al igual que las variables una función debe de ser declarada antes de utilizarse, es decir, antes de invocarla. La declaración de una función se realiza escribiendo el prototipo de la función. El prototipo de una función coincide con el encabezado de la misma terminando con punto y como (;) El prototipo de una función sólo indica al compilador que existe y cómo es, más no lo que hace por lo tanto se debe definir después. Por ejemplo, el prototipo de la función antes definida sería:

float aFahrenheit(float tempC);

Cabe señalar que en el prototipo de las funciones se puede omitir los identificadores de los parámetros, más no los tipos.

Para ilustrar todo lo anterior, a continuación se muestra la codificación del algoritmo modular diseñado en la sección anterior.

/* Programa: promedioTemp.c * Descripción: Calcula el promedio de las temperaturas promedio * mensuales registrada a lo largo de un año*/

/* Biblioteca */ #include<stdio.h> #include<stdlib.h>

/* Variables globales */ int meses = 12;

/* Prototipos de las funciones */ /* Función que lee las temperaturas promedio mensuales registradas en un año*/ void leerTemps( float temps[]);

/* Función que calcula el promedio de las temperaturas promedio mensuales registradas en un año*/ float promTemps( float temps[]);

/* Función que convierte de grados Celsius a grados Fahrenheit */

Page 40: un56

Fundamentos de programación Program Desarrollado

float aFahrenheit(float tempC);

/* Función principal */ main() {

/* Declaración de variables locales a main */ float temps[12], tempsF[12], promF, promC; int mes;

/* Lectura de las temperaturas, invocando a leerTemps*/ printf(“Ingresa los promedios de temperaturas mensuales\n”); leerTemps(temps);

/* Cálculo del promedio utilizando la función promTemps */ promC = promTemps(temps);

/* Conversión del promedio a grados Fahrenheit, invocandoal

módulo aFahrenheit */promF = aFahrenheit(promC);

/* Conversión de las temperaturas promedio mensuales agrados

Fahrenheit, invocando al módulo aFahrenheit */ for(mes = 0; mes<=11; mes++)

tempsF[mes] = aFahrenheit(temps[mes]);

/* Impresión de temperaturas promedio mensuales en grados Fahrenheti*/

for(mes = 1; mes<=12; mes++) printf(“\n La temperatura en grados Fahrenheit del mes

%d es %.2f: ”, mes, tempsF[mes-1]);

/* Impresión del promedio */ printf(“\n\n El promedio anual en grados Celsius es: %.2f ”,

promC);printf(“\n El promedio anual en grados Fahrenheit es: %.2f ”,

promF);

system(“pause”);

} /* fin main */

Page 41: un56

Fundamentos de programación Programa Desarrollado

/* Definición de funciones */

void leerTemps (float temps[]) { /* Definición de variables locales a leerTemps */

int mes;

for(mes = 1; mes<=12; mes++) {

printf(“\n Ingresa la temperatura promedio del mes %d: ”,mes);

scanf(“%f”, &temps[mes-1]);}

} /* fin leerTemps */

float promTemps (float temps[]) { /* Definición de variables locales a promTemps */

int mes; float suma=0;

for(mes = 0; mes<=11; mes++) suma = suma + temps[mes];

return (suma/12); } /* fin leerTemps */

float aFahrenheit(float tempC) {

return ((9.0/5.0)*tempC+32); } /* fin aFahrenheit */

Programa 6.1: promedioTemp.c

Page 42: un56

Fundamentos de programación Programa Desarrollado

Figura 6.2: Ejecución del programa promedioTemp.c

Actividad 2. Funciones

En esta actividad presentarás una versión preliminar del programa que has trabajado como parte de las evidencias de aprendizaje anteriores, el cual, ahora debe incorporar módulos para hacer más eficiente su funcionamiento. En el aula virtual encontrarás más detalles.

6.3. Alcance de las variables El alcance de las variables es la parte del programa dentro de la cual se pueden utilizar. Cuando son declaradas fuera del cuerpo de cualquier función se denominan variables globales y pueden ser utilizadas en cualquier punto del programa a partir del lugar donde fueron declaradas, en cambio cuando son declaradas dentro del cuerpo de alguna función se denominan variables locales a ésta, es

Page 43: un56

Fundamentos de programación Programa Desarrollado

decir sólo dentro de esa función pueden ser utilizadas. Las variables locales que tienen en el mismo nombre pero fueron declaradas en diferentes funciones, no tienen relación, son espacios de memoria totalmente independientes uno de otro. Podemos decir que, son como dos personas diferentes que tienen el mismo nombre. Por otro lado las variables que se ponen como argumentos en la declaración de una función se consideran locales a estas. Para ejemplificar lo anterior, se muestra el siguiente programa, en el cual se distinguen con diferentes colores el alcance de las variables.

#include<stdio.h> #include<stdlib.h>

Variable global int TAM = 5;

Variable local a

void inicializaA(int A[]){ Variable local

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

A [i] = 0;}

main() {

int i; int A[] = {1,1,1,1,1};

inicializaA

Referencia a una variable global

Declaración de variables locales a main

printf(“Arreglo antes de la llamada a inicializaA: A = [”); for (i=0; i<TAM; i++)

{ if(i< TAM -1) printf(“%d ,”,A[i]);

else printf(“%d ]\n\n\t”,A[i]);

}inicializaA(A);

printf(“Arreglo despues de la llamada a inicializaA: A = [”); for (i=0; i<TAM; i++)

{ if(i< TAM -1) printf(“%d ,”,A[i]);

else printf(“%d ]\n\n\t”,A[i]);

}system(“pause”);

}Programa 6.2: porReferencia.c

Page 44: un56

Fundamentos de programación Programa Desarrollado

Al utilizar variables globales todas las funciones pueden manipularlas, sus valores permanecen mientras el programa está en ejecución. Sin embargo su uso puede promover errores de tipo lógico, ya que al modificar el valor de una variable dentro de una función puede afectar el resultado de otra. Por ejemplo, supongamos que la función inicializaA() modifica el valor de la variable TAM que almacena el número de elementos del arreglo A, este cambio repercutirá en los ciclos de la función main, los cuales imprimen el arreglo A. En este caso se producirá un error en la ejecución, pues si el valor es menor a cinco no se imprimirán todos los valores y se es mayor entonces habrá elementos indefinidos. Detectar y corregir este tipo de errores puede ser una tarea nada fácil, por lo que no se recomienda el uso de variables globales, lo cual no ocurre si son constantes.

Las variables locales por otra parte favorecen mucho la reutilización de código y la modularidad, ya que cada función declara y manipula sus propias variables sin depender de lo que ocurra en otras funciones, esto no significa que al utilizar solamente variables locales no sea posible compartir datos entre las diferentes funcione, esto se hace mediante sus datos de entrada y retorno, una posible desventaja es que el valor de las variables locales se pierde cada vez que la función termina.

6.4. Paso de parámetros

EL paso de parámetros se refiere a la forma en la que se transfieren como parámetro una variable a una función, esencialmente, si se le otorgan o no permisos para modificar los valores originales. Cuando no se le otorga permisos para que la modifique se dice que es paso de parámetros por valor, pues en este caso sólo se transfiere el valor de la variable, el cual se almacena en una variable local de la función que se está llamando. En cambio, cuando la función puede modificar el valor de la variable se dice que es un paso de parámetro por referencia, pues en este caso no se pasa sólo el valor sino la dirección de memoria que le corresponde a la variable.

En los siguiente subtemas se explica más a detalle los dos tipos de paso de parámetro.

6.4.1. Por valor

Cuando se realiza una llamada a una función por valor y en ésta aparece una variable como uno de los argumentos, en realidad no se está pasando la variable sino una copia del valor que ésta contiene, lo cual implica que si dentro de la función se modifica el argumento esto no se ve reflejado en el programa desde el cual se hizo la llamada, pues son localidades de memoria diferentes (recuerda que en cada llamada a una función se crean nuevas variables y se destruyen una vez finaliza la ejecución).

#include<stdio.h> #include<stdlib.h>

void inicializa(int a) {

a = 0;

Page 45: un56

Fundamentos de programación Programa Desarrollado

printf("\nEl valor de la variable local \"a\" es %d\n\n",a);}

main() {

int a=10;

/* Llamada a la función incializa */ printf("\nEl valor de \"a\" antes de la llamada es %i\n\n", a); inicializa(a); printf("\nEl valor de \"a\" despues de la llamada es %i\n\n", a);

system("pause"); }

Programa 6.3: porValor.c

La ejecución del programa es:

Figura 6.3: Ejecución del programa pasoValor.c

En la ejecución puedes ver que la variable local a main no se modifica, esto es porque se pasa una copia del valor que almacena cuando se realiza la llamada a la función inicializa(a). Este valor se guarda en un espacio de memoria, también llamado a, que es una variable local a la función y que existe mientras ésta se ejecuta. Observa que el cambio si se realiza en la variable local de la función inicializa.

6.4.2. Por referencia

La llamada a una función por referencia sí modifica el valor de la variable, pues lo que realmente se está pasando es la dirección de memoria asignada a la variable para que la función pueda modificar el valor. En C los arreglos siempre se pasan por referencia, ya que el nombre del arreglo en realidad almacena la dirección de memoria donde se encuentra almacenado el primer elemento del arreglo. De esta manera cuando se realiza una llamada a una función y se escribe el identificador de un arreglo como

Page 46: un56

Fundamentos de programación Progrrrollado

parámetro, se está pasando la dirección. Para ejemplificar lo anterior se muestra la ejecución del programa pasoReferencia.c que se presentó en un subtema anterior.

Figura 6.4: Ejecución del programa pasoRefencia.c

En la ejecución del programa se observa que después de la llamada a la función cambia el estado del arreglo A.

Finalmente, cabe mencionar que para realizar la llamada por referencia de una variable de tipo básico en lenguaje C es necesario pasar la dirección de la variable para lo cual se utiliza el operador & seguido del nombre de la variable (&nombre), como se hace en la función scanf, este operador regresa la dirección de memoria que le corresponde a la variable indicada. Por otra parte, para almacenar la dirección de memoria de una variable se utiliza una variable de tipo apuntador. Una variable apuntador se encarga de almacenar una dirección de memoria, la declaración de una variable apuntador es similar a la de cualquier otra variable, con la diferencia que se debe escribir un asterisco entre el tipo de la variable y el identificador. El tema de los apuntadores es muy interesante, sin embargo, no es uno de los objetivos de este curso.

Para que apliques lo aprendido en esta unidad y a lo largo del curso se propone la siguiente evidencia de aprendizaje.

Evidencia de aprendizaje: Programa en C. Entrega final del programa modular que soluciona el problema planteado

Esta es la última evidencia, deberás realizar el diseño modular del problema original que describiste en la evidencia de la unidad dos, así como la codificación de tu solución. Para conocer los detalles ingresa al aula virtual.

Consideraciones específicas de la unidad

Te sugerimos que escribas, compiles y ejecutes los programas que se desarrollaron a lo largo de esta unidad para que comprendas mejor los contenidos y te apropies de ellos.

Page 47: un56

Fundamentos de programación Programa Desarrollado

Referencias

Deitel H, M., & Deitel P, J. Cómo programar en C/C++. México: Prentice Hall.

Joyanes, L., & Zohanero, I. (2005). Programación en C. Metodología, algoritmos y estructuras de datos. aspaño: Mc Graw Hill.

Kernighan, B., & Ritchie, D. (1991). El lenguaje de programción C. México: Prentice-HallHispanoamericana.

López, L. (2005). Programación estructurada en lenguaje C. México: Alfaomega.

Pérez, H. (1992). Física General (Primera Edición ed.). México: Publicaciones Cultura.