lunes, 23 de julio de 2018

Pasar argumentos por valor (ByVal) o por referencia (byRef) en VBA para Excel

Puede descargar el archivo ReferenciaValor.xlsm

En el lenguaje de programación VBA para Excel podemos elegir entre pasar parámetros o argumentos a las funciones o subprocedimientos por valor (ByVal) o por referencia (ByRef). Veremos las diferencias de ambos métodos y pondremos varios ejemplos.


  • ByVal. Cuando se pasa un parámetro por valor lo que se hace es enviar un número si la variable es numérica o un string si la variable es una cadena, o del tipo que sea. No son la misma variable la interna al subprocedimiento o función que la del procedimiento principal, por lo que al terminar el subprocedimiento y retornar el flujo del programa al procedimiento principal la variable no retorna con el valor con el que hubiera acabado el subprocedimiento. Al retornar al procedimiento principal la variable continúa valiendo lo mismo que valía cuando se envió como parámetro. Por ejemplo, si la variable i se envía como parámetro con el valor 5, y al terminar el subprocedimiento o función finaliza con el valor 10, al retornar al procedimiento principal la variable i continuará con el valor 5.
  • ByRef. Cuando se pasa un parámetro por referencia lo que hacemos es enviar una variable con un cierto valor, el que tenga en el momento del envío. Al retornar al procedimiento principal la variable viene con el valor con el que hubiera terminado en el subprocedimiento o función. Por ejemplo, si la variable i se envía como parámetro con el valor 5, y al terminar el subprocedimiento o función finaliza con el valor 10, al retornar al procedimiento principal la variable i vendrá con el valor 10.

Por defecto los parámetros en VBA se pasan ByRef



Caso 1

Disponemos de un procedimiento principal que contiene un bucle for que se repite cuatro veces. Dentro del bucle únicamente tenemos una línea de código que llama al subprocedimiento Saluda y le pasa el valor de i como parámetro.
El subprocedimiento Saluda recoge el parámetro i como valor (ByVal). Mediante un MsgBox mostrará en una pequeña ventana emergente un saludo diciendo Hola junto al valor de i, que será un número de 1 al 4. Luego, y dentro del subprocedimiento, se asigna a i el valor 100.

  • ByVal. Si pasamos el valor de i por valor, al lanzar el procedimiento principal la macro nos saludará cuatro veces y al final ejecutará la última línea del procedimiento principal y nos dirá que i es igual a 5.
  • ByRef. Si pasamos el valor de i por referencia, al lanzar el procedimiento principal únicamente nos saludará una vez diciendo Hola1, pero no seguirá con los siguientes ciclos del bucle for ya que la variable i vuelve con el valor 100. Como 100 es superior al límite dado en el bucle for que es cuatro, se considera que el bucle ha finalizado y por tanto se ejecutará la última línea del procedimiento principal que hace que se muestre en pantalla el valor 101.
 


Sub Principal_1()
For i = 1 To 4
  Call Saluda(i)
Next i
MsgBox (i)
End Sub
Sub Saluda(ByVal i)
  MsgBox ("Hola" & i)
  i = 100
End Sub



Caso 2

En el procedimiento principal asignamos a las variables x, y, z el valor 5. Imprimiremos su valor empleando para la variable x la columna A, para la variable y la columna B y para la variable z la columna C. Llamamos al subprocedimiento Dobla y le pasamos como parámetros los valores x, y, z.
El subprocedimiento Dobla recibe el parámetro x por referencia (ByRef), el parámetro y por valor (ByVal) y el parámetro z lo recibe sin indicar nada para ver cómo trata VBA por defecto los parámetros que se pasan a un subprocedimiento o función. Se dobla el valor de x, y, z. Se imprime nuevamente el valor de x, y, z en la fila dos de las columnas A, B, C respectivamente. Veremos que los valores impresos son 10, 10, 10, esto es así ya que estamos imprimiendo el valor de la variable interna del subprocedimiento y en los tres casos esta variable es el doble de cinco.
Finalizado el subprocedimiento se vuelve al procedimiento principal y en la fila tres se imprimen nuevamente los valores de x, y, z. Pero ahora vemos que lo que se imprime es 10, 5, 10. Veamos el motivo de esta diferencia.


  • ByRef. El parámetro x se pasó por referencia. Continúa siendo la misma variable la que se definió inicialmente en el procedimiento principal, la que luego se usa en el subprocedimiento y la que finalmente retorna al procedimiento principal. Al retornar, vuelve con el valor 10.
  • ByVal. El parámetro y se pasó por valor y por tanto lo que se hace es pasarlo como si fuera un número sin que quede vinculación con la variable inicialmente definida en el procedimiento principal. En el subprocedimiento se dobla el valor y para de 5 a 10, pero al retornar al procedimiento principal no se retorna ningún valor, y la variable y en el procedimiento principal sigue valiendo 5 que es tal y como se definió en un principio.
  • Por defecto. El parámetro z se pasó sin indicar nada y al final se ha comportado como la variable x. Esto indica que si no ponemos nada, por defecto VBA pasa los parámetros por referencia (ByRef).


Sub Principal_2()
x = 5: y = 5: z = 5
Cells(1, "A") = x: Cells(1, "B") = y: Cells(1, "C") = z
Call Dobla(x, y, z)
Cells(3, "A") = x: Cells(3, "B") = y: Cells(3, "C") = z
End Sub
Sub Dobla(ByRef x, ByVal y, z)
x = 2 * x: y = 2 * y: z = 2 * z
Cells(2, "A") = x: Cells(2, "B") = y:: Cells(2, "C") = z
End Sub



Caso 3

Queremos listar los números del 10 al 20. Disponemos de un procedimiento principal que llama a un subprocedimiento que se denomina Listado, al que se pasan dos parámetros a y b. El parámetro b se puede pasar por valor o por referencia. Si se pasa por valor la lista impresa se genera en vertical y si se pasa por referencia la lista impresa se muestra en diagonal.

En el procedimiento principal inicializamos la variable
b haciendo que su valor se igual a uno. Nos metemos en un bucle for donde la variable a varía entre 10 y 20. Dentro del bucle se llama al subprocedimiento Listado pasándole los parámetros a y b.

El subprocedimiento Listado escribe el valor de a en la fila a y columna b. Finalmente tiene una línea de código que incrementa el valor de b en una unidad.

  • ByRef. Si pasamos el parámetro b por referencia el incremento de la variable b en una unidad que se hace en la última línea de código se recordará en el procedimiento principal y por tanto la variable b en el primer ciclo del bucle for valdrá 1, pero en el segundo ciclo ya valdrá 2, en el tercero 3, y así sucesivamente. Esto hace que al ejecutar reiteradamente el subprocedimiento y escribir con cells cada vez se haga en una fila más a la derecha, lo que provoca que el resultado se vea en diagonal.
  • ByVal. Si pasamos el parámetro b por valor la última línea de código que hace que b se incremente en una unidad no se recordará al llegar la procedimiento principal y por tanto b continuará valiendo 1 que es el valor con el que se inicializó. Esto provoca que al ser b siempre igual a uno, no nos movamos de la columna uno, y por tanto la serie de números se imprima en vertical todos ellos en la columna uno.





Sub Principal_3()
b = 1
For a = 10 To 20
  Call Listado(a, b)
Next a
End Sub
Sub Listado(a, ByRef b)
Cells(a, b) = a
b = b + 1
End Sub



Caso 4

Vamos a calcular el montante final al que se llega aplicando la ley de capitalización compuesta con los siguientes datos.
Capital inicial C = 1.000 €
tipo de interés i = 0,15
tiempo años t = 3

Disponemos de un procedimiento principal que llama dos veces a la función Montante. En la función existe una última línea de código que hace que el valor del tipo de interés i se reduzca en un 5%.

  • ByRef. Si pasamos el parámetro i por referencia la primera vez el tipo de interés valdrá 15% y la segunda vez valdrá 10% ya que se ha reducido el valor del tipo de interés y el procedimiento principal toma ese valor ya que no olvida el último valor con el que quedó la variable. Esto provoca que los dos montantes sean diferentes, el primero calculado al 15% y el segundo al 10%.
  • ByVal. Si pasamos el parámetro i por valor la última línea del subprocedimiento no se recordará al llegar al procedimiento principal y por tanto el valor del tipo de interés no se reducirá y continuará siendo del 15%. Esto provoca que los dos montantes sean iguales, ambos calculados al 10%.




Sub Principal_4()
i = 0.15
t = 3
C = 1000
Cells(1, "H") = Montante(i, t, C)
Cells(2, "H") = Montante(i, t, C)
End Sub
Function Montante(ByVal i, t, C)
Montante = C * (1 + i) ^ t
i = i - 0.05
End Function

2 comentarios:

  1. Estimado señor, muchas gracias por su esfuerzo en ayudarnos, es muy magistral su forma de explicar, me resolvió una gran duda. Saludos desde Chile.

    ResponderEliminar
  2. Muchas gracias por las explicaciones, pero me surge la duda con respecto a las variables de objeto. Si pasamos una variable que referencia a una instancia de un objeto dado, ¿podemos pasarla también por valor o por referencia, según esté definida la función? ¿o, en cambio, no importa cómo esté definida la función, al ser un objeto, siempre se pasa por referencia?
    Gracias.

    ResponderEliminar