Temas:
Motivación
Escribir un programa que determine la cantidad de combinaciones que se pueden realizar tomando k elementos distintos de un grupo de n elementos. El número de combinaciones está dado por la siguiente fórmula:
| n | n!
| | = ---------
| k | k! (n-k)!
Por esta razón, prácticamente todos los lenguajes incorporan algún mecanismo para que los programadores puedan definir sus propias funciones cuando no existe la función predefinida apropiada. Para definir una nueva función, el programador debe escribir el código (programa) que calcula esa función.
Por ejemplo, el siguiente programa calcula el número de combinaciones:
(Ver el programa completo en Combinaciones.java.)
class Combinaciones extends Program {
void run() {
print("Ingrese k ? ");
int k= readInt();
print("Ingrese n ? ");
int n= readInt();
int combinaciones= fact(n)/(fact(k)*fact(n-k));
println("Combinaciones= "+combinaciones);
}
// Definición de la funcion fact
int fact(int x) {
// fact recibe un argumento entero y entrega un resultado entero.
// El siguiente código calcula el factorial de x
int prod= 1;
int i= 1;
while (i<=x) {
prod= prod*i;
i= i+1;
}
// Indica qué valor entrega esta función.
return prod;
}
}
El siguiente patrón de programación se usa para definir varias funciones que pueden ser usadas en un programa:
Explicación:
class ... extends Program {
void run() {
...
}
tipo nombre-función ( parámetros ) {
... programa que calcula esta función ...
return expresión;
}
... Más funciones ...
}
en donde argumentos son 0, 1, 2 o más expresiones separadas por coma.
Por ejemplo:
nombre-función ( argumentos )
La semántica de una invocación de función es la siguiente:
fact(n-k)
x= 5;
Después de ejecutar este código, la variable prod almacena el
valor 120 (5!) e i el valor 6.
int prod= 1;
int i= 1;
while (i<=x) {
prod= prod*i;
i= i+1;
}
Como esta expresión es simplemente la variable prod, el resultado
de la evaluación es 120. Este valor es el resultado de
la invocación de fact y substituye toda la expresión de invocación:
return prod;
fact(n-k)
En cada invocación el argumento es distinto y por tanto el valor entregado por la función.
Ejercicio 1:
Definir la función repite(s,n) que entrega el string s concatenado con sí mismo n veces. Es decir, se desea que repite("hola",3) entregue (o retorne) como resultado el string "holaholahola".
El programa anterior produce la siguiente salida:
class Triangulo extends Program {
String repite(String s, int n) {
String r= "";
int i= 1;
while (i<=n) {
r= r+s;
i= i+1;
}
return r;
}
void run() {
int i=1;
while (i<=8) {
println(repite("*",i));
i= i+1;
}
}
}
*
**
***
****
*****
******
*******
********
Ejercicio 2:
Defina la función esPrimo(n) que entrega verdadero cuando n es un número primo y falso en caso contrario. Utilice cualquier método para determinar si n es primo. Ejemplos:
esPrimo(2) | true |
esPrimo(3) | true |
esPrimo(25) | false |
esPrimo(31) | true |
esPrimo(9) | false |
Ejercicio 3:
Escriba un programa que use la función esPrimo para mostrar los números primos entre 200 y 300.
Ejercicio 4:
Defina una función mayor que calcule el máximo valor en un arreglo nativo. Por ejemplo, si el arreglo tab contiene las siguientes asociaciones:
índice | valor |
---|---|
0 | 4.5 |
1 | 15.0 |
2 | 2.2 |
3 | 4.3 |
entonces:
El primer argumento es el arreglo, el segundo es el índice inicial en el
arreglo y el tercero es el índice final.
println( mayor(tab, 0, 3) ); // despliega 15.0
println( mayor(tab, 2, 3) ); // despliega 4.3
Solución:
Este ejemplo muestra que una función puede recibir como argumentos
referencias a objetos. En otros ejemplos veremos que una función puede
entregar (retornar) referencias a objetos.
double mayor(double[] tab, int pri, int ult) {
int i= pri;
double max= tab[pri];
while (i<=ult) {
if (tab[i]>max)
max= tab[i];
i= i+1;
}
return max;
}
println( ... );
Las acciones que realiza la invocación de println consisten en desplegar
en pantalla el argumento que recibe. Es ilegal escribir asignaciones
como:
porque println no retorna ningún valor.
x= println( ... );
Ejemplos de uso de mostrar son los siguientes:
void mostrar(double[] tab, int pri, int ult) {
int i= pri;
while (i<=ult) {
println(i+" -> "+tab[i]);
i= i+1;
}
// no hay return
}
que produce el siguiente resultado en pantalla:
mostrar(tab, 0, 3);
0 -> 4.5
1 -> 15.0
2 -> 2.2
3 -> 4.3
Defina el procedimiento grabar que reciba como argumentos un arreglo con valores de tipo double, dos enteros que señalan el rango de llaves válidas (pri y ult) y el nombre de un archivo. Su procedimiento debe producir un archivo con el contenido del arreglo. Por ejemplo, la siguiente invocación:
debe producir un archivo de nombre "cont.txt" con 4 líneas cuyo contenido
es:
grabar(tab, 0, 3, "cont.txt");
4.5
15.0
2.2
4.3
debe producir el siguiente resultado en pantalla:
double[ ] tabB= new double[4];
leer(tabB, "cont.txt");
mostrar(tabB, 0, 3);
0 -> 4.5
1 -> 15.0
2 -> 2.2
3 -> 4.3
Una función o procedimiento puede modificar los arreglos que recibe
como argumentos. En general, pueden producir efectos laterales
sobre sus parámetros, cuando son algún tipo de objetos.
void leer(double[ ] tab, String nom) {
TextReader lect= new TextReader(nom);
int i= 0;
while (true) {
double x= lect.readDouble();
if (lect.eofReached())
break;
tab[i]= x;
i= i+1;
}
lect.close();
}
En una función o procedimiento, los parámetros que corresponden a algún tipo de objetos son en realidad referencias de objetos (recuerde que una referencia es como el teléfono celular del objeto). Por lo tanto, dentro del procedimiento leer, la variable tab referencia el mismo objeto que es referenciado por la variable tabB en la invocación de leer:
Para esto hay que cambiar la definición de leer por:
double[ ] tabc= new double[1000]; // tabc está vacío
int n= leer(tabC, "cont.txt");
mostrar(tabC, 0, n-1);
En este caso leer2 es una función con efectos laterales
porque su labor no es sólo retornar un valor, si no que además debe
alterar los valores que se encuentran en el arreglo.
int leer2(double[ ] tab, String nom) {
... // idéntico a leer
return i; // la última llave inicializada
}
Observación:
En la primera versión de leer la asignación:
es ilegal porque se definió leer como void. Con la segunda
versión sí es válida esta asignación.
int ult= leer(tabB, "cont.txt");
Experimento:
¿Qué despliega el siguiente programa?
En este caso, el valor desplegado para i es 1. La variable i que
usa la función leer es interna a esa función y no tiene relación alguna
con la variable i recién escrita. Diremos que la variable i declarada
internamente en la función leer es invisible fuera de la función o
procedimiento.
int i=1;
double[ ] tabD= new double[1000];
int ult= leer2(tabD, "cont.txt");
println("leidos= "+i);
en donde leer3 es una función que recibe el nombre de un archivo
y construye un arreglo con los valores almacenados en el archivo.
Se define como:
double[ ] tabE= leer3("cont.txt");
mostrar(tab3);
Esta función cuenta los reales contenidos en el archivo, crea un arreglo
del tamaño apropiado, coloca los números contenidos en el archivo en
el arreglo, y al final retorna la referencia del arreglo. Esta es una
función sin efectos laterales, porque no produce ningún cambio
en niguno de sus argumentos.
double[ ] leer3(String nom) {
// Se lee una vez el archivo para determinar cuantos reales contiene
TextReader lect= new TextReader(nom);
int i= 0;
while (true) {
double x= lect.readDouble();
if (lect.eofReached())
break;
i= i+1;
}
lect.close();
int nelem= i;
// volvemos a leer el archivo para llenarlo
double[] tab= new double[nelem];
lect= new TextReader(nom);
i= 0;
while (i<nelem) {
tab[i]= lect.readDouble();
i= i+1;
}
lect.close();
return tab; // la última llave inicializada
}
Cuando se ejecuta la instrucción return, se termina con la ejecución
de la función en donde aparece y la función entrega el resultado de
evaluar la expresión que acompaña return. Por ejemplo, la
siguiente función calcula si un número es primo:
return exp;
La instrucción return en este caso es más ``fuerte'' que un break,
puesto que no sólo termina el ciclo, también termina la función completa.
boolean esPrimo(int n) {
int i= 2;
while (i<n) {
if (n%i==0) {
return false;
}
i= i+1;
}
return true;
}