Está en la página 1de 60

Ciencias Exactas Ingeniería y Tecnología

Primer Semestre

Programa de la asignatura:
Fundamentos de programación

Unidad 3. Funciones y Estructuras de Datos

Ciudad de México, junio de 2019

Universidad Abierta y a Distancia de México


Unidad 3. Funciones y estructuras de datos

Índice

Unidad 3: funciones y estructuras de datos ................................................................................. 3


Presentación de la unidad ....................................................................................................................... 3
Propósitos............................................................................................................................................................ 4
Competencia específica ............................................................................................................................ 4
3.1. Diseño descendente (Top-Down)................................................................................................ 5
3.2. Definición, declaración e invocación de funciones en C .......................................... 12
3.3. Alcance de las variables ................................................................................................................... 18
3.4. Paso de parámetros .......................................................................................................................... 20
3.4.1. Llamada a una función por valor ........................................................................................... 21
3.4.2. Llamada a una función por referencia ............................................................................. 22
3.5. Estructuras de datos ......................................................................................................................... 23
3.5.1. Arreglos.................................................................................................................................................... 25
3.5.2. Cadenas ................................................................................................................................................. 44
3.5.3. Estructuras .......................................................................................................................................... 48
Cierre de la unidad ...................................................................................................................................... 59
Fuentes de consulta .................................................................................................................................. 59
Unidad 3. Funciones y estructuras de datos

Unidad 3: Funciones y Estructuras de Datos


Presentación de la Unidad

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 éste sería muy grande y, por lo tanto, difícil de
corregir o modificar.

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.
Según Levine (2001), se podría definir un módulo como un conjunto de
instrucciones que realizan una función específica, que están contenidos en un
fragmento del código principal y que tiene la característica de no ser
directamente ejecutable, sino que debe ser llamado para efectuar sus
funciones. Los buenos hábitos de programación indican que el módulo
principal debe ser pequeño y fácil de entender, y estar constituido casi por
completo de llamadas a otros módulos de más bajo nivel que realicen
efectivamente el trabajo.
Unidad 3. Funciones y estructuras de datos

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.

Así mismo y con el fin de poder dar solución a problemas cada vez más
complejos, conoceremos las estructuras que nos permiten procesar datos
relacionados entre sí y que están compuestas de conjuntos de datos básicos.

Propósitos

En esta unidad:

 Identificarás la forma en la que puedes analizar un problema para


resolver tareas simples que en su conjunto encuentren la solución total.
 Diseñarás algoritmos modulares para solucionar un problema.
 Construirás funciones en lenguaje C que realicen tareas específicas.
 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

Implementar funciones y utilizar estructuras de


datos para almacenar y manipular información con
el fin de resolver problemas cotidianos a través del
desarrollo de programas modulares escritos en
lenguaje C.
Unidad 3. Funciones y estructuras de datos

3.1. Diseño descendente (top-down)

La ventaja principal de los módulos o subrutinas es que organizan


funcionalmente las acciones dentro de un sistema de programación por
medio de su capacidad sintetizadora, que permite definir y asignar funciones
de acuerdo con un plan maestro sin tener que resolver cada vez localmente
problemas que pueden ser atendidos en un solo lugar (Levine, G. 2001 p.233).
Esta característica de los módulos da lugar a una metodología de
programación conocida como diseño estructurado.

Dentro del diseño estructurado existen varias “escuelas”, una de las cuales
parte de los módulos de más alto nivel, delegando responsabilidades a los de
más abajo cuando así convenga, y sin tener que esperar a que éstos estén
terminados (Levine, G. 2001 p.234). A esta metodología se le conoce como
diseño descendente (top-down).

La metodología del diseño descendente permite establecer 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, se descompone el problema en etapas de
estructuración, módulos o subrutinas jerárquicos, 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.
Unidad 3. Funciones y estructuras de datos

La creación de un módulo 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 con el que se definió.

No existe un método para saber en cuántos 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 3.1. Análisis y diseño con el método descendente (Top Down):


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.
Unidad 3. Funciones y estructuras de datos

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.

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.

Diagrama modular del problema 3.1


Unidad 3. Funciones y estructuras de datos

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:

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

Módulo leerTemps(temp[ ])

Inicio
Desde mes ← 1 mientras
mes ≤ 12, mes ← mes + 1
Imprimir
“Proporciona la
temperatura del
mes”, mes
Leer temps[mes-
1]
Fin_Desde
Fin

Observa que el ciclo del módulo leerTemps se inicia con mes igual a 1 y termina
cuando mes es 13, se propone así porque se pide la temperatura de los meses
1, 2, 3,.. 12, así que la variable mes guardará 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.

Algoritmo 3.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
Unidad 3. Funciones y estructuras de datos

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 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 3.3: Algoritmos de módulos secundarios del problema 3.1


(pseudocódigo)
Módulo aFahrenheit(tempC)
Inicio
tempF = 9⁄5 tempC + 32
Regresa tempF
Fin

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


módulo principal.
Unidad 3. Funciones y estructuras de datos

Algoritmo 3.4: Algoritmo del módulo principal del problema 3.1


(pseudocódigo)

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
A manera de ejercicio, puedes realizar 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[])

Este no es el caso del resto de los módulos secundarios que sí devuelven un


valor, así que se utiliza el símbolo de proceso (rectángulo).
Unidad 3. Funciones y estructuras de datos

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 ejemplo, el módulo
aFahrenheit se utiliza 13 veces (12 en el ciclo y una para convertir el
promedio) y sólo bastó 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
pertinentes.

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


modular.

Una vez que se ha ilustrado cómo 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.
Unidad 3. Funciones y estructuras de datos

3.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 anteriormente se explicó 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


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

La expresión <tipo de dato retorno> indica el tipo de dato que devuelve la


función (puede ser cualquiera de los tipos básicos), para lo cual se utiliza la
palabra reservada return. Cuando la función no devuelve ningún dato, se
especifica mediante el tipo void y no debe incluir la palabra reservada return.
La expresión <identificador de la función> es el nombre que le vamos a
dar a la función y con este nombre vamos a hacer referencia a ella. Se deben
seguir las mismas reglas establecidas para los identificadores de variables y se
recomienda que sean nemónicos. Después del nombre se coloca, entre
paréntesis, la lista de parámetros o la declaración de las variables locales que
van a contener los datos de entrada para la función, se debe especificar
explícitamente el tipo y nombre de la variable, separados por comas aun
Unidad 3. Funciones y estructuras de datos

cuando sean del mismo tipo. Ejemplo tipo1 param1, tipo2 param2, …, tipoN
paramN. Finalmente, 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 3.1: Codificación del módulo aFahrenheit

La llamada o invocación de 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 determinados y pueden


ser constantes (8,’a’, 5.2, “cadena constante”), una variable (el valor almacenado
pasa a la función) o una expresión. Por ejemplo, la llamada a la función que
definimos se muestra en la siguiente instrucción:
Unidad 3. Funciones y estructuras de datos

promF = aFahrenheit(promC);

Al igual que las variables, una función debe 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 coma (;) 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, la función debe definirse después. Por ejemplo, el prototipo de la
función anterior es:
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 */


float aFahrenheit(float tempC);
Unidad 3. Funciones y estructuras de datos

/* 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, invocando


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

/* Conversión de las temperaturas promedio mensuales a


grados
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 */

/* Definición de funciones */

void leerTemps (float temps[])


Unidad 3. Funciones y estructuras de datos

{
/* 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 3.2: promedioTemp.c


Unidad 3. Funciones y estructuras de datos

Figura 3.1: Ejecución del programa promedioTemp.c

Con este ejemplo se representa mediante un ejemplo, la estructura de un


programa en C incorporando funciones. Se debe poner especial atención en la
declaración de funciones para evitar errores de compilación.

Considera que el compilador sí distingue las letras mayúsculas de las letras


minúsculas; por lo tanto, debes tener especial cuidado al seleccionar los
nombres de las funciones.
Unidad 3. Funciones y estructuras de datos

3.3. Alcance de las variables

Cuando nos referimos al alcance de las variables, hacemos referencia al ámbito


en que se podrán utilizar dentro del código 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 decir sólo dentro de esa
función pueden ser utilizadas.

Las variables locales que tienen 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.
Unidad 3. Funciones y estructuras de datos

/asterisco porReferencia.casterisco/
#include<stdio.h>
#include<stdlib.h>
Variable global
int TAM = 5;
Variable local A
inicializaA
void inicializaA(int A[])
{ Variable local i
int i;
for (i=0; i<TAM; i++)
A [i] = 0; Referencia a una
} variable global

main()
{ Declaración de
int i; variables locales a main
int A[] = {1,1,1,1,1};
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 después 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 3.3: porReferencia.c

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.
Unidad 3. Funciones y estructuras de datos

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 si 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 funciones, 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.

3.4. Paso de parámetros

El paso de parámetros se refiere a la forma en la que se transfiere 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.

A continuación, se explica más a detalle los dos tipos de paso de parámetro.


Unidad 3. Funciones y estructuras de datos

3.4.1. Llamada a una función 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 finalizada la ejecución).

/*porValor.c*/
#include<stdio.h>
#include<stdlib.h>

void inicializa(int a)
{
a = 0;
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\" después de la llamada es %i\n\n",
a);

system("pause");
}
Programa 3.4: porValor.c

La ejecución del programa es:


Unidad 3. Funciones y estructuras de datos

Figura 3.2: 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 sí se realiza en la variable local de la
función inicializa().

3.4.2. Llamada a una función 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 parámetro, se está
pasando la dirección. Para ejemplificar lo anterior se muestra la ejecución del
programa pasoReferencia.c que se presentó anteriormente.

Figura 3.3: Ejecución del programa pasoRefencia.c


Unidad 3. Funciones y estructuras de datos

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.

3.5. Estructuras de datos

En muchas ocasiones nos vemos en la necesidad de procesar datos que están


relacionados entre sí, a este tipo de datos se les conoce como estructurados,
ya que están compuestos de un conjunto de datos básicos. A continuación se
muestran los tipos de esctructuras de datos.

Esquema de los tipos de estructuras de datos.


Por ejemplo pensemos en el nombre completo de una persona, que está
compuesto por: nombre, apellido paterno y apellido materno, o bien, en una
Unidad 3. Funciones y estructuras de datos

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

Figura 3.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:

Problema 3.2: Se requiere un programa para llevar el registro de calificaciones


de un grupo de diez estudiantes y generar los siguientes reportes:
 promedio del grupo,
 calificación máxima,
 número de estudiantes con calificación superior al promedio del grupo.
Unidad 3. Funciones y estructuras de datos

En este caso, es claro que las calificaciones de cada estudiante se pueden 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
las calificaciones 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 todas 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 éstos 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.

3.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í,
Unidad 3. Funciones y estructuras de datos

como es el caso de las calificaciones en el Problema mencionado, ya que todas


las calificaciones se pueden considerar como valores enteros.

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

A 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 un elemento en particular.

Podemos 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
mencionado, éstas pueden ser almacenadas en un arreglo unidimensional
como el que se muestra en la ¡Error! No se encuentra el origen de la r
eferencia.siguiente:
Unidad 3. Funciones y estructuras de datos

Representación gráfica de un arreglo unidimensional

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.

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
Unidad 3. Funciones y estructuras de datos

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,


de ahí la necesidad de dos índices, como se muestra en la figura siguiente:

Representación gráfica de un arreglo bidimensional

En ella 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 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 sí nos identifica a un elemento en particular.

Declaración e inicialización
En lenguaje C los índices de los arreglos siempre empiezan en cero, es decir, al
primer elemento del arreglo le corresponde la posición 0, al segundo la
posición 1, al tercero la posición 2 y así sucesivamente hasta llegar al elemento
TAM-1, donde TAM corresponde al tamaño del arreglo, en el ejemplo antes
mencionado.
Unidad 3. Funciones y estructuras de datos

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 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 [ TAM ],
después del 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>];

El tipo de dato para los arreglos puede ser de cualquier tipo básico, es decir
entero, flotante o caracter (en C int, float, double o char ). De todos ellos, los
arreglos de tipo caracter (char) tienen un tratamiento especial, ya que un
arreglo de este tipo se considera una cadena.

Debido a la importancia que tienen las cadenas en la programación más


adelante los abordaremos 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 uno de los elementos del conjunto de valores iniciales, la sintaxis
se muestra a continuación:
<tipo><nombre>[<tamaño>]={<valor0>,<valor1>,…,<valorTAM-1>};
Unidad 3. Funciones y estructuras de datos

La asignación de cada valor inicial se hace consecutivamente desde el


elemento 0, por lo tanto, no es posible asignar valores a elementos
discontinuos.

Veamos como ejemplo la declaración del arreglo unidimensional en la


imagen:

planteado para las calificaciones del problema mencionado. 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 cómo queda la declaración del arreglo bidimensional tabla


mostrado en la imagen anterior, inicializando sus valores:

int tabla[5][3]={{9,10,8},{5,9,6},{7,9,4},{8,9,6},{7,9,4}};
Unidad 3. Funciones y estructuras de datos

Aunque también es posible declararlo de la siguiente forma, para fines de


comprensión de código se recomienda utilizar la estructura anterior:

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

Esto se debe a que como ya se mencionó antes, un arreglo bidimensional se


puede ver como un arreglo de arreglos.

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.
int lista[10] = {9,10,8,5,9,6,7,9,4,8};

 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.
Unidad 3. Funciones y estructuras de datos

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 declarar 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,}};

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


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

En el caso de que la declaración fuera:


int tabla[5][3]={9,10,5,7,9,4,8,9,};
Unidad 3. Funciones y estructuras de datos

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


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

Como puedes observar la correcta definición de arreglos te permitirá evitar


errores en el manejo de los datos que se almacenen en él.

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 del 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.
Unidad 3. Funciones y estructuras de datos

Veamos algunos ejemplos:


Instrucción Descripción
tabla[0][2] = 8; Asignar el valor de 8 al tercer
elemento del primer renglón del
arreglo tabla.
Imprimir en pantalla el quinto
printf(“%d”,lista[4]);
elemento del arreglo lista.
Leer un número entero desde el
scanf(“%d”,&tabla[0][0]); teclado y lo asigna en la primera
posición del arreglo tabla.
Incrementar en uno el valor del
lista[1]++;
segundo elemento del arreglo lista.

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 siguiente figura:
Unidad 3. Funciones y estructuras de datos

Procesamiento de arreglos unidimensionales mediante ciclos

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

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 manipular a un índice, la
estructura general para estos ciclos se muestra en la siguiente figura:
Unidad 3. Funciones y estructuras de datos

Procesamiento de arreglos bidimensionales mediante ciclos anidados

Como se puede apreciar dos ciclos están anidados cuando uno está dentro
del otro, de tal forma que, por cada iteración del externo, el interno completa
todo un ciclo.

Considerando esto, observa que 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
Unidad 3. Funciones y estructuras de datos

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

Si analizamos el diagrama de flujo podemos observar 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).

Ejemplo Veamos cómo utilizar arreglos en la solución de un problema,


resolviendo el ejemplo de las calificaciones, enfocándonos únicamente en la
lectura de 10 valores 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:
Unidad 3. Funciones y estructuras de datos

Datos de entada: Calificaciones de los 10 estudiantes (calif [ ])


Salida: Promedio de calificaciones (prom)
Método:

∑9𝑖=0 𝑐𝑎𝑙𝑖𝑓[𝑖]
prom =
10

La solución del problema se representa mediante el pseudocódigo que se


muestra en el siguiente algoritmo.

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 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( )
Unidad 3. Funciones y estructuras de datos

{
/*Declaración del arreglo calificaciones*/
int calif[TAM];
double prom = 0;
int i;
printf("*******************************************\n”);
printf(“* El siguiente programa calcula el promedio de *\n");
printf(“* un grupo de diez estudiantes *\n”);
printf("********************************************\n”);
/*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];
}
/*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");
}
Codificación promCalificaciones.c

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


Unidad 3. Funciones y estructuras de datos

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.
A continuación, se presenta otro ejemplo para ilustrar el uso de arreglos
bidimensionales.

Ejemplo 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)
Unidad 3. Funciones y estructuras de datos

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 anteriormente, 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:
𝒅𝒆𝒕 = 𝑨[𝟎][𝟎] ∗ 𝑨[𝟏][𝟏] − 𝑨[𝟎][𝟏] ∗ 𝑨[𝟏][𝟎]

La solución del problema se da en el siguiente diagrama de flujo.


Unidad 3. Funciones y estructuras de datos

Algoritmo Determinante de una matriz 2x2


Unidad 3. Funciones y estructuras de datos

Puedes llevar a cabo la codificación del algoritmo como ejercicio, misma que
se muestra a continuación:
/* 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("***************************************\n");
printf("* Determinante de una matriz A de 2x2 *\n");
printf("***************************************\n");

/* 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 determinantes.c

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


Unidad 3. Funciones y estructuras de datos

Ejecución del programa determinante.c

3.5.2. 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 caracter 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 constate de tipo cadena se indica entre comillas dobles,
por ejemplo:
“Calle 2 #135”
Unidad 3. Funciones y estructuras de datos

Una cadena en lenguaje C termina siempre con el caracter 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 caracter 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.


Unidad 3. Funciones y estructuras de datos

Una diferencia importante entre usar scanf y gets es que con la primera
función la lectura de la cadena se da por terminada cuando el usuario presiona
la tecla [Enter] o Espacio, mientras que la segunda termina la lectura de la
cadena únicamente cuando el usuario presiona la tecla [Enter], tal como se
muestra en los siguientes ejemplos:

Cadena Instrucción Contenido del arreglo cad[]


ingresada
“Calle 2 #135” scanf(“%s”, cad[]:
cad); C a l l e / / … /
0 0 0
“Calle 2 #135” gets(cad); cad[]:
C a l l e 2 # 1 3 5 / /
0 0

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ón
Impresió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(){
Unidad 3. Funciones y estructuras de datos

char mensaje[30]=”Mar
profundo ”;
puts(mensaje);
system(“pause”);
}

Las funciones que nos permiten el manejo de cadenas se encuentran en la


biblioteca estándar string.h, para ilustrar algunas se muestra el siguiente
programa en leguaje C.

Ejemplo 3.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 de cadenas */
main( )
{
/* Declaración de variables */
char pwscorrecto[9]=”jk278la0”; /* Clave correcta */
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 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*/
Unidad 3. Funciones y estructuras de datos

}
else {
strcpy(mensaje, “Acceso denegado”); /* copia la
cadena acceso denegado al mensaje */
puts(mensaje); /* imprime la cadena*/
}
system("pause");
}
Programa password.c

En la siguiente figura se muestran dos ejecuciones del programa


b)

a)Clave inválida Clave válida

Ejecución del programa password.c

Con esto concluye la sección de arreglos. Recuerda que un arreglo es un


conjunto de datos del mismo tipo. En la siguiente sección verás cómo puedes
agrupar datos de diferentes tipos.

3.5.3. Estructuras

Las estructuras en lenguaje 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 memoria no
homogéneos de tal forma que una estructura puede estar formada por datos
de diferentes tipos.
Unidad 3. Funciones y estructuras de datos

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.

Definición, declaración e inicialización de una estructura de datos


“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 el lenguaje 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];
Unidad 3. Funciones y estructuras de datos

int edad;
float estatura;
char sexo;
}

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 caracter (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 llevar a cabo 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;
Unidad 3. Funciones y estructuras de datos

} 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:

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.
Unidad 3. Funciones y estructuras de datos

Acceso a los elementos de una estructura


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, hace referencia al


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:

En el siguiente programa se declara una estructura de tipo perro, que tiene los
siguientes elementos:
Elemento Tipo
Raza char[]
Edad int
Peso float

Posteriormente se declaran dos variables de este 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>
Unidad 3. Funciones y estructuras de datos

#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 y pesa %.2f
kilos\n",fido.raza,fido.edad,164,fido.peso);
printf("\nPluto es de raza %s, tiene %d a%cos y pesa %.2f
kilos\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)
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");
Unidad 3. Funciones y estructuras de datos

getch();
}

Codificación programa perros.c

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

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

Ejecución del programa perros.c

Para concluir con esta sección veamos el siguiente ejemplo donde se utilizan
estructuras y arreglos:
Se requiere un programa que permita registrar los datos de los perros que
ingresan a refugio canino, 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)
Unidad 3. Funciones y estructuras de datos

 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 o desplegar los datos de un perro. 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 se tiene como entrada los datos del perro. Para la opción de despliegue
se tendrá que dar, como dato de entrada, la clave asignada al 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 sobrepase la capacidad de atención del albergue (100 perros), 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:”
Unidad 3. Funciones y estructuras de datos

Leer op
Casos para op
Caso 1: Si c≥100 entonces
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 clave 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 Registro perros
Unidad 3. Funciones y estructuras de datos

Puedes llevar a cabo la prueba de escritorio como ejercicio. 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);
Unidad 3. Funciones y estructuras de datos

printf( "nombre: ");


fflush(stdin);
gets( perros[c].nombre);
printf( "color: ");
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 correspondiente
a 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 válida\n");
}
}while (op!=3); /* El ciclo do-while se repite mientras la
opción no sea salir (3) */
Unidad 3. Funciones y estructuras de datos

Programa registroPerros.c

Cierre de la unidad

Como hemos podido observar la implementación de funciones facilita el


desarrollo de módulos que pueden ser reutilizados y actualizado de forma más
fácil y eficiente. Al utilizar estructuras de datos para resolver problemas,
almacenar y manipular la información en el flujo de datos dentro del desarrollo
de programas modulares es una característica que robustece la funcionalidad
de los programas desarrollados en lenguaje C. Si requieres profundizar o
resolver alguna duda sobre el tema no dudes en consultar a tu Docente en
línea.

Fuentes de consulta

 Alonso Jordá, P., García Granada, F., Onaidía de la Rivadeherrera, E. (1998).


Diseño e implementación de programas en lenguaje C. Valencia:
Universidad Politécnica de Valencia, Colección Libro Docente.

 Cairo Osvaldo, Guardati Buemo Silvia. (2006). Estructura de Datos.


México: McGraw-Hill

 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.
Unidad 3. Funciones y estructuras de datos

 Levine G. (2001) Introducción a la Computación y a la Programación


Estructurada, México: Mc Graw Hill

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


Prentice-Hall Hispanoamericana.

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

También podría gustarte