Go: Struct Pointer ve Dereferencing

Selam, Go’nun temel özelliklerinden Struct ile pointerları nasıl kullanırız bir bakalım.

Go’da Struct structure sözcüğünden gelmekte ve C’de olduğu gibi (C++’ta da vardır. Bu yapı yerine OOP yaklaşımına uygun olarak class‘lar kullanılmakta, struct’ın farkı class’lara göre default olarak public olmasıdır.) birbiri ile ilişkili olan verileri ifade etmede kullanılır. Yani bir kişi ile ilgili verileri tutmak istediğinizi varsayalım, bu kişinin adını, yaşını, telefon numarasını ve e-posta adresini tutmak için Struct yapısı kullanılır. Aşağıdaki gibi bir yapı ile oluşturulur ve içine Go’da tanımlı tüm veri tipleri ile değişkenler atayabilirsiniz.

type Kisi struct {
    adi string
    yasi  int
}

Struct’ımızı oluşturduktan sonra şöyle kullanabiliriz, örnek olarak:

func yeniKisi(name string) *Kisi {
    p := Kisi{adi: name}
    p.yasi = 42
    return &p
}

Burada yeniKisi fonksiyonumuzun argümanı olarak name değerini string olarak verdik ve Kisi struct’ını da pointer (Asteriks ifadesi ile pointer’ı refere etmiş olduk.) olarak ekledik. Dolayısıyla Kisi struct’ı ile oluşturulan herhangi bir değere bu fonksiyon ile name değişkenine bağlı olarak bir adi değeri ve yasi değerini de sabit olarak 42 tamsayısını atamış oluruz. return satırında da bu yeni kişiyi refere ederek fonksiyonun çıktısı olarak sunmuş oluyoruz.

Gelelim pointer ile ve pointer’sız nasıl çıktılar elde ediyoruz.

func main() {

    fmt.Println(Kisi{"Ali", 20})
    fmt.Println(Kisi{adi: "Ayse", yasi: 30})
    fmt.Println(Kisi{adi: "Mehmet"})
    fmt.Println(&Kisi{adi: "Ahmet", yasi: 40})
    fmt.Println(yeniKisi("Haydar"))

    s := Kisi{adi: "Yavuz", yasi: 50}
    fmt.Println(s.adi)

    sp := &s
    fmt.Println(sp.yasi)

    sp.yasi = 51
    fmt.Println(sp.yasi)
    fmt.Println(s.yasi)

    k := yeniKisi("Ozan")
    y := &k
    fmt.Println(y)
    fmt.Println(&k)
    fmt.Println(&y)
    fmt.Println(*y)

    fmt.Println(k.adi)
    k.adi = "Merve"
    fmt.Println(k.adi)
    fmt.Println(*y)
}

Tahmin edelim, ilk çıktımız {"Ali" 20} şeklinde olacak, evet doğru. İkincisi ise {"Ayse" 30} olacak, kesinlikle öyle. Üçüncü de benzer şekilde olacak, burada unutulmaması gereken yasi değeri girilmediği için Mehmet’in yaşı 0 olarak girilecektir. 6. satırda & (ampersand) ifadesi referencing yani pointer değişkeni ile göstermeyi kastetmektedir. Şöyle ki Ahmet (40) kişisi hafızada bir yer işgal ediyor ve bizde onun referansını görüyoruz. 7. satırda da aslında 6. satırda olduğu gibi fakat bu kez bizim yazdığımız yeniKisi fonksiyonu ile pointer refere ediyoruz.

Gelelim 9. satıra, struct ile değerler s değişkenine atanıyor. Bir alttaki satirda da yalnızca bu değişkene atanmış adi değeri okunuyor, tahmin edersiniz ki dönen değer Yavuz olacaktır. 12. satırda sp değişkenini oluşturup diyoruz ki senin değerin s değişkeninin hafızadaki adresi, yani aslında s’nin kendisi değil s’nin değerlerinin olduğu hafıza adresi. sp’de tanımlı bu bilgi ile ilgili adresteki kişinin yaşını öğreniyoruz, yani dönen değer 50 olacaktır. 15. satırda sp’de tanımlı yaş bilgisini 51 olarak değiştiriyoruz. sp.yasi diyerek değiştirdiğimiz değeri kontrol ediyoruz, çıktı 51. sp için refere ettiğimiz s değerimizin yaşını kontrol edersek o da 51 olarak dönecektir. Çünkü biz s’nin belirttiği hafızayı sp ile refere ederek onun üzerinden değiştirdik. Pointerların avantajı da bu noktada devamlı olarak verileri başka değişkenlere aktarmadan bu tip işlemleri yapabilmemizi sağlamasıdır, yani hafızayı daha verimli kullanmamızı sağlamaktadır.

Üst kısımda tanımladığımız yeniKisi fonksiyonu ile bir kişi oluşturalım, hatırlayacak olursak Kisi struct’ını refere ederek yeni bir kişi oluşturuyordu. k değişkeni ile bu fonksiyonu çağırırsak ve bu değeri y değişkeni ile refere edersek, y’nin çıktısı ne olur? y bir hafıza adresinin referansı olduğundan bu programın çalıştığı hafızada refere ettiği adresi gösterecektir. Örneğin, 0xc00000e030 gibi. Bir alttaki satırda herhangi bir değişkene atamadan k’nın hafızadaki adresini öğrenmek istersek nasıl bir çıktı alırız gibi düşünebilirsiniz. Evet bir önceki satırda olduğu adresle aynı adresi vermeli, 0xc00000e030. Peki fmt.Println(&y) bize ne anlatmaktadır, aslında çok basit o da y’nin adresini refere etmektedir, y’nin karşılığı hafızada bir adresti hatırlayın. y değişkeninin adresi örneğin şöyle bir şey olabilir: 0xc00000e038. Kafanız yeterince karıştıysa fmt.Println(*y) satırı da bizim refere ettiğimiz değeri verecektir. Yani &{Ozan 42} çıktısını görürüz.

Bir de k değişkeninde yarattığımız Kisi tipindeki kişinin adını değiştirelim, Merve yapalım. k’nın adını öğrenelim, çıktımız "Merve" olacaktır. Peki bizim k için refere ettiğimiz y değeri ne oldu bu durumda? Evet o da değişti ve Merve oldu, son satırda da &{Merve 42} çıktısını elde etmiş olduk.

Şayet bu pointer’ın alt değerini almak isteseydik (*y).adi yazmamız gerekirdi.

func main() {
  (*y).adi = "Mahmut"
  fmt.Println((*y).adi)
  fmt.Println(k.adi)
}

Burada da çıktılarımız Mahmut ve Mahmut olacaktır. Sonuç olarak bir değişkenin değerlerini o değişkeni kullanmadan da olsa yapabiliyoruz. & (ampersand) ile refere edip * (asteriks) ile defere etmeyi başardık.

Kodların tamamını repl.it’te bulabilirsiniz.