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.Taban — main.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.