Files
build-web-application-with-…/tr/02.3.md
Bora Kaplan 76e7b5d2af Translated 02.3.md to Turkish
Code blocks are not translated yet.
2016-08-31 02:19:32 +03:00

18 KiB
Raw Blame History

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 is greater than 10")
} else {
	fmt.Println("x is less than or equal to 10")
} 

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 := computedValue(); x > 10 {
	fmt.Println("x is greater than 10")
} else {
	fmt.Println("x is less than 10")
}

// Bu satır derlenemez
fmt.Println(x)

Birden fazla şart için if-else kullanın.

if integer == 3 {
	fmt.Println("The integer is equal to 3")
} else if integer < 3 {
	fmt.Println("The integer is less than 3")
} else {
	fmt.Println("The integer is greater than 3")
}

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
Here:   // label ends with ":"
	fmt.Println(i)
	i++
	goto Here   // jump to label "Here"
}

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 expression1; expression2; expression3 {
	//...
}

expression1, expression2 ve expression3 birer ifade olmak üzere, expression1 ve expression3 değişken tanımlama veya bir fonksiyondan dönen return olabilirken, expression2 ise kontrol ifadesidir. expression1 ifadesi döngüye girmeden önce bir kere işlenecektir. expression3 ise her döngü sonunda işlenir.

Örnekler düz yazıdan daha yararlı olacaktır.

package main
import "fmt"

func main(){
	sum := 0;
	for index:=0; index < 10 ; index++ {
    	sum += index
	}
	fmt.Println("sum is equal to ", sum)
}
// Printsum is equal to 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.

İhtiyacımız yoksa expression1 ve expression1 ifadelerini çıkarabiliriz.

sum := 1
for ; sum < 1000;  {
	sum += sum
}

Hatta ; bile çıkarılabilir. Tanıdık geldi mi? Evet, tamamen while gibi oldu.

sum := 1
for sum < 1000 {
	sum += sum
}

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 // or continue
	}
	fmt.Println(index)
}
// break prints 10、9、8、7、6
// continue prints 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's key:",k)
	fmt.Println("map's val:",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's val:", 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 sExpr {
case expr1:
	some instructions
case expr2:
	some other instructions
case expr3:
	some other instructions
default:
	other code
}

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 is equal to 1")
case 2, 3, 4:
	fmt.Println("i is equal to 2, 3 or 4")
case 10:
	fmt.Println("i is equal to 10")
default:
	fmt.Println("All I know is that i is an integer")
}

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.

integer := 6
switch integer {
case 4:
	fmt.Println("integer <= 4")
	fallthrough
case 5:
	fmt.Println("integer <= 5")
	fallthrough
case 6:
	fmt.Println("integer <= 6")
	fallthrough
case 7:
	fmt.Println("integer <= 7")
	fallthrough
case 8:
	fmt.Println("integer <= 8")
	fallthrough
default:
	fmt.Println("default case")
}

This program prints the following information.

integer <= 6
integer <= 7
integer <= 8
default case

Functions

func terimini kullanarak bir fonksiyon tanımlayın.

func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
	// function body
	// multi-value return
	return value1, value2
}

Yukarıdaki örnekten tahmin edebileceğiniz üzere aşağıda açıklamaları bulunur.

  • funcName adlı foonksiyonu 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 output1 ve output2 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"

// return greater value between a and b
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) // call function max(x, y)
	max_xz := max(x, z) // call function max(x, z)

	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)) // call function here
}

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"

// return results of A + B and A * B
func SumAndProduct(A, B int) (int, int) {
return A+B, A*B
}

func main() {
	x := 3
	y := 4

	xPLUSy, xTIMESy := SumAndProduct(x, y)

	fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
	fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}

Ü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 SumAndProduct(A, B int) (add int, multiplied int) {
	add = A+B
	multiplied = 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("And the number is: %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"

// simple function to add 1 to a
func add1(a int) int {
	a = a+1 // we change value of a 
	return a // return new value of a
}

func main() {
	x := 3

	fmt.Println("x = ", x)  // should print "x = 3"

	x1 := add1(x)  // call add1(x)

	fmt.Println("x+1 = ", x1) // should print "x+1 = 4"
	fmt.Println("x = ", x)    // should print "x = 3"
}

Gördünüz mü? add1 fonksiyonuna x i gönderdiğimiz halde asıl değeri değişmedi.

Sebebi basit: add1 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 add1 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"

// simple function to add 1 to a
func add1(a *int) int {
	*a = *a+1 // we changed value of a
	return *a // return new value of a
}

func main() {
	x := 3

	fmt.Println("x = ", x)  // should print "x = 3"

	x1 := add1(&x)  // call add1(&x) pass memory address of x

	fmt.Println("x+1 = ", x1) // should print "x+1 = 4"
	fmt.Println("x = ", x)    // should print "x = 4"
}

Ş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 ReadWrite() bool {
	file.Open("file")
// Do some work
	if failureX {
    	file.Close()
    	return false
	}

	if failureY {
    	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 ReadWrite() bool {
	file.Open("file")
	defer file.Close()
	if failureX {
    	return false
	}
	if failureY {
    	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 ayno tür olarak görülebilir.

type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])

Bunun avantajı nedir? Cevap, fonksiyonları değer olarak verebilmemizi sağlamasıdır.

package main
import "fmt"

type testInt func(int) bool // define a function type of variable

func isOdd(integer int) bool {
	if integer%2 == 0 {
    	return false
	}
	return true
}

func isEven(integer int) bool {
	if integer%2 == 0 {
    	return true
	}
	return false
}

// pass the function `f` as an argument to another function

func filter(slice []int, f testInt) []int {
	var result []int
	for _, value := range slice {
    	if f(value) {
        	result = append(result, value)
    	}
	}
	return result
}

func main(){
	slice := []int {1, 2, 3, 4, 5, 7}
	fmt.Println("slice = ", slice)
	odd := filter(slice, isOdd)    // use function as values
	fmt.Println("Odd elements of slice are: ", odd)
	even := filter(slice, isEven) 
	fmt.Println("Even elements of slice are: ", even)
}

Interface kullanılan durumlarda çok yararlıdır. Gördüğünüz gibi testInt fonksiyon türünde bir değişkendir ve filter ın 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 user = os.Getenv("USER")

func init() {
	if user == "" {
    	panic("no value for $USER")
	}
}

Bu örnek de panic kontrolü yapılmasını gösterir.

func throwsPanic(f func()) (b bool) {
	defer func() {
    	if x := recover(); x != nil {
        	b = true
    	}
	}()
	f() // if f causes panic, it will recover
	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.

Figure 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.