Tutorial Aprender Visual Basic
PROGRAMACIÓN DE MÓDULOS. TIPOS. PROCEDIMIENTOS SUB, FUNCIONES FUNCTION.
Visual Basic, como no podía ser de otra manera, está orientado a la programación modular. Ya hemos comentado que puede haber cierta confusión terminológica: el concepto de módulo que hemos usado en cursos de bases de programación de aprenderaprogramar.com no coincide con el concepto de módulo en la terminología de Visual Basic. Vamos a hacer una clasificación libre tratando de conectar el Visual Basic con el desarrollo que hemos realizado en otros cursos.
Dado que el código contenido en un módulo estándar de Visual Basic es accesible desde distintos formularios del programa, será ventajoso colocar en este módulo todo lo que queramos disponer como "código compartido".
Buscando analogías con el desarrollo que hacemos en los cursos de pseudocódigo de aprenderaprogramar.com, usaremos el evento que se produce cuando ordenamos la ejecución de nuestros programas (carga del formulario o Form_Load) para disponer en él el código del "algoritmo principal" o guía del programa y el resto del código irá ordenado en procedimientos conducidos por eventos, procedimientos generales ó procedimientos función.
Habíamos dicho que un módulo no se ejecuta hasta que es llamado a ejecutarse desde el algoritmo principal de acuerdo con la sintaxis de pseudocódigo:
Llamar [Nombre del Módulo]
Sin embargo con programación guiada por eventos esto es sólo parcialmente cierto: un módulo puede ejecutarse por ser llamado desde algún punto del código pero también sin ser llamado desde el código, cuando tiene lugar un determinado evento.
La declaración de un procedimiento general se realizará mediante la sintaxis:
[Carácter Público o Privado] Sub [Nombre del procedimiento]([Parámetros])
EJEMPLO
Private Sub Calcular()
Todo procedimiento tiene un final indicado mediante End Sub, aunque se puede provocar una salida forzada usando la expresión Exit Sub.
El carácter público o privado se establece mediante las palabras clave Public o Private, que dan lugar a que dicho procedimiento pueda invocarse o no desde otros módulos. Sub indica que se está declarando un procedimiento Sub con un nombre determinado, y los paréntesis están destinados a contener parámetros requeridos por el procedimiento para ser invocado. El paso de parámetros lo veremos más adelante, por lo que consideraremos de momento procedimientos sin parámetro (paréntesis vacíos).
La declaración de un procedimiento conducido por eventos la realizaremos usando las listas desplegables de objetos y eventos que vimos cuando hablamos de botones (Buttons o Command Buttons). De esta manera, al seleccionar un objeto y un evento automáticamente nos aparecerá un código del tipo (según versiones habrá diferencias):
Este es un procedimiento conducido por eventos que se invoca cuando el texto contenido en el TextBox cambia. No prestaremos atención de momento a los parámetros que puedan aparecer automáticamente.
La declaración de una función se realizará mediante la sintaxis:
[Carácter Público o Privado] Function [Nombre de la función]([Parámetros]) As [Tipo de dato]
EJEMPLO
Private Function Hipotenusa() As Single
·
·
·
End Function
Como hemos dicho, una función devuelve un valor, de ahí que especifiquemos un tipo de dato para
ella. En caso de no especificarse, el tipo será Variant u Object (dependiendo de la versión) por defecto.·
·
·
End Function
El flujo para una función sigue las mismas reglas que para un procedimiento general o conducido por eventos: al llegar a End Function el control vuelve a la sentencia inmediatamente posterior a la llamada efectuada. Se puede provocar la salida forzada de una función utilizando la expresión Exit Function.
Los procedimientos pueden insertarse en el programa en cualquier orden, aunque siempre será recomendable tratar de disponerlos en el mismo orden que está previsto que se ejecuten.
La llamada de un procedimiento general o conducido por eventos se realiza, cuando no hay parámetros que pasar, simplemente escribiendo su nombre, o bien usando Call [Nombre]. La llamada a una función se hará normalmente para obtener un valor o asignar un valor a una variable, en expresiones del tipo:
Label1.Text = Label1.Text & [Nombre de la función]()
Variable = [Nombre de la función]()
If [Nombre de la función]() > [Variable]
Hay que recordar siempre que una "función" ejecuta un código y devuelve un valor: podríamos decir que es un híbrido entre una variable y un procedimiento.
La llamada a un procedimiento desde sí mismo es posible, dando lugar a un anidamiento o recursión. Habrá de existir una condición que evolucione para dar lugar a la salida de la recursión, regresando el control del flujo a la instrucción posterior desde la que se autollamó el módulo. No vamos a desarrollar contenidos relativos a la recursión, pero si tienes interés en profundizar en esta técnica de programación te remitimos a profundizar en esta materia mediante otros cursos que se ofrecen en aprenderaprogramar.com.
Una llamada del algoritmo principal a sí mismo, que en su momento escribimos como Llamar Inicio, sería posible con Visual Basic, pero no vamos a entrar a detallar este tipo de cuestiones que raramente se usarán.
VARIABLES LOCALES, DE CLASE Y GLOBALES EN VISUAL BASIC.
Ya hemos dicho que existen distintas formas de declarar variables con Visual Basic, aunque hayamos venido utilizando principalmente una: la instrucción Dim. Vamos a ver ahora el concepto de variable local, de clase y global trasladado a este lenguaje.
En primer lugar, usaremos el término ámbito para referirnos a aquel conjunto de partes del programa en el que una variable es conocida. Así distinguiremos:
• Variables con ámbito un procedimiento (Sub).
Son declaradas dentro del procedimiento y sólo son conocidas dentro de él. Son declaradas al principio del procedimiento (cabecera del procedimiento) o en un punto intermedio del mismo. No puede invocarse la variable sin que antes se haya declarado. En general, para una mejor ordenación del programa siempre será preferible que las declaraciones sean en cabecera. Podemos llamarlas variables locales al procedimiento.
• Variables con ámbito el módulo o la clase de formulario.
Son declaradas en la cabecera del código asociado al formulario y no están dentro de un procedimiento o función (Sub) concreto. Podemos llamarlas variables locales al módulo de formulario ó en las versiones más recientes de Visual Basic la denominación es “variables de clase” o “atributos de la clase”, ya que en las versiones más recientes de Visual Basic decimos que el código se organiza en “clases”. Estas variables son conocidas por todos los procedimientos existentes en el código asociado al formulario.
• Variables con ámbito todos los módulos o todas las clases del programa.
Son declaradas dentro de un módulo estándar usando la declaración Public.
Prueba el siguiente código (izquierda, versiones menos recientes VB. Derecha: versiones más recientes):
Las variables Valor y Número son variables locales al procedimiento Positiv, resultando no conocidas en el resto del programa.
MÓDULOS GENÉRICOS Y PARÁMETROS DE ENTRADA EN VISUAL BASIC.
Ya hemos visto que en Visual Basic una declaración de procedimiento siempre lleva unos paréntesis donde se pueden indicar parámetros de entrada. Los parámetros de entrada son indicados por el programador, o bien se generan automáticamente al tener lugar un evento y el propio ordenador envía ese parámetro de entrada al procedimiento que se dispara como consecuencia de que tenga lugar el evento. Veamos algunos ejemplos de procedimientos asociados a eventos.
Código 1 (versiones menos recientes VB)
Private Sub Text1_Change()
·
·
·
End Sub
Código 1 (versiones más recientes VB)·
·
·
End Sub
Private Sub TextBox1_TextChanged(ByVal sender As
System.Object, ByVal e As System.EventArgs) Handles
TextBox1.TextChanged
·
·
·
End Sub
Código 2 (versiones menos recientes VB)System.Object, ByVal e As System.EventArgs) Handles
TextBox1.TextChanged
·
·
·
End Sub
Private Sub Text1_KeyPress(KeyAscii
As Integer)
End Sub
Código 2 (versiones más recientes VB)As Integer)
End Sub
Private Sub Text1_KeyPress(ByVal sender As Object, ByVal e As
System.Windows. Forms.KeyPressEventArgs) Handles
TextBox1.KeyPress
·
·
·
·
·
·
End Sub
El primer código corresponde a un procedimiento conducido por eventos que se invoca cuando el texto
contenido en el TextBox Text1 cambia. En las versiones menos recientes de Visual Basic algunos
procedimientos conducidos por eventos no tienen parámetros de entrada. En las versiones más
recientes siempre suelen aparecer unos parámetros de entrada.System.Windows. Forms.KeyPressEventArgs) Handles
TextBox1.KeyPress
·
·
·
·
·
·
End Sub
El segundo código sería el de un procedimiento conducido por eventos que se invoca cuando se produce la pulsación de una tecla en el TextBox1. En este caso, en las versiones menos recientes hay un parámetro (podría haber varios) que es enviado al procedimiento cuando tiene lugar el evento: un valor tipo Integer que se corresponde con el número asociado a la tecla pulsada según el código AscII. En las versiones más recientes esa información viene dentro del parámetro “e” que lleva distinta información asociada relacionada con el evento. Crea un TextBox, un Label y prueba el siguiente código:
Comprobarás que cada vez que pulsas una tecla sobre el TextbBox aparece en pantalla un número: para la A, el 65, y para la a, el 97. Ese número es información que se pasa automáticamente al procedimiento cuando tiene lugar el evento. De esta forma se obtiene información útil sobre lo que está pasando (qué tecla ha pulsado el usuario, por dónde ha movido el ratón, qué botón del ratón ha pulsado, etc.) y se puede establecer un código previsto a ejecutar ante determinadas circunstancias.
Una declaración de procedimiento genérico que espera un parámetro de entrada podría ser esta:
Código versiones menos recientes VB:
Private Sub Raíz(Número As Single)
·
·
·
End Sub
Código versiones más recientes VB:·
·
·
End Sub
Private Sub Raíz(ByVal Número As Single)
·
·
·
End Sub
Dentro de los paréntesis se indican los parámetros de entrada requeridos, y el tipo de dato que han de
ser (de momento no prestaremos atención a cuestiones adicionales como la aparición de la palabra
ByVal). Si existen varios parámetros se escriben separados por comas, por ejemplo: (Número As Single,
Valor As Double, Nombre As String). Si no se especifica tipo de dato para los parámetros serán del tipo
por defecto (Variant en versiones menos recientes de Visual Basic u Object en las versiones más
recientes). Nosotros siempre declararemos el tipo de los parámetros porque es una buena práctica de
programación y no hacerlo podría dar lugar a diferentes problemas. La llamada al procedimiento podría
hacerse con una de estas sintaxis:·
·
·
End Sub
Call Raíz(dato)
Raíz dato
Raíz (dato)
La diferencia entre usar una u otra forma de llamada la veremos en breve.Raíz dato
Raíz (dato)
Una declaración de función genérica que espera un parámetro de entrada podría ser esta:
Código versiones menos recientes VB:
Private Function Raíz(Número As Single) As Single
·
·
·
End Sub
Código versiones más recientes VB:·
·
·
End Sub
Private Function Raíz(ByVal Número As Single) As Single
·
·
·
End Function
Las llamadas a funciones se realizan escribiendo el nombre de la función seguido de los parámetros
entre paréntesis. Dado que devuelven un resultado, se pueden usar para asignar contenido a una
variable, para mostrar algo en pantalla, etc. Por ejemplo:·
·
·
End Function
Resultado = Raíz(dato)
Label1 = Raíz(dato) ó Label1.Text = Raíz(dato)
Ejemplos de llamadas a un módulo genérico podrían ser estas:
Si tratáramos de realizar una llamada del tipo Raíz("Nueva York") nos aparecería un mensaje de error como: “Error 13. No coinciden los tipos.” ó “La conversión de la cadena "Nueva york" en el tipo 'Single' no es válida.”
El mismo "objetivo" cumplido utilizando funciones lo exponemos a continuación. Recuerda que una función, además de ejecutar un código, devuelve un valor.
PASO DE ARGUMENTOS POR REFERENCIA Y POR VALOR EN VISUAL BASIC.
Con Visual Basic se utilizan los términos "Paso de argumentos por Referencia (ByRef)", equivalente a lo que en el curso “Bases de la programación nivel II” de aprenderaprogramar.com hemos denominado transferencia de variable, y "Paso de argumentos por Valor (ByVal)", equivalente a lo que hemos denominado transferencia por valor.
Si no tienes claro el concepto de paso por referencia y paso por valor consulta el curso “Bases de la programación nivel II” de aprenderaprogramar.com.
El comportamiento de Visual Basic depende de la versión que estemos utilizando:
a) Con versiones menos recientes de Visual Basic: se permite no especificar cómo se pasa un parámetro. Por ejemplo Private Function suma (a As Integer, b As Integer). En caso de no estar especificado, la opción de defecto es que los parámetros pasan por referencia, con lo cual es posible que se modifique el valor de la variable inicial que se pasa como parámetro. En el último programa pasábamos la variable Dato como parámetro de la función Raíz. Al tener lugar la transformación del parámetro en valor absoluto, si introducíamos un número negativo su valor quedaba transformado. ¿Cómo hacer que la tranferencia sea por valor? Bastará con hacer la declaración de función o de procedimiento de la siguiente manera:
Private Function Raíz(ByVal Número As Single)
Private Sub Raíz(ByVal Número As Single)
Es decir, indicamos que el parámetro Número se procesa por valor.
b) Con versiones más recientes de Visual Basic: no se permite dejar sin especificar cómo se pasa un parámetro. Por ejemplo no se admite Private Function suma (a As Integer, b As Integer), de hecho si escribimos esto el editor lo transformará automáticamente en Private Function suma (ByVal a As Integer, ByVal b As Integer) ya que se asume que los parámetros pasarán por defecto por valor.
Para no tener dudas, lo más sencillo será especificar siempre cómo se pasa un parámetro a un procedimiento o una función. En caso de que no sepamos cómo pasarlo, recomendamos usar siempre ByVal para evitar que se generen modificaciones indeseadas dentro del procedimiento o función.
En la hipótesis de que existieran distintos parámetros y uno se quiera procesar de una manera y otro de otra, lo indicaremos antecediendo la palabra clave ByRef o ByVal delante del nombre del parámetro como en el siguiente ejemplo:
Private Function Raíz(ByVal Número As Single, ByRef x As Integer)
En las versiones menos recientes de Visual Basic, mientras que el modo de transferencia en las funciones sólo puede indicarse en la definición de la función, en los procedimientos podemos indicarlo bien en la declaración del procedimiento bien en la forma de llamada que empleemos. En versiones menos recientes de VB, el tipo de llamada condiciona cómo se transfiere el argumento. Así tenemos que:
a) Implican transferencia de variable llamadas del tipo: Call Raíz(dato) y Raíz dato
b) Implican transferencia de valor llamadas del tipo: Raíz(dato)
Se observa que es una diferencia mínima de escritura la que diferencia uno y otro tipo de llamada. Sin embargo, las consecuencias de usar uno u otro tipo pueden ser relevantes, y conocer y comprender estas implicaciones nos puede evitar más de un dolor de cabeza. Visual Basic no admite sintaxis del tipo Llamar Raíz(Dato) Por Valor.
Sin embargo, cuando en la declaración del procedimiento o función hemos indicado cómo se pasan los parámetros, resulta indistinto usar uno u otro tipo de sintaxis de llamada. Por tanto, en las versiones más recientes de Visual Basic, donde el modo de paso de parámetros es siempre indicado, no tiene relevancia cómo se haga la llamada.
ARRAYS DINÁMICOS Y ARRAYS ESTÁTICOS. INSTRUCCIÓN REDIMENSIONAR (REDIM Y REDIM PRESERVE)
En el apartado correspondiente a declaración de variables ya hemos tratado la forma de declarar arrays estáticos unidimensionales y multidimensionales a través de la instrucción Dim. Usaremos esta misma instrucción para declarar arrays dinámicos. Recordemos que habíamos definido array dinámico como aquel que es declarado con un número de elementos componentes indefinido. La sintaxis a emplear será:
Dim [Nombre del array]() As [Tipo de datos]
Redim [Nombre del array]([dimensiones])
Es decir, en la cabecera de código del formulario o de un procedimiento haremos una declaración sin especificar dimensiones que luego concretaremos a través de la instrucción Redim. Por ejemplo:
Dim A() As Integer supone la declaración del array dinámico (sin tamaño definido inicialmente).
ReDim A(2) establece que el array dinámico definido anteriormente tiene índice máximo 2.
En el caso de querer declarar una matriz (array o arreglo de varias dimensiones) como M (5, 7) que podría representar una tabla con datos como por ejemplo M (0,0) = 33, M (0,1) = 18, M (0,2) = 41 ... etc. debemos hacerlo de la siguiente manera:
a) En las versiones menos recientes de Visual Basic declararemos inicialmente Dim M() As Integer y posteriormente ReDim M (5, 7). Una vez fijado que M tiene dos dimensiones ya no es posible cambiar el número de dimensiones por otro número.
b) En las versiones más recientes de Visual Basic declararemos inicialmente Dim M( , ) As Integer, donde la coma indica que van a existir dos dimensiones que se declararán posteriormente. Si fuéramos a declarar posteriormente tres dimensiones escribiríamos Dim M ( , , ) As Integer, para 4 dimensiones Dim M ( , , , ) y así sucesivamente. No es posible cambiar el número de dimensiones de un array dinámico, que será el declarado inicialmente.
Un array estático no puede ser redimensionado. En cambio, uno dinámico puede sufrir múltiples redimensionamientos. No se puede declarar el tipo de dato cada vez que redimensionemos. Hay que hacerlo al declarar la matriz dinámica, una sola vez.
Por defecto, el uso de Redim implicará perder todos los datos anteriormente contenidos en la matriz (array, arreglo) que se redimensiona. Para conservar los datos anteriores podemos hacerlo de dos maneras. Por un lado, usando la cláusula Preserve según esta sintaxis:
Redim Preserve [Nombre del array]([dimensiones])
La limitación de usar Preserve viene dada porque únicamente se nos permite cambiar la última dimensión del array (la situada más a la derecha). Tampoco se permite cambiar el número de dimensiones. Si estamos trabajando con un array unidimensional no habrá problemas, pero con otro tipo de arrays puede haberlos.
Ejemplo: si declaramos un array multidimensional denominado B como Dim B( , , ) As Integer (en versiones menos recientes simplemente Dim B () As Integer), luego redimensionamos ReDim B(4, 2, 6) y posteriormente intentamos hacer ReDim B (2,2,2) obtendremos un mensaje de error ya que sólo puede cambiarse la dimensión más a la derecha. Es decir, ReDim (4, 2, x) donde x es la dimensión cuyo tamaño cambiamos es válido. Pero cambiar las dimensiones anteriores no es válido.
Cuando se utiliza Preserve aumentando el número de elementos del array, los nuevos elementos tienen valor cero o nulo y los que existían anteriormente conservan su valor. Cuando se trata de una disminución de los elementos de un array, aquellos contenidos en índices que desaparecen dejan de existir a todos los efectos, mientras que los existentes en dimensiones que se conservan mantienen su valor. En caso de arrays de varias dimensiones, con Redim Preserve sólo podemos cambiar la última dimensión, al igual que hemos visto antes. Para un array dinámico que se ha redimensionado como A(5, 5, 5):
a) Es válido: Redim Preserve A(5, 5, 8) Modifica la última dimensión.
b) No es válido: Redim Preserve A(5, 8, 5) Modifica una dimensión que no es la última.
Para conservar el contenido de arrays cuando no es posible a través de Preserve habremos de hacerlo declarando otro array con las dimensiones deseadas y transfiriendo elemento a elemento los valores desde un array a otro.
Una cuestión a tener en cuenta es que la instrucción Erase, cuyo funcionamiento habíamos visto que con matrices estáticas daba lugar a que todos los elementos de la matriz se convirtieran en ceros o cadenas vacías, al ser aplicada a un array dinámico da lugar a que este desaparezca totalmente. Por tanto, no podremos invocar la matriz después de usar Erase a no ser que la declaremos de nuevo usando ReDim nuevamente. Para borrar el contenido de una matriz dinámica bastará con usar la instrucción Redim sin variar las dimensiones, o bien establecer el valor de cada uno de los elementos a cero o cadena vacía a través del código.
En las versiones menos recientes de Visual Basic era posible variar el número de localizadores de un array con ReDim (sin Preserve), tanto para aumentarlos como para disminuirlos, usando código de este tipo (siempre sin Preserve):
‘Curso Visual Basic aprenderaprogramar.com
Option Explicit
Dim A() As Integer
Private Sub Form_Load()
ReDim A(2, 3, 4, 5)
A(1, 1, 1, 1) = 3
Label1 = "El valor de A(1,1,1,1) es " & A(1, 1, 1, 1)
ReDim A(2) '[Reducción del número de localizadores]
End Sub
Donde observamos que el array A, declarado inicialmente como de cuatro dimensiones, es
redimensionado para dejarlo como unidimensional. En las versiones más recientes de Visual Basic esto
no es posible, porque la declaración del array A tiene que establecer un número de dimensiones fijas
para el mismo. En este ejemplo tendríamos que declarar Dim A( , , , ) As Integer y de este modo el
número de dimensiones de A será siempre 4.Option Explicit
Dim A() As Integer
Private Sub Form_Load()
ReDim A(2, 3, 4, 5)
A(1, 1, 1, 1) = 3
Label1 = "El valor de A(1,1,1,1) es " & A(1, 1, 1, 1)
ReDim A(2) '[Reducción del número de localizadores]
End Sub
MANEJO DE DATOS CON ARRAYS. LÍMITES SUPERIOR (UBOUND) E INFERIOR (LBOUND).
Definido un array A(n) donde n define el índice más grande que puede tener un elemento del array, la función Ubound(A) nos devuelve el valor de n y el valor LBound(A) nos devuelve el valor del índice más bajo que puede tener el array (que normalmente será 0).
En las versiones menos recientes de Visual Basic se permitían declaraciones del tipo A(m To n), estando los índices del array comprendidos entre m y n. En este caso la función Lbound(A) nos devuelve el valor del índice menor del array (m) y la instrucción Ubound(A) devuelve el valor del índice mayor (n).
En general el índice menor por defecto en un array será 0. No obstante, en las versiones menos recientes de Visual Basic se permitía establecer como primer índice de un array por defecto el 1 mediante la instrucción Option Base escribiendo Option Base 1. Si se hace uso de esa opción los resultados de Lbound se pueden ver afectados por el valor establecido para Option Base, obteniéndose un 0 o un 1 según el caso. En las versiones más recientes de Visual Basic el índice inferior será siempre el cero.
El número de elementos del array será m – n + 1, donde m es el límite superior y n el límite inferior. Por ejemplo un array declarado como Dim A(3) As Integer consta de 3-0+1 = 4 elementos que son A(0), A(1), A(2) y A(3). Prueba el siguiente código:
En el caso de arrays multidimensionales se hace necesario indicar si queremos referirnos al primer localizador, al segundo, tercero, etc. Así Lbound(A, 3) nos devolvería el índice menor del array A en su dimensión 3. Por ejemplo:
Para: Dim A(55, 20, 33, 60)
LBound(A, 1) devuelve 0, UBound(A, 1) devuelve 55, LBound(A, 2) devuelve 0, UBound(A, 2) devuelve 20, LBound(A, 3) devuelve 0, UBound(A, 3) devuelve 33, LBound(A, 4) devuelve 0, UBound(A, 4) devuelve 60.