Files
merdincz ea4a5390e5 Update 02.3.md
typo fix
2018-03-31 21:38:03 +03:00

519 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2.3 Kontrol ifadeleri ve fonksiyonlar
Bu bölümde, Go da kontrol ifadeleri ve fonksiyon işlemleri hakkında konuşacağız.
## Kontrol ifadesi
Akış kontrolü programcılıkta en büyük icattır. Onun sayesinde, basit kontrol ifadeleri kullanarak daha karmaşık lojik ifadeler temsil edilebilir. Akış kontrolünü üç kategoriye ayırabiliriz: şartlı, döngü kontrollü ve şartsız çıkış.
### if
`if` kelimesi büyük ihtimalle programlarında en sık geçen kelimedir. Eğer şartlar sağlanırsa birşeyler yapar, sağlanmazsa başka şeyler yapar.
`if` şartları için Go da paranteze gerek yoktur.
if x > 10 {
fmt.Println("x 10'dan büyüktür")
} else {
fmt.Println("x 10'dan küçük veya eşittir")
}
Go da çok işe yarar bir `if` kullanımı, şart ifadesinden önce değişken tanımlama yapmaktır. Bu değişkenin kapsamı sadece `if` blok alanı içerisinde geçerlidir.
// x'i tanımla, ve 10 dan büyük olup olmadığını kontrol et
if x := islenenDeger(); x > 10 {
fmt.Println("x 10'dan büyüktür")
} else {
fmt.Println("x 10'dan küçüktür")
}
// Bu satır derlenemez
fmt.Println(x)
Birden fazla şart için `if-else` kullanın.
if sayı == 3 {
fmt.Println("Sayı 3'e eşittir")
} else if integer < 3 {
fmt.Println("Sayı 3'ten küçüktür")
} else {
fmt.Println("Sayı 3'ten büyüktür")
}
### goto
Go da `goto` terimi mevcuttur fakat kullanırken dikkatli olun. `goto` programın kontrol akışını önceden belirlenmiş aynı gövde içindeki bir `label` a yönlendirir.
func myFunc() {
i := 0
Buraya: // label sonuna ":" koyulur
fmt.Println(i)
i++
goto Buraya // "Buraya" labeline git
}
Label'ın adı büyük-küçük harfe duyarlıdır.
### for
`for` Go da bulunan en güçlü kontrol lojiğidir. Datayı döngüsel olarak ve tekrarlı işlemlerle okuyabilir, `while` döngüsü gibi.
for ifade1; ifade2; ifade3 {
//...
}
`ifade1`, `ifade2` ve `ifade3` birer ifade olmak üzere, `ifade1` ve `ifade3` değişken tanımlama veya bir fonksiyondan dönen değer olabilirken, `ifade2` ise kontrol ifadesidir. `ifade1` ifadesi döngüye girmeden önce bir kere işlenecektir. `ifade3` ise her döngü sonunda işlenir.
Örnekler düz yazıdan daha yararlı olacaktır.
package main
import "fmt"
func main(){
toplam := 0;
for index:=0; index < 10 ; index++ {
toplam += index
}
fmt.Println("sayıların toplamı= ", toplam)
}
// Ekran çıktısısayıların toplamı= 45
Bazen birden fazla atama yapmak gerekir fakat Go bunun için kullanılacak bir `,` operatörü yoktur. Biz de `i, j = i + 1, j - 1` gibi paralel atamalar yaparız.
İhtiyaç yoksa `ifade1` ve `ifade3` çıkartılabilir.
toplam := 1
for ; toplam < 1000; {
toplam += toplam
}
Hatta `;` bile çıkarılabilir. Tanıdık geldi mi? Evet, tamamen `while` gibi oldu.
toplam := 1
for toplam < 1000 {
toplam += toplam
}
Döngülerde `break` ve `continue` adında iki önemli işlem vardır. `break` döngüden çıkartır ve `continue` o anki tekrarı atlar ve sonraki tekrara geçer. Eğer birden fazla iç içe döngüleriniz varsa `break` ile labelları kullabilirsiniz.
for index := 10; index>0; index-- {
if index == 5{
break // veya continue
}
fmt.Println(index)
}
// break varsa yazılanlar: 10、9、8、7、6
// continue varsa yazılanlar: 10、9、8、7、6、4、3、2、1
`for` döngüsü `range` kullanıldığında `slice` ve `map` de bulunan datayı okuyabilir.
for k,v:=range map {
fmt.Println("map'in k anahtarı:", k)
fmt.Println("map'in k anahtarındaki değeri:", v)
}
Go da birden fazla değer return yapılabildiği ve kullanılmayan bir değişken olduğunda derleyici hata verdiği için, kullanmak istemediğiniz değişkenler için `_` kullanabilirsiniz.
for _, v := range map{
fmt.Println("map'in değeri:", v)
}
### switch
Bazı durumlarda çok fazla `if-else` kullandığınızı farkedebilirsiniz. Bu programı okumayı zorlaştırır ve gelecekte bakım yapmayı zorlaştırabilir. Bu durumda problemi çözmek için `switch` kullanmak mükemmeldir.
switch anaIfade {
case ifade1:
// eşleşme olursa işlenecek kod
case ifade2:
// eşleşme olursa işlenecek kod
case ifade3:
// eşleşme olursa işlenecek kod
default:
// eşleşme olmazsa işlenecek kod
}
`sExpr`, `expr1`, `expr2`, ve `expr3` ifadelerinin türleri aynı olmalıdır. `switch` çok esnektir. Şartlar sabit olmak zorunda değildir ve şart sağlanana kadar yukarıdan aşağıya doğru çalışır. Eğer `switch` in ardından bir ifade gelmiyorsa `true` olarak görülür.
i := 10
switch i {
case 1:
fmt.Println("i 1'e eşittir")
case 2, 3, 4:
fmt.Println("i 2, 3 veya 4'e eşittir")
case 10:
fmt.Println("i 10'a eşittir")
default:
fmt.Println("Tek bildiğim i nin bir sayi olduğu")
}
Beşinci satırdaki gibi `case` içinde birden fazla değer olabilir. `case` sonlarına `break` eklemeye gerek yoktur, şart sağlanıp işlem yapıldıktan sonra çıkacaktır. Eğer çıkmasını istemiyorsanız `fallthrough` ifadesini kullanarak bir sonraki şarta devam edebilirsiniz.
sayı := 6
switch sayı {
case 4:
fmt.Println("sayı <= 4")
fallthrough
case 5:
fmt.Println("sayı <= 5")
fallthrough
case 6:
fmt.Println("sayı <= 6")
fallthrough
case 7:
fmt.Println("sayı <= 7")
fallthrough
case 8:
fmt.Println("sayı <= 8")
fallthrough
default:
fmt.Println("default durumu")
}
Programın sonucu şu olacaktır.
sayı <= 6
sayı <= 7
sayı <= 8
default durumu
## Fonksiyonlar
`func` terimini kullanarak bir fonksiyon tanımlayın.
func fonksiyonAdı(parametre1 tür1, parametre2 tür2) (çıkış1 tür1, çıkış2 tür2) {
// fonksiyon gövdesi
// birden fazla return
return dönüş1, dönüş2
}
Yukarıdaki örnekten tahmin edebileceğiniz üzere aşağıda açıklamaları bulunur.
- `fonksiyonAdı` adlı fonksiyonu tanımlamak için `func` terimini kullanın.
- Fonksiyonlar sıfır veya daha fazla parametreye sahip olabilir. Parametrenin türü adından sonra gelir ve birden fazla parametre varsa `,` ile ayrılır.
- Fonksiyonlar birden fazla değer döndürebilirler.
- Bu örnekte ıkış1` ve ıkış2` adında iki değer döndürülmüş. Bunlara ad vermek zorunda değilsiniz, türünü yazmanız yeterli.
- Eğer sadece bir değer döndürecekseniz parantez olmadan yazmalısınız.
- Eğer en az bir değer döndürüyorsanız, fonksiyonun içinde istediğiniz yerde `return` terimini kullanmalısınız.
Şimdi pratik bir örnek görelim. (Maksimum değerini hesaplama)
package main
import "fmt"
// a ile b arasından büyük olanı döndür
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
x := 3
y := 4
z := 5
max_xy := max(x, y) // max(x, y) fonskiyonunu çağır
max_xz := max(x, z) // max(x, z) fonksiyonunu çağır
fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // burada da fonksiyon çağırıldı
}
Yukarıdaki örnek fonksiyon `max` da iki aynı tür parametre `int` olduğu için bir tane yazmak yeterli olur. Yani `a int, b int` yerine `a, b int` kullanılır. Birden fazla parametre için de aynı kural geçerlidir. Farkettiyseniz `max` fonksiyonu sadece bir değer döndürür ve zorunda olmadığımız için o değere bir isim vermedik, bu kısa halini kullandık.
### Çok değerli döndürme
Go'nun C'den iyi olduğu bir şey birden fazla değer döndürmeyi desteklemesidir.
Alttaki örnekte bunu kullanalım.
package main
import "fmt"
// A+B ve A*B nin sonuçlarını döndür
func toplaVeCarp(A, B int) (int, int) {
return A+B, A*B
}
func main() {
x := 3
y := 4
xARTIy, xCARPIy := toplaVeCarp(x, y)
fmt.Printf("%d + %d = %d\n", x, y, xARTIy)
fmt.Printf("%d * %d = %d\n", x, y, xCARPIy)
}
Üstteki fonksiyon isimsiz iki değer döndürür -isterseniz isim verebilirsiniz. Eğer isimlendirseydik, `return` yazıp isimlerini yazmamız yeterdi. Çünkü fonksiyonun içinde tanımlılar. Şuna dikkat etmelisiniz ki eğer fonksiyonu başka bir pakette kullanacaksanız (fonksiyonun ilk harfi büyük harfle başlamalıdır) `return` yapacaklarınızı tam bir ifade olarak yazmanız daha iyi olacaktır. Kodu daha okunur hale getirir.
func ToplaVeCarp(A, B int) (toplanan int, carpılan int) {
toplanan = A+B
carpılan = A*B
return
}
### Variadic fonksiyonlar
Go birden fazla argüman alabilen fonksiyonları destekler. Bunlara variadic (belirsiz-değişen sayıda argüman alan) fonksiyon denir.
func myfunc(arg ...int) {}
`arg …int` kısmı Go ya bu fonksiyonun değişen sayıda argüman aldığını söyler. Bu argümanların türü `int` dir. `arg` fonksiyonun gövdesinde `int` türünde bir `slice` olur.
for _, n := range arg {
fmt.Printf("Sayı: %d\n", n)
}
### Değer ile devretmek ve pointerlar
Bir fonksiyon çağırıp ona argüman verdiğimizde o fonksiyon aslında değişkenin bir kopyasını alır. Dolayısı ile yapılan işlemler değişkende bir değişiklik yaratmaz.
Bunun kanıtı olarak bir örnek görelim.
package main
import "fmt"
// a ya 1 eklemek için basit bir fonksiyon
func birEkle(a int) int {
a = a+1 // a'nın değeri değişti
return a // a'nın yeni değeri döndürülüyor
}
func main() {
x := 3
fmt.Println("x = ", x) // sonuç "x = 3" olmalı
x1 := birEkle(x) // birEkle(x) fonksiyonu çağırıldı
fmt.Println("x+1 = ", x1) // sonuç "x+1 = 4" olmalı
fmt.Println("x = ", x) // sonuç "x = 3" olmalı
}
Gördünüz mü? `birEkle` fonksiyonuna `x` i gönderdiğimiz halde asıl değeri değişmedi.
Sebebi basit: `birEkle` i çağırdığımızda ona `x` in bir kopyasını gönderdik `x` in kendisini değil.
Şimdi sorabilirsiniz `x` in kendisini nasıl fonksiyona verebilirim diye.
Burada pointer kullanmamız gerekiyor. Biliyoruz ki değişkenler bellekte tutulur ve bir adresleri vardır. Eğer değişkenin aslını değiştirmek istiyorsak onun bellek adresini kullanmalıyız. Böylelikle `birEkle` fonksiyonu `x` in adresini kullanarak onun değerini değiştirebilir. Parametre türünü `*int` olarak değiştiriyoruz ve değişkenin adresini `&x` ile fonksiyona veriyoruz. Fonksiyona değerin bir kopyasını değil de bir pointer verdiğimize dikkat edin.
package main
import "fmt"
// a ya 1 eklemek için basit bir fonksiyon
func birEkle(a *int) int {
*a = *a+1 // a'nın değeri değişti
return *a // a'nın yeni değeri döndürülüyor
}
func main() {
x := 3
fmt.Println("x = ", x) // sonuç "x = 3" olmalı
x1 := birEkle(&x) // birEkle(x) fonksiyonu çağırıldı ve x'in adresi verildi
fmt.Println("x+1 = ", x1) // sonuç "x+1 = 4" olmalı
fmt.Println("x = ", x) // sonuç "x = 4" olmalı
}
Şimdi `x` in asıl değerini değiştirebiliriz. Neden pointer kullanıyoruz? Avantajı nedir?
- Bir değişken üzerinde çalışmak için birden fazla fonksiyon kullanabilmemize olanak sağlar.
- Adres (8 byte) verdiğimiz için maliyet azalır. Değerin kopyasını vermek maliyet ve zaman için verimli bir yöntem değildir.
- `string`, `slice`, `map` referans türlerdir. Yani fonksiyona verilirken standart olarak pointer olarak verilirler. (Dikkat: Eğer `slice` ın uzunluğunu değiştirmek istiyorsanız açıkça pointer olarak vermeniz gerekir.)
### defer
Go da iyi tasarlanmış `defer` (ertelemek) adlı bir terim vardır. Bir fonksiyonda birden fazla `defer` ifadesi bulunabilir, program çalıştığında sondan başa sırayla çalışacaklardır. Programın dosya açtığı durumlarda bu dosyaların hata vermeden önce kapatılması gerekir. Örneklere bakalım.
func OkuYaz() bool {
file.Open("dosya")
// Kod
if hataX {
file.Close()
return false
}
if hataY {
file.Close()
return false
}
file.Close()
return true
}
Bazı kodların tekrar ettiğini görüyoruz. `defer` bu problemi çok iyi çözer. Daha temiz kod yazmanıza yardım etmekle kalmaz kodunuzu daha okunur yapar.
func OkuYaz() bool {
file.Open("dosya")
defer file.Close()
if hataX {
return false
}
if hataY {
return false
}
return true
}
Eğer birden fazla `defer` varsa ters sırayla çalışırlar. Sıradaki örnek `4 3 2 1 0` sonucunu yazar.
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
### Değer ve tür olarak fonksiyonlar
Go'da fonksiyonlar aynı zamanda değişken olabilirler. `type` onları kullanarak tanımlayabiliriz. Aynı imzaya sahip fonksiyonlar aynı tür olarak görülebilir.
type türAdı func(parametre1 tür1, parametre2 tür2 [, ...]) (çıkış1 tür1 [, ...])
Bunun avantajı nedir? Cevap, fonksiyonları değer olarak verebilmemizi sağlamasıdır.
package main
import "fmt"
type testInt func(int) bool // değişken olarak fonksiyon tanımlandı
func tekMi(sayı int) bool {
if sayı%2 == 0 {
return false
}
return true
}
func çiftMi(sayı int) bool {
if sayı%2 == 0 {
return true
}
return false
}
// 'f' fonksiyonunu bir fonksiyona parametre olarak ata
func filtre(slice []int, f testInt) []int {
var sonuc []int
for _, deger := range slice {
if f(deger) {
sonuc = append(sonuc, deger)
}
}
return sonuc
}
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
tek := filtre(slice, tekMi) // fonksiyonu değer olarak kullan
fmt.Println("Slice'daki tek sayılar: ", tek)
çift := filtre(slice, çiftMi)
fmt.Println("Slice'daki çift sayılar: ", çift)
}
Interface kullanılan durumlarda çok yararlıdır. Gördüğünüz gibi `testInt` fonksiyon türünde bir değişkendir ve `filtre`'nin döndürdüğü argümanlar ve değerler `testInt` ile aynıdır. Böylelikle programlarımızda karmaşık mantık yürütebilir ve esneklik kazanabiliriz.
### Panic ve Recover
Go da Java gibi `try-catch` yapısı yoktur. Go exception fırlatmak yerine `panic` ve `recover` ile hatalarla ilgilenir. Etkili olmasına karşın `panic` çok fazla kullanılmamalıdır.
`Panic` programın normal akışını durdurmak ve panik haline sokmak için kullanılır. `F` adında bir fonksiyon `panic` çağırdığında fonksiyon çalışmayı durdurur ve ertelenen `defer` fonksiyonları çalıştırılır. Sonra `F` panik halinin başladığı yere döner. Bütün fonksiyonlar `F` gibi başladığı yerdeki `goroutine` e dönmeden program sonlanmaz. `panic` programın içinde `panic` olarak çağırıldığında gerçekleşir. Ayrıca dizi sınırları dışına erişim hataları gibi bazı hatalar da `panic` sebebi olabilir.
`Recover` (kurtarmak) `goroutine` leri panik halinden kurtarmak için kullanılır. `defer` fonksiyonlarının içinde `recover` çağırmak yararlıdır çünkü panik halinde normal fonksiyonlar çalışmayı durdurur. Çağırıldığında varsa `panic` değerlerini yakalar yoksa `nil` yakalar.
Aşağıdaki örnekte `panic` nasıl kullanılır gösterilmiştir.
var kullanıcı = os.Getenv("KULLANICI")
func init() {
if kullanıcı == "" {
panic("$KULLANICI için bir değer bulunamadı")
}
}
Bu örnek de `panic` kontrolü yapılmasını gösterir.
func panicYapar(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f() // eğer f() panic yaparsa recover yapılır
return
}
### `main` ve `init` fonksiyonları
`init` fonksiyonu tüm paketlerde kullanılabilirken `main` fonksiyonu sadece `main` paketinde kullanılabilir. Bu iki fonksiyonun parametreleri yoktur ve değer döndürmezler. Bir paket içinde birden çok `init` yazılabilse de sadece bir tane yazılmasını kesinlikle tavsiye ederim.
Go programları `init()` ve `main()` i otomatik çağırır. Her paket için `init` fonksiyonu isteğe bağlı iken `main` paketi için sadece bir tane `main` fonksiyonu kesinlikle bulunmak zorundadır.
Programlar işleme `main` paketinden başlar. Eğer `main` başka paketler dahil (import) ediyorsa derleme sürecinde onlar da dahil edilir. Bir paket birden fazla kez dahil edildiyse bile sadece bir kere derlenir. Paketler dahil edildikten sonra program bu paketlerdeki değişkenleri ve sabitleri parafe eder ve sonra varsa `init` çalıştırılır ve böyle devam eder. Bütün paketler parafe edildikten sonra program `main` paketindeki değişkenleri ve sabitleri parafe eder ve varsa `init` çalıştırılır. Alttaki şekil bu süreci gösterir.
![](images/2.3.init.png?raw=true)
Şekil 2.6 Go da programların parafe edilmesinin akışı
### import
Go programlarında `import` (dahil etmek) aşağıdaki gibi çok sık kullanılır
import(
"fmt"
)
Sonra dahil edilen bu paketteki fonksiyonlar aşağıdaki gibi kullanılır.
fmt.Println("hello world")
`fmt` Go standart kütüphanesinde bulunur. Bilgisayarda $GOROOT/pkg da bulunur. Go üçüncü şahış paketlerini iki şekilde destekler.
1. Göreceli yol
import "./model" // load package in the same directory, I don't recommend this way.
2. Kesin yol
import "shorturl/model" // load package in path "$GOPATH/pkg/shorturl/model"
Paketler dahil edilirken kullanılan özel operatörler vardır ve bunlar genelde yeni başlayanların kafalarını kurcalar.
1. Nokta operatörü
Bazen aşağıdaki gibi kullanıldığını görürüz.
import(
. "fmt"
)
Nokta operatörünün görevi, bir paketten fonksiyon çağırırken paketin adının yazılma gereksinimini kaldırmasıdır. Böylelikle `fmt.Printf("Hello world")` yerine `Printf("Hello world")` yazılır.
2. Takma isim verme
Pakete takma isim vererek fonksiyon çağırırken kullanılan ad değiştirilir.
import(
f "fmt"
)
Böylelikle `fmt.Printf("Hello world")` yerine `f.Printf("Hello world")` yazılır.
3. `_` operatorü
Bunu anlamak zordur özellikle biri size açıklamadıysa.
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)
Bu operatör dahil edilen paketin sadece `init` fonksiyonun çağrılıp çalıştırılması için kullanılır. Paketteki diğer fonksiyonları kullanacağınıza emin değilseniz kullanabilirsiniz.
## Linkler
- [Rehber](preface.md)
- Önceki bölüm: [Go temelleri](02.2.md)
- Sonraki bölüm: [struct](02.4.md)