Análisis y Diseño de Algoritmos                                                                                                      

Prof: Ing. Victor Garro

Asistente: Marco Elizondo Vargas

 

PROGRAMACION EN C++

 

 

CAPITULO 8 Métodos y funciones

 

Cuando un programa comienza a ser largo y complejo (la mayoría de los problemas

reales se solucionan con programas de este tipo) no es apropiado tener un único texto con sentencias una tras otra. La razón es que no se comprende bien qué hace el programa debido a que se intenta abarcar toda la solución a la vez. Asimismo el programa se vuelve monolítico y difícil de modificar. Además suelen aparecer trozos de código muy similares entre sí repetidos a lo largo de todo el programa.

Para solucionar estos problemas y proporcionar otras ventajas adicionales a la programación, los lenguajes de alto nivel suelen disponer de una herramienta que permite estructurar el programa principal como compuesto de subprogramas (rutinas) que resuelven problemas parciales del problema principal. A su vez, cada uno de estos subprogramas puede estar resuelto por otra conjunción de problemas parciales, etc... Los procedimientos y las funciones son mecanismos de estructuración que permiten ocultar los detalles de la solución de un problema y resolver una parte de dicho problema en otro lugar del código.

Supongamos que queremos calcular la media de votos que un número (dado por el

usuario) de candidatos ha conseguido obtener en una votación. La entrada y salida de datos se pretende mejorar separándolas mediante una línea de guiones (Figura 1).

 

 

-----------------------------------------------------

¿Cuántos candidatos hay? ... 3

-----------------------------------------------------

Candidato número: 1

Teclee el número de votos para este candidato: ... 27

-----------------------------------------------------

Candidato número: 2

Teclee el número de votos para este candidato: ... 42

-----------------------------------------------------

Candidato número: 3

Teclee el número de votos para este candidato: ... 3

-----------------------------------------------------

Número medio de votos conseguidos por candidato es: 2.4E+1

-----------------------------------------------------

 

Figura 1. Ejemplo de una entrada y salida de datos para el problema de la votación.

 

Como podemos observar en la Figura 2, el código de esta solución se ve oscurecido por la aparición repetitiva de las sentencias que se usan para dibujar la línea. Si tenemos en cuenta que estas sentencias no son ni siquiera una parte fundamental de la solución del problema y que se mezclan con las otras visualmente y que repetimos código similar, llegaremos a la conclusión de que esta solución no es la mejor.

En su concepción más simple, un procedimiento es una construcción que permite dar

nombre a un conjunto de sentencias y declaraciones asociadas que se usan para resolver un subproblema dado. Usando procedimientos (Figura 3) la solución es más corta,

comprensible y fácilmente modificable.

 

Los procedimientos no siempre realizan la misma función y pueden recibir parámetros

como se verá en breve. No repetir código no es la única razón para estructurar un programa usando procedimientos. Puesto que un subproblema puede codificarse como un procedimiento, un problema complejo puede dividirse en subproblemas más simples, quienes a su vez pueden ser de nuevo subdivididos hasta llegar a la descripción de subproblemas muy simples que se puedan codificar como procedimientos escritos en C++.

 

La filosofía que acabamos de presentar se denomina Refinamiento Progresivo o por

pasos, diseño descendente, Programación Top-Down o bien Divide y Vencerás.

 

 

/**************************************************

* Autor:

* Fecha: Versión:

***************************************************/

 

#include <iostream.h>

const int longitudLinea = 65;

const int maximoNumeroCandidatos = 50;

int main()

{

int numeroDeVotos, totalDeVotos, numeroDeCandidatos;

float media;

int i;

int numeroDeCandidato;

for( i = 1; i <= longitudLinea; i++ )

{

cout << "-";

}

cout << endl;

cout << "¿Cuántos candidatos hay? ...";

cin >> numeroDeCandidatos;

totalDeVotos = 0;

for( numeroDeCandidato = 1;

numeroDeCandidato <= numeroDeCandidatos;

numeroDeCandidato++ )

{

for( i = 1; i <= longitudLinea; i++ )

{

cout << "-";

}

cout << endl;

cout << "Candidato número:" << numeroDeCandidato;

cout << endl;

cout << "Teclee el número de votos para este candidato ";

cin >> numeroDeVotos;

totalDeVotos += numeroDeVotos;

}

for( i = 1; i <= longitudLinea; i++ )

{

cout << "-";

}

cout << endl;

media = float (totalDeVotos) / float (numeroDeCandidatos);

cout << "Número medio de votos conseguidos por candidatos es:";

cout << media << endl;

for( i = 1; i <= longitudLinea; i++ )

{

cout << "-";

}

cout << endl;

return 0;

}

Figura 2. Solución sin procedimientos del problema de la votación.

 

Declaración y llamada de procedimientos

 

Como el resto de entidades (objetos) en C++, los procedimientos (y las funciones) deben declararse antes de ser usados. La declaración de un procedimiento se realiza en dos partes: prototipo e implementación. El prototipo de un procedimiento sirve para declarar el nombre del procedimiento y los parámetros que recibe. La implementación sirve para definir qué trabajo realiza el procedimiento y cómo lo lleva a cabo. La sintaxis de la declaración de un procedimiento se presenta en la Figura 4.

 

 

/**************************************************

* Autor:

* Fecha: Versión:

***************************************************/

#include <iostream.h>

const int longitudLinea = 65;

const int maximoNumeroCandidatos = 50;

void dibujarLinea();

int main()

{

int numeroDeVotos, totalDeVotos, numeroDeCandidatos;

float media;

int numeroDeCandidato;

dibujarLinea();

cout << "¿Cuántos candidatos hay? ...";

cin >> numeroDeCandidatos;

totalDeVotos = 0;

for( numeroDeCandidato = 1;

numeroDeCandidato <= numeroDeCandidatos;

numeroDeCandidato++ )

{

dibujarLinea();

cout << "Candidato número: " << numeroDeCandidato;

cout << endl;

cout << "Teclee el número de votos para este candidato ";

cin >> numeroDeVotos;

totalDeVotos += numeroDeVotos;

}

dibujarLinea();

media = float (totalDeVotos) / float (numeroDeCandidatos);

cout << "Número medio de votos conseguidos por candidatos es: ";

cout << media << endl;

dibujarLinea();

return 0;

}

void dibujarLinea()

{

int i;

for( i = 1; i <= longitudLinea; i++ )

{

cout << "-";

}

cout << endl;

}

Figura 3. Solución con procedimientos del problema de la votación.

 

Por el momento no vamos a presentar procedimientos con parámetros y por esa razón no vamos a describir el elemento <ParámetrosFormales>.

 

Dentro del código fuente de nuestro programa, el prototipo de cada procedimiento

aparecerá antes del cuerpo principal de nuestro programa (función main), tras la declaración de constantes. Por otro lado, la implementación de cada procedimiento se realizará al final del código fuente del programa tras el cuerpo principal del mismo. Un ejemplo de esto puede verse en la Figura 3.

 

En las declaraciones es conveniente separar los procedimientos por una o varias líneas

en blanco para después poder encontrarlos rápidamente. Naturalmente todo procedimiento debería tener asociado un comentario respecto a lo que hace y el algoritmo que utiliza para ello.

 

El uso de procedimientos es un claro exponente de la programación estructurada y

modular, en el sentido que los errores se pueden localizar en el procedimiento que los

produce. La solución al problema es más comprensible y clara, y en el futuro, con una buena documentación, será posible realizar cambios en el programa de forma sencilla.

 

Una vez declarado, el procedimiento puede ser llamado (invocado) en el programa. Para

ello basta especificar su nombre (y parámetros en el caso de que los tuviese) como si se

tratara de una sentencia más. Cuando se alcanza la llamada, el control pasa a la primera

sentencia de dicho procedimiento y cuando éste acaba de ejecutar su cuerpo el control vuelve a la siguiente instrucción escrita tras la llamada al procedimiento.

 

La sentencia nula

 

El cuerpo de un procedimiento (en la implementación del procedimiento) está formado por una secuencia de sentencias. Las sentencias más importantes de C++ se han descrito en los temas anteriores. En este apartado se describirá una sentencia particular de C++ sintácticamente útil para la escritura de programas: la sentencia nula.

 

La sentencia nula es una sentencia de C++ que no realiza ninguna acción en el programa

(no hace nada). Como ya se ha dicho, la principal utilidad de esta sentencia es de origen

sintáctico, ya que se utiliza en aquellos puntos del código en C++ donde debe aparecer una sentencia pero no queremos que el programa realize ninguna tarea. Esta sentencia se

representa en C++ mediante el carácter punto y coma (;).

 

Un ejemplo típico de utilización de la sentencia nula son los cuerpos de bucles en los que no hay que realizar ninguna acción. Por ejemplo, en la Figura 5 puede verse un

procedimiento con un bucle cuyo cuerpo es la sentencia nula y cuya finalidad es esperar

hasta que se pulse un retorno de carro.

ImplementaciónProcedimiento:

CabeceraProcedim. Sentencias

CabeceraProcedim.:

void Identificador

ParámetrosFormales

{ }

( )

PrototipoProcedimiento:

CabeceraProcedim. ;

void esperaRetorno()

{

cout << "Pulsar retorno de carro para continuar...";

while( cin.get() != ‘\n’ );

}

Figura 5. Ejemplo de bucle con sentencia nula como cuerpo.

 

3. Localidad, anidamiento, ámbito y visibilidad

 

La introducción de procedimientos y funciones en un programa, permite tener varias

zonas de declaraciones de entidades (variables, constantes, etc... ) a lo largo del programa. Es necesario, por tanto, establecer ciertas reglas que definan las zonas de visibilidad o accesibilidad de los mismos en función del lugar donde aparecen declarados. Estas reglas son las reglas de ámbito. Las reglas de ámbito establecidas en la asignatura Elementos de Programación para el pseudolenguaje utilizado en la misma son totalmente válidas para C++ y, por tanto, aplicables a nuestros programas escritos en dicho lenguaje, por lo que no volverán a ser descritas en este tema.

 

La única aclaración que es necesario hacer a estas reglas de ámbito se refiere a la

visibilidad de los procedimientos. Al no permitir C++ el anidamiento de procedimientos

(declarar un procedimiento dentro de otro), las reglas de ámbito relativas a los procedimientos anidados no son aplicables.

 

4. Procedimientos con parámetros

 

Un procedimiento puede resultar mucho más útil si se puede variar su comportamiento

de una llamada a otra. Esto se consigue añadiéndole parámetros. Por ejemplo, sería poco útil que el procedimiento system de la biblioteca stdlib ejecutase siempre el mismo comando de sistema. Por eso es posible pasarle un parámetro que se corresponde con el comando que queremos ejecutar en cada momento.

 

system( "dir" );

 

En C++ existen dos tipos de parámetros diferentes:

1.Parámetros de entrada (valor)

2.Parámetros de entrada/salida (referencia)

Los parámetros únicamente de salida no son implementados por C++.

Los parámetros que se declaran en la cabecera de un procedimiento se denominan

 

Parámetros Formales, mientras que los parámetros que aparecen en una llamada al

procedimiento se denominan Parámetros Actuales o Reales.

 

 

Parámetros de entrada (valor)

 

Los parámetros de entrada (valor) se usan para proporcionar información de entrada a

un procedimiento. Dentro de éste pueden considerarse como variables cuyo valor inicial es el resultado de evaluar los parámetros actuales.

 

Como parámetro actual debe aparecer una expresión cuyo resultado sea un valor de un

tipo asignable al correspondiente parámetro formal. Puesto que las variables usadas como parámetros formales de entrada no sirven para cambiar a los parámetros actuales (sólo para conocer su valor en el momento de la llamada y asignarle un nombre a ese valor dentro del procedimiento) se les suele denominar Parámetros por valor.

En la Figura 7 puede verse un ejemplo de paso de parámetros por valor. En esta figura

puede apreciarse un procedimiento llamado dibLineas que recibe dos parámetros por

 

valor: anchura y altura.

void dibLineas( int anchura, int altura )

{

int nFila;

int nColumna;

for( nFila = 1 ; nFila <= altura; nFila ++ )

{

for( nColumna = 1; nColumna <= anchura; nColumna++ )

{

ParámetrosFormales

ParámetroFormal

,

ParámetroFormal

IdentificadorTipo Identificador

&

cout << "-";

}

cout << endl;

}

}

Figura 4.7. Parámetros de entrada

 

Como ejemplo, la llamada siguiente llamada a dibLineas genera una línea con 65

guiones:

 

dibLineas( 65, 1 )

 

Esta otra llamada a dibLineas dibuja 5 líneas en blanco:

 

dibLineas( 0, 5 )

 

Por último, la llamada a dibLineas que se muestra a continuación no hace nada:

 

dibLineas( 5, 0 )

 

 

Parámetros de entrada/salida (referencia)

 

Para usar parámetros de entrada/salida, el parámetro formal debe estar precedido por el

símbolo & y el parámetro actual debe ser una variable (no una expresión cualquiera). Los parámetros de entrada/salida se usan cuando se desea que un procedimiento cambie el contenido de la variable actual. El hecho de definir estos parámetros explícitamente como variables hace consciente al programador de los lugares donde un procedimiento puede modificar una variable que se le pase como parámetro.

 

El funcionamiento de los parámetros de entrada/salida está basado en pasar al procedimiento una referencia a la variable actual en lugar de su valor. Por ello, a estos parámetros también se los denominan parámetros por referencia.

 

Un ejemplo de paso de parámetros por referencia puede verse en la Figura 4.8. En esta figura se muestra un procedimiento que recibe tres parámetros por valor (a, b, c) y utiliza dos parámetros por referencia (R1 y R2) para devolver el valor de las raíces de un polinomio de segundo grado.

 

 

void raices( float a, float b, float c, float &R1, float &R2 )

{

float DiscriminanteS;

// Se supone un discriminante positivo

DiscriminanteS = sqrt ( b * b - 4.0 * a * c );

R1 = ( -b + DiscriminanteS ) / ( 2.0 * a );

R2 = ( -b - DiscriminanteS ) / ( 2.0 * a );

}

 

Figura 4.8. Parámetros de entrada/salida

 

Una posible llamada a la función de la Figura 4.8 sería:

 

Raices( 1.0, 2.0, 1.0, a, b )

 

donde a y b son dos variables de tipo float.

 

Los parámetros formales reciben valores que se reflejarán en los parámetros actuales

usados en la llamada al procedimiento. Los tipos de la variable formal y actual deben

coincidir.

 

Para el compilador, la zona de memoria representada por el parámetro formal y actual es la misma, y al terminar la ejecución del procedimiento el parámetro actual puede haber cambiado su valor.

 

No existe ninguna relación entre los identificadores de los parámetros formales y

actuales. La transferencia de valores se efectúa por posición en la lista de parámetros y no por el nombre que éstos tengan. Por ejemplo, el resultado de ejecutar:

 

a= 5;

b = 3;

p( a, b );

 

es el mismo que el de ejecutar:

 

b = 5;

a = 3;

p( b, a );

 

siempre que el procedimiento p no intente cambiar el valor de las variables que recibe

como parámetros.

 

Funciones

 

Mientras que un procedimiento ejecuta un grupo de sentencias, una función además

devuelve un valor al punto donde se llamó. Una llamada a una función puede aparecer como operando de alguna expresión. El valor de la función se usa, por tanto, para calcular el valor total de la expresión.

 

En la Figura 9 se muestra un ejemplo de función que recibe dos parámetros de tipo

float y devuelve un valor de este mismo tipo. Concretamente, la función de la Figura 9

 

devuelve el mayor valor que se le pase por parámetro.

float Maximo( float x, float y )

{

float resultado;

if (x>y)

{

resultado = x;

}

else

{

resultado = y;

}

return resultado;

}

Figura 4.9. Ejemplo de una función que calcula el máximo de entre dos números.

 

 

El uso de esta función puede ser algo como:

 

valor = 2.0 + Maximo( 3.0, P ) / 6.90

 

de forma que la llamada devolverá el mayor valor entre 3.0 y el contenido de la variable

p y con dicho valor se evaluará el resto de la expresión. Otro ejemplo puede ser:

 

p = Maximo( 3.0, p );

 

Obsérvese que el tipo del resultado que devolverá la función aparece declarado en la

cabecera sustituyendo a la palabra void que identifica a un procedimiento. En este sentido, en C++, puede verse a un procedimiento como un tipo especial de función que devuelve un valor void (nulo).

 

Las funciones en C++ pueden devolver cualquier tipo menos arrays.

Toda función debe ejecutar una sentencia return.

 

Ejemplo

 

El algoritmo de codificación del Cesar codifica un texto sustituyendo cada letra por la

letra tres posiciones a su derecha en el alfabeto. Realizar un programa que codifique un

texto terminado en el carácter ‘.’ mediante el algoritmo del Cesar. Este programa deberá

incluir una función para codificar una letra y hacer uso de esta función para codificar el

texto.

 

/**************************************************

* Autor:

* Fecha: Versión:

***************************************************/

#include <iostream.h>

#include <stdlib.h>

char codifica( char letra );

int main()

{

char l, c;

cout << "Introduzca texto terminado en punto";

cin >> l;

while ( l != ’.’ )

{

c = codifica ( l );

cout << c;

}

system( "pause" );

return 0;

}

char codifica( char letra )

{

/* Función que devuelve el valor de letra codificado por el método

del Cesar */

/* Implementar */

}

 

Dos números a y b se dice que son amigos si la suma de los divisores de a (salvo él

mismo) coincide con b y viceversa. Diseña un programa que tenga como entrada dos

números naturales n y m y que muestre en la pantalla todas las parejas de números

amigos que existan en el intervalo determinado por n y m.

 

/**************************************************

* Autor:

* Fecha: Versión:

***************************************************/

#include <iostream>

void ordena( int &x, int &y );

int sumaDivisores( int x );

bool sonAmigos( int x, int y );

int main()

{

int i, j, m, n;

cout << "Introduzca dos números";

cin >> n >> m;

ordena(m,n); /* garantizo que m es menor que n */

for( i = m; i <= n ; i++ )

{

for( j = i+1; j <= n; j++ )

{

if (sonAmigos(i,j))

{

cout << i << " y " << j;

cout << " son amigos" << endl;

}

}

}

system( "pause" );

return 0;

}

void ordena( int &x, int &y )

{

/* Procedimiento que ordena x, y de manera que x<=y */

/* Implementar */

}

int sumaDivisores( int x )

{

/* Función que retorma la suma de los divisores de un número*/

/* Implementar */

}

bool sonAmigos( int x, int y )

{

/* Función que retorma CIERTO si los dos números son amigos,

* si no FALSO */

/* Implementar */

}

 

El  juego NIM consiste en una cesta con 12 piedras. En cada turno, cada jugador retira de la cesta de 1 a 3 piedras. Pierde el jugador que se quede la última piedra. Nuestro amigo nos propone que hagamos un programa que juegue contra él y le gane al juego del NIM. El primer turno del juego corresponde siempre al ordenador, quien deberá elegir un número de piedras entre 1 y 3 intentando que el jugador se quede con la última piedra. Después pedirá por teclado el número de piedras que quita el jugador y así sucesivamente hasta que uno de los dos pierda, momento en que mostrará un mensaje diciendo quien ha ganado.

 

/**************************************************

* Autor:

* Fecha: Versión:

***************************************************/

#include <iostream>

int ordenador( int saco );

int jugador( int saco );

int main()

{

int saco;

bool fin;

saco = 12;

fin = false;

while (!fin)

{

if (saco<=1)

{

cout << "Has ganado";

fin = true;

}

else

{

saco = saco - ordenador ( saco );

}

if (saco<=1)

{

cout << "He ganado";

fin = true;

}

else

{

saco = saco - jugador ( saco );

}

}

system( "pause" );

return 0;

}

int ordenador( int saco )

{

/* Función que calcula cuantas piedras saca el ordenador intentando

que el jugador se quede con la última */

/* Implementar */

}

int jugador( int saco )

{

/* Función que lee por teclado cuantas piedras quita el jugador.

Debe comprobar que no es menor de 1 ni mayor de 3 o del número de

piedras que quedan en el saco */

/* Implementar */

}

 

Free Web Hosting