Difference between revisions of "ES/Manuales/GuiaAOO/TemasAvanzados/Macros/StarBasic/ElLenguajeOOoBasic/FuncionesSubrutinas"
(Created page with "{{DISPLAYTITLE:Funciones y subrutinas}} <div align="right"><tt>“</tt>Todo lo complejo puede dividirse en partes simples<tt>”</tt><br>'''Rene Descartes'''</div> === Divid...") |
|||
Line 1: | Line 1: | ||
{{DISPLAYTITLE:Funciones y subrutinas}} | {{DISPLAYTITLE:Funciones y subrutinas}} | ||
− | |||
<div align="right"><tt>“</tt>Todo lo complejo puede dividirse en partes simples<tt>”</tt><br>'''Rene Descartes'''</div> | <div align="right"><tt>“</tt>Todo lo complejo puede dividirse en partes simples<tt>”</tt><br>'''Rene Descartes'''</div> | ||
+ | |||
=== Divide y vencerás === | === Divide y vencerás === |
Revision as of 19:37, 7 February 2013
Rene Descartes
Contents
Divide y vencerás
En ocasiones, las funciones e instrucciones incorporadas del lenguaje no son suficientes para resolver algún problema planteado, o su complejidad nos obliga a pensar en otra alternativa.
En otras ocasiones el código se vuelve tan largo que se vuelve difícil de leer, analizar o mejorar.
En estos casos, como en otros más que tal vez se nos presenten, recurrimos a crear nuestras propias funciones y subrutinas. En este capitulo veremos como declararlas y usarlas.
En el tema Instrucciones y funciones en OOo Basic , vimos algunos conceptos que nos serán muy útiles en este tema.
Empezaremos con los ejemplos, y después, las explicaciones.
Subrutinas
Copia y ejecuta la siguiente macro.
Option Explicit Sub MostrarMensaje1() MsgBox "Estoy aprendiendo OOo Basic", 48, "Aprendiendo OOo Basic" MsgBox "Es fácil y divertido", 48, "Aprendiendo OOo Basic" MsgBox "Ya voy a medio camino", 48, "Aprendiendo OOo Basic" End Sub
Este sencillo código nos resultara muy útil para nuestros propósitos. Vamos a suponer una segunda macro similar a la anterior.
Sub MostrarMensaje2() MsgBox "Es un poco tarde", 48, "Aprendiendo OOo Basic" MsgBox "Ya tengo sueño", 48, "Aprendiendo OOo Basic" MsgBox "Solo acabamos este tema", 48, "Aprendiendo OOo Basic" End Sub
Ahora tenemos dos subrutinas que hacen cosas muy similares. En ambas, el icono mostrado y el titulo del cuadro de dialogo siempre son los mismos; lo único que cambia es la cadena mostrada.
Este caso es idóneo para que creemos una subrutina a la que pasaremos uno o más parámetros o argumentos y realice una tarea repetitiva simplificando nuestro código.
Copia la siguiente subrutina y modifica las dos primeras para que queden como mostramos a continuación.
Option Explicit Sub MostrarMensaje3() Call MuestraMensaje( "Estoy aprendiendo OOo Basic" ) Call MuestraMensaje( "Es fácil y divertido" ) Call MuestraMensaje( "Ya voy a medio camino" ) Call MuestraMensaje( "Es un poco tarde" ) Call MuestraMensaje( "Ya tengo sueño" ) Call MuestraMensaje( "Solo acabamos este tema" ) End Sub Sub MuestraMensaje(Mensaje As String) MsgBox Mensaje, 48, "Aprendiendo OOo Basic" End Sub
Lo importante es que aprendas como llamamos a una subrutina (macro) con argumentos; es muy importante que los argumentos pasados sean del mismo tipo de los esperados.
La macro MuestraMensaje “necesita” un argumento llamado Mensaje que es de tipo String.
Al llamarla, “necesitamos” pasarle un valor o una variable (no importa el nombre) pero que sea de tipo String.
En el ejemplo utilizamos la palabra clave Call, y encerramos los argumentos entre paréntesis, lo cual no es obligatorio para llamar a una subrutina (macro); las siguiente líneas son equivalentes:
Sub MostrarMensaje3() Call MuestraMensaje( "Estoy aprendiendo OOo Basic" ) MuestraMensaje( "Estoy aprendiendo OOo Basic" ) MuestraMensaje "Estoy aprendiendo OOo Basic" End Sub
Observa como en la primera usamos Call y paréntesis, en la segunda omitimos Call y en la tercera omitimos tanto Call como los paréntesis.
Algunos programadores sólo utilizan Call para diferenciar cuando llaman a una subrutina personalizada, pero esto queda a tu criterio, al igual que el uso de paréntesis.
Argumentos por valor o por referencia
A una subrutina se le pueden pasar los argumentos o parámetros de dos formas: por valor o por referencia. Es de suma importancia conocer la diferencia entre ambos sistemas.
Cuando pasamos los argumentos por valor lo que se hace es pasar una “copia” del valor de la variable. El valor de este argumento solo se usa “dentro” de la subrutina, que es independiente del valor de la variable “fuera” de la subrutina.
En cambio, cuando los argumentos se pasan por referencia, se pasar la “ubicación” de la variable en la memoria, lo que es equivalente a pasarle la variable misma, por lo que al modificar su valor “dentro” de la subrutina, también lo modificamos “fuera” de esta.
Veamos un ejemplo.
Option Explicit Sub PasoPorReferencia() Dim sMensaje As String 'Asignamos una cadena a la variable sMensaje <nowiki>=</nowiki> "La travesía de mil kilómetros comienza con un paso" 'Llamamos a la subrutina y le pasamos el argumento Call CambiaValor( sMensaje ) 'Mostramos la variable con el nuevo valor, cambiado en la subrutina MsgBox sMensaje End Sub Sub CambiaValor(Cadena As String) 'Mostramos el mensaje con la cadena pasada MsgBox Cadena, 48, "Cadena Original" 'Modificamos el argumento pasado Cadena <nowiki>=</nowiki> "Eso lo dijo Lao Tse" End Sub
En este caso al modificar dentro de la subrutina el valor del argumento pasado, el cambio se ve reflejado en la variable origen fuera de la subrutina.
No ocurre así en el siguiente ejemplo.
Option Explicit Sub PasoPorValor() Dim sMensaje As String 'Asignamos una cadena a la variable sMensaje <nowiki>=</nowiki> "El trabajo es el refugio de los que no tienen nada que hacer" 'Llamamos a la subrutina y le pasamos el argumento Call NoCambiaValor( sMensaje ) 'Mostramos la variable que nos muestra lo mismo, es decir 'no hemos podido cambiar su valor en la subrutina MsgBox sMensaje End Sub 'Observa el cambio en la declaracion del argumento usando ByVal Sub NoCambiaValor( ByVal Cadena As String ) 'Mostramos el mensaje con la cadena pasada MsgBox Cadena, 48, "Cadena Original" 'Intentamos modificar el argumento pasado Cadena <nowiki>=</nowiki> "Eso lo dijo Oscar Wilde" End Sub
En este segundo ejemplo vemos como la variable origen (fuera de la subrutina) quedó intacta a pesar de que hemos modificado su valor dentro de la subrutina.
Utilizar un sistema u otro depende de la forma en que declaramos los argumentos en la declaración de la subrutina.
Para pasar un argumento por valor utilizamos la palabra clave ByVal delante del nombre del argumento.
Podemos utilizar la palabra clave ByRef delante del nombre para declarar que pasamos el argumento por referencia, si bien, es el modo por defecto, por lo que no es necesario indicarlo.
Para saber si debemos declarar una variable por valor o referencia debemos preguntarnos: ¿necesito manipular su valor en la subrutina llamada y que la modificación se refleje en la subrutina que la llamó?
Si la respuesta es SÍ debemos pasar la variable por referencia; si es NO, debemos pasarla por valor.
Mensajes de error por no pasar los argumentos
Si intentamos ejecutar directamente una subrutina con argumentos tanto desde el código como desde el IDE, y no le pasamos tantos valores o variables como argumentos precisa, obtendremos este mensaje de error:
Si intentamos ejecutarla desde la interfaz del usuario (desde el menú Herramientas | Macros | Ejecutar macro...) obtendremos también un mensaje de error:
Las subrutinas nos ayudan a dividir nuestro código en bloques lógicos más manejables, pequeños y reutilizables, donde además, podremos detectar y corregir más fácilmente los errores de nuestro código.
La instrucción Exit Sub
Habrá ocasiones en que sea necesario salir anticipadamente de una subrutina.
En este caso, utilizaremos la instrucción Exit Sub. Veamos su uso.
Option Explicit Sub Ejemplo_ExitSub() Dim sFrase As String sFrase <nowiki>=</nowiki> Trim(InputBox("Escribe una frase")) Call ContarLetras( sFrase ) End Sub Sub ContarLetras( Cadena As String) If Cadena <nowiki>=</nowiki> "" Then Exit Sub Else MsgBox "Hay" & Str(Len(Cadena)) & " letras en la cadena" & Chr(13) & Chr(13) & Cadena End If End Sub
Observa como en la subrutina ContarLetras si el argumento pasado está vacío, salimos inmediatamente de la macro con la instrucción Exit Sub.
Algunos autores no recomiendan el uso esta instrucción; en su opinión, toda subrutina debería estar estructurada adecuadamente de forma tal que no precise de salidas forzadas.
Sub ContarLetras1( Cadena As String) If Cadena <nowiki><></nowiki> "" then MsgBox "Hay" & Str( Len(Cadena) ) & " letras en la cadena" & Chr(13) & Chr(13) & Cadena End If End Sub
En esta macro solo se ejecuta el código si el argumento Cadena no está vacío.
Validación de datos
Algunos autores más exigentes argumentan que la validación de datos se tiene que hacer antes de llamar a una subrutina, de forma tal que éstas deberían hacer solo su trabajo y serían las subrutinas llamantes las que deben pasarle los datos correctos.
Para ejemplificar esto, veamos las modificaciones a las macros anteriores.
Sub Ejemplo_ExitSub2() Dim sFrase As String sFrase <nowiki>=</nowiki> Trim(InputBox("Escribe una frase")) If sFrase <nowiki><></nowiki> "" Then Call ContarLetras2( sFrase ) End If End Sub Sub ContarLetras2( Cadena As String) MsgBox "Hay" & Str(Len(Cadena)) & " letras en la cadena" & Chr(13) & Chr(13) & Cadena End Sub
La validación la hacemos en la linea:
If sFrase <nowiki><></nowiki> "" Then
Por lo que al llamar a la macro ContarLetras2 ya le estamos pasando un dato validado (correcto), por lo que dentro de la subrutina es innecesario hacer ninguna validación.
Utilicemos uno u otro criterio, nunca debemos dejar de validar los datos, para garantizar que sean correctos.
Un alto porcentaje de errores al programar son debidos a una deficiente validación de datos; para evitarlos, un gran porcentaje de código deberá emplearse para validar datos.
Funciones
Veamos otro ejemplo. Supongamos que nos piden hacer una subrutina (macro) que solicite el radio de un circulo y calcule su área.
Option Explicit Sub CalcularAreaCirculo1() Dim dArea As Double Dim sRadio As Single Const PI As Single <nowiki>=</nowiki> 3.1416 'Solicitamos el radio del circulo a calcular, observa que primero limpiamos los espacios 'con Trim, después convertimos el valor a tipo Single, que es el tipo de variable esperado sRadio <nowiki>=</nowiki> CSng( Trim( InputBox( "¿Cual es el radio?", "Área de un circulo", "1" ) ) ) 'Solo si tenemos valores mayores a cero If sRadio > 0 Then dArea <nowiki>=</nowiki> PI <nowiki>*</nowiki> ( sRadio ^ 2 ) MsgBox "El área de un circulo de radio = " & Str(sRadio) & " es =: " & Str(dArea) End If End Sub
La subrutina creada también podría ser algo así:
Option Explicit Sub CalcularAreaCirculo2() Dim dArea As Double Dim sRadio As Single sRadio <nowiki>=</nowiki> CSng( Trim( InputBox( "¿Cual es el radio?", "Área de un circulo", "1" ) ) ) If sRadio > 0 Then Call DevuelveAreaCirculo( sRadio, dArea ) MsgBox "El área de un circulo de radio " & Str(sRadio) & " es =: " & Str(dArea) End If End Sub Sub DevuelveAreaCirculo( Radio As Single, Area As Double ) Const PI As Single <nowiki>=</nowiki> 3.1416 Area <nowiki>=</nowiki> PI <nowiki>*</nowiki> ( Radio ^ 2 ) End Sub
Si bien lo anterior funciona, no es común usar subrutinas para manipular variables y argumentos.
Lo más correcto, si se quiere devolver un valor, es usar una función en vez de una subrutina.
Podemos decir, simplificando mucho, que una función es una subrutina que devuelve un valor.
La macro anterior, usando una función, quedaría de la siguiente manera.
Option Explicit Sub CalcularAreaCirculo3() Dim dArea As Double Dim sRadio As Single sRadio <nowiki>=</nowiki> CSng( Trim( InputBox( "¿Cual es el radio?", "Area de un circulo", "1" ) ) ) If sRadio > 0 Then 'Observa como usamos la funcion y asignamos el resultado a una variable dArea <nowiki>=</nowiki> AreaCirculo( sRadio ) MsgBox "El area de un circulo de radio = " & Str(sRadio) & " es =: " & Str(dArea) End If End Sub Function AreaCirculo( Radio As Single ) As Double Const PI As Single <nowiki>=</nowiki> 3.1416 'Observa como usamos el nombre de la función para devolver al valor AreaCirculo <nowiki>=</nowiki> PI <nowiki>*</nowiki> ( Radio ^ 2 ) End Function
Declarar una función
Para declarar una función en lugar de utilizar la palabra clave Sub utilizamos la palabra clave Function., y para declarar el final de la función, utilizamos End Function en lugar de End Sub.
La declaración de los argumentos se realiza del mismo modo que en las subrutinas.
Function AreaCirculo( Radio As Single ) As Double ' código de la función End Function
Pero existe un cambio importante, y es que debemos (aunque no es obligatorio) declarar el tipo del valor devuelto por una función.
Function AreaCirculo( Radio As Single ) As Double
En caso de omitir esta declaración (lo que no es muy recomendable), devolverá el tipo por defecto de las variables (Variant).
Para devolver el valor, dentro de la función utilizamos el nombre de la misma como destino.
'Observa como usamos el nombre de la función para devolver al valor AreaCirculo <nowiki>=</nowiki> PI <nowiki>*</nowiki> ( Radio ^ 2 )
Argumentos opcionales
Podemos pasar argumentos a subrutinas y funciones de forma opcional.
Supongamos ahora que se nos pide hacer una macro que calcule ya sea el área o el perímetro de un circulo. Para poder utilizar la misma función, agregamos un argumento opcional que defina si queremos calcular es el área o el perímetro.
Option Explicit Sub CalcularCirculo Dim dArea As Double Dim dPeri As Double Dim sRadio As Single sRadio <nowiki>=</nowiki> CSng( Trim( InputBox( "¿Cual es el radio?", "Circulo", "1" ) ) ) If sRadio > 0 Then 'Aquí usamos la función SIN el argumento opcional dArea <nowiki>=</nowiki> Circulo( sRadio ) 'Y aquí usamos la función CON el argumento opcional dPeri <nowiki>=</nowiki> Circulo( sRadio, True ) MsgBox "Área = " & Str(dArea) & chr(13) & _ "Perímetro = " & Str(dPeri), 64, "Circulo" End If End Sub Function Circulo( Radio As Single, Optional Perimetro As Boolean ) As Double Const PI As Single <nowiki>=</nowiki> 3.1416 'Comprobamos si el parámetro se paso o no If IsMissing( Perimetro ) Then 'Si no se paso le asignamos el valor por default Perimetro <nowiki>=</nowiki> False End If If Perimetro Then Circulo <nowiki>=</nowiki> PI <nowiki>*</nowiki> ( Radio <nowiki>*</nowiki> 2 ) Else Circulo <nowiki>=</nowiki> PI <nowiki>*</nowiki> ( Radio ^ 2 ) End If End Function
Hemos hecho uso de la palabra clave Optional delante de la declaración del argumento para definir que éste puede ser pasado de forma opcional.
En este caso utilizamos una variable tipo booleana (Boolean), que puede tomar el valor verdadero o falso.
Podemos declarar tantos argumentos opcionales como necesitemos, y del tipo que necesitemos.
Es importante que comprobemos si se pasó o no el argumento, y en caso negativo le asignemos un valor por defecto.
La función IsMissing
Para verificar si se paso un argumento utilizamos la función IsMissing( Argumento ), como mostramos en el ejemplo anterior. Los argumentos opcionales deben ser obligatoriamente declarados los últimos.
Uso de matrices como argumentos
Las funciones también admiten como argumentos matrices. Las matrices argumento pueden ser de cualquier tipo de dato admitido, excepto si se llenan utilizando la función Array, en cuyo caso el argumento debe declararse como Variant.:
Option Explicit Sub Sumando Dim mDatos(9) As Integer Dim co1 As Integer 'Llenamos la matriz con datos aleatorios entre 1 y 100 For co1 <nowiki>=</nowiki> LBound( mDatos() ) To UBound( mDatos() ) mDatos( co1 ) <nowiki>=</nowiki> Rnd() <nowiki>*</nowiki> 100 + 1 Next MsgBox "La suma de la matriz es = " & Str( SumaMatriz( mDatos() ) ) End Sub Function SumaMatriz ( Datos() As Integer ) As Integer Dim co1 As Integer For co1 <nowiki>=</nowiki> LBound( Datos() ) To UBound( Datos() ) SumaMatriz <nowiki>=</nowiki> SumaMatriz + Datos( co1 ) Next End Function
En este caso se declaró la matriz de tipo Integer, en correspondencia con el tipo de argumento en la declaración de la función.
En el siguiente ejemplo llenamos la matriz haciendo uso de la función Array:
Option Explicit Sub Sumando2() Dim mDatos() As Integer Dim iSuma As Integer 'Llenamos la matriz con la funcion Array mDatos() <nowiki>=</nowiki> Array(10,20,30,40,50,60,70,80,90) 'Intentamos sumar la matriz iSuma <nowiki>=</nowiki> SumaMatriz( mDatos() ) MsgBox Str( iSuma ) End Sub Function SumaMatriz ( Datos() As Integer ) As Integer Dim co1 As Integer For co1 <nowiki>=</nowiki> LBound( Datos() ) To UBound( Datos() ) SumaMatriz <nowiki>=</nowiki> SumaMatriz + Datos( co1 ) Next End Function
Al ejecutar obtendremos un error. La razón es que la función espera una matriz de tipo Integer pero se le esta pasando una matriz de tipo Variant, ya que la función Array, siempre, independientemente de como hayamos declarado la matriz, devuelve una matriz tipo Variant.
Cambiando la declaración de la función debe de funcionar:
Function SumaMatriz ( Datos() As Variant ) As Integer Dim co1 As Integer For co1 <nowiki>=</nowiki> LBound( Datos() ) To UBound( Datos() ) SumaMatriz <nowiki>=</nowiki> SumaMatriz + Datos( co1 ) Next End Function
La instrucción Exit Function
Las funciones también admiten el uso de la instrucción Exit Function:
Option Explicit Sub Correo() Dim sCorreo As String sCorreo <nowiki>=</nowiki> Trim(InputBox("Dame tu correo")) If ValidarCorreo( sCorreo ) Then MsgBox "Correo Valido" Else MsgBox "Correo NO valido" End If End Sub 'Para fines didácticos, solo validaremos que el correo tenga 'el obligado símbolo de arroba (@) y que no sea ni el primer 'ni el ultimo carácter Function ValidarCorreo( Correo As String ) As Boolean Dim pos As Integer 'Si el argumento Correo esta vacío, salimos de la función If Correo <nowiki>=</nowiki> "" Then 'Si lo deseas esta linea la puedes omitir, pues al salir con Exit Function 'la función devuelve Falso, pero tal vez en otros casos que no sea booleana 'la respuesta, necesites asignarle un valor predeterminado diferente ValidarCorreo <nowiki>=</nowiki> False Exit Function Else 'Buscamos la posición de la arroba con la función InStr pos <nowiki>=</nowiki> InStr( 1, Correo, "@" ) 'No debe ser ni el primero ni el ultimo carácter 'en el siguiente tema aprenderemos más de los operadores lógicos If pos > 1 And pos <nowiki><</nowiki> Len(Correo) Then ValidarCorreo <nowiki>=</nowiki> True Else ValidarCorreo <nowiki>=</nowiki> False End If End If End Function
La función Join
Un último ejemplo. Supongamos que tenemos que mostrar muchos mensajes al usuario, por ejemplo, el siguiente.
Option Explicit Sub MostrarMensajes1() Dim sMensaje As String sMensaje <nowiki>=</nowiki> "Por favor escoge una opcion: CANCELAR = Sales del programa " & _ "REPETIR = Intenta de nuevo IGNORAR = No hace nada" MsgBox sMensaje, 2, "Opcion" End Sub
En este ejemplo la estética brilla por su ausencia.
Mejoremos un poco la estética del cuadro de mensaje insertando unos saltos de linea mediante el uso de la función Chr con el argumento 10, que es el valor ASCII para el salto de linea:
Sub MostrarMensajes2() Dim sMensaje As String sMensaje <nowiki>=</nowiki> "Por favor escoge una opcion:" & Chr(10) & Chr(10) & _ "CANCELAR = Sales del programa" & Chr(10) & _ "REPETIR = Intenta de nuevo" & Chr(10) & _ "IGNORAR = No hace nada" MsgBox sMensaje, 2, "Opción" End Sub
Ahora el cuadro de mensaje tiene mucha mejor presentación.
Si debemos mostrar múltiples mensajes con diferentes cadenas, concatenar los saltos de linea en cada una no es una actividad muy placentera. Podemos crear una función que lo haga por nosotros:
Function InsertarSaltos( Datos() ) As String InsertarSaltos <nowiki>=</nowiki> Join( Datos(), Chr(13) ) End Function
Utilizamos la función Join de OOo Basic que devuelve una cadena uniendo los elementos de una matriz utilizando un carácter separador; ambos, matriz y carácter, son los argumentos de la función.
En el ejemplo siguiente, formamos una cadena de números separados por un guión:
Sub JuntarDatos() Dim mDatos() As Variant Dim sCadena As String mDatos() <nowiki>=</nowiki> Array(100,200,300,400,500) sCadena <nowiki>=</nowiki> Join( mDatos(), "-") MsgBox sCadena End Sub
Utilizaremos la función InsertarSaltos de la siguiente manera:
Sub MostrarMensajes3() Dim mMensajes() As Variant Dim sMensaje As String 'Recuerda que la función Array SIEMPRE devuelve una matriz Variant mMensajes() <nowiki>=</nowiki> Array("Por favor escoge una opción:","","CANCELAR = Sales del programa","REPETIR = Intenta de nuevo","IGNORAR = No hace nada") 'Llamamos a la función que inserta los saltos de linea sMensaje <nowiki>=</nowiki> InsertarSaltos( mMensajes() ) 'Mostramos el mensaje MsgBox sMensaje, 2, "Opción" End Sub
Si tienes dudas acerca de lo aquí explicado, tienes algún problema con AOO, o quieres ampliar la información, no dudes en dirigirte al Foro Oficial en español de Apache OpenOffice para Macros y API UNO |