İçeriğe geç
Educora
Orta16 dk6 / 10

Struct'lar, metotlar ve işaretçiler

Struct'larla kendi türlerini oluştur, işaretçilerin neden gerektiğini anla, değer ve işaretçi alıcılı metotlar yaz, bileşimi kullan.

Kendini test et
Bu derste öğreneceklerin
  • Bir struct tanımlamak, literal ile oluşturmak ve %+v ile yazdırmak
  • & ve * ile işaretçileri kullanmak
  • Değer ve işaretçi alıcı arasında doğru seçim yapmak
  • NewX kurucu fonksiyonları ve struct gömme ile çalışmak

Bir öğrenciyle ilgili bilgiler (ad, yaş, not ortalaması) bir arada durmalıdır. Onları üç ayrı değişkende tutmak zahmetlidir. Go'da sınıf yoktur; onun yerine ilgili alanları tek bir türde toplayan struct'lar ve bu türe davranış ekleyen metotlar vardır. Bu derste ayrıca işaretçileri de öğreneceksin: onlar olmadan bir fonksiyon asıl veriyi değiştiremez.

Struct'lar

Bir struct type Ad struct { ... } biçiminde tanımlanır ve içinde alanlar ile türleri sıralanır. Bir değer oluşturmak için struct literal'i yazılır: Student{Name: "Aysel", Age: 15}. Alanlara nokta ile erişilir: s.Name. Değer verilmeyen alan sıfır değerini alır; bu yüzden boş bir struct bile tamamen kullanılabilir bir değerdir.

Go
package main

import "fmt"

type Student struct {
	Name  string
	Age   int
	Score float64
}

func main() {
	s := Student{Name: "Aysel", Age: 15, Score: 92.5}
	var empty Student
	s.Age++
	fmt.Println(s)
	fmt.Printf("%+v\n", s)
	fmt.Printf("%+v\n", empty)
	fmt.Println(s.Name, s.Score)
}
Beklenen çıktı
{Aysel 16 92.5}
{Name:Aysel Age:16 Score:92.5}
{Name: Age:0 Score:0}
Aysel 92.5

%v (ve Println) yalnızca değerleri gösterir, %+v ise alan adlarını da ekler; hata ayıklarken bu çok kullanışlıdır. Bütün alanları karşılaştırılabilir olan struct'lar == ile karşılaştırılabilir: iki Student değeri tüm alanları eşit olduğunda eşittir.

İşaretçiler

Go'da bir fonksiyona verilen her şey kopyalanır. Fonksiyon parametresini değiştirirse çağıran taraftaki değişken etkilenmez. Asıl değeri değiştirmek için bir işaretçi gerekir: değişkenin bellekteki adresi. &x adresi alır, *p ise o adresteki değeri okur ya da değiştirir. *int türü “bir int'e işaretçi” demektir. Go'da işaretçi aritmetiği (C'deki gibi p++) yoktur; bu yüzden işaretçiler güvenlidir.

Go
package main

import "fmt"

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

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

func main() {
	x := 5
	p := &x
	*p = 7
	fmt.Println(x, *p, p == &x)
	doubleValue(x)
	fmt.Println(x)
	doublePointer(&x)
	fmt.Println(x)
}
Beklenen çıktı
7 7 true
7
14
Tanım
İşaretçi (pointer)

Başka bir değişkenin bellekteki adresini tutan değer. Sıfır değeri nil'dir. & adresi alır, * ise o adresteki değere erişir.

Metotlar: değer ve işaretçi alıcılar

Bir metot, alıcı (receiver) adı verilen özel bir parametresi olan fonksiyondur. Alıcı, func sözcüğü ile metodun adı arasına yazılır: func (a Account) Summary() string. Alıcı bir değerse (a Account) metot nesnenin kopyasıyla çalışır. Bir işaretçiyse (a *Account) metot asıl nesneyi değiştirebilir.

Go
package main

import "fmt"

type Account struct {
	Owner   string
	Balance float64
}

func (a Account) Summary() string {
	return fmt.Sprintf("%s: %.2f AZN", a.Owner, a.Balance)
}

func (a *Account) Deposit(amount float64) {
	a.Balance += amount
}

func (a Account) BrokenDeposit(amount float64) {
	a.Balance += amount
}

func main() {
	acc := Account{Owner: "Murad", Balance: 100}
	acc.Deposit(50)
	acc.BrokenDeposit(1000)
	fmt.Println(acc.Summary())
	p := &acc
	p.Deposit(25.5)
	fmt.Println(p.Summary())
}
Beklenen çıktı
Murad: 150.00 AZN
Murad: 175.50 AZN

BrokenDeposit 1000 manat eklemeye çalıştı ama bakiye değişmedi, çünkü metot bir kopya üzerinde çalışıyordu. Dikkat: acc bir işaretçi değil, yine de acc.Deposit(50) çalışıyor; Go otomatik olarak (&acc).Deposit(50) çağırır. Aynı şekilde p işaretçisi üzerinden değer alıcılı Summary metodu da çağrılabilir.

AlıcıNe zaman seçilmeli
func (a Account)Metot nesneyi değiştirmiyor ve tür küçük (birkaç alan, time.Time gibi).
func (a *Account)Metot alanları değiştiriyor, tür büyük ya da içinde sync.Mutex gibi kopyalanmaması gereken bir alan var.
Hata: değişiklik kaybolur
type Counter struct {
	value int
}

func (c Counter) Increment() {
	c.value++ // changes a copy
}
Düzeltildi: işaretçi alıcı
type Counter struct {
	value int
}

func (c *Counter) Increment() {
	c.value++ // changes the original
}

Kurucular ve gömme

Go'da özel kurucular (constructor) yoktur. Onların yerine adı New ile başlayan sıradan bir fonksiyon yazılır: NewTeacher(...) bir nesne oluşturur ve ona bir işaretçi döndürür. Kalıtım da yoktur; Go bileşimi (composition) tercih eder. Bir struct başka bir struct'ın içine adsız bir alan olarak gömülebilir (embedding): o zaman gömülen struct'ın alanları ve metotları dıştaki struct üzerinden doğrudan kullanılabilir.

Go
package main

import "fmt"

type Person struct {
	Name string
}

func (p Person) Greet() string {
	return "Hi, I am " + p.Name
}

type Teacher struct {
	Person
	Subject string
}

func NewTeacher(name, subject string) *Teacher {
	return &Teacher{Person: Person{Name: name}, Subject: subject}
}

func main() {
	t := NewTeacher("Leyla", "Math")
	fmt.Println(t.Greet())
	fmt.Println(t.Name, "teaches", t.Subject)
	fmt.Printf("%+v\n", *t)
}
Beklenen çıktı
Hi, I am Leyla
Leyla teaches Math
{Person:{Name:Leyla} Subject:Math}

Önemli noktalar

  • Bir struct ilgili alanları tek bir türde toplar; %+v onu alan adlarıyla yazdırır.
  • Go her şeyi değerle aktarır; asıl değeri değiştirmek için bir işaretçi (&x, *p) gerekir.
  • Değer alıcı bir kopyayla, işaretçi alıcı asıl nesneyle çalışır; & işaretini Go kendisi ekler.
  • nil bir işaretçi üzerinden alana erişmek panic'e yol açar.
  • Kurucular yerine NewX fonksiyonları, kalıtım yerine struct gömme (bileşim) kullanılır.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
fmt.Printf("%+v", Point{X: 1, Y: 2}) ne yazar?