diff --git a/lectures/01-basics/append/append.go b/lectures/01-basics/append/append.go new file mode 100644 index 0000000..7454dfa --- /dev/null +++ b/lectures/01-basics/append/append.go @@ -0,0 +1,3 @@ +package main + +func append(s []int, elem ...int) []int diff --git a/lectures/01-basics/countwords/main.go b/lectures/01-basics/countwords/main.go new file mode 100644 index 0000000..9f36964 --- /dev/null +++ b/lectures/01-basics/countwords/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "net/http" + + "golang.org/x/net/html" +) + +func CountWordsAndImages(url string) (words, images int, err error) { + resp, err := http.Get(url) + if err != nil { + return + } + doc, err := html.Parse(resp.Body) + resp.Body.Close() + if err != nil { + err = fmt.Errorf("parsing HTML: %s", err) + return + } + words, images = countWordsAndImages(doc) + return +} + +func countWordsAndImages(n *html.Node) (words, images int) { + return +} diff --git a/lectures/01-basics/exported/example.go b/lectures/01-basics/exported/example.go new file mode 100644 index 0000000..c027fc4 --- /dev/null +++ b/lectures/01-basics/exported/example.go @@ -0,0 +1,13 @@ +package example + +const C0 = 9.8 +const c1 = 15 + +var V0 string +var v1 string + +func F0() {} +func f1() {} + +type T0 int +type t1 string diff --git a/lectures/01-basics/flag/main.go b/lectures/01-basics/flag/main.go new file mode 100644 index 0000000..ac92c66 --- /dev/null +++ b/lectures/01-basics/flag/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "flag" + "fmt" + "strings" +) + +var ( + n = flag.Bool("n", false, "omit trailing newline") + sep = flag.String("s", " ", "separator") +) + +func main() { + flag.Parse() + fmt.Print(strings.Join(flag.Args(), *sep)) + if !*n { + fmt.Println() + } +} diff --git a/lectures/01-basics/github/github.go b/lectures/01-basics/github/github.go new file mode 100644 index 0000000..b68d324 --- /dev/null +++ b/lectures/01-basics/github/github.go @@ -0,0 +1,32 @@ +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" +) + +type IssuesSearchResult struct{} + +// SearchIssues queries the GitHub issue tracker. +func SearchIssues(terms []string) (*IssuesSearchResult, error) { + q := url.QueryEscape(strings.Join(terms, " ")) + resp, err := http.Get(IssuesURL + "?q=" + q) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("search query failed: %s", resp.Status) + } + + var result IssuesSearchResult + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/lectures/01-basics/lecture.slide b/lectures/01-basics/lecture.slide new file mode 100644 index 0000000..a7e50f6 --- /dev/null +++ b/lectures/01-basics/lecture.slide @@ -0,0 +1,700 @@ +Базовые конструкции языка +Лекция 2 + +Фёдор Короткий + +* Имена + +25 ключевых слов. + + break default func interface select + case defer go map struct + chan else goto package switch + const fallthrough if range type + continue for import return var + +* Predeclated identifiers + +Константы + + true false iota nil + +Типы + + int int8 int16 int32 int64 + uint uint8 uint16 uint32 uint64 uintptr + float32 float64 complex128 complex64 + bool byte rune string error + +Функции + + make len cap new append copy close delete + complex real imag + panic recover + +Идентификаторы можно переопределять по обычным правилам + + // just example, don't do this + var true = false + +* Exported identifiers + +Регистр первой буквы определяет видимость имени за пределами пакета + +.play exported/example.go + +Пакеты всегда называют в нижнем регистре и без подчеркиваний + + fmt + grpcmiddleware + +* Declaration + +- 4 типы объявлений `var`, `const`, `type`, `func` +- Пакет состоит из множества `.go` файлов +- Порядок объявлений в коде не важен + +* var + +Общая форма + + var name type = expression + +Примеры + + var i, j, k int // int, int, int + var b, f, s = true, 2.3, "four" // bool, float64, string + var f, err = os.Open(name) // os.Open returns a file and an error + +* short variable declaration + + i, j := 0, 1 + +Существующим переменным присваиваются новые значения + + in, err := os.Open(infile) + // ... + out, err := os.Create(outfile) + +Но должна объявляться хотябы одна новая переменная + + f, err := os.Open(infile) + // ... + f, err := os.Create(outfile) // compile error: no new variables + +* pointers + + x := 1 + p := &x // p, of type *int, points to x + fmt.Println(*p) // "1" + *p = 2 + fmt.Println(x) + +* escape analysis + + var p = f() + + func f() *int { + v := 1 + return &v + } + +* flag + +.play flag/main.go + +* new + +`new(T)` создаёт новую переменную с типом `T`. + + func newInt() *int { + return new(int) + } + + func newInt() *int { + var dummy int + return &dummy + } + +* zero size type + + p := new(int) + q := new(int) + fmt.Println(p == q) // "false" + + p := new(struct{}) + q := new(struct{}) + fmt.Println(p == q) // "true" or "false", depending on implementation + +* variable lifetime + +Память освобождается, после того как переменая становится недостижимой. + +Компилятор может переместить переменную со стека на кучу. + + var global *int + func f() { + var x int + x = 1 + global = &x + } + +И с кучи на стек. + + func g() { + y := new(int) + *y = 1 + } + +* type declaration + + type name underlying-type + +.play tempconv0/conv.go + +* packages + +- Файл `tempconv/types.go` + +.play tempconv/tempconv.go + +- Файл `tempconv/conv.go` + +.play tempconv/conv.go + +* packages + + fmt.Printf("Brrrr! %v\n", tempconv.AbsoluteZeroC) + + fmt.Println(tempconv.CToF(tempconv.BoilingC)) + +* package initialization + + var a = b + c // a initialized third, to 3 + var b = f() // b initialized second, to 2, by calling f + var c = 1 // c initialized first, to 1 + + func f() int { return c + 1 } + +* package initialization + +.play popcount/popcount.go + +* scope + +.play scope/scope.go + +* scope + +.play scope/if.go + +* scope if + +- Неправильно + + if f, err := os.Open(fname); err != nil { // compile error: unused: f + return err + } + f.ReadByte() // compile error: undefined f + f.Close() // compile error: undefined f + +- Правильно + + f, err := os.Open(fname) + if err != nil { + return err + } + f.ReadByte() + f.Close() + +* scope + + var cwd string + + func init() { + cwd, err := os.Getwd() // NOTE: wrong! + if err != nil { + log.Fatalf("os.Getwd failed: %v", err) + } + log.Printf("Working directory = %s", cwd) + } + +* strings + +`string` - неизменяемая последовательность байт. + +`s[i]` - обращается к i-тому байту (не символу). + + var s = "hello" + + var doc = `Go is a tool for managing Go source code. + + Usage: + go command [arguments] + ... + ` + +* unicode + +Символы кодируются числами. + + type rune int32 + +Кодировка utf8 + + 0xxxxxx runes 0−127 + 11xxxxx 10xxxxxx 128−2047 + 110xxxx 10xxxxxx 10xxxxxx 2048−65535 + 1110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536−0x10ffff + +Разница между рунами и байтами + + import "unicode/utf8" + + func countRunes() { + s := "Hello, 世界" + fmt.Println(len(s)) // "13" + fmt.Println(utf8.RuneCountInString(s)) // "9" + } + +* utf8 + + for i := 0; i < len(s); { + r, size := utf8.DecodeRuneInString(s[i:]) + fmt.Printf("%d\t%c\n", i, r) + i += size + } + +Декодирование utf8 встроено в язык + + for i, r := range "Hello, 世界" { + fmt.Printf("%d\t%q\t%d\n", i, r, r) + } + + runes := []rune("Hello, 世界") + s := string(runes) + +Некорректный байт превращается _unicode_replacement_character_ `'\uFFFD'`. + +- Может ли строка `string([]rune(s))` быть больше `s`? + +* stdlib + +- `strings` - HasSuffix, Split, Join, etc. +- `bytes` - аналог `strings` для `[]byte`. +- `unicode` - IsDigit, IsLetter. +- `strconv` - конвертация между строкой и `int`, `float`. +- `path` - работа с unix путями +- `filepath` - работа с путями текущей платформы + +* []byte + + s := "abc" + b := []byte(s) + s2 := string(b) + +* bytes.Buffer + + func intsToString(values []int) string { + var buf bytes.Buffer + buf.WriteByte('[') + for i, v := range values { + if i > 0 { + buf.WriteString(", ") + } + fmt.Fprintf(&buf, "%d", v) + } + buf.WriteByte(']') + return buf.String() + } + +* constants + + const ( + a = 1 + b + c = 2 + d + ) + + fmt.Println(a, b, c, d) // "1 1 2 2" + + type Weekday int + const ( + Sunday Weekday = iota + Monday + Tuesday + ) + + type Flags uint + const ( + FlagUp Flags = 1 << iota // is up + FlagBroadcast // supports broadcast access capability + FlagLoopback // is a loopback interface + FlagPointToPoint // belongs to a point-to-point link + FlagMulticast // supports multicast access capability + ) + +* untyped constants + + const ( + _ = 1 << (10 * iota) + KiB + MiB + GiB + TiB // (exceeds 1 << 32) + PiB + EiB + ZiB // (exceeds 1 << 64) + YiB + ) + +- Компилятор гарантирует как минимум 256 бит точности + + fmt.Println(YiB/ZiB) // "1024" + +* arrays + + var a [3]int + var q [3]int = [3]int{1, 2, 3} + var r [3]int = [3]int{1, 2} + + d := [...]int{1, 2, 3} + h := [...]int{99: -1} + +- arrays are values + + func zero(ptr *[32]byte) { + *ptr = [32]byte{} + } + +* slices + +- slice это тройка `data`, `len`, `cap`. + + s := make([]int, 10) + s = s[:0] + s = s[:10] + +* slices + + // reverse reverses a slice of ints in place. + func reverse(s []int) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } + } + +* slices + + var a, b []string + fmt.Println(a == b) + + var c []int + d := []int{} + fmt.Println(c != d) + + // c == nil && d != nil + // len(c) == 0 && len(d) == 0 + +* slices + + make([]T, len) // len == cap + make([]T, len, cap) + + func append(s []T, elem ...T) []T + + var s []int + s = append(s, 1) + s = append(s, 2, 3) + + var a, b []int + a = append(a, b...) + +* stack + + stack = append(stack, v) // push v + + top := stack[len(stack)-1] // top of stack + + stack = stack[:len(stack)-1] // pop + +* copy + + func remove(slice []int, i int) []int { + copy(slice[i:], slice[i+1:]) + return slice[:len(slice)-1] + } + +* maps + + ages := make(map[string]int) + + ages := map[string]int{ + "alice": 31, + "charlie": 34, + } + + ages := make(map[string]int) + ages["alice"] = 31 + ages["charlie"] = 34 + +* maps + + ages["alice"] = 32 + fmt.Println(ages["alice"]) // "32" + fmt.Println(ages["bob"]) // "0" + + delete(ages, "alice") + + ages["bob"] += 1 + ages["bob"]++ + _ = &ages["bob"] // compile error: cannot take address of map element + +* maps + + for name, age := range ages { + fmt.Printf("%s\t%d\n", name, age) + } + + var ages map[string]int + fmt.Println(ages == nil) // "true" + fmt.Println(len(ages) == 0) // "true" + fmt.Println(ages["alice"]) // "0" + ages["alice"] = 21 // panic + +* maps + + age, ok := ages["bob"] + if !ok { /* "bob" is not a key in this map; age == 0. */ } + + if age, ok := ages["bob"]; !ok { /* ... */ } + +* set + + var s0 map[string]bool + var s1 map[string]struct{} + +* struct + + type Employee struct { + ID int + Name string + Address string + } + + var dilbert Employee + + dilbert.Salary -= 5000 // demoted, for writing too few lines of code + +* struct + + type tree struct { + value int + left, right *tree + } + +* struct + + type Point struct{ X, Y int } + p := Point{1, 2} + p := Point{X: 1, Y: 2} + +* comparing structs + + type Point struct{ X, Y int } + p := Point{1, 2} + q := Point{2, 1} + fmt.Println(p.X == q.X && p.Y == q.Y) // "false" + fmt.Println(p == q) // "false" + +Можно использовать структуры как ключи + + type address struct { + hostname string + port int + } + + hits := make(map[address]int) + hits[address{"golang.org", 443}]++ + +* struct embedding + + type Point struct { + X, Y int + } + + type Circle struct { + Point + Radius int + } + + c := Circle{ + Point: Point{X: 10, Y: 10}, + Radius: 1, + } + + c.X = 0 + +* json + + type Movie struct { + Title string + Year int `json:"year"` + Color bool `json:"color,omitempty"` + Actors []string + } + +- marshal + + data, err := json.Marshal(movies) + if err != nil { + log.Fatalf("JSON marshaling failed: %s", err) + } + fmt.Printf("%s\n", data) + + data, err := json.MarshalIndent(movies, "", " ") + if err != nil { + log.Fatalf("JSON marshaling failed: %s", err) + } + fmt.Printf("%s\n", data) + +* unmarshal + + var movie Movie + if err := json.Unmarshal(data, &movie); err != nil { + log.Fatalf("JSON unmarshaling failed: %s", err) + } + fmt.Println(movie) + +* github + +.play github/github.go /func Search/,/^}/ + +* functions + + func name(parameter-list) (result-list) { + body + } + +Примеры + + func hypot(x, y float64) float64 { + return math.Sqrt(x*x + y*y) + } + fmt.Println(hypot(3, 4)) // "5" + + func f(i, j, k int, s, t string) { /* ... */ } + func f(i int, j int, k int, s string, t string) { /* ... */ } + +* multiple return values + + func Get(url string) (*http.Responce, error) { + // ... + if err != nil { + return nil, err + } + // ... + } + + func logAndGet(url string) (*http.Responce, error) { + log.Printf("logAndGet %s", url) + return Get(url) + } + +* named return values + +.play countwords/main.go /func Count/,/^}/ + +* errors + + resp, err := http.Get(url) + if err != nil { + return nil, err + } + +Дополнительный контекст + + doc, err := html.Parse(resp.Body) + if err != nil { + return nil, fmt.Errorf("parsing %s as HTML: %w", url, err) + } + +Текст ошибки должен быть в lowercase. + + genesis: crashed: no parachute: G-switch failed: bad relay orientation + +* EOF + + package io + + import "errors" + + // EOF is the error returned by Read when no more input is available. + var EOF = errors.New("EOF") + + in := bufio.NewReader(os.Stdin) + for { + r, _, err := in.ReadRune() + if err == io.EOF { + break // finished reading + } + + if err != nil { + return fmt.Errorf("read failed: %v", err) + } + // ...use r... + } + +* variadic functions + + func sum(vals ...int) int { + total := 0 + for _, val := range vals { + total += val + } + return total + } + + fmt.Println(sum()) // "0" + fmt.Println(sum(3)) // "3" + fmt.Println(sum(1, 2, 3, 4)) // "10" + + values := []int{1, 2, 3, 4} + fmt.Println(sum(values...)) + fmt.Println(sum(0, values...)) // compilation error + +* function values + + func Inc(i int) int { return i + 1 } + + var f func(i int) int + if f != nil { + f = Inc + } + + f = func(i int) int { + return i * 2 + } + +* recursion + + type Node struct { + V int + L, R *Node + } + + func PrintAll(w io.Writer, root *Node) { + var visit func(n *Node) + visit = func(n *Node) { + fmt.Fprintln(w, n.V) + if n.L != nil { + visit(n.L) + } + if n.R != nil { + visit(n.R) + } + } + + visit(w, root) + } diff --git a/lectures/01-basics/popcount/popcount.go b/lectures/01-basics/popcount/popcount.go new file mode 100644 index 0000000..488b5de --- /dev/null +++ b/lectures/01-basics/popcount/popcount.go @@ -0,0 +1,22 @@ +package popcount + +// pc[i] is the population count of i. +var pc [256]byte + +func init() { + for i := range pc { + pc[i] = pc[i/2] + byte(i&1) + } +} + +// PopCount returns the population count (number of set bits) of x. +func PopCount(x uint64) int { + return int(pc[byte(x>>(0*8))] + + pc[byte(x>>(1*8))] + + pc[byte(x>>(2*8))] + + pc[byte(x>>(3*8))] + + pc[byte(x>>(4*8))] + + pc[byte(x>>(5*8))] + + pc[byte(x>>(6*8))] + + pc[byte(x>>(7*8))]) +} diff --git a/lectures/01-basics/scope/if.go b/lectures/01-basics/scope/if.go new file mode 100644 index 0000000..03369cd --- /dev/null +++ b/lectures/01-basics/scope/if.go @@ -0,0 +1,16 @@ +package main + +import "fmt" + +func f() int { return 0 } +func g(x int) int { return x } + +func example() { + if x := f(); x == 0 { + fmt.Println(x) + } else if y := g(x); x == y { + fmt.Println(x, y) + } else { + fmt.Println(x, y) + } +} diff --git a/lectures/01-basics/scope/scope.go b/lectures/01-basics/scope/scope.go new file mode 100644 index 0000000..240136a --- /dev/null +++ b/lectures/01-basics/scope/scope.go @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + x := "hello" + for _, x := range x { + x := x + 'A' - 'a' + fmt.Printf("%c", x) // "HELLO" (one letter per iteration) + } +} diff --git a/lectures/01-basics/tempconv/conv.go b/lectures/01-basics/tempconv/conv.go new file mode 100644 index 0000000..33da8c6 --- /dev/null +++ b/lectures/01-basics/tempconv/conv.go @@ -0,0 +1,4 @@ +package tempconv + +func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) } +func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) } diff --git a/lectures/01-basics/tempconv/tempconv.go b/lectures/01-basics/tempconv/tempconv.go new file mode 100644 index 0000000..3bdaf40 --- /dev/null +++ b/lectures/01-basics/tempconv/tempconv.go @@ -0,0 +1,13 @@ +package tempconv + +import "fmt" + +type Celsius float64 +type Fahrenheit float64 + +const ( + AbsoluteZeroC Celsius = -273.15 +) + +func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } +func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) } diff --git a/lectures/01-basics/tempconv0/conv.go b/lectures/01-basics/tempconv0/conv.go new file mode 100644 index 0000000..253b23b --- /dev/null +++ b/lectures/01-basics/tempconv0/conv.go @@ -0,0 +1,13 @@ +package tempconv + +type Celsius float64 +type Fahrenheit float64 + +const ( + AbsoluteZeroC Celsius = -273.15 + FreezingC Celsius = 0 + BoilingC Celsius = 100 +) + +func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) } +func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }