Methods (https://tour.golang.org/methods/1)
- Talvez pareça meio estranho mas...Go não tem classes porém você pode declarar métodos.
- Quem pode ter métodos? Types!
- Para saber a diferença entre um method e uma function, repare que method tem uma variável definida logo após a palavra "func" e ela que determina de qual tipo é o method.
package main
import (
"fmt"
)
type Person struct {
name string
age int
}
// Method
func (p Person) Describe() string {
return fmt.Sprintf("Name: %s Age: %d", p.name, p.age)
}
// Function
func Describe(p Person) string {
return fmt.Sprintf("Name: %s Age: %d", p.name, p.age)
}
func main() {
p1 := Person{"Bob", 40}
fmt.Println(p1.Describe())
fmt.Println(Describe(p1))
}
- Method pode ser definido também para types que não são structs.
package main
import (
"fmt"
)
type MyNumber int
func (m MyNumber) isOdd() bool {
return m % 2 != 0
}
func main() {
m1 := MyNumber(3)
fmt.Println(m1.isOdd())
}
- Vocẽ pode fazer um método receber um ponteiro para um tipo ao invés de receber o valor. Isso é útil para alterar a variável em si.
- Vamos dar uma revisada em pointeiros e analisar o exemplo abaixo.
package main
import "fmt"
func changeNumber(p *int) {
*p = 8
}
func dontChangeNumber(p *int) {
p = new(int)
*p = 9
}
func main() {
var i *int
j := 5 // short declaration variable
k := new(int) // returns a pointer to a int type
fmt.Printf("Just declared - i: %+v j: %+v k: %+v k value: %+v \n", i, j, k, *k)
changeNumber(&j)
fmt.Printf("After changeNumber(&j) - i: %+v j: %+v k: %+v k value: %+v \n", i, j, k, *k)
dontChangeNumber(&j)
fmt.Printf("After dontChangeNumber(&j) - i: %+v j: %+v k: %+v k value: %+v \n", i, j, k, *k)
// panic: runtime error: invalid memory address or nil pointer dereference
//changeNumber(i)
//fmt.Printf("After changeNumber(i) - i: %+v j: %+v k: %+v k value: %+v \n", i, j, k, *k)
dontChangeNumber(i)
fmt.Printf("After dontChangeNumber(i) - i: %+v j: %+v k: %+v k value: %+v \n", i, j, k, *k)
changeNumber(k)
fmt.Printf("After changeNumber(k) - i: %+v j: %+v k: %+v k value: %+v \n", i, j, k, *k)
dontChangeNumber(k)
fmt.Printf("After dontChangeNumber(k) - i: %+v j: %+v k: %+v k value: %+v \n", i, j, k, *k)
}
Saída:
Just declared - i: <nil> j: 5 k: 0x414020 k value: 0
After changeNumber(&j) - i: <nil> j: 8 k: 0x414020 k value: 0
After dontChangeNumber(&j) - i: <nil> j: 8 k: 0x414020 k value: 0
After dontChangeNumber(i) - i: <nil> j: 8 k: 0x414020 k value: 0
After changeNumber(k) - i: <nil> j: 8 k: 0x414020 k value: 8
After dontChangeNumber(k) - i: <nil> j: 8 k: 0x414020 k value: 8
-
Vamos começar com a declaração de variáveis.
-
var i *int: quando declaramos dessa forma, "i" terá o zero value correspondente ao seu tipo sendo, no caso, "nil".
-
j := 5 : essa forma é chamada de "short declaration". Curioso? Veja a especificação:
shortVarDecl
func (p *parser) shortVarDecl(decl *ast.AssignStmt, list []ast.Expr) {
NewObj
func NewObj(kind ObjKind, name string) *Object
- k := new(int) : new é uma builtin que retorna um ponteiro do tipo int. Curioso? Veja a especificação:
new
func new(Type) *Type
newobject
func newobject(typ *_type) unsafe.Pointer
Pointer represents a pointer to an arbitrary type.
-
Por isso, temos a saída:
Just declared - i: <nil> j: 5 k: 0x414020 k value: 0
-
Analisando changeNumber(&j). Estamos passando o endereço da memória da varíavel j, ou seja, &j. Esse endereço é passado por valor e o recebemos na varíavel p. Usando "*p" fazemos com que esse endereço aponte para o valor "8".
-
Analisando dontChangeNumber(&j). Estamos passando o endereço da memória da varíavel j, ou seja, &j. Esse endereço é passado por valor e o recebemos na varíavel p. Mudamos o valor da varíavel p em "p = new(int)" e no endereço recebido da builtin new, apontamos para o valor "9". Note que isso não alterou o valor de j pois conforme especificação de chamada de função em Go, passamos o endereço da memória da varíavel j POR VALOR. Assim, "p = new(int)" não altera nossa varíavel.
After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution.
Referências:
https://golang.org/ref/spec#Calls
https://medium.com/@kavehmz/call-by-value-in-go-even-for-references-2bc4b2a3c590
Muito louco, né?
-
Analisando changeNumber(i). Aqui ocorre erro pois tentamos apontar "nil" para o valor "8".
-
Analisando dontChangeNumber(i). Aqui passamos o valor "nil" mas ele não é utilizado. Não altera nada.
-
Analisando changeNumber(k). Note que aqui passamos k direto por já ser um endereço de memória, sem precisar usar o "&'. O valor é alterado e aponta para "8".
-
Analisando dontChangeNumber(k). Ocorre o mesmo que já explicamos antes. A varíavel k não sofre alteração.
-
ATENÇÃO PARA A PEGADINHA
-
Veja o programa abaixo. Será que vai dar erro? Qual a saída?
package main
import "fmt"
type Chiclete struct {
Sabor string
}
func (c *Chiclete) MudarSabor(s string) {
c.Sabor = s
}
func main() {
c := Chiclete{"morango"}
fmt.Println(c)
c.MudarSabor("melancia")
fmt.Println(c)
p := &Chiclete{"banana"}
fmt.Println(p)
p.MudarSabor("uva")
fmt.Println(p)
}
- Resposta: Parece estranho que tanto a variável c do tipo Chiclete quanto a variável p do tipo ponteiro para Chiclete funcionem da mesma maneira ao chamar o método MudarSabor mas esse é um comportamento esperado em Go pois métodos que recebem ponteiros também aceitam valores. Go interpreta o "c.MudarSabor" como "(&c).MudarSabor"
Faça agora o exercício 3 e veja como se comporta no caso contrário: o método espera valor mas recebe ponteiro.
Como escolher se o meu método vai receber valor ou ponteiro?
- Usando ponteiro, o método poderá alterar o valor da variável.
- Usando ponteiro, você evita que o valor seja copiado para toda vez que o método for chamado.
Tópico Avançado:
- Um ponteiro é uma variável que contém um endereço de memória onde está armazenado determinado valor.
- Esse valor deve ficar armazenado na heap até não estar mais em uso e quem verifica isso é o Garbage Collector. #javafeelings
- Quanto mais valores lá, mais trabalho o Garbage Collector terá.
Então, ao decidir por usar ponteiros, considere os prós e contras no contexto da sua aplicação.
Referências:
https://medium.com/@vCabbage/go-are-pointers-a-performance-optimization-a95840d3ef85
https://segment.com/blog/allocation-efficiency-in-high-performance-go-services/
https://blog.gopheracademy.com/advent-2018/avoid-gc-overhead-large-heaps/
- Crie um método que calcula quantos anos a pessoa têm a partir da sua data de nascimento (não precisa se preocupar muito com precisão).
Dica: https://golang.org/pkg/time/#Time.Sub Tema: Methods
-
Aproveitando a struct Person do primeiro exemplo, crie um método que altere o atributo name. Tema: Pointer receivers
-
Aproveitando o exemplo onde consta o tipo Chiclete, o que aconteceria se mudasse de "func (c *Chiclete) MudarSabor(s string)" para "func (c Chiclete) MudarSabor(s string)"? Tema: Pointer receivers