go
Go dersleri
Go/Tipler ve Soyutlama

Struct'lar ve Metotlar

Kendi tiplerini tanımla: struct'lar, metotlar, alıcılar ve gömme.

Ders 8 / 2125 dkBaşlangıç
Bu derste öğreneceklerin
  • Struct tanımlama ve struct literal'leri
  • Anonim struct'lar
  • Metot tanımlama
  • Değer alıcı ve işaretçi alıcı (receiver)
  • Gömme (embedding) ve kompozisyon
  • Yapıcı fonksiyon deseni (NewX)
  • Struct karşılaştırma ve etiketler (tags)

Şimdiye kadar dilin hazır verdiği tiplerle çalıştın: sayılar, metinler, dilimler, haritalar. Gerçek programlar ise kendi kavramlarıyla konuşur: bir kullanıcı, bir sipariş, bir dikdörtgen, bir banka hesabı. Bu kavramları ifade etmenin Go'daki yolu struct'tır: Birbiriyle ilgili alanları tek bir isim altında toplayan bir veri tipi.

Struct'ları tanımladıktan sonra onlara davranış eklersin. Go'da bunun adı metottur: Belirli bir tiple ilişkilendirilmiş bir fonksiyon. Ama burada önemli bir fark vardır. Go'da sınıf yoktur, kalıtım yoktur, this yoktur. Bunun yerine sade bir fikir vardır: Bir fonksiyonun adından önce, o fonksiyonun hangi tipe ait olduğunu söyleyen bir alıcı (receiver) yazarsın. Bu küçük sözdizimsel fark, nesne yönelimli dillerin karmaşık miras hiyerarşilerine gerek kalmadan aynı işi görmeni sağlar.

Go'nun tercihi kalıtım yerine kompozisyondur: Bir tipi başka bir tipin içine koyarak yeteneklerini birleştirirsin. Bu derste struct tanımlamayı, literal yazımlarını, anonim struct'ları, metotları, değer ve işaretçi alıcılar arasındaki kritik farkı, gömmeyi (embedding), yapıcı fonksiyon desenini, struct karşılaştırmasını ve etiketleri (tags) öğreneceksin. Bu konular Go'da kendi kütüphanelerini yazmanın temelidir.

Struct tanımlama

Bir struct, alanların (field) sıralı bir listesidir. Her alanın bir adı ve bir tipi vardır:

main.go
package main

import "fmt"

type Book struct {
	Title  string
	Author string
	Year   int
	Pages  int
}

func main() {
	// 1) Alan adlarıyla (önerilen): sıra önemsiz, eksik alanlar sıfır değer alır
	b1 := Book{
		Title:  "Tutunamayanlar",
		Author: "Oğuz Atay",
		Year:   1972,
	}

	// 2) Sırayla: tüm alanlar verilmeli, sıraya bağımlı — kırılgandır
	b2 := Book{"Kürk Mantolu Madonna", "Sabahattin Ali", 1943, 160}

	// 3) Boş struct: tüm alanlar sıfır değerde
	var b3 Book

	fmt.Printf("%+v\n", b1)
	fmt.Printf("%+v\n", b2)
	fmt.Printf("%+v\n", b3)

	// Alanlara nokta ile erişilir
	b1.Pages = 724
	fmt.Println(b1.Title, "-", b1.Pages, "sayfa")
}
Çıktı
{Title:Tutunamayanlar Author:Oğuz Atay Year:1972 Pages:0}
{Title:Kürk Mantolu Madonna Author:Sabahattin Ali Year:1943 Pages:160}
{Title: Author: Year:0 Pages:0}
Tutunamayanlar - 724 sayfa

Alan adları da büyük/küçük harf kuralına tabidir: Büyük harfle başlayan alanlar paket dışından erişilebilir, küçük harfle başlayanlar erişilemez. Bu, bir tipin iç durumunu gizlemenin (encapsulation) Go'daki yoludur.

%+v fiili struct'ı alan adlarıyla birlikte yazdırır; hata ayıklarken en sık kullanacağın fiildir. %#v ise Go sözdizimiyle yazdırır, kopyalayıp koda yapıştırabileceğin biçimde.

İç içe ve anonim struct'lar

Bir struct'ın alanı başka bir struct olabilir. Bu, gerçek dünyadaki iç içe yapıları doğal biçimde ifade eder:

main.go
package main

import "fmt"

type Address struct {
	City    string
	Country string
}

type Company struct {
	Name    string
	Address Address // iç içe struct
	Founded int
}

func main() {
	c := Company{
		Name:    "Yıldız Yazılım",
		Address: Address{City: "İzmir", Country: "Türkiye"},
		Founded: 2015,
	}
	fmt.Println(c.Address.City)

	// Anonim struct: tek kullanımlık, isim vermeye değmeyen yapılar
	stats := struct {
		Total   int
		Average float64
	}{Total: 42, Average: 3.5}
	fmt.Printf("%+v\n", stats)

	// Tablo güdümlü testlerde çok kullanılan desen
	cases := []struct {
		input string
		want  int
	}{
		{"", 0},
		{"go", 2},
		{"merhaba", 7},
	}
	for _, tc := range cases {
		got := len(tc.input)
		fmt.Printf("%q%d (beklenen %d, doğru mu: %t)\n", tc.input, got, tc.want, got == tc.want)
	}
}
Çıktı
İzmir
{Total:42 Average:3.5}
"" → 0 (beklenen 0, doğru mu: true)
"go" → 2 (beklenen 2, doğru mu: true)
"merhaba" → 7 (beklenen 7, doğru mu: true)

Anonim struct'lar, yalnızca bir yerde kullanılacak geçici veri şekilleri için idealdir. En yaygın kullanım alanı tablo güdümlü testlerdir; bunu Test ve Benchmark dersinde göreceksin.

Metotlar

Metot, alıcısı olan bir fonksiyondur. Alıcı, func ile fonksiyon adının arasına parantez içinde yazılır:

main.go
package main

import (
	"fmt"
	"math"
)

type Rectangle struct {
	Width  float64
	Height float64
}

// Değer alıcı: r, çağrıldığında kopyalanır
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
	return 2 * (r.Width + r.Height)
}

// İşaretçi alıcı: r, gerçek nesneyi gösterir
func (r *Rectangle) Scale(factor float64) {
	r.Width *= factor
	r.Height *= factor
}

func (r Rectangle) Diagonal() float64 {
	return math.Hypot(r.Width, r.Height)
}

func main() {
	rect := Rectangle{Width: 3, Height: 4}

	fmt.Printf("alan: %.1f, çevre: %.1f, köşegen: %.1f\n",
		rect.Area(), rect.Perimeter(), rect.Diagonal())

	rect.Scale(2) // Go otomatik olarak (&rect).Scale(2) yapar
	fmt.Printf("ölçeklendikten sonra: %+v, alan: %.1f\n", rect, rect.Area())
}
Çıktı
alan: 12.0, çevre: 14.0, köşegen: 5.0
ölçeklendikten sonra: {Width:6 Height:8}, alan: 48.0

Metot yalnızca struct'lara değil, kendi paketinde tanımladığın her tipe eklenebilir. Bir sayıya, bir dilime veya bir fonksiyon tipine bile metot yazabilirsin:

main.go
package main

import (
	"fmt"
	"strings"
)

type Celsius float64

func (c Celsius) Fahrenheit() float64 { return float64(c)*9/5 + 32 }
func (c Celsius) String() string      { return fmt.Sprintf("%.1f°C", float64(c)) }

type Tags []string

func (t Tags) Join() string { return strings.Join(t, ", ") }
func (t Tags) Has(name string) bool {
	for _, tag := range t {
		if tag == name {
			return true
		}
	}
	return false
}

func main() {
	temp := Celsius(23.5)
	fmt.Println(temp, "=", temp.Fahrenheit(), "°F")

	tags := Tags{"go", "backend", "api"}
	fmt.Println(tags.Join())
	fmt.Println("api var mı:", tags.Has("api"), "| web var mı:", tags.Has("web"))
}
Çıktı
23.5°C = 74.3 °F
go, backend, api
api var mı: true | web var mı: false

String() string metodu özeldir: fmt paketi bu metodu olan tipleri yazdırırken onu çağırır. Bu, fmt.Stringer arayüzünün gereğidir ve Arayüzler dersinin konusudur.

Değer alıcı mı, işaretçi alıcı mı?

Bu, Go öğrenirken en çok kafa karıştıran konudur; ama kural aslında kısadır.

Değer alıcı (func (r Rectangle)) metodu çağrıldığında struct kopyalanır. Metot içinde yaptığın değişiklikler orijinali etkilemez. Okuma yapan, yeni bir değer hesaplayan metotlar için uygundur.

İşaretçi alıcı (func (r *Rectangle)) metodu orijinal nesneyi gösterir. Değişiklikler kalıcıdır ve büyük struct'larda kopyalama maliyeti ortadan kalkar.

main.go
package main

import "fmt"

type Counter struct {
	value int
	name  string
}

func (c Counter) IncrementByValue() { c.value++ } // kopyayı artırır: etkisiz
func (c *Counter) Increment()       { c.value++ } // gerçek nesneyi artırır
func (c Counter) Value() int        { return c.value }

func main() {
	c := Counter{name: "istek"}

	c.IncrementByValue()
	c.IncrementByValue()
	fmt.Println("değer alıcıdan sonra:", c.Value())

	c.Increment()
	c.Increment()
	c.Increment()
	fmt.Println("işaretçi alıcıdan sonra:", c.Value())

	// Go, adres alma ve türetmeyi otomatik yapar
	p := &c
	fmt.Println("işaretçi üzerinden değer metodu:", p.Value()) // (*p).Value()
	c.Increment()                                              // (&c).Increment()
	fmt.Println("son değer:", c.Value())
}
Çıktı
değer alıcıdan sonra: 0
işaretçi alıcıdan sonra: 3
işaretçi üzerinden değer metodu: 3
son değer: 4

Pratikte kullanacağın kurallar:

  1. Metot alıcıyı değiştiriyorsa işaretçi alıcı kullan. Başka seçenek yoktur.
  2. Struct büyükse işaretçi alıcı kullan. Her çağrıda yüzlerce baytın kopyalanmasını önlersin.
  3. Tipin metotları karışık olmasın. Bir tipin bazı metotları işaretçi alıcıysa, hepsini işaretçi alıcı yap. Karışık kullanım hem kafa karıştırır hem de arayüz uyumunda sürprizlere yol açar.
  4. Küçük, değişmez (immutable) tiplerde değer alıcı kullan. time.Time, Celsius gibi tipler böyle tasarlanmıştır.
  5. Alıcı sync.Mutex gibi kopyalanmaması gereken bir alan içeriyorsa işaretçi alıcı zorunludur.

Gömme (embedding) ve kompozisyon

Bir struct'ın içine, alan adı yazmadan doğrudan bir tip koyarsan buna gömme denir. Gömülen tipin alanları ve metotları, dıştaki tipe yükseltilir (promoted): Sanki onun kendi üyeleriymiş gibi erişilir.

main.go
package main

import "fmt"

type Animal struct {
	Name string
	Legs int
}

func (a Animal) Describe() string {
	return fmt.Sprintf("%s (%d bacak)", a.Name, a.Legs)
}

func (a Animal) Speak() string { return "..." }

type Dog struct {
	Animal // gömülü: alan adı yok
	Breed  string
}

// Dog kendi Speak metodunu tanımlayarak gömülü olanı gölgeler
func (d Dog) Speak() string { return "Hav!" }

func main() {
	d := Dog{
		Animal: Animal{Name: "Karabaş", Legs: 4},
		Breed:  "Kangal",
	}

	fmt.Println(d.Name)           // Animal'dan yükseltilen alan
	fmt.Println(d.Describe())     // Animal'dan yükseltilen metot
	fmt.Println(d.Speak())        // Dog'un kendi metodu
	fmt.Println(d.Animal.Speak()) // gömülü olana açıkça erişim
	fmt.Printf("%+v\n", d)
}
Çıktı
Karabaş
Karabaş (4 bacak)
Hav!
...
{Animal:{Name:Karabaş Legs:4} Breed:Kangal}

Gömme kalıtım değildir. Dog, Animal "değildir"; Dog, bir Animal içerir ve onun yeteneklerini devralmış gibi görünür. Aradaki fark önemlidir: Bir fonksiyon Animal parametresi bekliyorsa ona Dog geçiremezsin; d.Animal yazman gerekir. Gerçek çok biçimlilik (polymorphism) Go'da arayüzlerle sağlanır.

Gömmenin en sık kullanıldığı yer, bir tipin davranışını sararak genişletmektir. Standart kütüphanede bufio.ReadWriter tam olarak bunu yapar: içine *bufio.Reader ve *bufio.Writer gömerek her ikisinin metotlarını tek tipte toplar.

Yapıcı fonksiyon deseni

Go'da yapıcı (constructor) diye bir dil özelliği yoktur. Yerine, tipin geçerli bir örneğini döndüren bir fonksiyon yazılır. Gelenek, adının New ile başlamasıdır:

main.go
package main

import (
	"errors"
	"fmt"
	"strings"
)

type Account struct {
	owner   string
	balance int // kuruş cinsinden: ondalık hatası olmaz
}

// NewAccount geçerli bir hesap oluşturur; kuralları tek noktada uygular.
func NewAccount(owner string, initial int) (*Account, error) {
	owner = strings.TrimSpace(owner)
	if owner == "" {
		return nil, errors.New("hesap sahibi boş olamaz")
	}
	if initial < 0 {
		return nil, errors.New("başlangıç bakiyesi negatif olamaz")
	}
	return &Account{owner: owner, balance: initial}, nil
}

func (a *Account) Deposit(amount int) error {
	if amount <= 0 {
		return errors.New("yatırılan tutar pozitif olmalı")
	}
	a.balance += amount
	return nil
}

func (a *Account) Withdraw(amount int) error {
	if amount > a.balance {
		return fmt.Errorf("yetersiz bakiye: %d kuruş istendi, %d kuruş var", amount, a.balance)
	}
	a.balance -= amount
	return nil
}

func (a *Account) String() string {
	return fmt.Sprintf("%s: %.2f TL", a.owner, float64(a.balance)/100)
}

func main() {
	acc, err := NewAccount("  Ayşe  ", 10_000)
	if err != nil {
		fmt.Println("hata:", err)
		return
	}
	fmt.Println(acc)

	_ = acc.Deposit(2_500)
	fmt.Println("yatırma sonrası:", acc)

	if err := acc.Withdraw(50_000); err != nil {
		fmt.Println("çekim reddedildi:", err)
	}

	if _, err := NewAccount("", 100); err != nil {
		fmt.Println("oluşturma reddedildi:", err)
	}
}
Çıktı
Ayşe: 100.00 TL
yatırma sonrası: Ayşe: 125.00 TL
çekim reddedildi: yetersiz bakiye: 50000 kuruş istendi, 12500 kuruş var
oluşturma reddedildi: hesap sahibi boş olamaz

Alanların küçük harfle başlaması, paket dışından doğrudan değiştirilmelerini engeller. Böylece "bakiye asla negatif olamaz" gibi değişmezleri (invariant) yalnızca metotlar üzerinden garanti edebilirsin. Bu, Go'da kapsüllemenin tipik uygulanışıdır.

Karşılaştırma, kopyalama ve etiketler

İki struct, tüm alanları karşılaştırılabilir tiplerdeyse == ile karşılaştırılabilir. Karşılaştırma alan alan yapılır:

main.go
package main

import "fmt"

type Point struct{ X, Y int }

type Config struct {
	Name string
	Tags []string // dilim: karşılaştırılamaz
}

func main() {
	p1 := Point{1, 2}
	p2 := Point{1, 2}
	fmt.Println("eşit mi:", p1 == p2)

	// Karşılaştırılabilir struct'lar map anahtarı olabilir
	grid := map[Point]string{
		{0, 0}: "başlangıç",
		{1, 2}: "hedef",
	}
	fmt.Println(grid[Point{1, 2}])

	// Atama struct'ı KOPYALAR
	p3 := p1
	p3.X = 99
	fmt.Println(p1, p3)

	// Dilim içeren struct == ile karşılaştırılamaz (derleme hatası olurdu).
	// Onun yerine reflect.DeepEqual veya alan alan karşılaştırma kullanılır.
	c := Config{Name: "prod", Tags: []string{"a"}}
	d := c // sığ kopya: Tags aynı diziyi gösterir
	d.Tags[0] = "değişti"
	fmt.Println(c.Tags[0], d.Tags[0])
}
Çıktı
eşit mi: true
hedef
{1 2} {99 2}
değişti değişti

Son örnek sığ kopyalama (shallow copy) tuzağını gösterir: Struct kopyalandığında içindeki dilim başlığı da kopyalanır, ama altta yatan dizi paylaşılır. Derin kopya istiyorsan dilimi açıkça klonlaman gerekir.

Struct etiketleri

Alanların arkasına ters tırnak içinde yazılan metinlere etiket (tag) denir. Derleyici bunlara dokunmaz; çalışma zamanında yansıma (reflection) ile okunurlar. En yaygın kullanıcıları JSON, XML ve veritabanı kütüphaneleridir:

main.go
package main

import (
	"encoding/json"
	"fmt"
)

type Product struct {
	ID       int     `json:"id"`
	Name     string  `json:"name"`
	Price    float64 `json:"price"`
	Discount float64 `json:"discount,omitempty"` // sıfırsa çıktıya konmaz
	internal string  // dışa açık değil: JSON'a hiç girmez
}

func main() {
	p := Product{ID: 7, Name: "Klavye", Price: 899.90, internal: "gizli"}

	data, err := json.Marshal(p)
	if err != nil {
		fmt.Println("hata:", err)
		return
	}
	fmt.Println(string(data))

	var decoded Product
	if err := json.Unmarshal([]byte(`{"id":9,"name":"Fare","price":249.5,"discount":10}`), &decoded); err != nil {
		fmt.Println("hata:", err)
		return
	}
	fmt.Printf("%+v\n", decoded)
}
Çıktı
{"id":7,"name":"Klavye","price":899.9}
{ID:9 Name:Fare Price:249.5 Discount:10 internal:}

Etiketlerin ayrıntısını ve JSON dönüşümlerini Dosyalar, I/O ve JSON dersinde işleyeceğiz.

Struct tasarlarken düşünülecekler

Bir tipi tasarlamak, alanları yan yana dizmekten fazlasıdır. Aşağıdaki sorular, kod incelemelerinde en çok tartışılan konuları özetler.

Sıfır değeri kullanışlı mı? Go'nun en güzel alışkanlıklarından biri, bir tipin hiç kurulmadan, doğrudan tanımlanarak kullanılabilmesidir. Standart kütüphanede bunun pek çok örneği vardır: bir metin biriktirici, bir kilit ya da bir tampon, tanımlandığı anda hazırdır. Kendi tiplerini tasarlarken de aynı hedefi gözet. Sıfır değeri anlamsız oluyorsa, bunu bir yapıcı fonksiyonla telafi et ve belgede açıkça belirt.

Hangi bilgi dışarıya açık olmalı? Bir alanı dışa açtığında, onun her türlü değerinin geçerli olduğunu kabul etmiş olursun. Bir kural varsa — negatif olmayacak, boş kalmayacak, diğer alanla tutarlı olacak — o alanı gizle ve değişimi metotlar üzerinden yaptır. Aksi hâlde kuralı her çağrı yerinde yeniden kontrol etmen gerekir.

Tip ne kadar büyük? Küçük ve değişmez tipler değer olarak dolaşmaya uygundur; kopyalanmaları ucuzdur, eşzamanlı kullanımda sorun çıkarmazlar. Büyük tipler ya da içinde kilit, tampon gibi paylaşılması gereken şeyler barındıran tipler ise işaretçi olarak dolaştırılmalıdır. Bu karar, tipin metot alıcılarını da belirler.

Davranışı nereye koymalı? Yeni başlayanlar her şeyi metot yapma eğilimindedir. Oysa bir işlem tipin iç durumuna ihtiyaç duymuyorsa, onu sade bir fonksiyon olarak yazmak daha iyidir: Test etmesi kolay, bağımlılığı az olur. Metotları, gerçekten tipin durumuyla ilgilenen işlemlere ayır.

İsimler ne anlatıyor? Go'da kısa isimler makbuldür ama belirsiz isimler değil. Bir alanın adı, birimini ve anlamını taşımalıdır. Süre tutan bir alanın adında biriminin geçmesi, para tutan bir alanın kuruş mu lira mı olduğunu belli etmesi, ileride saatlerce süren hata avlarını baştan önler.

Bu soruların ortak noktası şudur: Tipi kullanan kişinin ne kadar az şey bilmesi gerekiyorsa, tasarım o kadar iyidir.

Sık yapılan hatalar

  • Değer alıcılı metotla nesneyi değiştirmeye çalışmak. Kopya üzerinde çalışırsın; değişiklik kaybolur.
  • Aynı tipte değer ve işaretçi alıcıları karıştırmak. Tutarlı ol; aksi hâlde arayüz uyumu şaşırtıcı biçimde bozulur.
  • Gömmeyi kalıtım sanmak. Dog bir Animal değildir; tip uyumluluğu için arayüz gerekir.
  • Sıraya dayalı struct literal'i kullanmak. Alan eklendiğinde kod sessizce bozulur.
  • Sığ kopyanın dilim ve map alanlarını paylaştığını unutmak. Gerekirse slices.Clone veya maps.Clone kullan.
  • sync.Mutex içeren bir struct'ı kopyalamak. Kilit de kopyalanır ve koruma anlamını yitirir. go vet bunu yakalar.
  • Etiketlerde tırnak yerine düz tırnak kullanmak. Etiketler ters tırnak (`) ile yazılır ve json:"ad" biçiminde boşluksuz olmalıdır.

Alıştırmalar

Alıştırma·Öğrenci not ortalaması
Kolay

Student adında bir struct tanımla: ad, numara ve notlar dilimi. Ortalamayı hesaplayan bir metot ve öğrenciyi okunur biçimde yazdıran bir String() metodu ekle. Üç öğrenci oluşturup yazdır.

İpucu

Ortalama hesaplarken boş not dilimini unutma; sıfıra bölme paniğe yol açar.

Çözümü göster
main.go
package main

import "fmt"

type Student struct {
	Name   string
	Number int
	Grades []int
}

func (s Student) Average() float64 {
	if len(s.Grades) == 0 {
		return 0
	}
	total := 0
	for _, g := range s.Grades {
		total += g
	}
	return float64(total) / float64(len(s.Grades))
}

func (s Student) String() string {
	return fmt.Sprintf("%-8s (%d) ortalama: %.2f", s.Name, s.Number, s.Average())
}

func main() {
	students := []Student{
		{Name: "Ayşe", Number: 101, Grades: []int{90, 85, 78}},
		{Name: "Mehmet", Number: 102, Grades: []int{60, 72}},
		{Name: "Zeynep", Number: 103},
	}

	for _, s := range students {
		fmt.Println(s)
	}
}
Çıktı
Ayşe     (101) ortalama: 84.33
Mehmet   (102) ortalama: 66.00
Zeynep   (103) ortalama: 0.00

String() metodu değer alıcılıdır çünkü hiçbir şeyi değiştirmez ve struct küçüktür. fmt paketi bu metodu bulduğu için fmt.Println(s) doğrudan biçimli çıktıyı verir.

Alıştırma·Stack tipi
Orta

IntStack adında bir tip yaz: Push, Pop ve Peek metotları olsun. Pop ve Peek, yığın boşsa ikinci dönüş değeri olarak false versin. Hangi metotların işaretçi alıcı olması gerektiğine dikkat et.

İpucu

Push ve Pop yığını değiştirdiği için işaretçi alıcı olmalı; Peek ve Len değer alıcı olabilir ama tutarlılık için hepsini işaretçi yapmak da kabul edilir.

Çözümü göster
main.go
package main

import "fmt"

type IntStack struct {
	items []int
}

func (s *IntStack) Push(v int) {
	s.items = append(s.items, v)
}

func (s *IntStack) Pop() (int, bool) {
	if len(s.items) == 0 {
		return 0, false
	}
	last := len(s.items) - 1
	v := s.items[last]
	s.items = s.items[:last]
	return v, true
}

func (s *IntStack) Peek() (int, bool) {
	if len(s.items) == 0 {
		return 0, false
	}
	return s.items[len(s.items)-1], true
}

func (s *IntStack) Len() int { return len(s.items) }

func main() {
	var st IntStack // sıfır değer kullanıma hazır

	for _, v := range []int{3, 7, 11} {
		st.Push(v)
	}
	fmt.Println("uzunluk:", st.Len())

	top, _ := st.Peek()
	fmt.Println("tepe:", top)

	for {
		v, ok := st.Pop()
		if !ok {
			break
		}
		fmt.Print(v, " ")
	}
	fmt.Println()

	_, ok := st.Pop()
	fmt.Println("boş yığından çekilebildi mi:", ok)
}
Çıktı
uzunluk: 3
tepe: 11
11 7 3
boş yığından çekilebildi mi: false

Push ve Pop iç dilimi değiştirdiği için işaretçi alıcı zorunludur: append yeni bir dilim başlığı üretir ve bunun struct'a geri yazılması gerekir. Yığın veri yapısını genel hâliyle Yığın (Stack) dersinde inceleyeceğiz.

Alıştırma·Gömme ile günlük kaydı
Zor

Logger adında, ön ek tutan ve Log(mesaj string) metodu olan bir tip yaz. Ardından bu tipi gömen Service ve Worker tipleri tanımla. Service kendi Log metodunu tanımlayarak gömülü olanı gölgelesin ve mesaja fazladan bilgi eklesin. Çıktıda hem gölgeleyen hem gömülü metodun çağrılabildiğini göster.

İpucu

Gömülü metoda açıkça erişmek için s.Logger.Log(...) yazılır. Gölgeleyen metot içinde de bunu çağırabilirsin.

Çözümü göster
main.go
package main

import (
	"fmt"
	"strings"
)

type Logger struct {
	Prefix string
	lines  []string
}

func (l *Logger) Log(msg string) {
	l.lines = append(l.lines, fmt.Sprintf("[%s] %s", l.Prefix, msg))
}

func (l *Logger) Dump() string {
	return strings.Join(l.lines, "\n")
}

type Worker struct {
	Logger // gömülü: Log ve Dump doğrudan kullanılabilir
	ID     int
}

type Service struct {
	Logger
	Name string
}

// Service, gömülü Log metodunu gölgeler ve zenginleştirir.
func (s *Service) Log(msg string) {
	s.Logger.Log(s.Name + ": " + msg)
}

func main() {
	w := Worker{Logger: Logger{Prefix: "worker"}, ID: 3}
	w.Log("iş alındı")
	w.Log("iş tamamlandı")

	s := Service{Logger: Logger{Prefix: "servis"}, Name: "ödeme"}
	s.Log("istek geldi")      // gölgeleyen metot
	s.Logger.Log("ham mesaj") // gömülü metot

	fmt.Println(w.Dump())
	fmt.Println(s.Dump())
	fmt.Println("worker ön eki:", w.Prefix, "| id:", w.ID)
}
Çıktı
[worker] iş alındı
[worker] iş tamamlandı
[servis] ödeme: istek geldi
[servis] ham mesaj
worker ön eki: worker | id: 3

Gömme sayesinde Worker hiçbir kod yazmadan Log ve Dump yeteneklerini kazandı. Service ise aynı adı taşıyan kendi metodunu tanımlayarak dıştaki çağrıları yakaladı; gömülü davranışa s.Logger.Log ile erişmeye devam ediyor. Bu, kalıtımdaki "super çağrısı" fikrinin Go'daki karşılığıdır — ama aradaki ilişki "sahiptir" (has-a) ilişkisidir, "dır" (is-a) değil.

Kısa sınav

Kısa sınav

Değer alıcılı bir metot, alıcının alanını değiştirirse ne olur?

Go'da gömme (embedding) ile kalıtım arasındaki temel fark nedir?

Hangi struct == ile karşılaştırılabilir?

type Celsius float64 tipine metot eklenebilir mi?

Yapıcı fonksiyon deseni (NewX) neden kullanılır?

Bir struct'ı kopyaladığında dilim alanına ne olur?

Özet

  • Struct, ilgili alanları tek tipte toplar; alan adlarıyla yazılan literal'ler değişikliğe dayanıklıdır.
  • Alan ve metot adlarının ilk harfi görünürlüğü belirler; küçük harf kapsüllemenin Go'daki yoludur.
  • Metot, alıcısı olan bir fonksiyondur ve kendi paketindeki her isimlendirilmiş tipe eklenebilir.
  • Değer alıcı kopya üzerinde çalışır; işaretçi alıcı orijinali değiştirir ve büyük struct'larda kopyalamayı önler. Bir tipin alıcı türünü tutarlı tut.
  • Gömme, alan ve metotları yükseltir; kompozisyon sağlar ama kalıtım değildir ve tip uyumluluğu doğurmaz.
  • New... yapıcı fonksiyonları geçerlilik kurallarını tek noktada toplar ve genelde (*T, error) döndürür.
  • Tüm alanları karşılaştırılabilir olan struct'lar == ile karşılaştırılabilir ve map anahtarı olabilir.
  • Struct ataması sığ kopyadır; dilim ve map alanları paylaşılmaya devam eder.
  • Etiketler derleyici için anlamsızdır; JSON gibi kütüphaneler bunları yansıma ile okur.
Bu dersi bitirdin mi?
İlerlemen bu tarayıcıda saklanır.