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.
Desafio
FácilVamos 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 structCartItemcom os camposName(string),Price(float64) eQuantity(int). Crie duas funções que tentam atualizar a quantidade:UpdateQuantityByValuerecebe umCartItem(por valor) e uma nova quantidade, então define a quantidade do item para o novo valorUpdateQuantityByPointerrecebe um*CartItem(ponteiro) e uma nova quantidade, então define a quantidade do item para o novo valor
TotalemCartItemque 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.97Observe 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á)
}
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
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool