Menu
Coddy logo textTech

Passagem por Valor vs Referência

Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 21 de 107.

Go é uma linguagem de passagem por valor. Quando você passa uma variável para uma função, o Go cria uma cópia desse valor. Isso significa que alterações dentro da função não afetam a variável original.

func double(n int) {
    n = n * 2
}

func main() {
    x := 10
    double(x)
    fmt.Println(x)  // 10 (inalterado)
}

O mesmo se aplica a structs. Passar uma struct para uma função copia todos os seus campos, então as modificações dentro da função são perdidas.

type Person struct {
    Name string
    Age  int
}

func birthday(p Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(alice)
    fmt.Println(alice.Age)  // 30 (inalterado)
}

Para modificar o valor original, passe um ponteiro em vez disso. O ponteiro em si é copiado, mas ele ainda aponta para o mesmo endereço de memória, permitindo que você altere os dados originais.

func birthday(p *Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(&alice)
    fmt.Println(alice.Age)  // 31 (modificado!)
}

Use ponteiros quando precisar modificar o valor original ou ao passar structs grandes para evitar cópias dispendiosas. Use valores quando quiser proteger os dados originais de alterações indesejadas.

challenge icon

Desafio

Fácil

Vamos construir um sistema de carrinho de compras que demonstra a diferença entre passar structs por valor versus por ponteiro. Você verá em primeira mão como apenas parâmetros de ponteiro podem modificar os dados originais.

Você organizará seu código em dois arquivos:

  • cart.go: Defina uma struct CartItem com os campos Name (string), Price (float64) e Quantity (int). Crie duas funções que tentam atualizar a quantidade:
    • UpdateQuantityByValue recebe um CartItem (por valor) e uma nova quantidade, então define a quantidade do item para o novo valor
    • UpdateQuantityByPointer recebe um *CartItem (ponteiro) e uma nova quantidade, então define a quantidade do item para o novo valor
    Também crie um método Total em CartItem que retorna o preço multiplicado pela quantidade.
  • main.go: Leia os detalhes do item da entrada, crie um CartItem e, em seguida, demonstre ambas as abordagens de atualização. Primeiro tente atualizar com a função de valor e mostre o resultado, depois atualize com a função de ponteiro e mostre o resultado novamente.

As seguintes entradas serão fornecidas:

  • Linha 1: Nome do item
  • Linha 2: Preço (float)
  • Linha 3: Quantidade inicial (inteiro)
  • Linha 4: Nova quantidade a ser definida (inteiro)

Formato de saída:

Initial: [Name] x[Quantity] = $[Total]
After value update: [Name] x[Quantity] = $[Total]
After pointer update: [Name] x[Quantity] = $[Total]

Por exemplo, dado Laptop, 999.99, 1 e 3, sua saída deve ser:

Initial: Laptop x1 = $999.99
After value update: Laptop x1 = $999.99
After pointer update: Laptop x3 = $2999.97

Observe como a quantidade permanece em 1 após a atualização por valor (a função modificou uma cópia), mas muda para 3 após a atualização por ponteiro (a função modificou o original). Formate os totais com duas casas decimais.

Folha de consulta

Go é uma linguagem de passagem por valor. Quando você passa uma variável para uma função, o Go cria uma cópia desse valor, portanto, as alterações dentro da função não afetam a variável original.

func double(n int) {
    n = n * 2
}

func main() {
    x := 10
    double(x)
    fmt.Println(x)  // 10 (inalterado)
}

O mesmo se aplica a structs. Passar uma struct para uma função copia todos os seus campos:

type Person struct {
    Name string
    Age  int
}

func birthday(p Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(alice)
    fmt.Println(alice.Age)  // 30 (inalterado)
}

Para modificar o valor original, passe um ponteiro. O ponteiro em si é copiado, mas ele ainda aponta para o mesmo endereço de memória:

func birthday(p *Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(&alice)
    fmt.Println(alice.Age)  // 31 (modificado!)
}

Use ponteiros quando precisar modificar o valor original ou ao passar structs grandes para evitar cópias dispendiosas. Use valores quando quiser proteger os dados originais de alterações indesejadas.

Experimente você mesmo

package main

import (
	"fmt"
)

func main() {
	// Ler entrada
	var name string
	var price float64
	var initialQty, newQty int

	fmt.Scanln(&name)
	fmt.Scanln(&price)
	fmt.Scanln(&initialQty)
	fmt.Scanln(&newQty)

	// TODO: Criar um CartItem com os valores de entrada

	// TODO: Imprimir o estado inicial usando o formato:
	// fmt.Printf("Initial: %s x%d = $%.2f\n", ...)

	// TODO: Tentar atualizar a quantidade usando UpdateQuantityByValue
	// Em seguida, imprimir o resultado (observe que a quantidade não mudará)

	// TODO: Atualizar a quantidade usando UpdateQuantityByPointer
	// Em seguida, imprimir o resultado (observe que a quantidade mudará)
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos