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.