24 - Punteros - Parámetros de tipo array

Cuando pasamos un array a una función lo que sucede es que se hace una copia de la variable que le pasamos dentro de la función. Si modificamos el array que le pasamos no se está modificando la variable definida en la main sino la copia. Este ejemplo nos deja en claro que el vector (array) es una copia:

Programa: ejercicio115.go

package main

import "fmt"

func modificar(vec [5]int) {
    for f := 0; f < len(vec); f++ {
        vec[f] = 10
    }
    fmt.Println("Contenido del parámetro de la funcón")    
    fmt.Println(vec) // 10 10 10 10 10
}

func main() {
    vec := [5]int{5,5,5,5,5}
    fmt.Println("Contenido del vector definido en la main en forma inicial") // 5 5 5 5 5 
    fmt.Println(vec)
    modificar(vec)
    fmt.Println("Contenido del vector definido en la main luego de llamar a la función")  // 5 5 5 5 5  
    fmt.Println(vec)
}

Como vemos el vector (vec) definido en la función main antes y después de llamar a la función modificar sigue almacenando los mismos valores, por más que en la función modificar cambiamos los valores del parámetro. Esto es debido a que el parámetro vec de la función modificar tiene una copia de la variable vec definida en la función main.

Si queremos modificar efectivamente el array dentro de una función lo que debemos hacer es enviar la dirección del vector y que lo reciba un puntero:

Programa: ejercicio116.go

package main

import "fmt"

func modificar(vec *[5]int) {
    for f := 0; f < len(vec); f++ {
        vec[f] = 10
    }
    fmt.Println("Contenido del parámetro de la funcón")    
    fmt.Println(vec) // 10 10 10 10 10
}

func main() {
    vec := [5]int{5,5,5,5,5}
    fmt.Println("Contenido del vector definido en la main en forma inicial") // 5 5 5 5 5 
    fmt.Println(vec)
    modificar(&vec)
    fmt.Println("Contenido del vector definido en la main luego de llamar a la función")  // 10 10 10 10 10
    fmt.Println(vec)
}

Lo primero que podemos ver que la función modificar recibe un puntero a un array:

func modificar(vec *[5]int) {

Con esto lo que sucede es que llega la dirección de memoria donde se almacena el array vec definido en la función main. Cuando modificamos el vector en la función estamos modificando realmente el vector definido en la main.

Es importante notar que en la función main no enviamos el vector a la función modificar sino le enviamos la dirección del array vec:

    modificar(&vec)

Problema 1:

Confeccionar un programa que contenga tres funciones:
1 - Cargar por teclado un vector de 5 elementos de tipo entero.
2 - Ordenar el vector de menor a mayor.
3 - Imprimir el vector. .

Programa: ejercicio117.go

package main

import "fmt"

func cargar(vec *[5]int) {
    for f := 0; f < len(vec); f++ {
        fmt.Print("Ingrese elemento:")
        fmt.Scan(&vec[f])
    }
}

func ordenar(vec *[5]int) {
    for k := 0; k < len(vec)-1; k++ {
        for f := 0; f < len(vec) - 1 - k; f++ {
            if vec[f] > vec [f + 1] {
                aux := vec[f]
                vec[f] = vec[f + 1]
                vec[f + 1] = aux
            }
        }
    }
}

func imprimir(vec [5]int) {
    fmt.Println("Vector ordenado")
    fmt.Println(vec)    
}


func main() {
    var vec [5]int
    cargar(&vec)
    ordenar(&vec)
    imprimir(vec)
}

Dos de estas funciones modificarán el contenido del vector, por eso desde la main enviamos tanto a la función de cargar como ordenar la dirección del vector:

func main() {
    var vec [5]int
    cargar(&vec)
    ordenar(&vec)

Como la función imprimir solo accede a los elementos para mostrarlos le enviamos directamente el vector:

    imprimir(vec)

El algoritmo de "cargar" ingresamos por teclado los 5 elementos del vector (es importante notar que la función recibe un puntero):

func cargar(vec *[5]int) {
    for f := 0; f < len(vec); f++ {
        fmt.Print("Ingrese elemento:")
        fmt.Scan(&vec[f])
    }
}

La función "ordenar" aplica el método de la burbuja para intercambiar elementos hasta que quede ordenado de menor a mayor:

func ordenar(vec *[5]int) {
    for k := 0; k < len(vec)-1; k++ {
        for f := 0; f < len(vec) - 1 - k; f++ {
            if vec[f] > vec [f + 1] {
                aux := vec[f]
                vec[f] = vec[f + 1]
                vec[f + 1] = aux
            }
        }
    }
}

El algoritmo consiste en comparar si la primera componente es mayor a la segunda, en caso que la condición sea verdadera, intercambiamos los contenidos de las componentes.

Vamos a suponer que se ingresan los siguientes valores por teclado:

1200
750
820
550
490

En este ejemplo: ¿es 1200 mayor a 750? La respuesta es verdadera, por lo tanto intercambiamos el contenido de la componente 0 con el de la componente 1.
Luego comparamos el contenido de la componente 1 con el de la componente 2: ¿Es 1200 mayor a 820?
La respuesta es verdadera entonces intercambiamos.
Si hay 5 componentes hay que hacer 4 comparaciones, por eso el for interno se repite 4 veces.
Generalizando: si el vector tiene N componentes hay que hacer N-1 comparaciones.

Cuando		f = 0		f = 1		f  = 2		f = 3
		
		750		750		750		750
		1200		820		820		820
		820		1200		550		550
		550		550		1200		490
		490		490		490		1200

Podemos ver cómo el valor más grande del vector desciende a la última componente. Empleamos una variable auxiliar (aux) para el proceso de intercambio:

                aux := vec[f]
                vec[f] = vec[f + 1]
                vec[f + 1] = aux

Al salir del for interno en este ejemplo el contenido del vector es el siguiente:

750
820
550
490
1200

Analizando el algoritmo podemos comprobar que el elemento mayor del vector se ubica ahora en el último lugar.
Podemos definir otros vectores con distintos valores y comprobar que siempre el elemento mayor queda al final.

Pero todavía con este algoritmo no se ordena un vector. Solamente está ordenado el último elemento del vector.

Ahora bien, con los 4 elementos que nos quedan podemos hacer el mismo proceso visto anteriormente, con lo cual quedará ordenado otro elemento del vector. Este proceso lo repetiremos hasta que quede ordenado por completo el vector.

Como debemos repetir el mismo algoritmo disponemos el for externo regido por el contador k.

Realicemos una prueba del siguiente algoritmo:

Cuando k = 0
		f = 0		f = 1		f = 2		f = 3
		750		750		750		750
		1200		820		820		820
		820		1200		550		550
		550		550		1200		490
		490		490		490		1200
		
Cuando k = 1
		f = 0		f = 1		f  = 2		f = 3
		750		750		750		750	
		820		550		550		550
		550		820		490		490
		490		490		820		820
		1200		1200		1200		1200

Cuando k = 2
		f = 0		f = 1		f  = 2		f = 3
		550		550		550		550
		750		490		490		490
		490		750		750		750
		820		820		820		820
		1200		1200		1200		1200


Cuando k = 3
		f = 0		f = 1		f  = 2		f = 3
		490		490		490		490
		550		550		550		550
		750		750		750		750
		820		820		820		820
		1200		1200		1200		1200

¿Porque repetimos 4 veces el for externo?
Como sabemos cada vez que se repite en forma completa el for interno queda ordenada una componente del vector. A primera vista diríamos que deberíamos repetir el for externo la cantidad de componentes del vector, en este ejemplo el vector tiene 5 componentes.

Si observamos, cuando quedan dos elementos por ordenar, al ordenar uno de ellos queda el otro automáticamente ordenado (podemos imaginar que si tenemos un vector con 2 elementos no se requiere el for externo, porque este debería repetirse una única vez)

La función imprimir recibe el vector directamente, es decir una copia:

func imprimir(vec [5]int) {
    fmt.Println("Vector ordenado")
    fmt.Println(vec)    
}

Desde la main le enviamos el vector y no la dirección del vector:

func main() {
    var vec [5]int
    cargar(&vec)
    ordenar(&vec)
    imprimir(vec)
}

Por eficiencia si tenemos un vector con miles o decenas de miles de elementos conviene enviar siempre la dirección del vector (inclusive si solo hay que imprimirlo o consultar sus datos), con esto evitamos que se haga una copia en la función.

Problema propuesto

  • Definir dos vectores paralelos 5 elementos donde almacenemos en uno los nombres de productos y en otro sus precios.
    Implementar las funciones:
    1 - La carga de productos y sus precios.
    2 - Consulta de un producto por su nombre, mostrar el precio o un mensaje indicando que no existe dicho producto.
Solución
ejercicio118.go

package main

import "fmt"

func cargar(productos *[5]string, precios *[5]float64) {
    for f := 0; f < len(productos); f++ {
        fmt.Print("Ingrese el nombre del producto:")
        fmt.Scan(&productos[f])
        fmt.Print("Ingrese el precio:")
        fmt.Scan(&precios[f])
    }    
}

func consultaPorNombre(productos [5]string, precios[5]float64) {
    var aux string
    existe := 0
    fmt.Print("Ingrese el nombre del producto a consultar:")
    fmt.Scan(&aux)
    for f := 0; f < len(productos); f++ {
        if aux==productos[f] {
            fmt.Println("El producto tiene un precio de ", precios[f])
            existe=1
        }
    }
    if existe == 0 {
        fmt.Println("No existe un producto con dicho nombre almacenado")
    }
}

func main() {
    var productos [5]string
    var precios [5]float64
    cargar(&productos, &precios)
    consultaPorNombre(productos, precios)
}