1. Struct Temelleri

Go dilinde bir struct, farklı veya benzer türdeki verileri tek bir varlık içine toplamak için kullanılan bir bileşik veri türüdür. Struct'lar, geleneksel nesne yönelimli programlama dillerinden hafif farklılıklarla temel bir nesne yönelimli programlamanın önemli bir parçası olarak Go'da önemli bir konuma sahiptir.

Struct'ların ihtiyacı aşağıdaki yönlerden ortaya çıkar:

  • Kodun bakımını geliştirmek için güçlü bir şekilde ilgili değişkenleri düzenleme.
  • "Sınıfları" taklit etmek için bir araç sağlayarak kapsülleme ve birleştirme özelliklerini kolaylaştırma.
  • JSON, veritabanı kayıtları vb. veri yapılarıyla etkileşimde, struct'lar uygun bir eşleme aracı sunar.

Struct'larla veri düzenlemek, kullanıcılar, siparişler vb. gibi gerçek dünya nesne modellerinin daha net bir temsilini sağlar.

2. Struct Tanımlama

Bir struct tanımlamanın sözdizimi aşağıdaki gibidir:

type StructAdı struct {
    Alan1 AlanTürü1
    Alan2 AlanTürü2
    // ... diğer üye değişkenler
}
  • type anahtar kelimesi struct tanımını tanıtır.
  • StructAdı, Go'nun tanımlama kurallarını takip ederek genellikle ihracatlanabilir olduğunu belirtmek için büyük harfle yazılır.
  • struct anahtar kelimesi, bunun bir struct türü olduğunu gösterir.
  • Süslü parantezler {} içinde struct'ın üye değişkenleri (alanları) belirtilir, her biri kendi türünün ardından gelir.

Struct üye türleri, int, string vb. gibi temel türler ve diziler, dilimler, başka bir struct vb. gibi karmaşık türler de dahil olmak üzere herhangi bir tür olabilir.

Örneğin, bir kişiyi temsil eden bir struct'ı tanımlamak:

type Person struct {
    Name   string
    Age    int
    Emails []string // dilimler gibi karmaşık türleri içerebilir
}

Yukarıdaki kodda, Person struct'ının üç üye değişkeni vardır: string türünde Name, tamsayı türünde Age ve string dilimi türünde Emails, bu da bir kişinin birden fazla e-posta adresine sahip olabileceğini gösterir.

3. Struct Oluşturma ve Başlatma

3.1. Struct Örneği Oluşturma

Bir struct örneği oluşturmanın iki yolu vardır: doğrudan bildirim veya new anahtar kelimesi kullanarak.

Doğrudan bildirim:

var p Person

Yukarıdaki kod, p adında türü Person olan bir örnek oluşturur, burada struct'ın her üye değişkeni kendi türünün sıfır değeridir.

new anahtar kelimesi kullanarak:

p := new(Person)

new anahtar kelimesini kullanarak bir struct oluşturmak, struct'a işaret eden bir değişken oluşturur. Bu noktada p değişkeni, struct'ın üye değişkenlerinin sıfır değerlerle başlatıldığı *Person türündedir.

3.2. Struct Örneklerinin Başlatılması

Struct örnekleri oluşturulurken, alan adlarıyla veya alan adları olmadan, iki yöntemle tek seferde başlatılabilir.

Alan adlarıyla başlatma:

p := Person{
    Name:   "Alice",
    Age:    30,
    Emails: []string{"[email protected]", "[email protected]"},
}

Alan atama formuyla başlatıldığında, başlatmanın sırasının struct'ın tanımlanmasının sırasıyla aynı olması gerekmeksizin herhangi bir başlatılmamış alanın sıfır değerlerini koruyacağını unutmayın.

Alan adları olmadan başlatma:

p := Person{"Bob", 25, []string{"[email protected]"}}

Alan adları olmadan başlatıldığında, her üye değişkeninin başlangıç değerlerinin struct tanımlandığı sırada belirtildiği sırada olmasına dikkat edin ve hiçbir alan atlanamaz.

Ayrıca, struct'lar belirli alanlarla başlatılabilir ve belirtilmeyen alanlar sıfır değerler alacaktır:

p := Person{Name: "Charlie"}

Bu örnekte yalnızca Name alanı başlatılırken, Age ve Emails alanları sırasıyla kendi sıfır değerlerini alır.

4. Struct Üye Değişkenlerine Erişme

Go'da struct'ın üye değişkenlerine erişmek oldukça basittir, bunu nokta (.) operatörünü kullanarak başarabilirsiniz. Bir struct değişkeniniz varsa, üye değerlerini bu şekilde okuyabilir veya değiştirebilirsiniz.

// Anahtarlık paketini içeri aktar
import "fmt"

// Person yapısını tanımla
type Person struct {
    Name string
    Age  int
}

func main() {
    // Person türünde bir değişken oluştur
    p := Person{"Alice", 30}

    // Yapı elemanlarına eriş
    fmt.Println("İsim:", p.Name)
    fmt.Println("Yaş:", p.Age)

    // Eleman değerlerini değiştir
    p.Name = "Bob"
    p.Age = 25

    // Değiştirilmiş eleman değerlerine tekrar eriş
    fmt.Println("\nGüncellenmiş İsim:", p.Name)
    fmt.Println("Güncellenmiş Yaş:", p.Age)
}

Bu örnekte, öncelikle Person yapısını Name ve Age isimli iki eleman değişkeni ile tanımlıyoruz. Daha sonra bu yapının bir örneğini oluşturuyor ve bu elemanları nasıl okuyup değiştireceğimizi gösteriyoruz.

5 Yapı Kompozisyonu ve Gömmesi

Yapılar yalnızca bağımsız olarak var olmakla kalmaz, aynı zamanda daha karmaşık veri yapılarını oluşturmak için birbirleriyle birleştirilip gömülebilirler.

5.1 Anonim Yapılar

Anonim bir yapı, yeni bir türü açıkça bildirmez, ancak doğrudan yapı tanımını kullanır. Bu, bir yapıyı bir kez oluşturmanız ve gereksiz türlerin oluşturulmasını önleyerek sadece kullanmanız gerektiğinde kullanışlıdır.

Örnek:

package main

import "fmt"

func main() {
    // Anonim bir yapıyı tanımla ve başlat
    person := struct {
        Name string
        Age  int
    }{
        Name: "Eve",
        Age:  40,
    }

    // Anonim yapı elemanlarına eriş
    fmt.Println("İsim:", person.Name)
    fmt.Println("Yaş:", person.Age)
}

Bu örnekte, yeni bir tür oluşturmak yerine doğrudan bir yapı tanımlıyor ve bir örneğini oluşturuyoruz. Bu örnek, anonim bir yapıyı nasıl başlatacağınızı ve elemanlarına nasıl erişeceğinizi gösterir.

5.2 Yapı Gömmesi

Yapı gömme, bir yapıyı başka bir yapının elemanı olarak gömmeyi içerir. Bu, daha karmaşık veri modelleri oluşturmamıza izin verir.

Örnek:

package main

import "fmt"

// Adres yapısını tanımla
type Address struct {
    City    string
    Country string
}

// Adres yapısını Person yapısına göm
type Person struct {
    Name    string
    Age     int
    Address Address
}

func main() {
    // Bir Person örneğini başlat
    p := Person{
        Name: "Charlie",
        Age:  28,
        Address: Address{
            City:    "New York",
            Country: "USA",
        },
    }

    // Gömülü yapı elemanlarına eriş
    fmt.Println("İsim:", p.Name)
    fmt.Println("Yaş:", p.Age)
    // Adres yapısının elemanlarına eriş
    fmt.Println("Şehir:", p.Address.City)
    fmt.Println("Ülke:", p.Address.Country)
}

Bu örnekte, Address yapısını tanımlıyor ve onu Person yapısının bir elemanı olarak gömüyoruz. Bir Person örneği oluştururken aynı anda bir Address örneği de oluşturuyoruz. Nokta notasyonunu kullanarak gömülü yapı elemanlarına erişebiliriz.

6 Yapı Metotları

Nesne yönelimli programlama (OOP) özellikleri, yapı metotları aracılığıyla uygulanabilir.

6.1 Metotların Temel Kavramları

Go dilinde, geleneksel sınıf ve nesne kavramı olmasa da, benzer OOP özellikleri yapı metotlarına bağlanarak elde edilebilir. Bir yapı metodu, belirli bir yapı türüyle ilişkilendirilen özel bir fonksiyon türüdür (veya bir yapı işaretçisiyle ilişkilendirilen bir fonksiyon), bu türün kendi setini metotlara sahip olmasını sağlar.

// Basit bir yapı tanımla
type Rectangle struct {
    length, width float64
}

// Dikdörtgen yapısı için alan hesaplamak için bir metot tanımla
func (r Rectangle) Area() float64 {
    return r.length * r.width
}

Yukarıdaki kodda, Area metodu Rectangle yapısıyla ilişkilidir. Metodun tanımında (r Rectangle), alıcısıdır, bu da bu metodun Rectangle türüyle ilişkilendirildiğini belirtir. Alıcı metod adından önce görünür.

### 6.2 Değer Alıcıları ve İşaretçi Alıcıları


Metodlar alıcının türüne bağlı olarak değer alıcıları ve işaretçi alıcıları olarak kategorize edilebilir. Değer alıcıları, metodun çağrılması için bir struct kopyasını kullanır, işaretçi alıcıları ise bir struct işaretçisi kullanır ve orijinal struct'ı değiştirebilir.

```go
// Değer alıcısı ile bir metod tanımlama
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.length + r.width)
}

// Orijinal struct'ı değiştirebilen işaretçi alıcısı ile bir metod tanımlama
func (r *Rectangle) SetLength(newLength float64) {
    r.length = newLength // orijinal struct'ın değerini değiştirebilir
}

Yukarıdaki örnekte, Perimeter bir değer alıcısı metodudur, onu çağırmak Rectangle'ın değerini değiştirmez. Ancak SetLength, bir işaretçi alıcısı metodu olup, bu metodu çağırmak orijinal Rectangle örneğini etkileyecektir.

6.3 Metod Çağrısı

Bir struct'ın metodlarını, struct değişkeni ve onun işaretçisi kullanarak çağırabilirsiniz.

func main() {
    rect := Rectangle{length: 10, width: 5}

    // Değer alıcısı ile bir metod çağırma
    fmt.Println("Alan:", rect.Area())

    // Değer alıcısı ile bir metod çağırma
    fmt.Println("Çevre:", rect.Perimeter())

    // İşaretçi alıcısı ile bir metod çağırma
    rect.SetLength(20)

    // Değer alıcısı ile bir metod daha çağırma, uzunluğunun değiştiğine dikkat edin
    fmt.Println("Değiştikten sonra Alan:", rect.Area())
}

Bir metod kullanarak bir işaretçi çağırdığınızda, Go, metodunuzun değer alıcısı veya işaretçi alıcısı ile tanımlanıp tanımlanmadığına bakılmaksızın, otomatik olarak değerler ve işaretçiler arasındaki dönüşümü yönetir.

6.4 Alıcı Türü Seçimi

Metodları tanımlarken, duruma bağlı olarak bir değer alıcısı veya işaretçi alıcısı kullanıp kullanmamaya karar vermelisiniz. İşte bazı yaygın yönergeler:

  • Metodun, yapı içeriğini değiştirmesi gerekiyorsa, işaretçi alıcısı kullanın.
  • Yapı büyükse ve kopyalamanın maliyeti yüksekse, işaretçi alıcısı kullanın.
  • Metodun, alıcı işaret ettiği değeri değiştirmesini istiyorsanız, işaretçi alıcısı kullanın.
  • Verimlilik açısından, yapı içeriğini değiştirmeseniz bile büyük yapılar için işaretçi alıcısını kullanmak mantıklıdır.
  • Küçük yapılar için veya değiştirme ihtiyacı olmadan veri okuma durumunda, genellikle bir değer alıcısı kullanmak daha basit ve verimlidir.

Metodlar aracılığıyla, Go'da nesne yönelimli programlamanın bazı özelliklerini taklit edebiliriz, böylece Go'daki bu yaklaşım nesnelerin kavramını basitleştirirken ilişkili işlevleri düzenleme ve yönetme yeteneği sağlar.

7 Struct ve JSON Serileştirme

Go'da, bir yapıyı ağ iletişimi veya yapılandırma dosyası olarak JSON biçimine serileştirmek sıkça gereklidir. Benzer şekilde, JSON'u yapı örneklerine çözümlememiz de gerekir. Go'da encoding/json paketi bu işlevselliği sağlar.

Bir yapıyı JSON'a ve JSON'dan yapı örneklerine dönüştürmenin bir örneği aşağıda verilmiştir:

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

// Person yapısını tanımlayın ve struct alanları ile JSON alan isimleri arasındaki eşleşmeyi tanımlamak için json etiketlerini kullanın
type Person struct {
	Name   string   `json:"name"`
	Age    int      `json:"age"`
	Emails []string `json:"emails,omitempty"`
}

func main() {
	// Person'un yeni bir örneğini oluşturun
	p := Person{
		Name:   "John Doe",
		Age:    30,
		Emails: []string{"[email protected]", "[email protected]"},
	}

	// JSON'a seri hale getirme
	jsonData, err := json.Marshal(p)
	if err != nil {
		log.Fatalf("JSON seri hale getirme başarısız oldu: %s", err)
	}
	fmt.Printf("JSON biçimi: %s\n", jsonData)

	// Bir yapıya çözümleme
	var p2 Person
	if err := json.Unmarshal(jsonData, &p2); err != nil {
		log.Fatalf("JSON çözümleme başarısız oldu: %s", err)
	}
	fmt.Printf("Kurtarılan Yapı: %#v\n", p2)
}

Yukarıdaki kodda, "omitempty" seçeneğiyle boş veya eksikse alanın JSON'a dahil edilmeyeceğini belirten bir dilim türü alanı içeren Person yapısını tanımladık.

Bir yapı örneğini JSON'a seri hale getirmek için json.Marshal işlevini ve JSON verisini bir yapı örneğine çözümlemek için json.Unmarshal işlevini kullandık.

8 Yapılarla İlgili Gelişmiş Konular

8.1 Struct Karşılaştırma

Go'da, doğrudan iki struct örneğini karşılaştırmak mümkündür, ancak bu karşılaştırma struct içindeki alanların değerlerine dayanır. Eğer tüm alan değerleri eşitse, o zaman iki struct örneği eşit kabul edilir. Her alan tipinin karşılaştırılamayacağına dikkat edilmelidir. Örneğin, slice içeren bir struct doğrudan karşılaştırılamaz.

Aşağıda struct'ların karşılaştırılmasına bir örnek verilmiştir:

package main

import "fmt"

type Point struct {
	X, Y int
}

func main() {
	p1 := Point{1, 2}
	p2 := Point{1, 2}
	p3 := Point{1, 3}

fmt.Println("p1 == p2:", p1 == p2) // Çıktı: p1 == p2: true
fmt.Println("p1 == p3:", p1 == p3) // Çıktı: p1 == p3: false
}

Bu örnekte, p1 ve p2 tüm alan değerlerinin aynı olması nedeniyle eşit kabul edilir. Ve p3, p1'e eşit değildir çünkü Y değeri farklıdır.

8.2 Struct Kopyalama

Go'da, struct örnekleri atama yoluyla kopyalanabilir. Bu kopyanın derin kopya mı yoksa sığ kopya mı olacağı, struct içindeki alanların tiplerine bağlıdır.

Eğer struct sadece temel tipleri içeriyorsa (örneğin int, string, vb.), kopya derin bir kopya olacaktır. Eğer struct referans tiplerini içeriyorsa (örneğin slice'lar, map'ler, vb.), kopya sığ bir kopya olacaktır ve orijinal örnek ile kopyalanan örnek, referans tiplerinin belleğini paylaşacaktır.

Aşağıda bir struct'un kopyalanmasına bir örnek verilmiştir:

package main

import "fmt"

type Data struct {
Numbers []int
}

func main() {
// Data struct'ının bir örneğini başlatma
original := Data{Numbers: []int{1, 2, 3}}

// Struct'u kopyalama
copied := original

// Kopyalanan slice'ın elemanlarını değiştirme
copied.Numbers[0] = 100

// Orijinal ve kopyalanan örneklerin elemanlarını görüntüleme
fmt.Println("Orijinal:", original.Numbers) // Çıktı: Orijinal: [100 2 3]
fmt.Println("Kopyalanan:", copied.Numbers) // Çıktı: Kopyalanan: [100 2 3]
}

Örnekte gösterildiği gibi, original ve copied örnekleri aynı slice'ı paylaşırlar, bu nedenle copied içindeki slice verisini değiştirmek, aynı zamanda original içindeki slice verisini etkileyecektir.

Bu sorunu önlemek için, slice içeriğini yeni bir slice'a açıkça kopyalayarak gerçek derin kopyalama elde edebilirsiniz:

newNumbers := make([]int, len(original.Numbers))
copy(newNumbers, original.Numbers)
copied := Data{Numbers: newNumbers}

Bu şekilde, copied içinde yapılan herhangi bir değişiklik, original'i etkilemeyecektir.