Функции и указатели

17.10.2018

Но преди това...

Въпрос за мъфин #1

Възможно ли е да използвам go1.11 за да компилирам програма от 2012г.? Защо?

Въпрос за мъфин #2

Какъв тип връша main функцията?

Въпрос за мъфин #3

Можем ли да дефинираме променлива без да пишем тима излишно?

Да - чрез type inference

a := 42
b := "a string"

Не можем да го ползваме в глобалния скоуп

Въпрос за мъфин #4

Как се import-ва пакет, публикуван публично в gitlab?

import "gitlab.com/user/awesomepack"

Въпрос за мъфин #5

Какъв е идиоматичния начин за правене на enum в Go?

За какво се използва iota в Go и коя подред буква в гръцката азбука е?

const (
    City = iota
    Town
    Village
)

Здравна сигурност

Мъфините съдържат

... и няколко неща, които пропуснахме

Това е гофер (gopher)

Те са сладки

Отворете сърцето си за gopher-ите

note: Не сме секта.

Credits

Сладкия Gopher е създаден от Renée French. Той е централна причина за успеха на езика.

Funcs 101

С ключовата дума func могат да се създават три неща

1. Функции

2. Ламбди

3. Методи

Функции

package main

import (
    "fmt"
    "strings"
)

func ping() {
    fmt.Println("pong")
}

func myprint(s string) {
    fmt.Println(s)
}

func shout(s string) string {
    return strings.ToUpper(s) + "!!!1!"
}

func main() {
    ping()
    myprint("woot")
    myprint(shout("woot"))
}

Аргументи

func foo(a int, b string) float64

Функцията foo приема int и string и връща float64

Когато няколко аргумента са от един тип:

func bar(a, b int, c float64) float64

Произволен брой аргументи

func sum(args ...int) int

Функцията sum приема произволен брой числа и връща техния сбор

func sum(args ...int) int {
    result := 0
    for _, v := range args {
        result += v
    }

    return result
}

Извикваме я с колкото ни трябват

sum()           //0
sum(2, 3)       //5
sum(2, 3, 4, 5) //14

Трябва да е последния аргумент на функцията
* Следващия път ще говорим по-подробно за range

Множество стойности като резултат

package main

import "fmt"

func main() {
    sum, count := sumAndCount(2, 3, 4, 5)
    fmt.Println("Резултатът от sumAndCount() е", sum, count)
}

func sumAndCount(args ...int) (int, int) {
    result := 0
    for _, v := range args {
        result += v
    }

    return result, len(args)
}

Защо?

Как е реализирано в "стари" езици:

Как е реализирано в Go?

Ами ако не ни трябват всичките резултати?

1. Знаем, че ако дефинираме променлива и не я използваме, гърми
2. Ако искаме онзи сбор и не ни интересува броят аргументи, това ще изгърми

result, count := sumAndCount(2, 3, 4, 5)

3. Ако нямаме нужда от дадена стойност, я присвояваме на _:

result, _ := sumAndCount(2, 3, 4, 5)

Именовани резултати

func sumAndCount(args ...int) (result int, count int) {
    count = len(args)
    for _, v := range args {
        result += v
    }

    return
}

Фунцкиите като стойности

func foo(bar func(int, float64) float64) float64 {
    return bar(5, 3.2)
}
func createRandomGenerator() func() int {
    return func() int {
        return 4
    }
}

...или да изпълни себе си

package main

import "fmt"

func main() {
	fmt.Println("factorial(5) returns:")
	fmt.Println(factorial(5))
}

func factorial(x uint) uint {
    if x == 0 {
        return 1
    }

    return x * factorial(x-1)
}

Именоване на функции

Анонимни функции

func(x, y int) int { return x * y }
package main

import "fmt"

func main() {
    add := func(x, y int) int { return x + y }
    fmt.Println(add(4, 5))

    fmt.Println(func(x, y int) int { return x + y }(3, 8))
}

Scope, visibility & escape analysis

package main

import "fmt"

func counter(start int) func() int {
    count := start
    return func() int {
        count++
        return count
    }
}

func main() {
    c := counter(3)
    fmt.Println("The first 3 values are", c(), c(), c())
}

Променливата count нужна ли е въобще?

defer

package main

import "fmt"

func main() {
    fmt.Println("counting")

    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("done")
}

Пример:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

Какви са проблемите с този код?

По-красивият, правилен и работещ начин е това:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

defer се използва за сигурно и лесно почистване на ресурси (отворени файлове, заключени mutex-и, etc.)

Доуточнения

Три прости правила за defer (1)

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

Три прости правила за defer (2)

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

Три прости правила за defer (3)

func c() (i int) {
    defer func() { i++ }()
    return 1
}

Примери

package main

import (
	"fmt"
)

func deferExample() {
    for i := 0; i < 5; i++ {
        defer func(i int) {
            fmt.Printf(" %v", i)
        }(i)
    }
}

func main() {
	deferExample()
}

-

package main

import (
	"fmt"
)

func deferExample() {
    for i := 0; i < 5; i++ {
        defer func() {
            fmt.Printf(" %v", i)
        }()
    }
}

func main() {
	deferExample()
}

Методи

За тях ще си говорим като стигнем до дефиниране на типове

Указатели

Указатели

За останалите: Опреснителен курс

Пример

package main

import "fmt"

var (
    name   string = "Чочко"
    age    uint8  = 27
    pName *string
)

func main() {
    pName = &name
    fmt.Printf("name е на адрес %p и има стойност %s\n", pName, name)
    fmt.Printf("age е на адрес %p и има стойност %d\n", &age, age)
}

Как да си направим дупка в крака?

package main

func main() {
    var p *int = nil
    *p = 0
}

Указатели и функции

func foo(a int, b *string) float64

Функцията foo приема int и указател към string и връща float64

Демек a бива копиран в скоупа на foo, а b просто сочи към някаква стойност отвън.

*Следващия път ще видим защо указател към string може да не ни спести кой-знае колко памет

Въпроси?