Funciones y subrutinas

From Apache OpenOffice Wiki
< ES‎ | Manuales‎ | GuiaAOO‎ | TemasAvanzados‎ | Macros‎ | StarBasic‎ | ElLenguajeOOoBasic
Revision as of 19:38, 7 February 2013 by Salva (Talk | contribs)

Jump to: navigation, search

Todo lo complejo puede dividirse en partes simples
Rene Descartes

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


ES StarBasic FuncionesSubrutinas.02.png


Si intentamos ejecutarla desde la interfaz del usuario (desde el menú Herramientas | Macros | Ejecutar macro...) obtendremos también un mensaje de error:


ES StarBasic FuncionesSubrutinas.03.png


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.


ES StarBasic FuncionesSubrutinas.01.png


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.


ES StarBasic FuncionesSubrutinas.04.png


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



ES.Plantillas.Logo foro es.png
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

Personal tools