Files
build-web-application-with-…/tr/02.3.md
2016-08-31 01:34:43 +03:00

518 lines
18 KiB
Markdown
Raw 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 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`
It's very useful when we use interfaces. As you can see `testInt` is a variable that has a function as type and the returned values and arguments of `filter` are the same as those of `testInt`. Therefore, we can have complex logic in our programs, while maintaining flexibility in our code.
### Panic and Recover
Go doesn't have `try-catch` structure like Java does. Instead of throwing exceptions, Go uses `panic` and `recover` to deal with errors. However, you shouldn't use `panic` very much, although it's powerful.
`Panic` is a built-in function to break the normal flow of programs and get into panic status. When a function `F` calls `panic`, `F` will not continue executing but its `defer` functions will continue to execute. Then `F` goes back to the break point which caused the panic status. The program will not terminate until all of these functions return with panic to the first level of that `goroutine`. `panic` can be produced by calling `panic` in the program, and some errors also cause `panic` like array access out of bounds errors.
`Recover` is a built-in function to recover `goroutine`s from panic status. Calling `recover` in `defer` functions is useful because normal functions will not be executed when the program is in the panic status. It catches `panic` values if the program is in the panic status, and it gets `nil` if the program is not in panic status.
The following example shows how to use `panic`.
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
The following example shows how to check `panic`.
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` function and `init` function
Go has two retentions which are called `main` and `init`, where `init` can be used in all packages and `main` can only be used in the `main` package. These two functions are not able to have arguments or return values. Even though we can write many `init` functions in one package, I strongly recommend writing only one `init` function for each package.
Go programs will call `init()` and `main()` automatically, so you don't need to call them by yourself. For every package, the `init` function is optional, but `package main` has one and only one `main` function.
Programs initialize and begin execution from the `main` package. If the `main` package imports other packages, they will be imported in the compile time. If one package is imported many times, it will be only compiled once. After importing packages, programs will initialize the constants and variables within the imported packages, then execute the `init` function if it exists, and so on. After all the other packages are initialized, programs will initialize constants and variables in the `main` package, then execute the `init` function inside the package if it exists. The following figure shows the process.
![](images/2.3.init.png?raw=true)
Figure 2.6 Flow of programs initialization in Go
### import
We use `import` very often in Go programs as follows.
import(
"fmt"
)
Then we use functions in that package as follows.
fmt.Println("hello world")
`fmt` is from Go standard library, it is located within $GOROOT/pkg. Go supports third-party packages in two ways.
1. Relative path
import "./model" // load package in the same directory, I don't recommend this way.
2. Absolute path
import "shorturl/model" // load package in path "$GOPATH/pkg/shorturl/model"
There are some special operators when we import packages, and beginners are always confused by these operators.
1. Dot operator.
Sometime we see people use following way to import packages.
import(
. "fmt"
)
The dot operator means you can omit the package name when you call functions inside of that package. Now `fmt.Printf("Hello world")` becomes to `Printf("Hello world")`.
2. Alias operation.
It changes the name of the package that we imported when we call functions that belong to that package.
import(
f "fmt"
)
Now `fmt.Printf("Hello world")` becomes to `f.Printf("Hello world")`.
3. `_` operator.
This is the operator that is difficult to understand without someone explaining it to you.
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)
The `_` operator actually means we just want to import that package and execute its `init` function, and we are not sure if want to use the functions belonging to that package.
## Links
- [Directory](preface.md)
- Previous section: [Go foundation](02.2.md)
- Next section: [struct](02.4.md)