índice del curso de VB .NET

Curso de iniciación a la programación
con Visual Basic .NET

Entrega número nueve, (25/Dic/2002)
Publicada el 25/Dic/2002 (revisada el 09/May/03)


En esta novena entrega del curso de iniciación a la programación con Visual Basic .NET vamos a ver un tipo especial de datos, al menos eso será lo que pienses cuando te diga lo que es pero, (si has trabajado con las versiones anteriores de Visual Basic), a lo largo de esta entrega descubrirás que no todo lo que creías sigue siendo igual que antes... no, no te preocupes, no hablaré de Grandes Temas (en mayúsculas), ni de lo que puede que estés pensando al ver la fecha en la que escribo esta entrega... aquí sólo se tratan temas relacionados con la programación, así que descansa, relájate y no te preocupes, que esta noche podrás ir a la Misa del Gallo... (¡Guille! te van a tachar de ateo y blasfemo, así que cuidadín con lo que dices...)

Que el personal esté tranquilo, que me refería al tema de los Arrays.

Si no sabes nada de Arrays, (o matrices o arreglos, como prefieras llamarlo, en la documentación en español de Visual Studio .NET se llaman matrices, pero yo voy a usar el nombre en inglés, ya que es el que estoy acostumbrado a usar.), lo que voy a comentar en este párrafo te será indiferente, pero aún así, podrías leerlo para que tengas más oportunidades de saber de qué estoy hablando...
Los arrays en las versiones anteriores de Visual Basic eran tipos de datos de los llamados por valor, al igual que lo son los tipos Integer, Double, etcétera. Pero en Visual Basic .NET, los arrays realmente son tipos por referencia.

Antes de entrar en detalles sobre los arrays, creo que, ya que lo he mencionado, deberíamos ver en qué se diferencian los tipos de datos por valor y los tipos de datos por referencia.

Tipos de datos por valor

Cuando dimensionamos una variable, por ejemplo, de tipo Integer, el CLR reserva el espacio de memoria que necesita para almacenar un valor del tipo indicado; cuando a esa variable le asignamos un valor, el CLR almacena dicho valor en la posición de memoria que reservó para esa variable. Si posteriormente asignamos un nuevo valor a dicha variable, ese valor se almacena también en la memoria, sustituyendo el anterior, eso lo tenemos claro, ya que así es como funcionan las variables. Si a continuación creamos una segunda variable y le asignamos el valor que contenía la primera, tendremos dos posiciones de memoria con dos valores, que por pura casualidad, resulta que son iguales, entre otras cosas porque a la segunda hemos asignado un valor "igual" al que tenía la primera.
Vale, seguramente pensarás que eso es así y así es como te lo he explicado o al menos, así es como lo habías entendido.
Pero, espera a leer lo siguiente y después seguimos hablando... a ver si te parece que lo tenías "aprendido".

Tipos de datos por referencia

En Visual Basic .NET también podemos crear objetos, los objetos se crean a partir de una clase, (del tema de las clases hablaremos largo y tendido en entregas posteriores). Cuando dimensionamos una variable cuyo tipo es una clase, simplemente estamos creando una variable que es capaz de manipular un objeto de ese tipo, en el momento de la declaración, el CLR no reserva espacio para el objeto que contendrá esa variable, ya que esto sólo lo hace cuando usamos la instrucción New. En el momento en que creamos el objeto, (mediante New), es cuando el CLR reserva la memoria para dicho objeto y le dice a la variable en que parte de la memoria está almacenado, de forma que la variable pueda acceder al objeto que se ha creado. Si posteriormente declaramos otra variable del mismo tipo que la primera, tendremos dos variables que saben "manejar" datos de ese tipo, pero si a la segunda variable le asignamos el contenido de la primera, en la memoria no existirán dos copias de ese objeto, sólo existirá un objeto que estará referenciado por dos variables. Por tanto, cualquier cambio que se haga en dicho objeto se reflejará en ambas variables.

¿Qué? ¿Te aclaras? A que no... pues no me extraña... porque esto no es para aclararse... pero, confía en mí y aunque no te enteres, sigue leyendo, no abandones ahora... (lo que no te dice el Guille es que todavía quedan cosas más complicadas y difíciles de digerir... je, je, ¡no te quea ná!)

Vamos a ver un par de ejemplos para aclarar esto de los tipos por valor y los tipos por referencia.

Crea un nuevo proyecto de tipo consola y añade el siguiente código:

Sub Main()
    ' creamos una variable de tipo Integer
    Dim i As Integer
    ' le asignamos un valor
    i = 15
    ' mostramos el valor de i
    Console.WriteLine("i vale {0}", i)
    '
    ' creamos otra variable
    Dim j As Integer
    ' le asignamos el valor de i
    j = i
    Console.WriteLine("hacemos esta asignación: j = i")
    '
    ' mostramos cuanto contienen las variables
    Console.WriteLine("i vale {0} y j vale {1}", i, j)
    '
    ' cambiamos el valor de i
    i = 25
    Console.WriteLine("hacemos esta asignación: i = 25")
    ' mostramos nuevamente los valores
    Console.WriteLine("i vale {0} y j vale {1}", i, j)
    '
    Console.WriteLine("Pulsa Intro para finalizar")
    Console.ReadLine()
End Sub

Como puedes comprobar, cada variable tiene un valor independiente del otro.
Esto está claro.

Ahora vamos a ver qué es lo que pasa con los tipos por referencia.
Para el siguiente ejemplo, vamos a crear una clase con una sola propiedad, ya que las clases a diferencia de los tipos por valor, deben tener propiedades a las que asignarles algún valor.
No te preocupes si no te enteras, que de todo esto hablaremos en otra ocasión, pero por ahora sólo es para ver, prácticamente, esto de los tipos por referencia.

Class prueba
    Public Nombre As String
End Class

Sub Main()
    ' creamos una variable de tipo prueba
    Dim a As prueba
    ' creamos (instanciamos) el objeto en memoria
    a = New prueba()
    ' le asignamos un valor
    a.Nombre = "hola"
    ' mostramos el contenido de a
    Console.WriteLine("a vale {0}", a.Nombre)
    '
    ' dimensionamos otra variable
    Dim b As prueba
    '
    ' asignamos a la nueva el valor de a
    b = a
    Console.WriteLine("hacemos esta asignación: b = a")
    '
    ' mostramos el contenido de las dos
    Console.WriteLine("a vale {0} y b vale {1}", a.Nombre, b.Nombre)
    ' cambiamos el valor de la anterior
    a.Nombre = "adios"
    '
    Console.WriteLine("hacemos una nueva asignación a a.Nombre")
    '
    ' mostramos nuevamente los valores
    Console.WriteLine("a vale {0} y b vale {1}", a.Nombre, b.Nombre)
    '
    Console.WriteLine("Pulsa Intro para finalizar")
    Console.ReadLine()
End Sub

La clase prueba es una clase muy simple, pero como para tratar de los tipos por referencia necesitamos una clase, he preferido usar una creada por nosotros que cualquiera de las clases que el .NET Framework nos ofrece.
Dimensionamos una variable de ese tipo y después creamos un nuevo objeto del tipo prueba, el cual asignamos a la variable a.
Una vez que tenemos "instanciado" (o creado) el objeto al que hace referencia la variable a, le asignamos a la propiedad Nombre de dicho objeto un valor.
Lo siguiente que hacemos es declarar otra variable del tipo prueba y le asignamos lo que contiene la primera variable.

Hasta aquí, es casi lo mismo que hicimos anteriormente con las variables de tipo Integer. La única diferencia es la forma de manipular las clases, ya que no podemos usarlas "directamente", porque tenemos que crearlas (mediante New) y asignar el valor a una de las propiedades que dicha clase contenga. Esta es la primera diferencia entre los tipos por valor y los tipos por referencia, pero no es lo que queríamos comprobar, así que sigamos con la explicación del código mostrado.

Cuando mostramos el contenido de la propiedad Nombre de ambas variables, las dos muestran lo mismo, que es lo esperado, pero cuando asignamos un nuevo valor a la variable a, al volver a mostrar los valores de las dos variables, ¡las dos siguen mostrando lo mismo!
Y esto no es lo que ocurría en el primer ejemplo.
¿Por qué ocurre? Porque las dos variables, apuntan al mismo objeto que está creado en la memoria.
¡Las dos variables hacen referencia al mismo objeto! y cuando se realiza un cambio mediante una variable, ese cambio afecta también a la otra, por la sencilla razón que se está modificando el único objeto de ese tipo que hay creado en la memoria.

Para simplificar, los tipos por valor son los tipos de datos que para usarlos no tenemos que usar New, mientras que los tipos por referencia son tipos (clases) que para crear un nuevo objeto hay que usar la instrucción New.
Los tipos por valor son los tipos "básicos" (o elementales), tales como Integer, Double, Decimal, Boolean, etcétera. En la primera tabla de la entrega cuatro tienes una relación de los tipos por valor.
Los tipos por referencia son todos los demás tipos.

Sin querer parecer que quiero confundirte más, te diré que en .NET Framework, realmente todos los tipos son clases, aunque esos que están relacionados en la tabla mencionada, son los que actúan como tipos por valor y el .NET Framework los trata de una forma especial... ¡vale! lo dejo, no voy a "calentarte" más la cabeza, que seguramente ya la tendrás en ebullición...

 

Ahora sí, ahora vamos a seguir complicándonos la existencia.

Los Arrays

Las variables que hemos estado usando hasta ahora, eran de tipo escalar: sólo pueden contener un valor a la vez, (no te asustes por la palabra esa que ha usado el Guille: escalar, es que lo ha leído en la ayuda del VS y se las quiere dar de entendido). Pero resulta que en ocasiones nos podemos ver en la necesidad de querer tener en una misma variable, valores que de alguna forma están relacionados. Por ejemplo, si tenemos una colección de discos, nos podría interesar tener esa discografía incluida en una misma variable para poder acceder a cualquiera de esos discos sin necesidad de tener que crear una variable distinta para cada uno de los discos, ya que sería totalmente ineficiente si, por ejemplo, quisiéramos imprimir una relación de los mismos.

Realmente para el ejemplo este que estoy poniendo, hay otros tipos de datos que serían más prácticos, pero... es eso, sólo un ejemplo, y cuando veamos esos otros tipos de datos, serás tú el que decida cual utilizar.

Una de las formas en las que podemos agrupar varios datos es mediante los arrays (o matrices).
Usando un array, podemos acceder a cualquiera de los valores que tenemos almacenado mediante un índice numérico. Por ejemplo, si tenemos la variable discografía y queremos acceder al tercer disco, podríamos hacerlo de la siguiente forma: discografía(3).
Sabiendo esto, podemos comprobar que sería fácil recorrer el contenido de los arrays mediante un bucle For.


¿Qué tipos de datos se pueden usar para crear arrays?

Los tipos de datos de las variables usadas como array, pueden ser de cualquier tipo, dependiendo de lo que queramos guardar. Por ejemplo, si queremos guardar los nombres de los discos que tenemos, podemos usar un array del tipo String, que lo que nos interesa es saber el porcentaje de goles por partido de nuestro equipo de fútbol favorito, el tipo de datos del array podía ser Decimal. Incluso si queremos, también podemos crear un array de un tipo que nosotros hayamos definido o de cualquier clase que exista en el .NET Framework.

Vale, muy bien, pero ¿cómo narices le digo al Visual Basic que una variable es un array?


Declarar variables como arrays

Para poder indicarle al VB que nuestra intención es crear un array podemos hacerlo de dos formas distintas, para este ejemplo crearemos un array de tipo Integer:

1- La clásica (la usada en versiones anteriores)
Dim a() As Integer

2- La nueva forma introducida en .NET:
Dim a As Integer()

De cualquiera de estas dos formas estaríamos creando un array de tipo Integer llamada a.
Cuando declaramos una variable de esta forma, sólo le estamos indicando al VB que nuestra intención es que la variable a sea un array de tipo Integer, pero ese array no tiene reservado ningún espacio de memoria.


Reservar memoria para un array

Para poder hacerlo tenemos que usar la instrucción ReDim:
ReDim a(5)
Al ejecutarse este código, tendremos un array con capacidad para 6 elementos.
Y son seis y no cinco, (como por lógica habría que esperar), porque en .NET Framework el índice menor de un array siempre es cero y en Visual Basic, el índice superior es el indicado entre paréntesis. Por tanto el array a tendrá reservada memoria para 6 valores de tipo Integer, los índices serían desde 0 hasta 5 ambos inclusive.

Además de usar ReDim, que realmente sirve para "redimensionar" el contenido de un array, es decir, para volver a dimensionarlo o cambiarlo por un nuevo valor. Si sabemos con antelación el tamaño que contendrá el array, podemos hacerlo de esta forma:
Dim a(5) As Integer
Con este código estaríamos declarando la variable a como un array de 6 elementos (de 0 a 5) del tipo Integer.
Cuando indicamos la cantidad de elementos que contendrá el array no podemos usar la segunda forma de declaración que te mostré anteriormente: Dim a As Integer(5) ya que esto produciría un error sintáctico.

 

Nota:
Aunque este curso es de Visual Basic .NET te diré que en C#, cuando declaramos un array con el equivalente a Dim a(5) As Integer, que sería algo como:
int[] a = new int[5], lo que estamos creando es un array de tipo int (el Integer de C#) con 5 elementos, de 0 a 4.

El que en Visual Basic tenga este tratamiento diferente en la declaración de los arrays, según nos dicen, es por compatibilidad con las versiones anteriores... ¿compatibilidad? ¡JA!
La verdad es que muchas veces parece como si quisieran seguir marginando a los programadores de Visual Basic. Ahora que con VB.NET tenemos un lenguaje "decente", nos lo enfangan con una pretendida compatibilidad "hacia atrás"... es que estos diseñadores de lenguajes no saben eso de: "para atrás, ni para tomar carrerilla" (el Guille hubiese dicho: patrás, ni pa coger carrerilla).
Pero esto es lo que hay, así que... tendremos que adaptarnos.

 

 


Asignar valores a un array

Para asignar un valor a un elemento de un array, se hace de la misma forma que con las variables normales, pero indicando el índice (o posición) en el que guardará el valor.
Por ejemplo, para almacenar el valor 15 en la posición 3 del array a, haríamos lo siguiente:
a(3) = 15


Acceder a un elemento de un array

De igual forma, si queremos utilizar ese elemento que está en la posición 3 para una operación, podemos hacerlo como con el resto de las variables, pero siempre usando el paréntesis y el número de elemento al que queremos acceder:
i = b * a(3)

El índice para poder acceder al elemento del array puede ser cualquier expresión numérica, una variable o como hemos estado viendo en estos ejemplos, una constante. La única condición es que el resultado sea un número entero.
Por tanto, podríamos acceder a la posición indicada entre paréntesis, siempre que el resultado sea un valor entero y, por supuesto, esté dentro del rango que hayamos dado al declarar el array:
x = a(i + k)

 

Nota aclaratoria:
Cuando digo el elemento que está en la posición 3, no me refiero al tercer elemento del array, ya que si un array empieza por el elemento 0, el tercer elemento será el que esté en la posición 2, ya que el primer elemento será el que ocupe la posición cero.

 


Los límites de los índices de un array

Como ya he comentado antes, el índice inferior de un array, siempre es cero, esto es invariable, todos los arrays de .NET Framework empiezan a contar por cero.
Pero el índice superior puede ser el que nosotros queramos, aunque sin pasarnos, que la memoria disponible se puede agotar si pretendemos usar un valor exageradamente alto. Realmente el índice superior de un array es 2^64 -1 (el valor máximo de un tipo Long)


Saber el tamaño de un array

Cuando tenemos un array declarado y asignado, podemos acceder a los elementos de ese array mediante un índice, esto ya lo hemos visto; pero si no queremos "pasarnos" cuando queramos acceder a esos elementos, nos será de utilidad saber cuantos elementos tiene el array, para ello podemos usar la propiedad Length, la cual devuelve el número total de elementos, por tanto, esos elementos estarán comprendidos entre 0 y Length - 1.
Esto es útil si queremos acceder mediante un bucle For, en el siguiente código se mostrarían todos los elementos del array a:

For i = 0 To a.Length - 1
    Console.WriteLine(a(i))
Next


Inicializar un array al declararla

Al igual que las variables normales se pueden declarar y al mismo tiempo asignarle un valor inicial, con los arrays también podemos hacerlo, pero de una forma diferente, ya que no es lo mismo asignar un valor que varios.
Aunque hay que tener presente que si inicializamos un array al declararla, no podemos indicar el número de elementos que tendrá, ya que el número de elementos estará supeditado a los valores asignados.
Para inicializar un array debemos declarar ese array sin indicar el número de elementos que contendrá, seguida de un signo igual y a continuación los valores encerrados en llaves. Veamos un ejemplo:

Dim a() As Integer = {1, 42, 15, 90, 2}

También podemos hacerlo de esta otra forma:

Dim a As Integer() = {1, 42, 15, 90, 2}

Usando cualquiera de estas dos formas mostradas, el número de elementos será 5, por tanto los índices irán desde 0 hasta 4.


Los arrays pueden ser de cualquier tipo

En todos estos ejemplos estamos usando valores de tipo Integer, pero podríamos hacer lo mismo si fuesen de tipo String o cualquier otro.
En el caso de que sean datos de tipo String, los valores a asignar deberán estar entre comillas dobles o ser variables de tipo String. Por ejemplo:

Dim s As String() = {"Hola", "Mundo, ", "te", "saludo"}

s(3) = "saludamos"

Dim i As Integer
For i = 0 To s.Length - 1
    Console.WriteLine(s(i))
Next


Usar un bucle For Each para recorrer los elementos de un array

El tipo de bucle For Each es muy útil para recorrer los elementos de un array, además de ser una de las pocas, por no decir la única, formas de poder acceder a un elemento de un array sin indicar el índice.

Dim a() As Integer = {1, 42, 15, 90, 2}
'
Console.WriteLine("Elementos del array a()= {0}", a.Length)
'
Dim i As Integer
For Each i In a
    Console.WriteLine(i)
Next
'
Console.WriteLine("Pulsa Intro para finalizar")
Console.ReadLine()


Clasificar el contenido de un array

Todos los arrays están basados realmente en una clase del .NET Framework, (recuerda que TODO en .NET Framework son clases, aunque algunas con un tratamiento especial)
La clase en las que se basan los arrays, es precisamente una llamada Array.
Y esta clase tiene una serie de métodos y propiedades, entre los cuales está el método Sort, el cual sirve para clasificar el contenido de un array.
Para clasificar el array a, podemos hacerlo de dos formas:
a.Sort(a)
La variable a es un array, por tanto es del tipo Array y como tal, tiene el método Sort, el cual se usa pasando como parámetro el array que queremos clasificar.
Pero esto puede parecer una redundancia, así que es preferible, por claridad, usar el segundo método:
Array.Sort(a)
En el que usamos el método Sort de la clase Array. Este método es lo que se llama un método "compartido" y por tanto se puede usar directamente... no te doy más explicaciones, ya que esto de los métodos compartidos y de instancia lo veremos cuando tratemos el tema de las clases.

Veamos ahora un ejemplo completo en el que se crea y asigna un array al declararla, se muestra el contenido del array usando un bucle For Each, se clasifica y se vuelve a mostrar usando un bucle For normal.

Sub Main()
    Dim a() As Integer = {1, 42, 15, 90, 2}
    '
    Console.WriteLine("Elementos del array a(): {0}", a.Length)
    '
    Dim i As Integer
    For Each i In a
        Console.WriteLine(i)
    Next
    '
    Console.WriteLine()
    '
    Array.Sort(a)
    '
    For i = 0 To a.Length - 1
        Console.WriteLine(a(i))
    Next
    '
    Console.WriteLine("Pulsa Intro para finalizar")
    Console.ReadLine()
End Sub


Si quieres saber más cosas de los arrays, te recomiendo que le eches un vistazo a la documentación de Visual Studio .NET, pero no te preocupes, que no es que quiera "quitarte" de en medio, simplemente es para que te acostumbres a usar la documentación, ya que allí encontrarás más cosas y explicadas de una forma algo más extensa y sobre todo formal... que aquí parece que nos lo tomamos a "cachondeo"... je, je.


Para terminar con esta entrega, vamos a ver lo que te comentaba al principio: que los arrays son tipos por referencia en lugar de tipos por valor.

El contenido de los arrays son tipos por referencia

Cuando tenemos el siguiente código:

Dim m As Integer = 7
Dim n As Integer
'
n = m
'
m = 9
'
Console.WriteLine("m = {0}, n = {1}", m, n)

El contenido de n será 7 y el de m será 9, es decir cada variable contiene y mantiene de forma independiente el valor que se le ha asignado y a pesar de haber hecho la asignación n = m, y posteriormente haber cambiado el valore de m, la variable n no cambia de valor.

Sin embargo, si hacemos algo parecido con dos arrays, veremos que la cosa no es igual:

Dim a() As Integer = {1, 42, 15, 90, 2}
Dim b() As Integer
Dim i As Integer
'
b = a
'
a(3) = 55
'
For i = 0 To a.Length - 1
    Console.WriteLine("a(i) = {0}, b(i) = {1}", a(i), b(i))
Next

En este caso, al cambiar el contenido del índice 3 del array a, también cambiamos el contenido del mismo índice del array b, esto es así porque sólo existe una copia en la memoria del array creado y cuando asignamos al array b el contenido de a, realmente le estamos asignando la dirección de memoria en la que se encuentran los valores, no estamos haciendo una nueva copia de esos valores, por tanto, al modificar el elemento 3 del array a, estamos modificando lo que tenemos "guardado" en la memoria y como resulta que el array b está apuntando (o hace referencia) a los mismos valores... pues pasa lo que pasa... que tanto a(3) como b(3) devuelven el mismo valor.


Para poder tener arrays con valores independientes, tendríamos que realizar una copia de todos los elementos del array a en el array b.

Copiar los elementos de un array en otro array

La única forma de tener copias independientes de dos arrays que contengan los mismos elementos es haciendo una copia de un array a otro.
Esto lo podemos hacer mediante el método CopyTo, al cual habrá que indicarle el array de destino y el índice de inicio a partir del cual se hará la copia. Sólo aclarar que el destino debe tener espacio suficiente para recibir los elementos indicados, por tanto deberá estar inicializado con los índices necesarios.
Aclaremos todo esto con un ejemplo:

Dim a() As Integer = {1, 42, 15, 90, 2}
Dim b(a.Length - 1) As Integer
'
a.CopyTo(b, 0)
'
a(3) = 55
'
Dim i As Integer
For i = 0 To a.Length - 1
    Console.WriteLine("a(i) = {0}, b(i)= {1}", a(i), b(i))
Next

En este ejemplo, inicializamos un array, declaramos otro con el mismo número de elementos, utilizamos el método CopyTo del array con los valores, en el parámetro le decimos qué array será el que recibirá una copia de esos datos y la posición (o índice) a partir de la que se copiarán los datos, (indicando cero se copiarán todos los elementos); después cambiamos el contenido de uno de los elementos del array original y al mostrar el contenido de ambos arrays, comprobamos que cada uno es independiente del otro.

 

Con esto, no está todo dicho sobre los arrays, aún quedan más cosas, pero habrá que dejarlo para otra entrega, que esta ya ha dado bastante de sí.

Así que, disfruta, si puedes o te apetece, de estos días de fiesta y prepara el cuerpo para lo que seguirá, casi con toda seguridad el próximo año. Si no quieres esperar tanto, ya sabes, abres el Visual Studio .NET, y busca en la ayuda arrays o matrices (esto último si la ayuda está en español) y verás que es lo que te encontrarás en la próxima entrega... bueno, todo no, que es mucho, sólo una parte... lo que yo considere más necesario, que del resto ya te encargarás tú de investigarlo por tu cuenta (eso espero).

 

Lo dicho, que disfrutes y a pasarlo bien: ¡Feliz Navidad! (y si lees esto después del día 25 de Diciembre, pues... ¡Feliz loquesea!)


Nos vemos.
Guillermo
Nerja, 24-25 de diciembre de 2002


ir a la entrega anterior ir al índice del curso vb.NET
 
ir al Glosario .NET
ir a la siguiente entrega (o al índice si esta es la última)

la Luna del Guille o... el Guille que está en la Luna... tanto monta...