İçeriğe geç
academia.sh

Ders 12 / 18

Yapılar

Yazılmayan alanların sıfır değeri, alanların sırayla yazılabilmesinin taşıdığı kırılganlık, ve gömmenin yazılmayan adları dış tipe taşıması ölçülüyor: bir gömülü tipin yöntemleri dış tipte hiç yazılmadan çağrılabiliyor. Sınırlayıcı ölçüm gömmenin kalıtım olmadığını gösteriyor: gömülü tipin yöntemi çağrıldığında alıcı gömülü değerin kendisidir, dış yapı değil.

İçindekiler

Sözlüğün anahtarı bölünmez bir değerdi: tek bir dizgi ya da tam sayı, tek bir bilgi taşıyordu. Yapı birden çok alanı bir arada tutan ve her alanına adıyla erişilen bir bileşik tiptir. Sözlükle paylaştığı bir soru var — alanlara değer verilmezse ne olur — ama yapının kendine özgü iki sorusu daha var: alanların sırası bir anlam taşıyor mu, ve bir yapı başka bir yapının içine gömüldüğünde ne oluyor?

Bu iki soru, dizi ve dilim derslerinde kurulan “kopya mı, paylaşım mı” sorusundan ayrı bir eksende duruyor. Diziler ve Dilimler dersi bir değerin ne kadarının kopyalandığını ölçüyordu; yapı da bir değerdir ve aynı kurala uyuyor. Ama yapının kendine özgü sorusu kopyalamayla ilgili değil, alanların adlandırılmasıyla ilgili: bir alanın adı, konumu ve kaynağı — yazılıp yazılmadığı — birbirinden bağımsız üç ayrı bilgi taşıyor.

Yazılmayan Alanların Sıfır Değeri

Bir yapı değişkeni ilk değeri verilmeden tanımlandığında, her alanı kendi tipinin sıfır değerini alır — dizgi alanı boş dizgi, tam sayı alanı sıfır, mantıksal alan false. Bu, Sıfır Değerler dersinin bağlayıcı 8/4/3 dağılımındaki tanımlı sınıfına giren durumlardan biridir; burada tek cümleyle anılıyor, dağılımın tamamı yeniden kurulmuyor.

// sifir.go — yazilmayan yapi alanlarinin sifir degeri
package main

import "fmt"

type Kayit struct {
	Ad    string
	Sayi  int
	Etkin bool
}

func main() {
	var bos Kayit
	fmt.Printf("bos kayit: %q %d %t\n", bos.Ad, bos.Sayi, bos.Etkin)
}
bos kayit: "" 0 false

Üç alan, üç ayrı tipin sıfır değerini taşıyor ve üçü de tek bir satırda, var bos Kayit yazımından geliyor. Hiçbir alana açıkça değer verilmedi; yapının kendisi hangi alanın hangi tipte olduğunu bildiği için, her birinin sıfır değerini kendisi dolduruyor. Bu, bir yapı değişkeninin kurulması için ayrı bir yapıcı çağrısı gerekmediği anlamına geliyor — var bos Kayit kendi başına kullanılabilir bir değer üretiyor, hiçbir alanı “henüz kurulmamış” durumda bırakmıyor.

Bu doldurma gömülü tipler için de işliyor: bir yapı başka bir yapıyı gömüyorsa, gömülü alanın kendisi de kendi sıfır değerini alıyor.

// gomulu-sifir.go — gomulu tipin sifir degeri de kendiliginden doluyor
package main

import "fmt"

type Taban struct {
	Ad string
}

type Genisletilmis struct {
	Taban
	Sifre string
}

func main() {
	var g Genisletilmis
	fmt.Printf("bos genisletilmis: Ad=%q Sifre=%q\n", g.Ad, g.Sifre)
}
bos genisletilmis: Ad="" Sifre=""

g.Ad burada Tabanın alanı, g.Sifre Genisletilmisin kendi alanı; ikisi de tek bir var g Genisletilmis satırından geldi. Sıfır değer doldurma iç içe geçmiş yapılarda da duruyor, çünkü kural her düzeyde aynı: her alan kendi tipinin sıfır değerini alıyor, alanın kendisi başka bir yapı olsa bile.

Yapı da tıpkı dizi gibi bir değerdir ve atandığında tamamen kopyalanır — bu ders boyunca görülecek her yapı örneği bu kuralın altında duruyor. kopya := kaynak yazıldıktan sonra kopya üzerinde yapılan bir değişiklik kaynakı hiç etkilemiyor, çünkü iki değişken artık birbirinden bağımsız iki kopya taşıyor. Bu davranış İşaretçiler dersinde asıl önemini kazanacak: bir yapıyı değiştirmek isteyen bir işlev, kendisine bir kopya geçirildiğinde çağırandaki hiçbir şeyi değiştiremiyor.

Alanların Sırayla Yazılabilmesinin Kırılganlığı

Bir yapı değişmezi iki biçimde yazılabilir: alan adı yazmadan, yalnızca sırayla (pozisyonel), ya da her değeri kendi alan adıyla eşleyerek (anahtarlı). Pozisyonel yazım kısadır ama bir varsayıma dayanır — yazıldığı sıradaki alan dizilişinin, yapının güncel tanımıyla aynı kalacağı varsayımına. Bu varsayım kırıldığında derleyici, iki alan aynı tipteyse, hiçbir uyarı vermez.

// siralama.go — pozisyonel yazim alan sirasina bagli, anahtarli yazim degil
package main

import "fmt"

type AdresV1 struct {
	Sehir string
	Ulke  string
}

type AdresV2 struct { // alan sirasi degisti: Ulke basa alindi
	Ulke  string
	Sehir string
}

func main() {
	v1 := AdresV1{"Ankara", "Turkiye"}
	v2 := AdresV2{"Ankara", "Turkiye"} // kod guncellenmedi, degerler V1'e gore yazildi
	fmt.Println("V1 pozisyonel :", "Sehir=", v1.Sehir, " Ulke=", v1.Ulke)
	fmt.Println("V2 pozisyonel :", "Ulke=", v2.Ulke, " Sehir=", v2.Sehir)
	v2Dogru := AdresV2{Sehir: "Ankara", Ulke: "Turkiye"}
	fmt.Println("V2 anahtarli  :", "Ulke=", v2Dogru.Ulke, " Sehir=", v2Dogru.Sehir)
}
V1 pozisyonel : Sehir= Ankara  Ulke= Turkiye
V2 pozisyonel : Ulke= Ankara  Sehir= Turkiye
V2 anahtarli  : Ulke= Turkiye  Sehir= Ankara

AdresV2, AdresV1 ile aynı iki alanı taşıyor, yalnızca sırası değişmiş: Ulke başa alınmış. AdresV2{"Ankara", "Turkiye"} satırı hiç güncellenmeden kalmış — hâlâ eski sırayı (önce şehir, sonra ülke) varsayıyor. Derleyici burada hiçbir şeyi reddetmiyor, çünkü iki alan da string tipinde; pozisyonel yazım yalnızca konuma bakıyor, adı hiç sormuyor. Sonuç: Ankara şimdi Ulke alanına, Turkiye Sehir alanına yazılıyor — değerler yer değiştirmiş. Anahtarlı yazım aynı iki değeri, hangi sırada yazılırsa yazılsın, doğru alana koyuyor; v2Dogru alan sırasından tamamen bağımsız.

Bu kırılganlık yalnızca alan sırası değiştiğinde görünür oluyor, ve iki alan aynı tipte olduğu sürece derleyici bunu asla yakalamıyor — reddedilen dört durumun (kullanılmayan değişken, kullanılmayan içe aktarım, dönüşün yazılmaması, örtük tip dönüşümü) hiçbiri bu durumu kapsamıyor. Alanların tipi farklı olsaydı pozisyonel yazım derlenmezdi ve hata derleme zamanında görünürdü; tipin aynı kalması, hatayı çalışma zamanına, hatta hiçbir zaman fark edilmemeye kadar erteliyor.

BT11.AdresV1 ve AdresV2 burada bilinçli olarak iki alanlı ve iki alanı da aynı tipte kuruldu; amaç, derleyicinin yakalayamayacağı en dar durumu göstermek. Üçüncü bir alan farklı tipte eklenseydi pozisyonel yazım o alanda derlenmezdi ve kırılganlık görünmezdi.

Bu durum, gerçek bir kod tabanında bir yapının alan sırası sonradan değiştirildiğinde ortaya çıkıyor: yapının tanımı güncellenir ama onu pozisyonel yazımla kuran her satır otomatik olarak güncellenmiyor. Derleyici hiçbir uyarı vermediği için bu, çalıştırılıp sonucu gözle kontrol edilmeden fark edilmesi zor bir kusur sınıfı oluyor — anahtarlı yazım bu sınıfı tamamen ortadan kaldırıyor, çünkü hangi değerin hangi alana gittiği yazımın kendisinde durur, alan sırasına hiç bağlı değildir.

Karşılaştırılabilirlik Alanlardan Miras Kalır

Diziler ve Dilimler dersi bir dizinin == ile karşılaştırılabildiğini, bir dilimin karşılaştırılamadığını ölçmüş ve bunun yapılara da yansıyacağını bir cümleyle bırakmıştı. Bir yapının karşılaştırılabilir olup olmadığı, kendi kuralı değil, alanlarının karşılaştırılabilirliğinin toplamıdır.

BT12. — Bu ölçüm bir derleyici çağrısıyla koşturuluyor ve yalnızca “derlendi” ya da “derlenmedi” sınıfı basılıyor; iki tip tanımı da aynı görünen tek alanı taşıyor, farkı yalnızca o alanın tipi belirliyor.

// karsilastirma.go — yapinin karsilastirilabilirligi alanlarindan miras kalir
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

func gecerMi(kaynak string) bool {
	dizin, err := os.MkdirTemp("", "sinama")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dizin)
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dizin, "kod.go"), []byte(kaynak), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

func main() {
	diziAlanli := `package sinama
	type K struct { Etiketler [2]string }
	func F() bool {
		a := K{[2]string{"x", "y"}}
		b := K{[2]string{"x", "y"}}
		return a == b
	}`
	dilimAlanli := `package sinama
	type K struct { Etiketler []string }
	func F() bool {
		a := K{[]string{"x", "y"}}
		b := K{[]string{"x", "y"}}
		return a == b
	}`
	fmt.Println("yalniz dizi alanli yapi:", gecerMi(diziAlanli))
	fmt.Println("dilim alanli yapi      :", gecerMi(dilimAlanli))
}
yalniz dizi alanli yapi: true
dilim alanli yapi      : false

İki yapı da tek bir alan taşıyor, ikisi de görünüşte aynı bilgiyi (iki etiket) tutuyor. Fark alanın tipinde: biri dizi, öteki dilim. Dizi alanlı yapı == ile karşılaştırılabiliyor çünkü dizi kendisi karşılaştırılabilir; dilim alanlı yapı karşılaştırılamıyor çünkü dilim karşılaştırılamaz ve bu özellik yapıya aynen geçiyor. Yapı kendi başına bir karşılaştırma kuralı tanımlamıyor — alanlarının kuralını devralıyor.

Gömme: Yazılmayan Adların Dış Tipe Taşınması

Bir yapı, adlandırılmış bir alan yerine yalnızca başka bir tipin adını taşıyan bir alan içerebilir; buna gömme deniyor. Gömülü tipin alanları ve yöntemleri, dış tipte hiç yazılmadan, dış tip üzerinden doğrudan çağrılabilir hâle geliyor. Bu, tek bir alan ile birden çok davranışın aynı anda dış tipe taşınmasını sağlıyor; aşağıdaki ölçümde Tabanın iki yöntemi, Genisletilmis tarafında hiç yazılmadan görünüyor.

// gomme.go — gomulu tipin adlari dis tipe yazilmadan tasinir
package main

import "fmt"

type Taban struct {
	Ad string
}

func (t Taban) Selamla() string {
	return "Merhaba, " + t.Ad
}

func (t Taban) KimKonusuyor() string {
	return fmt.Sprintf("%T diyor ki: %s", t, t.Ad)
}

type Genisletilmis struct {
	Taban
	Sifre string
}

func (g Genisletilmis) Dogrula(s string) bool {
	return g.Sifre == s
}

func main() {
	g := Genisletilmis{Taban: Taban{Ad: "Ayse"}, Sifre: "gizli"}
	fmt.Println("gomulu alan dogrudan   :", g.Ad)
	fmt.Println("gomulu yontem dogrudan :", g.Selamla())
	fmt.Println("kendi yontemi          :", g.Dogrula("gizli"))
}
gomulu alan dogrudan   : Ayse
gomulu yontem dogrudan : Merhaba, Ayse
kendi yontemi          : true

Genisletilmis yapısında Ad diye yazılmış bir alan yok, Selamla diye yazılmış bir yöntem de yok — ikisi de Tabana ait. Ama g.Ad ve g.Selamla() çalışıyor, sanki Genisletilmis bu ikisini kendisi tanımlamış gibi. Tabanın iki yöntemi (Selamla, KimKonusuyor) Genisletilmise yazılmadan taşındı; Genisletilmisin kendi yazdığı tek yöntem Dogrula. Gömülü bir tipin adları, gömen tipin adlarıymış gibi görünüyor — sözlükteki bir anahtarın aksine, burada erişim adla değil, doğrudan alan seçimiyle oluyor.

BT13. — Taşınan yöntem sayısı burada ikidir (Selamla, KimKonusuyor); sayı Tabana kaç yöntem yazıldığına bağlıdır ve gömen tipin kendi yazdığı yöntem sayısından bağımsızdır. Genisletilmis sıfır yöntem yazsaydı bile ikisi yine görünür olurdu.

Gömme, bir yapının birden çok tipi gömmesiyle de çalışıyor ve taşınan adlar üst üste birikir; iki gömülü tipin aynı adda bir alanı ya da yöntemi varsa, bu ders o çakışmayı sınamıyor ama derleyici böyle bir çakışmada dış tipten doğrudan erişimi reddediyor — hangi gömülü tipin kastedildiği açıkça yazılmak zorunda kalıyor. Bu ders yalnızca tek gömme düzeyini ölçüyor; adı ve yöntemi çakışmayan tek bir gömülü tip.

Sınırlayıcı Ölçüm: Gömme Kalıtım Değildir

Gömme, çağrı yazımı düzeyinde kalıtıma benziyor ama aynı şey değil. Fark, çağrılan yöntemin içindeki alıcının kim olduğunda yatıyor.

// alici.go — gomulu yontem cagrildiginda alici gomulu degerin kendisidir
package main

import "fmt"

type Taban struct {
	Ad string
}

func (t Taban) KimKonusuyor() string {
	return fmt.Sprintf("%T diyor ki: %s", t, t.Ad)
}

type Genisletilmis struct {
	Taban
	Sifre string
}

func main() {
	g := Genisletilmis{Taban: Taban{Ad: "Ayse"}, Sifre: "gizli"}
	fmt.Println("aliciyi soyleyen yontem:", g.KimKonusuyor())
}
aliciyi soyleyen yontem: main.Taban diyor ki: Ayse

g.KimKonusuyor() çağrısı Genisletilmis üzerinden yapıldı, ama yöntemin içinde %T ile basılan tip main.Tabanmain.Genisletilmis değil. Bu, alıcının gnin kendisi değil, gnin içindeki gömülü Taban değeri olduğunu kanıtlıyor. Kalıtımı olan dillerde alt sınıf üzerinden çağrılan bir üst sınıf yöntemi, kendini çağıran nesnenin tam kimliğini görebilir; Go’da gömülü bir yöntem kendi tipinin dışına hiç çıkamıyor — Tabanın hiçbir yöntemi Genisletilmisin var olduğunu bilmiyor, çünkü Taban tanımlandığı anda Genisletilmis diye bir tip henüz yoktu. Gömme, derleyicinin g.KimKonusuyor() yazımını g.Taban.KimKonusuyor()e otomatik olarak açmasıdır; yeni bir çağrı biçimi değil, var olan bir çağrının kısaltılmış yazımı.

Bu, dış tip gömülü tiple aynı adda bir alan tanımladığında daha da belirginleşiyor.

// golge.go — dis tipin ayni adli alani gomulu olani golgeler, ama yontem etkilenmez
package main

import "fmt"

type Taban struct {
	Ad string
}

func (t Taban) Selamla() string {
	return "Taban diyor: Merhaba " + t.Ad
}

type Genisletilmis struct {
	Taban
	Ad string // dis tipte ayni adli alan, gomulu olani golgeler
}

func main() {
	g := Genisletilmis{Taban: Taban{Ad: "GomuluAd"}, Ad: "DisAd"}
	fmt.Println("g.Ad (dogrudan)      :", g.Ad)
	fmt.Println("g.Taban.Ad (acikca)  :", g.Taban.Ad)
	fmt.Println("g.Selamla() (gomulu) :", g.Selamla())
}
g.Ad (dogrudan)      : DisAd
g.Taban.Ad (acikca)  : GomuluAd
g.Selamla() (gomulu) : Taban diyor: Merhaba GomuluAd

Genisletilmis kendi Ad alanını tanımladığında bu alan gömülü Taban.Adı gölgeliyor: g.Ad artık dış tipin kendi alanına gidiyor ("DisAd"), gömülü alana ulaşmak için açıkça g.Taban.Ad yazmak gerekiyor. Ama g.Selamla() çağrısı hâlâ "GomuluAd"ı kullanıyor — çünkü Selamla Tabana ait ve Tabanın kendi Adı hiç gölgelenmedi, yalnızca dış tipten görünürlüğü değişti. Bu, alıcının gömülü değerin kendisi olduğunun bir başka kanıtı: dış tipin aynı adı yeniden kullanması, gömülü tipin kendi içindeki hiçbir şeyi değiştirmiyor.

Özet

  • Yazılmayan bir yapı alanı kendi tipinin sıfır değerini alır; bu, Sıfır Değerler dersindeki bağlayıcı dağılımın tanımlı sınıfına giren bir örnektir.
  • Pozisyonel yapı değişmezleri alan sırasına bağlıdır; iki alan aynı tipteyse sıra değiştiğinde derleyici hiçbir şey yakalamaz, değerler sessizce yer değiştirir. Anahtarlı yazım bu kırılganlığı taşımaz.
  • Bir yapının == ile karşılaştırılabilirliği kendi kuralı değildir; her alanının karşılaştırılabilirliğinin toplamıdır — bir dilim alan yapıyı karşılaştırılamaz yapar.
  • Gömme, gömülü tipin alanlarını ve yöntemlerini dış tipe yazılmadan taşır; erişim doğrudan alan seçimiyle olur.
  • Gömülü bir yöntem çağrıldığında alıcı gömülü değerin kendisidir, dış yapı değil; gömme kalıtım değildir, derleyicinin otomatik açtığı bir kısaltmadır.

Sonraki Adım

Bu derste Taban{Ad: "Ayse"} yazımı bir değerdi ve g.Ade erişim doğrudan o değer üzerinden oldu. Ama bir yapıyı değiştirmek isteyen bir yöntem, kendi kopyası üzerinde çalışırsa çağıranı hiç etkilemez — tıpkı dizinin fonksiyona geçtiğinde kopyalanması gibi. Sıradaki ders bu soruna bakıyor: bir değişkenin adresini almadan bir yapıyı değiştirmek neden mümkün değil, ve adres alındığında hangi çağrılar çağıranı etkiliyor?

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat