1. Go Programlama Dili Temelleri
Kitabın önceki bölümünde Go programlama diline genel bir bakış yaparak fikir sahibi olmaya çalışmıştık. Bu
bölümde ise, Go programlama dilinin temel özelliklerine odaklanarak programlama dünyasına giriyoruz.
Bu bölümde, her programlama dili için önemli olan temel programatik nesneler ve dil özelliklerini inceleyeceğiz.
Akış Kontrol
Programlama dillerinin en temel öğelerinden olan akış kontrol mekanizmaları, geliştirilen yazılımın kod
çalıştırma akışını yönetebilmek için kullanılır.
Go dilinde iki temel akış kontrol mekanizması vardır. Bunlar:
- if
- switch
Diğer programlama dillerine göre Go dilindeki if ve switch deyimlerinin daha esnek ve efektif kullanılması
nedeniyle, Go dilinde akış kontrolü yapmak hem kolay hem de daha yönetilebilir bir yapıya sahiptir.
if
En sık kullanacağımız akış kontrolü if deyimidir.
Örnek:
foo := 1
if foo == 1 {
println("bar")
}
Yukarıdaki en basit halini uyguladığımız if kullanımını biraz inceleyelim. foo adında bir kısa değişken
tanımlayarak bu değişkene bir(1) değerini atadık. Sonrasında if akış kontrolüne foo değişkenini vererek “Eğer bu
değişkenin değeri 1 ise, if bloğuna gir” tanımını yapmış olduk! Bu örnek uygulamayı çalıştırdığımızda foo’nun
değeri 1 olduğu için ekranda bar değerini gösterecektir.
Not : foo ve bar değerlerinin bir anlamı yoktur. Bu değerler, yazılım dünyasında dummy data olarak kullanılır.
if gibi akış kontrollerini kullanmanın temel amacı, true ya da false olarak geri dönüş yapan herhangi bir işlemin
sonucuna göre işlem yapabilmektir. Yukarıdaki örnekte tek bir olasılığı programlayarak foo’nun değerine göre
bir altındaki bloğa girişini kontrol ettik. Ancak bu yöntem gerçek uygulamalarda kullanılsa da, genelde yeterli
olmaz. Bizim if akış kontrol üzerinde birden fazla işlemi kontrol ederek uygulamanın akışını değiştirebiliyor
olmamız gerekiyor. Bu akış seçeneklerini çoğaltma işlemini Go dilinde else ve else if ile yapabiliyoruz.
else bloğu : if bloğu şartı sağlanmazsa ve eğer else if bloğu kullanılmadıysa, else bloğu mutlaka çalışır.
Örnek:
age := 15
if age > 13 {
fmt.Println("Yaşınız 13'den büyük olduğu için kayıt olabilirsiniz.")
} else {
cihanozhan.com
2. fmt.Println("Sosyal platformlar senin için uygun değil!")
}
Örnek çıktısı:
Yukarıdaki örnekte kullanılan yaş değerini 13 olarak değiştirerek tekrar deneyelim.
Örnek çıktısı:
Şimdi de, yukarıdaki örneğe else if bloğunu ekleyerek geliştirelim.
Örnek:
age := 13
if age > 13 {
fmt.Println("Yaşınız 13'den büyük olduğu için kayıt olabilirsiniz.")
} else if age < 13 {
fmt.Println("Sosyal platformlar senin için uygun değil.")
} else {
fmt.Println("Sanırım senin yaşın tam sınırda! Bi düşünelim!")
}
Örnek çıktısı:
Her programlama dilinin akış kontrollerine bakış açısı biraz farklılık gösterebilir. Go dili if akış kontrol
mekanizmasına ek bir yeteneği daha dahil ederek if bloğunun başlık kısmında değişken tanımlamamıza izin
veriyor. Böylelikle, sadece if bloğu içerisinde geçerli olacak değişkenler tanımlayabiliriz.
if foo := 2; foo == 1 {
println("bar")
} else {
println("buz")
}
Yukarıdaki örnekteki if bloğunda tanımlanan foo değişkeni sadece if bloğunda geçerlidir. Bu değişkene
dışarıdan ulaşılamaz.
Örnek çıktısı:
buz
Bu konuyla ilgili akıllara bir soru gelebilir. Eğer biz if bloğundan önce de foo adında bir değişken tanımlarsak,
bu iki aynı isme sahip ama farklı olan değişkenler uygulamanın çalışmasını nasıl etkiler?
Go dili, bu durumu bir hata olarak görmez ve derleme işlemini gerçekleştirir. Çünkü, if bloğundaki değişken ile
dışarıda tanımlanan değişkenlerin çalıştığı etki alanları birbirinden farklıdır.
Örnek:
cihanozhan.com
3. foo := 5
if foo := 2; foo == 1 {
println("bar")
} else {
println("İç foo: " + " buz")
println("İç foo: " + strconv.Itoa(foo))
}
println("Dış foo: " + strconv.Itoa(foo))
Örnek çıktısı:
if akış kontrol mekanizmasının daha kısa kod yazmak için oluşturulan yöntemini de kullanabiliriz.
Örnek:
func main() {
if v, err := justDoIt(); err == nil {
fmt.Println("value: ", v)
}
}
func justDoIt() (string, error) {
return "", fmt.Errorf("error")
}
Yukarıdaki yöntemde if akış kontrol mekanizmasında değer değişkeni olan v ile birlikte hata yönetimi için
kullanacağımız err nesnesini de tanımlıyoruz. Sonrasında ise hata kontrolü yapacağımız işlemi gerçekleştirecek
olan justDoIt() isimli fonksiyonu err değişkenine atıyoruz. Eğer bu fonksiyon herhangi bir hata mesajı dönerse
if scope başlığında tanımladığımız err nesnesi nil olmayacaktır. Eğer herhangi bir hata ile karşılaşmazsak if
içerisindeki kodlar çalıştırılarak uygulama doğal yaşam döngüsüne devam edecektir.
En son uyguladığımız if örneğini birçok açık kaynak projede sık sık görebilirsiniz. Çünkü bu yöntem kod
kısaltma amacıyla çok sık kullanılır. Ancak bazen de tam aksine kod okunabilirliğini azalttığı için bu yöntem
tercih edilmeyebilir.
switch
Programatik olarak, genellikle switch deyimi if deyimiyle aynı işlemleri yapabilir. switch deyiminin en temel
farkı, if deyiminden daha sade bir yapıya sahip olmasıdır.
Bazı kaynaklarda bu akış kontrol mekanizmasına switch case denildiğini de görebilirsiniz. Bunun nedeni, switch
ile birlikte kullanılması gereken case bloklarıdır.
Örnek:
foo := 2
switch {
case foo == 1:
println("bir")
cihanozhan.com
4. case foo == 2:
println("iki")
case foo > 3:
println("Üç'ten büyük bir değer")
}
Örnek çıktısı:
İki
Yukarıdaki örnekte foo değişkenine 2 değerini atadık. Sonrasında ise her case bloğu ile bu değişkenin değerini
kontrol ederek girilecek bloğu(yapılacak işlemi) belirledik.
Not : Switch bloğundaki case alanlarıyla, if bloğundaki else if alanları aynı amaçla kullanılır.
Şimdi de, switch ile biraz daha karmaşık bir işlem yaparak, öğrenci notuna göre sınav derecesini ölçelim. Ayrıca
aşağıdaki örnekte switch içerisinde default alanları kullanmayı da inceleyeceğiz.
switch içerisinde default bloğu ne işe yarar?
default bloğunun amacı, if akış kontrolündeki else ile aynıdır. Eğer switch tanımındaki veri ile bloktaki
case’lerden hiç biri eşleşmezse, uygulama default bloğuna girecektir.
Örnek:
var puan float64
fmt.Print("Son sınav puanınızı giriniz: ")
fmt.Scanf("%v", &puan)
switch {
case puan <= 59:
fmt.Println("Dereceniz F")
case puan <= 69:
fmt.Println("Dereceniz D")
case puan <= 79:
fmt.Println("Dereceniz C")
case puan <= 89:
fmt.Println("Dereceniz B")
case puan <= 100:
fmt.Println("Dereceniz A")
default:
fmt.Println("Lütfen 100'den küçük ya da eşit bir değer giriniz.")
}
Not : Go dosyanıza fmt paketini import etmeyi unutmayın!
Örnek çıktısı: Uygulamaya 50 notunu girdik
Dereceniz F
Örnek çıktısı: Uygulamaya 101 notunu girdik
Lütfen 100'den küçük ya da eşit bir değer giriniz.
cihanozhan.com
5. Konu Ödevi : Şu ana kadar if ve switch akış kontrollerinin kullanımını öğrendiniz. Ancak switch ile yaptığımız
not uygulamasını if ile yapmamıştık! Uygulama ödevi olarak, bu not hesaplama uygulamasını if ile yapınız.
Go dili, if akış kontrolünde olduğu gibi, switch akış kontrolünde de switch deyiminin başlık kısmında değişken
tanımlayarak sadece switch içerisinde geçerli olmak üzere kullanılmasına izin veriyor.
Örnek:
switch foo := 1; foo {
case 1:
println("bir")
case 2:
println("iki")
}
İfadesiz switch kullanımı nedir ve nasıl kullanılır?
İfadesiz switch kullanımı switch’in if akış kontrolü gibi karmaşık işlemlere cevap verebilmesini sağlayan bir
switch genişletmesidir.
number := 75
switch {
case number >= 0 && number <= 50:
fmt.Println("number değeri 0'dan büyük ve 50'den küçüktür")
case number >= 51 && number <= 100:
fmt.Println("number değeri 51'den büyük ve 100'den küçüktür")
case number >= 101:
fmt.Println("number değeri 100'den büyüktür")
}
Peki, switch içerisinde sadece sayısal değerleri mi kontrol edebiliyoruz? Hayır! Metinsel veri de kullanılabilir.
Örnek: switch içerisinde çoklu ifade kullanımı
page := "page2"
switch page {
case "index":
fmt.Println("Ana sayfaya yönlendir.")
case "about":
fmt.Println("Hakkımda sayfasına yönlendir.")
case "page1", "page2", "page3":
fmt.Println("Bu sayfaları yayından kaldırdık!") // Page not found!
default:
fmt.Println("Bu görevi tanımlayamadık!")
}
Örnek çıktısı:
Bu sayfaları yayından kaldırdık!
Yukarıdaki örnekte görüldüğü üzere, bazen ihtiyaç duyacağımız metinsel verilerin switch ile kullanımı da Go
dilinde mümkündür.
cihanozhan.com
6. Son olarak, switch ile gerçek dünya uygulamalarında kullanabileceğimiz örneklerden birini daha yapalım. Go
dilinin işletim sistemleriyle ilgili açıklamalar yaparken, Go uygulamasının üzerinde çalıştığı işletim sistemi ve
platformu tanıyabildiği ve buna göre işlemler yapabildiğinden bahsetmiştik. Bu işlemi basit bir şekilde
örnekleyelim.
Örnek:
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
}
Örnek çıktısı:
Go runs on windows.
Yukarıdaki uygulamanın amacı çok nettir. Go’nun üzerinde çalıştığı işletim sistemini bulmak ve bu işletim
sistemine özgü metodu çalıştırmaktır. Ben Windows işletim sistemi üzerinde çalıştığım için de uygulamanın
çıktısı yukarıdaki gibi oldu.
Bu örnek Go dili tur sayfalarında da kullanılan temel bir örnektir. İlgili sayfayı ziyaret etmek için:
https://tour.golang.org/flowcontrol/9
Fallthrough
…
cihanozhan.com
7. Diziler
Geliştirilen tüm yazılımların bazı genel gereksinimleri vardır. Bunlardan birisi de, uygulama aktif olarak
çalışırken bilgisayar hafızasında yer tutmaktır. Bunu en temel olarak değişkenler ile gerçekleştiriyoruz. Ancak
bazı durumlarda bu yeterli olmaz. Bazen uygulama içerisinde aynı ya da farklı veri tiplerine sahip birden
fazla(bazen binlerce) veriyi bir bütün olarak tutmak gerekebilir.
Örneğin, switch akış kontrol örneğinde geliştirdiğimiz öğrenci not uygulamasını ele alalım. Bu uygulamadaki
öğrenci notlarının tamamını tek bir nesne içerisinde tutmak isteyebiliriz. Ya da veritabanından elde ettiğimiz
kullanıcılar listesini, uygulama çalışırken hafızada tutmak, uygulama performansı açısından faydalı olacaktır.
İşte bu ve daha birçok nedenden dolayı ihtiyacımız olan bir programlama nesnesi var: Diziler
Diziler için, en basit tabirle değişkenler listesi demek yanlış olmaz. Diziler hakkında detaylı açıklamalara
girmeden önce, kuralları daha iyi benimseyebilmeniz için küçük bir dizi örneği yapacağız.
Dizi İlklendirmek(Array Initialization)
Bir dizi nesnesine değer ataması yapabilmek ya da veri tutmasını sağlayabilmek için öncelikle
ilklendirmek(initialization) yapılmalıdır. Bu işlem, bir dizinin bilgisayar hafızasında eleman sayısı ve veri tipinin
kapasitesi kadar kullanım alanına sahip olmasını sağlar. En basit tabirle, ilklendirilmemiş bir dizi ham bir
nesnedir diyebiliriz. Bu nesneyi kullanıma hazır hale getirmek için de ilklendirme işlemini yaparız.
Örnek:
scores := [3]int{}
Yukarıdaki örnekte, en basit haliyle bir dizi değişkeni tanımladık. Bu dizi değişkeninin adını scores olarak
belirledik ve veri tipi olarak integer, tutacağı eleman sayısının da 3 olacağını bildirdik. Artık bu dizi değişkenini
kullanarak veri ekleyebilir, veriyi elde edebilir ya da değiştirebiliriz.
Şimdi Go dilindeki diğer dizi tanımlama yöntemlerine bir göz atalım.
Otomatik Boyutlandırma İle Dizi Oluşturmak
Bir dizinin eleman sayısı sonradan değiştirilemez. Ancak dizinin oluşturulma aşamasında(ilklendirme) diziye
dinamik değerler vererek bu diziyi dinamik şekilde oluşturabiliriz.
Örnek:
numbers := [...]int{56, 65, 100}
Bu kullanım yönteminin kafaları karıştırmaması için, şöyle bir açıklama getirebiliriz: Bu kullanımda, Go
derleyicisinin davranışı çıkarım yapma üzerine kuruludur. Yani, biz numbers dizisini oluştururken, bu diziye
eklemek istediğimiz veriyi de birlikte gönderiyoruz. Go derleyicisi bu aşamada, bizim gönderdiğimiz
elemanların sayısını toplayarak, geri planda bu eleman adedine göre bir dizi değişkeni oluşturur. Bu da, bizim
ilklendirme aşamasında dinamik eleman sayısına sahip bir değişken oluşturabilmemizi sağlamaktadır.
Short hand Dizi Oluşturmak
Short hand yöntemi söz dizimi olarak ‘Otomatik Boyutlandırma’ yöntemine çok benzese de aynı değildir. Bu
yöntemde amaç, dizi değişken değerinin dinamik olarak belirlenmesi değil, sadece dizinin ilklendirme sırasında
veriyi de birlikte göndermektir.
cihanozhan.com
8. Örnek:
numbers := [3]int{43, 88, 70}
Yukarıdaki tanımlamada görüldüğü üzere, integer tipine eleman sayısını veren köşeli parantez içerisinde eleman
sayısını belirtiyoruz. Dizi tanımının devamında ise, 3 eleman için değer ataması yapıyoruz.
numbers dizi değişkenini aşağıdaki gibi de tanımlayabilirdik:
Örnek:
numbers := [3]int{43}
Bu tanımlama yönteminde de eleman sayısı değişmez ve gene 3 elemanlı bir dizi değişkeni oluşturduk. Ancak
diğer kullanımdan tek farkı, bu değişkende sadece tek bir değer ataması yaptık. Ve eğer bu kullanımı
uygulayarak dizi değişkenini ekrana yazdırırsak aşağıdaki gibi bir sonuç elde ederiz.
Örnek çıktısı:
[43 0 0]
Görüldüğü üzere, artık 3 elemanlı bir dizi değişkenine sahibiz. Ancak içerisinde sadece tek bir eleman değeri
var. Diğer iki adet sıfır(0) ise, dizinin ilklendirilmesi sırasında Go derleyicisi tarafından atanan başlangıç
değerleridir.
Diziler Hakkında Bilinmesi Gereken Temel Unsurlar
Yukarıda oluşturduğumuz diziyi kullanmak için bilmemiz gereken birkaç ön bilgi daha var.
- Diziler tanımlama sırasında belirtilen eleman sayısı kadar eleman alabilir.
Bilgisayar bilimlerinde dizi kavramının temel kurallarından biri, dizilerin eleman sayısı adedince eleman
alabileceğidir. Ayrıca, bir dizinin eleman sayı sınırı, dizinin ilklendirme işlemi sırasında belirtilmelidir. Bir
diziden bir eleman silinse bile, o elemanın hafızadaki yeri, dizi işaretçisi tarafından bilinir ve kullanılmaya
devam edilir. Silinen elemanın yerine yeni bir değer yazılabilir.
- Dizilerin eleman sayısı sonradan değiştirilemez!
Dizilerin eleman sayısı sonradan değiştirilemez! Programlama dilleri, mevcut bir dizinin eleman sayı sınırını
değiştirebilmek için farklı yöntemler uygulasalar da, bu işlem sadece mevcuttaki değiştirilemezlik sorununu
programcıya yansıtmamak için uygulanan bir çözüm yöntemidir. Mevcut bir dizinin eleman sayı kapasitesini
değiştirdiğinizde, aslında o programlama dili o diziyi geri planda yok eder ve yeni bir dizi oluşturarak önceki
dizinin elemanlarını yeni diziye aktararak kullanmanıza izin verir. Buna genel olarak yeniden
boyutlandırma(resize) işlemi denir ve bu yeterli bir çözümdür.
- Dizilerin elemanlarına index’ler üzerinden erişilir.
Dizilerin elemanlarına elemanların sırası ile değil, elemanların index’leri üzerinden erişilir. Bu kural
programlamaya yeni başlayanların en sık karıştırdıkları konulardan biridir. Dizi elemanlarının sayısı sayma
sayılarıyla(1 … ~) başlar. Ancak, dizi index’leri ise doğal sayılar(0 … ~) ile başlar. Bu nedenle, eğer dizinin ilk
elemanına ulaşmak isterseniz 1 değil, 0 değerini kullanmalısınız.
cihanozhan.com
9. Dizi Kullanımı
Şimdi daha önce oluşturduğumuz scores adındaki diziyi kullanalım.
Örnek:
scores := [3]int{} // Diziyi ilklendirmiştik!
scores[0] = 34 // Diziye değer ataması yapmak!
scores[1] = 87
scores[2] = 91
fmt.Println(scores) // Diziyi ekrana basalım.
Örnek çıktısı:
[34 87 91]
Bu örnek üzerinden bir dizinin tek bir elemanını elde etmeyi de inceleyebiliriz:
Örnek:
fmt.Println(scores[1]) // Dizinin 1. index’ini almak(2. Eleman)
Örnek çıktısı:
87
Yukarıdaki örnekte, basit bir integer dizi tanımlayarak scores adındaki dizimize integer veri tutacağını
bildirdik. Ayrıyeten, bu dizinin 3 elemana sahip olacağını ve buna göre bir hazırlık yapmasını da Go
derleyicisine iletmiş olduk! Bu bildirim sayesinde, Go derleyicisi bu dizi için hafızada 3 integer kapasiteli hafıza
alanı oluşturacak ve bu hafıza alanlarının adresini scores isimli dizinin işaretçisi(pointer) olarak tanımlayacaktır.
Bu sayede, biz scores üzerinden bu elemanların hafızadaki alanlarına erişebileceğiz.
Başka bir gerçek dünya örneği daha yapalım.
Örneğin, herhangi bir veri kaynağından elde ettiğimiz renk isimlerini bir listede tutmak akıllıca olabilir. Bu renk
listesini uygulama ayakta kaldığı sürece kullanabiliriz. Bu örnekte, elimizde sadece 3 renk(eleman) olacak.
var colors [3]string
colors[0] = "Red"
colors[1] = "Green"
colors[2] = "Blue"
fmt.Println(colors)
Bir Dizinin Eleman Sayısını Bulmak
Go ile yazılım geliştirirken birçok kez eleman sayısını elde etmemiz gerekecek. Bu işlem, Go diliyle birlikte
gelen built-in fonksiyonlarla birlikte çok kolaydır.
Dizi elemanlarının toplamını bulmak için len(arrayName) fonksiyonunu kullanacağız.
Örnek:
// 1. Dizi
var colors [3]string
cihanozhan.com
10. colors[0] = "Red"
colors[1] = "Green"
colors[2] = "Blue"
// 2. Dizi
var numbers = [5]int{5, 3, 1, 2, 4}
// Dizilerin eleman sayılarının toplamını hesaplayıp ekrana basalım
fmt.Println("Renklerin toplam adedi:", len(colors))
fmt.Println("Rakamların toplam adedi:", len(numbers))
Örnek çıktısı:
Renklerin toplam adedi: 3
Rakamların toplam adedi: 5
Döngü İle Dizinin Değerini Elde Etmek
Şu ana kadar farklı birçok dizi tanımlaması yaptık. Bu tanımlamalar sadece diziler konusunu hızlı kavrayabilmek
için verilen temel seviye örneklerdi. İlerleyen bölümlerde bu nesneleri farklı birçok uygulama içerisinde
kullanacağız. Şimdi bir dizinin içerisindeki veriyi nasıl döngü oluşturarak ekrana yazdıracağımıza bakalım.
Not : Bir sonraki bölümde döngüleri detaylı bir şekilde inceleyeceğiz. Ancak bu bölümde temel seviyede döngü
ile dizi kullanımını örneklememiz dizileri daha iyi kavramanıza yardımcı olacaktır.
Örnek:
strPattern := "Bu değerin x'deki index sırası %d ve değeri %.2fn"
x := [...]float64{65.4, 334.7, 3.21, 97}
for i := 0; i < len(x); i++ {
fmt.Printf(strPattern, i, x[i])
}
Örnek çıktısı:
Döngü ile dizi kullanımı örneğinde, ilk olarak ekranda göstereceğimiz metni programatik format(yer tutucular)
ile birlikte hazırladık. Bu string formatı içerisindeki %d yer tutucu desenini integer gibi sayısal değerleri, %.2f
yer tutucu desenini ise ondalıklı verileri ekrana basmak için kullanıyoruz. Sonrasında x adında, veri tipi float64
olan, içerisinde 4 adet veri olan bir dizi değişkeni oluşturduk. Bundan sonraki satırda ise, döngü işlemine geçtik.
Döngüde dikkat ederseniz i := 0 gibi bir alan mevcut. Bu alanda dizi içerisindeki index’lere eş gelecek değeri
üretecek değişkeni oluşturduk. Ve dikkat ederseniz, bu alanın değeri 0’dan başlamaktadır. Çünkü, hatırlarsanız
dizilerde index değerleri sıfır(0)’dan başlıyordu. Sonrasında ise, len() fonksiyonunu kullanarak x dizisinin
eleman uzunluğunu elde ettik. Ve bu sayede, sıfır(0) index’inden, dizinin eleman sayısına kadar olacak şekilde
bir döngü başladı.
cihanozhan.com
11. Diziler Referans Tipi Midir? Değer Tipi Mi?
Bu bölümde, bu kitabın ilk bölümünde incelediğimiz referans ve değer tipi kavramının dizilerdeki yansımasını
inceleyeceğiz.
Kısa hatırlatma:
- Referans tipindeki nesneler verinin hafızadaki adresini tutarlar ve kopyalanırken de hafıza adresini
kopyalarlar. Verinin kendisini değil!
- Değer tipindeki nesneler verinin kendisini tutar ve kopyalanması halinde verinin kendisini kopyalar
Peki diziler referans tipine mi dahiller, yoksa değer tipine mi?
Örnek:
co := [...]string{"Turkey", "USA", "China", "India", "Germany"}
x := co // co nesnesini x adında yeni oluşturulan bir diziye atadık!
x[0] = "Pakistan"
fmt.Println("co değeri: ", co)
fmt.Println("x değeri: ", x)
Örnek çıktısı:
Yukarıdaki örnek çıktıyı yorumlayalım:
co adında yeni bir dizi değişkeni oluşturarak, bu değişkene 5 adet ülke adını veri olarak ekledik. Sonraki satırda
x adında yeni bir değişken oluşturduk. Bu oluşturduğumuz x değişkenine de co adındaki dizi değişkeni atadık!
Bunun anlamı, ‘co dizisindeki değerleri x’e kopyala’ demektir. Buraya kadar her şey olağan! Sonraki satırda
ise x değişkeninin sıfırıncı(0) index’indeki Turkey değerini değiştirmek istedik. Eğer diziler bir referans tipi ise
bu değer ataması x dizisinin işaretçisi üzerinden co değişkeninin de verisini değiştirecektir. Ancak sonuç
çıktısından görüldüğü üzere, x dizisi üzerinde yapılan bir değişiklik sadece x’i etkilemektedir. Bu değişiklik co
dizisini etkilememektedir. Bu sonuç da bize dizilerin bir değer tipi olduğunu ispatlamaktadır.
Çok Boyutlu Diziler
Çok boyutlu diziler, her bir item’ında bir dizi bulunduran dizilerdir. Normal dizilerin her item’ında sadece tek bir
eleman, yani değer vardır. Ancak çok boyutlu diziler, her item’ında birer dizi taşırlar. Bu nedenle, çok boyutlu
bir diziden elde edilen her item’ın da alt elemanları elde edilebilir.
Not : Bu konu çok karmaşık gibi görünebilir. Ancak çok boyutlu bir dizinin temel prensipleri çok açıktır. Bu
prensipleri anladığınız takdirde tüm çok boyutlu dizi yapılarını rahatlıkla anlayabilir ve oluşturabilirsiniz.
Basit bir çok boyutlu dizi oluşturalım. Bu dizi 3’e 2’lik bir yapıya sahip olsun.
Örnek:
var unicorn [3][2]string
Yukarıdaki çok boyutlu diziyi string veri tipinde oluşturduk. Diziyi oluştururken belirttiğimiz 3 ve 2’lik sayılar
ise dizinin temel yapısını tanımlıyor. Buradaki 3 sayısı ana dizinin kaç eleman alacağını belirtir. Bu 3 dizi
cihanozhan.com
12. elemanının her bir item’ı ise birer dizi alacaktır. Ve aynı zamanda bu 3 dizi item’ın her birinin kapasitesi de 2
string eleman alacak şekilde oluşturulmuştur.
Şimdi bu oluşturduğumuz unicorn isimli çok boyutlu değişkene veri ekleyelim.
unicorn[0][0] = "Apple"
unicorn[0][1] = "Google"
unicorn[1][0] = "Microsoft"
unicorn[1][1] = "Uber"
unicorn[2][0] = "Facebook"
unicorn[2][1] = "Oracle"
Veri ekleme kodlarına dikkat ederseniz, soldaki index’ler 0’dan başlayarak 2 dahil 2’ye kadar gidiyor. Bunun
anlamı, 3 elemanlı bir dış diziye sahibiz. Sağdaki index alanlarında ise, sadece 0 ve 1’ler mevcut! Bunun anlamı
ise, sadece 2 adet eleman alabilen bir iç diziye de sahibiz. Bu örnekteki her dış dizi, 2 eleman alabilen bir iç
diziye sahiptir.
Bu çok boyutlu dizi içerisinden istediğimiz bir veriye nasıl ulaşabiliriz?
Eğer sadece tek bir veriyi bulmayı hedefliyorsak ve yukarıdaki gibi bir dizilime sahipsek, aşağıdaki gibi
yapabiliriz.
Örneğin, Uber verisini elde etmek istiyorsak:
Örnek:
fmt.Println(unicorn[1][1])
Örnek çıktısı:
Uber
Aynı şekilde bu yöntem ile veri değişikliği de yapabiliriz.
Örnek:
unicorn[1][1] = "Yahoo"
fmt.Println(unicorn[1][1])
Örnek çıktısı:
Yahoo
Çok boyutlu dizilerin bir başka oluşturulma yöntemi ise aşağıdaki gibidir:
Örnek:
// Diğer bir çok boyutlu dizi oluşturma yöntemi
data := [3][2]string{
{"apple", "pear"},
{"banana", "cucumber"},
{"lemon", "watermelon"},
}
Not : Döngüler konusu ve range kullanımı bir sonraki bölüm olan Döngüler bölümünde detaylı bir şekilde
cihanozhan.com
13. anlatılmaktadır. Ancak döngülerin diziler ve dizilerden veri elde etmede aktif olarak kullanılması nedeniyle bu
bölümde de bir örnekle incelenmiştir. Bir sonraki bölümde döngü yapılarını inceledikten sonra tekrar bu örneğe
göz atmanızı öneririm.
// Çok boyutlu dizinin her bir item'ını iç içe döngü ile ekrana basmak
// loop1 döngüsü, yukarıda tanımlanan data üzerinde dış dizi için döner
// loop1 her item’ında loop2’nin bir iç diziye sahip olmasını sağlar
for _, loop1 := range data {
// loop2 döngüsü, loop1 üzerinde bir iç dizi almak için döner
for _, loop2 := range loop1 {
fmt.Printf("%s ", loop2)
}
fmt.Printf("n")
}
Örnek çıktısı:
apple pear
banana cucumber
lemon watermelon
Döngüler
Programlama dünyasında, tekrar eden bazı işlemleri yapmak gerekebilir. Bu bazen bir veritabanından elde edilen
veri üzerinde, bazen de network üzerinden elde edilen veri paketleri üzerinde işlem yapmak olabilir. Bu tür
işlemleri yapabilmek için, belirli kurallara göre işlemleri tekrar edebilme yeteneği gerekir. Programlama
dünyasında bu amaçla çalışan nesnelere döngüler diyoruz. Bu bölümde, Go dilinin döngü yeteneklerini
inceleyeceğiz.
Go programlama dilinde diğer diğer dillerin aksine tek bir döngü yapısı vardır: for
for
Diziler konusunu incelerken for döngüsünü dizi örnekleri üzerinde kullanmıştık. Şimdi bu bölümde for
döngüsünü detaylı şekilde inceleyeceğiz.
for söz dizimi:
for başlatma; şart; işlem {
}
İlk basit for örneğimizi oluşturalım ve inceleyelim.
Elimizde basit bir ekrana yazma işlemi var ve bunu N defa yapacağız.
Örnek:
for i := 0; i < 5; i++ {
fmt.Println("i'nin değeri:", i)
}
Örnek çıktısı:
cihanozhan.com
14. i'nin değeri: 0
i'nin değeri: 1
i'nin değeri: 2
i'nin değeri: 3
i'nin değeri: 4
Not : Bu for döngü yöntemi C-family dillerin tümünde geçerli bir kullanımdır. Go dili de C dilinin bir çok iyi
yönünü bünyesine dahil ettiği için, bu kullanımı olduğu gibi desteklemektedir.
Yukarıdaki örnekte, ekrana yazma işlemini 5 kez tekrarlamayı sağlayan bir kod yazdık. Şimdi bu kullanımı adım
adım inceleyelim.
- başlatma: Bu alanda bir değişken tanımlayarak for döngü bloğunun bu değişkenin değeri ile
kontrol edilebilmesi sağlanır. Bu değişken genel olarak sıfır(0)’dan başlatılır ve döngü her
çalıştığında, istenen algoritmaya göre değişmekle birlikte, azaltılır ya da artırılır. Bu sayede
döngünün belirtilen işlemi kaç kez yaptığını bilebiliriz. Bu değişken sadece for döngü bloğunun
içerisinde geçerlidir. Bu değişkene dışarıdan erişilemez.
- şart: başlatma bloğunda tanımlanan değişkenin değeri için bir şart belirtmemizi sağlar. Örneğin, i <
5 olduğu sürece devam et gibi bir şart verebiliriz.
- işlem: bu scope ise döngünün yönünü belirler. Başlatma bloğunda tanımlanan değişkenin ileri ya
da geri olacak şekilde, artırılması ya da azaltılmasını sağlayarak uygulama sürecinde belirleyici rol
oynar.
Eğer for döngüsünün şart alanını boş bırakırsak ne olur?
Eğer for döngüsünün şart alanını boş bırakırsak, döngünün bir bitiş değeri(limiti) olmayacaktır. Bu da doğal
olarak bir sonsuz döngü tanımladığımız anlamına gelir.
Not : Aşağıdaki kodu çalıştırdıktan sonra sonsuz döngü oluşacaktır. Visual Studio Code editöründe sonsuz
döngüyü kırmak için terminal üzerindeyken CTRL+C klavye kombinasyonunu kullanmanız yeterlidir.
Örnek:
for i := 0; ; i++ {
fmt.Println("i'nin değeri: ", i)
}
Örnek çıktısı
…
i'nin değeri: 2837
i'nin değeri: 2838
i'nin değeri: 2839
…
Eğer döngünün işlem alanını boş bırakırsak ne olur?
Eğer döngünün işlem alanını boş bırakırsak, döngü gene sonsuz döngü olacaktır. Ancak döngünün yönünü
belirtmediğimiz için i değişkeninin değeri artmayacak ve ekrana sürekli sıfır(0) değeri yazılacaktır. Bu kullanım
çok özel durumlar haricinde kullanılan bir yöntem değildir. Ancak for döngüsünün davranışlarını anlayabilmeniz
için önemlidir.
Örnek:
for i := 0; i < 3; {
cihanozhan.com
15. fmt.Println("i'nin değeri: ", i)
}
Örnek çıktısı:
…
i'nin değeri: 0
i'nin değeri: 0
i'nin değeri: 0
…
for ile While Döngü Yöntemi Oluşturmak
Normal bir for kullanımında oluşturduğumuz başlatma değişkeni sadece for scope’u içerisinde geçerli olduğu
için bu değişkene dışarıdan erişemeyiz. Ancak bazen döngü içerisinde üretilen ve değeri değişen veriye döngü
dışından erişme ihtiyacımız olabilir. Şimdi bu durumu nasıl yönetebileceğimize bakalım.
Örnek:
x := 1
for x <= 10 {
fmt.Println("x değeri: ", x)
x += 1
}
Yukarıdaki örnekte, for scope’u dışında x adında bir değişken tanımladık ve bu değişkenin for scope’u
içerisinde kullanılmasını sağladık. Bu sayede, döngünün içerisinde üretilen yeni değerlere döngünün dışından da
erişebildik. Ayrıca, dikkat ederseniz normalde işlem scope’unda yaptığımız değer artırma işlemini de for
döngüsünün içerisinde yaptık.
Örnek sonucu:
range
Önceki bölümlerde diziler üzerine birçok işlem gerçekleştirdik. Dizileri hatırlarsanız, döngü ile dizilerin her bir
item’ını elde ederek, bu item’lar üzerinde çeşitli işlemler yapabiliyorduk. Ancak modern yazılım geliştirme
süreçlerinde, bir integer’dan daha karmaşık veri tiplerine sahip dizi nesneleri üzerinde çalışmamız
gerekmektedir. Bu tür karmaşık diziler üzerinde iteration(tekrarlama) uygulayabilmek için Go diline eklenen
for döngü yapısına da range diyoruz.
Not : C# programlama diliyle ilgilenenler için; Go dilindeki for range uygulaması(implementation), C#
dilindeki foreach döngü yapısının Go dilindeki karşılığıdır.
cihanozhan.com
16. Söz dizimi:
for key, value := range arrayObject {
println(key, value)
}
Şimdi for range ile ilgili basit bir örnek yapalım.
Örnek:
nums := []int{5, 6, 7}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("toplam: ", sum)
Örnek çıktısı:
toplam: 18
Yukarıdaki for range döngüsünde nums dizisi üzerinde bir tekrarlama işlemi yaptık. Yalnız dikkat ederseniz,
for tanımından sonraki “_, num” kısmında bir alt çizgi kullandık. Bunun nedeni, for range yapısının bize iki
değer veriyor olmasıdır. Bu değerlerden ilki elemanın index’i, diğeri ise elemanın değeridir. Ancak biz bu
uygulamada elemanın index’ini almak istemediğimiz için, index alanına bir değişken adı vermek yerine, alt
çizgi( _ ) kullandık. Eğer bu index değerine de ihtiyacımız var ise, şu şekilde bu değeri elde edebiliriz.
Not : Kitabın ilk bölümlerinden hatırlarsanız, Go dili tanımlanmış ama kullanılmamış nesnelerin kod içerisinde
bulunmasına izin vermiyor ve derleme zamanında hata fırlatıyor. Bu nedenle, döngünün index alanında bir
değişken tanımlarsanız, onu kullanmak zorunda kalırsınız. Aksi halde, alt çizgi ile bu sorunu çözebiliyoruz.
Örnek:
nums := []int{5, 6, 7}
sum := 0
for ind, num := range nums {
sum += num
fmt.Println("index: ", ind)
}
fmt.Println("toplam: ", sum)
Örnek çıktısı:
index: 0
index: 1
index: 2
toplam: 18
Go dilinde for range döngü modelini birçok farklı nesne(map, slice, struct vb.) üzerinde kullanabiliyoruz. Bu
nesnelerin bir kısmını henüz incelemediğimiz için, bu örneklerle ilgili uygulamaları sonraki bölümlere
bırakıyoruz.
cihanozhan.com
17. Bir for döngüsündeki range yapısını farklı bir şekilde de kullanabiliriz. Örneğin, elimizde bir liste var ama
verinin kendisini elde etmeden sadece veri adedince bir işlemin gerçekleştirilmesini istiyorsak aşağıdaki
yöntemle veriyi göz ardı ederek veri adedince dönen bir döngü oluşturabiliriz.
list := map[string]string{
"X": "Ferrari",
"Y": "Jaguar",
}
for range list {
fmt.Println("Dönüyor")
}
Örnek çıktısı:
Dönüyor
Dönüyor
Son uyguladığımız döngü yapısı biraz anlamsız gibi görünebilir. Ancak bu yöntemi Go dilindeki channel
nesneleriyle birlikte kullanılabiliyoruz.
break ve continue
Döngüler gerekli şartlar sağlanana kadar dönmek üzere tasarlanan nesnelerdir. Ancak bazen bir döngünün iç
akışını kontrol etmemiz ve değiştirmemiz gerekebilir.
- break: Matematiksel bir değeri bulana kadar işleme devam eden bir döngüye sahip olduğumuzu
varsayalım. Eğer bu döngüyü sonlandırmazsak, bu işlem bitene kadar devam etmesi gerekir. Peki
matematiksel sonucu elde edersek, döngüyü nasıl kıracağız? İşte bunu sağlayan komutun adı
break’tir.
- continue: break için verdiğimiz matematiksel hesaplama örneğinin aynısı continue komutu için de
geçerli olsun. Bu işlem devam ederken 10 adımlı bir algoritma çalıştırdığımızı varsayalım. İlk 3
matematiksel işlemin sonucunu biliyoruz. Eğer bu ilk 3 matematik işleminin sonucu hatalı ise,
sonraki 7 matematiksel işlemin bir önemi kalmayacaktır. Daha sonra, bu ilk 3 işlemden sonraki 7
işlemi yapmasını beklemek mantıklı mıdır? Bu durumda, döngüyü kırmadan sadece bu işlemden
vazgeçip döngünün başına dönülmesi, zaman ve kaynak yönetimi açısından daha mantıklıdır. İşte
bu döngüyü kırmadan döngünün başına dönmemizi sağlayan komutun adı continue’dur.
Bu iki komutu da tek bir uygulama içerisinde örnekleyerek nasıl çalıştığını inceleyeceğiz.
Örnek:
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
i := 0
cihanozhan.com
18. for {
fmt.Print("Enter value: ")
str, _ := reader.ReadString('n')
val, err := strconv.Atoi(strings.TrimSpace(str))
if err != nil {
fmt.Println("Beklenmeyen bir değer girildi. Başa dönülüyor.")
continue
}
if val == 3 {
fmt.Println("Döngü kırıldı!")
break
}
if val == 5 {
fmt.Println("Şartlar sağlanamadı. Döngünün başına dönüldü.")
continue
}
fmt.Println("index'in değeri: ", i)
i++
}
fmt.Println("Uygulamanın sonu.")
}
Örnek çıktısı:
Bu örneği çalıştırdığımızda, bizden nümerik bir değer girmemizi isteyecektir. Bu alan için eğer 3 değerini
girersek, döngü sonlanarak ekranda “Döngü kırıldı! Uygulamanın sonu." mesajını görürsünüz. Ancak eğer
uygulamaya 5 değerini girerseniz, uygulama bulunduğu konumdan başa dönerek akışı tekrar başlatır. Bu işlem
döngünün yeniden başladığı anlamına gelmez, sadece bir sonraki döngü sürecine geçilerek devam edilir.
Break/Continue - 1
Break/Continue - 2
cihanozhan.com
19. Ayrıca, yukarıdaki uygulamada küçük bir hata yönetimi işlemi de gerçekleştirdik. Bunun nedeni, gerçek bir
uygulama içerisindeki olası hata durumlarını örneklemek ve bu durumda neler yapılabileceğini anlamanızı
sağlamaktır. Örneğin, bu uygulamada kullanıcıdan bir sayısal değer istiyoruz. Ancak bu değeri öncelikle
metinsel veri(string) olarak alıyoruz. Ondan sonra da, geri planda bu metinsel veriyi sayıya çeviriyoruz. Eğer
kullanıcı veri girişi aşamasında gerçek bir sayı girmezse, bu uygulama çökecektir! İşte bu olası durumu
yönetebilmek için, veri dönüşümü gerçekleştirdiğimiz kısımdaki err hata nesnesini aktif ederek kullandık. Ve
eğer err nesnesi nil(null) değilse, bu işlem bir hata üretmiş demektir. Eğer uygulamada bir hata meydana gelirse,
uygulamanın continue ile başa dönerek devam etmesini sağladık.
Sonsuz Döngü
Programlamada bazı işlemlerin sürekli olarak yapılması gerekebilir. Örneğin, eğer bir bilgisayar ağından veri
okuma işlemi yapıyorsanız, bunu genellikle sürekli ve anlık olarak yapmalısınız. Bu işlemi yapabilmek için de,
ağ üzerindeki bir port’u anlık olarak dinleyen bir yazılım geliştirmelisiniz. Bu ve daha birçok kullanım amacı
için sonsuz döngüler oluşturulabilir.
Söz dizimi:
for {
}
Sonsuz döngü söz dizimi için basit bir örnek yapalım.
Örnek:
for {
fmt.Println("Merhaba Mars!")
}
Örnek çıktısı:
Bu işlemin sonucunda ekrana sürekli olarak “Merhaba Mars!” metni basılacaktır.
cihanozhan.com
20. Slice
Slice, Go programlama diline özgü, yeni bir programlama nesnesidir. Bir slice, arka planda dizileri kullanan ve
dizinin hafızadaki adresini işaret eden yeni bir dizi kullanım yöntemidir. Bir slice’ın kendisine ait veri tutma
yeteneği yoktur. Mevcut bir dizi üzerinden bir slice oluşturulabildiği gibi, bir slice’ı doğrudan da oluşturabiliriz.
Ancak doğrudan bir slice oluştursak bile, Go derleyicisi arka planda bu slice için bir dizi tanımlaması yapar ve
oluşturulan slice’ın işaretçisini bu dizinin hafıza adresine bağlar.
Not : Slice nesnesinin Türkçe karşılığı “dilim” olarak tanımlanır.
Bir slice oluşturmanın yöntemleri:
- Bir dizi üzerinden slice oluşturmak
- Bir diziden bağımsız olarak yeni bir slice oluşturmak
İlk olarak bir dizi üzerinde nasıl slice oluşturabileceğimizi inceleyelim.
Bir dizi üzerinde birden fazla slice oluşturulabilir. Bu oluşturulan slice’ların tümü tek bir dizinin hafızadaki
adresine bağlı olurlar. Eğer bir dizi üzerinde oluşturulan slice’lardan herhangi birisi üzerindeki elemanlarda bir
değişiklik yapılırsa, bu işlem doğrudan işaret edilen dizinin gerçek verisi üzerinde gerçekleştirilir. Bu nedenle,
herhangi bir slice’ta yapılan değişiklik, hem ana dizinin verisini, hem de bu dizi üzerinden üretilen tüm slice’ları
etkiler.
Örnek:
// Bir dizi tanımlıyoruz
numsArr := [5]float32{3.23, 4.56, 1.74, 76.94, 13.53}
Bir dizi ya da slice üzerinde, yeni bir slice oluşturmak için kullandığımız veriyi kesme yöntemi aşağıdaki
gibidir:
array|slice[baslangic:bitis]
Şimdi bu dizi üzerinde yeni bir slice oluşturalım.
slice1 := numsArr[:]
slice1 isimli slice’ımız numsArr dizisinin tüm verisini yansıtacak şekilde ayarlandı. Bunu sağlayan şey ise [:]
tanımıdır. Bu işlemin aynısını numsArr[0:] yöntemiyle de elde edebilirsiniz.
Oluşturulan slice verisini ekrana bastırmak için:
fmt.Println(sliceName)
Yukarıdaki söz diziminde bulunan start alanı, veri kesme işleminin hangi index’ten başlayacağın, end alanı ise
bu kesme işleminin hangi index’e kadar olan veriyi kapsayacağını belirtmek için kullanılır.
slice1 isimli slice’ı oluştururken start ve end alanlarını boş bıraktık. Bunun anlamı, herhangi bir limit olmadan
tüm veriyi yeni slice’a istiyoruz demektir.
numsArr dizisindeki 4.56 ondalıklı değerinin indexi bir(1)’dir. Sadece bu veriyi alacak bir slice oluşturmak
istersek şu şekilde yapabiliriz.
slice2 := numsArr[1:2] // sonuç: [4.56]
cihanozhan.com
21. Not : Dizilerdeki index’leme mantığını iyi bilen bir geliştirici için slice’lar üzerinde çalışmak çok kolaydır.
Diğer bir örnek olarak, dizi elemanlarının ilki hariç, geri kalan tüm elemanları alarak yeni bir slice oluşturalım.
slice3 := numsArr[1:]
Veri kesme alanında dinamik bir belirteç de kullanabiliriz.
Örneğin, numsArr dizisinin toplam eleman uzunluğunun 2 eksiği kadar bir elemanı elde edelim.
Örnek:
slice4 := numsArr[:len(numsArr)-2]
Örnek çıktısı:
[3.23 4.56 1.74]
Not : Yukarıdaki örnekte kullandığımız len(arrayName) fonksiyonu ile bir dizi ya da slice’ın elemanlarının
toplamını elde edebiliyoruz.
Uygulama Ödevi : Yukarıdaki gibi bir dinamik veri kesme yöntemini cap() fonksiyonuyla uygulayınız.
Şu ana kadar mevcut bir dizi üzerinden nasıl slice oluşturabileceğimizi ve çeşitli veri kesme yöntemlerini
inceledik. Şimdi de bir slice nesnesini herhangi bir diziden bağımsız olarak nasıl oluşturabileceğimize bakalım.
Bir Diziden Bağımsız Olarak Yeni Bir Slice Oluşturmak
Go dili yazılım geliştiricilerinin slice’lar ile olabildiğince esnek çalışabilmelerini sağlamak için slice’lara birçok
özellik ve esneklik eklemiştir. Bunlardan biri de, slice’ların dizilerden bağımsız olarak kullanılabilmesidir. Bu
konuya teknik olarak bakacak olursak, aslında slice’ın bilgisayar belleğinde veri depolayabilmek için dizilere
ihtiyaç duyar. Bu nedenle, biz yeni bir slice oluşturduğumuzda, Go derleyicisi arka planda bu slice için bir dizi
oluşturarak, slice’ları bağımsız şekilde kullanabilmemize izin vermektedir.
Şimdi bu yöntemi uygulayarak yeni bir slice oluşturalım.
Örnek:
Uygulayacağımız örnek mevsimler ve aylar ile ilgili olacak. Yeni bir slice oluşturacağız ve yılın 12 ayını bu
slice’a ekleyeceğiz. Sonrasında bu slice üzerinden farklı slice’lar oluşturarak bu ayları mevsimlere göre
ayıracağız.
months := [...]string{
1: "Ocak",
2: "Şubat",
3: "Mart",
4: "Nisan",
5: "Mayıs",
6: "Haziran",
7: "Temmuz",
8: "Ağustos",
9: "Eylül",
10: "Ekim",
cihanozhan.com
22. 11: "Kasım",
12: "Aralık"}
Not : months dizişi 13 eleman kapasitesine sahiptir(cap/len(months) ile kontrol edin). Ancak biz ay
numaralandırmasını bir(1)’den başlatabilmek için sıfırıncı(0) index’i kullanmadık. Bu sayede, daha kolay
okunabilir ve yönetilebilir bir slice’a sahip olduk.
Yukarıdaki months slice’ını oluştururken köşeli parantezler içerisinde üç nokta kullandık([…]). Bunun nedeni,
yeni slice’ımızın kapasitesini, başlangıçtaki atanan elemanların dinamik olarak belirlemesini sağlamaktı.
Not : Bu örnekte kullanılacak eleman sayısı sabit olduğu için burada sabit bir değer de kullanabilirsiniz.
Ayları mevsimlere ayırma uygulamamıza adım adım devam ederken, şimdi 12 ay verisini üçerli gruplardan
oluşacak şekilde 4 mevsime bölelim. Bu işlemi birinci aydan onikinci aya kadar olacak şekilde baştan başlayarak
yapacağız.
p1 := months[1:4] // Ocak, Şubat, Mart
p2 := months[4:7] // Nisan, Mayıs, Haziran
p3 := months[7:10] // Temmuz, Ağustos, Eylül
p4 := months[10:13] // Ekim, Kasım, Aralık
Not : p1, p2 şeklinde p ile başlayan isimlendirmenin açılımı part’tır.
Yılın ilkbahar aylarını, spring adında bir slice oluşturarak ayıralım.
spring := p1[2:3] // 3. ayı ilkbahara ait olduğu için aldık
springSub := p2[0:2] // 4. ve 5. ayları ilkbahara ait oldukları için aldık
for _, month := range springSub {
spring = append(spring, month)
}
fmt.Println("İlkbahar: ", spring)
İlkbahar aylarının çıktısı:
İlkbahar: [Mart Nisan Mayıs]
Yılın yaz aylarını, summer adında bir slice oluşturarak ayıralım.
summer := p2[2:3] // 6. ayı yaza ait olduğu için aldık
summerSub := p3[0:2] // 7. ve 8. ayları yaza ait oldukları için aldık
for _, month := range summerSub {
summer = append(summer, month)
}
fmt.Println("Yaz: ", summer)
Yaz aylarının çıktısı:
Yaz: [Haziran Temmuz Ağustos]
Yılın sonbahar aylarını autumn adında bir slice oluşturarak ayıralım.
autumn := p3[2:3] // 9. ayı sonbahara ait olduğu için aldık
autumnSub := p4[0:2] // 10. ve 11. ayları sonbahara ait oldukları için aldık
for _, month := range autumnSub {
cihanozhan.com
23. autumn = append(autumn, month)
}
fmt.Println("Sonbahar: ", autumn)
Sonbahar aylarının çıktısı:
Sonbahar: [Eylül Ekim Kasım]
Yılın kış aylarını winter adında bir slice oluşturarak ayıralım.
winter := p4[2:3] // Yılın son ayı kışa ait olduğu için aldık
winterSub := p1[0:2] // Yılın ilk iki ayı kışa ait olduğu için aldık
for _, month := range winterSub {
winter = append(winter, month)
}
fmt.Println("Kış: ", winter)
Kış aylarının çıktısı:
Kış: [Aralık Ocak Şubat]
Yılın aylarını mevsimlere göre ayırma uygulamamızı tamamladık. Artık ilgili slice’ları istediğiniz gibi
kullanarak mevsimlere göre işlemler yapabilirsiniz. Ancak, sanki biraz uzun bir kodlama oldu gibi! Mesela her
işlem için ayrı ayrı for döngüsü oluşturarak slice’ları birbiriyle birleştirdik. Aslında bu işlem yerine bir metot
oluşturarak 4 farklı işlem için tek bir metot kullanabilirdik. Bu bir yöntemdi… Ancak henüz metotları
incelemedik ve metotların önemini anlamak için bu tür sorunları yaşamak gerekiyor! Merak etmeyin! Başka bir
yöntem daha mevcut! Şimdi bunu yeni yöntemi summer slice’ı için yaptığımız örneğin alternatifi olarak
yapalım.
Kısa Kod: Yılın yaz aylarını summer adında bir slice oluşturarak ayıralım.
spring := p1[2:3]
springSub := p2[0:2]
// for döngüsü ile yaptığımız işlemin aynısını tek başına yapan kod!
summer = append(summer, summerSub...)
Kıssadan Hisse : Eğer kullandığınız programlama dili ve platformu iyi tanımazsanız, bu örnekte olduğu gibi
daha fazla kod yazmanız gerekebilir ve hatta bazen çözümsüz kalabilirsiniz.
İki Slice İçerisindeki Kesişen Verileri Bulmak
Gerçek dünya uygulamaları geliştirirken slice’lar ile çok sık oynamanız gerekecek. Biz de gerçek bir ihtiyaçtan
yola çıkarak, küçük bir kesişen verileri bulma uygulaması yaparak slice’lar ile oynayalım.
Bazı isimler sadece erkek çocukları için, bazıları sadece kız çocukları için kullanılır. Ancak bazı isimler unisex
olarak tanımlanırlar. Yani, bu isimleri hem erkek çocukları hem de kız çocukları kullanabilir. Biz de elimizdeki
veri seti üzerinde unisex isimleri bulmak için küçük bir algoritma çalıştıracağız. Bu uygulamayı gerçek dünya
uygulaması haline getirmek de mümkündür. Sadece elinizde Türk unisex isimleri listesinin olması yeterlidir.
Örnek:
girlNames := [4]string{"Ayşe", "Fatma", "Doğa", "Deniz"}
boyNames := [4]string{"Kadir", "Ahmet", "Deniz", "Doğa"}
cihanozhan.com
24. unisexName := ""
for _, girlName := range girlNames {
for _, boyName := range boyNames {
if girlName == boyName {
unisexName = girlName
fmt.Printf("%s unisex bir isimdir.n", unisexName)
}
}
}
Örnek çıktısı:
Doğa unisex bir isimdir.
Deniz unisex bir isimdir.
Bir Slice Üzerindeki Verilerde Değişiklik Yapmak
Bilgisayar hafızası üzerindeki verilerde hızlı bir şekilde değişiklikler yapabilmek önemlidir. Örneğin, bir slice’da
bulunan verilerde çok fazla kod yazmadan değişiklikler yapabiliriz.
Sorun : Elimizde 10 adetlik bir kullanıcı listesi var. Bu kullanıcı listesindeki kullanıcıların son 5 tanesinin yaş
bilgisi sisteme yanlış girilmiş. Bu 5 kullanıcıyı bulup, yaş bilgilerinin bir artırılması gerekiyor.
Örnek:
ages := [...]int{32, 43, 18, 54, 22, 21, 34, 19, 59, 36}
changeThat := ages[5:10]
fmt.Println("Verinin İlk Hali: ", ages)
for i := range changeThat {
changeThat[i]++
}
fmt.Println("Verinin Son Hali: ", ages)
Örnek sonucu:
Verinin İlk Hali: [32 43 18 54 22 21 34 19 59 36]
Verinin Son Hali: [32 43 18 54 22 22 35 20 60 37]
Çok Boyutlu Slice Kullanımı
Diziler konusundan hatırlayacağınız çok boyutlu çalışabilme yeteneğini bir slice üzerinde de uygulayabiliriz.
Şimdi, çok boyutlu slice’lar ile nasıl çalışabileceğimize bir bakalım.
Örnek:
programmingLanguages := [][]string{
{"C", "C++"},
{"C#", "Java"},
{"JavaScript", "Rust"},
{"Go", "Erlang"},
cihanozhan.com
25. }
for _, loop1 := range programmingLanguages {
for _, loop2 := range loop1 {
fmt.Printf("%s ", loop2)
}
fmt.Printf("n")
}
Örnek sonucu:
C C++
C# Java
JavaScript Rust
Go Erlang
Bir Slice’ın Nil Durumunu Kontrol Etmek
Go dilinde nesnelerin null olma durumu nil ile yönetilir. Ve her slice’ın başlangıç değeri varsayılan olarak
nil’dir. Eğer bir slice’ın başlangıcında değer aldığını algoritmik olarak garanti edemiyorsak, o zaman slice için
bir nil kontrolü yapmalıyız.
Not : Go dilinde hata yönetimini “Hata Yönetimi” bölümünde derinlemesine inceleyeceğiz. Ancak yeri
gelmişken, slice ile ilgili küçük bir bilgi notu olarak bu eklemeyi yapmayı uygun gördük.
Örnek:
var names []string
if names == nil {
names = append(names, "Cihan", "Barış", "Erdal")
fmt.Println("İsimler: ", names)
}
Örnek sonucu:
İsimler: []
İsimler: [Cihan Barış Erdal]
Yukarıdaki işlem gayet basittir. Slice’ı if ile kontrol ederek nil olup olmadığına bakıyoruz. Eğer slice’ın değer
durumu nil ise if scope’u içerisinde slice’a değer ekliyoruz.
cihanozhan.com
26. Map
Map nesneleri, Go dilindeki unordered key/value pair koleksiyonlardır. Her Map nesnesinin key ve value
alanları bulunur. Key/value pair nesne modelinin yapısı gereği, key alanları benzersiz bir anahtar içermelidir.
Value alanları ise herhangi bir veri yapısında olabilir. Bir Map nesnesini oluşturmak için make() fonksiyonu
kullanılır. Map nesnelerinin başlangıç değerleri nil’dir. Bir Map nesnesinin key ve value alanları farklı veri
tiplerine sahip olabilir.
Map Kullanımı
İlk örneğimizde key ve value alanlarının ikisini de string veri tipiyle tanımlayan bir örnek yapacağız.
Örnek:
states := make(map[string]string) // Map nesnesini oluştur
fmt.Println("Boş : ", states) // nil değere sahip map nesnesini ekrana bas
Örnek çıktısı:
Boş : map[]
Şimdi de oluşturduğumuz states isimli Map nesnesine birkaç şehir verisi ekleyelim.
Örnek:
states["IST"] = "İstanbul" // Benzersiz olan IST anahtarına Istanbul verisini ekler
states["ANK"] = "Ankara"
states["ANT"] = "Antalya"
fmt.Println("Dolu : ", states)
Örnek çıktısı:
Dolu : map[IST:İstanbul ANK:Ankara ANT:Antalya]
Bir Map’in Elemanlarına Erişim
Oluşturduğumuz states adındaki Map nesnesinin bir elemanına erişelim.
Örnek:
antalya := states["ANT"]
fmt.Println("Seçili Şehir : ", antalya)
Map’in bir elemanına erişmek için mapName[key] yapısını kullanmak yeterlidir.
Örnek çıktısı:
Seçili Şehir : Antalya
Bir Map Nesnesinden Eleman Silmek
Map nesnesinden eleman silmek için Go’nun built-in fonksiyonlarından delete()’i kullanıyoruz.
Örnek:
delete(states, "ANK")
cihanozhan.com
27. fmt.Println(states)
Örnek çıktısı:
map[IST:İstanbul ANT:Antalya]
Döngü ile Map Verisine Erişim
Map nesneleri genellikle birden fazla veriye sahip oldukları için, bu nesne üzerinde iteration(yineleme) işlemleri
uygulamak önemlidir. Şimdi bir Map nesnesini döngü ile nasıl kullanabileceğimize bakalım.
Öncelikle, states nesnesinin eleman adedini artırmak için bir eleman daha ekliyoruz.
states["ERZ"] = "Erzurum"
Örnek:
for key, value := range states {
fmt.Printf("%v: %vn", key, value)
}
Örnek çıktısı:
IST: İstanbul
ERZ: Erzurum
ANT: Antalya
Yukarıdaki döngü işleminde states isimli Map üzerinden key ve value değerlerini alarak ekrana yazdırdık.
Burada kullandığımız key ve value tanımlamaları kodun anlaşılması için tercih edilen isimlerdir. Bu alanlar için
istediğiniz isimlendirmeyi kullanabilirsiniz. Ayrıca bazı durumlarda sadece value değerlerini elde etmek
isteyebilirsiniz. Bu tür durumlarda key yerine altçizgi( _ ) kullanarak sadece value nesnesine erişmek istediğinizi
derleyiciye bildirmiş olursunuz. Aynı kural value nesnesi için de geçerlidir.
for _, value := range states
ya da
for key, _ := range states
Şimdi de, gene döngüleri kullanarak Map nesnesi üzerinde farklı bir işlem yapalım.
İstek: states isimli Map nesnesinin eleman sayısı kapasitesine sahip bir key list oluşturun.
Örnek:
keys := make([]string, len(states))
i := 0
for k := range states {
keys[i] = k
i++
}
fmt.Println(keys)
cihanozhan.com
28. Örnek çıktısı:
[ANT IST ERZ]
Peki bu key listesini alfabetik olarak sıralayabilir miyiz?
Sıralama işlemi Go built-in paketlerinden sort ile çok kolay…
Örnek:
sort.Strings(keys)
Örnek çıktısı:
[ANT ERZ IST]
Sıraladığımız key’lerin index değerlerine göre states nesnesindeki şehirleri yazdıralım.
Örnek:
for i := range keys {
fmt.Println(states[keys[i]])
}
Örnek çıktısı:
Antalya
Erzurum
İstanbul
Map İçerisindeki Bir Elemanın Varlığını Kontrol Etmek
Bazı durumlarda eldeki veriyi koleksiyon nesnelerindeki verileri karşılaştırarak verinin varlığını kontrol etmek
gerekebilir. Bu tür işlemler, normal arama ve bulma fonksiyonlarıyla yapılırken çok maliyetli olabilir. Ancak
key/value pair koleksiyon modelinde bu tür bir işlemi key üzerinden düşük maliyet ile yapabilmekteyiz.
Örnek:
state, ok := states["ANK"]
if !ok {
fmt.Println("Aranan veri bulunamadı!")
return
}
fmt.Println(state)
Yukarıdaki örnekte, states isimli Map nesnesinin ANK adında bir key’e sahip olup olmadığını kontrol ediyoruz.
Eğer bu Map nesnesi ANK isimli bir key’e sahipse, sol kısımda tanımladığımız ok değişkenine true değerini,
state değişkenine de eşleşen key’in value değerini dönecektir. Eğer bu eşleşme gerçekleşmezse, ok değişkenine
false değerini dönecektir.
Örnek sonucu:
Ankara
Yukarıdaki işlemde states içerisinde olmayan bir veriyi kontrol etmeyi deneyin. Ekrana “Aranan veri
bulunamadı.” mesajı basılacak ve hemen altındaki return komutu ile de işlem sonlandırılacaktır.
cihanozhan.com
29. Bu yöntemin daha doğru kullanımı şu şekildedir:
Örnek:
state, ok := states["ANK"]
if ok {
fmt.Println(state)
}
Yani, eğer Map içerisinde ANK ile eşleşen bir eleman varsa bunu ekrana bas, yoksa herhangi bir şey yapmana
gerek yoktur.
type Anahtar Kelimesi
Daha önce, Go dilinin yazılım geliştirme süreçlerindeki sıkıntılara odaklandığını belirtmiştik. type anahtar
kelimesi de, bu tür sorunlardan birine çözüm olmak için dile eklenen bir özelliktir. En temel anlamda
düşünürsek, Go kod dosyalarınızda tanımladığınız değişkenlerdeki kompozit(composite) tipleri sonradan
değiştirmek(redeclare) gibi süreçler baş ağrıtıcı olabilir. Bu nedenle, bu tür tipleri tanımlarken type anahtar
kelimesini kullanmak, sonradan revize etme gibi işlemlerde işinizi kolaylaştırabilir. Biraz karmaşık gibi görünse
de, bu konuyu örneklerle inceleyeceğiz.
Söz dizimi: Tekli
type typeName underlying-type
Söz dizimi: Çoklu
type (
typeName1 sourceType1
typeName2 sourceType2
)
type ile ilgili temel tanımlar:
- Yeni tanımlanan tip(typeName1 gibi) ile, onun altında yatan sourceType1(int gibi) tipleri aynıdır.
- Aynı alt tipler ile tanımlanan tipler birbirlerine dönüştürülebilir.
- type ile oluşturulacak tipler fonksiyonların gövdesinde tanımlanabilir.
type Kullanımı
Genel olarak type anahtar kelimesinin amacını anladığımıza göre, şimdi type ve farklı birçok veri tipini
kullanarak oluşturacağımız örnekleri inceleyebiliriz.
type ile kullanabileceğimiz bazı Go dili nesnelerini henüz incelemedik. Ancak type ile neler yapılabileceğini
anlamak adına bu örnekleri de bu başlık altında görebileceksiniz.
type anahtar kelimesinin en temel kullanımı şu şekildedir:
type Age int
Bu basit tanımlamada Age adında bir tip oluşturarak bu tipin arka planda bir integer ile temsil edileceğini
belirtmiş olduk. Bunun anlamı, Age’in veri tipi de kapasitesi de integer ile aynıdır.
type kullanımına çoklu tanımlama örneği olarak ise:
cihanozhan.com
30. type (
Name string
Age int
)
Bu tanımlama yönteminin temel amacı, birden fazla type nesnesinin dağınık olarak oluşturulmaktansa, daha
okunabilir ve düzenli olmasını sağlayacak şekilde bir arada tanımlanmasını sağlamaktır. Çoklu type bloğu
içerisinde farklı veri tiplerine sahip birden fazla type nesnesi oluşturabilirsiniz.
Şimdi type anahtar kelimesinin farklı tipler ile nasıl kullanılabileceğine bir göz atalım.
type anahtar kelimesini bir sonraki bölümde inceleyeceğimiz struct nesneleriyle birlikte de sık sık kullanacağız.
Buna da basit bir örnek verebiliriz:
Örnek:
type Video struct {
ID, Time int
Trainer, Title string
}
type anahtar kelimesini gene sonraki bölümlerde inceleyeceğimiz fonksiyonlar ile birlikte de kullanabileceğiz.
Örnek:
type Convert func(inputFile int, isFast bool)
(convertedFilePath string, err error)
type anahtar kelimesini daha önceki bölümlerde incelediğimiz dizi ve slice nesneleriyle birlikte de kullanabiliriz.
Go dili bu konuda çok esnek ve çeşitliliğe sahiptir. Hatırlarsanız, değişken ve nesne tanımlamada var adında bir
tanımlayıcı ve özel bir var scope’una sahiptik. Aynı şekilde var ile tanımlayabildiğimiz bu nesneleri type ile de
tanımlayabiliyoruz.
Örnek: Dizi için…
type Articles [5]string
Örnek: Slice için…
type Comments []string
Tanımlı Tipler & Tanımlanmamış Tipler
Go 1.9 versiyonu öncesinde tip tanımlaması üzerinde herhangi bir ayrım yapmamıza gerek yoktu. Ancak Go
1.9’dan sonra eklenen yeni kullanım yöntemiyle birlikte bir ayrım yapmamız gerekiyor.
Go 1.9 öncesinde kullandığımız yönteme zaten tanımlı tipler diyorduk. Go 1.9 sonrasında ise tanımsız(non-
defined type) diyebileceğimiz yeni bir kullanım yöntemi eklendi.
Şimdi bir alias tip oluşturarak tanımsız tiplere örnek verelim.
cihanozhan.com
31. Örnek:
type (
ID = int
Name = string
)
Yukarıdaki alias tipi kullanımının diğer kullanımdan farkı, tanımlayıcı isim ile veri tipi arasındaki eşittir(=)
işaretidir. Bu nedenle, eğer alias tipi olarak bir type tanımlayacaksanız aradaki eşittir(=) işaretini koymayı
unutmamalısınız.
Peki bu yöntemi nasıl ve neden kullanırız?
Örnek:
Kullanıcının adını ve veritabanındaki ID bilgisini tutacak bir Map değişkeni oluşturmak istediğimizi düşünelim.
Bu örnek aşağıdaki gibi olabilirdi:
type users = map[string]int
Kullanıcının isim bilgilerini users adındaki Map nesnesinin değer alanında, kullanıcının ID bilgilerini ise key
alanında tutabiliriz. Peki bu kullanımı biraz daha kullanıcılar verisinin yapısına göre tasarlamak istersek?
type users = map[Name]ID
Sanırım son oluşturduğumuz users Map nesnesinin biraz daha açık ve kullanıcı verisine göre özelleştirilmiş
olduğu konusunda hemfikiriz.
Şimdi tanımlı ve tanımsız tipleri karşılaştırarak bu konuyu sonlandıralım.
Örnek:
type Tip1 []string // Tip1 tanımlı tiptir.
type Tip2 = Tip1 // Tip2 nesnesi Tip1 nesnesiyle eşitlendi!
// Bu nedenle Tip2'de tanımlı bir tiptir.
type Tip3 = []string // Alias type(Tip3) ve []string tanımsız tiptir.
type Dönüştürme
type anahtar kelimesini tanımlarken, aynı alt veri tipine sahip type tiplerinin birbirlerine
dönüştürülebileceğinden bahsetmiştik. Şimdi bunu nasıl yapabileceğimize bir bakalım.
Örnek: Hatalı!
type age int16
var newAge int16 = 27
var UserAge age = newAge
Yukarıdaki örnekteki ilk satırda, int16 veri tipine sahip age adında bir tip oluşturduk. İkinci satırda ise, newAge
adında ve int16 veri tipine sahip bir değişken oluşturduk ve bu değişkene 27 değerini atadık. Üçüncü ve son
satırda ise, yeni oluşturduğumuz UserAge adındaki bir değişkene veri tipi olarak ilk satırda oluşturduğumuz age
adındaki tipi atadık. Ve bu UserAge değişkenine, age tipiyle aynı veri tipine sahip olan newAge değişkenini
atadık.
cihanozhan.com
32. Yukarıdaki örneği çalıştırmanıza gerek kalmadan hata verdiğini ve VSCode editörünün hatalı kodun altını
kırmızıyla çizdiğini görebilirsiniz. Bunun nedeni, age tipi ile newAge değişkenlerinin aynı veri tipine(int16)
sahip olsalar bile, derleyici tarafından karıştırılmasıdır. Derleyici bu iki nesnenin de aynı tipe sahip olduğunu
bilse bile, bu nesneleri birbirine atayabilmek için explicit conversion işlemi uygulanmasını şart koşuyor. Yani,
bu tipleri birbirine atamak istediğimizi özel olarak belirtmemiz gerekiyor. Peki nasıl?
Örnek:
type age int16
var newAge int16 = 27
var UserAge age = age(newAge)
Yukarıdaki örneği çalıştırdığınızda ekrana 27 değerinin basıldığını görebilirsiniz. Dikkat ederseniz, son
örneğimizin diğer örnekten tek farkı newAge değişkeni üzerinde uygulanan age tipine dönüştürme işlemidir. Bu
dönüştürmeyi de açık bir şekilde age(newAge) diyerek gerçekleştirdik.
cihanozhan.com